如何通过编程找到用于签署给定证书的证书?

16
在我的C#代码中,我有一个X509Certificate2对象,它代表了一个SSL证书(来自本地存储或通过SSL的HTTP请求成功获得)。该证书由一些中间证书签名,这些中间证书可能存在于本地存储中,也可能不存在,因此使用X509Chain.Build()可能不起作用。
Firefox证书查看器的图片(因为我还没有可用的代码):

enter image description here

在“详细信息”中的“证书层次结构”下,我看到了这个:
- DigiCert High Assurance EV Root CA
- DigiCert SHA2 Extended Validation Server CA
- github.com
我的对象代表“github.com”,是链中最底部的一行。我需要以编程方式识别中间行(“DigiCert SHA2 Extended Validation Server CA”)。
我如何知道指纹或任何等效物,以便让我识别用于签署我的证书的证书?

4
您在元问题中询问了关闭的问题,我将在那里详细回答。请给我几分钟时间来回复。 - Wai Ha Lee
我已经回答了。对于延迟表示抱歉。 - Wai Ha Lee
IssuerName 属性有什么问题? - erickson
@erickson,除了它不必匹配签名证书的主题外,甚至如果匹配也无法知道它是否完全正确或只是具有相同主题的某些证书。 - sharptooth
@sharptooth,您是错误的。签名检查将证明这一点。 - Crypt32
显示剩余4条评论
2个回答

11
在这种情况下(github.com),X509Chain.Build将起作用,因为终端证书包含有关颁发者证书位置的信息(在Authority Information Access扩展中)。
但有时可能会出现问题(例如Thawte证书),因为Thawte没有提供关于颁发者证书位置的明确信息。如果证书安装在本地证书存储区,则无法自动定位颁发者。
选项1--SSL连接
但是,如果您使用SSL证书并且可以建立SSL会话,则可以通过向ServicePointManager.ServerCertificateValidationCallback属性添加侦听器来获取证书:https://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx RemoteCertificateValidationCallback委托包含多个参数之一是chain,其中包含服务器返回的SSL证书链。如果远程服务器包含颁发者证书,则将在ChainElements集合中呈现该证书。这个对象通常包含几个元素:
-Leaf Certificate
    -Issuer Certificate
        -(Optional Issuer certs when available)

因此,您需要检查两件事:

  1. 如果ChainElements至少包含两个元素(例如,叶证书和建议发行者)。
  2. 如果ChainElements集合的第一个元素在ChainelementStatus集合中没有NotSignatureValid状态。

您可以将以下代码片段添加到RemoteCertificateValidationCallback委托中执行这些检查:

X509Certificate2 issuer = null;
if (
    chain.ChainElements.Count > 1 &&
    !chain.ChainElements[0].ChainElementStatus.Any(x => x.Status == X509ChainStatusFlags.NotSignatureValid)) {
    issuer = chain.ChainElements[1].Certificate;
}

如果运行此代码后issuer变量为null,则无法自动确定证书颁发者。这将需要进行一些额外的研究。如果它不是null,那么issuer变量将保存实际的颁发者证书。

选项2 -- 搜索本地证书存储

好的,根据您的评论,您想确定颁发者证书是否安装在本地证书存储中。通过阅读您的问题,我并没有理解。为什么我们要猜测您实际上正在寻找什么?最终,我仍然不确定您知道/理解自己想要实现什么。

如果您想查找颁发者是否安装在本地存储中,可以使用以下算法:

1)使用X509Certificate2Collection.Find方法,通过主题名称查找候选证书

2)在候选列表中查找(在步骤1中检索到的候选列表),其中Subject Key Identifier值与主题中的证书的Authority Key Identifier值相同。

