Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET application fails to send client certificate - Win 7 vs Win XP?

Tags:

I am developing an ASP.NET web application that sends a request to another server using HttpWebRequest. It sends the request over HTTPS, and the remote server requires a client certificate. The request fails in the .NET application, apparently unable to send the correct client certificate. I am able to successfully connect and send the client certificate if I simply visit the url with a web browser (Chrome specifically).

The code below is a simple reproduction, with just a basic GET request.

var r = WebRequest.Create(url) as HttpWebRequest;
r.ClientCertificates = new X509CertificateCollection { myX509Cert };
using (var resp = r.GetResponse() as HttpWebResponse) {
    ...
}

I get our favorite exception, "Could not create SSL/TLS secure channel". Typically, these types of problems point to issues with permissions on your certificate's private key. I tried everything I could think of to ensure this is all configured correctly, but perhaps I missed something. Long story short, the remote server is sending a TLS CertificateRequest with a list that that does seem to properly identify my client certificate, but my appliation fails to respond with any client certificate.

Here is my setup:

  • Windows 7 professional 64 bit
  • Able to reproduce the problem in an ASP.NET MVC 3 / .NET 4 application running in the Visual Studio dev server, ASP.NET WebForms / .NET 3.5 application running in local IIS, and in a .NET console application / .NET 4
  • Microsoft .NET Framework 4.5 was recently installed. Haven't examined yet whether this could be a problem

Here is everything I've tried, and what I know:

  • This code seemed to work fine when running on a Windows XP machine
  • I ensured that my client certificate is imported into the Local Computer, Personal Certificates store, with the Private Key permissions properly configured for myself and all relevant IIS users
  • I attempted reinstalling the certificate on my machine a couple of times
  • I have confirmed that the .NET application can access the X509 certificate and HasPrivateKey = true
  • Ensured that my client certificate is valid. It's actually an SSL cert for the web server on which this application will be running
  • I set PreAuthenticate = true in the request object. Did not make a difference
  • I tried setting ServicePointManager.Expect100Continue = false, did not make a difference
  • I tried setting ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3, but apparently the remote server requires TLS so that's not helping
  • I set a ServicePointManager.ServerCertificateValidationCallback delegate to always return true. But the request fails earlier in the TLS handshake, before it even gets to the point of calling this delegate
  • When I go to the URL in Chrome, it asks me to supply a client certificate, presents the correct client cert as a choice, I choose that cert and I get a valid response. Also works properly when I construct a request in Fiddler. So it definitely seems to be specifically a problem with my .NET code, not the certificate itself, or the remote server, etc.
  • The vendor I am working with has the same service set up on a different remote server, and that service requires a different client cert. So I tried the same thing with the different URL and client cert and got the same failure

I added System.Net trace and saw this:

SecureChannel#26717201 - We have user-provided certificates. The server has specified 6 issuer(s). Looking for certificates that match any of the issuers.

SecureChannel#26717201 - Left with 0 client certificates to choose from.

...

InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=CertUnknown).

I enabled full SCHANNEL logging, and saw this warning:

The remote server has requested SSL client authentication, but no suitable client certificate could be found. An anonymous connection will be attempted. This SSL connection request may succeed or fail, depending on the server's policy settings.

I ran Wireshark, and saw the remote server sent a CertificateRequest, and it seems to have a Distinguished Name entry with the CN\OU\O values of my client certificate specified exactly. After that, my application sends a Certificate response with no certificates.

It seems like I am missing something with setting up this certificate to work properly with .NET applications in Windows 7. My best guess is that there is something different on my new Windows 7 machine, compared to the XP machine, that is causing this to fail now. I don't have access to a Windows XP environment at the moment to confirm this; I will in a couple of days but would really like to resolve this ASAP.

Any ideas would be much appreciated. Thanks!

EDIT As described above, when connecting to the URL in Chrome, the browser asks me for a client certificate, and I can provide the correct certificate and connect successfully. I cannot do this successfully in Internet Explorer (9), however. I simply get "Internet Explorer cannot display the webpage", with no other prompts or explanation. I was told that this may be relevant as WebRequest.Create has similar behavior to IE. I am looking into what this might mean, but would value any thoughts on this.

EDIT Also, I should note that the remote server uses a self-signed SSL certificate. I originally thought that might be the problem, so I added the cert as a trusted root in MMC so that the certificate appears as valid on my machine. This did not fix the problem.

like image 900
Mike Mertsock Avatar asked Mar 25 '12 06:03

Mike Mertsock


People also ask

How do I enable client certificates?

On the taskbar, click Start, and then click Control Panel. In Control Panel, click Programs and Features, and then click Turn Windows Features on or off. Expand Internet Information Services, then select Client Certificate Mapping Authentication, and then click OK.

Can I use same certificate for server and client?

It's technically possible for a TLS certificate to be used as both a server certificate and a client certificate. The TLS certificate for this very site has its key usage set that way, for instance. But the server which requires a client certificate does so to authenticate the client.


1 Answers

This turned out be a rather simple problem, but it was hard to spot. The remote server that my application had my client certificate in its keystore, but not any of the root certificates in my client cert's trust chain.

I was able to use my code to successfully send a request to a different server that required client certificates. I took a capture in Wireshark while sending this successful request, and also took a capture while sending the failed request to the other server. In the Wireshark capture, I found the "Server Hello" and compared the messages sent from the remote servers. The "good" remote server was sending my client certificate and also also its root certificate in the "Certificate Request" portion of that message. The "bad" remote server was only sending my client certificate.

This reminded me that the System.Net diagnostic trace read "The server has specified 6 issuer(s) ... Left with 0 client certificates to choose from". So it turns out that "issuer" is the key term here. It was just easy to overlook initially because in my initial analysis of the TLS handshake, the server was sending my client cert in the Certificate Request. In hindsight, it makes sense that the server should be sending your root certificate, not your client certificate itself.

like image 183
Mike Mertsock Avatar answered Sep 30 '22 23:09

Mike Mertsock