【问题标题】:How to check if a X509 certificate has "Extended Validation" switched on?如何检查 X509 证书是否开启了“扩展验证”?
【发布时间】:2013-01-20 05:45:09
【问题描述】:

我正在努力寻找一种可靠的方法来从我的 C# (.Net 4.0) 应用程序中检查 X509Certificate(或 X509Certificate2)是否设置了“扩展验证”(EV)标志。有谁知道最好的方法吗?

【问题讨论】:

  • 您只是想检查证书是否是 EV 证书,还是还想执行实际的扩展验证?

标签: c# ssl x509certificate


【解决方案1】:

我想我会发布一个更完整的答案,即使这个问题已经很老了。我不会背弃现有的答案,以便完成这个答案。

EV 证书需要通过一些检查,以便浏览器认为该证书是 EV。

  1. 证书具有已知为 EV 策略的策略标识符。
  2. 证书的根指纹与固定的策略标识符匹配。
  3. 证书必须通过在线吊销检查。
  4. 如果证书的 notBefore(颁发日期)在 2015 年 1 月 1 日之后,则证书必须支持证书透明度。
  5. 证书必须由受信任的根颁发。
  6. 如果存在多个信任路径,则所有链都是有效的。

让我们逐一剖析。

策略标识符

证书有一个称为策略标识符的扩展。可以从X509Certificate2.Extensions 属性访问扩展。策略标识符扩展具有2.5.29.32 的对象标识符(“OID”)。因此,我们可以使用以下方式获取原始扩展:

var extension = certificate.Extensions["2.5.29.32"]

如果返回 null,意味着根本没有策略,您可以立即假设这不是 EV 证书。

虽然证书有某种政策,但更有可能。在这种情况下,您需要对数据进行解码。该属性将以原始 ASN.1 形式提供给您,我们需要理解它。

不幸的是,目前 .NET 中没有任何东西可以开箱即用。但是,如果您使用平台调用,CryptDecodeObjectEx 可以做到这一点。这样做的细节我会省略,但是有很多信息可以展示如何调用这个函数。您需要在将 lpszStructType 参数设置为值(IntPtr)16 的情况下调用它。这将返回一个CERT_POLICIES_INFO 结构,它有一个计数和指向CERT_POLICY_INFO 结构数组的指针。这个结构上有一个名为pszPolicyIdentifier 的字段。我们感兴趣的是这个策略 OID。

每个证书颁发机构都有一个或多个 OID,用于将证书制作为 EV。每个 CA 都会在其政策页面上记录它们。但是,获取最新列表的最佳位置可能是Chromium's Source Code

如果证书的策略与其中一个 OID 匹配,那么我们可以继续进行下一个检查。

根指纹

如果您查看上述链接中的 Chromium Source,您会看到除了策略标识符之外,它还保留了根的 SHA256 指纹。

这是因为除了具有正确 OID 的证书外,它还必须由指纹匹配的 CA 颁发。在 Chromium 源代码中,我们看到如下内容:

{{0x06, 0x3e, 0x4a, 0xfa, 0xc4, 0x91, 0xdf, 0xd3, 0x32, 0xf3, 0x08,
      0x9b, 0x85, 0x42, 0xe9, 0x46, 0x17, 0xd8, 0x93, 0xd7, 0xfe, 0x94,
      0x4e, 0x10, 0xa7, 0x93, 0x7e, 0xe2, 0x9d, 0x96, 0x93, 0xc0}},
    {
        // AC Camerfirma uses the last two arcs to track how the private key
        // is managed - the effective verification policy is the same.
        "1.3.6.1.4.1.17326.10.14.2.1.2", "1.3.6.1.4.1.17326.10.14.2.2.2",
    }

因此证书必须具有“1.3.6.1.4.1.17326.10.14.2.1.2”或“1.3.6.1.4.1.17326.10.14.2.2.2”策略标识符,但根必须具有 SHA1 指纹上面看到的二进制文件。

这可以防止恶意 CA 使用不属于它的策略 ID。

吊销检查

如果浏览器无法检查证书是否被吊销,则不会被视为 EV 证书。 在线必须进行撤销检查,尽管客户端可能会缓存结果。

您可以在使用X509Chain.Build 时执行撤销检查,方法是在调用Build 之前在链上设置适当的标志。

证书透明度

这个有点难检查,但谷歌在Certificate Transparency website 上有相应的文档。如果证书是在 2015 年 1 月 1 日之后颁发的,则需要证书透明度。如Chromium Project Page 所示,某些证书也被 Chrome 列入白名单。

可信根

这个相当简单,但证书必须属于受信任的根。如果证书是自签名的,则不能是 EV。这可以在调用X509Chain.Build()时再次检查。

多个信任路径

证书可能有多个信任路径,例如证书是否由交叉签名的根颁发。如果有多个信任路径,则所有路径都必须有效。同样,必须对所有路径进行吊销检查。如果任何路径显示证书已被吊销,则证书无效。

不幸的是,据我所知,.NET 甚至 Win32 都没有检查所有证书链甚至获取多个证书链的好方法。

综合所有这些,如果都通过了,那么该证书可以认为是EV证书。

