Search code examples
c#asp.netx509asn.1x509certificate2

Decode ASN.1 data from CAC/x509 certificate extension (Subject Directory Attributes > Country of Citizenship)


I need to read a x509 property not published via the X509Certificate2 class. Therefor I need to read it's OID and decoded the ASN.1 data myself.

Specifically, I need to read "Subject Directory Attributes" > "Country of Citizenship" OID 2.5.29.9 and 1.3.6.1.5.5.7.9.4 respectively. Keep in mind Subject Directory Attributes is a collection and I'm only truly after Citizenship.

Right now i can obtain the ASN.1 data and can run it through this javascript decoder and see OID (1.3.6.1.5.5.7.9.4) and the value I'm after (US) but I can't figure out how to decode the data in C# and further more target the Citizenship OID. Here is the following code I have so far:

var citizenship = (
    from X509Extension ext in x509.Extensions
    where ext.Oid.Value == "2.5.29.9"
    select new AsnEncodedData(ext.Oid, ext.RawData).Format(true)
);

And the RawData in hex is "30 12 30 10 06 08 2b 06 01 05 05 07 09 04 31 04 13 02 55 53"

[EDIT] I am looking to read this value within a webpage and serializing to disk doesn't really suite my needs here.


Solution

  • You can use Asn1Net.Reader. When you open the "RawData" (but saved as binary to a file) in ASN.1 editor or Asn1Viewer you can see the structure of the ASN.1 data. Below is the picture taken from ASN.1 editor.

    Structure of you citizenship ASN.1 object

    Then with Asn1Net.Reader (nuget here) you can parse the value with this code (no not null checking has been done; and I am using nunit test here)

    [Test]
    [TestCase("30 12 30 10 06 08 2B 06 01 05 05 07 09 04 31 04 13 02 55 53")]
    public void ReadCitizenship(string example)
    {
        // translates hex to byte[]
        var encoded = Helpers.GetExampleBytes(example);
    
        // initialize reader
        var reader = Helpers.ReaderFromData(encoded);
    
        // parse ASN.1 object to the end and read value as byte[]
        var subjDirAttributes = reader.ReadToEnd(true);
    
        // NO Checking for null has been done
        //                                  sequence      sequence      set           printable string
        var citizenship = subjDirAttributes.ChildNodes[0].ChildNodes[0].ChildNodes[1].ChildNodes[0];
    
        var value = citizenship.ReadContentAsPrintableString();
        Assert.IsTrue(value == "US");
    }
    

    You can parse any ASN.1 object with this reader and if you know the structure you can read any value out of it.

    UPDATE: According to RFC 3739 Subject Directory Attributes is defined in section 3.2.2. There is an example in appendix C.3. Subject directory attributes of given certificates are shown below.

    Subject directory attributes example from RFC 3739

    According to the structure defined in RFC 3739 this code should parse out all citizenship values.

    [Test]
    public void ReadCitizenship()
    {
        var exampleCert = @"MIIDEDCCAnmgAwIBAgIESZYC0jANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQGEwJE
    RTE5MDcGA1UECgwwR01EIC0gRm9yc2NodW5nc3plbnRydW0gSW5mb3JtYXRpb25z
    dGVjaG5payBHbWJIMB4XDTA0MDIwMTEwMDAwMFoXDTA4MDIwMTEwMDAwMFowZTEL
    MAkGA1UEBhMCREUxNzA1BgNVBAoMLkdNRCBGb3JzY2h1bmdzemVudHJ1bSBJbmZv
    cm1hdGlvbnN0ZWNobmlrIEdtYkgxHTAMBgNVBCoMBVBldHJhMA0GA1UEBAwGQmFy
    emluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDc50zVodVa6wHPXswg88P8
    p4fPy1caIaqKIK1d/wFRMN5yTl7T+VOS57sWxKcdDzGzqZJqjwjqAP3DqPK7AW3s
    o7lBG6JZmiqMtlXG3+olv+3cc7WU+qDv5ZXGEqauW4x/DKGc7E/nq2BUZ2hLsjh9
    Xy9+vbw+8KYE9rQEARdpJQIDAQABo4HpMIHmMGQGA1UdCQRdMFswEAYIKwYBBQUH
    CQQxBBMCREUwDwYIKwYBBQUHCQMxAxMBRjAdBggrBgEFBQcJATERGA8xOTcxMTAx
    NDEyMDAwMFowFwYIKwYBBQUHCQIxCwwJRGFybXN0YWR0MA4GA1UdDwEB/wQEAwIG
    QDASBgNVHSAECzAJMAcGBSskCAEBMB8GA1UdIwQYMBaAFAABAgMEBQYHCAkKCwwN
    Dg/+3LqYMDkGCCsGAQUFBwEDBC0wKzApBggrBgEFBQcLAjAdMBuBGW11bmljaXBh
    bGl0eUBkYXJtc3RhZHQuZGUwDQYJKoZIhvcNAQEFBQADgYEAj4yAu7LYa3X04h+C
    7+DyD2xViJCm5zEYg1m5x4znHJIMZsYAU/vJJIJQkPKVsIgm6vP/H1kXyAu0g2Ep
    z+VWPnhZK1uw+ay1KRXw8rw2mR8hQ2Ug6QZHYdky2HH3H/69rWSPp888G8CW8RLU
    uIKzn+GhapCuGoC4qWdlGLWqfpc=";
    
        var cer = new X509Certificate2(Convert.FromBase64String(exampleCert));
        var ext = cer.Extensions.OfType<X509Extension>().FirstOrDefault(p => p.Oid.Value == "2.5.29.9");
    
        var citizenshipValues = new List<string>();
    
        // initialize reader
        var reader = Helpers.ReaderFromData(ext.RawData);
    
        // parse ASN.1 object to the end and read value as byte[]
        var subjDirAttributes = reader.ReadToEnd(true);
    
    
        var citizenshipAsn1Object = subjDirAttributes.ChildNodes[0];
    
        foreach (var node in citizenshipAsn1Object.ChildNodes)
        {
            var shouldBeOidNode = node.ChildNodes[0];
    
            if (shouldBeOidNode.Identifier.Tag != Asn1Type.ObjectIdentifier) // should be oid
                throw new FormatException("Invalid structure of Subject Directory Attributes");
    
            var oidValue = shouldBeOidNode.ReadContentAsObjectIdentifier();
    
            if (oidValue != "1.3.6.1.5.5.7.9.4")
                continue;
    
            // found it
            
            var setNode = node.ChildNodes[1];
            if (setNode.Identifier.Tag != Asn1Type.Set)
                throw new FormatException("Invalid structure of Subject Directory Attributes");
    
            foreach (var internalNode in setNode.ChildNodes)
            {
                var valueOfCitizenship = internalNode.ReadContentAsPrintableString();
                if (valueOfCitizenship.Length != 2)
                    throw new FormatException("Invalid value in countryOfCitizenship. Length should be exactly 2 characters.");
    
                citizenshipValues.Add(valueOfCitizenship);
            }
    
            // found all values, lets break
            break;
        }
    
        Assert.IsTrue(citizenshipValues.Count == 1);
    }