HttpClient certificate when hosted in IIS/Asp.NET Application not work - c#

I have a strange problem.
I need to perform an SSL request using a CER client certificate, to a server that requires authentication by that certificate.
I am using the code below:
var cert = X509Certificate.CreateFromCertFile("cert.cer");
var handler = new WebRequestHandler();
handler.ClientCertificates.Add(cert);
var http_client = new HttpClient(handler);
http_client.BaseAddress = new Uri("https://service.com/");
var str_json = JsonConvert.SerializeObject(new
{
Field = "Value1",
Fiesl2 = "Value2"
});
var byteContent = new ByteArrayContent(Encoding.UTF8.GetBytes(str_json));
byteContent.Headers.Remove("Content-Type");
byteContent.Headers.Add("Content-Type", "application/json");
var res = http_client.PostAsync("ResourcePath", byteContent).Result;
res.EnsureSuccessStatusCode(); //THe error 401 ocurrs here
var res_body = res.Content.ReadAsStringAsync().Result;
This code works perfectly when I squeeze into a ConsoleApplicaiton or a WebApplication in IIS Express.
But when I squeeze exactly the same code in Local IIS or IIS Server, I get the 401-Unauthorized error. The strange thing is that using Fiddler, in this case I can not even see the request attempt.
I've already checked that path is not the problem.
The problem occours in .NET 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1 and etc..
Can anyone help me out, is it any configuration that should be performed in IIS. I've researched a lot, but I did not find that specific error.

A .cer file at client side does not contain private key, so usually it won't work if mutual SSL/TLS is required by the server. You need to get a valid certificate with private key (usually a .pfx file).

Related

RestSharp isn't consistent when calling wordpress/woocomerce site

In my dev environment, I have a wordpress/woocommerce site locally all hosted under IIS.
Using the following code restsharp works:
var fullEndPoint = string.Concat(site, "/wp-json/wc/v3/", endpoint)
var client = new RestSharp.RestClient(fullEndPoint)
{
Authenticator = OAuth1Authenticator.ForProtectedResource(consumerkey, woosecret, "", "")
};
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddJsonBody(serializedData);
client.Execute(request);
When I change the fullEndPoint, consumerkey and woosecret to be our main test site which mirrors our live site (running on apache/linux), I receive
Sorry you are not allowed to create Resource.
If I post to our test site the same data using postman, it goes through and works.
For the purposes of testing, if I then change the above declaration of client to be:
var client = new RestClient("https://wordpress.mytestsite.com/wp-json/wc/v3/products?oauth_consumer_key=ck_a24ad9ddea2d9fe71d9172c415fd51b4dc83a6dc&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1627038994&oauth_nonce=T9gfeZGdmNx&oauth_version=1.0&oauth_signature=OJB1TBpLpA%2Bet0A%2FDFbozOT9nf8%3D");
where fullendpoint is the code shown in postman's code windows for Restsharp, again the code works, so I know the error is misleading. So Why doesn't restsharp work in the initial way for me when I point to my test site?
Edit:
If I change client such that I'm now setting it up as:
var client = new RestSharp.RestClient(fullEndPoint)
{
Authenticator = OAuth1Authenticator.ForClientAuthentication(consumerkey, woosecret, username, password)
};
where username and password equal the user set up in woo commerce against the consumer key for read/write access, I still receive an access denied error

Request.HttpContext.Connection.ClientCertificate is always null

I have an ASP.Net core website deployed on Azure app service for Linux.
In the controller, I am trying to get the client certificate like below:
var callerCertificate = Request.HttpContext.Connection.ClientCertificate;
I always get callerCertificate as null.
I have tried await Request.HttpContext.Connection.GetClientCertificateAsync() with same result null.
My website webhost creation looks like below:
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseStartup<Startup>()
.UseSerilog();
I have also set SSL setting for the website (in Azure) as below:
The client side caller is a net462 project that uses Microsoft.Rest.CertificateCredentials to set the certificate to HTTP request.
var cred = new CertificateCredentials(_deviceCertificate)
...
await this.cred.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false);
You could try to add the certificate using HttpClient directly instead of using Microsoft.Rest.CertificateCredential.
var clientHandler = new HttpClientHandler();
clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
clientHandler.ClientCertificates.Add(_deviceCertificate);
var client = new HttpClient(clientHandler);
var result = client.GetAsync("https://yourservice").GetAwaiter().GetResult();
You may also need to configure the SSL protocol (SSL2, SSL3, TLS, etc.):
clientHandler.SslProtocols = SslProtocols.Tls;
Answering my own question:
I am able to get the client certificate from header
string clientCertFromHeader = Request.Headers["X-ARR-ClientCert"];
Though, it is still a mystery as to why Request.HttpContext.Connection.ClientCertificate is not giving the certificate.

