Search code examples
c#x509certificatebouncycastleasn.1der

Given the object identifier, how do we read the corresponding value from a ASN.1 DER encoded binary file?


I'm trying to read the value corresponding to a object identifier, from a Microsoft security catalog (*.cat) file, which is in fact a DER encoded ASN.1 object. I am using bouncy castle to create a Asn1Object. When I perform a .ToString() I can see my data in ASCII in the dumped text, against the object identifier "1.3.6.1.4.1.311.12.2.1" but is there a way to retrieve the data specifcally by passing this OID?

I see a class Org.BouncyCastle.Asn1.Microsoft, but I am not sure how to use this class. Any help appreciated! So far I have only this, where I call File.ReadAllBytes and pass to the function I've written below, on this I can call ToString() and see all data in the .cat

private Asn1Object asn1Object(byte[] data)
        {                
                Asn1InputStream asn1InputStream = new Asn1InputStream(data);

                if(asn1InputStream != null)
                {
                    return asn1InputStream.ReadObject().;
                }
                else
                {
                    return null;
                }

        }

Solution

  • The short answer: You would need to recursively walk the tree to find the DerSequence containing the DerObjectIdentifier and then returning the DerObjectIdentifiers next sibling.

    The long answer: Looking at the ASN1/DER structure, there doesn't appear to be a single entry in the object graph that has the OID and a value. It appears that for a particular OID, it will be the first child object in a DerSequence and the value will be the second child object.

    A recursive method that will find the DerSequence containing the DerObjectIdentifier matching your OID and return you the next sibling is:

    public static Asn1Object FindAsn1Value(string oid, Asn1Object obj)
    {
        Asn1Object result = null;
        if (obj is Asn1Sequence)
        {
            bool foundOID = false;
            foreach (Asn1Object entry in (Asn1Sequence)obj)
            {
                var derOID = entry as DerObjectIdentifier;
                if (derOID != null && derOID.Id == oid)
                {
                    foundOID = true;
                }
                else if (foundOID)
                {
                    return entry;
                } 
                else
                {
                    result = FindAsn1Value(oid, entry);
                    if (result != null)
                    {
                        return result;
                    }
                }
            }
        }
        else if (obj is DerTaggedObject)
        {
            result = FindAsn1Value(oid, ((DerTaggedObject)obj).GetObject());
            if (result != null)
            {
                return result;
            }
        }
        else
        {
            if (obj is DerSet)
            {
                foreach (Asn1Object entry in (DerSet)obj)
                {
                    result = FindAsn1Value(oid, entry);
                    if (result != null)
                    {
                        return result;
                    }
                }
            }
        }
        return null;
    }
    

    To call this you would load the Asn1Object using the method you provided, and then call the FindAsn1Value shown above. This should return you the Asn1Object you are after (in the case of my test cat file, this value was a DerOctetString).

    Asn1Object asn = asn1Object(File.ReadAllBytes(@"test_file.cat"));
    
    Asn1Object value = FindAsn1Value("1.3.6.1.4.1.311.12.2.1", asn);
    if (value is DerOctetString)
    {
        UnicodeEncoding unicode = new UnicodeEncoding(true, true);
        Console.WriteLine("DerOctetString = {0}", unicode.GetString(((DerOctetString)value).GetOctets()));
    }
    

    I'm not sure if the value for that OID will always be a DerOctetString nor whether my decoding choice is necessarily correct, however it gave me the most readable version of the value that my test was working with.

    Update

    If the same OID appears multiple times in the hierarchy and you need to return all the possible values, an alternative method could be:

    public static List<Asn1Object> FindAsn1Values(string oid, Asn1Object obj)
    {
        Asn1Object result = null;
        List<Asn1Object> results = new List<Asn1Object>();
        if (obj is Asn1Sequence)
        {
            bool foundOID = false;
            foreach (Asn1Object entry in (Asn1Sequence)obj)
            {
                var derOID = entry as DerObjectIdentifier;
                if (derOID != null && derOID.Id == oid)
                {
                    foundOID = true;
                }
                else if (foundOID)
                {
                    results.Add(entry);
                } 
                else
                {
                    result = FindAsn1Values(oid, entry);
                    if (result.Count > 0)
                    {
                        results.AddRange(result);
                    }
                }
            }
        }
        else if (obj is DerTaggedObject)
        {
            result = FindAsn1Values(oid, ((DerTaggedObject)obj).GetObject());
            if (result.Count > 0)
            {
                results.AddRange(result);
            }
        }
        else
        {
            if (obj is DerSet)
            {
                foreach (Asn1Object entry in (DerSet)obj)
                {
                    result = FindAsn1Values(oid, entry);
                    if (result.Count > 0)
                    {
                        results.AddRange(result);
                    }
                }
            }
        }
        return results;
    }