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?
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.
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.
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.
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.
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.
Each CA
has one or more ObjectIds OId
s. 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
.
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
.
/// <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.
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.
Let's dissect each of these.
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.
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.
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
.
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.
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()
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With