2 // X501Name.cs: X.501 Distinguished Names stuff
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Globalization;
35 using Mono.Security.Cryptography;
37 namespace Mono.Security.X509 {
40 // 1. Information technology - Open Systems Interconnection - The Directory: Models
41 // http://www.itu.int/rec/recommendation.asp?type=items&lang=e&parent=T-REC-X.501-200102-I
42 // 2. RFC2253: Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names
43 // http://www.ietf.org/rfc/rfc2253.txt
46 * Name ::= CHOICE { RDNSequence }
48 * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
50 * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
59 static byte[] countryName = { 0x55, 0x04, 0x06 };
60 static byte[] organizationName = { 0x55, 0x04, 0x0A };
61 static byte[] organizationalUnitName = { 0x55, 0x04, 0x0B };
62 static byte[] commonName = { 0x55, 0x04, 0x03 };
63 static byte[] localityName = { 0x55, 0x04, 0x07 };
64 static byte[] stateOrProvinceName = { 0x55, 0x04, 0x08 };
65 static byte[] streetAddress = { 0x55, 0x04, 0x09 };
66 //static byte[] serialNumber = { 0x55, 0x04, 0x05 };
67 static byte[] domainComponent = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 };
68 static byte[] userid = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x01 };
69 static byte[] email = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 };
75 static public string ToString (ASN1 seq)
77 StringBuilder sb = new StringBuilder ();
78 for (int i = 0; i < seq.Count; i++) {
80 AppendEntry (sb, entry, true);
82 // separator (not on last iteration)
83 if (i < seq.Count - 1)
86 return sb.ToString ();
89 #if INSIDE_CORLIB || NET_2_0
90 static public string ToString (ASN1 seq, bool reversed, string separator, bool quotes)
92 StringBuilder sb = new StringBuilder ();
95 for (int i = seq.Count - 1; i >= 0; i--) {
97 AppendEntry (sb, entry, quotes);
99 // separator (not on last iteration)
101 sb.Append (separator);
104 for (int i = 0; i < seq.Count; i++) {
105 ASN1 entry = seq [i];
106 AppendEntry (sb, entry, quotes);
108 // separator (not on last iteration)
109 if (i < seq.Count - 1)
110 sb.Append (separator);
113 return sb.ToString ();
117 static private void AppendEntry (StringBuilder sb, ASN1 entry, bool quotes)
119 // multiple entries are valid
120 for (int k = 0; k < entry.Count; k++) {
121 ASN1 pair = entry [k];
126 ASN1 poid = pair [0];
130 if (poid.CompareValue (countryName))
132 else if (poid.CompareValue (organizationName))
134 else if (poid.CompareValue (organizationalUnitName))
136 else if (poid.CompareValue (commonName))
138 else if (poid.CompareValue (localityName))
140 else if (poid.CompareValue (stateOrProvinceName))
141 sb.Append ("S="); // NOTE: RFC2253 uses ST=
142 else if (poid.CompareValue (streetAddress))
143 sb.Append ("STREET=");
144 else if (poid.CompareValue (domainComponent))
146 else if (poid.CompareValue (userid))
148 else if (poid.CompareValue (email))
149 sb.Append ("E="); // NOTE: Not part of RFC2253
152 sb.Append ("OID."); // NOTE: Not present as RFC2253
153 sb.Append (ASN1Convert.ToOid (poid));
157 string sValue = null;
158 // 16bits or 8bits string ? TODO not complete (+special chars!)
161 StringBuilder sb2 = new StringBuilder ();
162 for (int j = 1; j < s.Value.Length; j += 2)
163 sb2.Append ((char)s.Value[j]);
164 sValue = sb2.ToString ();
166 sValue = Encoding.UTF8.GetString (s.Value);
167 // in some cases we must quote (") the value
168 // Note: this doesn't seems to conform to RFC2253
169 char[] specials = { ',', '+', '"', '\\', '<', '>', ';' };
171 if ((sValue.IndexOfAny (specials, 0, sValue.Length) > 0) ||
172 sValue.StartsWith (" ") || (sValue.EndsWith (" ")))
173 sValue = "\"" + sValue + "\"";
179 // separator (not on last iteration)
180 if (k < entry.Count - 1)
185 static private X520.AttributeTypeAndValue GetAttributeFromOid (string attributeType)
187 string s = attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ();
190 return new X520.CountryName ();
192 return new X520.OrganizationName ();
194 return new X520.OrganizationalUnitName ();
196 return new X520.CommonName ();
198 return new X520.LocalityName ();
199 case "S": // Microsoft
200 case "ST": // RFC2253
201 return new X520.StateOrProvinceName ();
202 case "E": // NOTE: Not part of RFC2253
203 return new X520.EmailAddress ();
204 case "DC": // RFC2247
205 return new X520.DomainComponent ();
206 case "UID": // RFC1274
207 return new X520.UserId ();
210 // MUST support it but it OID may be without it
211 return new X520.Oid (s.Substring (4));
214 return new X520.Oid (s);
221 static private bool IsOid (string oid)
224 ASN1 asn = ASN1Convert.FromOid (oid);
225 return (asn.Tag == 0x06);
232 // no quote processing
233 static private X520.AttributeTypeAndValue ReadAttribute (string value, ref int pos)
235 while ((value[pos] == ' ') && (pos < value.Length))
238 // get '=' position in substring
239 int equal = value.IndexOf ('=', pos);
241 string msg = Locale.GetText ("No attribute found.");
242 throw new FormatException (msg);
245 string s = value.Substring (pos, equal - pos);
246 X520.AttributeTypeAndValue atv = GetAttributeFromOid (s);
248 string msg = Locale.GetText ("Unknown attribute '{0}'.");
249 throw new FormatException (String.Format (msg, s));
251 pos = equal + 1; // skip the '='
255 static private bool IsHex (char c)
257 if (Char.IsDigit (c))
259 char up = Char.ToUpper (c, CultureInfo.InvariantCulture);
260 return ((up >= 'A') && (up <= 'F'));
263 static string ReadHex (string value, ref int pos)
265 StringBuilder sb = new StringBuilder ();
266 // it is (at least an) 8 bits char
267 sb.Append (value[pos++]);
268 sb.Append (value[pos]);
269 // look ahead for a 16 bits char
270 if ((pos < value.Length - 4) && (value[pos+1] == '\\') && IsHex (value[pos+2])) {
271 pos += 2; // pass last char and skip \
272 sb.Append (value[pos++]);
273 sb.Append (value[pos]);
275 byte[] data = CryptoConvert.FromHex (sb.ToString ());
276 return Encoding.UTF8.GetString (data);
279 static private int ReadEscaped (StringBuilder sb, string value, int pos)
281 switch (value[pos]) {
291 sb.Append (value[pos]);
294 if (pos >= value.Length - 2) {
295 string msg = Locale.GetText ("Malformed escaped value '{0}'.");
296 throw new FormatException (string.Format (msg, value.Substring (pos)));
298 // it's either a 8 bits or 16 bits char
299 sb.Append (ReadHex (value, ref pos));
304 static private int ReadQuoted (StringBuilder sb, string value, int pos)
307 while (pos <= value.Length) {
308 switch (value[pos]) {
312 return ReadEscaped (sb, value, pos);
314 sb.Append (value[pos]);
319 string msg = Locale.GetText ("Malformed quoted value '{0}'.");
320 throw new FormatException (string.Format (msg, value.Substring (original)));
323 static private string ReadValue (string value, ref int pos)
326 StringBuilder sb = new StringBuilder ();
327 while (pos < value.Length) {
328 switch (value [pos]) {
330 pos = ReadEscaped (sb, value, ++pos);
333 pos = ReadQuoted (sb, value, ++pos);
339 string msg = Locale.GetText ("Malformed value '{0}' contains '{1}' outside quotes.");
340 throw new FormatException (string.Format (msg, value.Substring (original), value[pos]));
343 throw new NotImplementedException ();
346 return sb.ToString ();
348 sb.Append (value[pos]);
353 return sb.ToString ();
356 static public ASN1 FromString (string rdn)
359 throw new ArgumentNullException ("rdn");
362 ASN1 asn1 = new ASN1 (0x30);
363 while (pos < rdn.Length) {
364 X520.AttributeTypeAndValue atv = ReadAttribute (rdn, ref pos);
365 atv.Value = ReadValue (rdn, ref pos);
367 ASN1 sequence = new ASN1 (0x31);
368 sequence.Add (atv.GetASN1 ());