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

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?

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.

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.
That the certificate has a Policy Identifier that is known to be an EV policy.
The certificate's root's thumbprint matches a pinned policy identifier.
The certificate must pass online revocation checking.
If the certificate's notBefore (issuance date) is after 1/1/2015, the certificate must support Certificate Transparency.
The certificate must be issued by a trusted root.
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.

Related

Setting CX509ExtensionCertificatePolicies in CX509CertificateRequestPkcs10

I want to add a specific certificate policy extension to my CSR but I am not sure how to do it.
It looks like I have a class CX509ExtensionCertificatePolicies to handle those scenarios but the CX509CertificateRequestPkcs10 interface for this attribute is a read only.
I want to add these property to my CSR. I am adding other details as follows,
CX509CertificateRequestPkcs10 certRequest = new CX509CertificateRequestPkcs10();
certRequest.InitializeFromPublicKey(X509CertificateEnrollmentContext.ContextMachine, publicKey, "");
CX500DistinguishedName subjectName = new CX500DistinguishedName();
subjectName.Encode("CN=Test.Cert.com", X500NameFlags.XCN_CERT_NAME_STR_NONE);
certRequest.Subject = subjectName;
//CX509ExtensionCertificatePolicies CertPolicy = new CX509ExtensionCertificatePolicies();
//CertPolicy.Initialize(null, EncodingType.XCN_CRYPT_STRING_BASE64, "");
//certRequest.x = CertPolicy;
certRequest.Encode();
#######################Updating based on below response #############
I am using the following code but I don't see any impact on the issued certs.
obj.InitializeFromValue("1.3.6.1.4.1.911.108.100.1");
CX509ExtensionCertificatePolicies CertPolicy = new CX509ExtensionCertificatePolicies();
CCertificatePolicies cp = new CCertificatePolicies();
CCertificatePolicy cp1 = new CCertificatePolicy();
cp1.Initialize(obj);
cp.Add(cp1);
CertPolicy.InitializeEncode(cp);
// CertPolicy.Initialize(obj, EncodingType.XCN_CRYPT_STRING_BASE64, Convert.ToBase64String(test));
certRequest.X509Extensions.Add((CX509Extension)CertPolicy);
I still see the certs are issued with the default oids.
certRequest.Encode();
You need to add certificate policies extension to extension list:
CX509ExtensionCertificatePolicies CertPolicy = new CX509ExtensionCertificatePolicies();
CertPolicy.Initialize(null, EncodingType.XCN_CRYPT_STRING_BASE64, "");
// add at least one policy qualifiers of type of IPolicyQualifier
<...>
// add configured extension with policy qualifiers to CSR
certRequest.X509Extensions.Add(CertPolicy);
Keep in mind that certificate policy extension in CSRs are often ignored by CAs and you may not get issued certificate with requested policy.
Update based on OP question edits
I see that you are using Microsoft CA to sign certificates. As I already mentioned, CAs often ignore user-requested policies and Microsoft CA is no exception. The problem is that certificate policy is not an arbitrary thing. Every certificate policy is identified by OID and linked to Certificate Practices Statement (CPS) which is somewhat a legal document. PKI administrators define and legalize CPS and put a list of valid policies at CA level and CA then is limited only to these policies. I would suggest to read my two-part blog post series on this subject:
Certificate Policies extension โ€“ all you should know (part 1)
Certificate Policies extension โ€“ all you should know (part 2)
What you have to do in this case:
ensure if desired certificate policy is asserted in CA certificate itself. If not, you have to re-configure your CA and renew CA certificate with new key pair.
configure desired certificate policy in target certificate template. Go to Certificate Templates MMC snap-in, edit requested certificate template, go to Extensions tab and configure Certificate Policies extensions appropriately.

How exactly is a Certificate chain built?

