C# Coding

How to enable certificate pinning in your application with the WebClient or HttpRequest object?

24 views August 25, 2018 August 26, 2018 bicobro 0

In this article I show you how to perform certificate pinning in your C# application when you use for example the WebClient or the HttpClient object. To understand the solution below you have know that all HTTP SSL/TLS communication within your C# application is going through the ServicePointManager object.

To understand the solution below you have know that all HTTP SSL/TLS communication within your C# application is going through the ServicePointManager object.

This applies to every HTTP SSL call and is independent of the client object you use (as mentioned, the WebRequest, HttpClient or WebClient objects). It doesn’t care.

Block all traffic

To block all SSL traffic you simply register yourself to the ServerCertificateValidationCallback of the ServicePointManager as shown in the code below. The callback is a static member, you can call it from everywhere in your code.

   
using System.Security.Cryptography.X509Certificates; 
using System.Net;

...
     
private void EnableCertificatePinning()
{
    ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack;
}

private bool CertificateValidationCallBack(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    return false; // Tell the ServicePointManager to invalidate every HTTP SSL request
}

Pinning the domain certificate

The code example below performs certificate pinning for a specific domain. It only allows connections when a certificate is used with the given thumbprint. This means that traffic is blocked for all sites that do not use this specific certificate. Normally a certificate is applied to a single domain.

private void EnableCertificatePinningForLicenseService()
{
  ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack;
}

private bool CertificateValidationCallBack(object sender, X509Certificate certificate, X509Chain chain,	SslPolicyErrors sslPolicyErrors)
{   
    // Step 1 - Convert the .NET 1.1 cert object to a more advanced one
    X509Certificate2 certificate2 = new X509Certificate2(certificate);
    
    // Step 2 - Assure that the certificate is valid!
    bool isCertificateValid = certificate2.Verify();
    
    // Step 3 - Compare the expected thumbprint to the certificate thumbprint. This compare order prevents null pointer exceptions.
    bool isThumbprintAsExpected = "2105F6B740174BB86829F69F0860A89B9E88AB09".Equals(certificate2.Thumbprint);

    return isCertificateValid  && isThumbprintAsExpected;
}

The code above explained:

  • Step 1 – The certificate object given by the callback is converted to a more modern one, the X509Certificate2. This one contains the Verify() feature for step 2.
  • Step 2 – The certificate is verified. This means that the signature is checked so we can rely on the given thumbprint for step 3.
  • Step 3 – The thumbprint we expect is compared to the one within the certificate.

Pinning the Certification Authority (CA) certificate

The disadvantage of the solution above is that when the certificate changes, the thumbprint must be replaced in code. Because reliable certificates are part of a CA trust-chain, it is possible to verify the CA certificate instead of the domain certificate. CA certificate exists normally a lot longer so changing the CA thumbprint is needed less often.  The code example below shows CA certificate pinning for the domain mydomain.com.

The CA certificate content

private void EnableCertificatePinningForLicenseService()
{
  ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack;
}

private bool CertificateValidationCallBack(object sender, X509Certificate certificate, X509Chain chain,	SslPolicyErrors sslPolicyErrors)
{
    // 1. Certificate pinning for only the domain mydomain.com
    if (!certificate.Subject.ToLower().Contains("mydomain.com"))
    {
       return true;
    }

    // 2. Get the highest level Certification Authority (CA) in the chain
    X509Certificate2 highestCACertificate = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
    
    // 3. Assure that the CA certificate is valid!
    bool isCACertificateValid = highestCACertificate.Verify();
    
    // 4. Compare the expected thumbprint to the certificate thumbprint. This compare order prevents null pointer exceptions.
    bool isThumbprintAsExpected = "DAC9024F54D8F6DF94935FB1732638CA6AD77C13".Equals(highestCACertificate.Thumbprint);

    return isCACertificateValid && isThumbprintAsExpected;
}

The code above explained:

  • Step 1 – The code verifies the request is for a domain containing “mydomain.com”. If so, pinning is applied.
  • Step 2 – The highest level CA is collected
  • Step 3 – The CA certificate signature is verified to be sure we can rely on the thumbprint of the certificate in step 4
  • Step 4 – The thumbprint is verified

Was this helpful?