C# X509 证书验证,带在线 CRL 检查,无需将根证书导入受信任的根 CA 证书存储
C# X509 certificate validation, with Online CRL check, without importing root certificate to trusted root CA certificate store
我试图在不将根 CA 证书导入受信任的根 CA 证书存储的情况下验证 X509 证书链(在生产中,此代码将 运行 在 Azure 函数中,并且 you can't add certificates to the trusted root CA certificate store on Azure App Services ).
我们还需要对此证书链执行在线 CRL 检查。
我对此进行了搜索,发现许多其他人都面临着同样的问题,但 none 的建议似乎有效。我遵循了 dotnet/runtime GitHub 上的 this SO post, which echoes the suggestions from issue #26449 中概述的方法。这是一个重现问题的小型控制台应用程序(针对 .NET Core 3.1):
static void Main(string[] args)
{
var rootCaCertificate = new X509Certificate2("root-ca-cert.cer");
var intermediateCaCertificate = new X509Certificate2("intermediate-ca-cert.cer");
var endUserCertificate = new X509Certificate2("end-user-cert.cer");
var chain = new X509Chain();
chain.ChainPolicy.ExtraStore.Add(rootCaCertificate);
chain.ChainPolicy.ExtraStore.Add(intermediateCaCertificate);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.Build(endUserCertificate);
chain.Build(new X509Certificate2(endUserCertificate));
var errors = chain.ChainStatus.ToList();
if (!errors.Any())
{
Console.WriteLine("Certificate is valid");
return;
}
foreach (var error in errors)
{
Console.WriteLine($"{error.Status.ToString()}: {error.StatusInformation}");
}
}
当运行这个returns三个错误:
UntrustedRoot: A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.
RevocationStatusUnknown: The revocation function was unable to check revocation for the certificate.
OfflineRevocation: The revocation function was unable to check revocation because the revocation server was offline.
但是,如果我将根 CA 证书添加到受信任的根 CA 证书存储区,那么所有三个错误都会消失。
问题
- 这是我的实现有问题,还是我想做的不可能?
- 我有哪些选择可以尝试实现这一目标?一些谷歌搜索表明 .NET 5 中提供的
X509ChainPolicy.CustomTrustStore
可能会挽救局面。充气城堡是实现这一目标的另一种选择吗?
好吧,这不是一个完整的答案,但可能会让你继续下去。
我们之前构建的 Azure Web 应用程序(不是函数应用程序,但我想这无关紧要)完全可以满足您的需求。我们花了一个星期左右。完整的答案是发布整个应用程序的代码,我们显然做不到。不过有一些提示。
我们通过将证书上传到应用程序(TLS 设置)并通过 WEBSITE_LOAD_CERTIFICATES 设置在代码中访问它们来绕过证书问题(你把指纹放在那里,link 你也提到了已发布),而不是您可以在代码中获取它们并构建您自己的证书存储:
var certThumbprintsString = Environment.GetEnvironmentVariable("WEBSITE_LOAD_CERTIFICATES");
var certThumbprints = certThumbprintsString.Split(",").ToList();
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
for (var i = 0; i < certThumbprints.Count; i++)
{
store.Certificates.Find(X509FindType.FindByThumbprint, certThumbprint, false);
}
然后我们实施了一些验证器:主题、时间、证书链(您基本上以某种方式标记您的根,然后从您在商店中验证链的证书出发,看看您是否最终进入您的根)和 CRL。
对于 CRL Bouncy Castle 提供支持:
// Get CRL from certificate, fetch it, cache it
var crlParser = new X509CrlParser();
var crl = crlParser.ReadCrl(data);
var isRevoked = crl.IsRevoked(cert);
从证书中获取 CRL 很棘手,但可行(我出于这个目的,或多或少地遵循了这个 https://docs.microsoft.com/en-us/archive/blogs/joetalksmicrosoft/pki-authentication-as-a-azure-web-app)。
A bit of Googling suggests the X509ChainPolicy.CustomTrustStore offered in .NET 5 might save the day
是的。
不是将 rootCaCertificate 放入 ExtraStore,而是将其放入 CustomTrustStore,然后设置 chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
。现在你提供的根是唯一对链有效的根。您还可以删除 AllowUnknownCertificateAuthority 标志。
OfflineRevocation
这个错误有点误导。这意味着“已请求对该链进行撤销,但缺少一个或多个撤销响应”。在这种情况下,它丢失是因为构建器没有要求它,因为它不信任根。 (一旦您不信任根,您就无法信任 CRLs/OCSP 响应,那么为什么要询问它们呢?)
RevocationStatusUnknown
同样,未知是因为它没有要求它。此代码与 OfflineRevocation 不同,因为从技术上讲,有效的 OCSP 响应是(实际上)“我不知道”。那将是 online/unknown.
UntrustedRoot
已通过上面的自定义信任代码解决。
其他注意事项:确定证书有效的正确方法是从 chain.Build 中捕获布尔值 return。对于您当前的链,如果您禁用了撤销 (chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck
),那么 Build 将 returned true
... 但是 UntrustedRoot
错误仍会出现在 ChainStatus 中输出。 Build
中的布尔值 return 是 false
如果有任何错误 VerificationFlags 没有说要忽略 .
您也只需调用一次 Build :)。
static void Main(string[] args)
{
var rootCaCertificate = new X509Certificate2("root-ca-cert.cer");
var intermediateCaCertificate = new X509Certificate2("intermediate-ca-cert.cer");
var endUserCertificate = new X509Certificate2("end-user-cert.cer");
var chain = new X509Chain();
chain.ChainPolicy.CustomTrustStore.Add(rootCaCertificate);
chain.ChainPolicy.ExtraStore.Add(intermediateCaCertificate);
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
bool success = chain.Build(endUserCertificate);
if (success)
{
return;
}
foreach (X509ChainStatus error in chain.ChainStatus)
{
Console.WriteLine($"{error.Status.ToString()}: {error.StatusInformation}");
}
}
我试图在不将根 CA 证书导入受信任的根 CA 证书存储的情况下验证 X509 证书链(在生产中,此代码将 运行 在 Azure 函数中,并且 you can't add certificates to the trusted root CA certificate store on Azure App Services ).
我们还需要对此证书链执行在线 CRL 检查。
我对此进行了搜索,发现许多其他人都面临着同样的问题,但 none 的建议似乎有效。我遵循了 dotnet/runtime GitHub 上的 this SO post, which echoes the suggestions from issue #26449 中概述的方法。这是一个重现问题的小型控制台应用程序(针对 .NET Core 3.1):
static void Main(string[] args)
{
var rootCaCertificate = new X509Certificate2("root-ca-cert.cer");
var intermediateCaCertificate = new X509Certificate2("intermediate-ca-cert.cer");
var endUserCertificate = new X509Certificate2("end-user-cert.cer");
var chain = new X509Chain();
chain.ChainPolicy.ExtraStore.Add(rootCaCertificate);
chain.ChainPolicy.ExtraStore.Add(intermediateCaCertificate);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.Build(endUserCertificate);
chain.Build(new X509Certificate2(endUserCertificate));
var errors = chain.ChainStatus.ToList();
if (!errors.Any())
{
Console.WriteLine("Certificate is valid");
return;
}
foreach (var error in errors)
{
Console.WriteLine($"{error.Status.ToString()}: {error.StatusInformation}");
}
}
当运行这个returns三个错误:
UntrustedRoot: A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.
RevocationStatusUnknown: The revocation function was unable to check revocation for the certificate.
OfflineRevocation: The revocation function was unable to check revocation because the revocation server was offline.
但是,如果我将根 CA 证书添加到受信任的根 CA 证书存储区,那么所有三个错误都会消失。
问题
- 这是我的实现有问题,还是我想做的不可能?
- 我有哪些选择可以尝试实现这一目标?一些谷歌搜索表明 .NET 5 中提供的
X509ChainPolicy.CustomTrustStore
可能会挽救局面。充气城堡是实现这一目标的另一种选择吗?
好吧,这不是一个完整的答案,但可能会让你继续下去。
我们之前构建的 Azure Web 应用程序(不是函数应用程序,但我想这无关紧要)完全可以满足您的需求。我们花了一个星期左右。完整的答案是发布整个应用程序的代码,我们显然做不到。不过有一些提示。
我们通过将证书上传到应用程序(TLS 设置)并通过 WEBSITE_LOAD_CERTIFICATES 设置在代码中访问它们来绕过证书问题(你把指纹放在那里,link 你也提到了已发布),而不是您可以在代码中获取它们并构建您自己的证书存储:
var certThumbprintsString = Environment.GetEnvironmentVariable("WEBSITE_LOAD_CERTIFICATES");
var certThumbprints = certThumbprintsString.Split(",").ToList();
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
for (var i = 0; i < certThumbprints.Count; i++)
{
store.Certificates.Find(X509FindType.FindByThumbprint, certThumbprint, false);
}
然后我们实施了一些验证器:主题、时间、证书链(您基本上以某种方式标记您的根,然后从您在商店中验证链的证书出发,看看您是否最终进入您的根)和 CRL。
对于 CRL Bouncy Castle 提供支持:
// Get CRL from certificate, fetch it, cache it
var crlParser = new X509CrlParser();
var crl = crlParser.ReadCrl(data);
var isRevoked = crl.IsRevoked(cert);
从证书中获取 CRL 很棘手,但可行(我出于这个目的,或多或少地遵循了这个 https://docs.microsoft.com/en-us/archive/blogs/joetalksmicrosoft/pki-authentication-as-a-azure-web-app)。
A bit of Googling suggests the X509ChainPolicy.CustomTrustStore offered in .NET 5 might save the day
是的。
不是将 rootCaCertificate 放入 ExtraStore,而是将其放入 CustomTrustStore,然后设置 chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
。现在你提供的根是唯一对链有效的根。您还可以删除 AllowUnknownCertificateAuthority 标志。
OfflineRevocation
这个错误有点误导。这意味着“已请求对该链进行撤销,但缺少一个或多个撤销响应”。在这种情况下,它丢失是因为构建器没有要求它,因为它不信任根。 (一旦您不信任根,您就无法信任 CRLs/OCSP 响应,那么为什么要询问它们呢?)
RevocationStatusUnknown
同样,未知是因为它没有要求它。此代码与 OfflineRevocation 不同,因为从技术上讲,有效的 OCSP 响应是(实际上)“我不知道”。那将是 online/unknown.
UntrustedRoot
已通过上面的自定义信任代码解决。
其他注意事项:确定证书有效的正确方法是从 chain.Build 中捕获布尔值 return。对于您当前的链,如果您禁用了撤销 (chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck
),那么 Build 将 returned true
... 但是 UntrustedRoot
错误仍会出现在 ChainStatus 中输出。 Build
中的布尔值 return 是 false
如果有任何错误 VerificationFlags 没有说要忽略 .
您也只需调用一次 Build :)。
static void Main(string[] args)
{
var rootCaCertificate = new X509Certificate2("root-ca-cert.cer");
var intermediateCaCertificate = new X509Certificate2("intermediate-ca-cert.cer");
var endUserCertificate = new X509Certificate2("end-user-cert.cer");
var chain = new X509Chain();
chain.ChainPolicy.CustomTrustStore.Add(rootCaCertificate);
chain.ChainPolicy.ExtraStore.Add(intermediateCaCertificate);
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
bool success = chain.Build(endUserCertificate);
if (success)
{
return;
}
foreach (X509ChainStatus error in chain.ChainStatus)
{
Console.WriteLine($"{error.Status.ToString()}: {error.StatusInformation}");
}
}