RabbitMQ + C# + SSL

I'm trying to use C# to get RabbitMQ 3.6.2 to use SSL/TLS on Windows 7 against Erlang 18.0. I'm running into errors when I'm enabling SSL in my C# code. I have gone through the steps to set up SSL/TLS here. I've also gone through the [troubleshooting steps][2] which show turn up successful (except I couldn't do the stunnel step due to lack of knowledge of stunnel). Here's my C# code trying to connect to RabbitMQ:
var factory = new ConnectionFactory()
{
// NOTE: guest username ONLY works with HostName "localhost"!
//HostName = Environment.MachineName,
HostName = "localhost",
UserName = "guest",
Password = "guest",
};
// Without this line, RabbitMQ.log shows error: "SSL: hello: tls_handshake.erl:174:Fatal error: protocol version"
// When I add this line to go to TLS 1.2, .NET throws an exception: The remote certificate is invalid according to the validation procedure.
// https://stackoverflow.com/questions/9983265/the-remote-certificate-is-invalid-according-to-the-validation-procedure:
// Walked through this tutorial to add the client certificate as a Windows Trusted Root Certificate: http://www.sqlservermart.com/HowTo/Windows_Import_Certificate.aspx
factory.Ssl.Version = SslProtocols.Tls12;
factory.Ssl.ServerName = "localhost"; //System.Net.Dns.GetHostName();
factory.Ssl.CertPath = #"C:\OpenSSL-Win64\client\keycert.p12";
factory.Ssl.CertPassphrase = "Re$sp3cMyS3curi1ae!";
factory.Ssl.Enabled = true;
factory.Port = 5671;
// Error: "The remote certificate is invalid according to the validation procedure."
using (var connection = factory.CreateConnection())
{
}
There's a StackOverflow post regarding the "The remote certificate is invalid according to the validation procedure." exception, but the hack fix doesn't seem to take effect as the callback method suggested is never called. I think that I've added my certificate generated via OpenSSL to the Windows Trusted Root Certification Authorities certificates list for local computer. So I'm at a loss here. Any ideas on how to proceed?
Edit: Here's the final working code for anyone struggling to implement SSL on Rabbit:
var factory = new ConnectionFactory();
factory.HostName = ConfigurationManager.AppSettings["rabbitmqHostName"];
factory.AuthMechanisms = new AuthMechanismFactory[] { new ExternalMechanismFactory() };
// Note: This should NEVER be "localhost"
factory.Ssl.ServerName = ConfigurationManager.AppSettings["rabbitmqServerName"];
// Path to my .p12 file.
factory.Ssl.CertPath = ConfigurationManager.AppSettings["certificateFilePath"];
// Passphrase for the certificate file - set through OpenSSL
factory.Ssl.CertPassphrase = ConfigurationManager.AppSettings["certificatePassphrase"];
factory.Ssl.Enabled = true;
// Make sure TLS 1.2 is supported & enabled by your operating system
factory.Ssl.Version = SslProtocols.Tls12;
// This is the default RabbitMQ secure port
factory.Port = 5671;
factory.VirtualHost = "/";
// Standard RabbitMQ authentication (if not using ExternalAuthenticationFactory)
//factory.UserName = ConfigurationManager.AppSettings["rabbitmqUsername"];
//factory.Password = ConfigurationManager.AppSettings["rabbitmqPassword"];
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
// publish some messages...
}
}
Thanks,
Andy
Usual problem is mismatch between what you provide in Ssl.ServerName and host SSL certificate was issued for.
Also note that server-side SSL (encrypted connection between your client and server) and client-side authentication with certificate (you provide server with information which confirms that you have certificate it expects) are two different things. By providing Ssl.CertPath you intent to authorize at server using this certificate, which might or might not be what you want.
My problem was related to using self signed certificates. I had to add the SslOption AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateNameMismatch |
SslPolicyErrors.RemoteCertificateChainErrors
In the example connection factory creation code sslEnabled is true.
new ConnectionFactory()
{
Uri = uri,
ClientProvidedName = clientProvidedName,
AutomaticRecoveryEnabled = true,
Ssl = new SslOption(){
Enabled = sslEnabled,
AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateNameMismatch |
SslPolicyErrors.RemoteCertificateChainErrors} ,
NetworkRecoveryInterval = TimeSpan.FromSeconds(networkRecoveryIntervalSecs)
}
It can be done as simple as this
const string RabbitMqServerHostname = "myserver.northeurope.cloudapp.azure.com";
var factory = new ConnectionFactory()
{
HostName = RabbitMqServerHostname,
UserName = "myuser",
Password = "mypassword",
// The settings below turn on SSL
Port = 5671,
Ssl = new SslOption
{
Enabled = true,
ServerName = RabbitMqServerHostname
}
};
I just went through a similar frustrating exercise with the .NET 4.5 client (v. 3.6.6) and the RabbitMQ broker/service on Windows (v. 3.6.6, Erlang 19.2).
The correct combination of RabbitMQ config file options and client settings is not intuitive and the client factory object has changed since the documentation was last updated. Now there's an SslOption class.
Are you still having problems? Perhaps I can help you.
I've resolved the problem changing only the Ssl.ServerName to the Common Name (CN) of the issued certificate, because it was different of the server which hosts the service.
factory.Ssl.ServerName = "[certificate cn]";
I had tried with python (because the provider used that language) and it worked, I suppose then that Python doesn't validate that features of the certificates (it's more insecure?).

