Search code examples
c#.netdotnet-httpclientclient-certificates

HttpClient refusing to send self-signed client certificate


I am attempting to send a self-signed client certificate using HttpClient with the following code:

var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual; 
handler.ClientCertificates.Add(GetClientCertificate()); //Loads cert from a .pfx file
var client = new HttpClient(handler);
PerformRequest(client);

I've seen a dozen SO posts related to this, but none have resolved my issue.

Things to note:

  • I have verified via Wireshark that the server is requesting a client cert, but HttpClient is not sending one.

When sent via HttpClient

  • The exact same certificate (same .pfx file is used) is sent when I use Postman to perform the request

When sent via Postman

  • I have tried forcing the TLS version via handler.SslProtocols to 1.0, 1.1, 1.2 -- none work
  • The X509Certificate2 returned from GetClientCertificate() has a private key
  • This is on .NET Framework 4.7.2 -- I can't change this

I have been troubleshooting this for 3 days now and even read through the reference source trying to figure out why my cert isn't included, but I can't find a reason.
I suspect somewhere deep inside the HttpClient implementation, my cert is getting rejected because it is self-signed.

How can I force it to send my cert? (the server maintains a whitelist, so I don't care if it thinks my cert is invalid). Or, at bare minimum, how can I get any sort of debugging information out of this? Is there any way to get a reason why a cert is rejected?


Update:

After enabling tracing, I have found that .NET is correctly selecting my certificate:

System.Net Information: 0 : [23960] SecureChannel#64538993 - Selected certificate: <omitted>
System.Net Information: 0 : [23960] SecureChannel#64538993 - Left with 1 client certificates to choose from.
System.Net Information: 0 : [23960] SecureChannel#64538993 - Trying to find a matching certificate in the certificate store.
System.Net Information: 0 : [23960] SecureChannel#64538993 - Locating the private key for the certificate: <omitted>
System.Net Information: 0 : [23960] SecureChannel#64538993 - Certificate is of type X509Certificate2 and contains the private key.
System.Net Information: 0 : [23960] SecureChannel#64538993::.AcquireClientCredentials, new SecureCredential() (flags=(ValidateManual, NoDefaultCred, SendAuxRecord), m_ProtocolFlags=(Tls12Client), m_EncryptionPolicy=RequireEncryption)
System.Net Information: 0 : [23960] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent  = Outbound, scc     = System.Net.SecureCredential)
System.Net Information: 0 : [23960] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 21c5cd98:21cd2108, targetName = <omitted>, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [23960] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=100, returned code=ContinueNeeded).
... etc

However, it is still not being sent. Any more ideas on where to look would be greatly appreciated.


Solution

  • This answer led me to the solution.

    X509Certificate2.PrivateKey was throwing a NotSupportedException because I was using ECD as the signature algorithm. I switched to RSA and now it properly sends the certificate.

    What's strange is that tracing showed no issue with the ECD cert, and it was able to successfully identify the private key. I have no idea why this is the case -- it sounds like a bug to me. Nevertheless, RSA will work for my use case and I am tired of debugging this.