* BindingSourceTest.cs: New Filter/RemoveFilter tests.
[mono.git] / mcs / class / corlib / Mono.Security.X509 / X501Name.cs
index 7dd5c1a9f89e75dd10d9c451d11c188cfd83e8bf..8343e5fb557eab20797796e54a31cb5159cddae4 100644 (file)
@@ -5,9 +5,7 @@
 //     Sebastien Pouliot <sebastien@ximian.com>
 //
 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
-// (C) 2004 Novell (http://www.novell.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
@@ -34,6 +32,7 @@ using System.Globalization;
 using System.Text;
 
 using Mono.Security;
+using Mono.Security.Cryptography;
 
 namespace Mono.Security.X509 {
 
@@ -68,6 +67,11 @@ namespace Mono.Security.X509 {
                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 () 
                {
@@ -78,71 +82,7 @@ namespace Mono.Security.X509 {
                        StringBuilder sb = new StringBuilder ();
                        for (int i = 0; i < seq.Count; i++) {
                                ASN1 entry = seq [i];
-                               // multiple entries are valid\r
-                               for (int k = 0; k < entry.Count; k++) {\r
-                                       ASN1 pair = entry [k];\r
-                                       ASN1 s = pair [1];\r
-                                       if (s == null)\r
-                                               continue;\r
-\r
-                                       ASN1 poid = pair [0];\r
-                                       if (poid == null)\r
-                                               continue;\r
-\r
-                                       if (poid.CompareValue (countryName))\r
-                                               sb.Append ("C=");\r
-                                       else if (poid.CompareValue (organizationName))\r
-                                               sb.Append ("O=");\r
-                                       else if (poid.CompareValue (organizationalUnitName))\r
-                                               sb.Append ("OU=");\r
-                                       else if (poid.CompareValue (commonName))\r
-                                               sb.Append ("CN=");\r
-                                       else if (poid.CompareValue (localityName))\r
-                                               sb.Append ("L=");\r
-                                       else if (poid.CompareValue (stateOrProvinceName))\r
-                                               sb.Append ("S=");       // NOTE: RFC2253 uses ST=\r
-                                       else if (poid.CompareValue (streetAddress))\r
-                                               sb.Append ("STREET=");\r
-                                       else if (poid.CompareValue (domainComponent))\r
-                                               sb.Append ("DC=");\r
-                                       else if (poid.CompareValue (userid))\r
-                                               sb.Append ("UID=");\r
-                                       else if (poid.CompareValue (email))\r
-                                               sb.Append ("E=");       // NOTE: Not part of RFC2253\r
-                                       else {\r
-                                               // unknown OID\r
-                                               sb.Append ("OID.");     // NOTE: Not present as RFC2253\r
-                                               sb.Append (ASN1Convert.ToOid (poid));\r
-                                               sb.Append ("=");\r
-                                       }\r
-\r
-                                       string sValue = null;\r
-                                       // 16bits or 8bits string ? TODO not complete (+special chars!)\r
-                                       if (s.Tag == 0x1E) {\r
-                                               // BMPSTRING\r
-                                               StringBuilder sb2 = new StringBuilder ();\r
-                                               for (int j = 1; j < s.Value.Length; j += 2)\r
-                                                       sb2.Append ((char)s.Value[j]);\r
-                                               sValue = sb2.ToString ();\r
-                                       } else {\r
-                                               sValue = System.Text.Encoding.UTF8.GetString (s.Value);\r
-                                               // in some cases we must quote (") the value\r
-                                               // Note: this doesn't seems to conform to RFC2253\r
-                                               char[] specials = { ',', '+', '"', '\\', '<', '>', ';' };\r
-                                               if (sValue.IndexOfAny (specials, 0, sValue.Length) > 0)\r
-                                                       sValue = "\"" + sValue + "\"";\r
-                                               else if (sValue.StartsWith (" "))\r
-                                                       sValue = "\"" + sValue + "\"";\r
-                                               else if (sValue.EndsWith (" "))\r
-                                                       sValue = "\"" + sValue + "\"";\r
-                                       }\r
-\r
-                                       sb.Append (sValue);\r
-\r
-                                       // separator (not on last iteration)\r
-                                       if (k < entry.Count - 1)\r
-                                               sb.Append (", ");\r
-                               }
+                               AppendEntry (sb, entry, true);
 
                                // separator (not on last iteration)
                                if (i < seq.Count - 1)
@@ -151,9 +91,117 @@ namespace Mono.Security.X509 {
                        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) 
                {
-                       switch (attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ()) {
+                       string s = attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ();
+                       switch (s) {
                                case "C":
                                        return new X520.CountryName ();
                                case "O":
@@ -169,45 +217,182 @@ namespace Mono.Security.X509 {
                                        return new X520.StateOrProvinceName ();
                                case "E":       // NOTE: Not part of RFC2253
                                        return new X520.EmailAddress ();
-                               case "DC":
-//                                     return streetAddress;
-                               case "UID":
-//                                     return domainComponent;
+                               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:
-                                       return null;
+                                       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 = Locale.GetText ("No attribute found.");
+                               throw new FormatException (msg);
+                       }
+
+                       string s = value.Substring (pos, equal - pos);
+                       X520.AttributeTypeAndValue atv = GetAttributeFromOid (s);
+                       if (atv == null) {
+                               string msg = Locale.GetText ("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 = Locale.GetText ("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 = Locale.GetText ("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 = Locale.GetText ("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");
-                       // get string from here to ',' or end of string
-                       int start = 0;
-                       int end = 0;
+
+                       int pos = 0;
                        ASN1 asn1 = new ASN1 (0x30);
-                       while (start < rdn.Length) {
-                               end = rdn.IndexOf (',', end) + 1;
-                               if (end == 0)
-                                       end = rdn.Length + 1;
-                               string av = rdn.Substring (start, end - start - 1);
-                               // get '=' position in substring
-                               int equal = av.IndexOf ('=');
-                               // get AttributeType
-                               string attributeType = av.Substring (0, equal);
-                               // get value
-                               string attributeValue = av.Substring (equal + 1);
-
-                               X520.AttributeTypeAndValue atv = GetAttributeFromOid (attributeType);
-                               atv.Value = attributeValue;
-                               asn1.Add (new ASN1 (0x31, atv.GetBytes ()));
-
-                               // next part
-                               start = end;
-                               if (start != - 1) {
-                                       if (end > rdn.Length)
-                                               break;
-                               }
+                       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;
                }