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.");
}
}
}
我的软件使用 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.");
}
}
}