2010-02-27 Miguel de Icaza <miguel@novell.com>
[mono.git] / mcs / class / Mono.Security / 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                 static byte[] dnQualifier = { 0x55, 0x04, 0x2E };
71                 static byte[] title = { 0x55, 0x04, 0x0C };
72                 static byte[] surname = { 0x55, 0x04, 0x04 };
73                 static byte[] givenName = { 0x55, 0x04, 0x2A };
74                 static byte[] initial = { 0x55, 0x04, 0x2B };
75
76                 private X501 () 
77                 {
78                 }
79
80                 static public string ToString (ASN1 seq) 
81                 {
82                         StringBuilder sb = new StringBuilder ();
83                         for (int i = 0; i < seq.Count; i++) {
84                                 ASN1 entry = seq [i];
85                                 AppendEntry (sb, entry, true);
86
87                                 // separator (not on last iteration)
88                                 if (i < seq.Count - 1)
89                                         sb.Append (", ");
90                         }
91                         return sb.ToString ();
92                 }
93
94                 static public string ToString (ASN1 seq, bool reversed, string separator, bool quotes)
95                 {
96                         StringBuilder sb = new StringBuilder ();
97
98                         if (reversed) {
99                                 for (int i = seq.Count - 1; i >= 0; i--) {
100                                         ASN1 entry = seq [i];
101                                         AppendEntry (sb, entry, quotes);
102
103                                         // separator (not on last iteration)
104                                         if (i > 0)
105                                                 sb.Append (separator);
106                                 }
107                         } else {
108                                 for (int i = 0; i < seq.Count; i++) {
109                                         ASN1 entry = seq [i];
110                                         AppendEntry (sb, entry, quotes);
111
112                                         // separator (not on last iteration)
113                                         if (i < seq.Count - 1)
114                                                 sb.Append (separator);
115                                 }
116                         }
117                         return sb.ToString ();
118                 }
119
120                 static private void AppendEntry (StringBuilder sb, ASN1 entry, bool quotes)
121                 {
122                         // multiple entries are valid
123                         for (int k = 0; k < entry.Count; k++) {
124                                 ASN1 pair = entry [k];
125                                 ASN1 s = pair [1];
126                                 if (s == null)
127                                         continue;
128
129                                 ASN1 poid = pair [0];
130                                 if (poid == null)
131                                         continue;
132
133                                 if (poid.CompareValue (countryName))
134                                         sb.Append ("C=");
135                                 else if (poid.CompareValue (organizationName))
136                                         sb.Append ("O=");
137                                 else if (poid.CompareValue (organizationalUnitName))
138                                         sb.Append ("OU=");
139                                 else if (poid.CompareValue (commonName))
140                                         sb.Append ("CN=");
141                                 else if (poid.CompareValue (localityName))
142                                         sb.Append ("L=");
143                                 else if (poid.CompareValue (stateOrProvinceName))
144                                         sb.Append ("S=");       // NOTE: RFC2253 uses ST=
145                                 else if (poid.CompareValue (streetAddress))
146                                         sb.Append ("STREET=");
147                                 else if (poid.CompareValue (domainComponent))
148                                         sb.Append ("DC=");
149                                 else if (poid.CompareValue (userid))
150                                         sb.Append ("UID=");
151                                 else if (poid.CompareValue (email))
152                                         sb.Append ("E=");       // NOTE: Not part of RFC2253
153                                 else if (poid.CompareValue (dnQualifier))
154                                         sb.Append ("dnQualifier=");
155                                 else if (poid.CompareValue (title))
156                                         sb.Append ("T=");
157                                 else if (poid.CompareValue (surname))
158                                         sb.Append ("SN=");
159                                 else if (poid.CompareValue (givenName))
160                                         sb.Append ("G=");
161                                 else if (poid.CompareValue (initial))
162                                         sb.Append ("I=");
163                                 else {
164                                         // unknown OID
165                                         sb.Append ("OID.");     // NOTE: Not present as RFC2253
166                                         sb.Append (ASN1Convert.ToOid (poid));
167                                         sb.Append ("=");
168                                 }
169
170                                 string sValue = null;
171                                 // 16bits or 8bits string ? TODO not complete (+special chars!)
172                                 if (s.Tag == 0x1E) {
173                                         // BMPSTRING
174                                         StringBuilder sb2 = new StringBuilder ();
175                                         for (int j = 1; j < s.Value.Length; j += 2)
176                                                 sb2.Append ((char)s.Value[j]);
177                                         sValue = sb2.ToString ();
178                                 } else {
179                                         if (s.Tag == 0x14)
180                                                 sValue = Encoding.UTF7.GetString (s.Value);
181                                         else
182                                                 sValue = Encoding.UTF8.GetString (s.Value);
183                                         // in some cases we must quote (") the value
184                                         // Note: this doesn't seems to conform to RFC2253
185                                         char[] specials = { ',', '+', '"', '\\', '<', '>', ';' };
186                                         if (quotes) {
187                                                 if ((sValue.IndexOfAny (specials, 0, sValue.Length) > 0) ||
188                                                     sValue.StartsWith (" ") || (sValue.EndsWith (" ")))
189                                                         sValue = "\"" + sValue + "\"";
190                                         }
191                                 }
192
193                                 sb.Append (sValue);
194
195                                 // separator (not on last iteration)
196                                 if (k < entry.Count - 1)
197                                         sb.Append (", ");
198                         }
199                 }
200
201                 static private X520.AttributeTypeAndValue GetAttributeFromOid (string attributeType) 
202                 {
203                         string s = attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ();
204                         switch (s) {
205                                 case "C":
206                                         return new X520.CountryName ();
207                                 case "O":
208                                         return new X520.OrganizationName ();
209                                 case "OU":
210                                         return new X520.OrganizationalUnitName ();
211                                 case "CN":
212                                         return new X520.CommonName ();
213                                 case "L":
214                                         return new X520.LocalityName ();
215                                 case "S":       // Microsoft
216                                 case "ST":      // RFC2253
217                                         return new X520.StateOrProvinceName ();
218                                 case "E":       // NOTE: Not part of RFC2253
219                                         return new X520.EmailAddress ();
220                                 case "DC":      // RFC2247
221                                         return new X520.DomainComponent ();
222                                 case "UID":     // RFC1274
223                                         return new X520.UserId ();
224                                 case "DNQUALIFIER":
225                                         return new X520.DnQualifier ();
226                                 case "T":
227                                         return new X520.Title ();
228                                 case "SN":
229                                         return new X520.Surname ();
230                                 case "G":
231                                         return new X520.GivenName ();
232                                 case "I":
233                                         return new X520.Initial ();
234                                 default:
235                                         if (s.StartsWith ("OID.")) {
236                                                 // MUST support it but it OID may be without it
237                                                 return new X520.Oid (s.Substring (4));
238                                         } else {
239                                                 if (IsOid (s))
240                                                         return new X520.Oid (s);
241                                                 else
242                                                         return null;
243                                         }
244                         }
245                 }
246
247                 static private bool IsOid (string oid)
248                 {
249                         try {
250                                 ASN1 asn = ASN1Convert.FromOid (oid);
251                                 return (asn.Tag == 0x06);
252                         }
253                         catch {
254                                 return false;
255                         }
256                 }
257
258                 // no quote processing
259                 static private X520.AttributeTypeAndValue ReadAttribute (string value, ref int pos)
260                 {
261                         while ((value[pos] == ' ') && (pos < value.Length))
262                                 pos++;
263
264                         // get '=' position in substring
265                         int equal = value.IndexOf ('=', pos);
266                         if (equal == -1) {
267                                 string msg = Locale.GetText ("No attribute found.");
268                                 throw new FormatException (msg);
269                         }
270
271                         string s = value.Substring (pos, equal - pos);
272                         X520.AttributeTypeAndValue atv = GetAttributeFromOid (s);
273                         if (atv == null) {
274                                 string msg = Locale.GetText ("Unknown attribute '{0}'.");
275                                 throw new FormatException (String.Format (msg, s));
276                         }
277                         pos = equal + 1; // skip the '='
278                         return atv;
279                 }
280
281                 static private bool IsHex (char c)
282                 {
283                         if (Char.IsDigit (c))
284                                 return true;
285                         char up = Char.ToUpper (c, CultureInfo.InvariantCulture);
286                         return ((up >= 'A') && (up <= 'F'));
287                 }
288
289                 static string ReadHex (string value, ref int pos)
290                 {
291                         StringBuilder sb = new StringBuilder ();
292                         // it is (at least an) 8 bits char
293                         sb.Append (value[pos++]);
294                         sb.Append (value[pos]);
295                         // look ahead for a 16 bits char
296                         if ((pos < value.Length - 4) && (value[pos+1] == '\\') && IsHex (value[pos+2])) {
297                                 pos += 2; // pass last char and skip \
298                                 sb.Append (value[pos++]);
299                                 sb.Append (value[pos]);
300                         }
301                         byte[] data = CryptoConvert.FromHex (sb.ToString ());
302                         return Encoding.UTF8.GetString (data);
303                 }
304
305                 static private int ReadEscaped (StringBuilder sb, string value, int pos)
306                 {
307                         switch (value[pos]) {
308                         case '\\':
309                         case '"':
310                         case '=':
311                         case ';':
312                         case '<':
313                         case '>':
314                         case '+':
315                         case '#':
316                         case ',':
317                                 sb.Append (value[pos]);
318                                 return pos;
319                         default:
320                                 if (pos >= value.Length - 2) {
321                                         string msg = Locale.GetText ("Malformed escaped value '{0}'.");
322                                         throw new FormatException (string.Format (msg, value.Substring (pos)));
323                                 }
324                                 // it's either a 8 bits or 16 bits char
325                                 sb.Append (ReadHex (value, ref pos));
326                                 return pos;
327                         }
328                 }
329
330                 static private int ReadQuoted (StringBuilder sb, string value, int pos)
331                 {
332                         int original = pos;
333                         while (pos <= value.Length) {
334                                 switch (value[pos]) {
335                                 case '"':
336                                         return pos;
337                                 case '\\':
338                                         return ReadEscaped (sb, value, pos);
339                                 default:
340                                         sb.Append (value[pos]);
341                                         pos++;
342                                         break;
343                                 }
344                         }
345                         string msg = Locale.GetText ("Malformed quoted value '{0}'.");
346                         throw new FormatException (string.Format (msg, value.Substring (original)));
347                 }
348
349                 static private string ReadValue (string value, ref int pos)
350                 {
351                         int original = pos;
352                         StringBuilder sb = new StringBuilder ();
353                         while (pos < value.Length) {
354                                 switch (value [pos]) {
355                                 case '\\':
356                                         pos = ReadEscaped (sb, value, ++pos);
357                                         break;
358                                 case '"':
359                                         pos = ReadQuoted (sb, value, ++pos);
360                                         break;
361                                 case '=':
362                                 case ';':
363                                 case '<':
364                                 case '>':
365                                         string msg = Locale.GetText ("Malformed value '{0}' contains '{1}' outside quotes.");
366                                         throw new FormatException (string.Format (msg, value.Substring (original), value[pos]));
367                                 case '+':
368                                 case '#':
369                                         throw new NotImplementedException ();
370                                 case ',':
371                                         pos++;
372                                         return sb.ToString ();
373                                 default:
374                                         sb.Append (value[pos]);
375                                         break;
376                                 }
377                                 pos++;
378                         }
379                         return sb.ToString ();
380                 }
381
382                 static public ASN1 FromString (string rdn) 
383                 {
384                         if (rdn == null)
385                                 throw new ArgumentNullException ("rdn");
386
387                         int pos = 0;
388                         ASN1 asn1 = new ASN1 (0x30);
389                         while (pos < rdn.Length) {
390                                 X520.AttributeTypeAndValue atv = ReadAttribute (rdn, ref pos);
391                                 atv.Value = ReadValue (rdn, ref pos);
392
393                                 ASN1 sequence = new ASN1 (0x31);
394                                 sequence.Add (atv.GetASN1 ());
395                                 asn1.Add (sequence); 
396                         }
397                         return asn1;
398                 }
399         }
400 }