2010-04-06 Jb Evain <jbevain@novell.com>
[mcs.git] / class / corlib / Mono.Security.X509 / X501Name.cs
blob8343e5fb557eab20797796e54a31cb5159cddae4
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.
30 using System;
31 using System.Globalization;
32 using System.Text;
34 using Mono.Security;
35 using Mono.Security.Cryptography;
37 namespace Mono.Security.X509 {
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
46 * Name ::= CHOICE { RDNSequence }
48 * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
50 * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
52 #if INSIDE_CORLIB
53 internal
54 #else
55 public
56 #endif
57 sealed class X501 {
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 };
76 private X501 ()
80 static public string ToString (ASN1 seq)
82 StringBuilder sb = new StringBuilder ();
83 for (int i = 0; i < seq.Count; i++) {
84 ASN1 entry = seq [i];
85 AppendEntry (sb, entry, true);
87 // separator (not on last iteration)
88 if (i < seq.Count - 1)
89 sb.Append (", ");
91 return sb.ToString ();
94 static public string ToString (ASN1 seq, bool reversed, string separator, bool quotes)
96 StringBuilder sb = new StringBuilder ();
98 if (reversed) {
99 for (int i = seq.Count - 1; i >= 0; i--) {
100 ASN1 entry = seq [i];
101 AppendEntry (sb, entry, quotes);
103 // separator (not on last iteration)
104 if (i > 0)
105 sb.Append (separator);
107 } else {
108 for (int i = 0; i < seq.Count; i++) {
109 ASN1 entry = seq [i];
110 AppendEntry (sb, entry, quotes);
112 // separator (not on last iteration)
113 if (i < seq.Count - 1)
114 sb.Append (separator);
117 return sb.ToString ();
120 static private void AppendEntry (StringBuilder sb, ASN1 entry, bool quotes)
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;
129 ASN1 poid = pair [0];
130 if (poid == null)
131 continue;
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 ("=");
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 + "\"";
193 sb.Append (sValue);
195 // separator (not on last iteration)
196 if (k < entry.Count - 1)
197 sb.Append (", ");
201 static private X520.AttributeTypeAndValue GetAttributeFromOid (string attributeType)
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;
247 static private bool IsOid (string oid)
249 try {
250 ASN1 asn = ASN1Convert.FromOid (oid);
251 return (asn.Tag == 0x06);
253 catch {
254 return false;
258 // no quote processing
259 static private X520.AttributeTypeAndValue ReadAttribute (string value, ref int pos)
261 while ((value[pos] == ' ') && (pos < value.Length))
262 pos++;
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);
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));
277 pos = equal + 1; // skip the '='
278 return atv;
281 static private bool IsHex (char c)
283 if (Char.IsDigit (c))
284 return true;
285 char up = Char.ToUpper (c, CultureInfo.InvariantCulture);
286 return ((up >= 'A') && (up <= 'F'));
289 static string ReadHex (string value, ref int pos)
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]);
301 byte[] data = CryptoConvert.FromHex (sb.ToString ());
302 return Encoding.UTF8.GetString (data);
305 static private int ReadEscaped (StringBuilder sb, string value, int pos)
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)));
324 // it's either a 8 bits or 16 bits char
325 sb.Append (ReadHex (value, ref pos));
326 return pos;
330 static private int ReadQuoted (StringBuilder sb, string value, int pos)
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;
345 string msg = Locale.GetText ("Malformed quoted value '{0}'.");
346 throw new FormatException (string.Format (msg, value.Substring (original)));
349 static private string ReadValue (string value, ref int pos)
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;
377 pos++;
379 return sb.ToString ();
382 static public ASN1 FromString (string rdn)
384 if (rdn == null)
385 throw new ArgumentNullException ("rdn");
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);
393 ASN1 sequence = new ASN1 (0x31);
394 sequence.Add (atv.GetASN1 ());
395 asn1.Add (sequence);
397 return asn1;