C# X509 certificate verification with online CRL checking without importing root certificate into trusted root CA certificate store


Simon Pearson

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 a certificate to the trusted root CA certificate store Azure App Service ).

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

I've searched about this and I've seen many others facing the same problem, but none of these suggestions seem to work. I followed the approach outlined in this SO post , which echoes the advice in issue #26449 on the dotnet/runtime GitHub . Here's a small console application that reproduces the problem (targeting .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}");
    }
}

Three errors are returned when running:

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, all three errors go away.

question

  1. Is this a problem with my implementation, or is what I'm trying to do impossible?
  2. What are my options to try to achieve this? Some Googling suggests that what's available in .NET 5 might save the day. Are bouncy castles another option to achieve this?X509ChainPolicy.CustomTrustStore
Barton Jess

Some Googling suggests that X509ChainPolicy.CustomTrustStore available in .NET 5 might save the day

yes.

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

Undo offline

This error is a bit misleading. This means "A revocation has been requested for the chain, but one or more revocation responses are missing". In this case it's missing because the builder didn't ask for it because it doesn't trust the root. (Once you don't trust the root, you can't trust the CRL/OCSP responses, so why require them?)

Unknown revocation status

Again, unknown because it didn't ask for it. This code differs from OfflineRevocation because, technically, a valid OCSP response is (actually) "I don't know". That will be online/unknown.

untrusted root

Solved by the custom trust code above.


Additional Notes: The correct way to determine that the certificate is valid is to capture the boolean return value from chain.Build. For your current chain, if you disable undo( chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck) then Build will return true...but the UntrustedRooterror will still appear in the ChainStatus output. The boolean value returned Buildis falsewhether there are any errors that VerificationFlags doesn'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}");
    }
}

Related


Detect root x509 certificate in Go

Adam Williams: I have X509 certificate obtained using: block, additionalData := pem.Decode([]byte(str)) cert, err := x509.ParseCertificate(block.Bytes) I want to check if the certificate is a root certificate. I've tried checking IsCA, but the same is true fo

Detect root x509 certificate in Go

Adam Williams: I have X509 certificate obtained using: block, additionalData := pem.Decode([]byte(str)) cert, err := x509.ParseCertificate(block.Bytes) I want to check if the certificate is a root certificate. I've tried checking IsCA, but the same is true fo

Detect root x509 certificate in Go

Adam Williams: I have X509 certificate obtained using: block, additionalData := pem.Decode([]byte(str)) cert, err := x509.ParseCertificate(block.Bytes) I want to check if the certificate is a root certificate. I've tried checking IsCA, but the same is true fo

Detect root x509 certificate in Go

Adam Williams: I have X509 certificate obtained using: block, additionalData := pem.Decode([]byte(str)) cert, err := x509.ParseCertificate(block.Bytes) I want to check if the certificate is a root certificate. I've tried checking IsCA, but the same is true fo

Get the root CA certificate for the connection

ProgramCpp : I am connecting https to url. client.Get(url) Can I get the root certificate for validating the server certificate? I looked at crypto/tlsthe package PeerCertificates []*x509.Certificate // certificate chain presented by remote peer

Get the root CA certificate for the connection

ProgramCpp : I am connecting https to url. client.Get(url) Can I get the root certificate for validating the server certificate? I looked at crypto/tlsthe package PeerCertificates []*x509.Certificate // certificate chain presented by remote peer

Get the root CA certificate for the connection

ProgramCpp : I am connecting https to url. client.Get(url) Can I get the root certificate for validating the server certificate? I looked at crypto/tlsthe package PeerCertificates []*x509.Certificate // certificate chain presented by remote peer

Apply CA root certificate in Dockerfile

dan2k3k4 How can I download a certificate (and automatically renew) in a Docker container? In an Alpine-based Docker container, do the following: curl -I https://gtp.nlgworldwide.com But I get this error: curl: (60) SSL certificate problem: unable to get loca

Apply CA root certificate in Dockerfile

dan2k3k4 How can I download a certificate (and automatically renew) in a Docker container? In an Alpine-based Docker container, do the following: curl -I https://gtp.nlgworldwide.com But I get this error: curl: (60) SSL certificate problem: unable to get loca