从 CAC/x509 证书扩展中解码 ASN.1 数据(主题目录属性 > 国籍)

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

我需要阅读未通过 X509Certificate2 class 发布的 x509 属性。因此我需要读取它的 OID 并自己解码 ASN.1 数据。

具体来说,我需要分别读取"Subject Directory Attributes" > "Country of Citizenship" OID 2.5.29.9 和1.3.6.1.5.5.7.9.4。请记住主题目录属性是一个集合,我只是真正追求公民身份。

现在我可以获得ASN.1数据并且可以运行它通过 this javascript decoder 并查看 OID (1.3.6.1.5.5.7.9.4) 和我追求的值(美国),但我无法弄清楚如何在 C# 中解码数据并进一步针对 Citizenship OID .这是我到目前为止的以下代码:

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)
);

十六进制的原始数据是“30 12 30 10 06 08 2b 06 01 05 05 07 09 04 31 04 13 02 55 53”

[编辑] 我希望在网页中读取此值,但序列化到磁盘并不能真正满足我的需求。

你可以使用Asn1Net.Reader. When you open the "RawData" (but saved as binary to a file) in ASN.1 editor or Asn1Viewer你可以看到ASN.1数据的结构。下面是从 ASN.1 编辑器中截取的图片。

然后用 Asn1Net.Reader (nuget here) 你可以用这段代码解析值(没有做非空检查;我在这里使用 nunit 测试)

[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");
}

您可以使用此 reader 解析任何 ASN.1 对象,如果您知道结构,则可以从中读取任何值。

更新: 根据RFC 3739 Subject Directory Attributes is defined in section 3.2.2. There is an example in appendix C.3。给定证书的主题目录属性如下所示。

根据 RFC 3739 中定义的结构,此代码应解析出所有公民身份值。

[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);
}

我已经创建了用于解析证书主题属性的自定义通用函数。它会帮助你!

1) 创建自定义模型

    public class ModelCertParams
    {
        public string CN;
        public string S;
        public string E;
        public string L;
        public string O;
        public string C;
        public string STREET;
        public string OU;
        public string TIN = String.Empty;
        public string PINFL = String.Empty;
}

2) 从证书解析主题值

 X509Certificate2 myCert = new X509Certificate2(cert, "pkcs12password");

String issureName = myCert.GetIssuerName().ToString();
String ClientName = myCert.Subject;

ModelCertParams IssureModel = getExtentionSubject(issureName);
ModelCertParams CertParams = getExtentionSubject(ClientName);

3) 创建函数 getExtentionSubject()

 public static ModelCertParams getExtentionSubject(String data)
        {
            ModelCertParams mcert = new ModelCertParams();
            mcert.C = ExtParse(data, "C");
            mcert.CN = ExtParse(data, "CN");
            mcert.O = ExtParse(data, "O");
            mcert.OU = ExtParse(data, "OU");
            mcert.L = ExtParse(data, "L");
            mcert.S = ExtParse(data, "S");
            mcert.TIN = ExtParse(data, "TIN") == "" ? ExtParse(data, "OID.1.2.860.3.16.1.1") : "";
            mcert.PINFL = ExtParse(data, "PINFL") == "" ? ExtParse(data, "OID.1.2.860.3.16.1.2") : "";
            mcert.E = ExtParse(data, "E");
            mcert.STREET = ExtParse(data, "STREET");

            return mcert;
        }

4) 创建解析器函数

 public static string ExtParse(string data, string delimiter)
        {

            string result = String.Empty;
            try
            {
                if (data == null || data == "") return result;

                if (!delimiter.EndsWith("=")) delimiter = delimiter + "=";

                //data = data.ToUpper(); // if you need
                if (!data.Contains(delimiter)) return result;

                int start = data.IndexOf(delimiter) + delimiter.Length;

                string e = data.Substring(start, data.IndexOf('=', start) == -1 ? data.Length - start : data.IndexOf('=', start) - start);
                int tt = e.Length - e.LastIndexOf(", ");

                int length = data.IndexOf('=', start) == -1 ? data.Length - start : data.IndexOf('=', start) - start - tt;

                if (length == 0) return result; 
                if (length > 0)
                {
                    result = data.Substring(start, length);

                }
                else
                {
                    result = data.Substring(start);
                }
                return result;

            }
            catch (Exception)
            {
                return result;
            }
        }