简体   繁体   English

C# X509 证书验证,带在线 CRL 检查,无需将根证书导入受信任的根 CA 证书存储

[英]C# X509 certificate validation, with Online CRL check, without importing root certificate to trusted root CA certificate store

I'm trying to validate an X509 certificate chain without importing the root CA certificate into the trusted root CA certificate store (in production this code will run in an Azure Function, and you can't add certificates to the trusted root CA certificate store on Azure App Services ).我正在尝试验证 X509 证书链而不将根 CA 证书导入受信任的根 CA 证书存储(在生产中,此代码将在 Azure Function 中运行,并且您无法将证书添加到受信任的根 CA 证书存储Azure 应用服务)。

We also need to perform an online CRL check on this certificate chain.我们还需要对此证书链执行在线 CRL 检查。

I've searched on this and I see many others are facing the same problem, but none of the suggestions seem to work.我对此进行了搜索,发现许多其他人都面临同样的问题,但似乎没有任何建议有效。 I've followed the approach outlined in this SO post, which echoes the suggestions from issue #26449 on the dotnet/runtime GitHub.我遵循了这篇SO 帖子中概述的方法,这与 dotnet/runtime GitHub 上的 issue #26449中的建议相呼应。 Here's a small console application (targetting .NET Core 3.1) reproducing the problem:这是一个重现问题的小型控制台应用程序(针对 .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}");
    }
}

When ran this returns three errors:运行时返回三个错误:

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.

However, if I add the root CA certificate to the trusted root CA certificate store then all three errors disappear.但是,如果我将根 CA 证书添加到受信任的根 CA 证书存储中,则所有三个错误都会消失。

Questions问题

  1. Is this something wrong with my implementation, or is what I'm trying to do not possible?这是我的实现有问题,还是我试图做的事情不可能?
  2. What are my options to try to achieve this?我有什么选择来尝试实现这一目标? A bit of Googling suggests the X509ChainPolicy.CustomTrustStore offered in .NET 5 might save the day.一些谷歌搜索表明 .NET 5 中提供的X509ChainPolicy.CustomTrustStore可能会节省一天的时间。 Is Bouncy Castle another option for achieving this?充气城堡是实现这一目标的另一种选择吗?

Well, not a full answer, but probably it will get you going.好吧,不是一个完整的答案,但它可能会让你继续前进。

We've built before an Azure Web App (not a function app, but I guess wouldn't matter) which did exactly what you want.我们在 Azure Web 应用程序(不是 function 应用程序,但我想没关系)之前构建了您想要的应用程序。 Took us a week or so.花了我们一周左右的时间。 Full answer would be posting the code of the whole app, we I obviously cannot do.完整的答案是发布整个应用程序的代码,我们显然做不到。 Some hints though.不过有些提示。

We walked around the certificate problem by uploading certificates to the app (TLS settings) and accessing them in code through WEBSITE_LOAD_CERTIFICATES setting (you put thumbprints there, it's also mentioned in the link you posted), than you can get them in code and build your own certificate store:我们通过将证书上传到应用程序(TLS 设置)并通过 WEBSITE_LOAD_CERTIFICATES 设置在代码中访问它们来解决证书问题(您将指纹放在那里,您发布的链接中也提到了),而不是您可以在代码中获取它们并构建您的自己的证书存储:

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

Than we implemented a number of validators: for subject, time, certificate chain (you basically mark your root somehow and go from the certificate you validate up the chain in your store and see if you end up in your root) and CRL.比我们实施了许多验证器:对于主题、时间、证书链(你基本上以某种方式标记你的根和 go 从证书中验证你的商店中的链,看看你是否最终进入你的根)和 CRL。

For CRL Bouncy Castle offers support:对于 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);

Getting CRL from the certificate is tricky, but doable (I followed this for the purpose, more or less https://docs.microsoft.com/en-us/archive/blogs/joetalksmicrosoft/pki-authentication-as-a-azure-web-app ).从证书中获取 CRL 很棘手,但可行(我遵循此目的,或多或少https://docs.microsoft.com/en-us/archive/blogs/joetalksmicrosoft/pki-authentication-as-a-azure -网络应用程序)。

A bit of Googling suggests the X509ChainPolicy.CustomTrustStore offered in .NET 5 might save the day一些谷歌搜索表明 .NET 5 中提供的 X509ChainPolicy.CustomTrustStore 可能会节省一天的时间

Yep.是的。

Instead of putting rootCaCertificate into ExtraStore, put it into CustomTrustStore, then set chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;不是将rootCaCertificate放入ExtraStore,而是放入CustomTrustStore,然后设置chain.ChainPolicy.TrustMode chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; . . Now your provided root is the only root valid for the chain.现在您提供的根是唯一对链有效的根。 You can also remove the AllowUnknownCertificateAuthority flag.您还可以删除 AllowUnknownCertificateAuthority 标志。

OfflineRevocation离线撤销

This error is slightly misleading.这个错误有点误导。 It means "revocation was requested for the chain, but one or more revocation responses is missing".这意味着“请求撤销链,但缺少一个或多个撤销响应”。 In this case it's missing because the builder didn't ask for it, because it didn't trust the root.在这种情况下,它丢失了,因为构建器没有要求它,因为它不信任根。 (Once you don't trust the root you can't trust the CRLs/OCSP responses, so why ask for them at all?) (一旦您不信任根,您就无法信任 CRL/OCSP 响应,那么为什么要要求它们呢?)

RevocationStatusUnknown撤销状态未知

Again, the unknown is because it didn't ask for it.同样,未知是因为它没有要求它。 This code is different than OfflineRevocation because technically a valid OCSP response is (effectively) "I don't know".此代码与 OfflineRevocation 不同,因为从技术上讲,有效的 OCSP 响应是(实际上)“我不知道”。 That'd be an online/unknown.那将是在线/未知的。

UntrustedRoot不受信任的根

Solved by the custom trust code above.由上面的自定义信任代码解决。


Other things of note: The correct way to determine the certificate is valid is to capture the boolean return value from chain.Build.其他注意事项:确定证书有效的正确方法是从 chain.Build 捕获 boolean 返回值。 For your current chain, if you had disabled revocation ( chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck ) then Build would have returned true ... but the UntrustedRoot error would still be present in the ChainStatus output.对于您当前的链,如果您禁用了撤销 ( chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck ),那么 Build 将返回true ...但UntrustedRoot错误仍会出现在 ChainStatus output 中。 The boolean return from Build is false if there are any errors that the VerificationFlags didn't say to ignore .如果存在任何VerificationFlags 没有说要忽略的错误,则Build的 boolean 返回为false

You also only need to call Build once:).您也只需要调用一次 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}");
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM