I am trying to create a TLS1.1/TLS1.2 server using .Net's sslStream class. It appears that by default the only cipher suites that this stream accepts are:
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
I'd like to enable non-ECDHE versions of these (i.e. TLS_RSA_WITH_AES_128_CBC_SHA256). I googled around a bit and people talk about changing cipher suites by modifying the default SChannel settings -- either through the "SSL Cipher Suite Order" or using CNG functions: http://msdn.microsoft.com/en-us/library/windows/desktop/bb870930(v=vs.85).aspx
However, I tried this and I can't get anything to work. Using the above link's C++ code to list the enabled cipher suites with BCryptEnumContextFunctions()
shows that the cipher suites I want are enabled by default. I even added TLS_RSA_WITH_AES_128_CBC_SHA256 as a top priority suite, and sslStream still refuses a TLS connection from a client that only supports that cipher (Exception: "The client and server cannot communicate, because they do not possess a common algorithm") Any idea what is going on here?
(By the way, if my client supports one of the ECDHE cipher suites, everything works great)
How are other folks implementing TLS in .Net 4.5? Should I be looking at opensource solutions? What about a wrapper for SChannel to use the CNG api more directly?
I contacted Microsoft's technical support and after using their proprietary tracing ability, it turned out that the certificate I had installed on my server did not have it's private key marked as an "exchange key". Apparently the private key counterpart of every public key in the certificate store has certain uses for which it it is allowed. In my case, the private key was only allowed to be used for signatures and was not allowed to be used for encrypting a symmetric key during the SSL/TLS handshake. This meant that my server could only support ECDHE cipher suites.
It also turns out that you can't check the enabled uses of a private key in the Certificate MMC snap-in. Making matters worse, using the sslStream class, there is also no way of determining any information for a handshake failure beyond the generic exception "The client and server cannot communicate, because they do not possess a common algorithm".
The final thing to mention is how I managed to install a server certificate with a restricted private key in the first place. It turns out that I generated it that way. I was using the CertEnroll COM interface to programmatically generate a certificate signing request which I exported, had a certificate authority sign, and installed the certificate authority's response. The C# code that I used to generate the certificate signing request accidentally created a private key that was only enabled for signature use.
From my experience, the CertEnroll interface is difficult to use and it's hard to find working examples online. So for others' reference I am including my C# code that generates a base64 encoded certificate signing request functional for SSL/TLS handshakes. In my case, the line
objPrivateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
was missing.