0
votes

We've got a web app that has to communicate with a HTTP service using two-way SSL certificate authentication. The following is the relevant code

        X509Certificate certificate = GetCertFromStore(StoreName.My, StoreLocation.LocalMachine, certName);
        if (certificate != null)
        {
            Log.Debug($"Adding client cert {certificate.Subject}");

            WebRequestHandler handler = new WebRequestHandler();

            handler.ClientCertificates.Add(certificate);
            handler.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
            handler.ClientCertificateOptions = ClientCertificateOption.Manual;
            handler.UseProxy = false;

            using (var client = new HttpClient(handler))
            {
                ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;

                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                var postObj = new
                {
                    // somestuff
                };
                var postBody = JsonConvert.SerializeObject(postObj);

                var httpContent = new StringContent(postBody, Encoding.UTF8, "application/json");

                HttpResponseMessage httpResponse = client.PostAsync(queryUrl, httpContent).Result;
                if (httpResponse.IsSuccessStatusCode)
                {
                    // codey code
                }
                else
                {
                    Log.Error($"some error stuff.");
                }
            }
        }
        else
        {
            Log.Error($"Unable to find the certificate with name: {certName}");
        }

At the moment we're using a self-signed certificate which we've provided to the third-party service provider and installed the relevant server certificate from them.

On a local development machine all works well - the connection is established and the service calls and responses go through correctly.

However, when we've deployed on an Azure VM (our test environment) the certificate exchange fails. Looking at a Wireshark trace we can see that server certificate is accepted, but it shows that there are 0 client certificates sent. We know the client certificate is being found in the store (because of the logging) and we can see from the Wireshark trace that the client certificate name is indeed listed in the 'Distinguished Names' of the server's certificate request.

Can't figure out how, even though the code is adding our expected client certificate to the WebRequestHandler it disappears during the handshake.

1
Does the self-signed certificate exist on that Azure VM personal store?Nancy Xiong
Hi. Yes it's been installed in the VM personal store and our code, in the function GetCertFromStore(...), loads it.Stephen
Is there any specific error message?Nancy Xiong
No, just shows 0 client certificates sent.Stephen

1 Answers

0
votes

We got this worked out.

First I think the certificate was installed without its private key. In our function GetCertFromStore it was finding the certificate but I think because it didn't have the private key it wasn't sending it. (The fact that it was finding the certificate is what had us confused - we assumed that if it found it then all was good.)

To fix this we deleted it from the store and then reinstalled it using the .pfx file, making sure to also import the private key.

The there was an additional problem with permissions, the answer for which we found on StackOverflow here (note, not the answer marked as 'answer', but the answer about permissions). For a web application to be able to load and use a certificate with a private key the Application Pool identity (or impersonated user) must have read permissions on the installed certificate's key file (usually found in C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys).