grinder22
grinder22

Reputation: 573

.NET dropping client certificate over TLS 1.2

Hello it seems some form of this question has been asked a million different ways but many don't have answers, or the answers don't apply to me.

We have a trivial little .NET service that calls out to a 3rd party API that is only supporting TLS 1.2 now.

        var requestHandler = new WebRequestHandler();
        var clientCert = GetClientCert("THUMBPRINT");

        requestHandler.ClientCertificates.Add(clientCert);
        var encodedHeader = "FOO";
        var httpClient = new HttpClient(requestHandler) { BaseAddress = new Uri("https://foo.bar.com/rest/api/") };
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", encodedHeader);

        var httpResponse = httpClient.GetAsync("").Result;
        var responseContent = httpResponse.Content.ReadAsStringAsync().Result;

When I explicitly set the security protocol to 1.2, or target a framework that defaults to 1.2, the client certificate is not part of the packet that goes to the server. I've examined it in Wireshark and the certs are never present over 1.2. At that point the server sends the Fatal Alert Handshake Failure (40) which results in the following .Net exception:

The request was aborted: Could not create SSL/TLS secure channel.

I can make the same call through Chrome or Postman (native) and it works fine. If I set the protocol down to 1.1 it works fine. But if I run it as 1.2 through .NET it fails every time. I get the same exact error and same traffic in Wireshark even if I purposely do not add my client certificate at all.

I ran the 3rd party endpoint through https://www.ssllabs.com/ and it grades out fine. There are no MD5 signatures anywhere that I can see on either side. Everything is signed with sha1 or sha256 with RSA encryption.

I have tracing enabled. The following output seems to indicate there is no issue finding the certificate or its private key:

System.Net Information: 0 : [19512] SecureChannel#41622463 - Certificate is of type X509Certificate2 and contains the private key.
System.Net Information: 0 : [19512] SecureChannel#41622463::.AcquireClientCredentials, new SecureCredential() (flags=(ValidateManual, NoDefaultCred, SendAuxRecord, UseStrongCrypto), m_ProtocolFlags=(Zero), m_EncryptionPolicy=RequireEncryption)
System.Net Information: 0 : [19512] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent  = Outbound, scc     = System.Net.SecureCredential)
System.Net Information: 0 : [19512] InitializeSecurityContext(credential = 
System.Net.SafeFreeCredential_SECURITY, context = 1ed7465db80:1f1d854c910, targetName = foo.bar.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [19512] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=133, returned code=ContinueNeeded).

I also have SCHANNEL event logging turned on. There I can see it picking up the client cert with the private key.

The TLS client credential's private key has the following properties:

   CSP name: Microsoft Enhanced Cryptographic Provider v1.0
   CSP type: 1
   Key name: {some hex here}
   Key Type: key exchange
   Key Flags: 0x0

 The attached data contains the certificate.

Followed by the error that corresponds to the Alert packet in Wireshark:

A fatal alert was received from the remote endpoint. The TLS protocol defined fatal alert code is 40.

I am completely out of ideas. It feels like a bug in .Net. We thought for a moment that it was an incomplete trust chain with our client certificate, because our cert package only had the client cert and an intermediate CA. But we got the Root CA from the vendor and now Windows says the cert is legit.

One interesting thing I notice in Wireshark is that we send the client cert AND the intermediate CA on successful calls. I don't know how it's picking up the intermediate CA because I only pull in the client cert to attach to the request. Why wouldn't it also submit the root CA once we had it? My gut tells me the problem may still lie in here somewhere but I can't prove it. There is nothing in any logs anywhere that indicate a trust chain failure.

I've even hooked up ProcMon as well to see if I could see anything amiss but nothing jumped out. Man I could have rewritten the whole thing in Java in the amount of time I've spent on this.

Some other things I have tried that didn't work:

targeting .Net 4.8

machine store vs user store

loading directly from the pfx instead

HttpWebRequest instead of HttpClient

TcpClient with SslStream

newer Windows 10 release

EDIT I just read something that says SHA-1 signatures were also removed from TLS 1.2 along with MD5. Our client cert is signed in SHA-1. I think this might be it and we are contacting our vendor.

Upvotes: 2

Views: 2117

Answers (1)

grinder22
grinder22

Reputation: 573

My problem and my solution ultimately ended up being the same as this one. The only difference is that my client cert was SHA1 not MD5. .NET is clearly dropping SHA1 certificates as well.

C# and dotnet 4.7.1 not adding custom certificate for TLS 1.2 calls

This article gave me the clue, because other posts indicated that SHA1 was still accepted. My SHA1 certs still worked outside of .NET.

https://tools.ietf.org/id/draft-ietf-tls-md5-sha1-deprecate-00.html

Upvotes: 1

Related Questions