Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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. Does anyone know the best method?

like image 574
KenD Avatar asked Feb 05 '13 10:02

KenD


People also ask

How do I know if I have extended validation certificate?

The primary way to identify an EV certificate is by referencing the Certificate Policies extension field. Each issuer uses a different object identifier (OID) in this field to identify their EV certificates, and each OID is documented in the issuer's Certification Practice Statement.

How do I know if my certificate is EV or DV?

For both DV and OV, only the URL (no company name) show in the identity field. When the site has EV, the company name is displayed along with the URL. Chrome and MSIE use green background to the company name, while Firefox will use green text for the company name.

How do I know if SSL is EV?

In chrome 77, you can get to the details of EV SSL certificate by clicking on the padlock that shows to the left to of the website URL in the address bar. This will open a bubble displaying the required information.

What is the difference between OV and EV certificates?

Extended Validation (EV), like OV, verifies the identity of an organization. However, EV represents a higher standard of trust than OV and requires more rigorous validation checks to meet the requirements set by the CA/Browser Forum.


2 Answers

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.

like image 112
Dennis Alexander Avatar answered Oct 03 '22 04:10

Dennis Alexander


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.

like image 24
vcsjones Avatar answered Oct 03 '22 04:10

vcsjones