Reputation: 6367
I'm using this code to evaluate each certificate in a chain for Root CA status:
Function GetRootCaCertificates(Chain As X509Chain) As IEnumerable(Of X509Certificate2)
With Chain.ChainElements.Cast(Of X509ChainElement)
With .Select(Function(Element As X509ChainElement) Element.Certificate)
Return .Where(Function(Certificate As X509Certificate2)
With Certificate.Extensions.Cast(Of X509Extension)
With .Where(Function(Extension As X509Extension) TypeOf Extension Is X509BasicConstraintsExtension)
With .Cast(Of X509BasicConstraintsExtension)
Return .Where(Function(Extension As X509BasicConstraintsExtension) Extension.CertificateAuthority = True).Count > 0
End With
End With
End With
End Function).ToList
End With
End With
End Function
It works well, but in the current case it's returning the two StartCom certificates:
(Yes, I know that StartCom has been delisted—I'll be dealing with that later this week.)
The ServerCertificateValidationCallback
delegate receives the X509Chain
, which in turn contains an array of the certificates that make up the chain. I'm not confident that we can rely on the elements' order in the array to determine the top-level Root CA.
I found this and this, but the first relies on Magic Strings™ and the second relies on an optional field. And the Extension.CertificateAuthority
property above doesn't narrow it down enough, either. As we can see, two certificates in the chain have this property set to True
.
The public properties of an X509Certificate2
don't seem to include anything that we can use to reliably identify its issuer in the chain. Most handy would be something like Certificate.IssuerThumbprint
or similar.
Obviously this information is available somehow, since the chain was built in the first place. And I'm having a hard time considering the possibility that this simple capability has been overlooked in the API.
I must be missing it somewhere.
--EDIT--
I found the X509ChainPolicy.ExtraStore
property, which seems to contain all certificates except the one we're after. From here it's a simple matter of exclusion:
Dim oCertificates As List(Of X509Certificate2)
Dim oThumbprints As IEnumerable(Of String)
oThumbprints = Chain.ChainPolicy.ExtraStore.Cast(Of X509Certificate2).Select(Function(Certificate As X509Certificate2) Certificate.Thumbprint)
oCertificates = Chain.ChainElements.Cast(Of X509ChainElement).Select(Function(Element As X509ChainElement) Element.Certificate).ToList
oCertificates.RemoveAll(Function(Certificate As X509Certificate2) oThumbprints.Contains(Certificate.Thumbprint))
Is this a reliable means of finding the top-level Root CA in a chain?
Upvotes: 1
Views: 1717
Reputation: 33098
The ChainElements are in order from leaf-most to root-most. So as long as you don't get a PartialChain error, it's the last ChainElement's Certificate.
private static X509Certificate2 GetRootCertificate(X509Chain chain)
{
// Assumes that chain.Build was already called
foreach (X509ChainStatus status in chain.ChainStatus)
{
if (status.Status == X509ChainStatusFlags.PartialChain)
{
return null;
}
}
X509ChainElement chainElement = chain.ChainElements[chain.ChainElements.Count - 1];
return chainElement.Certificate;
}
Upvotes: 6