从专有名称中提取通用名称

时间:2011-10-07 14:11:09

标签: c# .net parsing ldap x509

是否在.NET中调用从rfc-2253编码的专有名称解析CN?我知道有一些第三方库可以做到这一点,但如果可能的话,我更愿意使用本机.NET库。

字符串编码DN的示例

  

CN = L。 Eagle,O = Sue \,Grabbit和Runn,C = GB

     

CN = Jeff Smith,OU =销售额,DC = Fabrikam,DC = COM

14 个答案:

答案 0 :(得分:8)

如果您使用的是X509Certificate2,则可以使用一种本机方法来提取DNS名称。 DNS名称通常等同于主证书的“主题”字段中的公共名称RDN:

x5092Cert.GetNameInfo(X509NameType.DnsName, false);

答案 1 :(得分:6)

当我找到你的时,我自己也有同样的问题。在BCL没找到任何东西;然而,我偶然发现了这篇CodeProject的文章,该文章直接敲响了头部。

我希望它也会帮助你。

http://www.codeproject.com/Articles/9788/An-RFC-2253-Compliant-Distinguished-Name-Parser

答案 2 :(得分:6)

在.NET源代码中挖掘后,看起来有一个内部实用程序类可以parse Distinguished Names进入它们的不同组件。遗憾的是,实用程序类未公开,但您可以使用反射访问它:

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 

请注意,这不是内部实用程序类,可能会在将来的版本中更改。

答案 3 :(得分:3)

Win32功能有效吗?您可以将PInvoke与DsGetRdnW一起使用。有关代码,请参阅我对其他问题的回答:https://stackoverflow.com/a/11091804/628981

答案 4 :(得分:1)

在这里加我的两分钱。如果您首先了解哪些业务规则最终将决定将在贵公司实施多少,那么此实现将“最佳”。

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);
}

答案 5 :(得分: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;
}

答案 6 :(得分:1)

很抱歉聚会晚了一点,但是我能够直接从c#调用Name属性

UserPrincipal p 

然后我就可以打电话

p.Name 

这给了我全名(通用名)

示例代码:

string name;
foreach(UserPrincipal p in PSR)
{
     //PSR refers to PrincipalSearchResult
     name = p.Name;
     Console.WriteLine(name); 
 }

很显然,您必须填写空白。但这比解析正则表达式要容易。

答案 7 :(得分:1)

您可以使用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 OS或.NET的未来版本中中断框架。

答案 8 :(得分:0)

你能不能只检索CN属性值?

正如你正确地注意到的那样,使用别人的课程,因为有许多有趣的边缘案例(转义逗号,转义其他字符),这使得解析DN看起来很简单,但实际上相当棘手。

我通常使用Novell(Now NetID)Identity Manager附带的Java类。所以这没有帮助。

答案 9 :(得分:0)

这个怎么样:

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 Regular Expression for Extracting Parts of an Active Directory Distiniguished Name

答案 10 :(得分:0)

您可以使用正则表达式来执行此操作。这是一个可以解析整个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

答案 11 :(得分:0)

这是我从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;
            }
        }
    }

答案 12 :(得分:0)

好吧,我是另一个迟到的人。这是我的解决方案:

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("\r\n"))
{
    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.
}

基本上它利用 X500DistinguishedName.Format 方法将事情放在线上。然后按行分割,再把每一行分割成key值。

答案 13 :(得分:-3)

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();
相关问题