1

I'm seeing this message when my code tries to send a request to a server which requires requests to be accompanied by a client certificate with its private key (this may be an unusual situation). Using a request sent via SoapUI, I have verified that the certificate works. Is my code perhaps attaching the certificate in the wrong way?

The code looks like this (it probably contains lots of stuff that isn't required in the search for the elusive solution):

// build stuff
var httpClientHandler = new HttpClientHandler
{
    AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
    ClientCertificateOptions = ClientCertificateOption.Manual
};
var certificate = new X509Certificate(certName, password, X509KeyStorageFlags.UserKeySet);
httpClientHandler.ClientCertificates.Add(certificate);
var request = new HttpRequestMessage(HttpMethod.Post, url)
{
    Content = new StringContent(requestBody, Encoding.UTF8, "application/xml")
};
var httpClient = new HttpClient(httpClientHandler);
httpClient                   
    .DefaultRequestHeaders
    .Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

// Enable protocols
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;

// fire! (here's where the exception is thrown)
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
OutstandingBill
  • 2,614
  • 26
  • 38
  • I suspect this line could be the reason `var certificate = new X509Certificate(certName, password, X509KeyStorageFlags.UserKeySet);` I would try X509Certificate2. First open X509Store (CurrentUser\My), find the client SSL certificate to use and add it to `httpClientHandler.ClientCertificates` – pepo Feb 08 '19 at 08:30
  • @pepo, I'm having some trouble finding the certificate in the store. Is the store somehow a necessary part of the process, or can I simply use an `X509Certificate2` in place of `X509Certificate`, while still importing from the file system? – OutstandingBill Feb 10 '19 at 22:30
  • 1
    If you have pfx/pkcs12 file then you IMHO don't have to use x509store. Just create new x509certificate2 instance. – pepo Feb 10 '19 at 22:35
  • @pepo, I actually had a .cer file, which it turns out was the problem. Thanks for your input. – OutstandingBill Feb 10 '19 at 23:47

1 Answers1

1

The problem in this instance was with the certificate - specifically the private key.

I was using a .cer certificate, which behaves differently than a .pfx file. SoapUI and curl both work fine with .cer, but C# code which loads a .cer loses the private key. (Why the server we connect to requires the private key is a different question - my understanding is that it shouldn't - grateful for any thoughts on that.) Similarly, .cer files loaded to the store via the MMC snap-in also lose the private key. I haven't found out why.

Although my initial efforts loading a .pfx file into the certificate store were successful, when I started running my app under a different account the "Could not create SSL/TLS secure channel" error returned (explanations here and here). Essentially, the certificate store holds the private key in a file system location only accessible to the user loading the cert into the store.

Instead I loaded the cert direct from the file system, like this:

var certBytes = File.ReadAllBytes(certFileName);
var certificate = new X509Certificate2(certBytes, password, X509KeyStorageFlags.Exportable);

var httpClientHandler = new HttpClientHandler();
httpClientHandler.ClientCertificates.Add(certificate);
var request = new HttpRequestMessage(HttpMethod.Post, url)
{
    Content = new StringContent(requestBody, Encoding.UTF8, "application/xml")
};

var httpClient = new HttpClient(httpClientHandler);

httpClient                   
    .DefaultRequestHeaders
    .Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

// fire!
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
OutstandingBill
  • 2,614
  • 26
  • 38