How to check if a X509 certificate has “Extended Validation” switched on?

后端 未结 2 522
时光说笑
时光说笑 2020-12-28 08:29

I\'m struggling to find a reliable way to check from my C# (.Net 4.0) application if an X509Certificate (or X509Certificate2) has the \"Extended Validation\" (EV) flag set.

相关标签:
2条回答
  • 2020-12-28 08:51

    I thought I would post a more complete answer even though this question is quite old. I won't piggy back off of the existing answer so that this one is complete.

    An EV certificate has a few checks that need to pass in order for a browser to consider that the certificate is EV.

    1. That the certificate has a Policy Identifier that is known to be an EV policy.
    2. The certificate's root's thumbprint matches a pinned policy identifier.
    3. The certificate must pass online revocation checking.
    4. If the certificate's notBefore (issuance date) is after 1/1/2015, the certificate must support Certificate Transparency.
    5. The certificate must be issued by a trusted root.
    6. That all chains are valid if there are multiple trust paths.

    Let's dissect each of these.

    Policy Identifier

    A certificate has an extension called policy identifiers. Extensions can be accessed from X509Certificate2.Extensions property. The policy identifier extension has an Object Identifier ("OID") of 2.5.29.32. So we can get the raw extension using something like this:

    var extension = certificate.Extensions["2.5.29.32"]
    

    If this returns null, meaning there is no policy at all, you can right off the bat assume this is not an EV certificate.

    More likely though the certificate has some kind of policy. In this case, you need to decode the data. The attribute will give it to you in raw ASN.1, we need to make sense out of it.

    Unfortunately there is nothing in .NET that can do it out of the box today. However CryptDecodeObjectEx can do it if you use platform invoke. The specifics on doing so I'll leave out, but there is plenty of information around to show how to call this function. You'll want to call it with the lpszStructType parameter set to a value of (IntPtr)16. This will give you back an CERT_POLICIES_INFO structure, which has a count and pointer to an array of CERT_POLICY_INFO structures. This structure has a field on it called pszPolicyIdentifier. It's this policy OID that we are interested in.

    Every certificate authority has one or more OIDs they use to make a certificate as EV. Every CA documents them on their policies page. However, the best place to get an up-to-date list of this is probably Chromium's Source Code.

    If the certificate has a policy that matches one of those OIDs, then we can move on to the next check.

    Root Fingerprint

    If you look at the Chromium Source in the above link, you'll see in addition to policy identifiers, it also keeps the SHA256 fingerprint of the root.

    This is because in addition to the certificate having the proper OID, it must be issued by a CA whose fingerprint matches. In the Chromium source, we see something like this:

    {{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",
        }
    

    So the certificate must have either the "1.3.6.1.4.1.17326.10.14.2.1.2" or "1.3.6.1.4.1.17326.10.14.2.2.2" policy identifiers, but the root must have a SHA1 fingerprint of the binary seen above.

    This prevents a rogue CA from ever using a policy ID it doesn't own.

    Revocation Checking

    If the browser is unable to check if the certificate is revoked, then it will not be considered an EV certificate. Online revocation checking must be done, though the client may cache the result.

    You can perform revocation checking when using X509Chain.Build by setting the appropriate flags on the chain before calling Build.

    Certificate Transparency

    This one is a bit harder to check, but Google has appropriate documentation on the Certificate Transparency website. If the certificate was issued after 1/1/2015, certificate transparency is required. Some certificates are also whitelisted by Chrome as indicated on the Chromium Project Page.

    Trusted Root

    This one is fairly straight forward, but the certificate must belong to a trusted root. If the certificate is self signed, it cannot be EV. This can be checked again when calling X509Chain.Build().

    Multiple Trust Paths

    It is possible for a certificate to have multiple trust paths, say if the certificate was issued by a root that was cross-signed. If there are multiple trust paths, all paths must be valid. Likewise revocation checking must be done with all paths. If any of the paths show the certificate as revoked, then the certificate is not valid.

    Unfortunately .NET and even Win32 do not have a great means of checking all certificate chains or even getting more than one chain, as far as I know.

    Combining all of these, if they all pass, then the certificate can be considered to be an EV certificate.

    0 讨论(0)
  • 2020-12-28 09:00

    You could check if the X509Certificate contains one of these OIds. Additionally you can check Chromium's Source for a list of implemented OIds. You can find the Source here. If you'd like to stick to Firefox, you can grab the implementation here.

    I now updated my source and tested it. I've written a small method to validate a X509Certificate2 against the OId-List from Wikipedia/Chromium. In this method I am using the Wikipedia-List, it might be better to take the Chromium-List instead.


    How is the OId saved?

    Each CAhas one or more ObjectIds OIds. They are not saved as an Extension as you might guess, they are saved as an entry within the Policy Extensions. To get the exact Extension it's recommended to use the Oid of the Policy Extension itself rather then using a Friendly Name. The OId of the Policy Extensions is 2.5.29.32.

    Extracting the Information

    To get the inner content of the Policy Extensions we can use System.Security.Cryptography.AsnEncodedData to convert it to a readable string. The string itself contains the policies we need to match against our string[] to ensure if it contains one of the OIds of an EV Certificate.

    Source

        /// <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()
                );
        }
    

    If you need some source to get started:

        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;
        }
    

    If someone knows a better way to achieve this goal, please let us know.

    0 讨论(0)
提交回复
热议问题