One of the joys Ive experienced in my recent dealings with the HTTP API and some lower-level SChannel code is that of trying to generate proper SSL test certificates. During the initial development phase of the product Im currently working on, we used one common test certificate for all our test/dev servers. Later on, we began to generate different certificates for each server so that we could test some cert verification code and also verify that we knew exactly what was required of certificates to work with our product. Needless to say (or I wouldnt have anything to write here), we immediately began to have many problems. Some servers worked just fine, while others failed. On the failing servers, test requests with WebRequest yielded the familiar but unhelpful error "Could not establish secure channel for SSL/TLS". Attempts to connect from IE produced the error "Cannot find server or DNS Error". In order to further troubleshoot, I tried making a connection using a plain SChannel-based SSL socket. The connection was established just fine and the client socket sent the hello message. As soon as the client socket attempted to read the server response, an exception was thrown, indicating that the connection had been closed by the server. Next, I enabled full SChannel logging on the server and restarted. Attempts to connect left 2 messages in the event logs: "Creating an SSL server credential", followed by "The SSL server credentials private key has the following properties..." and NO error. Baffled, I installed Microsofts SSL Diagnostics utility and attempted a handshake simulation. This yielded the error "Unexpected error receiving data (0x80090304)", which error code is defined as SEC_E_INTERNAL_ERROR; again, a most unhelpful description. This led me on a week-long study of X.509 certificates, makecert.exe and the MS Crypto Providers key container implementation. Im going to omit many details here in order to get to the point, which is to enumerate the problems and solutions Ive found for our situation. Ill follow up with another post to describe the gory details of this research in some depth. Anyway, simply stated, the problem is that the certificate's private key is either inaccessible or the keyspec is incorrect.
Our problems basically boiled down to three root causes:
1 - Improper import of good certificates, which caused them to appear faulty.
2 - Use of a build of makecert.exe that generates key containers improperly
3 - Failure to realize that makecert.exe does not, by default, generate certs that use the correct keyspec for an SSL cert.
Problem #1 - The problem with importing was that I sometimes imported the certificate into the CurrentUser MY store and then, realizing that I'd imported into the wrong store, copied the cert into the LocalMachine MY store. The problem is that once a certificate is imported into a CurrentUser store, the private key container resides in the users profile, where it cannot be found by a system process.
Problem #2 - One of the first things I do when I create a development VM is to add the path to the .NET SDK bin directory to my system path. I always want access to the tools therein from any command prompt. Unfortunately, the makecert.exe in that directory (v5.131.2157.1) wont work for generating SSL certs. The problem is that even if you specify a LocalMachine store for the cert, the private key container it generates is generated as a user key, not a machine key. Therefore, though everything looks good, when you use the cert from a system process, you experience the symptoms enumerated above. In order to use this build for SSL certs, youd have to pre-generate the key container and reference the pre-created container on the makecert command line.
Problem #3 - There are two standard key types for RSA keys: signature and exchange. Which key is used is defined by the keyspec property of the private key context belonging to the certificate. The keyspec property must be set to "Exchange" in order for the cert to work for SSL. This is obvious to me, but I never realized that makecert.exe sets the keyspec to Signature by default, and there isnt a standard interface to view this property of a certificate that Im aware of.
Solution #1 - Import certificates intended for SSL usage into a LocalMachine store directly. Dont try to copy a cert from a CurrentUser store into a LocalMachine store. If youve mistakenly imported a cert into a CurrentUser store, you need to delete the cert and the key container (deleting a certificate does not delete the key container), and then import the cert into a LocalMachine store. See my next post for details as to how to locate the key container and delete it.
Solution #2 - Dont use v5.131.2157.1 of makecert.exe (the one in the .NET v1.1 SDK) to generate SSL certificates. It always generates user key containers, which wont work from a system process. Fortunately, the version of makecert.exe (v5.131.3790.0) that ships with v2.0 of the .NET SDK, as well as the latest Platform SDKs, works properly.
Solution #3 - Always use the command line option "-sky exchange" with makecert.exe if youre generating SSL certificates. If you dont specify this, it will default to the signature keyspec, which wont work for SSL.
The difficulties Ive experienced have been compounded by the fact that any one or more of these problems could be combined in a single situation, making it terribly difficult to pinpoint precisely what is going on. To aid in the diagnosis of SSL certificate problems, Im making available a little command line utility that I wrote for this purpose.
checkcert: exe source
To use checkcert.exe, pass a store name and a cert subject search string (such as the common name). Checkcert will display some key information about the cert that is not available in the built-in tools (again, that Im aware of). Most importantly, checkcert will display the private key container location and the keyspec. You want to ensure that the key container location is "Machine" and that the keyspec is "Exchange" for an SSL cert. I hope this information and the utility will save someone the hassle of trying to unravel this mess from scratch.