Search code examples
c#tls1.2x509certificate2winhttp

Two way SSL - X509Certificate2.CreateFromPemFile - WINHTTP_CALLBACK_STATUS_REQUEST_ERROR


I am trying to call an API which requires two way SSL (TLS 1.2) and I have the following code:

var myCert = X509Certificate2.CreateFromPemFile(_publicFilename, _privateFilename);

using (var handler = new WinHttpHandler())
{
    handler.ClientCertificateOption = ClientCertificateOption.Manual;
    handler.ServerCertificateValidationCallback = (x, y, z, w) => true;
    handler.ClientCertificates.Add(myCert);
    handler.SslProtocols = SslProtocols.Tls12;

    using (var client = new HttpClient(handler))
    {
        var postData = new StringContent("", UnicodeEncoding.UTF8, "application/json");
        var response = await client.PostAsync("<API Endpoint>", postData);

        string responseString = await response.Content.ReadAsStringAsync();
        Console.WriteLine(responseString);
    }
}

However, when I call that I get the following WinHttp exception:

Error 12185 calling WINHTTP_CALLBACK_STATUS_REQUEST_ERROR, 'No credentials were available in the client certificate.'.

Also in the windows event viewer I get the following error message:

The TLS client credential's certificate does not have a private key information property attached to it. This most often occurs when a certificate is backed up incorrectly and then later restored. This message can also indicate a certificate enrollment failure.

Unable to figure out what the issue is. I am using the exact same certificate and key in postman and it works fine.


Solution

  • TLS on Windows (WinHttpHandler, SslStream, or the default HTTP handler (which uses SslStream)) requires that the certificate have a named private key.

    You can do that on a temporary basis by importing a PFX without PersistKeySet... but how do you get a PFX? Well, pretty easy.

    var myCert = X509Certificate2.CreateFromPemFile(_publicFilename, _privateFilename);
    
    using (var tmpCert = new X509Certificate2(myCert.Export(X509ContentType.Pfx)))
    using (var handler = new WinHttpHandler())
    {
        ...
        handler.ClientCertificates.Add(tmpCert);
        ...
    }
    

    The named key will be deleted when tmpCert gets Disposed. If you have complex or long life, you can create the cert not in a using statement. If the cert gets garbage collected and the process stays active long enough to run the finalizers the key will get cleaned up then.