77using AdvancedSystems . Security . Abstractions . Exceptions ;
88using AdvancedSystems . Security . Cryptography ;
99
10+ using static System . Net . WebRequestMethods ;
11+
1012namespace AdvancedSystems . Security . Extensions ;
1113
1214/// <summary>
@@ -46,7 +48,97 @@ public static X509Certificate2 GetCertificate<T>(this T store, string thumbprint
4648 ?? throw new CertificateNotFoundException ( "No valid certificate matching the search criteria could be found in the store." ) ;
4749 }
4850
49- public static DistinguishedName ParseDistinguishedName ( string distinguishedName )
51+ /// <summary>
52+ /// Attempts to parse the specified distinguished name (DN) string into a <see cref="DistinguishedName"/> object.
53+ /// </summary>
54+ /// <param name="distinguishedName">
55+ /// The DN string to parse, typically in the X.500 or LDAP DN format.
56+ /// </param>
57+ /// <param name="result">
58+ /// When this method returns, contains the parsed <see cref="DistinguishedName"/> object if the parsing was successful;
59+ /// otherwise, <see langword="null"/>.
60+ /// </param>
61+ /// <returns>
62+ /// <see langword="true"/> if the parsing was successful; otherwise, <see langword="false"/>.
63+ /// </returns>
64+ /// <remarks>
65+ /// The X.500 Distinguished Name (DN) and the LDAP Distinguished Name (DN) differ in syntax and conventions.
66+ /// <list type="table">
67+ /// <listheader>
68+ /// <term>
69+ /// X.500 Format
70+ /// </term>
71+ /// <description>
72+ /// Comes from the X.500 standard for directory services
73+ /// </description>
74+ /// </listheader>
75+ /// <item>
76+ /// <term>
77+ /// Separator
78+ /// </term>
79+ /// <description>
80+ /// Components are separated by commas (<c>,</c>).
81+ /// </description>
82+ /// </item>
83+ /// <item>
84+ /// <term>
85+ /// Order
86+ /// </term>
87+ /// <description>
88+ /// Attributes are typically listed in the most significant to least significant order
89+ /// (<i>root</i> to <i>leaf</i>).
90+ /// </description>
91+ /// </item>
92+ /// <item>
93+ /// <term>
94+ /// Attributes
95+ /// </term>
96+ /// <description>
97+ /// Attributes are case-insensitive but are conventionally written in uppercase. Note that
98+ /// attribute names are more verbose in some X.500 implementations.
99+ /// </description>
100+ /// </item>
101+ /// </list>
102+ /// <list type="table">
103+ /// <listheader>
104+ /// <term>
105+ /// LDAP
106+ /// </term>
107+ /// <description>
108+ /// A streamlined version of the X.500 DN, tailored for LDAP (Lightweight Directory Access Protocol).
109+ /// </description>
110+ /// </listheader>
111+ /// <item>
112+ /// <term>
113+ /// Separator
114+ /// </term>
115+ /// <description>
116+ /// Components are separated by commas (<c>,</c>), like X.500, but semicolons (<c>;</c>) are also sometimes
117+ /// allowed (albeit less common).
118+ /// </description>
119+ /// </item>
120+ /// <item>
121+ /// <term>
122+ /// Order
123+ /// </term>
124+ /// <description>
125+ /// Attributes are typically listed in the least significant to most significant order (<i>leaf</i> to <i>root</i>),
126+ /// though many implementations allow flexibility.
127+ /// </description>
128+ /// </item>
129+ /// <item>
130+ /// <term>
131+ /// Attributes
132+ /// </term>
133+ /// <description>
134+ /// LDAP uses abbreviated attribute names (e.g., CN for common name, O for organization, OU for organizational unit).
135+ /// It is also more permissive with regards to case sensitivity and attribute formatting.
136+ /// </description>
137+ /// </item>
138+ /// </list>
139+ /// </remarks>
140+ /// <seealso href="https://datatracker.ietf.org/doc/html/rfc4514"/>
141+ public static bool TryParseDistinguishedName ( string distinguishedName , out DistinguishedName ? result )
50142 {
51143 var rdns = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
52144 var dn = new X500DistinguishedName ( distinguishedName ) ;
@@ -61,21 +153,30 @@ public static DistinguishedName ParseDistinguishedName(string distinguishedName)
61153 rdns . Add ( attribute , value ) ;
62154 }
63155
64- rdns . TryGetValue ( RDN . C , out string ? country ) ;
65- rdns . TryGetValue ( RDN . CN , out string ? commonName ) ;
66- rdns . TryGetValue ( RDN . L , out string ? locality ) ;
67- rdns . TryGetValue ( RDN . O , out string ? organization ) ;
68- rdns . TryGetValue ( RDN . OU , out string ? organizationalUnit ) ;
69- rdns . TryGetValue ( RDN . S , out string ? state ) ;
156+ bool hasCountry = rdns . TryGetValue ( RDN . C , out string ? country ) ;
157+ bool hasCommonName = rdns . TryGetValue ( RDN . CN , out string ? commonName ) ;
158+ bool hasLocality = rdns . TryGetValue ( RDN . L , out string ? locality ) ;
159+ bool hasOrganization = rdns . TryGetValue ( RDN . O , out string ? organization ) ;
160+ bool hasOrganizationalUnit = rdns . TryGetValue ( RDN . OU , out string ? organizationalUnit ) ;
161+ bool hasState = rdns . TryGetValue ( RDN . S , out string ? state ) ;
70162
71- return new DistinguishedName
163+ bool hasRelativeDistinguishedNames = hasCountry
164+ || hasCommonName
165+ || hasLocality
166+ || hasOrganization
167+ || hasOrganizationalUnit
168+ || hasState ;
169+
170+ result = hasRelativeDistinguishedNames ? new DistinguishedName
72171 {
73172 CommonName = commonName ,
74173 OrganizationalUnit = organizationalUnit ,
75174 Organization = organization ,
76175 Locality = locality ,
77176 State = state ,
78177 Country = country ,
79- } ;
178+ } : null ;
179+
180+ return hasRelativeDistinguishedNames ;
80181 }
81182}
0 commit comments