TL;DR: how do I configure an ASP.Net Core service which:
I have no problems with the first 3 things, it's just the TLS 1.0 clients I have a problem with.
I don't believe it's a problem with my service, rather the Windows OS and/or the self-signed SSL Certificate.
The certificate is being generated via a wrapper around the CERTENROLLLib dll. I can change how this is generated (key lengths and hash algorithms etc), but it's not (clearly) my area of expertise....
Multiple clients based on Windows 7 and up work fine, assuming they have the self-signed cert added as a trusted cert and/or are simply written to not reject this cert...
When clients (well, the only single client I have available to test with) supporting only TLS 1.0 try to connect, they get SSL errors, and I can see in my Windows 10 System Event logs this:
An TLS 1.0 connection request was received from a remote client application,
but none of the cipher suites supported by the client application are
supported by the server. The TLS connection request has failed.
If I create a local client on my machine and force it to use only TLS 1.0 via:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
Then my local client works fine... but then this is the machine on which the cert was generated, so it'll have available exactly the same list of ciphers etc. so this isn't a huge surprise..
I have followed various advice found online about ensuring my windows 10 install will accept TLS 1.0 connections, and believe it is set up to do so.
So I think that the problem is one of these:
Does anyone have any idea which of these it's likely to be, or any suggestions for what hash algorithms etc. I should use in the cert if it's the latter? Or if I'm way off and there's something else amiss....?
Thanks.
OK, I finally figured this out. It was actually the certificate - but NOT cipher suite related (well, that may have been part of the initial problem, but it wasn't the final piece of the puzzle)
We had a third party service available which was accessible from the XP machine over SSL. There were a number of differences with this system (hosted on Win7/IIS Vs win10/Kestrel but after eliminating these (by eventually hosting a basic site on IIS/win 7 using different certs) I eventually found that it was simply the certificate.
Careful analysis of the one working cert vs. the self-signed ones we were generating revealed a number of differences:
The last part appears to have been the real key to this. When the cert had the 'Key Usage' extension marked as critical it simply wouldn't work.
What was very odd was that even when looking at the network traffic, you never saw the server actually sending the Certificate back to the client (usually all you'd see is the client sending ClientHello
TLS packets), so I don't fully understand how this caused the issue, but it definitely was the cert.
We were originally using c# code which wrapped around the 'CERTENROLLLib' Interop library to generate certificates. It turns out there is a bug in this code somewhere, which meant even if these parts were explicitly defined as Critical=false;
then they would still end up being flagged as Critical:
var keyUsageClass = new CX509ExtensionKeyUsageClass();
keyUsageClass.InitializeEncode(
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE
| CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE
| CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE
);
//This appears to be IGNORED; maybe fixed in some later version, no idea.
keyUsageClass.Critical = false;
...
cert.InitializeFromPrivateKey(
X509CertificateEnrollmentContext.ContextMachine,
privateKey, "");
cert.X509Extensions.Add((CX509Extension)keyUsageClass);
In the end I simply used powershell. This appears to still have the problem, but with the New-SelfSignedCertificate
cmdLet it is at least possible to specify a Key Usage of None
(seems not possible via CertEnrollLib), which means this section is omitted entirely.
For reference, this is the section of powershell script I used, which creates the cert then saves it to a .pfx file for import on clients as 'trusted'
#various vars to use in the create command
$certSubject = $env:computername
$certFriendlyName= "$($certSubject)_MyCert"
$certPfxFileName = "$($certFriendlyName).pfx"
$expiryDate = (Get-Date).AddYears(10)
$flagsForServerCert = "2.5.29.37={text}1.3.6.1.5.5.7.3.1"
$certPassword = "somePassword123!"
#Note: this is one command, on one line, broken here for readability:
New-SelfSignedCertificate
-DNSName $certSubject
-FriendlyName $certFriendlyName
-certstorelocation cert:\localmachine\my
-subject $certSubject
-HashAlgorithm SHA
-NotAfter $expiryDate
-TextExtension $flagsForServerCert
-KeyUsage None
#Now we need to fetch the thumbprint of that cert. note, multiple matching Certs will
#mean this doesn't work, so ensure FriendlyName is unique in the first place.
$thumbprint=(Get-ChildItem -Path cert:\localmachine\my | Where-Object {$_.FriendlyName -match $certFriendlyName}).Thumbprint
#Now to save as a pfx file, need to create a SecureString from the password.
$pwd = ConvertTo-SecureString -String $certPassword -Force -AsPlainText
#Get the certificate, and pipe it through Export-PfxCertificate to save it.
Get-ChildItem -Path "cert:\localmachine\my\$($thumbprint)" | Export-PfxCertificate -FilePath $certPfxFileName -Password $pwd
This then generated a Server cert which XP clients are happy with.