As the title states i want to know how the certificate chain is build internaly.
I have a self signed root certificate (CA) und an incoming user certificate, which supposedly has been signed by my root, which is what i have to test. Testing a X509Certificate2 can be done with the X509Certificate2.Verify() method, which uses the X509Chain with some standard settings. To have full control over the process i am using the X509Chain directly. As my root is not installed on the machine (not in trusted root certificate store) i am adding it to the ExtraStore of the ChainPolicy.
The whole magic happens within X509Chain.Build() method, where i pass my 'to be tested' user certificate. The chain gets build, starting with the passed certificate and ending with the self signed root. But how exactly? How does one test wether a certificate has been signed by one or another root? Does it use digital signatures internally?
I tried to debug the whole thing but ended at an DLL import, which uses the CertGetCertificateChain method from Libraries.Crypt32. That is C++ stuff, which exceeds my domain of knowledge.
Simplified code so far:
using (var chain = new X509Chain
{
ChainPolicy =
{
RevocationMode = X509RevocationMode.NoCheck,
RevocationFlag = X509RevocationFlag.ExcludeRoot,
VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority,
VerificationTime = DateTime.Now,
UrlRetrievalTimeout = new TimeSpan(0, 0, 0)
}
})
{
chain.ChainPolicy.ExtraStore.Add(authority);
if (!chain.Build(signedCertificate))
{
//Errorhandling
}
//Root certificate should always be last in the X509ChainElements collection
var isSignedByCorrectRootCert =
chain.ChainElements
.Cast<X509ChainElement>()
.Last()
.Certificate.Thumbprint == authority.Thumbprint;
if (!isSignedByCorrectRootCert)
{
//Errorhandling
}
}
Does it use digital signatures internally?
exactly.
I tried to debug the whole thing but ended at an DLL import, which uses the CertGetCertificateChain method from Libraries.Crypt32.
yes, .NET X509Chain class is nothing else than plain wrapper over CryptoAPI native (C++) functions. I would say that 95+% of cryptography stuff in .NET are wrappers over CryptoAPI functions.
Years ago I wrote a blog post that explains how chain building is performed in Microsoft Windows: Certificate Chaining Engine โ€” how it works. This post explains how chaining engine builds the chain and bind certificates in the chain before sending it to validation routine. Chain validation is a much more complex process. Validation logic is described in RFC 5280, ยง6

How to compare two X509Certificate2 c#

How can I compare two X509Certificate2 objects?
I need to find whether two certificates are same. It's for user authentication purpose and I need to find if both the certificates are of the same person.
Can I use its serial number or thumprint properties? or is there any other methods?
Also I am new to this and would like to know is it safe to use X509Certificate for user authentication?
A thumbprint is a unique value for the certificate, it is commonly used to find a particular certificate in a certificate store. More...
The serial number is a unique number issued by the certificate issuer. More...
As #Rattle pointed out:
The Equals method should not be used when comparing certificates for
security purposes. Instead, use a hash of the RawData property, or the
Thumbprint property.
Late to the party (recently needed to compare two X509 certificates myself).
The X509Certificate class has an Equals() method:
Two objects are considered equal if they are X509Certificate objects
and they have the same issuer and serial number.
using System;
using System.Security.Cryptography.X509Certificates;
public class X509
{
public static void Main()
{
// The paths to the certificate signed files
string Certificate = #"Signed1.exe";
string OtherCertificate = #"Signed2.exe";
// Starting with .NET Framework 4.6, the X509Certificate type implements the IDisposable interface...
using (X509Certificate certOne = X509Certificate.CreateFromCertFile(Certificate))
using (X509Certificate certTwo = X509Certificate.CreateFromCertFile(OtherCertificate))
{
bool result = certOne.Equals(certTwo);
Console.WriteLine(result);
}
}
}
Late, but...
Please note that an X509 certificate is, essentially, a binding between an identity (a distinguised name, the 'name' of the certificate owner) and a public key, signed with the private key of a third-party (known as Certification Authority or CA).
X509 certificates are public, and they can be cloned, copied, etc. A certificate alone is not enough for authenticating its owner.
Authentication schemes typically must ensure that the owner possess the private key associated with the public key in the certificate, typically by performing a private-key operation like signing a challenge (a nonce or random sequence of bits). The receiver then verifies that the operation was performed with the proper private key (using the public key in the certificate). Only when both keys come from the same pair that verification will succeed (this is the essence of public key cryptography).
And, additionally, the receiver must validate the certificate: look at the issuer and owner identities, check that the issuer is an approved CA, that the certificate is appropriate for the intended usage (Validity Dates, Policy and Key Usage), that the certificate signature is valid by using the public key in the CA certificate -which is either directly trusted, or signed by a higher-level CA which is trusted-, that the certificate is yet valid (certificates expire, nothing is eternal!), and that the certificate is not revoked by the CA that emitted it.
An important thing pointed by #Rattle is that for comparing certificates (for example, when comparing a code signer's certificate with a set of approved certificates), you should not use the X509Certificate.Equals() method.
Compare the thumbprints instead.

Can I ensure, using C#, that an X509Certificate was issued by a trusted authority?

