Search code examples
windowshttpsssl-certificateasp.net-core-2.2

Errors with TLS 1.0 client connecting to my ASP .Net Core service


TL;DR: how do I configure an ASP.Net Core service which:

  • Is installed on Windows (7 and above, though I'm developing/testing on Windows 10)
  • Serves data over HTTPS
  • Uses a Self-signed SSL Certificate (clients will reside on the same local network as the service & be accessed via local hostname, so purchasing a cert is not practical)
  • Allows TLS1.0 clients to connect (running on Windows XP, sadly)

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:

  • My windows machine will accept TLS1.0 connections, but doesn't have whatever form of encryption cipher the windows XP client is requesting so the two can't agree; or
  • The Self-Signed Certificate is using some form of hash algorithm that the Windows XP client doesn't understand.

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.


Solution

  • The Actual Problem

    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:

    • SHA-1 vs other, stronger encryption
    • very limited other information (e.g. Subject/Issuer were simply the Hostname, no additional DNS stuff, etc.
    • The 'Key Usage' part was marked as critical on our self-signed certs, but not on the working one

    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.

    The Fix:

    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.