C# .NET - 固定证书颁发机构 - 我做得对吗?

C# .NET - Pinning Certificates Authorities - I am doing it correctly?

我的软件使用 HTTPS 连接连接到 Dropbox 以检索一些敏感数据。

我想固定证书颁发机构以防止中间人攻击。

到目前为止我有以下代码:

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;
        }
}

它工作正常,但我不知道我是否在检查正确的东西。非常感谢一些加密专家的任何建议。

您的代码对于固定到根 CA public 密钥来说看起来是正确的。

不过,HPKP 要求您至少提供一个备用 PIN 码,我建议您遵循该指南。鉴于您固定到根 CA,提供另一个根 CA 的 public 密钥作为备份是合适的,以在第一个 CA 发生某些事情(例如停业)时减轻 DoS 的风险。

当然,您的代码包含多个要固定的 public 键,因此只需将附加键添加到您的字符串列表即可。

干杯

编辑 2017.10.23

这是我认为合理的根 CA Public 密钥固定和证书验证应该是什么样子的示例。这个快速示例是在 WebApi 项目中完成的,因此是样板值控制器。

请注意,我的示例中仅使用了一 (1) 个根 CA public 密钥,并且如上所述,应提供备用 pin(至少 2 个数组元素)。

这是一个示例,不打算用作生产代码 - 我建议对以下内容进行同行/安全审查:

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.");
        }
    }
}