如何确保我们从链中获得最顶层的根 CA 证书?

How to ensure we're getting the topmost Root CA certificate from a chain?

我正在使用此代码评估链中每个证书的根 CA 状态:

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

它运行良好,但在当前情况下它返回两个 StartCom 证书:

(是的,我知道 StartCom 已被除名——我将在本周晚些时候处理这​​件事。)

ServerCertificateValidationCallback 委托接收 X509Chain,后者又包含构成链的证书数组。我不确定我们是否可以依靠数组中元素的顺序来确定顶级根 CA。

我找到了 this and this,但第一个依赖于 Magic Strings™,第二个依赖于可选字段。而且上面的 Extension.CertificateAuthority 属性 也没有足够缩小范围。正如我们所见,链中的两个证书将此 属性 设置为 True.

X509Certificate2 的 public 属性似乎不包含我们可以用来在链中可靠地识别其发行者的任何内容。最方便的是 Certificate.IssuerThumbprint 或类似的东西。

显然,此信息以某种方式可用,因为链是首先构建的。我很难考虑这种简单的能力在 API.

中被忽视的可能性。

我一定是遗漏了什么地方。

--编辑--

我找到了 X509ChainPolicy.ExtraStore 属性,它似乎包含所有证书 除了 我们想要的证书。从这里开始,这是一个简单的排除问题:

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

这是在链中查找顶级根 CA 的可靠方法吗?

ChainElements 按从最叶到最根的顺序排列。所以只要不出现PartialChain错误,就是最后一个ChainElement的Certificate。

private static X509Certificate2 GetRootCertificate(X509Chain chain)
{
    // Assumes that chain.Build was already called

    X509ChainElement chainElement = chain.ChainElements[chain.ChainElements.Count - 1];

    foreach (X509ChainStatus status in chainElement.ChainElementStatus)
    {
        if (status.Status == X509ChainStatusFlags.PartialChain)
        {
            return null;
        }
    }

    return chainElement.Certificate;
}