X509Certificate2Collection certs = new X509Certificate2Collection();
// grab candidates from CA and Root stores
foreach (var storeName in new[] { StoreName.CertificateAuthority, StoreName.Root }) {
    X509Store store = new X509Store(storeName, StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly);
    certs.AddRange(store.Certificates);
    store.Close();
}
certs = certs.Find(X509FindType.FindBySubjectDistinguishedName, cert.Issuer, false);
if (certs.Count == 0) {
    Console.WriteLine("Issuer is not installed in the local certificate store.");
    return;
}
var aki = cert.Extensions["2.5.29.35"];
if (aki == null) {
    Console.WriteLine("Issuer candidates: ");
    foreach (var candidate in certs) {
        Console.WriteLine(candidate.Thumbprint);
    }
    return;
}
var match = Regex.Match(aki.Format(false), "KeyID=(.+)", RegexOptions.IgnoreCase);
if (match.Success) {
    var keyid = match.Groups[1].Value.Replace(" ", null).ToUpper();
    Console.WriteLine("Issuer candidates: ");
    foreach (var candidate in certs.Find(X509FindType.FindBySubjectKeyIdentifier, keyid, false)) {
        Console.WriteLine(candidate.Thumbprint);
    }
} else {
    // if KeyID is not presented in the AKI extension, attempt to get serial number from AKI:
    match = Regex.Match(aki.Format(false), "Certificate SerialNumber=(.+)", RegexOptions.IgnoreCase);
    var serial = match.Groups[1].Value.Replace(" ", null);
    Console.WriteLine("Issuer candidates: ");
    foreach (var candidate in certs.Find(X509FindType.FindBySerialNumber, serial, false)) {
        Console.WriteLine(candidate.Thumbprint);
    }
}
假设 cert 变量存储主题中的证书(用于搜索颁发者)。这种方法存在问题,因为它不会验证签名并可能返回虚警。

2
我问:“IssuerName属性有什么问题吗?”
回答是:“除了它不必与签名证书的主题匹配之外,即使匹配也无法知道它是否完全正确或只是具有相同主题的某些证书。”
这是不正确的。 PKIX § 6.1指出:“对于所有x∈{1,…,n-1},证书x的主题是证书x+1的发行者。”
子章节通过指示“将证书主题名称分配给working_issuer_name”,然后验证下一个证书链中的“…证书发行者名称是working_issuer_name”来澄清此问题。
您可能会感到困惑,因为您可能有许多具有相同名称但不同密钥的颁发者证书。在这种情况下,可以通过将颁发者的主题密钥标识符与主体的授权密钥标识符进行匹配来确定正确的签名密钥。然而,仅匹配密钥标识符是不够的:首先必须匹配名称。

不清楚您为什么要手动执行此操作。您应该能够提供所有可用的中间证书给您的路径构建库,并让它查找是否存在有效的链。


“ExtraStore”属性非常有用,但前提是您必须拥有正确的证书,而这并不总是成立。就像我之前提到的使用Thawte证书时,客户端并不一定拥有中间的Thawte CA证书来成功地在Web浏览器中工作,因为该证书是由Web服务器提供并在证书验证后被丢弃的。 - Crypt32
我通过添加一些代码并澄清了我的观点来编辑我的回复。希望这样能够更清楚明白。 - Crypt32
看起来很有前途。我没有使用路径构建库,因为在建立信任链时,中间证书可能来自存储或权威机构(通过AIA或其他方式),而我正在尝试定位本地存储中的证书。 - sharptooth
@sharptooth 你不能在证书集合上使用find方法吗?首先按名称选择,然后从子证书中提取授权密钥标识符,并缩小第一个结果,寻找具有该主题密钥标识符的发行者。我没有看到提取auth key id的API,所以您可能需要自己解析该扩展名的ASN.1结构。 - erickson
@sharptooth 如果您告诉我们您想要实现的最终目标,会更好。根据 http://stackoverflow.com/questions/35647719/how-do-i-programmatically-find-whether-the-intermediate-certificate-was-served-b 网址的内容,看起来您在这里提出的问题并不是您想要的答案。 - Crypt32
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接