2 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 using System.Collections.Generic;
34 * An X.500 name is a "distinguished name", which is an ordered sequence
35 * of RDN (relative distinguished names). Each RDN is an unordered set
36 * of name elements. A name element is an arbitrary value with an
37 * identifying OID; some (most) name element values are character
40 * X.500 names are used primarily to identify certificate owner entities
41 * (subject and issuer name in a certificate), and to serve as
42 * hierarchical indexing key for values in LDAP.
44 * An X.500 name is encoded and decoded using ASN.1. They can also be
45 * converted to a string representation. The string representation does
46 * not conserve all encoding details, so encoding+parsing will not
47 * necessarily restore the exact same binary DN.
50 public class X500Name {
53 * Get the individual name elements, in a "flattened" structure
54 * (if a SET in a RDN contains multiple values, then they are
55 * stored consecutively in that array).
57 * The returned array MUST NOT be modified.
59 public DNPart[] Parts {
67 * Get the individual name elements. Each internal array contains
68 * the name elements found in the SET for a specific RDN.
70 public DNPart[][] PartsGen {
78 * Check whether this DN is empty.
82 return Parts_.Length == 0;
89 * Constructor for parsing.
91 X500Name(List<List<DNPart>> dn)
96 void Init(List<List<DNPart>> dn)
99 List<DNPart> r = new List<DNPart>();
100 PartsGen_ = new DNPart[n][];
101 for (int i = 0; i < n; i ++) {
102 IDictionary<string, DNPart> dd =
103 new SortedDictionary<string, DNPart>(
104 StringComparer.Ordinal);
105 foreach (DNPart dnp in dn[i]) {
107 if (dd.ContainsKey(nt)) {
108 throw new AsnException(string.Format(
109 "multiple values of type {0}"
114 PartsGen_[i] = new DNPart[dd.Count];
116 foreach (DNPart p in dd.Values) {
117 PartsGen_[i][j ++] = p;
121 Parts_ = r.ToArray();
124 foreach (DNPart dnp in r) {
125 hc = ((hc << 7) | (hc >> 25)) + (uint)dnp.GetHashCode();
131 * Simplified parsing: this constructor checks that every SET
132 * in the sequence of RDN has size exactly 1, and decodes each
133 * name element as a "generic string".
135 * On decoding error, an AsnException is thrown.
137 public X500Name(AsnElt aDN) : this(aDN, true)
142 * Generic parsing. If 'strictStrings' is true, then the following
143 * rules are enforced:
144 * -- Every SET in the sequence of RDN must have size 1.
145 * -- Every name element is decoded as a string (by tag).
147 * If 'strictStrings' is false, then multiple elements may appear
148 * in each SET, and values needs not be decodable as string (values
149 * with a known OID must still be decodable).
151 * This constructor checks that within a single RDN, no two
152 * attributes may have the same type.
154 * On decoding error, an AsnException is thrown.
156 public X500Name(AsnElt aDN, bool strictStrings)
159 * Note: the SEQUENCE tag MUST be present, since the
160 * ASN.1 definition of Name starts with a CHOICE; thus,
161 * any tag override would have to be explicit, not
164 aDN.CheckConstructed();
165 aDN.CheckTag(AsnElt.SEQUENCE);
166 List<List<DNPart>> r = new List<List<DNPart>>();
167 foreach (AsnElt aRDN in aDN.Sub) {
168 aRDN.CheckConstructed();
169 aRDN.CheckTag(AsnElt.SET);
170 aRDN.CheckNumSubMin(1);
171 int n = aRDN.Sub.Length;
172 if (n != 1 && strictStrings) {
173 throw new AsnException(String.Format(
174 "several ({0}) values in RDN", n));
176 List<DNPart> r2 = new List<DNPart>();
178 for (int i = 0; i < n; i ++) {
179 AsnElt aTV = aRDN.Sub[i];
180 aTV.CheckConstructed();
181 aTV.CheckTag(AsnElt.SEQUENCE);
183 AsnElt aOID = aTV.GetSub(0);
184 aOID.CheckTag(AsnElt.OBJECT_IDENTIFIER);
185 AsnElt aVal = aTV.GetSub(1);
186 string nt = aOID.GetOID();
187 DNPart dnp = new DNPart(nt, aVal);
188 if (strictStrings && !dnp.IsString) {
189 throw new AsnException(
190 "RDN is not a string");
199 * Encode this DN into a string as specified in RFC 4514.
201 public override string ToString()
203 StringBuilder sb = new StringBuilder();
204 for (int i = PartsGen_.Length - 1; i >= 0; i --) {
205 DNPart[] dd = PartsGen_[i];
206 for (int j = 0; j < dd.Length; j ++) {
209 } else if (sb.Length > 0) {
212 sb.Append(dd[j].ToString());
215 return sb.ToString();
219 * Encode back this DN into an ASN.1 structure.
221 public AsnElt ToAsn1()
223 AsnElt[] t1 = new AsnElt[PartsGen_.Length];
224 for (int i = 0; i < PartsGen_.Length; i ++) {
225 DNPart[] dp = PartsGen_[i];
226 AsnElt[] t2 = new AsnElt[dp.Length];
227 for (int j = 0; j < dp.Length; j ++) {
228 t2[j] = AsnElt.Make(AsnElt.SEQUENCE,
229 AsnElt.MakeOID(dp[j].OID),
232 t1[i] = AsnElt.MakeSetOf(t2);
234 return AsnElt.Make(AsnElt.SEQUENCE, t1);
238 * Parse a string into a DN. The input is expected to use
239 * RFC 4514 format. Name elements that are provided as
240 * character strings will be mapped to ASN.1 PrintableString
241 * values (if they are compatible with that string type)
242 * or UTF8String values (otherwise).
244 * On parse error, an AsnException is thrown.
246 public static X500Name Parse(string str)
251 List<List<DNPart>> dn = new List<List<DNPart>>();
254 * Find the next unescaped '+' or ',' sign.
258 for (q = p; q < n; q ++) {
277 DNPart dnp = DNPart.Parse(str.Substring(p, q - p));
279 dn[dn.Count - 1].Add(dnp);
281 List<DNPart> r = new List<DNPart>();
287 acc = q < n && str[q] == '+';
291 return new X500Name(dn);
295 * Compare two DN for equality. "null" is equal to "null" but
298 public static bool Equals(X500Name dn1, X500Name dn2)
303 return dn1.Equals(dn2);
307 public override bool Equals(object obj)
309 return Equals(obj as X500Name);
312 public bool Equals(X500Name dn)
317 int n = PartsGen.Length;
318 if (dn.PartsGen.Length != n) {
321 for (int i = 0; i < n; i ++) {
322 DNPart[] p1 = PartsGen[i];
323 DNPart[] p2 = dn.PartsGen[i];
325 if (k != p2.Length) {
328 for (int j = 0; j < k; j ++) {
329 if (!p1[j].Equals(p2[j])) {
337 public override int GetHashCode()