7

My software connects to Dropbox using an HTTPS connection in order to retrieve some sensitive data.

I would like to pin the Certificates Authorities in order to prevent a man-in-the-middle attack.

So far I have the following code:

static bool VerifyServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
        try
        {
            var currentCaPublicKey = chain.ChainElements.Cast<X509ChainElement>().Last().Certificate.GetPublicKeyString();

            var caPublicKeys = new List<string>(){"00ad0e15cee443805cb187f3b760f97112a5aedc269488aaf4cef520392858600cf880daa9159532613cb5b128848a8adc9f0a0c83177a8f90ac8ae779535c31842af60f98323676ccdedd3ca8a2ef6afb21f25261df9f20d71fe2b1d9fe1864d2125b5ff9581835bc47cda136f96b7fd4b0383ec11bc38c33d9d82f18fe280fb3a783d6c36e44c061359616fe599c8b766dd7f1a24b0d2bff0b72da9e60d08e9035c678558720a1cfe56d0ac8497c3198336c22e987d0325aa2ba138211ed39179d993a72a1e6faa4d9d5173175ae857d22ae3f014686f62879c8b1dae45717c47e1c0eb0b492a656b3bdb297edaaa7f0b7c5a83f9516d0ffa196eb085f18774f"};

            return caPublicKeys.Any(s => currentCaPublicKey.Equals(s));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            return false;
        }
}

It works fine but I don't know if I am checking the right things. Any advice from some crypto experts would be greatly appreciated.

John
  • 1,011
  • 11
  • 18
  • PS: In case someone is looking for CA used by Dropbox, they are available there: https://github.com/dropbox/dropbox-sdk-python/blob/master/dropbox/trusted-certs.crt – John Oct 16 '17 at 18:43
  • Your code has multiple undefined symbols that make reading it hard. (Looks like they're editing artifacts, like `currentCaPk`.) – xxbbcc Oct 16 '17 at 18:52

1 Answers1

8

Your code looks correct for pinning to the Root CA public key.

HPKP requires that you provide at least one backup pin, however, and I would recommend that you follow that guidance. Given that you are pinning to a Root CA it would be appropriate to provide the public key of another Root CA as the backup to mitigate the risk of DoS should something happen to the first CA, e.g., out of business.

Of course, your code accommodates multiple public keys to pin against, so it would merely be a matter of adding the additional key to your list of strings.

Cheers

EDIT 2017.10.23

Here is a sample of what I think reasonable Root CA Public Key pinning and Cert Validation should look like. This quick sample was done in a WebApi project, thus the boiler plate values controller.

Please note that only one (1) Root CA public key is used in my sample, and as mentioned above, a backup pin should be provided (minimum 2 array elements).

This is a sample and not intended to be taken as production code - I suggest that the following be subjected to peer / security review:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Web.Http;

namespace CertPinPocClient.Controllers
{
    public class ValuesController : ApiController
    {
        // GET api/values
        public IEnumerable<string> Get()
        {
            ServicePointManager.ServerCertificateValidationCallback = new PinnedRootCaCertificate(new[]
            {
                "MIICCgKCAgEAzHYh7u+V5haaRSoGSVGm/gC4EYvZHkBR3/c/kQvTJeh1L9Bn/b7U1s7onw85SjvpZ28ohoT7p4vJRoNUBemR6hf3TM1mZmSE0tqnLGzBV9H4Nfrxx1+cubxYyYaOJ8iJfp1XslGGyZqQmUFFjWOUuU9cvOAbz4DqBIUn344JhG0xEHCf5IOF0gfuWE8yQC9vIjlveUQQ7dq/rDNZcQjqDhEb6DcF7za+1ZxjZdmtKewoYgDBPqzf66Gwi85BZsEcYFQTbjzvAhYaq4xPhJF6iPS4ihf+zjnMPxmy2oH1bm8n2fVuyxqV5JgIDU0ualx728UhfJUjcoBl57OLVsiJIdHFHpcDhN8Fn5QUGkNPgQqX27R1aw/+t2HfYTEsg6urH3aam8e7qRKUEXJs8qMKnXZ15aY0zlO7DLtfnK5tq2Cnu+HBBo4FlDhRO4kTBZOisFkvkEWI/Nj6jioOyMWsTsUvOdDK5KUpWZazpc3rwCvQy3KwBz6EyPU7ihrTm+nqqK5wiI9YwRcMjsPRBZfAur1cB0hNi+g98+2zzj+hwyR49KkOzFowp5MvXEWhnYDrY4cHSJ7zSdgMdO9HWPMke1HuKOUuUUUIpQMvPmFDAh4WQpAKqGvI/cOZeubnSwVMQra13QviYdlUeT56tFDTjgdbUNyBy0gxcFPVgTjzTj8CAwEAAQ==",
            }).Valid;

            var httpClient = new HttpClient
            {
                BaseAddress = new Uri("https://local.monitor.iontech.org")
            };

            var httpResponseMessage = httpClient.GetAsync(new Uri("https://local.monitor.iontech.org/api/status/")).Result;
            var result = httpResponseMessage.Content.ReadAsStringAsync().Result;
            return new[] {result};
        }
    }

    public class PinnedRootCaCertificate
    {
        private readonly string[] _rootCaPublicKeys;

        public PinnedRootCaCertificate(string[] rootCaPublicKeys)
        {
            _rootCaPublicKeys = rootCaPublicKeys;
        }

        public bool Valid(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslpolicyerrors)
        {
            if (sslpolicyerrors != SslPolicyErrors.None) return false;

            var rootCertificate = SelfSignedCertificate(chain);
            var publicKey = Convert.ToBase64String(rootCertificate.PublicKey.EncodedKeyValue.RawData);
            return rootCertificate.Verify() && _rootCaPublicKeys.Contains(publicKey);
        }

        private X509Certificate2 SelfSignedCertificate(X509Chain chain)
        {
            foreach (var x509ChainElement in chain.ChainElements)
            {
                if (x509ChainElement.Certificate.SubjectName.Name != x509ChainElement.Certificate.IssuerName.Name) continue;
                return x509ChainElement.Certificate;
            }
            throw new Exception("Self-signed certificate not found.");
        }
    }
}
Jim Speaker
  • 1,303
  • 10
  • 28
  • Thanks. Is there no need to verify the chain? Or is that done by the framework beforehand? – John Oct 22 '17 at 16:25
  • I’m not suggesting that you wouldn’t want to do Trust Chain validation, nor suggesting that the other usual steps in cert validation should be ignored. In the simple code bit above, I’m saying, “yep, that looks right.” Re using the framework, absolutely, you should. In fact, the pinning code you’ve posted, IMO, should be replaced with framework based code. I’m on my phone, ATM, but will elaborate a bit when I have a free moment in front of my machine. – Jim Speaker Oct 23 '17 at 16:43