简体   繁体   中英

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 ).

We also need to perform an online CRL check on this certificate chain.

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. Here's a small console application (targetting .NET Core 3.1) reproducing the problem:

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.

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. 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. 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:

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.

For CRL Bouncy Castle offers support:

// 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 ).

A bit of Googling suggests the X509ChainPolicy.CustomTrustStore offered in .NET 5 might save the day

Yep.

Instead of putting rootCaCertificate into ExtraStore, put it into CustomTrustStore, then set chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; . Now your provided root is the only root valid for the chain. You can also remove the AllowUnknownCertificateAuthority flag.

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?)

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". 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. 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. The boolean return from Build is false if there are any errors that the VerificationFlags didn't say to ignore .

You also only need to call Build once:).

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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