问题
Is there an easy way to get the Subject Alternate Names from an X509Certificate2 object?
foreach (X509Extension ext in certificate.Extensions)
{
if (ext.Oid.Value.Equals(/* SAN OID */"2.5.29.17"))
{
byte[] raw = ext.RawData;
// ?????? parse to get type and name ????????
}
}
回答1:
Use the Format method of the extension for a printable version.
X509Certificate2 cert = /* your code here */;
foreach (X509Extension extension in cert.Extensions)
{
// Create an AsnEncodedData object using the extensions information.
AsnEncodedData asndata = new AsnEncodedData(extension.Oid, extension.RawData);
Console.WriteLine("Extension type: {0}", extension.Oid.FriendlyName);
Console.WriteLine("Oid value: {0}",asndata.Oid.Value);
Console.WriteLine("Raw data length: {0} {1}", asndata.RawData.Length, Environment.NewLine);
Console.WriteLine(asndata.Format(true));
}
回答2:
To get the "Subject Alternative Name" from a certificate:
X509Certificate2 cert = /* your code here */;
Console.WriteLine("UpnName : {0}{1}", cert.GetNameInfo(X509NameType.UpnName, false), Environment.NewLine);
回答3:
Based on the answer from Minh, here is a self-contained static function that should return them all
public static IEnumerable<string> ParseSujectAlternativeNames(X509Certificate2 cert)
{
Regex sanRex = new Regex(@"^DNS Name=(.*)", RegexOptions.Compiled | RegexOptions.CultureInvariant);
var sanList = from X509Extension ext in cert.Extensions
where ext.Oid.FriendlyName.Equals("Subject Alternative Name", StringComparison.Ordinal)
let data = new AsnEncodedData(ext.Oid, ext.RawData)
let text = data.Format(true)
from line in text.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
let match = sanRex.Match(line)
where match.Success && match.Groups.Count > 0 && !string.IsNullOrEmpty(match.Groups[1].Value)
select match.Groups[1].Value;
return sanList;
}
回答4:
I have created a function to do this:
private static List<string> ParseSujectAlternativeName(X509Certificate2 cert)
{
var result = new List<string>();
var subjectAlternativeName = cert.Extensions.Cast<X509Extension>()
.Where(n => n.Oid.FriendlyName.EqualsCase(SubjectAlternativeName))
.Select(n => new AsnEncodedData(n.Oid, n.RawData))
.Select(n => n.Format(true))
.FirstOrDefault();
if (subjectAlternativeName != null)
{
var alternativeNames = subjectAlternativeName.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
foreach (var alternativeName in alternativeNames)
{
var groups = Regex.Match(alternativeName, @"^DNS Name=(.*)").Groups;
if (groups.Count > 0 && !String.IsNullOrEmpty(groups[1].Value))
{
result.Add(groups[1].Value);
}
}
}
return result;
}
回答5:
With .net core, its more relevant to need a cross-platform way to do this. @Jason Shuler solution is windows only, but with some extra work, can be platform-independent. I've adapted the code WCF uses to do this in the following snippet(MIT Licensed)
// Adapted from https://github.com/dotnet/wcf/blob/a9984490334fdc7d7382cae3c7bc0c8783eacd16/src/System.Private.ServiceModel/src/System/IdentityModel/Claims/X509CertificateClaimSet.cs
// We don't have a strongly typed extension to parse Subject Alt Names, so we have to do a workaround
// to figure out what the identifier, delimiter, and separator is by using a well-known extension
// If https://github.com/dotnet/corefx/issues/22068 ever goes anywhere, we can remove this
private static class X509SubjectAlternativeNameParser
{
private const string SAN_OID = "2.5.29.17";
private static readonly string platform_identifier;
private static readonly char platform_delimiter;
private static readonly string platform_seperator;
static X509SubjectAlternativeNameParser()
{
// Extracted a well-known X509Extension
byte[] x509ExtensionBytes = new byte[] {
48, 36, 130, 21, 110, 111, 116, 45, 114, 101, 97, 108, 45, 115, 117, 98, 106, 101, 99,
116, 45, 110, 97, 109, 101, 130, 11, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109
};
const string subjectName1 = "not-real-subject-name";
X509Extension x509Extension = new X509Extension(SAN_OID, x509ExtensionBytes, true);
string x509ExtensionFormattedString = x509Extension.Format(false);
// Each OS has a different dNSName identifier and delimiter
// On Windows, dNSName == "DNS Name" (localizable), on Linux, dNSName == "DNS"
// e.g.,
// Windows: x509ExtensionFormattedString is: "DNS Name=not-real-subject-name, DNS Name=example.com"
// Linux: x509ExtensionFormattedString is: "DNS:not-real-subject-name, DNS:example.com"
// Parse: <identifier><delimter><value><separator(s)>
int delimiterIndex = x509ExtensionFormattedString.IndexOf(subjectName1) - 1;
platform_delimiter = x509ExtensionFormattedString[delimiterIndex];
// Make an assumption that all characters from the the start of string to the delimiter
// are part of the identifier
platform_identifier = x509ExtensionFormattedString.Substring(0, delimiterIndex);
int separatorFirstChar = delimiterIndex + subjectName1.Length + 1;
int separatorLength = 1;
for (int i = separatorFirstChar + 1; i < x509ExtensionFormattedString.Length; i++)
{
// We advance until the first character of the identifier to determine what the
// separator is. This assumes that the identifier assumption above is correct
if (x509ExtensionFormattedString[i] == platform_identifier[0])
{
break;
}
separatorLength++;
}
platform_seperator = x509ExtensionFormattedString.Substring(separatorFirstChar, separatorLength);
}
public static IEnumerable<string> ParseSubjectAlternativeNames(X509Certificate2 cert)
{
return cert.Extensions
.Cast<X509Extension>()
.Where(ext => ext.Oid.Value.Equals(SAN_OID)) // Only use SAN extensions
.Select(ext => new AsnEncodedData(ext.Oid, ext.RawData).Format(false)) // Decode from ASN
// This is dumb but AsnEncodedData.Format changes based on the platform, so our static initialization code handles making sure we parse it correctly
.SelectMany(text => text.Split(platform_seperator, StringSplitOptions.RemoveEmptyEntries))
.Select(text => text.Split(platform_delimiter))
.Where(x => x[0] == platform_identifier)
.Select(x => x[1]);
}
}
来源:https://stackoverflow.com/questions/16698307/how-do-you-parse-the-subject-alternate-names-from-an-x509certificate2