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