grpc.net-6.0tls1.2

TLS 1.2 handshake fails on Windows Server 2012 R2


We created a .NET 6 gRPC server that uses the Kestrel web server and a server certificate for HTTPS. We also created a .NET 6 gRPC client that connects to the aforementioned server and supplies a client certificate for authentication.

We successfully tested both the client and the server on Windows 10 and Windows Server 2016. We're now deploying the client and server to Windows Server 2012 R2 servers and are running into TLS 1.2 issues.

Using Wireshark, we can see the client (running on Server 2012 R2) send a TLSv1.2 "Client Hello" to begin the handshake. The server (running on a different Server 2012 R2 server) immediately responds with a TLSv1.2 fatal alert that has a description of "Protocol Version (70)". According to this article from Microsoft, that alert means "The protocol version the client attempted to negotiate is recognized, but not supported. For example, old protocol versions might be avoided for security reasons."

We have explicitly disabled via registry TLS 1.0 and 1.1, but not TLS 1.2. According to this article from Microsoft, TLS 1.2 is enabled by default on Windows Server 2012 R2. I even confirmed that neither of the following registry keys are present, which could disable TLS 1.2 if specified and set to 1 and 0 respectively:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client\DisabledByDefault HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client\Enabled

That leads me to believe that the cipher suites could be the problem. This is where my very limited understanding of TLS tapers off. I can see in the Wireshark capture that these are the cipher suites passed from the client:

Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (0xc028)
Cipher Suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (0x009f)
Cipher Suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (0x009e)
Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d)
Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 (0xc023)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (0xc027)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA256 (0x003d)
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA256 (0x003c)
Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 (0xc024)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
Cipher Suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 (0x006a)
Cipher Suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 (0x0040)
Cipher Suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA (0x0038)
Cipher Suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA (0x0032)

On the server, in gpedit.msc > Computer Configuration > Administrative Templates > Network > SSL Configuration Settings, I can see that the "SSL Cipher Suite Order" setting state is not configured, so it should use the factory default cipher suite order. According to the description of the setting, the default order should be the following:

TLS 1.2 SHA256 and SHA384 cipher suites:
    TLS_RSA_WITH_AES_128_CBC_SHA256
    TLS_RSA_WITH_AES_256_CBC_SHA256
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P384
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P521
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P256
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P521
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256_P256
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256_P384
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256_P521
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384_P384
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384_P521
    TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
    TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
    TLS_RSA_WITH_NULL_SHA256
TLS 1.2 ECC GCM cipher suites:
    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256_P256
    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256_P384
    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256_P521
    TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384_P384
    TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384_P521

There are four SHA256 cipher suites that exist in both the client and the server (TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, and TLS_DHE_DSS_WITH_AES_128_CBC_SHA256), so I believe that means the TLS negotiation should find a match and use it.

That leaves me stumped as to what else could be the problem. Any suggestions would be greatly appreciated.


Solution

  • My suspicion about cipher suites was correct. Per this article:

    Kestrel has limited support for HTTP/2 on Windows Server 2012 R2 and Windows 8.1. Support is limited because the list of supported TLS cipher suites available on these operating systems is limited. A certificate generated using an Elliptic Curve Digital Signature Algorithm (ECDSA) may be required to secure TLS connections.

    Edit: here is the code we used to get this to work on Server 2012 R2. This must be done before starting the web app.

    'The following line is needed to allow HTTP/2 to work on Server 2012 R2.  See the following links for more info:
    'https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/5.0/kestrel-disables-http2-over-tls
    'https://github.com/dotnet/aspnetcore/issues/23068
    'https://learn.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-8-1
    'https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/http2?view=aspnetcore-6.0  'note that this link states that "A certificate generated using an Elliptic Curve Digital Signature Algorithm (ECDSA) may be required to secure TLS connections."  But upon testing that is not the case.
    Try
       AppContext.SetSwitch("Microsoft.AspNetCore.Server.Kestrel.EnableWindows81Http2", True)
    Catch ex As Exception
       ExceptionLogger.LogException(New Exception("Unable to set the 'Microsoft.AspNetCore.Server.Kestrel.EnableWindows81Http2' switch to allow HTTP/2 to work on Server 2012 R2.", ex))
    End Try