If I use X509Certificate.CreateFromSignedFile to get the certificate used to sign a file, can I confirm that it was signed by a trusted authority - and isn't just a "self-signed" cert of some kind?
I want to extract the "Subject" (company) name from the cert to ensure that an unmanaged DLL I'm using is unmolested (I can't checksum it as it's updated frequently and independently) and official.
However, I'm concerned that a fake DLL could be signed with a "self-signed" cert and return the original company's name. So, I want to ensure the the cert was issued by Versign, Thwate or similar (anything installed on the cert repository on the machine will be fine).
How can I do this, if at all, when using X509Certificate.CreateFromSignedFile? Or does it do this automatically (i.e. a "self-signed" cert will fail)?
If it is not valid certificate you will get an exception. What concerns that you want to check the Company name and etc...
Here is the code :
ServicePointManager.ServerCertificateValidationCallback +=
new System.Net.Security.RemoteCertificateValidationCallback(customXertificateValidation);
private static bool customXertificateValidation(
object sender, X509Certificate cert,
X509Chain chain, System.Net.Security.SslPolicyErrors error)
{
// check here 'cert' parameter properties (ex. Subject) and based on the result
// you expect return true or false
return false/true;
}
EDIT : The above code is suitable only when requesting https resource which is got not valid(self-signed, expired...etc) certificate. What concerns extracting signatures from signed files please check here : Extracting Digital Signatures from Signed Files with .NET
Isn't Verify() method enough?

Generate and sign certificate in different machines C#

I need to generate certificates to be used in secure communication between agents. Each agent generates a certificate and must send it to the system CA, in another machine, to be signed (and trusted by the other agents). I am doing it using C# with the following code for the agent:
//generate certificate
ECDsa elipticCurveNistP256Key = ECDsa.Create(ECCurve.CreateFromValue("1.2.840.10045.3.1.7")); // nistP256 curve
CertificateRequest certificateRequest = new CertificateRequest("CN=" + agentId, elipticCurveNistP256Key, HashAlgorithmName.SHA256);
certificateRequest.CertificateExtensions.Add(
new X509BasicConstraintsExtension(false, false, 0, false));
certificateRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(
X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation,
false));
// Add the SubjectAlternativeName extension
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Parse(agentIpAddress));
certificateRequest.CertificateExtensions.Add(sanBuilder.Build());
certificateRequest.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.8")
},
true));
certificateRequest.CertificateExtensions.Add(
new X509SubjectKeyIdentifierExtension(certificateRequest.PublicKey, false));
And the following code for the CA system:
X509Certificate2 signedCertificate = certificateRequest.Create(
caCertificatePFX,
DateTimeOffset.UtcNow.AddDays(-1),
DateTimeOffset.UtcNow.AddDays(30),
new byte[] {1, 2, 3, 4});
Of course, I use also code for communication between the machines that I do not show here. But I have at least two problems:
I would like to have a complete separation between certificate generation and signing but even with lots of tries this was the only code that I could manage to get to work. If I am not mistaken this code has the certificate creation at the CA system which is not the ideal scenario (CA has access to agent private key) but if I didn't find a better one it's something I can accept.
The second problem is that even if I accept the first problem I still need to send the CertificateRequest object from one machine to another and CertificateRequest is not serializable. I have found the method CreateSigningRequest() that "Creates an ASN.1 DER-encoded PKCS#10 CertificationRequest value representing the state of the current object." however I have not found a way to then make it be a CertificateRequest object again so that I can run the CA system code.
Does anyone know how I can do this? Hopefully to completely separate certificate generation and certificate signing, but if that is not possible at least to create CertificateRequest object back.
I am running .Net Framework 4.7.2 that I need to maintain in order to use previously developed Windows Forms.
Thanks
As you noted, there's not a way to read back the PKCS#10 request. That's largely because too many of the things are missing to be an "OK" Certificate Authority, so having a reader would just make for a lot of "bad" Certificate Authorities. (Since your CA doesn't support revocation it's also a "bad" CA, but you're mitigating that with short lifetime certificates.)
The PKCS#10 request contains:
A data format version
A name (presumably the one that the requester wants)
A public key
Attributes
The requested extensions comes here (EKUs, Subject Alternative Name, etc)
A signature, to prove that the requester has the private key.
The data format version is irrelevant if you're not using the data format, and the signature is not really important for "closed" issuers (CAs that only issue certificates to directly-known parties). So you just need to transport the public key and whatever other data you need for the request (looking at your current code, the agent ID and IP address).
The only tricky part is sending the public key... but with .NET Core 3.0+ you can normalize all of the keys to their SubjectPublicKeyInfo format:
byte[] spki = elipticCurveNistP256Key.ExportSubjectPublicKeyInfo();
While it would have been exceedingly clever for the PublicKey type to have an ImportSubjectPublicKeyInfo method, that hasn't happened yet. For generic parsing you'd want to try all the major key types, but since you're a closed CA on the other side you can know a priori that it's ECDSA:
using (ECDsa clientPub = ECDsa.Create())
{
clientPub.ImportSubjectPublicKeyInfo(transmittedSpki, out _);
// the rest of your code goes here.
}
I would strongly suggest to use CA software to sign certificate requests. Period.
Any attempt to roll own your CA code will make the solution unreliable, fragile and error-prone in many aspects. There are several options, starting with Microsoft ADCS (Windows) and EJBCA (Windows/Linux). Any other design will be simply bad.

Categories