Fix problems with overlong directory names: phase #1
[mono.git] / mcs / class / corlib / Mono.Security.X509 / X501Name.cs
1 //
2 // X501Name.cs: X.501 Distinguished Names stuff 
3 //
4 // Author:
5 //      Sebastien Pouliot <sebastien@ximian.com>
6 //
7 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
9 //
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:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
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.
28 //
29
30 using System;
31 using System.Globalization;
32 using System.Text;
33
34 using Mono.Security;
35 using Mono.Security.Cryptography;
36
37 namespace Mono.Security.X509 {
38
39         // References:
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
44
45         /*
46          * Name ::= CHOICE { RDNSequence }
47          * 
48          * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
49          * 
50          * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
51          */
52 #if INSIDE_CORLIB
53         internal
54 #else
55         public 
56 #endif
57         sealed class X501 {
58
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 };
70
71                 private X501 () 
72                 {
73                 }
74
75                 static public string ToString (ASN1 seq) 
76                 {
77                         StringBuilder sb = new StringBuilder ();
78                         for (int i = 0; i < seq.Count; i++) {
79                                 ASN1 entry = seq [i];
80                                 AppendEntry (sb, entry, true);
81
82                                 // separator (not on last iteration)
83                                 if (i < seq.Count - 1)
84                                         sb.Append (", ");
85                         }
86                         return sb.ToString ();
87                 }
88
89 #if INSIDE_CORLIB || NET_2_0
90                 static public string ToString (ASN1 seq, bool reversed, string separator, bool quotes)
91                 {
92                         StringBuilder sb = new StringBuilder ();
93
94                         if (reversed) {
95                                 for (int i = seq.Count - 1; i >= 0; i--) {
96                                         ASN1 entry = seq [i];
97                                         AppendEntry (sb, entry, quotes);
98
99                                         // separator (not on last iteration)
100                                         if (i > 0)
101                                                 sb.Append (separator);
102                                 }
103                         } else {
104                                 for (int i = 0; i < seq.Count; i++) {
105                                         ASN1 entry = seq [i];
106                                         AppendEntry (sb, entry, quotes);
107
108                                         // separator (not on last iteration)
109                                         if (i < seq.Count - 1)
110                                                 sb.Append (separator);
111                                 }
112                         }
113                         return sb.ToString ();
114                 }
115 #endif
116
117                 static private void AppendEntry (StringBuilder sb, ASN1 entry, bool quotes)
118                 {
119                         // multiple entries are valid
120                         for (int k = 0; k < entry.Count; k++) {
121                                 ASN1 pair = entry [k];
122                                 ASN1 s = pair [1];
123                                 if (s == null)
124                                         continue;
125
126                                 ASN1 poid = pair [0];
127                                 if (poid == null)
128                                         continue;
129
130                                 if (poid.CompareValue (countryName))
131                                         sb.Append ("C=");
132                                 else if (poid.CompareValue (organizationName))
133                                         sb.Append ("O=");
134                                 else if (poid.CompareValue (organizationalUnitName))
135                                         sb.Append ("OU=");
136                                 else if (poid.CompareValue (commonName))
137                                         sb.Append ("CN=");
138                                 else if (poid.CompareValue (localityName))
139                                         sb.Append ("L=");
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))
145                                         sb.Append ("DC=");
146                                 else if (poid.CompareValue (userid))
147                                         sb.Append ("UID=");
148                                 else if (poid.CompareValue (email))
149                                         sb.Append ("E=");       // NOTE: Not part of RFC2253
150                                 else {
151                                         // unknown OID
152                                         sb.Append ("OID.");     // NOTE: Not present as RFC2253
153                                         sb.Append (ASN1Convert.ToOid (poid));
154                                         sb.Append ("=");
155                                 }
156
157                                 string sValue = null;
158                                 // 16bits or 8bits string ? TODO not complete (+special chars!)
159                                 if (s.Tag == 0x1E) {
160                                         // BMPSTRING
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 ();
165                                 } else {
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 = { ',', '+', '"', '\\', '<', '>', ';' };
170                                         if (quotes) {
171                                                 if ((sValue.IndexOfAny (specials, 0, sValue.Length) > 0) ||
172                                                     sValue.StartsWith (" ") || (sValue.EndsWith (" ")))
173                                                         sValue = "\"" + sValue + "\"";
174                                         }
175                                 }
176
177                                 sb.Append (sValue);
178
179                                 // separator (not on last iteration)
180                                 if (k < entry.Count - 1)
181                                         sb.Append (", ");
182                         }
183                 }
184
185                 static private X520.AttributeTypeAndValue GetAttributeFromOid (string attributeType) 
186                 {
187                         string s = attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ();
188                         switch (s) {
189                                 case "C":
190                                         return new X520.CountryName ();
191                                 case "O":
192                                         return new X520.OrganizationName ();
193                                 case "OU":
194                                         return new X520.OrganizationalUnitName ();
195                                 case "CN":
196                                         return new X520.CommonName ();
197                                 case "L":
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 ();
208                                 default:
209                                         if (s == "OID.") {
210                                                 // MUST support it but it OID may be without it
211                                                 return new X520.Oid (s.Substring (4));
212                                         } else {
213                                                 if (IsOid (s))
214                                                         return new X520.Oid (s);
215                                                 else
216                                                         return null;
217                                         }
218                         }
219                 }
220
221                 static private bool IsOid (string oid)
222                 {
223                         try {
224                                 ASN1 asn = ASN1Convert.FromOid (oid);
225                                 return (asn.Tag == 0x06);
226                         }
227                         catch {
228                                 return false;
229                         }
230                 }
231
232                 // no quote processing
233                 static private X520.AttributeTypeAndValue ReadAttribute (string value, ref int pos)
234                 {
235                         while ((value[pos] == ' ') && (pos < value.Length))
236                                 pos++;
237
238                         // get '=' position in substring
239                         int equal = value.IndexOf ('=', pos);
240                         if (equal == -1) {
241                                 string msg = Locale.GetText ("No attribute found.");
242                                 throw new FormatException (msg);
243                         }
244
245                         string s = value.Substring (pos, equal - pos);
246                         X520.AttributeTypeAndValue atv = GetAttributeFromOid (s);
247                         if (atv == null) {
248                                 string msg = Locale.GetText ("Unknown attribute '{0}'.");
249                                 throw new FormatException (String.Format (msg, s));
250                         }
251                         pos = equal + 1; // skip the '='
252                         return atv;
253                 }
254
255                 static private bool IsHex (char c)
256                 {
257                         if (Char.IsDigit (c))
258                                 return true;
259                         char up = Char.ToUpper (c, CultureInfo.InvariantCulture);
260                         return ((up >= 'A') && (up <= 'F'));
261                 }
262
263                 static string ReadHex (string value, ref int pos)
264                 {
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]);
274                         }
275                         byte[] data = CryptoConvert.FromHex (sb.ToString ());
276                         return Encoding.UTF8.GetString (data);
277                 }
278
279                 static private int ReadEscaped (StringBuilder sb, string value, int pos)
280                 {
281                         switch (value[pos]) {
282                         case '\\':
283                         case '"':
284                         case '=':
285                         case ';':
286                         case '<':
287                         case '>':
288                         case '+':
289                         case '#':
290                         case ',':
291                                 sb.Append (value[pos]);
292                                 return pos;
293                         default:
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)));
297                                 }
298                                 // it's either a 8 bits or 16 bits char
299                                 sb.Append (ReadHex (value, ref pos));
300                                 return pos;
301                         }
302                 }
303
304                 static private int ReadQuoted (StringBuilder sb, string value, int pos)
305                 {
306                         int original = pos;
307                         while (pos <= value.Length) {
308                                 switch (value[pos]) {
309                                 case '"':
310                                         return pos;
311                                 case '\\':
312                                         return ReadEscaped (sb, value, pos);
313                                 default:
314                                         sb.Append (value[pos]);
315                                         pos++;
316                                         break;
317                                 }
318                         }
319                         string msg = Locale.GetText ("Malformed quoted value '{0}'.");
320                         throw new FormatException (string.Format (msg, value.Substring (original)));
321                 }
322
323                 static private string ReadValue (string value, ref int pos)
324                 {
325                         int original = pos;
326                         StringBuilder sb = new StringBuilder ();
327                         while (pos < value.Length) {
328                                 switch (value [pos]) {
329                                 case '\\':
330                                         pos = ReadEscaped (sb, value, ++pos);
331                                         break;
332                                 case '"':
333                                         pos = ReadQuoted (sb, value, ++pos);
334                                         break;
335                                 case '=':
336                                 case ';':
337                                 case '<':
338                                 case '>':
339                                         string msg = Locale.GetText ("Malformed value '{0}' contains '{1}' outside quotes.");
340                                         throw new FormatException (string.Format (msg, value.Substring (original), value[pos]));
341                                 case '+':
342                                 case '#':
343                                         throw new NotImplementedException ();
344                                 case ',':
345                                         pos++;
346                                         return sb.ToString ();
347                                 default:
348                                         sb.Append (value[pos]);
349                                         break;
350                                 }
351                                 pos++;
352                         }
353                         return sb.ToString ();
354                 }
355
356                 static public ASN1 FromString (string rdn) 
357                 {
358                         if (rdn == null)
359                                 throw new ArgumentNullException ("rdn");
360
361                         int pos = 0;
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);
366
367                                 ASN1 sequence = new ASN1 (0x31);
368                                 sequence.Add (atv.GetASN1 ());
369                                 asn1.Add (sequence); 
370                         }
371                         return asn1;
372                 }
373         }
374 }