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-2005 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 // multiple entries are valid
81 for (int k = 0; k < entry.Count; k++) {
82 ASN1 pair = entry [k];
91 if (poid.CompareValue (countryName))
93 else if (poid.CompareValue (organizationName))
95 else if (poid.CompareValue (organizationalUnitName))
97 else if (poid.CompareValue (commonName))
99 else if (poid.CompareValue (localityName))
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))
107 else if (poid.CompareValue (userid))
109 else if (poid.CompareValue (email))
110 sb.Append ("E="); // NOTE: Not part of RFC2253
113 sb.Append ("OID."); // NOTE: Not present as RFC2253
114 sb.Append (ASN1Convert.ToOid (poid));
118 string sValue = null;
119 // 16bits or 8bits string ? TODO not complete (+special chars!)
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 ();
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 + "\"";
141 // separator (not on last iteration)
142 if (k < entry.Count - 1)
146 // separator (not on last iteration)
147 if (i < seq.Count - 1)
150 return sb.ToString ();
153 static private X520.AttributeTypeAndValue GetAttributeFromOid (string attributeType)
155 string s = attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ();
158 return new X520.CountryName ();
160 return new X520.OrganizationName ();
162 return new X520.OrganizationalUnitName ();
164 return new X520.CommonName ();
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 ();
178 // MUST support it but it OID may be without it
179 return new X520.Oid (s.Substring (4));
182 return new X520.Oid (s);
189 static private bool IsOid (string oid)
192 ASN1 asn = ASN1Convert.FromOid (oid);
193 return (asn.Tag == 0x06);
200 // no quote processing
201 static private X520.AttributeTypeAndValue ReadAttribute (string value, ref int pos)
203 while ((value[pos] == ' ') && (pos < value.Length))
206 // get '=' position in substring
207 int equal = value.IndexOf ('=', pos);
209 string msg = Locale.GetText ("No attribute found.");
210 throw new FormatException (msg);
213 string s = value.Substring (pos, equal - pos);
214 X520.AttributeTypeAndValue atv = GetAttributeFromOid (s);
216 string msg = Locale.GetText ("Unknown attribute '{0}'.");
217 throw new FormatException (String.Format (msg, s));
219 pos = equal + 1; // skip the '='
223 static private bool IsHex (char c)
225 if (Char.IsDigit (c))
227 char up = Char.ToUpper (c, CultureInfo.InvariantCulture);
228 return ((up >= 'A') && (up <= 'F'));
231 static string ReadHex (string value, ref int pos)
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]);
243 byte[] data = CryptoConvert.FromHex (sb.ToString ());
244 return Encoding.UTF8.GetString (data);
247 static private int ReadEscaped (StringBuilder sb, string value, int pos)
249 switch (value[pos]) {
259 sb.Append (value[pos]);
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)));
266 // it's either a 8 bits or 16 bits char
267 sb.Append (ReadHex (value, ref pos));
272 static private int ReadQuoted (StringBuilder sb, string value, int pos)
275 while (pos <= value.Length) {
276 switch (value[pos]) {
280 return ReadEscaped (sb, value, pos);
282 sb.Append (value[pos]);
287 string msg = Locale.GetText ("Malformed quoted value '{0}'.");
288 throw new FormatException (string.Format (msg, value.Substring (original)));
291 static private string ReadValue (string value, ref int pos)
294 StringBuilder sb = new StringBuilder ();
295 while (pos < value.Length) {
296 switch (value [pos]) {
298 pos = ReadEscaped (sb, value, ++pos);
301 pos = ReadQuoted (sb, value, ++pos);
307 string msg = Locale.GetText ("Malformed value '{0}' contains '{1}' outside quotes.");
308 throw new FormatException (string.Format (msg, value.Substring (original), value[pos]));
311 throw new NotImplementedException ();
314 return sb.ToString ();
316 sb.Append (value[pos]);
321 return sb.ToString ();
324 static public ASN1 FromString (string rdn)
327 throw new ArgumentNullException ("rdn");
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);
335 ASN1 sequence = new ASN1 (0x31);
336 sequence.Add (atv.GetASN1 ());