从 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;
}
}
我需要阅读未通过 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;
}
}