X501Name.cs 11 KB

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