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:
Here is everything I've tried, and what I know:
HasPrivateKey
= truePreAuthenticate = true
in the request object. Did not make a differenceServicePointManager.Expect100Continue = false
, did not make a differenceServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3
, but apparently the remote server requires TLS so that's not helpingServicePointManager.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 delegateI 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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With