16

In my C# code I have a X509Certificate2 object which represents an SSL certificate (from a local store or from a successful HTTP request over SSL). The certificate is signed with some intermediate certificate which maybe is present in the local store, maybe not, so using X509Chain.Build() will probably not work.

A picture of Firefox certificate viewer (because I have no usable code yet):

enter image description here

Under Details, in the "Certificate Hierarchy", I see this:

  • DigiCert High Assurance EV Root CA
    • DigiCert SHA2 Extended Validation Server CA
      • github.com

My object represents "github.com", the lowest line in the chain. I need to programmatically identify the middle line ("DigiCert SHA2 Extended Validation Server CA").

How do I know a thumbprint or anything equivalent which would let me identify which certificate was used to sign my certificate?

Damian Yerrick
  • 4,602
  • 2
  • 26
  • 64
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • 4
    As you've [asked a meta question about the closure](http://meta.stackoverflow.com/q/317570/1364007), I will answer in detail there. Please allow me a few minutes to reply. – Wai Ha Lee Feb 24 '16 at 12:43
  • I have [answered](http://meta.stackoverflow.com/a/317581/1364007). Apologies for the delay. – Wai Ha Lee Feb 24 '16 at 13:09
  • What's wrong with the `IssuerName` property? – erickson Feb 25 '16 at 00:03
  • @erickson Nothing except it doesn't have to match the subject of the signer certificate and even if it matches there's no way to know if it's exactly the right certificate or just some certificate with the same subject. – sharptooth Feb 25 '16 at 08:23
  • @sharptooth, you are incorrect. Signature check will prove this. – Crypt32 Feb 25 '16 at 10:12
  • BTW, IssuerName in the leaf certificate must match the SubjectName of the issuer. – Crypt32 Feb 25 '16 at 10:54
  • @CryptoGuy How is such signature check done? – sharptooth Feb 25 '16 at 12:34
  • by taking issuer candidate's public key and validating target certificate's signature. You decrypt the signature and get signed hash value. Then, you calculate the hash for the target certificate. Of both hashes match -- candidate is valid issuer, otherwise -- not. – Crypt32 Feb 25 '16 at 12:39
  • @sharptooth you can find the full matching algorithm in [RFC 5280 (Section 6.1)](https://tools.ietf.org/html/rfc5280#section-6.1). Essentially, you start with the issuer/subject matching, and you verify the signature, and then you verify the attributes allow this kind of usage. It's OK to have multiple potential candidates in your trust anchors. – Bruno Feb 25 '16 at 14:37

2 Answers2

11

In this specific case (github.com), X509Chain.Build will work, because the end certificate contains information about the location of the issuer certificate (in the Authority Information Access extension).

But sometimes this may not work (for example, with Thawte certificates, because Thawte do not provide explicit information about issuer certificate location). And if the certificate is installed in the local certificate store, there is no way to automatically locate the issuer.

Option 1 -- SSL connection

However, if you work with an SSL certificate and you can establish an SSL session, you can get the certificate by adding a listener to the ServicePointManager.ServerCertificateValidationCallback property: https://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx

RemoteCertificateValidationCallback delegate contains several parameters, one of them is chain, which contains an SSL certificate chain returned by the server. And if the remote server contains an issuer certificate, it will be presented there in the ChainElements collection. This object usually contains several elements:

-Leaf Certificate
    -Issuer Certificate
        -(Optional Issuer certs when available)

So, you need to check two things:

  1. If ChainElements contains at least two elements (say, leaf certificate and proposed issuer).
  2. If first element of the ChainElements collection do not have NotSignatureValid status in the ChainelementStatus collection.

You could add the following piece of code in the RemoteCertificateValidationCallback delegate to perform these checks:

X509Certificate2 issuer = null;
if (
    chain.ChainElements.Count > 1 &&
    !chain.ChainElements[0].ChainElementStatus.Any(x => x.Status == X509ChainStatusFlags.NotSignatureValid)) {
    issuer = chain.ChainElements[1].Certificate;
}

If after running this piece of code the issuer variable is null, then you cannot automatically determine who is the issuer of your certificate. This process will require some additional research. And it is not null, then issuer variable will hold actual issuer certificate.

Option 2 -- searching local certificate store

Ok, according to your comments, you want to determine whether the issuer certificate is installed in the local certificate store or not. By reading your question I didn't get it. Why we should guess what you actually are looking? Eventually, I'm still unsure if you know/understand what you want to achieve.

If you want to find whether the issuer is installed in the local store, you can use the following algorithm:

1) use X509Certificate2Collection.Find method and find candidate certificates by their subject name

2) find in the candidate list (retrieved in step 1) ones that have Subject Key Identifier value the same as Authority Key Identifier value of the certificate in the subject.

