Client certificate with HttpClient in c#

Somnath Kadam picture Somnath Kadam · Oct 6, 2019 · Viewed 7.9k times · Source

Want to send data with client certificate (.p12 or .pfx) from Windows application to Server machine, Windows application developed in .Net Framework 4.6, OS is windows 10.

When hit from postman with client certificate (.p12 or .pfx) [Loaded in setting tab -> Add client certificate - > put hostname, select pfx file, put password], all working properly (client certificate send to server machine), but issue from below c# code,

X509Certificate2 certificate = new X509Certificate2(certificateFilePath, "password");
WebRequestHandler handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
handler.ClientCertificates.Add(certificate);


HttpClient request = new HttpClient(handler);
request.DefaultRequestHeaders.Add("User-Agent", UserAgent);
// added other headers and data
var result = request.PostAsync(url, byteContent).Result;
string resultContent = result.Content.ReadAsStringAsync().Result;

Also cross check with fiddler for Postman hit and c# hit.

When server not receive client certificate, it return 403 error.

Answer

wobuntu picture wobuntu · Oct 13, 2019

I assume that your handler has no access to the private key for authentication.

This may be caused due to line 1 in your example, in which you import the certificate with the default key storage flags. Of course this is just a guess and I don't have your certificate to check this, but you can verify that by calling

// Sample for RSA, use DSA if required
var privateKeyParams = ((RSA)certificate.PrivateKey).ExportParameters(true);

which will cause a CryptographicException ("Not supported" or similar) if the key parameters cannot be accessed.

Please try the following instead for loading the certificate:

X509Certificate2 certificate = new X509Certificate2(
  certificateFilePath, "password",
  X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet);

Just a further sidenote; If you use this in production code, be sure to extend your server certificate validation callback (your 4th line) to actually validate the server certificate. See X509Chain.Build, which also allows you to modify the validation options to your needs (what the path validation actually does can be read in RFC5280).