【讨论】:

    【解决方案2】:

    您可以检查X509Certificate 是否包含这些OIds 之一。此外,您可以查看 Chromium 的 Source 以获取已实施 OId 的列表。您可以找到来源here。如果您想坚持使用 Firefox,可以获取实现 here

    我现在更新了我的源代码并对其进行了测试。我编写了一个小方法来验证X509Certificate2 与来自 Wikipedia/Chromium 的 OId 列表。在这种方法中,我使用的是 Wikipedia-List,最好使用 Chromium-List。


    如何保存 OId?

    每个CA有一个或多个ObjectIds OIds。正如您可能猜到的那样,它们保存为扩展,它们保存为策略扩展中的条目。要获得确切的扩展名,建议使用 Policy Extension 本身的 Oid,而不是使用友好名称。策略扩展的 OId 是 2.5.29.32

    提取信息

    要获取策略扩展的内部内容,我们可以使用System.Security.Cryptography.AsnEncodedData 将其转换为可读的string。字符串本身包含我们需要与 string[] 匹配的策略,以确保它是否包含 EV Certificate 的 OId 之一。

    来源

        /// <summary>
        /// Checks if a X509Certificate2 contains Oids for EV
        /// </summary>
        /// <param name="certificate"></param>
        /// <returns></returns>
        private static bool IsCertificateEV(X509Certificate2 certificate)
        {
            // List of valid EV Oids
            // You can find correct values here:
            // http://code.google.com/searchframe#OAMlx_jo-ck/src/net/base/ev_root_ca_metadata.cc&exact_package=chromium
            // or in Wikipedia
            string[] extendedValidationOids = 
            {
                "1.3.6.1.4.1.34697.2.1",
                "1.3.6.1.4.1.34697.2.2",
                "1.3.6.1.4.1.34697.2.1", 
                "1.3.6.1.4.1.34697.2.3", 
                "1.3.6.1.4.1.34697.2.4",
                "1.2.40.0.17.1.22",
                "2.16.578.1.26.1.3.3",
                "1.3.6.1.4.1.17326.10.14.2.1.2", 
                "1.3.6.1.4.1.17326.10.8.12.1.2",
                "1.3.6.1.4.1.6449.1.2.1.5.1",
                "2.16.840.1.114412.2.1",
                "2.16.528.1.1001.1.1.1.12.6.1.1.1",
                "2.16.840.1.114028.10.1.2",
                "1.3.6.1.4.1.14370.1.6",
                "1.3.6.1.4.1.4146.1.1",
                "2.16.840.1.114413.1.7.23.3",
                "1.3.6.1.4.1.14777.6.1.1", 
                "1.3.6.1.4.1.14777.6.1.2",
                "1.3.6.1.4.1.22234.2.5.2.3.1",
                "1.3.6.1.4.1.782.1.2.1.8.1",
                "1.3.6.1.4.1.8024.0.2.100.1.2",
                "1.2.392.200091.100.721.1",
                "2.16.840.1.114414.1.7.23.3",
                "1.3.6.1.4.1.23223.2", 
                "1.3.6.1.4.1.23223.1.1.1", 
                "1.3.6.1.5.5.7.1.1",
                "2.16.756.1.89.1.2.1.1",
                "2.16.840.1.113733.1.7.48.1",
                "2.16.840.1.114404.1.1.2.4.1",
                "2.16.840.1.113733.1.7.23.6",
                "1.3.6.1.4.1.6334.1.100.1",
            };
    
            // Logic:
            // Locate Certificate Policy Extension
            // Convert to AsnEncodedData (String)
            // Check if any of the EV Oids exist
            return (
                    from X509Extension ext in certificate.Extensions 
                    where ext.Oid.Value == "2.5.29.32" 
                    select new AsnEncodedData(ext.Oid, ext.RawData).Format(true))
                    .Any(asnConvertedData => extendedValidationOids.Where(asnConvertedData.Contains).Any()
                );
        }
    

    如果您需要一些资源来开始:

        static void Main(string[] args)
        {
            // Create Delegate for analysis of X509Certificate
            ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
    
            // Make sample request to EV-Website to get Certificate
            var wc = new WebClient();
            wc.DownloadString("https://startssl.com");  // EV
            wc.DownloadString("https://petrasch.biz");  // Not EV
            Console.ReadLine();
        }
    
        public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            var cert = (X509Certificate2) certificate;
            Console.WriteLine("Certificate: " + cert.GetNameInfo(X509NameType.SimpleName, true) + " -> " + IsCertificateEV(cert));
            return true;
        }
    

    如果有人知道实现此目标的更好方法,请告诉我们。

    【讨论】:

    • 太棒了——这让我到达了我需要去的地方。谢谢!
    • 不幸的是,这似乎不起作用。尝试与startssl.com 一起工作似乎很好。如果您尝试使用个人 SSL 证书,这也会告诉您它也是 EV。我发现 OID 保存为 CertificatePolicy。我目前正在使用 CAPI 进行测试以获取这些策略的列表,但我无法访问此列表。这将是解决方案。
    • 我解决了这个问题并重写了我的答案。它现在包含一个验证方法和一些快速测试它的源。此外,我删除了一些内容以了解如何实现这一点。
    • 该代码可能会被欺骗以为它拥有 EV 证书,而实际上它没有。想象一个包含 Verisign 的 EV OID 的证书,然后让 Digicert 对其进行签名。只要不与他们的 EV OID 冲突,Digicert 可能不会在意。甚至自签名证书也可能会欺骗它,具体取决于其他检查。为了解决这个问题,我认为 OID 需要与 Issuer 配对。
    • 感谢您的评论。这甚至会提高安全级别。
    猜你喜欢
    • 2014-06-08
    • 2011-04-06
    • 2012-06-06
    • 2015-10-09
    • 1970-01-01
    • 2019-09-26
    • 2020-05-23
    • 1970-01-01
    • 2012-09-28
    相关资源
    最近更新 更多