Extract Common Name from Distinguished Name

时光毁灭记忆、已成空白 提交于 2019-12-23 06:55:01

问题


Is there a call in .NET that parses the CN from a rfc-2253 encoded distinguished name? I know there are some third-party libraries that do this, but I would prefer to use native .NET libraries if possible.

Examples of a string encoded DN

CN=L. Eagle,O=Sue\, Grabbit and Runn,C=GB

CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM


回答1:


If you are working with an X509Certificate2, there is a native method that you can use to extract the Simple Name. The Simple Name is equivalent to the Common Name RDN within the Subject field of the main certificate:

x5092Cert.GetNameInfo(X509NameType.SimpleName, false);

Alternatively, X509NameType.DnsName can be used to retrieve the Subject Alternative Name, if present; otherwise, it will default to the Common Name:

x5092Cert.GetNameInfo(X509NameType.DnsName, false);



回答2:


After digging around in the .NET source code it looks like there is an internal utility class that can parse Distinguished Names into their different components. Unfortunately the utility class is not made public, but you can access it using reflection:

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 

The results would look like this:

//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 

Please not this is an internal utility class and could change in a future release.




回答3:


I had the same question, myself, when I found yours. Didn't find anything in the BCL; however, I did stumble across this CodeProject article that hit the nail squarely on the head.

I hope it helps you out, too.

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




回答4:


Do Win32 functions count? You can use PInvoke with DsGetRdnW. For code, see my answer to another question: https://stackoverflow.com/a/11091804/628981.




回答5:


Just adding my two cents here. This implementation works "best" if you first learn what business rules are in place that will ultimately dictate how much of the RFC will ever be implemented at your company.

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



回答6:


If the order is uncertain, I do this:

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



回答7:


Could you not just retrieve the CN attribute values?

As you correctly note, use someone else's class as there are lots of fun edge cases (escaped commas, escaped other characters) that make parsing a DN look easy, but actually reasonably tricky.

I usually use a Java class that comes with the Novell (Now NetID) Identity Manager. So that is not helpful.




回答8:


How about this one:

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

Adapted from Powershell Regular Expression for Extracting Parts of an Active Directory Distiniguished Name.




回答9:


You could use regular expressions to do this. Here's a regex pattern than can parse the whole DN, then you can just take the parts you are interested in:

(?:^|,\s?)(?:(?<name>[A-Z]+)=(?<val>"(?:[^"]|"")+"|(?:\\,|[^,])+))+

Here it is formatted a bit nicer, and with some comments:

(?:^|,\s?)               <-- Start or a comma
(?:
    (?<name>[A-Z]+)
    =
    (?<val>
        "(?:[^"]|"")+"   <-- Quoted strings
        |
        (?:\\,|[^,])+    <-- Unquoted strings
    )
)+

This regex will give you name and val capture groups for each match.

DN strings can optionally be quoted (e.g. "Hello", which allows them to contain unescaped commas. Alternatively, if not quoted, commas must be escaped with a backslash (e.g. Hello\, there!). This regex handles both quoted and unquoted strings.

Here's a link so you can see it in action: https://regex101.com/r/7vhdDz/1




回答10:


This is my almost RFC-compliant fail-safe DN parser derived from https://www.codeproject.com/Articles/9788/An-RFC-2253-Compliant-Distinguished-Name-Parser and an example of its usage (extract subject name as CN and O, both optional, concatenated with comma):

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



回答11:


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


来源:https://stackoverflow.com/questions/7688445/extract-common-name-from-distinguished-name

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!