UWP app HttpClient HTTPS client certificate problems

I'm writing a UWP app in C# that is eventually destined for IoT, but right now I've only been debugging locally. I'm using Windows.Web.Http.HttpClient to connect to a self-hosted WCF REST web service that I've also written and have running as a Console app on the same machine for testing. The service requires mutual authentication with certificates, so I have a CA cert, service cert, and client cert.
My UWP code works like this:
Check app cert store for client cert and CA cert installed.
If not, install from PFX file and CER file, respectively.
Attach the Certificate to the HttpBaseProtocolFilter and add the filter to the HttpClient
Call the HttpClient.PostAsync
After I call PostAsync I get the following error: An Error Occurred in the Secure Channel Support. After plenty of searching online, and by common sense, I'm pretty sure HttpClient is barfing because of a problem establishing the mutually-authenticated SSL connection. But based on my troubleshooting I can't figure why.
To troublshoot further, I've written a plain old Console app using System.Net.Http.HttpClient, attached the client certificate to the request and everything works great. Sadly, System.Net isn't fully supported on UWP. I've also tried NOT attaching the certificate to the UWP HttpClient and the app prompts me with a UI to select an installed certificate. I select the correct cert and still get the same exception (this at least lets me know the cert is installed correctly and validating properly with the CA from the app's perspective). In additon, I hit the GET on the web service from a browser, select the client cert when prompted, and am able to download a file.
I've tried using Fiddler and, I assume because of the way it proxies traffic, it seems to work a little bit further, except my web service rejects the request as Forbidden (presumably because Fiddler is not including the correct client cert in the request). I haven't hit up Wireshark yet because it's a pain to get Wireshark to work using localhost on Windows.
My next step is to start changing the web service to not require client authentication and see if that is the problem.
Two questions: Why is Windows.Web.Http.HttClient not working in this case? And, less important, any recommendations on good HTTP monitoring tools to help me debug this further?
This MSDN post proved to have the answer. Seems like an oversight on MS part requiring a separate, meaningless call to the API beforehand. Oh well.
http://blogs.msdn.com/b/wsdevsol/archive/2015/03/26/how-to-use-a-shared-user-certificate-for-https-authentication-in-an-enterprise-application.aspx
Excerpt from the article:
However, the security subsystem requires user confirmation before allowing access to a certificates private key of a certificate stored in the shared user certificates store. To complicate matters, if a client certificate is specified in code then the lower level network functions assume the application has already taken care of this and will not prompt the user for confirmation.
If you look at the Windows Runtime classes related to certificates you won’t find any method to explicitly request access to the certificate private key, so what is the app developer to do?
The solution is to use the selected certificate to 'Sign' some small bit of data. When an application calls CryptographicEngine.SignAsync, the underlying code requests access to the private key to do the signing at which point the user is asked if they want to allow the application to access the certificate private key. Note that you must call 'Async' version of this function because the synchronous version of the function: Sign, uses an option that blocks the display of the confirmation dialog.
For example:
public static async Task<bool> VerifyCertificateKeyAccess(Certificate selectedCertificate)
{
bool VerifyResult = false; // default to access failure
CryptographicKey keyPair = await PersistedKeyProvider.OpenKeyPairFromCertificateAsync(
selectedCertificate, HashAlgorithmNames.Sha1,
CryptographicPadding.RsaPkcs1V15);
String buffer = "Data to sign";
IBuffer Data = CryptographicBuffer.ConvertStringToBinary(buffer, BinaryStringEncoding.Utf16BE);
try
{
//sign the data by using the key
IBuffer Signed = await CryptographicEngine.SignAsync(keyPair, Data);
VerifyResult = CryptographicEngine.VerifySignature(keyPair, Data, Signed);
}
catch (Exception exp)
{
System.Diagnostics.Debug.WriteLine("Verification Failed. Exception Occurred : {0}", exp.Message);
// default result is false so drop through to exit.
}
return VerifyResult;
}
You can then modify the earlier code example to call this function prior to using the client certificate in order to ensure the application has access to the certificate private key.
Add the Certificate file your Project
Add the Certificate to the Manifested file (give file path in attachment)
the Frist Service Call of in Ur Project use to ignore the certificate validation Following Code is most Suitable for Login Function.
try
{
var filter = new HttpBaseProtocolFilter();
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.RevocationFailure);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.RevocationInformationMissing);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.WrongUsage);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.IncompleteChain);
Windows.Web.Http.HttpClient client = new Windows.Web.Http.HttpClient(filter);
TimeSpan span = new TimeSpan(0, 0, 60);
var cts = new CancellationTokenSource();
cts.CancelAfter(span);
var request = new Windows.Web.Http.HttpRequestMessage()
{
RequestUri = new Uri(App.URL + "/oauth/token"),
Method = Windows.Web.Http.HttpMethod.Post,
};
//request.Properties. = span;
string encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(Server_Username + ":" + Server_Password));
var values = new Dictionary<string, string>
{ { "grant_type", "password" },{ "username", Uname}, { "password", Pwd }};
var content = new HttpFormUrlEncodedContent(values);
request.Headers.Add("Authorization", "Basic " + encoded);
request.Content = content;
User root = new User();
using (Windows.Web.Http.HttpResponseMessage response = await client.SendRequestAsync(request).AsTask(cts.Token))
{
HttpStatusCode = (int)response.StatusCode;
if (HttpStatusCode == (int)HttpCode.OK)
{
using (IHttpContent content1 = response.Content)
{
var jsonString = await content1.ReadAsStringAsync();
root = JsonConvert.DeserializeObject<User>(jsonString);
App.localSettings.Values["access_token"] = root.Access_token;
App.localSettings.Values["refresh_token"] = root.Refresh_token;
App.localSettings.Values["expires_in"] = root.Expires_in;
var json = JsonConvert.SerializeObject(root.Locations);
App.localSettings.Values["LocationList"] = json;
App.localSettings.Values["LoginUser"] = Uname;
}
}
}
}
catch (Exception ex)
{
ex.ToString();
}

