Search code examples
c#.netx509certificatedotnet-httpclientx509certificate2

Why might the server not receive a certificate attached to a request using HttpClient?


We're trying to connect to an endpoint that does nothing more than verify whether a certificate has been correctly attached.

Our code is straightforward:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

using (var handler = new HttpClientHandler())
{
    var rawcert = File.ReadAllText(@"C:\OpenSSL\bin\cert.pem");
    var rawkey = File.ReadAllText(@"C:\OpenSSL\bin\private.key");

    var provider = new CertificateFromFileProvider(rawcert, rawkey);
    var certificate = provider.Certificate;

    handler.ClientCertificateOptions = ClientCertificateOption.Manual;
    handler.ClientCertificates.Add(certificate);
    handler.ServerCertificateCustomValidationCallback +=
    (HttpRequestMessage req, X509Certificate2 cert2, X509Chain chain, SslPolicyErrors err) =>
    {
        Trace.WriteLine($"Sender: {req}");
        Trace.WriteLine($"cert: {cert2}");
        Trace.WriteLine($"chain: {chain}");
        Trace.WriteLine($"sslPolicyErrors: {err}");
        return true;
    };

    using (var client = new HttpClient(handler))
    {
        var response = await client.GetStringAsync("https://{uri}/mtlsTest");
        return response;
    }
}

(This is a sandbox environment for a proof of concept, so we're comfortable shortcutting the ValidationCallback for now.)

We can see the certificate is attached appears well-formed in the callback (origin and subject are as-expected), but we're getting a response from the server that indicates no certificate was attached. Why might that be?

We have also tried exporting the certificate as a pfx and attaching that instead of the original pem file, with an identical result.

Update

Thanks to useful comments below from @bartonjs and @Crypt32, we've tried adding the private key (using the handy Nuget Package from @stef-heyenrath), but although this results in the certificate that we add to the handler showing HasPrivateKey=true:

enter image description here

... when the ValidationCallback fires, the X509Certificate2 traces HasPrivateKey=false:

enter image description here

... and we get the same error as before - the server never acknowledges receiving the certificate.

If we package the .pem and the private key into a .pfx using open ssl: pkcs12 -inkey private.key -in cert.pem -export -out cert.pfx ... then we get the same result again, and the server can find no certificate. Likewise if we install the certificate in the personal store and load it from there (same result). An inspection with Wireshark suggests the certificate is simply never being attached.

Why would this be? Full revised code above.


Solution

  • The initial problem here was a badly-formed certificate provided by our test host.

    A further problem (once we had a good certificate to work with) was with the output of the CertificateFromFileProvider method of the OpenSSL.X509Certificate2Provider NuGet package.

    For whatever reason, the certificate created would not work correctly. Taking the same input files and generating a .pfx with OpenSSL resulted in an X509Certificate2 that worked fine.