X501Name.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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.
  28. //
  29. using System;
  30. using System.Globalization;
  31. using System.Text;
  32. namespace MediaBrowser.Server.Mono.Security {
  33. // References:
  34. // 1. Information technology - Open Systems Interconnection - The Directory: Models
  35. // http://www.itu.int/rec/recommendation.asp?type=items&lang=e&parent=T-REC-X.501-200102-I
  36. // 2. RFC2253: Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names
  37. // http://www.ietf.org/rfc/rfc2253.txt
  38. /*
  39. * Name ::= CHOICE { RDNSequence }
  40. *
  41. * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
  42. *
  43. * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
  44. */
  45. public sealed class X501 {
  46. static byte[] countryName = { 0x55, 0x04, 0x06 };
  47. static byte[] organizationName = { 0x55, 0x04, 0x0A };
  48. static byte[] organizationalUnitName = { 0x55, 0x04, 0x0B };
  49. static byte[] commonName = { 0x55, 0x04, 0x03 };
  50. static byte[] localityName = { 0x55, 0x04, 0x07 };
  51. static byte[] stateOrProvinceName = { 0x55, 0x04, 0x08 };
  52. static byte[] streetAddress = { 0x55, 0x04, 0x09 };
  53. //static byte[] serialNumber = { 0x55, 0x04, 0x05 };
  54. static byte[] domainComponent = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 };
  55. static byte[] userid = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x01 };
  56. static byte[] email = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 };
  57. static byte[] dnQualifier = { 0x55, 0x04, 0x2E };
  58. static byte[] title = { 0x55, 0x04, 0x0C };
  59. static byte[] surname = { 0x55, 0x04, 0x04 };
  60. static byte[] givenName = { 0x55, 0x04, 0x2A };
  61. static byte[] initial = { 0x55, 0x04, 0x2B };
  62. private X501 ()
  63. {
  64. }
  65. static public string ToString (ASN1 seq)
  66. {
  67. StringBuilder sb = new StringBuilder ();
  68. for (int i = 0; i < seq.Count; i++) {
  69. ASN1 entry = seq [i];
  70. AppendEntry (sb, entry, true);
  71. // separator (not on last iteration)
  72. if (i < seq.Count - 1)
  73. sb.Append (", ");
  74. }
  75. return sb.ToString ();
  76. }
  77. static public string ToString (ASN1 seq, bool reversed, string separator, bool quotes)
  78. {
  79. StringBuilder sb = new StringBuilder ();
  80. if (reversed) {
  81. for (int i = seq.Count - 1; i >= 0; i--) {
  82. ASN1 entry = seq [i];
  83. AppendEntry (sb, entry, quotes);
  84. // separator (not on last iteration)
  85. if (i > 0)
  86. sb.Append (separator);
  87. }
  88. } else {
  89. for (int i = 0; i < seq.Count; i++) {
  90. ASN1 entry = seq [i];
  91. AppendEntry (sb, entry, quotes);
  92. // separator (not on last iteration)
  93. if (i < seq.Count - 1)
  94. sb.Append (separator);
  95. }
  96. }
  97. return sb.ToString ();
  98. }
  99. static private void AppendEntry (StringBuilder sb, ASN1 entry, bool quotes)
  100. {
  101. // multiple entries are valid
  102. for (int k = 0; k < entry.Count; k++) {
  103. ASN1 pair = entry [k];
  104. ASN1 s = pair [1];
  105. if (s == null)
  106. continue;
  107. ASN1 poid = pair [0];
  108. if (poid == null)
  109. continue;
  110. if (poid.CompareValue (countryName))
  111. sb.Append ("C=");
  112. else if (poid.CompareValue (organizationName))
  113. sb.Append ("O=");
  114. else if (poid.CompareValue (organizationalUnitName))
  115. sb.Append ("OU=");
  116. else if (poid.CompareValue (commonName))
  117. sb.Append ("CN=");
  118. else if (poid.CompareValue (localityName))
  119. sb.Append ("L=");
  120. else if (poid.CompareValue (stateOrProvinceName))
  121. sb.Append ("S="); // NOTE: RFC2253 uses ST=
  122. else if (poid.CompareValue (streetAddress))
  123. sb.Append ("STREET=");
  124. else if (poid.CompareValue (domainComponent))
  125. sb.Append ("DC=");
  126. else if (poid.CompareValue (userid))
  127. sb.Append ("UID=");
  128. else if (poid.CompareValue (email))
  129. sb.Append ("E="); // NOTE: Not part of RFC2253
  130. else if (poid.CompareValue (dnQualifier))
  131. sb.Append ("dnQualifier=");
  132. else if (poid.CompareValue (title))
  133. sb.Append ("T=");
  134. else if (poid.CompareValue (surname))
  135. sb.Append ("SN=");
  136. else if (poid.CompareValue (givenName))
  137. sb.Append ("G=");
  138. else if (poid.CompareValue (initial))
  139. sb.Append ("I=");
  140. else {
  141. // unknown OID
  142. sb.Append ("OID."); // NOTE: Not present as RFC2253
  143. sb.Append (ASN1Convert.ToOid (poid));
  144. sb.Append ("=");
  145. }
  146. string sValue = null;
  147. // 16bits or 8bits string ? TODO not complete (+special chars!)
  148. if (s.Tag == 0x1E) {
  149. // BMPSTRING
  150. StringBuilder sb2 = new StringBuilder ();
  151. for (int j = 1; j < s.Value.Length; j += 2)
  152. sb2.Append ((char)s.Value[j]);
  153. sValue = sb2.ToString ();
  154. } else {
  155. if (s.Tag == 0x14)
  156. sValue = Encoding.UTF7.GetString (s.Value);
  157. else
  158. sValue = Encoding.UTF8.GetString (s.Value);
  159. // in some cases we must quote (") the value
  160. // Note: this doesn't seems to conform to RFC2253
  161. char[] specials = { ',', '+', '"', '\\', '<', '>', ';' };
  162. if (quotes) {
  163. if ((sValue.IndexOfAny (specials, 0, sValue.Length) > 0) ||
  164. sValue.StartsWith (" ") || (sValue.EndsWith (" ")))
  165. sValue = "\"" + sValue + "\"";
  166. }
  167. }
  168. sb.Append (sValue);
  169. // separator (not on last iteration)
  170. if (k < entry.Count - 1)
  171. sb.Append (", ");
  172. }
  173. }
  174. static private X520.AttributeTypeAndValue GetAttributeFromOid (string attributeType)
  175. {
  176. string s = attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ();
  177. switch (s) {
  178. case "C":
  179. return new X520.CountryName ();
  180. case "O":
  181. return new X520.OrganizationName ();
  182. case "OU":
  183. return new X520.OrganizationalUnitName ();
  184. case "CN":
  185. return new X520.CommonName ();
  186. case "L":
  187. return new X520.LocalityName ();
  188. case "S": // Microsoft
  189. case "ST": // RFC2253
  190. return new X520.StateOrProvinceName ();
  191. case "E": // NOTE: Not part of RFC2253
  192. return new X520.EmailAddress ();
  193. case "DC": // RFC2247
  194. return new X520.DomainComponent ();
  195. case "UID": // RFC1274
  196. return new X520.UserId ();
  197. case "DNQUALIFIER":
  198. return new X520.DnQualifier ();
  199. case "T":
  200. return new X520.Title ();
  201. case "SN":
  202. return new X520.Surname ();
  203. case "G":
  204. return new X520.GivenName ();
  205. case "I":
  206. return new X520.Initial ();
  207. default:
  208. if (s.StartsWith ("OID.")) {
  209. // MUST support it but it OID may be without it
  210. return new X520.Oid (s.Substring (4));
  211. } else {
  212. if (IsOid (s))
  213. return new X520.Oid (s);
  214. else
  215. return null;
  216. }
  217. }
  218. }
  219. static private bool IsOid (string oid)
  220. {
  221. try {
  222. ASN1 asn = ASN1Convert.FromOid (oid);
  223. return (asn.Tag == 0x06);
  224. }
  225. catch {
  226. return false;
  227. }
  228. }
  229. // no quote processing
  230. static private X520.AttributeTypeAndValue ReadAttribute (string value, ref int pos)
  231. {
  232. while ((value[pos] == ' ') && (pos < value.Length))
  233. pos++;
  234. // get '=' position in substring
  235. int equal = value.IndexOf ('=', pos);
  236. if (equal == -1) {
  237. string msg = ("No attribute found.");
  238. throw new FormatException (msg);
  239. }
  240. string s = value.Substring (pos, equal - pos);
  241. X520.AttributeTypeAndValue atv = GetAttributeFromOid (s);
  242. if (atv == null) {
  243. string msg = ("Unknown attribute '{0}'.");
  244. throw new FormatException (String.Format (msg, s));
  245. }
  246. pos = equal + 1; // skip the '='
  247. return atv;
  248. }
  249. static private bool IsHex (char c)
  250. {
  251. if (Char.IsDigit (c))
  252. return true;
  253. char up = Char.ToUpper (c, CultureInfo.InvariantCulture);
  254. return ((up >= 'A') && (up <= 'F'));
  255. }
  256. static string ReadHex (string value, ref int pos)
  257. {
  258. StringBuilder sb = new StringBuilder ();
  259. // it is (at least an) 8 bits char
  260. sb.Append (value[pos++]);
  261. sb.Append (value[pos]);
  262. // look ahead for a 16 bits char
  263. if ((pos < value.Length - 4) && (value[pos+1] == '\\') && IsHex (value[pos+2])) {
  264. pos += 2; // pass last char and skip \
  265. sb.Append (value[pos++]);
  266. sb.Append (value[pos]);
  267. }
  268. byte[] data = CryptoConvert.FromHex (sb.ToString ());
  269. return Encoding.UTF8.GetString (data);
  270. }
  271. static private int ReadEscaped (StringBuilder sb, string value, int pos)
  272. {
  273. switch (value[pos]) {
  274. case '\\':
  275. case '"':
  276. case '=':
  277. case ';':
  278. case '<':
  279. case '>':
  280. case '+':
  281. case '#':
  282. case ',':
  283. sb.Append (value[pos]);
  284. return pos;
  285. default:
  286. if (pos >= value.Length - 2) {
  287. string msg = ("Malformed escaped value '{0}'.");
  288. throw new FormatException (string.Format (msg, value.Substring (pos)));
  289. }
  290. // it's either a 8 bits or 16 bits char
  291. sb.Append (ReadHex (value, ref pos));
  292. return pos;
  293. }
  294. }
  295. static private int ReadQuoted (StringBuilder sb, string value, int pos)
  296. {
  297. int original = pos;
  298. while (pos <= value.Length) {
  299. switch (value[pos]) {
  300. case '"':
  301. return pos;
  302. case '\\':
  303. return ReadEscaped (sb, value, pos);
  304. default:
  305. sb.Append (value[pos]);
  306. pos++;
  307. break;
  308. }
  309. }
  310. string msg = ("Malformed quoted value '{0}'.");
  311. throw new FormatException (string.Format (msg, value.Substring (original)));
  312. }
  313. static private string ReadValue (string value, ref int pos)
  314. {
  315. int original = pos;
  316. StringBuilder sb = new StringBuilder ();
  317. while (pos < value.Length) {
  318. switch (value [pos]) {
  319. case '\\':
  320. pos = ReadEscaped (sb, value, ++pos);
  321. break;
  322. case '"':
  323. pos = ReadQuoted (sb, value, ++pos);
  324. break;
  325. case '=':
  326. case ';':
  327. case '<':
  328. case '>':
  329. string msg =("Malformed value '{0}' contains '{1}' outside quotes.");
  330. throw new FormatException (string.Format (msg, value.Substring (original), value[pos]));
  331. case '+':
  332. case '#':
  333. throw new NotImplementedException ();
  334. case ',':
  335. pos++;
  336. return sb.ToString ();
  337. default:
  338. sb.Append (value[pos]);
  339. break;
  340. }
  341. pos++;
  342. }
  343. return sb.ToString ();
  344. }
  345. static public ASN1 FromString (string rdn)
  346. {
  347. if (rdn == null)
  348. throw new ArgumentNullException ("rdn");
  349. int pos = 0;
  350. ASN1 asn1 = new ASN1 (0x30);
  351. while (pos < rdn.Length) {
  352. X520.AttributeTypeAndValue atv = ReadAttribute (rdn, ref pos);
  353. atv.Value = ReadValue (rdn, ref pos);
  354. ASN1 sequence = new ASN1 (0x31);
  355. sequence.Add (atv.GetASN1 ());
  356. asn1.Add (sequence);
  357. }
  358. return asn1;
  359. }
  360. }
  361. }