X509Certificate2Collection certs = new X509Certificate2Collection();
// grab candidates from CA and Root stores
foreach (var storeName in new[] { StoreName.CertificateAuthority, StoreName.Root }) {
    X509Store store = new X509Store(storeName, StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly);
    certs.AddRange(store.Certificates);
    store.Close();
}
certs = certs.Find(X509FindType.FindBySubjectDistinguishedName, cert.Issuer, false);
if (certs.Count == 0) {
    Console.WriteLine("Issuer is not installed in the local certificate store.");
    return;
}
var aki = cert.Extensions["2.5.29.35"];
if (aki == null) {
    Console.WriteLine("Issuer candidates: ");
    foreach (var candidate in certs) {
        Console.WriteLine(candidate.Thumbprint);
    }
    return;
}
var match = Regex.Match(aki.Format(false), "KeyID=(.+)", RegexOptions.IgnoreCase);
if (match.Success) {
    var keyid = match.Groups[1].Value.Replace(" ", null).ToUpper();
    Console.WriteLine("Issuer candidates: ");
    foreach (var candidate in certs.Find(X509FindType.FindBySubjectKeyIdentifier, keyid, false)) {
        Console.WriteLine(candidate.Thumbprint);
    }
} else {
    // if KeyID is not presented in the AKI extension, attempt to get serial number from AKI:
    match = Regex.Match(aki.Format(false), "Certificate SerialNumber=(.+)", RegexOptions.IgnoreCase);
    var serial = match.Groups[1].Value.Replace(" ", null);
    Console.WriteLine("Issuer candidates: ");
    foreach (var candidate in certs.Find(X509FindType.FindBySerialNumber, serial, false)) {
        Console.WriteLine(candidate.Thumbprint);
    }
}

assuming that cert variable stores certificate in subject (for which the issuer is searched). This approach have issues as it do not validate signature and may return false positives.

Crypt32
  • 12,850
  • 2
  • 41
  • 70
2

I asked, "What's wrong with the IssuerName property?"

The reply was, "Nothing except it doesn't have to match the subject of the signer certificate and even if it matches there's no way to know if it's exactly the right certificate or just some certificate with the same subject."

This is incorrect.

PKIX § 6.1 says, "for all x in {1, ..., n-1}, the subject of certificate x is the issuer of certificate x+1."

Subsections clarify this by instructing, "Assign the certificate subject name to working_issuer_name," and then, for the next certificate in the chain, verifying that the "… certificate issuer name is the working_issuer_name."

You might be confused because you may have many issuer certificates with the same name, but different keys. In that case, the correct signing key can be identified by matching the issuer's subject key identifier to the subject's authority key identifier. Matching key identifiers is not sufficient, however: the names have to match first.

It's not clear why you are trying to do this manually. You should be able to provide all available intermediates to your path-building library, and let find a valid chain if any exists.

Community
  • 1
  • 1
erickson
  • 265,237
  • 58
  • 395
  • 493
  • `ExtraStore` property is helpful if you have the right certificate which is not always the case. As I mentioned with Thawte, clients do not necessary have intermediate Thawte CA certificate to successfully work in web browser, because this certificate is delivered by the web server and is disposed after certificate validation. – Crypt32 Feb 25 '16 at 17:07
  • @CryptoGuy I don't see any relevance in your comment. If the server sends you the intermediate, you have it, and can put it into `ExtraStore`. Whether it's the "right" certificate is something you test by validation. Can you make your point more clearly? – erickson Feb 25 '16 at 17:24
  • I edited my response by adding some code and clarified my point. Hope this make it clear. – Crypt32 Feb 25 '16 at 17:46
  • Looks promising. I'm not using the path building library because while the trust chain is being built the intermediate certificates may come either from the store or from the authority (via AIA or something) and what I'm trying to do is locate the certificate in the local store. – sharptooth Feb 26 '16 at 10:24
  • @sharptooth "*from a local store or [...] request over SSL*": what you're trying to do doesn't necessarily make sense (or at least you'll need to be careful what you're trying to do). Validating a certificate in your local store with others needs further specification. In particular, you'll not only need to match issuers and signatures to build your chain, but you'll also need to match date/time validity and purpose attributes. In principle, you may otherwise have multiple candidates for your matches. Path building libraries tend to do all that for you, within the context of an SSL connection. – Bruno Feb 26 '16 at 14:17
  • @sharptooth Can't you use the [find](https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2collection.find%28v=vs.110%29.aspx) method on your collection of certificates? First select by name, then extract the authority key identifier from the child certificate and narrow down the first results looking for issuers with that subject key identifier. I don't see API for extracting the auth key id directly, so you may have to parse the ASN.1 structure of that extension yourself. – erickson Feb 26 '16 at 18:25
  • @sharptooth it could be better if you would tell us the final goal you want to achieve. Looking at http://stackoverflow.com/questions/35647719/how-do-i-programmatically-find-whether-the-intermediate-certificate-was-served-b, it appears that the question you asked here is not what you are looking for. – Crypt32 Feb 27 '16 at 16:20
  • @CryptoGuy Actually I need both - verify that the right certificate is in the server store (that will be server side check) and verify that the certificate is being served (that will be client side check). – sharptooth Feb 29 '16 at 09:00