从Distinguished Name中提取Common Name



在。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
    )
)+

此正则表达式将为每个匹配提供nameval捕获组。

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。格式化方法,将内容放在行上。然后按行分割,然后将每行分割成键值

相关内容

最新更新