如何以编程方式查找用于签署给定证书的证书?

时间:2016-02-24 11:40:49

标签: c# .net ssl x509certificate x509certificate2

在我的C#代码中,我有一个X509Certificate2对象,它代表一个SSL证书(来自本地存储或来自SSL的成功HTTP请求)。证书是使用某些中间证书签署的,这些证书可能存在于本地商店中,可能不存在,因此使用X509Chain.Build()可能无效。

Firefox证书查看器的图片(因为我还没有可用的代码):

enter image description here

在详细信息下,在"证书层次结构"中,我看到了:

  • DigiCert High Assurance EV Root CA.
    • DigiCert SHA2扩展验证服务器CA.
      • github.com

我的对象代表" github.com",链中的最低行。我需要以编程方式识别中间行(" DigiCert SHA2扩展验证服务器CA")。

我如何知道指纹或其他任何可以识别用于签署证书的证书?

2 个答案:

答案 0 :(得分:11)

在这种特定情况下(github.com),X509Chain.Build将起作用,因为结束证书包含有关颁发者证书位置的信息(在授权信息访问扩展中)。

但有时这可能不起作用(例如,使用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集合的第一个元素在NotSignatureValid集合中没有ChainelementStatus状态。
  3. 您可以在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中检索),其中主题密钥标识符值与主题中证书的授权密钥标识符值相同。

    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变量在主题中存储证书(为其搜索发行者)。这种方法存在问题,因为它不验证签名并且可能返回误报。

答案 1 :(得分:2)

我问," IssuerName属性有什么问题?"

答复是,"没有什么,除了它不必匹配签名者证书的主题,即使它匹配,也无法知道它是否完全是正确的证书或只是一些具有相同主题的证书。"

这是不正确的。

PKIX § 6.1说,"对于{1,...,n-1}中的所有x,证书x的主题是证书x + 1的发行者。"

小节通过指示来解释这一点,"将证书主题名称分配给working_issuer_name,"然后,对于链中的下一个证书,验证" ...证书颁发者名称是否为working_issuer_name。"

您可能会感到困惑,因为您可能拥有许多具有相同名称但不同密钥的颁发者证书。在这种情况下,可以通过将发行者的subject key identifier与主体的权限密钥标识符进行匹配来识别正确的签名密钥。但是,匹配密钥标识符是不够的:名称必须先匹配。

目前尚不清楚为什么要手动执行此操作。您应该provide all available intermediates path-building library,到{{3}}并找到有效链(如果存在)。