我的软件使用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;
}
}
它工作正常,但我不知道我是否正在检查正确的事情。一些加密专家的任何建议都将不胜感激。
答案 0 :(得分:3)
您的代码看起来正确固定到根CA公钥。
HPKP要求您提供至少一个备份引脚,但我建议您遵循该指导。鉴于您正在固定到根CA,因此提供另一个根CA的公钥作为备份是合适的,以便在第一个CA发生某些事情时(例如,停业时)降低DoS的风险。
当然,您的代码可以容纳多个公钥来锁定,因此只需将附加键添加到字符串列表中即可。
干杯
编辑2017.10.23
以下是我认为合理的根CA公钥锁定和证书验证应该是什么样子。这个快速样本是在WebApi项目中完成的,因此锅炉板值控制器。
请注意,我的示例中只使用了一(1)个根CA公钥,如上所述,应提供备份引脚(最少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.");
}
}
}