123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- //
- // X501Name.cs: X.501 Distinguished Names stuff
- //
- // Author:
- // Sebastien Pouliot <sebastien@ximian.com>
- //
- // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
- // Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
- //
- // Permission is hereby granted, free of charge, to any person obtaining
- // a copy of this software and associated documentation files (the
- // "Software"), to deal in the Software without restriction, including
- // without limitation the rights to use, copy, modify, merge, publish,
- // distribute, sublicense, and/or sell copies of the Software, and to
- // permit persons to whom the Software is furnished to do so, subject to
- // the following conditions:
- //
- // The above copyright notice and this permission notice shall be
- // included in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- //
- using System;
- using System.Globalization;
- using System.Text;
- namespace Emby.Server.Core.Cryptography
- {
- // References:
- // 1. Information technology - Open Systems Interconnection - The Directory: Models
- // http://www.itu.int/rec/recommendation.asp?type=items&lang=e&parent=T-REC-X.501-200102-I
- // 2. RFC2253: Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names
- // http://www.ietf.org/rfc/rfc2253.txt
- /*
- * Name ::= CHOICE { RDNSequence }
- *
- * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
- *
- * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
- */
- public sealed class X501 {
- static byte[] countryName = { 0x55, 0x04, 0x06 };
- static byte[] organizationName = { 0x55, 0x04, 0x0A };
- static byte[] organizationalUnitName = { 0x55, 0x04, 0x0B };
- static byte[] commonName = { 0x55, 0x04, 0x03 };
- static byte[] localityName = { 0x55, 0x04, 0x07 };
- static byte[] stateOrProvinceName = { 0x55, 0x04, 0x08 };
- static byte[] streetAddress = { 0x55, 0x04, 0x09 };
- //static byte[] serialNumber = { 0x55, 0x04, 0x05 };
- static byte[] domainComponent = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 };
- static byte[] userid = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x01 };
- static byte[] email = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 };
- static byte[] dnQualifier = { 0x55, 0x04, 0x2E };
- static byte[] title = { 0x55, 0x04, 0x0C };
- static byte[] surname = { 0x55, 0x04, 0x04 };
- static byte[] givenName = { 0x55, 0x04, 0x2A };
- static byte[] initial = { 0x55, 0x04, 0x2B };
- private X501 ()
- {
- }
- static public string ToString (ASN1 seq)
- {
- StringBuilder sb = new StringBuilder ();
- for (int i = 0; i < seq.Count; i++) {
- ASN1 entry = seq [i];
- AppendEntry (sb, entry, true);
- // separator (not on last iteration)
- if (i < seq.Count - 1)
- sb.Append (", ");
- }
- return sb.ToString ();
- }
- static public string ToString (ASN1 seq, bool reversed, string separator, bool quotes)
- {
- StringBuilder sb = new StringBuilder ();
- if (reversed) {
- for (int i = seq.Count - 1; i >= 0; i--) {
- ASN1 entry = seq [i];
- AppendEntry (sb, entry, quotes);
- // separator (not on last iteration)
- if (i > 0)
- sb.Append (separator);
- }
- } else {
- for (int i = 0; i < seq.Count; i++) {
- ASN1 entry = seq [i];
- AppendEntry (sb, entry, quotes);
- // separator (not on last iteration)
- if (i < seq.Count - 1)
- sb.Append (separator);
- }
- }
- return sb.ToString ();
- }
- static private void AppendEntry (StringBuilder sb, ASN1 entry, bool quotes)
- {
- // multiple entries are valid
- for (int k = 0; k < entry.Count; k++) {
- ASN1 pair = entry [k];
- ASN1 s = pair [1];
- if (s == null)
- continue;
- ASN1 poid = pair [0];
- if (poid == null)
- continue;
- if (poid.CompareValue (countryName))
- sb.Append ("C=");
- else if (poid.CompareValue (organizationName))
- sb.Append ("O=");
- else if (poid.CompareValue (organizationalUnitName))
- sb.Append ("OU=");
- else if (poid.CompareValue (commonName))
- sb.Append ("CN=");
- else if (poid.CompareValue (localityName))
- sb.Append ("L=");
- else if (poid.CompareValue (stateOrProvinceName))
- sb.Append ("S="); // NOTE: RFC2253 uses ST=
- else if (poid.CompareValue (streetAddress))
- sb.Append ("STREET=");
- else if (poid.CompareValue (domainComponent))
- sb.Append ("DC=");
- else if (poid.CompareValue (userid))
- sb.Append ("UID=");
- else if (poid.CompareValue (email))
- sb.Append ("E="); // NOTE: Not part of RFC2253
- else if (poid.CompareValue (dnQualifier))
- sb.Append ("dnQualifier=");
- else if (poid.CompareValue (title))
- sb.Append ("T=");
- else if (poid.CompareValue (surname))
- sb.Append ("SN=");
- else if (poid.CompareValue (givenName))
- sb.Append ("G=");
- else if (poid.CompareValue (initial))
- sb.Append ("I=");
- else {
- // unknown OID
- sb.Append ("OID."); // NOTE: Not present as RFC2253
- sb.Append (ASN1Convert.ToOid (poid));
- sb.Append ("=");
- }
- string sValue = null;
- // 16bits or 8bits string ? TODO not complete (+special chars!)
- if (s.Tag == 0x1E) {
- // BMPSTRING
- StringBuilder sb2 = new StringBuilder ();
- for (int j = 1; j < s.Value.Length; j += 2)
- sb2.Append ((char)s.Value[j]);
- sValue = sb2.ToString ();
- } else {
- if (s.Tag == 0x14)
- sValue = Encoding.UTF7.GetString (s.Value);
- else
- sValue = Encoding.UTF8.GetString (s.Value);
- // in some cases we must quote (") the value
- // Note: this doesn't seems to conform to RFC2253
- char[] specials = { ',', '+', '"', '\\', '<', '>', ';' };
- if (quotes) {
- if ((sValue.IndexOfAny (specials, 0, sValue.Length) > 0) ||
- sValue.StartsWith (" ") || (sValue.EndsWith (" ")))
- sValue = "\"" + sValue + "\"";
- }
- }
- sb.Append (sValue);
- // separator (not on last iteration)
- if (k < entry.Count - 1)
- sb.Append (", ");
- }
- }
- static private X520.AttributeTypeAndValue GetAttributeFromOid (string attributeType)
- {
- string s = attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ();
- switch (s) {
- case "C":
- return new X520.CountryName ();
- case "O":
- return new X520.OrganizationName ();
- case "OU":
- return new X520.OrganizationalUnitName ();
- case "CN":
- return new X520.CommonName ();
- case "L":
- return new X520.LocalityName ();
- case "S": // Microsoft
- case "ST": // RFC2253
- return new X520.StateOrProvinceName ();
- case "E": // NOTE: Not part of RFC2253
- return new X520.EmailAddress ();
- case "DC": // RFC2247
- return new X520.DomainComponent ();
- case "UID": // RFC1274
- return new X520.UserId ();
- case "DNQUALIFIER":
- return new X520.DnQualifier ();
- case "T":
- return new X520.Title ();
- case "SN":
- return new X520.Surname ();
- case "G":
- return new X520.GivenName ();
- case "I":
- return new X520.Initial ();
- default:
- if (s.StartsWith ("OID.")) {
- // MUST support it but it OID may be without it
- return new X520.Oid (s.Substring (4));
- } else {
- if (IsOid (s))
- return new X520.Oid (s);
- else
- return null;
- }
- }
- }
- static private bool IsOid (string oid)
- {
- try {
- ASN1 asn = ASN1Convert.FromOid (oid);
- return (asn.Tag == 0x06);
- }
- catch {
- return false;
- }
- }
- // no quote processing
- static private X520.AttributeTypeAndValue ReadAttribute (string value, ref int pos)
- {
- while ((value[pos] == ' ') && (pos < value.Length))
- pos++;
- // get '=' position in substring
- int equal = value.IndexOf ('=', pos);
- if (equal == -1) {
- string msg = ("No attribute found.");
- throw new FormatException (msg);
- }
- string s = value.Substring (pos, equal - pos);
- X520.AttributeTypeAndValue atv = GetAttributeFromOid (s);
- if (atv == null) {
- string msg = ("Unknown attribute '{0}'.");
- throw new FormatException (String.Format (msg, s));
- }
- pos = equal + 1; // skip the '='
- return atv;
- }
- static private bool IsHex (char c)
- {
- if (Char.IsDigit (c))
- return true;
- char up = Char.ToUpper (c, CultureInfo.InvariantCulture);
- return ((up >= 'A') && (up <= 'F'));
- }
- static string ReadHex (string value, ref int pos)
- {
- StringBuilder sb = new StringBuilder ();
- // it is (at least an) 8 bits char
- sb.Append (value[pos++]);
- sb.Append (value[pos]);
- // look ahead for a 16 bits char
- if ((pos < value.Length - 4) && (value[pos+1] == '\\') && IsHex (value[pos+2])) {
- pos += 2; // pass last char and skip \
- sb.Append (value[pos++]);
- sb.Append (value[pos]);
- }
- byte[] data = CryptoConvert.FromHex (sb.ToString ());
- return Encoding.UTF8.GetString (data);
- }
- static private int ReadEscaped (StringBuilder sb, string value, int pos)
- {
- switch (value[pos]) {
- case '\\':
- case '"':
- case '=':
- case ';':
- case '<':
- case '>':
- case '+':
- case '#':
- case ',':
- sb.Append (value[pos]);
- return pos;
- default:
- if (pos >= value.Length - 2) {
- string msg = ("Malformed escaped value '{0}'.");
- throw new FormatException (string.Format (msg, value.Substring (pos)));
- }
- // it's either a 8 bits or 16 bits char
- sb.Append (ReadHex (value, ref pos));
- return pos;
- }
- }
- static private int ReadQuoted (StringBuilder sb, string value, int pos)
- {
- int original = pos;
- while (pos <= value.Length) {
- switch (value[pos]) {
- case '"':
- return pos;
- case '\\':
- return ReadEscaped (sb, value, pos);
- default:
- sb.Append (value[pos]);
- pos++;
- break;
- }
- }
- string msg = ("Malformed quoted value '{0}'.");
- throw new FormatException (string.Format (msg, value.Substring (original)));
- }
- static private string ReadValue (string value, ref int pos)
- {
- int original = pos;
- StringBuilder sb = new StringBuilder ();
- while (pos < value.Length) {
- switch (value [pos]) {
- case '\\':
- pos = ReadEscaped (sb, value, ++pos);
- break;
- case '"':
- pos = ReadQuoted (sb, value, ++pos);
- break;
- case '=':
- case ';':
- case '<':
- case '>':
- string msg =("Malformed value '{0}' contains '{1}' outside quotes.");
- throw new FormatException (string.Format (msg, value.Substring (original), value[pos]));
- case '+':
- case '#':
- throw new NotImplementedException ();
- case ',':
- pos++;
- return sb.ToString ();
- default:
- sb.Append (value[pos]);
- break;
- }
- pos++;
- }
- return sb.ToString ();
- }
- static public ASN1 FromString (string rdn)
- {
- if (rdn == null)
- throw new ArgumentNullException ("rdn");
- int pos = 0;
- ASN1 asn1 = new ASN1 (0x30);
- while (pos < rdn.Length) {
- X520.AttributeTypeAndValue atv = ReadAttribute (rdn, ref pos);
- atv.Value = ReadValue (rdn, ref pos);
- ASN1 sequence = new ASN1 (0x31);
- sequence.Add (atv.GetASN1 ());
- asn1.Add (sequence);
- }
- return asn1;
- }
- }
- }
|