How to compare two X509Certificate2 c# - 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.

Related

What do the different status error codes for the CheckValidationResult certificateProblem parameter stand for?

In the MSDN Example of the ICertificatePolicy interface, there is an enum which lists all the possible status error codes that can be returned via the ValidationCallback:
public enum CertificateProblem : long
{
CertEXPIRED = 0x800B0101,
CertVALIDITYPERIODNESTING = 0x800B0102,
CertROLE = 0x800B0103,
CertPATHLENCONST = 0x800B0104,
CertCRITICAL = 0x800B0105,
CertPURPOSE = 0x800B0106,
CertISSUERCHAINING = 0x800B0107,
CertMALFORMED = 0x800B0108,
CertUNTRUSTEDROOT = 0x800B0109,
CertCHAINING = 0x800B010A,
CertREVOKED = 0x800B010C,
CertUNTRUSTEDTESTROOT = 0x800B010D,
CertREVOCATION_FAILURE = 0x800B010E,
CertCN_NO_MATCH = 0x800B010F,
CertWRONG_USAGE = 0x800B0110,
CertUNTRUSTEDCA = 0x800B0112
}
What do these different status codes stand for? They are not in the SSPI Status Code document that the links reference.
I spent a long time trying to figure this out, and was finally able to find a VMware C# file containing explanations of the different codes:
CertEXPIRED
A required certificate is not within its validity period
CertVALIDITYPERIODNESTING
The validity periods of the certification chain do not nest correctly
CertROLE
A certificate that can only be used as an end-entity is being used as a CA or visa versa
CertPATHLENCONST
A path length constraint in the certification chain has been violated
CertCRITICAL
An extension of unknown type that is labeled 'critical' is present in a certificate
CertPURPOSE
A certificate is being used for a purpose other than that for which it is permitted
CertISSUERCHAINING
A parent of a given certificate in fact did not issue that child certificate
CertMALFORMED
A certificate is missing or has an empty value for an important field, such as a subject or issuer name
CertUNTRUSTEDROOT
A certification chain processed correctly, but terminated in a root certificate which isn't trusted by the trust provider
CertCHAINING
A chain of certs didn't chain as they should in a certain application of chaining
CertREVOKED
A certificate was explicitly revoked by its issuer
CertUNTRUSTEDTESTROOT
The root certificate is a testing certificate and the policy settings disallow test certificates
CertREVOCATION_FAILURE
The revocation process could not continue - the certificate(s) could not be checked
CertCN_NO_MATCH
The certificate's CN name does not match the passed value
CertWRONG_USAGE
The certificate is not valid for the requested usage
CertUNTRUSTEDCA
Untrusted CA

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

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.

How do you generate an x509Certificate based solely on a public key in RSA format?

I have an RSA public key in an XML format. I need to take this key and create an x.509 Certificate using that public key. I do not have access to the private key to complete the key pair. All examples that I have found involve either generating the key pair or having access to both the public and private keys.
Below is a small snippet from a test routine I have been working on to accomplish this.
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.FromXmlString("<RSAKeyValue><Modulus>puEVvRbrLAz.......c1W5j/vqJSUrXo16k=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>");
Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters key = Org.BouncyCastle.Security.DotNetUtilities.GetRsaPublicKey(provider);
//<!*** Do some code to take RsaKeyParameters and create an x.509 Certificate ***>
var fOut = new System.IO.StreamWriter(#"C:\certificate.pem", false);
var pw = new Org.BouncyCastle.OpenSsl.PemWriter(fOut);
pw.WriteObject(key);
Just to clarify the issue of impossibility of generating a certificate using a public key only.
AFAIK, certificate must be signed. And it must be done with a private key - that's why you need one for generating a certificate. If certificate is signed with the paired private key of the open key you are going to issue a certificate for, than it is a self-signed certificate. Otherwise you need a private key of so called Certificate Authority for signing.

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