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 }
;
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 }
;
80 static public string ToString (ASN1 seq
)
82 StringBuilder sb
= new StringBuilder ();
83 for (int i
= 0; i
< seq
.Count
; i
++) {
85 AppendEntry (sb
, entry
, true);
87 // separator (not on last iteration)
88 if (i
< seq
.Count
- 1)
91 return sb
.ToString ();
94 static public string ToString (ASN1 seq
, bool reversed
, string separator
, bool quotes
)
96 StringBuilder sb
= new StringBuilder ();
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)
105 sb
.Append (separator
);
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
];
129 ASN1 poid
= pair
[0];
133 if (poid
.CompareValue (countryName
))
135 else if (poid
.CompareValue (organizationName
))
137 else if (poid
.CompareValue (organizationalUnitName
))
139 else if (poid
.CompareValue (commonName
))
141 else if (poid
.CompareValue (localityName
))
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
))
149 else if (poid
.CompareValue (userid
))
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
))
157 else if (poid
.CompareValue (surname
))
159 else if (poid
.CompareValue (givenName
))
161 else if (poid
.CompareValue (initial
))
165 sb
.Append ("OID."); // NOTE: Not present as RFC2253
166 sb
.Append (ASN1Convert
.ToOid (poid
));
170 string sValue
= null;
171 // 16bits or 8bits string ? TODO not complete (+special chars!)
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 ();
180 sValue
= Encoding
.UTF7
.GetString (s
.Value
);
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
= { ',', '+', '"', '\\', '<', '>', ';' }
;
187 if ((sValue
.IndexOfAny (specials
, 0, sValue
.Length
) > 0) ||
188 sValue
.StartsWith (" ") || (sValue
.EndsWith (" ")))
189 sValue
= "\"" + sValue
+ "\"";
195 // separator (not on last iteration)
196 if (k
< entry
.Count
- 1)
201 static private X520
.AttributeTypeAndValue
GetAttributeFromOid (string attributeType
)
203 string s
= attributeType
.ToUpper (CultureInfo
.InvariantCulture
).Trim ();
206 return new X520
.CountryName ();
208 return new X520
.OrganizationName ();
210 return new X520
.OrganizationalUnitName ();
212 return new X520
.CommonName ();
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 ();
225 return new X520
.DnQualifier ();
227 return new X520
.Title ();
229 return new X520
.Surname ();
231 return new X520
.GivenName ();
233 return new X520
.Initial ();
235 if (s
.StartsWith ("OID.")) {
236 // MUST support it but it OID may be without it
237 return new X520
.Oid (s
.Substring (4));
240 return new X520
.Oid (s
);
247 static private bool IsOid (string oid
)
250 ASN1 asn
= ASN1Convert
.FromOid (oid
);
251 return (asn
.Tag
== 0x06);
258 // no quote processing
259 static private X520
.AttributeTypeAndValue
ReadAttribute (string value, ref int pos
)
261 while ((value[pos
] == ' ') && (pos
< value.Length
))
264 // get '=' position in substring
265 int equal
= value.IndexOf ('=', pos
);
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
);
274 string msg
= Locale
.GetText ("Unknown attribute '{0}'.");
275 throw new FormatException (String
.Format (msg
, s
));
277 pos
= equal
+ 1; // skip the '='
281 static private bool IsHex (char c
)
283 if (Char
.IsDigit (c
))
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
]) {
317 sb
.Append (value[pos
]);
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
));
330 static private int ReadQuoted (StringBuilder sb
, string value, int pos
)
333 while (pos
<= value.Length
) {
334 switch (value[pos
]) {
338 return ReadEscaped (sb
, value, pos
);
340 sb
.Append (value[pos
]);
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
)
352 StringBuilder sb
= new StringBuilder ();
353 while (pos
< value.Length
) {
354 switch (value [pos
]) {
356 pos
= ReadEscaped (sb
, value, ++pos
);
359 pos
= ReadQuoted (sb
, value, ++pos
);
365 string msg
= Locale
.GetText ("Malformed value '{0}' contains '{1}' outside quotes.");
366 throw new FormatException (string.Format (msg
, value.Substring (original
), value[pos
]));
369 throw new NotImplementedException ();
372 return sb
.ToString ();
374 sb
.Append (value[pos
]);
379 return sb
.ToString ();
382 static public ASN1
FromString (string rdn
)
385 throw new ArgumentNullException ("rdn");
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 ());