HttpRequestMessage.GetClientCertificate() returns

2019-02-21 19:44发布

问题:

I have a .NET4.5 WebAPI 2 app that uses SSL Client Certificates for some custom security related checks.

When debugging the app, request.GetClientCertificate() returns null for any service call, no matter what I tried so far.

The Tested Code:

Basically, on my service's side, I have a method that goes something like this:

class CertificateChecker : ICertificateChecker
{
    public bool Check(HttpRequestMessage request)
    {
        var cert = request.GetClientCertificate();
    }
}

My unit tests with the client cert attached to the request using request.Properties.Add(HttpPropertyKeys.ClientCertificateKey, cert) pass (the cert comes out exactly as expected, validations work, and so on).

However, when I use a test with an HttpClient calling the actual WebAPI app, with the break point set at the request.GetClientCertificate() line on service side, the same line returns null.

Here's what I tried:

Client Certs:

  • Created a fake CA and put it into trusted CA store (tried both LocalMachine and current user).
  • Created a Client Authentication (1.3.6.1.5.5.7.3.2) cert signed by that Fake CA and placed it into the Personal store (LocalMachine and Current User).

(basically, followed https://www.piotrwalat.net/client-certificate-authentication-in-asp-net-web-api-and-windows-store-apps/ and similar blogs)

On my test that calls the service using HttpClient, attached the client cert in the following ways (none of which worked):

  • Using WebRequestHandler with ClientCertificateOptions set to Manual and the cert added to the ClientCertificates collection.

    using (var handler = new WebRequestHandler())
    {
         var cert = GetTestCert();
         handler.ClientCertificateOptions = ClientCertificateOption.Manual;
         handler.ClientCertificates.Add(cert);
    
         // and so on...
    }
    
  • Using the HttpRequestMessage, adding the cert as a property with the key set to HttpPropertyKeys.ClientCertificateKey (this approach works when unit testing as described above).

    request.Properties.Add(HttpPropertyKeys.ClientCertificateKey, cert);
    

The cert variable is set to the expected X509Certificate2 object in both cases.

Hosting:

  • IISExpress: Tried to run the app in IISExpress.

    Edited applicationhost.config with iisClientCertificateMappingAuthentication enabled set to "true" and the following access settings (neither worked):

    <access sslFlags="Ssl, SslNegotiateCert" />
    <access sslFlags="SslNegotiateCert" />
    
  • Local IIS Set the web app to run in IIS on my local machine

    • The site in IIS is configured with an HTTPS binding using a trusted certificate.
    • Web app configured to use https protocol with IIS Client Cert mapping authentication enabled.
    • Tried both the access option combinations (Ssl, SslNegotiateCert and SslNegotiateCert) (via configuration editor)

In both cases, when accessing the web api via the https url using a web browser, I get the index view of the home controller to show without issue (so, the server side cert is trusted).

回答1:

Great detailed information about your problem.

I'm going to assume the issue is with attaching the client certificate w/ HttpClient, since you cannot view the certificate on the server side in that situation. All of the hosting and server side certificate configuration sounds good.

  1. I would make sure that the X509Certificate2 cert variable that you are attaching is a public key that exists in your local certificate store (I'm not sure what store location you are storing this in) (you can check using mmc.exe). Also make sure that public key has a private key with it since the HttpClient will need that to sign the requests.

  2. In your code snippet you have using (var handler = new WebRequestHandler()) before the rest of your code. Make sure you are constructing your HttpClient client = new HttpClient(handler) in the using as well or the handler will be disposed.

Otherwise the handler creation looks good.

Good luck!