Error connecting with client certificate from smart card / etoken to WCF Service

We have a WCF service configured to work with client certificates.
Our client is a .net 3.5 WPF application.
While testing, it works perfectly with certificates generated by Microsoft CA and others.
While testing it with a physical token (Aladdin/Safenet eToken Pro 64k) it also works well.
(On first try connecting to server the “Token Logon” window pops up.
After successful authentication to the token the server request works, and the next try to connect to server succeed without the token logon message showing)
Now, if we remove the token and re-insert it, when trying to connect with the same certificate we get an error “The request was aborted: Could not create SSL/TLS secure channel”. HResult 0x80131509
The only way to make it work again is restart the application.
Certificates are retrieved with System.Security.Cryptogragpy:
var store = new X509Store("My", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
//Getting the right certificate and then:
store.Close();
The connection to server is with this code:
string uriStr = txtUrl.Text + "/rest/auth/strong/Ping";
var client = WebRequest.Create(new Uri(uriStr)) as HttpWebRequest;
client.Method = "POST";
client.ContentType = "application/text; charset=unicode;";
client.ContentLength = 0;
client.PreAuthenticate = false;
client.KeepAlive = false;
client.ClientCertificates.Add(certificate); //Cert attached to the request
using (var res = client.GetResponse() as HttpWebResponse)
{
using (var responseStream = res.GetResponseStream())
{
using (var reader = new StreamReader(responseStream))
{
string s = reader.ReadToEnd();
ShowAndWriteToFile(s);
}
}
}
I looked at Verbose logging and Wireshark captures, Couldn't find the problem from there.
Other stuff I tried:
Clearing pin code cache – when clearing it before removing token, then on next attempt to connect to server the “Token logon” window pops up.
Clearing it after remove and re-insert didn’t change anything.
Restarting the WCF Service – after the token re-inserted, still same result.
Checking how other operations on the certificate behaves before and after token remove and re-insert.
For instance, if I Sign and verify some message with the certificate after the removal and re-insert of the token then the “Token logon” window pops up again and it continues working.
Trying to connect with self-created certificate put in the store – Worked, then deleting it from store and re-adding it – still worked.

Categories