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 static public string ToString (ASN1 seq, bool reversed, string separator, bool quotes)
91 StringBuilder sb = new StringBuilder ();
94 for (int i = seq.Count - 1; i >= 0; i--) {
96 AppendEntry (sb, entry, quotes);
98 // separator (not on last iteration)
100 sb.Append (separator);
103 for (int i = 0; i < seq.Count; i++) {
104 ASN1 entry = seq [i];
105 AppendEntry (sb, entry, quotes);
107 // separator (not on last iteration)
108 if (i < seq.Count - 1)
109 sb.Append (separator);
112 return sb.ToString ();
115 static private void AppendEntry (StringBuilder sb, ASN1 entry, bool quotes)
117 // multiple entries are valid
118 for (int k = 0; k < entry.Count; k++) {
119 ASN1 pair = entry [k];
124 ASN1 poid = pair [0];
128 if (poid.CompareValue (countryName))
130 else if (poid.CompareValue (organizationName))
132 else if (poid.CompareValue (organizationalUnitName))
134 else if (poid.CompareValue (commonName))
136 else if (poid.CompareValue (localityName))
138 else if (poid.CompareValue (stateOrProvinceName))
139 sb.Append ("S="); // NOTE: RFC2253 uses ST=
140 else if (poid.CompareValue (streetAddress))
141 sb.Append ("STREET=");
142 else if (poid.CompareValue (domainComponent))
144 else if (poid.CompareValue (userid))
146 else if (poid.CompareValue (email))
147 sb.Append ("E="); // NOTE: Not part of RFC2253
150 sb.Append ("OID."); // NOTE: Not present as RFC2253
151 sb.Append (ASN1Convert.ToOid (poid));
155 string sValue = null;
156 // 16bits or 8bits string ? TODO not complete (+special chars!)
159 StringBuilder sb2 = new StringBuilder ();
160 for (int j = 1; j < s.Value.Length; j += 2)
161 sb2.Append ((char)s.Value[j]);
162 sValue = sb2.ToString ();
164 sValue = Encoding.UTF8.GetString (s.Value);
165 // in some cases we must quote (") the value
166 // Note: this doesn't seems to conform to RFC2253
167 char[] specials = { ',', '+', '"', '\\', '<', '>', ';' };
169 if ((sValue.IndexOfAny (specials, 0, sValue.Length) > 0) ||
170 sValue.StartsWith (" ") || (sValue.EndsWith (" ")))
171 sValue = "\"" + sValue + "\"";
177 // separator (not on last iteration)
178 if (k < entry.Count - 1)
183 static private X520.AttributeTypeAndValue GetAttributeFromOid (string attributeType)
185 string s = attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ();
188 return new X520.CountryName ();
190 return new X520.OrganizationName ();
192 return new X520.OrganizationalUnitName ();
194 return new X520.CommonName ();
196 return new X520.LocalityName ();
197 case "S": // Microsoft
198 case "ST": // RFC2253
199 return new X520.StateOrProvinceName ();
200 case "E": // NOTE: Not part of RFC2253
201 return new X520.EmailAddress ();
202 case "DC": // RFC2247
203 return new X520.DomainComponent ();
204 case "UID": // RFC1274
205 return new X520.UserId ();
208 // MUST support it but it OID may be without it
209 return new X520.Oid (s.Substring (4));
212 return new X520.Oid (s);
219 static private bool IsOid (string oid)
222 ASN1 asn = ASN1Convert.FromOid (oid);
223 return (asn.Tag == 0x06);
230 // no quote processing
231 static private X520.AttributeTypeAndValue ReadAttribute (string value, ref int pos)
233 while ((value[pos] == ' ') && (pos < value.Length))
236 // get '=' position in substring
237 int equal = value.IndexOf ('=', pos);
239 string msg = Locale.GetText ("No attribute found.");
240 throw new FormatException (msg);
243 string s = value.Substring (pos, equal - pos);
244 X520.AttributeTypeAndValue atv = GetAttributeFromOid (s);
246 string msg = Locale.GetText ("Unknown attribute '{0}'.");
247 throw new FormatException (String.Format (msg, s));
249 pos = equal + 1; // skip the '='
253 static private bool IsHex (char c)
255 if (Char.IsDigit (c))
257 char up = Char.ToUpper (c, CultureInfo.InvariantCulture);
258 return ((up >= 'A') && (up <= 'F'));
261 static string ReadHex (string value, ref int pos)
263 StringBuilder sb = new StringBuilder ();
264 // it is (at least an) 8 bits char
265 sb.Append (value[pos++]);
266 sb.Append (value[pos]);
267 // look ahead for a 16 bits char
268 if ((pos < value.Length - 4) && (value[pos+1] == '\\') && IsHex (value[pos+2])) {
269 pos += 2; // pass last char and skip \
270 sb.Append (value[pos++]);
271 sb.Append (value[pos]);
273 byte[] data = CryptoConvert.FromHex (sb.ToString ());
274 return Encoding.UTF8.GetString (data);
277 static private int ReadEscaped (StringBuilder sb, string value, int pos)
279 switch (value[pos]) {
289 sb.Append (value[pos]);
292 if (pos >= value.Length - 2) {
293 string msg = Locale.GetText ("Malformed escaped value '{0}'.");
294 throw new FormatException (string.Format (msg, value.Substring (pos)));
296 // it's either a 8 bits or 16 bits char
297 sb.Append (ReadHex (value, ref pos));
302 static private int ReadQuoted (StringBuilder sb, string value, int pos)
305 while (pos <= value.Length) {
306 switch (value[pos]) {
310 return ReadEscaped (sb, value, pos);
312 sb.Append (value[pos]);
317 string msg = Locale.GetText ("Malformed quoted value '{0}'.");
318 throw new FormatException (string.Format (msg, value.Substring (original)));
321 static private string ReadValue (string value, ref int pos)
324 StringBuilder sb = new StringBuilder ();
325 while (pos < value.Length) {
326 switch (value [pos]) {
328 pos = ReadEscaped (sb, value, ++pos);
331 pos = ReadQuoted (sb, value, ++pos);
337 string msg = Locale.GetText ("Malformed value '{0}' contains '{1}' outside quotes.");
338 throw new FormatException (string.Format (msg, value.Substring (original), value[pos]));
341 throw new NotImplementedException ();
344 return sb.ToString ();
346 sb.Append (value[pos]);
351 return sb.ToString ();
354 static public ASN1 FromString (string rdn)
357 throw new ArgumentNullException ("rdn");
360 ASN1 asn1 = new ASN1 (0x30);
361 while (pos < rdn.Length) {
362 X520.AttributeTypeAndValue atv = ReadAttribute (rdn, ref pos);
363 atv.Value = ReadValue (rdn, ref pos);
365 ASN1 sequence = new ASN1 (0x31);
366 sequence.Add (atv.GetASN1 ());