这里的所有答案都是特定于平台或操作系统语言的,或者只能检索一个替代主题名称,因此我通过反向工程原始数据编写了自己的解析器,可以解析DNS和IP地址,并满足我的需求:
private const string SAN_OID = "2.5.29.17";
private static int ReadLength(ref Span<byte> span)
{
var length = (int)span[0];
span = span[1..];
if ((length & 0x80) > 0)
{
var lengthBytes = length & 0x7F;
length = 0;
for (var i = 0; i < lengthBytes; i++)
{
length = length * 0x100 + span[0];
span = span[1..];
}
}
return length;
}
public static IList<string> ParseSubjectAlternativeNames(byte[] rawData)
{
var result = new List<string>();
if (rawData.Length < 1 || rawData[0] != '0')
{
throw new InvalidDataException("They told me it will start with zero :(");
}
var data = rawData.AsSpan(1);
var length = ReadLength(ref data);
if (length != data.Length)
{
throw new InvalidDataException("I don't know who I am anymore");
}
while (!data.IsEmpty)
{
var type = data[0];
data = data[1..];
var partLength = ReadLength(ref data);
if (type == 135)
{
result.Add(new IPAddress(data[0..partLength]).ToString());
} else if (type == 160)
{
var index = data.IndexOf((byte)'\f') + 1;
var upnData = data[index..];
var upnLength = ReadLength(ref upnData);
result.Add(Encoding.UTF8.GetString(upnData[0..upnLength]));
} else
{
result.Add(Encoding.UTF8.GetString(data[0..partLength]));
}
data = data[partLength..];
}
return result;
}
public static IEnumerable<string> ParseSubjectAlternativeNames(X509Certificate2 cert)
{
return cert.Extensions
.Cast<X509Extension>()
.Where(ext => ext.Oid.Value.Equals(SAN_OID))
.SelectMany(x => ParseSubjectAlternativeNames(x.RawData));
}
我在corefx存储库本身中也找到了这个测试:
https://github.com/dotnet/corefx/blob/master/src/System.Security.Cryptography.Encoding/tests/AsnEncodedData.cs#L38
那里的想法只是将
asnData.Format
的结果分割成':', '=', ',',并取每个其他值,这是一种更简单的方法:
byte[] sanExtension =
{
0x30, 0x31, 0x82, 0x0B, 0x65, 0x78, 0x61, 0x6D,
0x70, 0x6C, 0x65, 0x2E, 0x6F, 0x72, 0x67, 0x82,
0x0F, 0x73, 0x75, 0x62, 0x2E, 0x65, 0x78, 0x61,
0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x6F, 0x72, 0x67,
0x82, 0x11, 0x2A, 0x2E, 0x73, 0x75, 0x62, 0x2E,
0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E,
0x6F, 0x72, 0x67,
};
AsnEncodedData asnData = new AsnEncodedData(
new Oid("2.5.29.17"),
sanExtension);
string s = asnData.Format(false);
string[] parts = s.Split(new[] { ':', '=', ',' }, StringSplitOptions.RemoveEmptyEntries);
string[] output = new string[parts.Length / 2];
for (int i = 0; i < output.Length; i++)
{
output[i] = parts[2 * i + 1];
}