Search code examples
c#securityx509certificateca

How to validate a certificate chain from a specific root CA in C#


I have a certificate chain that looks like this: root CA -> intermediate CA -> client certificate. How can I validate that a received certificate is explicitly created by "root CA"?

To validate the whole chain is not a problem. This can be done like this:

X509Certificate2 rootCert = new X509Certificate2(rootCertFile);
X509Certificate2 intermediateCert = new X509Certificate2(intermediateCertFile);
X509Certificate2 clientCert = new X509Certificate2(clientCertFile);

chain.ChainPolicy.ExtraStore.Add(rootCert);
chain.ChainPolicy.ExtraStore.Add(intermediateCert);

if(chain.Build(clientCert))
{
    // ... chain is valid
}

The issue here is that the certificate gets validated against the (Windows) certificate store but I just want to validate it against a specific root CA.

I also thought that it would be possible to check if the chain.ChainElements contains my expected root CA. But what if someone sends me a valid chain from a different root CA and just adds the my expected root CA?


Solution

  • The certificate chain API checks that each element signed the preceding element, so there's no way that someone can just tack your root CA on the end (provided you're not using something like a 384-bit RSA key with MD5 signatures, in which case they can just forge your signature).

    You can encode any extra checks that you like, such as that you know none of your chains will exceed length 3 (though you could just have encoded that in your root CA's X509 Basic Constraints extension).

    if (!chain.Build(cert))
    {
        return false;
    }
    
    if (chain.ChainElements.Length > 3)
    {
        return false;
    }
    
    X509Certificate2 chainRoot = chain.ChainElements[chain.ChainElements.Length - 1].Certificate;
    
    return chainRoot.Equals(root);
    

    If you prefer the last line could be return root.RawData.SequenceEquals(chainRoot.RawData); (ensure they have the same bytes).

    Some things of note:

    • When you call X509Chain.Build() every X509Certificate2 object it returns via an X509ChainElement is a new object. You may wish to Dispose any of the objects you aren't returning (which may be all of them).
    • Even when chain.Build returns false it will populate the ChainElements array so you can inspect why.
    • The X509Chain object itself is Disposable, you likely want to Dispose it (which you might already be doing outside of your snippet).
    • Disposing the chain won't Dispose any of the created certificates, since you could have been holding an object reference already.