在。net中是否有一个调用从rfc-2253编码的专有名称解析CN ?我知道有一些第三方库可以做到这一点,但如果可能的话,我更愿意使用本机。net库。
字符串编码DN
示例CN = L。鹰,O=苏,兔八哥和兔八哥,C=GB
CN =杰夫史密斯,OU =销售,DC = Fabrikam, DC = COM
如果您正在使用X509Certificate2
,那么您可以使用一个方法来提取简单名称。简单名称相当于主证书的主题字段内的通用名称RDN:
x5092Cert.GetNameInfo(X509NameType.SimpleName, false);
或者,X509NameType.DnsName
可以用来检索主题备选名称(如果存在);否则,它将默认为通用名称:
x5092Cert.GetNameInfo(X509NameType.DnsName, false);
在。net源代码中挖掘之后,看起来好像有一个内部实用程序类可以将区分名解析为不同的组件。不幸的是,实用程序类不是公共的,但是您可以使用反射来访问它:
string dn = "CN=TestGroup,OU=Groups,OU=UT-SLC,OU=US,DC=Company,DC=com";
Assembly dirsvc = Assembly.Load("System.DirectoryServices");
Type asmType = dirsvc.GetType("System.DirectoryServices.ActiveDirectory.Utils");
MethodInfo mi = asmType.GetMethod("GetDNComponents", BindingFlags.NonPublic | BindingFlags.Static);
string[] parameters = { dn };
var test = mi.Invoke(null, parameters);
//test.Dump("test1");//shows details when using Linqpad
//Convert Distinguished Name (DN) to Relative Distinguished Names (RDN)
MethodInfo mi2 = asmType.GetMethod("GetRdnFromDN", BindingFlags.NonPublic | BindingFlags.Static);
var test2 = mi2.Invoke(null, parameters);
//test2.Dump("test2");//shows details when using Linqpad
结果如下所示:
//test1 is array of internal "Component" struct that has name/values as strings
Name Value
CN TestGroup
OU Groups
OU UT-SLC
OU US
DC company
DC com
//test2 is a string with CN=RDN
CN=TestGroup
请不要,这是一个内部实用程序类,可能会在未来的版本中更改。
当我发现你的问题时,我自己也有同样的问题。BCL里什么都没发现;然而,我偶然发现了这篇击中要害的CodeProject文章。
我希望它也能帮助你。
http://www.codeproject.com/Articles/9788/An-RFC-2253-Compliant-Distinguished-Name-Parser
您可以使用AsnEncodedData类从asn .1编码的专有名称中提取通用名称:
var distinguishedName= new X500DistinguishedName("CN=TestGroup,OU=Groups,OU=UT-SLC,OU=US,DC=Company,DC=com");
var commonNameData = new AsnEncodedData("CN", distinguishedName.RawData);
var commonName = commonNameData.Format(false);
这种方法的一个缺点是,如果你指定一个无法识别的OID或与OID标识的字段在专有名称中缺失,Format
方法将返回一个十六进制字符串,其中包含完整专有名称的编码值,因此您可能想要验证结果。
此外,文档似乎没有指定AsnEncodedData构造函数的rawData参数是否允许包含除第一个参数指定的oid之外的其他oid,因此它可能在非windows操作系统或. net框架的未来版本中中断。
Win32函数计数吗?您可以在DsGetRdnW
中使用PInvoke。对于代码,请参阅我对另一个问题的回答:https://stackoverflow.com/a/11091804/628981.
如果你是在Windows上,@MaxKiselev的答案工作完美。在非windows平台上,它返回每个属性的ASN1转储。
。Net Core 5+包含ASN1解析器,因此您可以通过使用AsnReader
以跨平台的方式访问RDN。
助手类:
public static class X509DistinguishedNameExtensions
{
public static IEnumerable<KeyValuePair<string, string>> GetRelativeNames(this X500DistinguishedName dn)
{
var reader = new AsnReader(dn.RawData, AsnEncodingRules.BER);
var snSeq = reader.ReadSequence();
if (!snSeq.HasData)
{
throw new InvalidOperationException();
}
// Many types are allowable. We're only going to support the string-like ones
// (This excludes IPAddress, X400 address, and other wierd stuff)
// https://www.rfc-editor.org/rfc/rfc5280#page-37
// https://www.rfc-editor.org/rfc/rfc5280#page-112
var allowedRdnTags = new[]
{
UniversalTagNumber.TeletexString, UniversalTagNumber.PrintableString,
UniversalTagNumber.UniversalString, UniversalTagNumber.UTF8String,
UniversalTagNumber.BMPString, UniversalTagNumber.IA5String,
UniversalTagNumber.NumericString, UniversalTagNumber.VisibleString,
UniversalTagNumber.T61String
};
while (snSeq.HasData)
{
var rdnSeq = snSeq.ReadSetOf().ReadSequence();
var attrOid = rdnSeq.ReadObjectIdentifier();
var attrValueTagNo = (UniversalTagNumber)rdnSeq.PeekTag().TagValue;
if (!allowedRdnTags.Contains(attrValueTagNo))
{
throw new NotSupportedException($"Unknown tag type {attrValueTagNo} for attr {attrOid}");
}
var attrValue = rdnSeq.ReadCharacterString(attrValueTagNo);
var friendlyName = new Oid(attrOid).FriendlyName;
yield return new KeyValuePair<string, string>(friendlyName ?? attrOid, attrValue);
}
}
}
使用例子:
// Subject: CN=Example, O=Organization
var cert = new X509Certificate2("foo.cer");
var names = this.cert.SubjectName.GetRelativeNames().ToArray();
// names has [ { "CN": "Example" }, { "O": "Organization" } ]
由于这不涉及任何字符串解析,因此不会错误处理转义或注入。它不支持解码包含非字符串元素的DN,但这种情况似乎非常罕见。
这个呢:
string cnPattern = @"^CN=(?<cn>.+?)(?<!\),";
string dn = @"CN=Doe, John,OU=My OU,DC=domain,DC=com";
Regex re = new Regex(cnPattern);
Match m = re.Match(dn);
if (m.Success)
{
// Item with index 1 returns the first group match.
string cn = m.Groups[1].Value;
}
改编自Powershell正则表达式,用于提取活动目录可分辨名称的部分。
我只是补充一下我的观点。如果您首先了解哪些业务规则将最终决定在您的公司中实现多少 RFC,则此实现将"最佳"地工作。
private static string ExtractCN(string distinguishedName)
{
// CN=...,OU=...,OU=...,DC=...,DC=...
string[] parts;
parts = distinguishedName.Split(new[] { ",DC=" }, StringSplitOptions.None);
var dc = parts.Skip(1);
parts = parts[0].Split(new[] { ",OU=" }, StringSplitOptions.None);
var ou = parts.Skip(1);
parts = parts[0].Split(new[] { ",CN=" }, StringSplitOptions.None);
var cnMulti = parts.Skip(1);
var cn = parts[0];
if (!Regex.IsMatch(cn, "^CN="))
throw new CustomException(string.Format("Unable to parse distinguishedName for commonName ({0})", distinguishedName));
return Regex.Replace(cn, "^CN=", string.Empty);
}
您可以使用正则表达式来完成此操作。这是一个可以解析整个DN的正则表达式模式,然后您可以只取您感兴趣的部分:
(?:^|,s?)(?:(?<name>[A-Z]+)=(?<val>"(?:[^"]|"")+"|(?:\,|[^,])+))+
这里的格式稍微好一点,并带有一些注释:
(?:^|,s?) <-- Start or a comma
(?:
(?<name>[A-Z]+)
=
(?<val>
"(?:[^"]|"")+" <-- Quoted strings
|
(?:\,|[^,])+ <-- Unquoted strings
)
)+
此正则表达式将为每个匹配提供name
和val
捕获组。
DN字符串可以有选择地加引号(例如"Hello"
,这允许它们包含未转义的逗号)。或者,如果没有引号,逗号必须用反斜杠转义(例如Hello, there!
)。这个正则表达式可以处理带引号和不带引号的字符串。
这里有一个链接,这样你就可以看到它的作用:https://regex101.com/r/7vhdDz/1
如果顺序不确定,我这样做:
private static string ExtractCN(string dn)
{
string[] parts = dn.Split(new char[] { ',' });
for (int i = 0; i < parts.Length; i++)
{
var p = parts[i];
var elems = p.Split(new char[] { '=' });
var t = elems[0].Trim().ToUpper();
var v = elems[1].Trim();
if (t == "CN")
{
return v;
}
}
return null;
}
这是我从https://www.codeproject.com/Articles/9788/An-RFC-2253-Compliant-Distinguished-Name-Parser派生的几乎符合rfc的故障安全DN解析器及其用法示例(提取主题名称为CN和O,两者都是可选的,用逗号连接):
private static string GetCertificateString(X509Certificate2 certificate)
{
var subjectComponents = certificate.Subject.ParseDistinguishedName();
var subjectName = string.Join(", ", subjectComponents
.Where(m => (m.Item1 == "CN") || (m.Item1 == "O"))
.Select(n => n.Item2)
.Distinct());
return $"{certificate.SerialNumber} {certificate.NotBefore:yyyy.MM.dd}-{certificate.NotAfter:yyyy.MM.dd} {subjectName}";
}
private enum DistinguishedNameParserState
{
Component,
QuotedString,
EscapedCharacter,
};
public static IEnumerable<Tuple<string, string>> ParseDistinguishedName(this string value)
{
var previousState = DistinguishedNameParserState.Component;
var currentState = DistinguishedNameParserState.Component;
var currentComponent = new StringBuilder();
var previousChar = char.MinValue;
var position = 0;
Func<StringBuilder, Tuple<string, string>> parseComponent = sb =>
{
var s = sb.ToString();
sb.Clear();
var index = s.IndexOf('=');
if (index == -1)
{
return null;
}
var item1 = s.Substring(0, index).Trim().ToUpper();
var item2 = s.Substring(index + 1).Trim();
return Tuple.Create(item1, item2);
};
while (position < value.Length)
{
var currentChar = value[position];
switch (currentState)
{
case DistinguishedNameParserState.Component:
switch (currentChar)
{
case ',':
case ';':
// Separator found, yield parsed component
var component = parseComponent(currentComponent);
if (component != null)
{
yield return component;
}
break;
case '\':
// Escape character found
previousState = currentState;
currentState = DistinguishedNameParserState.EscapedCharacter;
break;
case '"':
// Quotation mark found
if (previousChar == currentChar)
{
// Double quotes inside quoted string produce single quote
currentComponent.Append(currentChar);
}
currentState = DistinguishedNameParserState.QuotedString;
break;
default:
currentComponent.Append(currentChar);
break;
}
break;
case DistinguishedNameParserState.QuotedString:
switch (currentChar)
{
case '\':
// Escape character found
previousState = currentState;
currentState = DistinguishedNameParserState.EscapedCharacter;
break;
case '"':
// Quotation mark found
currentState = DistinguishedNameParserState.Component;
break;
default:
currentComponent.Append(currentChar);
break;
}
break;
case DistinguishedNameParserState.EscapedCharacter:
currentComponent.Append(currentChar);
currentState = previousState;
currentChar = char.MinValue;
break;
}
previousChar = currentChar;
position++;
}
// Yield last parsed component, if any
if (currentComponent.Length > 0)
{
var component = parseComponent(currentComponent);
if (component != null)
{
yield return component;
}
}
}
很抱歉来晚了一点,但是我可以直接从c#中调用Name属性
UserPrincipal p
然后我可以调用
p.Name
给了我全名(通用名)
示例代码:
string name;
foreach(UserPrincipal p in PSR)
{
//PSR refers to PrincipalSearchResult
name = p.Name;
Console.WriteLine(name);
}
显然,你必须填空。但这应该比解析正则表达式更容易。
难道不能只检索CN属性值吗?
正如您正确注意到的,使用其他人的类,因为有许多有趣的边缘情况(转义的逗号,转义的其他字符),使解析DN看起来很容易,但实际上相当棘手。
我通常使用Novell(现在是NetID)身份管理器附带的Java类。所以这是没有用的。
using System.Linq;
var dn = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM";
var cn = dn.Split(',').Where(i => i.Contains("CN=")).Select(i => i.Replace("CN=", "")).FirstOrDefault();
我又是一个迟到的人。以下是我的解决方案:
var dn = new X500DistinguishedName("CN=TestGroup,OU=Groups,OU=UT-SLC,OU=US,DC="Company, inc",DC=com");
foreach(var part in dn.Format(true).Split("rn"))
{
if(part == "") continue;
var parts = part.Split('=', 2);
var key = parts[0];
var value = parts[1];
// use your key and value as you see fit here.
}
基本上它利用了x500distincishedname。格式化方法,将内容放在行上。然后按行分割,然后将每行分割成键值