1

I have two console applications to test a Mutual SSL connection with an API, one is .NET 6 the other is .NET 4.8.

Both run the following code:

X509Certificate2 certificate;

// The following Find Certificate code is from https://learn.microsoft.com/en-us/archive/msdn-magazine/2007/march/support-certificates-in-your-apps-with-the-net-framework-2-0
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try
{
    // create and open store for read-only access
    store.Open(OpenFlags.ReadOnly);

    // search store
    X509Certificate2Collection col = store.Certificates.Find(
                    X509FindType.FindBySubjectKeyIdentifier, "my_subject_key_id", true);

    // return first certificate found
    certificate = col[0];
}
// always close the store
finally { store.Close(); }

var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
handler.ClientCertificates.Add(certificate);    

var httpClient = new HttpClient(handler);

var content = new Dictionary<string, string>();
content.Add("grant_type", "client_credentials");
content.Add("client_id", "123");
content.Add("client_secret", "abc");
content.Add("scope", "MyScope");
                
var encodedContent = new FormUrlEncodedContent(content);
encodedContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded");

HttpResponseMessage tokenResponse = await httpClient.PostAsync("https://sample.com/api/v1/conf/oauth2/token",
                    encodedContent);

The .NET 6 application runs and the SSL/TLS connection succeeds with no problem, but the .NET 4.8 Framework console app, while it seemingly gets the certificate fine in code (including the private key), and the system.diagnostics tracing logs seem to show the certificate being sent (I see the byte data for it in those logs), the Wireshark logs show the certificate as missing.

EDIT: One thing that's different between the 4.8 and 6 calls is that the Private Key on the handler in .NET 6 has as KeyExchangeAlgorithm and SignatureAlgorithm of "RSA" while the .NET 4.8 Private Key has a KeyExchangeAlgorithm of "RSA-PKCS1-KeyEx" and SignatureAlgorithm of "http://www.w3.org/2000/09/xmldsig#rsa-sha1".

The SHA1 seems out of place since in Windows the Signature Algorithm for the cert says sha256RSA SHA256RSA Signature Algorithm

Any ideas why this works in .NET 6 but not .NET 4.8? Any suggestions welcome. Thanks!

EDIT: Since this is apparently a relatively popular question, for posterity I have to say that I never figured this out. But since I needed to make the connection work with both frameworks, as a workaround, I opened up an internal API between the two and called the working .NET 6 API from the .NET 4.8 solution. I'm still interested if anyone else has figure this out, though.

djeastm
  • 158
  • 2
  • 8
  • 1
    Possibly relevant: https://stackoverflow.com/a/49175003/14868997. Check that `certificate.PrivateKey` is not null, and maybe try creating your own `X509Chain` and adding all its certificates. Not relevant to the question, but `GetAwaiter().GetResult()` and can easily cause a deadlock. You should use `await` instead. – Charlieface Jul 21 '22 at 20:10
  • @Charlieface. Thanks for the comment! The GetAwaiter was just for convenience right now thanks. The certificate.PrivateKey property of the cert isn't null after getting from the store, it's an RSACryptoServiceProvider with KeyExchangeAlgorithm of "RSA-PKCS1-KeyEx". I'm not sure what you mean by creating my own X509Chain. Could you expand on that? – djeastm Jul 22 '22 at 12:26
  • Create a `X509Chain` (in a `using`) then call [`Build`](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509chain.build?view=net-6.0) with your certificate then do `handler.ClientCertificates.AddRange(chain.ChainElements.Select(e => e.Certificate)`. Also have you tried moving `store.Close()` to the end – Charlieface Jul 22 '22 at 12:36
  • @Charlieface Thanks. I got similar results rebuilding the X509Chain with the certificates inside a using and moving store.Close() to the end. However, when doing that I noticed that the Wireshark logs show a certificates length of 0 and I've updated the question to reflect that as possibly being the main problem. While the system.diagnostics logs seem to show my certificate byte data being sent (maybe?), Wireshark has the certificate missing as seen in the screenshot. – djeastm Jul 22 '22 at 13:40
  • 1
    You don't need to load a certificate to make an HTTPS call with HttpClient. It's the *server* that needs to use a certificate verifying the server's domain. `handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12` isn't enabling TLS1.2, that's on by default. This prevents your code from using TLS1.3 or better – Panagiotis Kanavos Jul 22 '22 at 13:41
  • @PanagiotisKanavos Looks like OP is trying to use client certificates. – Charlieface Jul 22 '22 at 13:42
  • `The server has specified 8 issuer(s). Looking for certificates that match any of the issuers` Indicates the server is demanding those specific issuers only, but your client certificate is not issued by any of them. – Charlieface Jul 22 '22 at 13:43
  • HttpClient already does this, using the machine's certificate. Using a different certificate will fail unless the server trusts it or its issuer. A self-signed certificate won't work, just as a self-signed *server* certificate won't work – Panagiotis Kanavos Jul 22 '22 at 13:45
  • @PanagiotisKanavos The client certificate I'm loading here is also on the remote server and it's signed by a CA. The .NET6 code shows TLS1.2 as being used successfully. – djeastm Jul 22 '22 at 13:49
  • @Charlieface, I wonder why the .NET6 code works then? – djeastm Jul 22 '22 at 13:50
  • A CA that isn't trusted by the server. What do the server logs show when using .NET 6? – Panagiotis Kanavos Jul 22 '22 at 13:51
  • @PanagiotisKanavos I don't know, it's a third-party server I don't have access to. I do have the Wireshark logs from that and everything looks the same until the packet I screenshot above where the certificate length is 0 (and CertificateVerify is missing). – djeastm Jul 22 '22 at 13:54
  • Is `Left with 0 client certificates to choose from.` from a client-side trace then? What happens when you use .NET 6? – Panagiotis Kanavos Jul 22 '22 at 13:56
  • Yes, that's client side. The formats of the traces seem different but NET6 shows "Choosing X509Certificate2 1714493318 as the Client Certificate. Certificate Subject: , Thumbprint: ." NET6 also prints out Private Key metadata whereas NET Framework just shows "Certificate is of type X509Certificate2 and contains the private key." but no printout – djeastm Jul 22 '22 at 14:36
  • Inconsistency should be reported as bugs to https://github.com/dotnet/runtime/issues and if Microsoft developers follow up with you closely such can be thoroughly analyzed. – Lex Li Jan 09 '23 at 17:47

0 Answers0