瀏覽代碼

update mono dependencies

Luke Pulverenti 9 年之前
父節點
當前提交
76e6c220d6

+ 17 - 5
MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj

@@ -23,6 +23,7 @@
     <WarningLevel>4</WarningLevel>
     <PlatformTarget>x86</PlatformTarget>
     <Externalconsole>true</Externalconsole>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
     <DebugType>full</DebugType>
@@ -60,11 +61,6 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll</HintPath>
     </Reference>
-    <Reference Include="Mono.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\ThirdParty\Mono.Security\Mono.Security.dll</HintPath>
-      <Private>False</Private>
-    </Reference>
     <Reference Include="Patterns.Logging, Version=1.0.5494.41209, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
@@ -95,6 +91,22 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Native\NativeApp.cs" />
     <Compile Include="Networking\NetworkManager.cs" />
+    <Compile Include="Security\ASN1.cs" />
+    <Compile Include="Security\ASN1Convert.cs" />
+    <Compile Include="Security\BitConverterLE.cs" />
+    <Compile Include="Security\CryptoConvert.cs" />
+    <Compile Include="Security\PKCS1.cs" />
+    <Compile Include="Security\PKCS12.cs" />
+    <Compile Include="Security\PKCS7.cs" />
+    <Compile Include="Security\PKCS8.cs" />
+    <Compile Include="Security\X501Name.cs" />
+    <Compile Include="Security\X509Builder.cs" />
+    <Compile Include="Security\X509Certificate.cs" />
+    <Compile Include="Security\X509CertificateBuilder.cs" />
+    <Compile Include="Security\X509CertificateCollection.cs" />
+    <Compile Include="Security\X509Extension.cs" />
+    <Compile Include="Security\X509Extensions.cs" />
+    <Compile Include="Security\X520Attributes.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>

+ 344 - 0
MediaBrowser.Server.Mono/Security/ASN1.cs

@@ -0,0 +1,344 @@
+//
+// ASN1.cs: Abstract Syntax Notation 1 - micro-parser and generator
+//
+// Authors:
+//	Sebastien Pouliot  <sebastien@ximian.com>
+//	Jesper Pedersen  <jep@itplus.dk>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// (C) 2004 IT+ A/S (http://www.itplus.dk)
+//
+// 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.Collections;
+using System.IO;
+using System.Text;
+
+namespace Mono.Security {
+
+	// References:
+	// a.	ITU ASN.1 standards (free download)
+	//	http://www.itu.int/ITU-T/studygroups/com17/languages/
+
+#if INSIDE_CORLIB
+	internal
+#else
+	public
+#endif
+	class ASN1 {
+
+		private byte m_nTag;
+		private byte[] m_aValue;
+		private ArrayList elist;
+
+		public ASN1 () : this (0x00, null) {}
+
+		public ASN1 (byte tag) : this (tag, null) {}
+
+		public ASN1 (byte tag, byte[] data) 
+		{
+			m_nTag = tag;
+			m_aValue = data;
+		}
+
+		public ASN1 (byte[] data) 
+		{
+			m_nTag = data [0];
+
+			int nLenLength = 0;
+			int nLength = data [1];
+
+			if (nLength > 0x80) {
+				// composed length
+				nLenLength = nLength - 0x80;
+				nLength = 0;
+				for (int i = 0; i < nLenLength; i++) {
+					nLength *= 256;
+					nLength += data [i + 2];
+				}
+			}
+			else if (nLength == 0x80) {
+				// undefined length encoding
+				throw new NotSupportedException ("Undefined length encoding.");
+			}
+
+			m_aValue = new byte [nLength];
+			Buffer.BlockCopy (data, (2 + nLenLength), m_aValue, 0, nLength);
+
+			if ((m_nTag & 0x20) == 0x20) {
+				int nStart = (2 + nLenLength);
+				Decode (data, ref nStart, data.Length);
+			}
+		}
+
+		public int Count {
+			get { 
+				if (elist == null)
+					return 0;
+				return elist.Count; 
+			}
+		}
+
+		public byte Tag {
+			get { return m_nTag; }
+		}
+
+		public int Length {
+			get { 
+				if (m_aValue != null)
+					return m_aValue.Length; 
+				else
+					return 0;
+			}
+		}
+
+		public byte[] Value {
+			get { 
+				if (m_aValue == null)
+					GetBytes ();
+				return (byte[]) m_aValue.Clone (); 
+			}
+			set { 
+				if (value != null)
+					m_aValue = (byte[]) value.Clone (); 
+			}
+		}
+
+		private bool CompareArray (byte[] array1, byte[] array2)
+		{
+			bool bResult = (array1.Length == array2.Length);
+			if (bResult) {
+				for (int i = 0; i < array1.Length; i++) {
+					if (array1[i] != array2[i])
+						return false;
+				}
+			}
+			return bResult;
+		}
+
+		public bool Equals (byte[] asn1) 
+		{
+			return CompareArray (this.GetBytes (), asn1);
+		}
+
+		public bool CompareValue (byte[] value) 
+		{
+			return CompareArray (m_aValue, value);
+		}
+
+		public ASN1 Add (ASN1 asn1) 
+		{
+			if (asn1 != null) {
+				if (elist == null)
+					elist = new ArrayList ();
+				elist.Add (asn1);
+			}
+			return asn1;
+		}
+
+		public virtual byte[] GetBytes () 
+		{
+			byte[] val = null;
+			
+			if (Count > 0) {
+				int esize = 0;
+				ArrayList al = new ArrayList ();
+				foreach (ASN1 a in elist) {
+					byte[] item = a.GetBytes ();
+					al.Add (item);
+					esize += item.Length;
+				}
+				val = new byte [esize];
+				int pos = 0;
+				for (int i=0; i < elist.Count; i++) {
+					byte[] item = (byte[]) al[i];
+					Buffer.BlockCopy (item, 0, val, pos, item.Length);
+					pos += item.Length;
+				}
+			} else if (m_aValue != null) {
+				val = m_aValue;
+			}
+
+			byte[] der;
+			int nLengthLen = 0;
+
+			if (val != null) {
+				int nLength = val.Length;
+				// special for length > 127
+				if (nLength > 127) {
+					if (nLength <= Byte.MaxValue) {
+						der = new byte [3 + nLength];
+						Buffer.BlockCopy (val, 0, der, 3, nLength);
+						nLengthLen = 0x81;
+						der[2] = (byte)(nLength);
+					}
+					else if (nLength <= UInt16.MaxValue) {
+						der = new byte [4 + nLength];
+						Buffer.BlockCopy (val, 0, der, 4, nLength);
+						nLengthLen = 0x82;
+						der[2] = (byte)(nLength >> 8);
+						der[3] = (byte)(nLength);
+					}
+					else if (nLength <= 0xFFFFFF) {
+						// 24 bits
+						der = new byte [5 + nLength];
+						Buffer.BlockCopy (val, 0, der, 5, nLength);
+						nLengthLen = 0x83;
+						der [2] = (byte)(nLength >> 16);
+						der [3] = (byte)(nLength >> 8);
+						der [4] = (byte)(nLength);
+					}
+					else {
+						// max (Length is an integer) 32 bits
+						der = new byte [6 + nLength];
+						Buffer.BlockCopy (val, 0, der, 6, nLength);
+						nLengthLen = 0x84;
+						der [2] = (byte)(nLength >> 24);
+						der [3] = (byte)(nLength >> 16);
+						der [4] = (byte)(nLength >> 8);
+						der [5] = (byte)(nLength);
+					}
+				}
+				else {
+					// basic case (no encoding)
+					der = new byte [2 + nLength];
+					Buffer.BlockCopy (val, 0, der, 2, nLength);
+					nLengthLen = nLength;
+				}
+				if (m_aValue == null)
+					m_aValue = val;
+			}
+			else
+				der = new byte[2];
+
+			der[0] = m_nTag;
+			der[1] = (byte)nLengthLen;
+
+			return der;
+		}
+
+		// Note: Recursive
+		protected void Decode (byte[] asn1, ref int anPos, int anLength) 
+		{
+			byte nTag;
+			int nLength;
+			byte[] aValue;
+
+			// minimum is 2 bytes (tag + length of 0)
+			while (anPos < anLength - 1) {
+				DecodeTLV (asn1, ref anPos, out nTag, out nLength, out aValue);
+				// sometimes we get trailing 0
+				if (nTag == 0)
+					continue;
+
+				ASN1 elm = Add (new ASN1 (nTag, aValue));
+
+				if ((nTag & 0x20) == 0x20) {
+					int nConstructedPos = anPos;
+					elm.Decode (asn1, ref nConstructedPos, nConstructedPos + nLength);
+				}
+				anPos += nLength; // value length
+			}
+		}
+
+		// TLV : Tag - Length - Value
+		protected void DecodeTLV (byte[] asn1, ref int pos, out byte tag, out int length, out byte[] content) 
+		{
+			tag = asn1 [pos++];
+			length = asn1 [pos++];
+
+			// special case where L contains the Length of the Length + 0x80
+			if ((length & 0x80) == 0x80) {
+				int nLengthLen = length & 0x7F;
+				length = 0;
+				for (int i = 0; i < nLengthLen; i++)
+					length = length * 256 + asn1 [pos++];
+			}
+
+			content = new byte [length];
+			Buffer.BlockCopy (asn1, pos, content, 0, length);
+		}
+
+		public ASN1 this [int index] {
+			get { 		
+				try {
+					if ((elist == null) || (index >= elist.Count))
+						return null;
+					return (ASN1) elist [index];
+				}
+				catch (ArgumentOutOfRangeException) {
+					return null;
+				}
+			}
+		}
+
+		public ASN1 Element (int index, byte anTag) 
+		{
+			try {
+				if ((elist == null) || (index >= elist.Count))
+					return null;
+
+				ASN1 elm = (ASN1) elist [index];
+				if (elm.Tag == anTag)
+					return elm;
+				else
+					return null;
+			}
+			catch (ArgumentOutOfRangeException) {
+				return null;
+			}
+		}
+
+		public override string ToString()
+		{
+			StringBuilder hexLine = new StringBuilder ();
+            
+			// Add tag
+			hexLine.AppendFormat ("Tag: {0} {1}", m_nTag.ToString ("X2"), Environment.NewLine);
+
+			// Add length
+			hexLine.AppendFormat ("Length: {0} {1}", Value.Length, Environment.NewLine);
+
+			// Add value
+			hexLine.Append ("Value: ");
+			hexLine.Append (Environment.NewLine);
+			for (int i = 0; i < Value.Length; i++) {
+				hexLine.AppendFormat ("{0} ", Value [i].ToString ("X2"));
+				if ((i+1) % 16 == 0)
+					hexLine.AppendFormat (Environment.NewLine);
+			}
+			return hexLine.ToString ();
+		}
+
+		public void SaveToFile (string filename)
+		{
+			if (filename == null)
+				throw new ArgumentNullException ("filename");
+
+			using (FileStream fs = File.Create (filename)) {
+				byte[] data = GetBytes ();
+				fs.Write (data, 0, data.Length);
+			}
+		}
+	}
+}

+ 212 - 0
MediaBrowser.Server.Mono/Security/ASN1Convert.cs

@@ -0,0 +1,212 @@
+//
+// ASN1Convert.cs: Abstract Syntax Notation 1 convertion routines
+//
+// Authors:
+//	Sebastien Pouliot  <sebastien@ximian.com>
+//	Jesper Pedersen  <jep@itplus.dk>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 IT+ A/S (http://www.itplus.dk)
+// Copyright (C) 2004-2007 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.Collections;
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Mono.Security {
+
+	// References:
+	// a.	ITU ASN.1 standards (free download)
+	//	http://www.itu.int/ITU-T/studygroups/com17/languages/
+
+#if INSIDE_CORLIB
+	internal
+#else
+	public
+#endif
+	static class ASN1Convert {
+		// RFC3280, section 4.2.1.5
+		// CAs conforming to this profile MUST always encode certificate
+		// validity dates through the year 2049 as UTCTime; certificate validity
+		// dates in 2050 or later MUST be encoded as GeneralizedTime.
+
+		// Under 1.x this API requires a Local datetime to be provided
+		// Under 2.0 it will also accept a Utc datetime
+		static public ASN1 FromDateTime (DateTime dt) 
+		{
+			if (dt.Year < 2050) {
+				// UTCTIME
+				return new ASN1 (0x17, Encoding.ASCII.GetBytes (
+					dt.ToUniversalTime ().ToString ("yyMMddHHmmss",
+					CultureInfo.InvariantCulture) + "Z"));
+			}
+			else {
+				// GENERALIZEDTIME
+				return new ASN1 (0x18, Encoding.ASCII.GetBytes (
+					dt.ToUniversalTime ().ToString ("yyyyMMddHHmmss", 
+					CultureInfo.InvariantCulture) + "Z"));
+			}
+		}
+
+		static public ASN1 FromInt32 (Int32 value) 
+		{
+			byte[] integer = BitConverterLE.GetBytes (value);
+			Array.Reverse (integer);
+			int x = 0;
+			while ((x < integer.Length) && (integer [x] == 0x00))
+				x++;
+			ASN1 asn1 = new ASN1 (0x02);
+			switch (x) {
+			case 0:
+				asn1.Value = integer;
+				break;
+			case 4:
+				asn1.Value = new byte [1];
+				break;
+			default:
+				byte[] smallerInt = new byte [4 - x];
+				Buffer.BlockCopy (integer, x, smallerInt, 0, smallerInt.Length);
+				asn1.Value = smallerInt;
+				break;
+			}
+			return asn1;
+		}
+
+		static public ASN1 FromOid (string oid) 
+		{
+			if (oid == null)
+				throw new ArgumentNullException ("oid");
+
+			return new ASN1 (CryptoConfig.EncodeOID (oid));
+		}
+
+		static public ASN1 FromUnsignedBigInteger (byte[] big) 
+		{
+			if (big == null)
+				throw new ArgumentNullException ("big");
+
+			// check for numbers that could be interpreted as negative (first bit)
+			if (big [0] >= 0x80) {
+				// in thie cas we add a new, empty, byte (position 0) so we're
+				// sure this will always be interpreted an unsigned integer.
+				// However we can't feed it into RSAParameters or DSAParameters
+				int length = big.Length + 1;
+				byte[] uinteger = new byte [length];
+				Buffer.BlockCopy (big, 0, uinteger, 1, length - 1);
+				big = uinteger;
+			}
+			return new ASN1 (0x02, big);
+		}
+
+		static public int ToInt32 (ASN1 asn1) 
+		{
+			if (asn1 == null)
+				throw new ArgumentNullException ("asn1");
+			if (asn1.Tag != 0x02)
+				throw new FormatException ("Only integer can be converted");
+
+			int x = 0;
+			for (int i=0; i < asn1.Value.Length; i++)
+				x = (x << 8) + asn1.Value [i];
+			return x;
+		}
+
+		// Convert a binary encoded OID to human readable string representation of 
+		// an OID (IETF style). Based on DUMPASN1.C from Peter Gutmann.
+		static public string ToOid (ASN1 asn1) 
+		{
+			if (asn1 == null)
+				throw new ArgumentNullException ("asn1");
+
+			byte[] aOID = asn1.Value;
+			StringBuilder sb = new StringBuilder ();
+			// Pick apart the OID
+			byte x = (byte) (aOID[0] / 40);
+			byte y = (byte) (aOID[0] % 40);
+			if (x > 2) {
+				// Handle special case for large y if x = 2
+				y += (byte) ((x - 2) * 40);
+				x = 2;
+			}
+			sb.Append (x.ToString (CultureInfo.InvariantCulture));
+			sb.Append (".");
+			sb.Append (y.ToString (CultureInfo.InvariantCulture));
+			ulong val = 0;
+			for (x = 1; x < aOID.Length; x++) {
+				val = ((val << 7) | ((byte) (aOID [x] & 0x7F)));
+				if ( !((aOID [x] & 0x80) == 0x80)) {
+					sb.Append (".");
+					sb.Append (val.ToString (CultureInfo.InvariantCulture));
+					val = 0;
+				}
+			}
+			return sb.ToString ();
+		}
+
+		static public DateTime ToDateTime (ASN1 time) 
+		{
+			if (time == null)
+				throw new ArgumentNullException ("time");
+
+			string t = Encoding.ASCII.GetString (time.Value);
+			// to support both UTCTime and GeneralizedTime (and not so common format)
+			string mask = null;
+			int year;
+			switch (t.Length) {
+				case 11:
+					// illegal format, still it's supported for compatibility
+					mask = "yyMMddHHmmZ";
+					break;
+				case 13: 
+					// RFC3280: 4.1.2.5.1  UTCTime
+					year = Convert.ToInt16 (t.Substring (0, 2), CultureInfo.InvariantCulture);
+					// Where YY is greater than or equal to 50, the 
+					// year SHALL be interpreted as 19YY; and 
+					// Where YY is less than 50, the year SHALL be 
+					// interpreted as 20YY.
+					if (year >= 50)
+						t = "19" + t;
+					else
+						t = "20" + t;
+					mask = "yyyyMMddHHmmssZ";
+					break;
+				case 15:
+					mask = "yyyyMMddHHmmssZ"; // GeneralizedTime
+					break;
+				case 17:
+					// another illegal format (990630000000+1000), again supported for compatibility
+					year = Convert.ToInt16 (t.Substring (0, 2), CultureInfo.InvariantCulture);
+					string century = (year >= 50) ? "19" : "20";
+					// ASN.1 (see ITU X.680 section 43.3) deals with offset differently than .NET
+					char sign = (t[12] == '+') ? '-' : '+';
+					t = String.Format ("{0}{1}{2}{3}{4}:{5}{6}", century, t.Substring (0, 12), sign, 
+						t[13], t[14], t[15], t[16]);
+					mask = "yyyyMMddHHmmsszzz";
+					break;
+			}
+			return DateTime.ParseExact (t, mask, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
+		}
+	}
+}

+ 239 - 0
MediaBrowser.Server.Mono/Security/BitConverterLE.cs

@@ -0,0 +1,239 @@
+//
+// Mono.Security.BitConverterLE.cs
+//  Like System.BitConverter but always little endian
+//
+// Author:
+//   Bernie Solomon
+//
+
+//
+// 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;
+
+namespace Mono.Security
+{
+	internal sealed class BitConverterLE
+	{
+		private BitConverterLE ()
+		{
+		}
+
+		unsafe private static byte[] GetUShortBytes (byte *bytes)
+		{
+			if (BitConverter.IsLittleEndian)
+				return new byte [] { bytes [0], bytes [1] };
+			else
+				return new byte [] { bytes [1], bytes [0] };
+		}
+
+		unsafe private static byte[] GetUIntBytes (byte *bytes)
+		{
+			if (BitConverter.IsLittleEndian)
+				return new byte [] { bytes [0], bytes [1], bytes [2], bytes [3] };
+			else
+				return new byte [] { bytes [3], bytes [2], bytes [1], bytes [0] };
+		}
+
+		unsafe private static byte[] GetULongBytes (byte *bytes)
+		{
+			if (BitConverter.IsLittleEndian)
+				return new byte [] { bytes [0], bytes [1], bytes [2], bytes [3],
+						     bytes [4], bytes [5], bytes [6], bytes [7] };
+			else
+				return new byte [] { bytes [7], bytes [6], bytes [5], bytes [4],
+						     bytes [3], bytes [2], bytes [1], bytes [0] };
+		}
+
+		unsafe internal static byte[] GetBytes (bool value)
+		{
+			return new byte [] { value ? (byte)1 : (byte)0 };
+		}
+
+		unsafe internal static byte[] GetBytes (char value)
+		{
+			return GetUShortBytes ((byte *) &value);
+		}
+
+		unsafe internal static byte[] GetBytes (short value)
+		{
+			return GetUShortBytes ((byte *) &value);
+		}
+
+		unsafe internal static byte[] GetBytes (int value)
+		{
+			return GetUIntBytes ((byte *) &value);
+		}
+
+		unsafe internal static byte[] GetBytes (long value)
+		{
+			return GetULongBytes ((byte *) &value);
+		}
+
+		unsafe internal static byte[] GetBytes (ushort value)
+		{
+			return GetUShortBytes ((byte *) &value);
+		}
+
+		unsafe internal static byte[] GetBytes (uint value)
+		{
+			return GetUIntBytes ((byte *) &value);
+		}
+
+		unsafe internal static byte[] GetBytes (ulong value)
+		{
+			return GetULongBytes ((byte *) &value);
+		}
+
+		unsafe internal static byte[] GetBytes (float value)
+		{
+			return GetUIntBytes ((byte *) &value);
+		}
+
+		unsafe internal static byte[] GetBytes (double value)
+		{
+			return GetULongBytes ((byte *) &value);
+		}
+
+		unsafe private static void UShortFromBytes (byte *dst, byte[] src, int startIndex)
+		{
+			if (BitConverter.IsLittleEndian) {
+				dst [0] = src [startIndex];
+				dst [1] = src [startIndex + 1];
+			} else {
+				dst [0] = src [startIndex + 1];
+				dst [1] = src [startIndex];
+			}
+		}
+
+		unsafe private static void UIntFromBytes (byte *dst, byte[] src, int startIndex)
+		{
+			if (BitConverter.IsLittleEndian) {
+				dst [0] = src [startIndex];
+				dst [1] = src [startIndex + 1];
+				dst [2] = src [startIndex + 2];
+				dst [3] = src [startIndex + 3];
+			} else {
+				dst [0] = src [startIndex + 3];
+				dst [1] = src [startIndex + 2];
+				dst [2] = src [startIndex + 1];
+				dst [3] = src [startIndex];
+			}
+		}
+
+		unsafe private static void ULongFromBytes (byte *dst, byte[] src, int startIndex)
+		{
+			if (BitConverter.IsLittleEndian) {
+				for (int i = 0; i < 8; ++i)
+					dst [i] = src [startIndex + i];
+			} else {
+				for (int i = 0; i < 8; ++i)
+					dst [i] = src [startIndex + (7 - i)];
+			}
+		}
+
+		unsafe internal static bool ToBoolean (byte[] value, int startIndex)
+		{
+			return value [startIndex] != 0;
+		}
+
+		unsafe internal static char ToChar (byte[] value, int startIndex)
+		{
+			char ret;
+
+			UShortFromBytes ((byte *) &ret, value, startIndex);
+
+			return ret;
+		}
+
+		unsafe internal static short ToInt16 (byte[] value, int startIndex)
+		{
+			short ret;
+
+			UShortFromBytes ((byte *) &ret, value, startIndex);
+
+			return ret;
+		}
+
+		unsafe internal static int ToInt32 (byte[] value, int startIndex)
+		{
+			int ret;
+
+			UIntFromBytes ((byte *) &ret, value, startIndex);
+
+			return ret;
+		}
+
+		unsafe internal static long ToInt64 (byte[] value, int startIndex)
+		{
+			long ret;
+
+			ULongFromBytes ((byte *) &ret, value, startIndex);
+
+			return ret;
+		}
+
+		unsafe internal static ushort ToUInt16 (byte[] value, int startIndex)
+		{
+			ushort ret;
+
+			UShortFromBytes ((byte *) &ret, value, startIndex);
+
+			return ret;
+		}
+
+		unsafe internal static uint ToUInt32 (byte[] value, int startIndex)
+		{
+			uint ret;
+
+			UIntFromBytes ((byte *) &ret, value, startIndex);
+
+			return ret;
+		}
+
+		unsafe internal static ulong ToUInt64 (byte[] value, int startIndex)
+		{
+			ulong ret;
+
+			ULongFromBytes ((byte *) &ret, value, startIndex);
+
+			return ret;
+		}
+
+		unsafe internal static float ToSingle (byte[] value, int startIndex)
+		{
+			float ret;
+
+			UIntFromBytes ((byte *) &ret, value, startIndex);
+
+			return ret;
+		}
+
+		unsafe internal static double ToDouble (byte[] value, int startIndex)
+		{
+			double ret;
+
+			ULongFromBytes ((byte *) &ret, value, startIndex);
+
+			return ret;
+		}
+	}
+}

+ 754 - 0
MediaBrowser.Server.Mono/Security/CryptoConvert.cs

@@ -0,0 +1,754 @@
+//
+// CryptoConvert.cs - Crypto Convertion Routines
+//
+// Author:
+//	Sebastien Pouliot  <sebastien@ximian.com>
+//
+// (C) 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.Security.Cryptography;
+using System.Text;
+
+namespace Mono.Security.Cryptography {
+
+#if INSIDE_CORLIB
+	internal
+#else
+	public
+#endif
+	sealed class CryptoConvert {
+
+		private CryptoConvert () 
+		{
+		}
+
+		static private int ToInt32LE (byte [] bytes, int offset)
+		{
+			return (bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset];
+		}
+
+		static private uint ToUInt32LE (byte [] bytes, int offset)
+		{
+			return (uint)((bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset]);
+		}
+
+		static private byte [] GetBytesLE (int val)
+		{
+			return new byte [] { 
+				(byte) (val & 0xff), 
+				(byte) ((val >> 8) & 0xff), 
+				(byte) ((val >> 16) & 0xff), 
+				(byte) ((val >> 24) & 0xff)
+			};
+                }
+
+		static private byte[] Trim (byte[] array) 
+		{
+			for (int i=0; i < array.Length; i++) {
+				if (array [i] != 0x00) {
+					byte[] result = new byte [array.Length - i];
+					Buffer.BlockCopy (array, i, result, 0, result.Length);
+					return result;
+				}
+			}
+			return null;
+		}
+
+		// convert the key from PRIVATEKEYBLOB to RSA
+		// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/security/Security/private_key_blobs.asp
+		// e.g. SNK files, PVK files
+		static public RSA FromCapiPrivateKeyBlob (byte[] blob) 
+		{
+			return FromCapiPrivateKeyBlob (blob, 0);
+		}
+
+		static public RSA FromCapiPrivateKeyBlob (byte[] blob, int offset) 
+		{
+			if (blob == null)
+				throw new ArgumentNullException ("blob");
+			if (offset >= blob.Length)
+				throw new ArgumentException ("blob is too small.");
+
+			RSAParameters rsap = new RSAParameters ();
+			try {
+				if ((blob [offset]   != 0x07) ||				// PRIVATEKEYBLOB (0x07)
+				    (blob [offset+1] != 0x02) ||				// Version (0x02)
+				    (blob [offset+2] != 0x00) ||				// Reserved (word)
+				    (blob [offset+3] != 0x00) ||
+				    (ToUInt32LE (blob, offset+8) != 0x32415352))	// DWORD magic = RSA2
+					throw new CryptographicException ("Invalid blob header");
+				
+				// ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
+				// int algId = ToInt32LE (blob, offset+4);
+
+				// DWORD bitlen
+				int bitLen = ToInt32LE (blob, offset+12);
+
+				// DWORD public exponent
+				byte[] exp = new byte [4];
+				Buffer.BlockCopy (blob, offset+16, exp, 0, 4);
+				Array.Reverse (exp);
+				rsap.Exponent = Trim (exp);
+			
+				int pos = offset+20;
+				// BYTE modulus[rsapubkey.bitlen/8];
+				int byteLen = (bitLen >> 3);
+				rsap.Modulus = new byte [byteLen];
+				Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
+				Array.Reverse (rsap.Modulus);
+				pos += byteLen;
+
+				// BYTE prime1[rsapubkey.bitlen/16];
+				int byteHalfLen = (byteLen >> 1);
+				rsap.P = new byte [byteHalfLen];
+				Buffer.BlockCopy (blob, pos, rsap.P, 0, byteHalfLen);
+				Array.Reverse (rsap.P);
+				pos += byteHalfLen;
+
+				// BYTE prime2[rsapubkey.bitlen/16];
+				rsap.Q = new byte [byteHalfLen];
+				Buffer.BlockCopy (blob, pos, rsap.Q, 0, byteHalfLen);
+				Array.Reverse (rsap.Q);
+				pos += byteHalfLen;
+
+				// BYTE exponent1[rsapubkey.bitlen/16];
+				rsap.DP = new byte [byteHalfLen];
+				Buffer.BlockCopy (blob, pos, rsap.DP, 0, byteHalfLen);
+				Array.Reverse (rsap.DP);
+				pos += byteHalfLen;
+
+				// BYTE exponent2[rsapubkey.bitlen/16];
+				rsap.DQ = new byte [byteHalfLen];
+				Buffer.BlockCopy (blob, pos, rsap.DQ, 0, byteHalfLen);
+				Array.Reverse (rsap.DQ);
+				pos += byteHalfLen;
+
+				// BYTE coefficient[rsapubkey.bitlen/16];
+				rsap.InverseQ = new byte [byteHalfLen];
+				Buffer.BlockCopy (blob, pos, rsap.InverseQ, 0, byteHalfLen);
+				Array.Reverse (rsap.InverseQ);
+				pos += byteHalfLen;
+
+				// ok, this is hackish but CryptoAPI support it so...
+				// note: only works because CRT is used by default
+				// http://bugzilla.ximian.com/show_bug.cgi?id=57941
+				rsap.D = new byte [byteLen]; // must be allocated
+				if (pos + byteLen + offset <= blob.Length) {
+					// BYTE privateExponent[rsapubkey.bitlen/8];
+					Buffer.BlockCopy (blob, pos, rsap.D, 0, byteLen);
+					Array.Reverse (rsap.D);
+				}
+			}
+			catch (Exception e) {
+				throw new CryptographicException ("Invalid blob.", e);
+			}
+
+#if INSIDE_CORLIB && MOBILE
+			RSA rsa = RSA.Create ();
+			rsa.ImportParameters (rsap);
+#else
+			RSA rsa = null;
+			try {
+				rsa = RSA.Create ();
+				rsa.ImportParameters (rsap);
+			}
+			catch (CryptographicException ce) {
+				// this may cause problem when this code is run under
+				// the SYSTEM identity on Windows (e.g. ASP.NET). See
+				// http://bugzilla.ximian.com/show_bug.cgi?id=77559
+				try {
+					CspParameters csp = new CspParameters ();
+					csp.Flags = CspProviderFlags.UseMachineKeyStore;
+					rsa = new RSACryptoServiceProvider (csp);
+					rsa.ImportParameters (rsap);
+				}
+				catch {
+					// rethrow original, not the later, exception if this fails
+					throw ce;
+				}
+			}
+#endif
+			return rsa;
+		}
+
+		static public DSA FromCapiPrivateKeyBlobDSA (byte[] blob)
+		{
+			return FromCapiPrivateKeyBlobDSA (blob, 0);
+		}
+
+		static public DSA FromCapiPrivateKeyBlobDSA (byte[] blob, int offset)
+		{
+			if (blob == null)
+				throw new ArgumentNullException ("blob");
+			if (offset >= blob.Length)
+				throw new ArgumentException ("blob is too small.");
+
+			DSAParameters dsap = new DSAParameters ();
+			try {
+				if ((blob [offset] != 0x07) ||				// PRIVATEKEYBLOB (0x07)
+				    (blob [offset + 1] != 0x02) ||			// Version (0x02)
+				    (blob [offset + 2] != 0x00) ||			// Reserved (word)
+				    (blob [offset + 3] != 0x00) ||
+				    (ToUInt32LE (blob, offset + 8) != 0x32535344))	// DWORD magic
+					throw new CryptographicException ("Invalid blob header");
+
+				int bitlen = ToInt32LE (blob, offset + 12);
+				int bytelen = bitlen >> 3;
+				int pos = offset + 16;
+
+				dsap.P = new byte [bytelen];
+				Buffer.BlockCopy (blob, pos, dsap.P, 0, bytelen);
+				Array.Reverse (dsap.P);
+				pos += bytelen;
+
+				dsap.Q = new byte [20];
+				Buffer.BlockCopy (blob, pos, dsap.Q, 0, 20);
+				Array.Reverse (dsap.Q);
+				pos += 20;
+
+				dsap.G = new byte [bytelen];
+				Buffer.BlockCopy (blob, pos, dsap.G, 0, bytelen);
+				Array.Reverse (dsap.G);
+				pos += bytelen;
+
+				dsap.X = new byte [20];
+				Buffer.BlockCopy (blob, pos, dsap.X, 0, 20);
+				Array.Reverse (dsap.X);
+				pos += 20;
+
+				dsap.Counter = ToInt32LE (blob, pos);
+				pos += 4;
+
+				dsap.Seed = new byte [20];
+				Buffer.BlockCopy (blob, pos, dsap.Seed, 0, 20);
+				Array.Reverse (dsap.Seed);
+				pos += 20;
+			}
+			catch (Exception e) {
+				throw new CryptographicException ("Invalid blob.", e);
+			}
+
+#if INSIDE_CORLIB && MOBILE
+			DSA dsa = (DSA)DSA.Create ();
+			dsa.ImportParameters (dsap);
+#else
+			DSA dsa = null;
+			try {
+				dsa = (DSA)DSA.Create ();
+				dsa.ImportParameters (dsap);
+			}
+			catch (CryptographicException ce) {
+				// this may cause problem when this code is run under
+				// the SYSTEM identity on Windows (e.g. ASP.NET). See
+				// http://bugzilla.ximian.com/show_bug.cgi?id=77559
+				try {
+					CspParameters csp = new CspParameters ();
+					csp.Flags = CspProviderFlags.UseMachineKeyStore;
+					dsa = new DSACryptoServiceProvider (csp);
+					dsa.ImportParameters (dsap);
+				}
+				catch {
+					// rethrow original, not the later, exception if this fails
+					throw ce;
+				}
+			}
+#endif
+			return dsa;
+		}
+
+		static public byte[] ToCapiPrivateKeyBlob (RSA rsa) 
+		{
+			RSAParameters p = rsa.ExportParameters (true);
+			int keyLength = p.Modulus.Length; // in bytes
+			byte[] blob = new byte [20 + (keyLength << 2) + (keyLength >> 1)];
+
+			blob [0] = 0x07;	// Type - PRIVATEKEYBLOB (0x07)
+			blob [1] = 0x02;	// Version - Always CUR_BLOB_VERSION (0x02)
+			// [2], [3]		// RESERVED - Always 0
+			blob [5] = 0x24;	// ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
+			blob [8] = 0x52;	// Magic - RSA2 (ASCII in hex)
+			blob [9] = 0x53;
+			blob [10] = 0x41;
+			blob [11] = 0x32;
+
+			byte[] bitlen = GetBytesLE (keyLength << 3);
+			blob [12] = bitlen [0];	// bitlen
+			blob [13] = bitlen [1];	
+			blob [14] = bitlen [2];	
+			blob [15] = bitlen [3];
+
+			// public exponent (DWORD)
+			int pos = 16;
+			int n = p.Exponent.Length;
+			while (n > 0)
+				blob [pos++] = p.Exponent [--n];
+			// modulus
+			pos = 20;
+			byte[] part = p.Modulus;
+			int len = part.Length;
+			Array.Reverse (part, 0, len);
+			Buffer.BlockCopy (part, 0, blob, pos, len);
+			pos += len;
+			// private key
+			part = p.P;
+			len = part.Length;
+			Array.Reverse (part, 0, len);
+			Buffer.BlockCopy (part, 0, blob, pos, len);
+			pos += len;
+
+			part = p.Q;
+			len = part.Length;
+			Array.Reverse (part, 0, len);
+			Buffer.BlockCopy (part, 0, blob, pos, len);
+			pos += len;
+
+			part = p.DP;
+			len = part.Length;
+			Array.Reverse (part, 0, len);
+			Buffer.BlockCopy (part, 0, blob, pos, len);
+			pos += len;
+
+			part = p.DQ;
+			len = part.Length;
+			Array.Reverse (part, 0, len);
+			Buffer.BlockCopy (part, 0, blob, pos, len);
+			pos += len;
+
+			part = p.InverseQ;
+			len = part.Length;
+			Array.Reverse (part, 0, len);
+			Buffer.BlockCopy (part, 0, blob, pos, len);
+			pos += len;
+
+			part = p.D;
+			len = part.Length;
+			Array.Reverse (part, 0, len);
+			Buffer.BlockCopy (part, 0, blob, pos, len);
+
+			return blob;
+		}
+
+		static public byte[] ToCapiPrivateKeyBlob (DSA dsa)
+		{
+			DSAParameters p = dsa.ExportParameters (true);
+			int keyLength = p.P.Length; // in bytes
+
+			// header + P + Q + G + X + count + seed
+			byte[] blob = new byte [16 + keyLength + 20 + keyLength + 20 + 4 + 20];
+
+			blob [0] = 0x07;	// Type - PRIVATEKEYBLOB (0x07)
+			blob [1] = 0x02;	// Version - Always CUR_BLOB_VERSION (0x02)
+			// [2], [3]		// RESERVED - Always 0
+			blob [5] = 0x22;	// ALGID
+			blob [8] = 0x44;	// Magic
+			blob [9] = 0x53;
+			blob [10] = 0x53;
+			blob [11] = 0x32;
+
+			byte[] bitlen = GetBytesLE (keyLength << 3);
+			blob [12] = bitlen [0];
+			blob [13] = bitlen [1];
+			blob [14] = bitlen [2];
+			blob [15] = bitlen [3];
+
+			int pos = 16;
+			byte[] part = p.P;
+			Array.Reverse (part);
+			Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+			pos += keyLength;
+
+			part = p.Q;
+			Array.Reverse (part);
+			Buffer.BlockCopy (part, 0, blob, pos, 20);
+			pos += 20;
+
+			part = p.G;
+			Array.Reverse (part);
+			Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+			pos += keyLength;
+
+			part = p.X;
+			Array.Reverse (part);
+			Buffer.BlockCopy (part, 0, blob, pos, 20);
+			pos += 20;
+
+			Buffer.BlockCopy (GetBytesLE (p.Counter), 0, blob, pos, 4);
+			pos += 4;
+
+			part = p.Seed;
+			Array.Reverse (part);
+			Buffer.BlockCopy (part, 0, blob, pos, 20);
+
+			return blob;
+		}
+
+		static public RSA FromCapiPublicKeyBlob (byte[] blob) 
+		{
+			return FromCapiPublicKeyBlob (blob, 0);
+		}
+
+		static public RSA FromCapiPublicKeyBlob (byte[] blob, int offset) 
+		{
+			if (blob == null)
+				throw new ArgumentNullException ("blob");
+			if (offset >= blob.Length)
+				throw new ArgumentException ("blob is too small.");
+
+			try {
+				if ((blob [offset]   != 0x06) ||				// PUBLICKEYBLOB (0x06)
+				    (blob [offset+1] != 0x02) ||				// Version (0x02)
+				    (blob [offset+2] != 0x00) ||				// Reserved (word)
+				    (blob [offset+3] != 0x00) || 
+				    (ToUInt32LE (blob, offset+8) != 0x31415352))	// DWORD magic = RSA1
+					throw new CryptographicException ("Invalid blob header");
+
+				// ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
+				// int algId = ToInt32LE (blob, offset+4);
+
+				// DWORD bitlen
+				int bitLen = ToInt32LE (blob, offset+12);
+
+				// DWORD public exponent
+				RSAParameters rsap = new RSAParameters ();
+				rsap.Exponent = new byte [3];
+				rsap.Exponent [0] = blob [offset+18];
+				rsap.Exponent [1] = blob [offset+17];
+				rsap.Exponent [2] = blob [offset+16];
+			
+				int pos = offset+20;
+				// BYTE modulus[rsapubkey.bitlen/8];
+				int byteLen = (bitLen >> 3);
+				rsap.Modulus = new byte [byteLen];
+				Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
+				Array.Reverse (rsap.Modulus);
+#if INSIDE_CORLIB && MOBILE
+				RSA rsa = RSA.Create ();
+				rsa.ImportParameters (rsap);
+#else
+				RSA rsa = null;
+				try {
+					rsa = RSA.Create ();
+					rsa.ImportParameters (rsap);
+				}
+				catch (CryptographicException) {
+					// this may cause problem when this code is run under
+					// the SYSTEM identity on Windows (e.g. ASP.NET). See
+					// http://bugzilla.ximian.com/show_bug.cgi?id=77559
+					CspParameters csp = new CspParameters ();
+					csp.Flags = CspProviderFlags.UseMachineKeyStore;
+					rsa = new RSACryptoServiceProvider (csp);
+					rsa.ImportParameters (rsap);
+				}
+#endif
+				return rsa;
+			}
+			catch (Exception e) {
+				throw new CryptographicException ("Invalid blob.", e);
+			}
+		}
+
+		static public DSA FromCapiPublicKeyBlobDSA (byte[] blob)
+		{
+			return FromCapiPublicKeyBlobDSA (blob, 0);
+		}
+
+		static public DSA FromCapiPublicKeyBlobDSA (byte[] blob, int offset)
+		{
+			if (blob == null)
+				throw new ArgumentNullException ("blob");
+			if (offset >= blob.Length)
+				throw new ArgumentException ("blob is too small.");
+
+			try {
+				if ((blob [offset] != 0x06) ||				// PUBLICKEYBLOB (0x06)
+				    (blob [offset + 1] != 0x02) ||			// Version (0x02)
+				    (blob [offset + 2] != 0x00) ||			// Reserved (word)
+				    (blob [offset + 3] != 0x00) ||
+				    (ToUInt32LE (blob, offset + 8) != 0x31535344))	// DWORD magic
+					throw new CryptographicException ("Invalid blob header");
+
+				int bitlen = ToInt32LE (blob, offset + 12);
+				DSAParameters dsap = new DSAParameters ();
+				int bytelen = bitlen >> 3;
+				int pos = offset + 16;
+
+				dsap.P = new byte [bytelen];
+				Buffer.BlockCopy (blob, pos, dsap.P, 0, bytelen);
+				Array.Reverse (dsap.P);
+				pos += bytelen;
+
+				dsap.Q = new byte [20];
+				Buffer.BlockCopy (blob, pos, dsap.Q, 0, 20);
+				Array.Reverse (dsap.Q);
+				pos += 20;
+
+				dsap.G = new byte [bytelen];
+				Buffer.BlockCopy (blob, pos, dsap.G, 0, bytelen);
+				Array.Reverse (dsap.G);
+				pos += bytelen;
+
+				dsap.Y = new byte [bytelen];
+				Buffer.BlockCopy (blob, pos, dsap.Y, 0, bytelen);
+				Array.Reverse (dsap.Y);
+				pos += bytelen;
+
+				dsap.Counter = ToInt32LE (blob, pos);
+				pos += 4;
+
+				dsap.Seed = new byte [20];
+				Buffer.BlockCopy (blob, pos, dsap.Seed, 0, 20);
+				Array.Reverse (dsap.Seed);
+				pos += 20;
+
+				DSA dsa = (DSA)DSA.Create ();
+				dsa.ImportParameters (dsap);
+				return dsa;
+			}
+			catch (Exception e) {
+				throw new CryptographicException ("Invalid blob.", e);
+			}
+		}
+
+		static public byte[] ToCapiPublicKeyBlob (RSA rsa) 
+		{
+			RSAParameters p = rsa.ExportParameters (false);
+			int keyLength = p.Modulus.Length; // in bytes
+			byte[] blob = new byte [20 + keyLength];
+
+			blob [0] = 0x06;	// Type - PUBLICKEYBLOB (0x06)
+			blob [1] = 0x02;	// Version - Always CUR_BLOB_VERSION (0x02)
+			// [2], [3]		// RESERVED - Always 0
+			blob [5] = 0x24;	// ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
+			blob [8] = 0x52;	// Magic - RSA1 (ASCII in hex)
+			blob [9] = 0x53;
+			blob [10] = 0x41;
+			blob [11] = 0x31;
+
+			byte[] bitlen = GetBytesLE (keyLength << 3);
+			blob [12] = bitlen [0];	// bitlen
+			blob [13] = bitlen [1];	
+			blob [14] = bitlen [2];	
+			blob [15] = bitlen [3];
+
+			// public exponent (DWORD)
+			int pos = 16;
+			int n = p.Exponent.Length;
+			while (n > 0)
+				blob [pos++] = p.Exponent [--n];
+			// modulus
+			pos = 20;
+			byte[] part = p.Modulus;
+			int len = part.Length;
+			Array.Reverse (part, 0, len);
+			Buffer.BlockCopy (part, 0, blob, pos, len);
+			pos += len;
+			return blob;
+		}
+
+		static public byte[] ToCapiPublicKeyBlob (DSA dsa)
+		{
+			DSAParameters p = dsa.ExportParameters (false);
+			int keyLength = p.P.Length; // in bytes
+
+			// header + P + Q + G + Y + count + seed
+			byte[] blob = new byte [16 + keyLength + 20 + keyLength + keyLength + 4 + 20];
+
+			blob [0] = 0x06;	// Type - PUBLICKEYBLOB (0x06)
+			blob [1] = 0x02;	// Version - Always CUR_BLOB_VERSION (0x02)
+			// [2], [3]		// RESERVED - Always 0
+			blob [5] = 0x22;	// ALGID
+			blob [8] = 0x44;	// Magic
+			blob [9] = 0x53;
+			blob [10] = 0x53;
+			blob [11] = 0x31;
+
+			byte[] bitlen = GetBytesLE (keyLength << 3);
+			blob [12] = bitlen [0];
+			blob [13] = bitlen [1];
+			blob [14] = bitlen [2];
+			blob [15] = bitlen [3];
+
+			int pos = 16;
+			byte[] part;
+
+			part = p.P;
+			Array.Reverse (part);
+			Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+			pos += keyLength;
+
+			part = p.Q;
+			Array.Reverse (part);
+			Buffer.BlockCopy (part, 0, blob, pos, 20);
+			pos += 20;
+
+			part = p.G;
+			Array.Reverse (part);
+			Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+			pos += keyLength;
+
+			part = p.Y;
+			Array.Reverse (part);
+			Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+			pos += keyLength;
+
+			Buffer.BlockCopy (GetBytesLE (p.Counter), 0, blob, pos, 4);
+			pos += 4;
+
+			part = p.Seed;
+			Array.Reverse (part);
+			Buffer.BlockCopy (part, 0, blob, pos, 20);
+
+			return blob;
+		}
+
+		// PRIVATEKEYBLOB
+		// PUBLICKEYBLOB
+		static public RSA FromCapiKeyBlob (byte[] blob) 
+		{
+			return FromCapiKeyBlob (blob, 0);
+		}
+
+		static public RSA FromCapiKeyBlob (byte[] blob, int offset) 
+		{
+			if (blob == null)
+				throw new ArgumentNullException ("blob");
+			if (offset >= blob.Length)
+				throw new ArgumentException ("blob is too small.");
+
+			switch (blob [offset]) {
+				case 0x00:
+					// this could be a public key inside an header
+					// like "sn -e" would produce
+					if (blob [offset + 12] == 0x06) {
+						return FromCapiPublicKeyBlob (blob, offset + 12);
+					}
+					break;
+				case 0x06:
+					return FromCapiPublicKeyBlob (blob, offset);
+				case 0x07:
+					return FromCapiPrivateKeyBlob (blob, offset);
+			}
+			throw new CryptographicException ("Unknown blob format.");
+		}
+
+		static public DSA FromCapiKeyBlobDSA (byte[] blob)
+		{
+			return FromCapiKeyBlobDSA (blob, 0);
+		}
+
+		static public DSA FromCapiKeyBlobDSA (byte[] blob, int offset)
+		{
+			if (blob == null)
+				throw new ArgumentNullException ("blob");
+			if (offset >= blob.Length)
+				throw new ArgumentException ("blob is too small.");
+
+			switch (blob [offset]) {
+				case 0x06:
+					return FromCapiPublicKeyBlobDSA (blob, offset);
+				case 0x07:
+					return FromCapiPrivateKeyBlobDSA (blob, offset);
+			}
+			throw new CryptographicException ("Unknown blob format.");
+		}
+
+		static public byte[] ToCapiKeyBlob (AsymmetricAlgorithm keypair, bool includePrivateKey) 
+		{
+			if (keypair == null)
+				throw new ArgumentNullException ("keypair");
+
+			// check between RSA and DSA (and potentially others like DH)
+			if (keypair is RSA)
+				return ToCapiKeyBlob ((RSA)keypair, includePrivateKey);
+			else if (keypair is DSA)
+				return ToCapiKeyBlob ((DSA)keypair, includePrivateKey);
+			else
+				return null;	// TODO
+		}
+
+		static public byte[] ToCapiKeyBlob (RSA rsa, bool includePrivateKey) 
+		{
+			if (rsa == null)
+				throw new ArgumentNullException ("rsa");
+
+			if (includePrivateKey)
+				return ToCapiPrivateKeyBlob (rsa);
+			else
+				return ToCapiPublicKeyBlob (rsa);
+		}
+
+		static public byte[] ToCapiKeyBlob (DSA dsa, bool includePrivateKey)
+		{
+			if (dsa == null)
+				throw new ArgumentNullException ("dsa");
+
+			if (includePrivateKey)
+				return ToCapiPrivateKeyBlob (dsa);
+			else
+				return ToCapiPublicKeyBlob (dsa);
+		}
+
+		static public string ToHex (byte[] input) 
+		{
+			if (input == null)
+				return null;
+
+			StringBuilder sb = new StringBuilder (input.Length * 2);
+			foreach (byte b in input) {
+				sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
+			}
+			return sb.ToString ();
+		}
+
+		static private byte FromHexChar (char c) 
+		{
+			if ((c >= 'a') && (c <= 'f'))
+				return (byte) (c - 'a' + 10);
+			if ((c >= 'A') && (c <= 'F'))
+				return (byte) (c - 'A' + 10);
+			if ((c >= '0') && (c <= '9'))
+				return (byte) (c - '0');
+			throw new ArgumentException ("invalid hex char");
+		}
+
+		static public byte[] FromHex (string hex) 
+		{
+			if (hex == null)
+				return null;
+			if ((hex.Length & 0x1) == 0x1)
+				throw new ArgumentException ("Length must be a multiple of 2");
+
+			byte[] result = new byte [hex.Length >> 1];
+			int n = 0;
+			int i = 0;
+			while (n < result.Length) {
+				result [n] = (byte) (FromHexChar (hex [i++]) << 4);
+				result [n++] += FromHexChar (hex [i++]);
+			}
+			return result;
+		}
+	}
+}

+ 495 - 0
MediaBrowser.Server.Mono/Security/PKCS1.cs

@@ -0,0 +1,495 @@
+//
+// PKCS1.cs - Implements PKCS#1 primitives.
+//
+// Author:
+//	Sebastien Pouliot  <sebastien@xamarin.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.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.Security.Cryptography;
+
+namespace Mono.Security.Cryptography { 
+
+	// References:
+	// a.	PKCS#1: RSA Cryptography Standard 
+	//	http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/index.html
+	
+#if INSIDE_CORLIB
+	internal
+#else
+	public
+#endif
+	sealed class PKCS1 {
+
+		private PKCS1 () 
+		{
+		}
+
+		private static bool Compare (byte[] array1, byte[] array2) 
+		{
+			bool result = (array1.Length == array2.Length);
+			if (result) {
+				for (int i=0; i < array1.Length; i++)
+					if (array1[i] != array2[i])
+						return false;
+			}
+			return result;
+		}
+	
+		private static byte[] xor (byte[] array1, byte[] array2) 
+		{
+			byte[] result = new byte [array1.Length];
+			for (int i=0; i < result.Length; i++)
+				result[i] = (byte) (array1[i] ^ array2[i]);
+			return result;
+		}
+	
+		private static byte[] emptySHA1   = { 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 };
+		private static byte[] emptySHA256 = { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 };
+		private static byte[] emptySHA384 = { 0x38, 0xb0, 0x60, 0xa7, 0x51, 0xac, 0x96, 0x38, 0x4c, 0xd9, 0x32, 0x7e, 0xb1, 0xb1, 0xe3, 0x6a, 0x21, 0xfd, 0xb7, 0x11, 0x14, 0xbe, 0x07, 0x43, 0x4c, 0x0c, 0xc7, 0xbf, 0x63, 0xf6, 0xe1, 0xda, 0x27, 0x4e, 0xde, 0xbf, 0xe7, 0x6f, 0x65, 0xfb, 0xd5, 0x1a, 0xd2, 0xf1, 0x48, 0x98, 0xb9, 0x5b };
+		private static byte[] emptySHA512 = { 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce, 0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0, 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f, 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81, 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e };
+	
+		private static byte[] GetEmptyHash (HashAlgorithm hash) 
+		{
+			if (hash is SHA1)
+				return emptySHA1;
+			else if (hash is SHA256)
+				return emptySHA256;
+			else if (hash is SHA384)
+				return emptySHA384;
+			else if (hash is SHA512)
+				return emptySHA512;
+			else
+				return hash.ComputeHash ((byte[])null);
+		}
+	
+		// PKCS #1 v.2.1, Section 4.1
+		// I2OSP converts a non-negative integer to an octet string of a specified length.
+		public static byte[] I2OSP (int x, int size) 
+		{
+			byte[] array = BitConverterLE.GetBytes (x);
+			Array.Reverse (array, 0, array.Length);
+			return I2OSP (array, size);
+		}
+	
+		public static byte[] I2OSP (byte[] x, int size) 
+		{
+			byte[] result = new byte [size];
+			Buffer.BlockCopy (x, 0, result, (result.Length - x.Length), x.Length);
+			return result;
+		}
+	
+		// PKCS #1 v.2.1, Section 4.2
+		// OS2IP converts an octet string to a nonnegative integer.
+		public static byte[] OS2IP (byte[] x) 
+		{
+			int i = 0;
+			while ((x [i++] == 0x00) && (i < x.Length)) {
+				// confuse compiler into reporting a warning with {}
+			}
+			i--;
+			if (i > 0) {
+				byte[] result = new byte [x.Length - i];
+				Buffer.BlockCopy (x, i, result, 0, result.Length);
+				return result;
+			}
+			else
+				return x;
+		}
+	
+		// PKCS #1 v.2.1, Section 5.1.1
+		public static byte[] RSAEP (RSA rsa, byte[] m) 
+		{
+			// c = m^e mod n
+			return rsa.EncryptValue (m);
+		}
+	
+		// PKCS #1 v.2.1, Section 5.1.2
+		public static byte[] RSADP (RSA rsa, byte[] c) 
+		{
+			// m = c^d mod n
+			// Decrypt value may apply CRT optimizations
+			return rsa.DecryptValue (c);
+		}
+	
+		// PKCS #1 v.2.1, Section 5.2.1
+		public static byte[] RSASP1 (RSA rsa, byte[] m) 
+		{
+			// first form: s = m^d mod n
+			// Decrypt value may apply CRT optimizations
+			return rsa.DecryptValue (m);
+		}
+	
+		// PKCS #1 v.2.1, Section 5.2.2
+		public static byte[] RSAVP1 (RSA rsa, byte[] s) 
+		{
+			// m = s^e mod n
+			return rsa.EncryptValue (s);
+		}
+	
+		// PKCS #1 v.2.1, Section 7.1.1
+		// RSAES-OAEP-ENCRYPT ((n, e), M, L)
+		public static byte[] Encrypt_OAEP (RSA rsa, HashAlgorithm hash, RandomNumberGenerator rng, byte[] M) 
+		{
+			int size = rsa.KeySize / 8;
+			int hLen = hash.HashSize / 8;
+			if (M.Length > size - 2 * hLen - 2)
+				throw new CryptographicException ("message too long");
+			// empty label L SHA1 hash
+			byte[] lHash = GetEmptyHash (hash);
+			int PSLength = (size - M.Length - 2 * hLen - 2);
+			// DB = lHash || PS || 0x01 || M
+			byte[] DB = new byte [lHash.Length + PSLength + 1 + M.Length];
+			Buffer.BlockCopy (lHash, 0, DB, 0, lHash.Length);
+			DB [(lHash.Length + PSLength)] = 0x01;
+			Buffer.BlockCopy (M, 0, DB, (DB.Length - M.Length), M.Length);
+	
+			byte[] seed = new byte [hLen];
+			rng.GetBytes (seed);
+	
+			byte[] dbMask = MGF1 (hash, seed, size - hLen - 1);
+			byte[] maskedDB = xor (DB, dbMask);
+			byte[] seedMask = MGF1 (hash, maskedDB, hLen);
+			byte[] maskedSeed = xor (seed, seedMask);
+			// EM = 0x00 || maskedSeed || maskedDB
+			byte[] EM = new byte [maskedSeed.Length + maskedDB.Length + 1];
+			Buffer.BlockCopy (maskedSeed, 0, EM, 1, maskedSeed.Length);
+			Buffer.BlockCopy (maskedDB, 0, EM, maskedSeed.Length + 1, maskedDB.Length);
+	
+			byte[] m = OS2IP (EM);
+			byte[] c = RSAEP (rsa, m);
+			return I2OSP (c, size);
+		}
+	
+		// PKCS #1 v.2.1, Section 7.1.2
+		// RSAES-OAEP-DECRYPT (K, C, L)
+		public static byte[] Decrypt_OAEP (RSA rsa, HashAlgorithm hash, byte[] C) 
+		{
+			int size = rsa.KeySize / 8;
+			int hLen = hash.HashSize / 8;
+			if ((size < (2 * hLen + 2)) || (C.Length != size))
+				throw new CryptographicException ("decryption error");
+	
+			byte[] c = OS2IP (C);
+			byte[] m = RSADP (rsa, c);
+			byte[] EM = I2OSP (m, size);
+	
+			// split EM = Y || maskedSeed || maskedDB
+			byte[] maskedSeed = new byte [hLen];
+			Buffer.BlockCopy (EM, 1, maskedSeed, 0, maskedSeed.Length);
+			byte[] maskedDB = new byte [size - hLen - 1];
+			Buffer.BlockCopy (EM, (EM.Length - maskedDB.Length), maskedDB, 0, maskedDB.Length);
+	
+			byte[] seedMask = MGF1 (hash, maskedDB, hLen);
+			byte[] seed = xor (maskedSeed, seedMask);
+			byte[] dbMask = MGF1 (hash, seed, size - hLen - 1);
+			byte[] DB = xor (maskedDB, dbMask);
+	
+			byte[] lHash = GetEmptyHash (hash);
+			// split DB = lHash' || PS || 0x01 || M
+			byte[] dbHash = new byte [lHash.Length];
+			Buffer.BlockCopy (DB, 0, dbHash, 0, dbHash.Length);
+			bool h = Compare (lHash, dbHash);
+	
+			// find separator 0x01
+			int nPos = lHash.Length;
+			while (DB[nPos] == 0)
+				nPos++;
+	
+			int Msize = DB.Length - nPos - 1;
+			byte[] M = new byte [Msize];
+			Buffer.BlockCopy (DB, (nPos + 1), M, 0, Msize);
+	
+			// we could have returned EM[0] sooner but would be helping a timing attack
+			if ((EM[0] != 0) || (!h) || (DB[nPos] != 0x01))
+				return null;
+			return M;
+		}
+	
+		// PKCS #1 v.2.1, Section 7.2.1
+		// RSAES-PKCS1-V1_5-ENCRYPT ((n, e), M)
+		public static byte[] Encrypt_v15 (RSA rsa, RandomNumberGenerator rng, byte[] M) 
+		{
+			int size = rsa.KeySize / 8;
+			if (M.Length > size - 11)
+				throw new CryptographicException ("message too long");
+			int PSLength = System.Math.Max (8, (size - M.Length - 3));
+			byte[] PS = new byte [PSLength];
+			rng.GetNonZeroBytes (PS);
+			byte[] EM = new byte [size];
+			EM [1] = 0x02;
+			Buffer.BlockCopy (PS, 0, EM, 2, PSLength);
+			Buffer.BlockCopy (M, 0, EM, (size - M.Length), M.Length);
+	
+			byte[] m = OS2IP (EM);
+			byte[] c = RSAEP (rsa, m);
+			byte[] C = I2OSP (c, size);
+			return C;
+		}
+	
+		// PKCS #1 v.2.1, Section 7.2.2
+		// RSAES-PKCS1-V1_5-DECRYPT (K, C)
+		public static byte[] Decrypt_v15 (RSA rsa, byte[] C) 
+		{
+			int size = rsa.KeySize >> 3; // div by 8
+			if ((size < 11) || (C.Length > size))
+				throw new CryptographicException ("decryption error");
+			byte[] c = OS2IP (C);
+			byte[] m = RSADP (rsa, c);
+			byte[] EM = I2OSP (m, size);
+	
+			if ((EM [0] != 0x00) || (EM [1] != 0x02))
+				return null;
+	
+			int mPos = 10;
+			// PS is a minimum of 8 bytes + 2 bytes for header
+			while ((EM [mPos] != 0x00) && (mPos < EM.Length))
+				mPos++;
+			if (EM [mPos] != 0x00)
+				return null;
+			mPos++;
+			byte[] M = new byte [EM.Length - mPos];
+			Buffer.BlockCopy (EM, mPos, M, 0, M.Length);
+			return M;
+		}
+	
+		// PKCS #1 v.2.1, Section 8.2.1
+		// RSASSA-PKCS1-V1_5-SIGN (K, M)
+		public static byte[] Sign_v15 (RSA rsa, HashAlgorithm hash, byte[] hashValue) 
+		{
+			int size = (rsa.KeySize >> 3); // div 8
+			byte[] EM = Encode_v15 (hash, hashValue, size);
+			byte[] m = OS2IP (EM);
+			byte[] s = RSASP1 (rsa, m);
+			byte[] S = I2OSP (s, size);
+			return S;
+		}
+
+		internal static byte[] Sign_v15 (RSA rsa, string hashName, byte[] hashValue) 
+		{
+			using (var hash = CreateFromName (hashName))
+				return Sign_v15 (rsa, hash, hashValue);
+		}
+
+		// PKCS #1 v.2.1, Section 8.2.2
+		// RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S)
+		public static bool Verify_v15 (RSA rsa, HashAlgorithm hash, byte[] hashValue, byte[] signature) 
+		{
+			return Verify_v15 (rsa, hash, hashValue, signature, false);
+		}
+
+		internal static bool Verify_v15 (RSA rsa, string hashName, byte[] hashValue, byte[] signature) 
+		{
+			using (var hash = CreateFromName (hashName))
+				return Verify_v15 (rsa, hash, hashValue, signature, false);
+		}
+
+		// DO NOT USE WITHOUT A VERY GOOD REASON
+		public static bool Verify_v15 (RSA rsa, HashAlgorithm hash, byte [] hashValue, byte [] signature, bool tryNonStandardEncoding)
+		{
+			int size = (rsa.KeySize >> 3); // div 8
+			byte[] s = OS2IP (signature);
+			byte[] m = RSAVP1 (rsa, s);
+			byte[] EM2 = I2OSP (m, size);
+			byte[] EM = Encode_v15 (hash, hashValue, size);
+			bool result = Compare (EM, EM2);
+			if (result || !tryNonStandardEncoding)
+				return result;
+
+			// NOTE: some signatures don't include the hash OID (pretty lame but real)
+			// and compatible with MS implementation. E.g. Verisign Authenticode Timestamps
+
+			// we're making this "as safe as possible"
+			if ((EM2 [0] != 0x00) || (EM2 [1] != 0x01))
+				return false;
+			int i;
+			for (i = 2; i < EM2.Length - hashValue.Length - 1; i++) {
+				if (EM2 [i] != 0xFF)
+					return false;
+			}
+			if (EM2 [i++] != 0x00)
+				return false;
+
+			byte [] decryptedHash = new byte [hashValue.Length];
+			Buffer.BlockCopy (EM2, i, decryptedHash, 0, decryptedHash.Length);
+			return Compare (decryptedHash, hashValue);
+		}
+	
+		// PKCS #1 v.2.1, Section 9.2
+		// EMSA-PKCS1-v1_5-Encode
+		public static byte[] Encode_v15 (HashAlgorithm hash, byte[] hashValue, int emLength) 
+		{
+			if (hashValue.Length != (hash.HashSize >> 3))
+				throw new CryptographicException ("bad hash length for " + hash.ToString ());
+
+			// DigestInfo ::= SEQUENCE {
+			//	digestAlgorithm AlgorithmIdentifier,
+			//	digest OCTET STRING
+			// }
+		
+			byte[] t = null;
+
+			string oid = CryptoConfig.MapNameToOID (hash.ToString ());
+			if (oid != null)
+			{
+				ASN1 digestAlgorithm = new ASN1 (0x30);
+				digestAlgorithm.Add (new ASN1 (CryptoConfig.EncodeOID (oid)));
+				digestAlgorithm.Add (new ASN1 (0x05));		// NULL
+				ASN1 digest = new ASN1 (0x04, hashValue);
+				ASN1 digestInfo = new ASN1 (0x30);
+				digestInfo.Add (digestAlgorithm);
+				digestInfo.Add (digest);
+
+				t = digestInfo.GetBytes ();
+			}
+			else
+			{
+				// There are no valid OID, in this case t = hashValue
+				// This is the case of the MD5SHA hash algorithm
+				t = hashValue;
+			}
+
+			Buffer.BlockCopy (hashValue, 0, t, t.Length - hashValue.Length, hashValue.Length);
+	
+			int PSLength = System.Math.Max (8, emLength - t.Length - 3);
+			// PS = PSLength of 0xff
+	
+			// EM = 0x00 | 0x01 | PS | 0x00 | T
+			byte[] EM = new byte [PSLength + t.Length + 3];
+			EM [1] = 0x01;
+			for (int i=2; i < PSLength + 2; i++)
+				EM[i] = 0xff;
+			Buffer.BlockCopy (t, 0, EM, PSLength + 3, t.Length);
+	
+			return EM;
+		}
+	
+		// PKCS #1 v.2.1, Section B.2.1
+		public static byte[] MGF1 (HashAlgorithm hash, byte[] mgfSeed, int maskLen) 
+		{
+			// 1. If maskLen > 2^32 hLen, output "mask too long" and stop.
+			// easy - this is impossible by using a int (31bits) as parameter ;-)
+			// BUT with a signed int we do have to check for negative values!
+			if (maskLen < 0)
+				throw new OverflowException();
+	
+			int mgfSeedLength = mgfSeed.Length;
+			int hLen = (hash.HashSize >> 3); // from bits to bytes
+			int iterations = (maskLen / hLen);
+			if (maskLen % hLen != 0)
+				iterations++;
+			// 2. Let T be the empty octet string.
+			byte[] T = new byte [iterations * hLen];
+	
+			byte[] toBeHashed = new byte [mgfSeedLength + 4];
+			int pos = 0;
+			// 3. For counter from 0 to \ceil (maskLen / hLen) - 1, do the following:
+			for (int counter = 0; counter < iterations; counter++) {
+				// a.	Convert counter to an octet string C of length 4 octets
+				byte[] C = I2OSP (counter, 4); 
+	
+				// b.	Concatenate the hash of the seed mgfSeed and C to the octet string T:
+				//	T = T || Hash (mgfSeed || C)
+				Buffer.BlockCopy (mgfSeed, 0, toBeHashed, 0, mgfSeedLength);
+				Buffer.BlockCopy (C, 0, toBeHashed, mgfSeedLength, 4);
+				byte[] output = hash.ComputeHash (toBeHashed);
+				Buffer.BlockCopy (output, 0, T, pos, hLen);
+				pos += hLen;
+			}
+			
+			// 4. Output the leading maskLen octets of T as the octet string mask.
+			byte[] mask = new byte [maskLen];
+			Buffer.BlockCopy (T, 0, mask, 0, maskLen);
+			return mask;
+		}
+
+		static internal string HashNameFromOid (string oid, bool throwOnError = true)
+		{
+			switch (oid) {
+			case "1.2.840.113549.1.1.2":	// MD2 with RSA encryption 
+				return "MD2";
+			case "1.2.840.113549.1.1.3":	// MD4 with RSA encryption 
+				return "MD4";
+			case "1.2.840.113549.1.1.4":	// MD5 with RSA encryption 
+				return "MD5";
+			case "1.2.840.113549.1.1.5":	// SHA-1 with RSA Encryption 
+			case "1.3.14.3.2.29":		// SHA1 with RSA signature 
+			case "1.2.840.10040.4.3":	// SHA1-1 with DSA
+				return "SHA1";
+			case "1.2.840.113549.1.1.11":	// SHA-256 with RSA Encryption
+				return "SHA256";
+			case "1.2.840.113549.1.1.12":	// SHA-384 with RSA Encryption
+				return "SHA384";
+			case "1.2.840.113549.1.1.13":	// SHA-512 with RSA Encryption
+				return "SHA512";
+			case "1.3.36.3.3.1.2":
+				return "RIPEMD160";
+			default:
+				if (throwOnError)
+					throw new CryptographicException ("Unsupported hash algorithm: " + oid);
+				return null;
+			}
+		}
+		
+		static internal HashAlgorithm CreateFromOid (string oid)
+		{
+			return CreateFromName (HashNameFromOid (oid));
+		}
+		
+		static internal HashAlgorithm CreateFromName (string name)
+		{
+#if FULL_AOT_RUNTIME
+			switch (name) {
+			case "MD2":
+				return MD2.Create ();
+			case "MD4":
+				return MD4.Create ();
+			case "MD5":
+				return MD5.Create ();
+			case "SHA1":
+				return SHA1.Create ();
+			case "SHA256":
+				return SHA256.Create ();
+			case "SHA384":
+				return SHA384.Create ();
+			case "SHA512":
+				return SHA512.Create ();
+			case "RIPEMD160":
+				return RIPEMD160.Create ();
+			default:
+				try {
+					return (HashAlgorithm) Activator.CreateInstance (Type.GetType (name));
+				}
+				catch {
+					throw new CryptographicException ("Unsupported hash algorithm: " + name);
+				}
+			}
+#else
+			return HashAlgorithm.Create (name);
+#endif
+		}
+	}
+}

+ 2001 - 0
MediaBrowser.Server.Mono/Security/PKCS12.cs

@@ -0,0 +1,2001 @@
+//
+// PKCS12.cs: PKCS 12 - Personal Information Exchange Syntax
+//
+// Author:
+//	Sebastien Pouliot  <sebastien@xamarin.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004,2005,2006 Novell Inc. (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Key derivation translated from Bouncy Castle JCE (http://www.bouncycastle.org/)
+// See bouncycastle.txt for license.
+//
+// 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.Collections;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+using Mono.Security;
+using Mono.Security.Cryptography;
+
+namespace Mono.Security.X509 {
+
+#if INSIDE_CORLIB
+	internal
+#else
+	public 
+#endif
+	class PKCS5 {
+
+		public const string pbeWithMD2AndDESCBC = "1.2.840.113549.1.5.1";
+		public const string pbeWithMD5AndDESCBC = "1.2.840.113549.1.5.3";
+		public const string pbeWithMD2AndRC2CBC = "1.2.840.113549.1.5.4";
+		public const string pbeWithMD5AndRC2CBC = "1.2.840.113549.1.5.6";
+		public const string pbeWithSHA1AndDESCBC = "1.2.840.113549.1.5.10";
+		public const string pbeWithSHA1AndRC2CBC = "1.2.840.113549.1.5.11";
+
+		public PKCS5 () {}
+	}
+
+#if INSIDE_CORLIB
+	internal
+#else
+	public 
+#endif
+	class PKCS9 {
+
+		public const string friendlyName = "1.2.840.113549.1.9.20";
+		public const string localKeyId = "1.2.840.113549.1.9.21";
+
+		public PKCS9 () {}
+	}
+
+
+	internal class SafeBag {
+		private string _bagOID;
+		private ASN1 _asn1;
+
+		public SafeBag(string bagOID, ASN1 asn1) {
+			_bagOID = bagOID;
+			_asn1 = asn1;
+		}
+
+		public string BagOID {
+			get { return _bagOID; }
+		}
+
+		public ASN1 ASN1 {
+			get { return _asn1; }
+		}
+	}
+
+
+#if INSIDE_CORLIB
+	internal
+#else
+	public 
+#endif
+	class PKCS12 : ICloneable {
+
+		public const string pbeWithSHAAnd128BitRC4 = "1.2.840.113549.1.12.1.1";
+		public const string pbeWithSHAAnd40BitRC4 = "1.2.840.113549.1.12.1.2";
+		public const string pbeWithSHAAnd3KeyTripleDESCBC = "1.2.840.113549.1.12.1.3";
+		public const string pbeWithSHAAnd2KeyTripleDESCBC = "1.2.840.113549.1.12.1.4";
+		public const string pbeWithSHAAnd128BitRC2CBC = "1.2.840.113549.1.12.1.5";
+		public const string pbeWithSHAAnd40BitRC2CBC = "1.2.840.113549.1.12.1.6";
+
+		// bags
+		public const string keyBag  = "1.2.840.113549.1.12.10.1.1";
+		public const string pkcs8ShroudedKeyBag  = "1.2.840.113549.1.12.10.1.2";
+		public const string certBag  = "1.2.840.113549.1.12.10.1.3";
+		public const string crlBag  = "1.2.840.113549.1.12.10.1.4";
+		public const string secretBag  = "1.2.840.113549.1.12.10.1.5";
+		public const string safeContentsBag  = "1.2.840.113549.1.12.10.1.6";
+
+		// types
+		public const string x509Certificate = "1.2.840.113549.1.9.22.1";
+ 		public const string sdsiCertificate = "1.2.840.113549.1.9.22.2";
+		public const string x509Crl = "1.2.840.113549.1.9.23.1";
+
+		// Adapted from BouncyCastle PKCS12ParametersGenerator.java
+		public class DeriveBytes {
+
+			public enum Purpose {
+				Key,
+				IV,
+				MAC
+			}
+
+			static private byte[] keyDiversifier = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 };
+			static private byte[] ivDiversifier  = { 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 };
+			static private byte[] macDiversifier = { 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 };
+
+			private string _hashName;
+			private int _iterations;
+			private byte[] _password;
+			private byte[] _salt;
+
+			public DeriveBytes () {}
+
+			public string HashName {
+				get { return _hashName; } 
+				set { _hashName = value; }
+			}
+
+			public int IterationCount {
+				get { return _iterations; }
+				set { _iterations = value; }
+			}
+
+			public byte[] Password {
+				get { return (byte[]) _password.Clone (); }
+				set { 
+					if (value == null)
+						_password = new byte [0];
+					else
+						_password = (byte[]) value.Clone ();
+				}
+			}
+
+			public byte[] Salt {
+				get { return (byte[]) _salt.Clone ();  }
+				set {
+					if (value != null)
+						_salt = (byte[]) value.Clone ();
+					else
+						_salt = null;
+				}
+			}
+
+			private void Adjust (byte[] a, int aOff, byte[] b) 
+			{
+				int x = (b[b.Length - 1] & 0xff) + (a [aOff + b.Length - 1] & 0xff) + 1;
+
+				a [aOff + b.Length - 1] = (byte) x;
+				x >>= 8;
+
+				for (int i = b.Length - 2; i >= 0; i--) {
+					x += (b [i] & 0xff) + (a [aOff + i] & 0xff);
+					a [aOff + i] = (byte) x;
+					x >>= 8;
+				}
+			}
+
+			private byte[] Derive (byte[] diversifier, int n) 
+			{
+				HashAlgorithm digest = PKCS1.CreateFromName (_hashName);
+				int u = (digest.HashSize >> 3); // div 8
+				int v = 64;
+				byte[] dKey = new byte [n];
+
+				byte[] S;
+				if ((_salt != null) && (_salt.Length != 0)) {
+					S = new byte[v * ((_salt.Length + v - 1) / v)];
+
+					for (int i = 0; i != S.Length; i++) {
+						S[i] = _salt[i % _salt.Length];
+					}
+				}
+				else {
+					S = new byte[0];
+				}
+
+				byte[] P;
+				if ((_password != null) && (_password.Length != 0)) {
+					P = new byte[v * ((_password.Length + v - 1) / v)];
+
+					for (int i = 0; i != P.Length; i++) {
+						P[i] = _password[i % _password.Length];
+					}
+				}
+				else {
+					P = new byte[0];
+				}
+
+				byte[] I = new byte [S.Length + P.Length];
+
+				Buffer.BlockCopy (S, 0, I, 0, S.Length);
+				Buffer.BlockCopy (P, 0, I, S.Length, P.Length);
+
+				byte[]  B = new byte[v];
+				int     c = (n + u - 1) / u;
+
+				for (int i = 1; i <= c; i++) {
+					digest.TransformBlock (diversifier, 0, diversifier.Length, diversifier, 0);
+					digest.TransformFinalBlock (I, 0, I.Length);
+					byte[] A = digest.Hash;
+					digest.Initialize ();
+					for (int j = 1; j != _iterations; j++) {
+						A = digest.ComputeHash (A, 0, A.Length);
+					}
+
+					for (int j = 0; j != B.Length; j++) {
+						B [j] = A [j % A.Length];
+					}
+
+					for (int j = 0; j != I.Length / v; j++) {
+						Adjust (I, j * v, B);
+					}
+
+					if (i == c) {
+						Buffer.BlockCopy(A, 0, dKey, (i - 1) * u, dKey.Length - ((i - 1) * u));
+					}
+					else {
+						Buffer.BlockCopy(A, 0, dKey, (i - 1) * u, A.Length);
+					}
+				}
+
+				return dKey;
+			}
+
+			public byte[] DeriveKey (int size) 
+			{
+				return Derive (keyDiversifier, size);
+			}
+
+			public byte[] DeriveIV (int size) 
+			{
+				return Derive (ivDiversifier, size);
+			}
+
+			public byte[] DeriveMAC (int size) 
+			{
+				return Derive (macDiversifier, size);
+			}
+		}
+
+		const int recommendedIterationCount = 2000;
+
+		//private int _version;
+		private byte[] _password;
+		private ArrayList _keyBags;
+		private ArrayList _secretBags;
+		private X509CertificateCollection _certs;
+		private bool _keyBagsChanged;
+		private bool _secretBagsChanged;
+		private bool _certsChanged;
+		private int _iterations;
+		private ArrayList _safeBags;
+		private RandomNumberGenerator _rng;
+
+		// constructors
+
+		public PKCS12 () 
+		{
+			_iterations = recommendedIterationCount;
+			_keyBags = new ArrayList ();
+			_secretBags = new ArrayList ();
+			_certs = new X509CertificateCollection ();
+			_keyBagsChanged = false;
+			_secretBagsChanged = false;
+			_certsChanged = false;
+			_safeBags = new ArrayList ();
+		}
+
+		public PKCS12 (byte[] data)
+			: this ()
+		{
+			Password = null;
+			Decode (data);
+		}
+
+		/*
+		 * PFX ::= SEQUENCE {
+		 *	version INTEGER {v3(3)}(v3,...),
+		 *	authSafe ContentInfo,
+		 *	macData MacData OPTIONAL
+		 * }
+		 * 
+		 * MacData ::= SEQUENCE {
+		 *	mac DigestInfo,
+		 *	macSalt OCTET STRING,
+		 *	iterations INTEGER DEFAULT 1
+		 *	-- Note: The default is for historical reasons and its use is deprecated. A higher
+		 *	-- value, like 1024 is recommended.
+		 * }
+		 * 
+		 * SafeContents ::= SEQUENCE OF SafeBag
+		 * 
+		 * SafeBag ::= SEQUENCE {
+		 *	bagId BAG-TYPE.&id ({PKCS12BagSet}),
+		 *	bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
+		 *	bagAttributes SET OF PKCS12Attribute OPTIONAL
+		 * }
+		 */
+		public PKCS12 (byte[] data, string password)
+			: this ()
+		{
+			Password = password;
+			Decode (data);
+		}
+
+		public PKCS12 (byte[] data, byte[] password)
+			: this ()
+		{
+			_password = password;
+			Decode (data);
+		}
+
+		private void Decode (byte[] data)
+		{
+			ASN1 pfx = new ASN1 (data);
+			if (pfx.Tag != 0x30)
+				throw new ArgumentException ("invalid data");
+			
+			ASN1 version = pfx [0];
+			if (version.Tag != 0x02)
+				throw new ArgumentException ("invalid PFX version");
+			//_version = version.Value [0];
+
+			PKCS7.ContentInfo authSafe = new PKCS7.ContentInfo (pfx [1]);
+			if (authSafe.ContentType != PKCS7.Oid.data)
+				throw new ArgumentException ("invalid authenticated safe");
+
+			// now that we know it's a PKCS#12 file, check the (optional) MAC
+			// before decoding anything else in the file
+			if (pfx.Count > 2) {
+				ASN1 macData = pfx [2];
+				if (macData.Tag != 0x30)
+					throw new ArgumentException ("invalid MAC");
+				
+				ASN1 mac = macData [0];
+				if (mac.Tag != 0x30)
+					throw new ArgumentException ("invalid MAC");
+				ASN1 macAlgorithm = mac [0];
+				string macOid = ASN1Convert.ToOid (macAlgorithm [0]);
+				if (macOid != "1.3.14.3.2.26")
+					throw new ArgumentException ("unsupported HMAC");
+				byte[] macValue = mac [1].Value;
+
+				ASN1 macSalt = macData [1];
+				if (macSalt.Tag != 0x04)
+					throw new ArgumentException ("missing MAC salt");
+
+				_iterations = 1; // default value
+				if (macData.Count > 2) {
+					ASN1 iters = macData [2];
+					if (iters.Tag != 0x02)
+						throw new ArgumentException ("invalid MAC iteration");
+					_iterations = ASN1Convert.ToInt32 (iters);
+				}
+
+				byte[] authSafeData = authSafe.Content [0].Value;
+				byte[] calculatedMac = MAC (_password, macSalt.Value, _iterations, authSafeData);
+				if (!Compare (macValue, calculatedMac)) {
+					byte[] nullPassword = {0, 0};
+					calculatedMac = MAC(nullPassword, macSalt.Value, _iterations, authSafeData);
+					if (!Compare (macValue, calculatedMac))
+						throw new CryptographicException ("Invalid MAC - file may have been tampe red!");
+					_password = nullPassword;
+				}
+			}
+
+			// we now returns to our original presentation - PFX
+			ASN1 authenticatedSafe = new ASN1 (authSafe.Content [0].Value);
+			for (int i=0; i < authenticatedSafe.Count; i++) {
+				PKCS7.ContentInfo ci = new PKCS7.ContentInfo (authenticatedSafe [i]);
+				switch (ci.ContentType) {
+					case PKCS7.Oid.data:
+						// unencrypted (by PKCS#12)
+						ASN1 safeContents = new ASN1 (ci.Content [0].Value);
+						for (int j=0; j < safeContents.Count; j++) {
+							ASN1 safeBag = safeContents [j];
+							ReadSafeBag (safeBag);
+						}
+						break;
+					case PKCS7.Oid.encryptedData:
+						// password encrypted
+						PKCS7.EncryptedData ed = new PKCS7.EncryptedData (ci.Content [0]);
+						ASN1 decrypted = new ASN1 (Decrypt (ed));
+						for (int j=0; j < decrypted.Count; j++) {
+							ASN1 safeBag = decrypted [j];
+							ReadSafeBag (safeBag);
+						}
+						break;
+					case PKCS7.Oid.envelopedData:
+						// public key encrypted
+						throw new NotImplementedException ("public key encrypted");
+					default:
+						throw new ArgumentException ("unknown authenticatedSafe");
+				}
+			}
+		}
+
+		~PKCS12 () 
+		{
+			if (_password != null) {
+				Array.Clear (_password, 0, _password.Length);
+			}
+			_password = null;
+		}
+
+		// properties
+
+		public string Password {
+			set {
+				// Clear old password.
+				if (_password != null)
+					Array.Clear (_password, 0, _password.Length);
+				_password = null;
+				if (value != null) {
+					if (value.Length > 0) {
+						int size = value.Length;
+						int nul = 0;
+						if (size < MaximumPasswordLength) {
+							// if not present, add space for a NULL (0x00) character
+							if (value[size - 1] != 0x00)
+								nul = 1;
+						} else {
+							size = MaximumPasswordLength;
+						}
+						_password = new byte[(size + nul) << 1]; // double for unicode
+						Encoding.BigEndianUnicode.GetBytes (value, 0, size, _password, 0);
+					} else {
+						// double-byte (Unicode) NULL (0x00) - see bug #79617
+						_password = new byte[2];
+					}
+				}
+			}
+		}
+
+		public int IterationCount {
+			get { return _iterations; }
+			set { _iterations = value; }
+		}
+
+		public ArrayList Keys {
+			get {
+				if (_keyBagsChanged) {
+					_keyBags.Clear ();
+					foreach (SafeBag sb in _safeBags) {
+						if (sb.BagOID.Equals (keyBag)) {
+							ASN1 safeBag = sb.ASN1;
+							ASN1 bagValue = safeBag [1];
+							PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+							byte[] privateKey = pki.PrivateKey;
+							switch (privateKey [0]) {
+							case 0x02:
+								DSAParameters p = new DSAParameters (); // FIXME
+								_keyBags.Add (PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p));
+								break;
+							case 0x30:
+								_keyBags.Add (PKCS8.PrivateKeyInfo.DecodeRSA (privateKey));
+								break;
+							default:
+								break;
+							}
+							Array.Clear (privateKey, 0, privateKey.Length);
+
+						} else if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+							ASN1 safeBag = sb.ASN1;
+							ASN1 bagValue = safeBag [1];
+							PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+							byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+							PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+							byte[] privateKey = pki.PrivateKey;
+							switch (privateKey [0]) {
+							case 0x02:
+								DSAParameters p = new DSAParameters (); // FIXME
+								_keyBags.Add (PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p));
+								break;
+							case 0x30:
+								_keyBags.Add (PKCS8.PrivateKeyInfo.DecodeRSA (privateKey));
+								break;
+							default:
+								break;
+							}
+							Array.Clear (privateKey, 0, privateKey.Length);
+							Array.Clear (decrypted, 0, decrypted.Length);
+						}
+					}
+					_keyBagsChanged = false;
+				}
+				return ArrayList.ReadOnly(_keyBags);
+			}
+		}
+
+		public ArrayList Secrets {
+			get {
+				if (_secretBagsChanged) {
+					_secretBags.Clear ();
+					foreach (SafeBag sb in _safeBags) {
+						if (sb.BagOID.Equals (secretBag)) {
+							ASN1 safeBag = sb.ASN1;
+							ASN1 bagValue = safeBag [1];
+							byte[] secret = bagValue.Value;
+							_secretBags.Add(secret);
+						}
+					}
+					_secretBagsChanged = false;
+				}
+				return ArrayList.ReadOnly(_secretBags);
+			}
+		}
+
+		public X509CertificateCollection Certificates {
+			get {
+				if (_certsChanged) {
+					_certs.Clear ();
+					foreach (SafeBag sb in _safeBags) {
+						if (sb.BagOID.Equals (certBag)) {
+							ASN1 safeBag = sb.ASN1;
+							ASN1 bagValue = safeBag [1];
+							PKCS7.ContentInfo cert = new PKCS7.ContentInfo (bagValue.Value);
+							_certs.Add (new X509Certificate (cert.Content [0].Value));
+						}
+					}
+					_certsChanged = false;
+				}
+				return _certs;
+			}
+		}
+
+		internal RandomNumberGenerator RNG {
+			get {
+				if (_rng == null)
+					_rng = RandomNumberGenerator.Create ();
+				return _rng;
+			}
+		}
+
+		// private methods
+
+		private bool Compare (byte[] expected, byte[] actual) 
+		{
+			bool compare = false;
+			if (expected.Length == actual.Length) {
+				for (int i=0; i < expected.Length; i++) {
+					if (expected [i] != actual [i])
+						return false;
+				}
+				compare = true;
+			}
+			return compare;
+		}
+
+		private SymmetricAlgorithm GetSymmetricAlgorithm (string algorithmOid, byte[] salt, int iterationCount)
+		{
+			string algorithm = null;
+			int keyLength = 8;	// 64 bits (default)
+			int ivLength = 8;	// 64 bits (default)
+
+			PKCS12.DeriveBytes pd = new PKCS12.DeriveBytes ();
+			pd.Password = _password; 
+			pd.Salt = salt;
+			pd.IterationCount = iterationCount;
+
+			switch (algorithmOid) {
+				case PKCS5.pbeWithMD2AndDESCBC:			// no unit test available
+					pd.HashName = "MD2";
+					algorithm = "DES";
+					break;
+				case PKCS5.pbeWithMD5AndDESCBC:			// no unit test available
+					pd.HashName = "MD5";
+					algorithm = "DES";
+					break;
+				case PKCS5.pbeWithMD2AndRC2CBC:			// no unit test available
+					// TODO - RC2-CBC-Parameter (PKCS5)
+					// if missing default to 32 bits !!!
+					pd.HashName = "MD2";
+					algorithm = "RC2";
+					keyLength = 4;		// default
+					break;
+				case PKCS5.pbeWithMD5AndRC2CBC:			// no unit test available
+					// TODO - RC2-CBC-Parameter (PKCS5)
+					// if missing default to 32 bits !!!
+					pd.HashName = "MD5";
+					algorithm = "RC2";
+					keyLength = 4;		// default
+					break;
+				case PKCS5.pbeWithSHA1AndDESCBC: 		// no unit test available
+					pd.HashName = "SHA1";
+					algorithm = "DES";
+					break;
+				case PKCS5.pbeWithSHA1AndRC2CBC:		// no unit test available
+					// TODO - RC2-CBC-Parameter (PKCS5)
+					// if missing default to 32 bits !!!
+					pd.HashName = "SHA1";
+					algorithm = "RC2";
+					keyLength = 4;		// default
+					break;
+				case PKCS12.pbeWithSHAAnd128BitRC4: 		// no unit test available
+					pd.HashName = "SHA1";
+					algorithm = "RC4";
+					keyLength = 16;
+					ivLength = 0;		// N/A
+					break;
+				case PKCS12.pbeWithSHAAnd40BitRC4: 		// no unit test available
+					pd.HashName = "SHA1";
+					algorithm = "RC4";
+					keyLength = 5;
+					ivLength = 0;		// N/A
+					break;
+				case PKCS12.pbeWithSHAAnd3KeyTripleDESCBC: 
+					pd.HashName = "SHA1";
+					algorithm = "TripleDES";
+					keyLength = 24;
+					break;
+				case PKCS12.pbeWithSHAAnd2KeyTripleDESCBC:	// no unit test available
+					pd.HashName = "SHA1";
+					algorithm = "TripleDES";
+					keyLength = 16;
+					break;
+				case PKCS12.pbeWithSHAAnd128BitRC2CBC: 		// no unit test available
+					pd.HashName = "SHA1";
+					algorithm = "RC2";
+					keyLength = 16;
+					break;
+				case PKCS12.pbeWithSHAAnd40BitRC2CBC: 
+					pd.HashName = "SHA1";
+					algorithm = "RC2";
+					keyLength = 5;
+					break;
+				default:
+					throw new NotSupportedException ("unknown oid " + algorithm);
+			}
+
+			SymmetricAlgorithm sa = null;
+#if INSIDE_CORLIB && FULL_AOT_RUNTIME
+			// we do not want CryptoConfig to bring the whole crypto stack
+			// in particular Rijndael which is not supported by CommonCrypto
+			switch (algorithm) {
+			case "DES":
+				sa = DES.Create ();
+				break;
+			case "RC2":
+				sa = RC2.Create ();
+				break;
+			case "TripleDES":
+				sa = TripleDES.Create ();
+				break;
+			case "RC4":
+				sa = RC4.Create ();
+				break;
+			default:
+				throw new NotSupportedException (algorithm);
+			}
+#else
+			sa = SymmetricAlgorithm.Create (algorithm);
+#endif
+			sa.Key = pd.DeriveKey (keyLength);
+			// IV required only for block ciphers (not stream ciphers)
+			if (ivLength > 0) {
+				sa.IV = pd.DeriveIV (ivLength);
+				sa.Mode = CipherMode.CBC;
+			}
+			return sa;
+		}
+
+		public byte[] Decrypt (string algorithmOid, byte[] salt, int iterationCount, byte[] encryptedData) 
+		{
+			SymmetricAlgorithm sa = null;
+			byte[] result = null;
+			try {
+				sa = GetSymmetricAlgorithm (algorithmOid, salt, iterationCount);
+				ICryptoTransform ct = sa.CreateDecryptor ();
+				result = ct.TransformFinalBlock (encryptedData, 0, encryptedData.Length);
+			}
+			finally {
+				if (sa != null)
+					sa.Clear ();
+			}
+			return result;
+		}
+
+		public byte[] Decrypt (PKCS7.EncryptedData ed)
+		{
+			return Decrypt (ed.EncryptionAlgorithm.ContentType, 
+				ed.EncryptionAlgorithm.Content [0].Value, 
+				ASN1Convert.ToInt32 (ed.EncryptionAlgorithm.Content [1]),
+				ed.EncryptedContent);
+		}
+
+		public byte[] Encrypt (string algorithmOid, byte[] salt, int iterationCount, byte[] data) 
+		{
+			byte[] result = null;
+			using (SymmetricAlgorithm sa = GetSymmetricAlgorithm (algorithmOid, salt, iterationCount)) {
+				ICryptoTransform ct = sa.CreateEncryptor ();
+				result = ct.TransformFinalBlock (data, 0, data.Length);
+			}
+			return result;
+		}
+
+		private DSAParameters GetExistingParameters (out bool found)
+		{
+			foreach (X509Certificate cert in Certificates) {
+				// FIXME: that won't work if parts of the parameters are missing
+				if (cert.KeyAlgorithmParameters != null) {
+					DSA dsa = cert.DSA;
+					if (dsa != null) {
+						found = true;
+						return dsa.ExportParameters (false);
+					}
+				}
+			}
+			found = false;
+			return new DSAParameters ();
+		}
+
+		private void AddPrivateKey (PKCS8.PrivateKeyInfo pki) 
+		{
+			byte[] privateKey = pki.PrivateKey;
+			switch (privateKey [0]) {
+				case 0x02:
+					bool found;
+					DSAParameters p = GetExistingParameters (out found);
+					if (found) {
+						_keyBags.Add (PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p));
+					}
+					break;
+				case 0x30:
+					_keyBags.Add (PKCS8.PrivateKeyInfo.DecodeRSA (privateKey));
+					break;
+				default:
+					Array.Clear (privateKey, 0, privateKey.Length);
+					throw new CryptographicException ("Unknown private key format");
+			}
+			Array.Clear (privateKey, 0, privateKey.Length);
+		}
+
+		private void ReadSafeBag (ASN1 safeBag) 
+		{
+			if (safeBag.Tag != 0x30)
+				throw new ArgumentException ("invalid safeBag");
+
+			ASN1 bagId = safeBag [0];
+			if (bagId.Tag != 0x06)
+				throw new ArgumentException ("invalid safeBag id");
+
+			ASN1 bagValue = safeBag [1];
+			string oid = ASN1Convert.ToOid (bagId);
+			switch (oid) {
+				case keyBag:
+					// NEED UNIT TEST
+					AddPrivateKey (new PKCS8.PrivateKeyInfo (bagValue.Value));
+					break;
+				case pkcs8ShroudedKeyBag:
+					PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+					byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+					AddPrivateKey (new PKCS8.PrivateKeyInfo (decrypted));
+					Array.Clear (decrypted, 0, decrypted.Length);
+					break;
+				case certBag:
+					PKCS7.ContentInfo cert = new PKCS7.ContentInfo (bagValue.Value);
+					if (cert.ContentType != x509Certificate)
+						throw new NotSupportedException ("unsupport certificate type");
+					X509Certificate x509 = new X509Certificate (cert.Content [0].Value);
+					_certs.Add (x509);
+					break;
+				case crlBag:
+					// TODO
+					break;
+				case secretBag: 
+					byte[] secret = bagValue.Value;
+					_secretBags.Add(secret);
+					break;
+				case safeContentsBag:
+					// TODO - ? recurse ?
+					break;
+				default:
+					throw new ArgumentException ("unknown safeBag oid");
+			}
+
+			if (safeBag.Count > 2) {
+				ASN1 bagAttributes = safeBag [2];
+				if (bagAttributes.Tag != 0x31)
+					throw new ArgumentException ("invalid safeBag attributes id");
+
+				for (int i = 0; i < bagAttributes.Count; i++) {
+					ASN1 pkcs12Attribute = bagAttributes[i];
+						
+					if (pkcs12Attribute.Tag != 0x30)
+						throw new ArgumentException ("invalid PKCS12 attributes id");
+
+					ASN1 attrId = pkcs12Attribute [0];
+					if (attrId.Tag != 0x06)
+						throw new ArgumentException ("invalid attribute id");
+						
+					string attrOid = ASN1Convert.ToOid (attrId);
+
+					ASN1 attrValues = pkcs12Attribute[1];
+					for (int j = 0; j < attrValues.Count; j++) {
+						ASN1 attrValue = attrValues[j];
+
+						switch (attrOid) {
+						case PKCS9.friendlyName:
+							if (attrValue.Tag != 0x1e)
+								throw new ArgumentException ("invalid attribute value id");
+							break;
+						case PKCS9.localKeyId:
+							if (attrValue.Tag != 0x04)
+								throw new ArgumentException ("invalid attribute value id");
+							break;
+						default:
+							// Unknown OID -- don't check Tag
+							break;
+						}
+					}
+				}
+			}
+
+			_safeBags.Add (new SafeBag(oid, safeBag));
+		}
+
+		private ASN1 Pkcs8ShroudedKeyBagSafeBag (AsymmetricAlgorithm aa, IDictionary attributes) 
+		{
+			PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo ();
+			if (aa is RSA) {
+				pki.Algorithm = "1.2.840.113549.1.1.1";
+				pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((RSA)aa);
+			}
+			else if (aa is DSA) {
+				pki.Algorithm = null;
+				pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((DSA)aa);
+			}
+			else
+				throw new CryptographicException ("Unknown asymmetric algorithm {0}", aa.ToString ());
+
+			PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo ();
+			epki.Algorithm = pbeWithSHAAnd3KeyTripleDESCBC;
+			epki.IterationCount = _iterations;
+			epki.EncryptedData = Encrypt (pbeWithSHAAnd3KeyTripleDESCBC, epki.Salt, _iterations, pki.GetBytes ());
+
+			ASN1 safeBag = new ASN1 (0x30);
+			safeBag.Add (ASN1Convert.FromOid (pkcs8ShroudedKeyBag));
+			ASN1 bagValue = new ASN1 (0xA0);
+			bagValue.Add (new ASN1 (epki.GetBytes ()));
+			safeBag.Add (bagValue);
+
+			if (attributes != null) {
+				ASN1 bagAttributes = new ASN1 (0x31);
+				IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+				while (de.MoveNext ()) {
+					string oid = (string)de.Key;
+					switch (oid) {
+					case PKCS9.friendlyName:
+						ArrayList names = (ArrayList)de.Value;
+						if (names.Count > 0) {
+							ASN1 pkcs12Attribute = new ASN1 (0x30);
+							pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+							ASN1 attrValues = new ASN1 (0x31);
+							foreach (byte[] name in names) {
+								ASN1 attrValue = new ASN1 (0x1e);
+								attrValue.Value = name;
+								attrValues.Add (attrValue);
+							}
+							pkcs12Attribute.Add (attrValues);
+							bagAttributes.Add (pkcs12Attribute);
+						}
+						break;
+					case PKCS9.localKeyId:
+						ArrayList keys = (ArrayList)de.Value;
+						if (keys.Count > 0) {
+							ASN1 pkcs12Attribute = new ASN1 (0x30);
+							pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+							ASN1 attrValues = new ASN1 (0x31);
+							foreach (byte[] key in keys) {
+								ASN1 attrValue = new ASN1 (0x04);
+								attrValue.Value = key;
+								attrValues.Add (attrValue);
+							}
+							pkcs12Attribute.Add (attrValues);
+							bagAttributes.Add (pkcs12Attribute);
+						}
+						break;
+					default:
+						break;
+					}
+				}
+
+				if (bagAttributes.Count > 0) {
+					safeBag.Add (bagAttributes);
+				}
+			}
+
+			return safeBag;
+		}
+
+		private ASN1 KeyBagSafeBag (AsymmetricAlgorithm aa, IDictionary attributes) 
+		{
+			PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo ();
+			if (aa is RSA) {
+				pki.Algorithm = "1.2.840.113549.1.1.1";
+				pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((RSA)aa);
+			}
+			else if (aa is DSA) {
+				pki.Algorithm = null;
+				pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((DSA)aa);
+			}
+			else
+				throw new CryptographicException ("Unknown asymmetric algorithm {0}", aa.ToString ());
+
+			ASN1 safeBag = new ASN1 (0x30);
+			safeBag.Add (ASN1Convert.FromOid (keyBag));
+			ASN1 bagValue = new ASN1 (0xA0);
+			bagValue.Add (new ASN1 (pki.GetBytes ()));
+			safeBag.Add (bagValue);
+
+			if (attributes != null) {
+				ASN1 bagAttributes = new ASN1 (0x31);
+				IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+				while (de.MoveNext ()) {
+					string oid = (string)de.Key;
+					switch (oid) {
+					case PKCS9.friendlyName:
+						ArrayList names = (ArrayList)de.Value;
+						if (names.Count > 0) {
+							ASN1 pkcs12Attribute = new ASN1 (0x30);
+							pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+							ASN1 attrValues = new ASN1 (0x31);
+							foreach (byte[] name in names) {
+								ASN1 attrValue = new ASN1 (0x1e);
+								attrValue.Value = name;
+								attrValues.Add (attrValue);
+							}
+							pkcs12Attribute.Add (attrValues);
+							bagAttributes.Add (pkcs12Attribute);
+						}
+						break;
+					case PKCS9.localKeyId:
+						ArrayList keys = (ArrayList)de.Value;
+						if (keys.Count > 0) {
+							ASN1 pkcs12Attribute = new ASN1 (0x30);
+							pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+							ASN1 attrValues = new ASN1 (0x31);
+							foreach (byte[] key in keys) {
+								ASN1 attrValue = new ASN1 (0x04);
+								attrValue.Value = key;
+								attrValues.Add (attrValue);
+							}
+							pkcs12Attribute.Add (attrValues);
+							bagAttributes.Add (pkcs12Attribute);
+						}
+						break;
+					default:
+						break;
+					}
+				}
+
+				if (bagAttributes.Count > 0) {
+					safeBag.Add (bagAttributes);
+				}
+			}
+
+			return safeBag;
+		}
+
+		private ASN1 SecretBagSafeBag (byte[] secret, IDictionary attributes) 
+		{
+			ASN1 safeBag = new ASN1 (0x30);
+			safeBag.Add (ASN1Convert.FromOid (secretBag));
+			ASN1 bagValue = new ASN1 (0x80, secret);
+			safeBag.Add (bagValue);
+
+			if (attributes != null) {
+				ASN1 bagAttributes = new ASN1 (0x31);
+				IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+				while (de.MoveNext ()) {
+					string oid = (string)de.Key;
+					switch (oid) {
+					case PKCS9.friendlyName:
+						ArrayList names = (ArrayList)de.Value;
+						if (names.Count > 0) {
+							ASN1 pkcs12Attribute = new ASN1 (0x30);
+							pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+							ASN1 attrValues = new ASN1 (0x31);
+							foreach (byte[] name in names) {
+								ASN1 attrValue = new ASN1 (0x1e);
+								attrValue.Value = name;
+								attrValues.Add (attrValue);
+							}
+							pkcs12Attribute.Add (attrValues);
+							bagAttributes.Add (pkcs12Attribute);
+						}
+						break;
+					case PKCS9.localKeyId:
+						ArrayList keys = (ArrayList)de.Value;
+						if (keys.Count > 0) {
+							ASN1 pkcs12Attribute = new ASN1 (0x30);
+							pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+							ASN1 attrValues = new ASN1 (0x31);
+							foreach (byte[] key in keys) {
+								ASN1 attrValue = new ASN1 (0x04);
+								attrValue.Value = key;
+								attrValues.Add (attrValue);
+							}
+							pkcs12Attribute.Add (attrValues);
+							bagAttributes.Add (pkcs12Attribute);
+						}
+						break;
+					default:
+						break;
+					}
+				}
+
+				if (bagAttributes.Count > 0) {
+					safeBag.Add (bagAttributes);
+				}
+			}
+
+			return safeBag;
+		}
+
+		private ASN1 CertificateSafeBag (X509Certificate x509, IDictionary attributes) 
+		{
+			ASN1 encapsulatedCertificate = new ASN1 (0x04, x509.RawData);
+
+			PKCS7.ContentInfo ci = new PKCS7.ContentInfo ();
+			ci.ContentType = x509Certificate;
+			ci.Content.Add (encapsulatedCertificate);
+
+			ASN1 bagValue = new ASN1 (0xA0);
+			bagValue.Add (ci.ASN1);
+
+			ASN1 safeBag = new ASN1 (0x30);
+			safeBag.Add (ASN1Convert.FromOid (certBag));
+			safeBag.Add (bagValue);
+
+			if (attributes != null) {
+				ASN1 bagAttributes = new ASN1 (0x31);
+				IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+				while (de.MoveNext ()) {
+					string oid = (string)de.Key;
+					switch (oid) {
+					case PKCS9.friendlyName:
+						ArrayList names = (ArrayList)de.Value;
+						if (names.Count > 0) {
+							ASN1 pkcs12Attribute = new ASN1 (0x30);
+							pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+							ASN1 attrValues = new ASN1 (0x31);
+							foreach (byte[] name in names) {
+								ASN1 attrValue = new ASN1 (0x1e);
+								attrValue.Value = name;
+								attrValues.Add (attrValue);
+							}
+							pkcs12Attribute.Add (attrValues);
+							bagAttributes.Add (pkcs12Attribute);
+						}
+						break;
+					case PKCS9.localKeyId:
+						ArrayList keys = (ArrayList)de.Value;
+						if (keys.Count > 0) {
+							ASN1 pkcs12Attribute = new ASN1 (0x30);
+							pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+							ASN1 attrValues = new ASN1 (0x31);
+							foreach (byte[] key in keys) {
+								ASN1 attrValue = new ASN1 (0x04);
+								attrValue.Value = key;
+								attrValues.Add (attrValue);
+							}
+							pkcs12Attribute.Add (attrValues);
+							bagAttributes.Add (pkcs12Attribute);
+						}
+						break;
+					default:
+						break;
+					}
+				}
+
+				if (bagAttributes.Count > 0) {
+					safeBag.Add (bagAttributes);
+				}
+			}
+
+			return safeBag;
+		}
+
+		private byte[] MAC (byte[] password, byte[] salt, int iterations, byte[] data) 
+		{
+			PKCS12.DeriveBytes pd = new PKCS12.DeriveBytes ();
+			pd.HashName = "SHA1";
+			pd.Password = password;
+			pd.Salt = salt;
+			pd.IterationCount = iterations;
+
+			HMACSHA1 hmac = (HMACSHA1) HMACSHA1.Create ();
+			hmac.Key = pd.DeriveMAC (20);
+			return hmac.ComputeHash (data, 0, data.Length);
+		}
+
+		/*
+		 * SafeContents ::= SEQUENCE OF SafeBag
+		 * 
+		 * SafeBag ::= SEQUENCE {
+		 *	bagId BAG-TYPE.&id ({PKCS12BagSet}),
+		 *	bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
+		 *	bagAttributes SET OF PKCS12Attribute OPTIONAL
+		 * }
+		 */
+		public byte[] GetBytes () 
+		{
+			// TODO (incomplete)
+			ASN1 safeBagSequence = new ASN1 (0x30);
+
+			// Sync Safe Bag list since X509CertificateCollection may be updated
+			ArrayList scs = new ArrayList ();
+			foreach (SafeBag sb in _safeBags) {
+				if (sb.BagOID.Equals (certBag)) {
+					ASN1 safeBag = sb.ASN1;
+					ASN1 bagValue = safeBag [1];
+					PKCS7.ContentInfo cert = new PKCS7.ContentInfo (bagValue.Value);
+					scs.Add (new X509Certificate (cert.Content [0].Value));
+				}
+			}
+
+			ArrayList addcerts = new ArrayList ();
+			ArrayList removecerts = new ArrayList ();
+
+			foreach (X509Certificate c in Certificates) {
+				bool found = false;
+
+				foreach (X509Certificate lc in scs) {
+					if (Compare (c.RawData, lc.RawData)) {
+						found = true;
+					}
+				}
+
+				if (!found) {
+					addcerts.Add (c);
+				}
+			}
+			foreach (X509Certificate c in scs) {
+				bool found = false;
+
+				foreach (X509Certificate lc in Certificates) {
+					if (Compare (c.RawData, lc.RawData)) {
+						found = true;
+					}
+				}
+
+				if (!found) {
+					removecerts.Add (c);
+				}
+			}
+
+			foreach (X509Certificate c in removecerts) {
+				RemoveCertificate (c);
+			}
+
+			foreach (X509Certificate c in addcerts) {
+				AddCertificate (c);
+			}
+			// Sync done
+
+			if (_safeBags.Count > 0) {
+				ASN1 certsSafeBag = new ASN1 (0x30);
+				foreach (SafeBag sb in _safeBags) {
+					if (sb.BagOID.Equals (certBag)) {
+						certsSafeBag.Add (sb.ASN1);
+					}
+				}
+
+				if (certsSafeBag.Count > 0) {
+					PKCS7.ContentInfo contentInfo = EncryptedContentInfo (certsSafeBag, pbeWithSHAAnd3KeyTripleDESCBC);
+					safeBagSequence.Add (contentInfo.ASN1);
+				}
+			}
+
+			if (_safeBags.Count > 0) {
+				ASN1 safeContents = new ASN1 (0x30);
+				foreach (SafeBag sb in _safeBags) {
+					if (sb.BagOID.Equals (keyBag) ||
+					    sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+						safeContents.Add (sb.ASN1);
+					}
+				}
+				if (safeContents.Count > 0) {
+					ASN1 content = new ASN1 (0xA0);
+					content.Add (new ASN1 (0x04, safeContents.GetBytes ()));
+				
+					PKCS7.ContentInfo keyBag = new PKCS7.ContentInfo (PKCS7.Oid.data);
+					keyBag.Content = content;
+					safeBagSequence.Add (keyBag.ASN1);
+				}
+			}
+
+			// Doing SecretBags separately in case we want to change their encryption independently.
+			if (_safeBags.Count > 0) {
+				ASN1 secretsSafeBag = new ASN1 (0x30);
+				foreach (SafeBag sb in _safeBags) {
+					if (sb.BagOID.Equals (secretBag)) {
+						secretsSafeBag.Add (sb.ASN1);
+					}
+				}
+
+				if (secretsSafeBag.Count > 0) {
+					PKCS7.ContentInfo contentInfo = EncryptedContentInfo (secretsSafeBag, pbeWithSHAAnd3KeyTripleDESCBC);
+					safeBagSequence.Add (contentInfo.ASN1);
+				}
+			}
+
+
+			ASN1 encapsulates = new ASN1 (0x04, safeBagSequence.GetBytes ());
+			ASN1 ci = new ASN1 (0xA0);
+			ci.Add (encapsulates);
+			PKCS7.ContentInfo authSafe = new PKCS7.ContentInfo (PKCS7.Oid.data);
+			authSafe.Content = ci;
+			
+			ASN1 macData = new ASN1 (0x30);
+			if (_password != null) {
+				// only for password based encryption
+				byte[] salt = new byte [20];
+				RNG.GetBytes (salt);
+				byte[] macValue = MAC (_password, salt, _iterations, authSafe.Content [0].Value);
+				ASN1 oidSeq = new ASN1 (0x30);
+				oidSeq.Add (ASN1Convert.FromOid ("1.3.14.3.2.26"));	// SHA1
+				oidSeq.Add (new ASN1 (0x05));
+
+				ASN1 mac = new ASN1 (0x30);
+				mac.Add (oidSeq);
+				mac.Add (new ASN1 (0x04, macValue));
+
+				macData.Add (mac);
+				macData.Add (new ASN1 (0x04, salt));
+				macData.Add (ASN1Convert.FromInt32 (_iterations));
+			}
+			
+			ASN1 version = new ASN1 (0x02, new byte [1] { 0x03 });
+			
+			ASN1 pfx = new ASN1 (0x30);
+			pfx.Add (version);
+			pfx.Add (authSafe.ASN1);
+			if (macData.Count > 0) {
+				// only for password based encryption
+				pfx.Add (macData);
+			}
+
+			return pfx.GetBytes ();
+		}
+
+		// Creates an encrypted PKCS#7 ContentInfo with safeBags as its SafeContents.  Used in GetBytes(), above.
+		private PKCS7.ContentInfo EncryptedContentInfo(ASN1 safeBags, string algorithmOid)
+		{
+			byte[] salt = new byte [8];
+			RNG.GetBytes (salt);
+
+			ASN1 seqParams = new ASN1 (0x30);
+			seqParams.Add (new ASN1 (0x04, salt));
+			seqParams.Add (ASN1Convert.FromInt32 (_iterations));
+
+			ASN1 seqPbe = new ASN1 (0x30);
+			seqPbe.Add (ASN1Convert.FromOid (algorithmOid));
+			seqPbe.Add (seqParams);
+
+			byte[] encrypted = Encrypt (algorithmOid, salt, _iterations, safeBags.GetBytes ());
+			ASN1 encryptedContent = new ASN1 (0x80, encrypted);
+
+			ASN1 seq = new ASN1 (0x30);
+			seq.Add (ASN1Convert.FromOid (PKCS7.Oid.data));
+			seq.Add (seqPbe);
+			seq.Add (encryptedContent);
+
+			ASN1 version = new ASN1 (0x02, new byte [1] { 0x00 });
+			ASN1 encData = new ASN1 (0x30);
+			encData.Add (version);
+			encData.Add (seq);
+
+			ASN1 finalContent = new ASN1 (0xA0);
+			finalContent.Add (encData);
+
+			PKCS7.ContentInfo bag = new PKCS7.ContentInfo (PKCS7.Oid.encryptedData);
+			bag.Content = finalContent;
+			return bag;
+		}
+
+		public void AddCertificate (X509Certificate cert)
+		{
+			AddCertificate (cert, null);
+		}
+
+		public void AddCertificate (X509Certificate cert, IDictionary attributes)
+		{
+			bool found = false;
+
+			for (int i = 0; !found && i < _safeBags.Count; i++) {
+				SafeBag sb = (SafeBag)_safeBags [i];
+
+				if (sb.BagOID.Equals (certBag)) {
+					ASN1 safeBag = sb.ASN1;
+					ASN1 bagValue = safeBag [1];
+					PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+					X509Certificate c = new X509Certificate (crt.Content [0].Value);
+					if (Compare (cert.RawData, c.RawData)) {
+						found = true;
+					}
+				}
+			}
+
+			if (!found) {
+				_safeBags.Add (new SafeBag (certBag, CertificateSafeBag (cert, attributes)));
+				_certsChanged = true;
+			}
+		}
+
+		public void RemoveCertificate (X509Certificate cert)
+		{
+			RemoveCertificate (cert, null);
+		}
+
+		public void RemoveCertificate (X509Certificate cert, IDictionary attrs)
+		{
+			int certIndex = -1;
+
+			for (int i = 0; certIndex == -1 && i < _safeBags.Count; i++) {
+				SafeBag sb = (SafeBag)_safeBags [i];
+
+				if (sb.BagOID.Equals (certBag)) {
+					ASN1 safeBag = sb.ASN1;
+					ASN1 bagValue = safeBag [1];
+					PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+					X509Certificate c = new X509Certificate (crt.Content [0].Value);
+					if (Compare (cert.RawData, c.RawData)) {
+						if (attrs != null) {
+							if (safeBag.Count == 3) {
+								ASN1 bagAttributes = safeBag [2];
+								int bagAttributesFound = 0;
+								for (int j = 0; j < bagAttributes.Count; j++) {
+									ASN1 pkcs12Attribute = bagAttributes [j];
+									ASN1 attrId = pkcs12Attribute [0];
+									string ao = ASN1Convert.ToOid (attrId);
+									ArrayList dattrValues = (ArrayList)attrs [ao];
+
+									if (dattrValues != null) {
+										ASN1 attrValues = pkcs12Attribute [1];
+
+										if (dattrValues.Count == attrValues.Count) {
+											int attrValuesFound = 0;
+											for (int k = 0; k < attrValues.Count; k++) {
+												ASN1 attrValue = attrValues [k];
+												byte[] value = (byte[])dattrValues [k];
+									
+												if (Compare (value, attrValue.Value)) {
+													attrValuesFound += 1;
+												}
+											}
+											if (attrValuesFound == attrValues.Count) {
+												bagAttributesFound += 1;
+											}
+										}
+									}
+								}
+								if (bagAttributesFound == bagAttributes.Count) {
+									certIndex = i;
+								}
+							}
+						} else {
+							certIndex = i;
+						}
+					}
+				}
+			}
+
+			if (certIndex != -1) {
+				_safeBags.RemoveAt (certIndex);
+				_certsChanged = true;
+			}
+		}
+
+		private bool CompareAsymmetricAlgorithm (AsymmetricAlgorithm a1, AsymmetricAlgorithm a2)
+		{
+			// fast path
+			if (a1.KeySize != a2.KeySize)
+				return false;
+			// compare public keys - if they match we can assume the private match too
+			return (a1.ToXmlString (false) == a2.ToXmlString (false));
+		}
+
+		public void AddPkcs8ShroudedKeyBag (AsymmetricAlgorithm aa)
+		{
+			AddPkcs8ShroudedKeyBag (aa, null);
+		}
+
+		public void AddPkcs8ShroudedKeyBag (AsymmetricAlgorithm aa, IDictionary attributes)
+		{
+			bool found = false;
+
+			for (int i = 0; !found && i < _safeBags.Count; i++) {
+				SafeBag sb = (SafeBag)_safeBags [i];
+
+				if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+					ASN1 bagValue = sb.ASN1 [1];
+					PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+					byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+					PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+					byte[] privateKey = pki.PrivateKey;
+
+					AsymmetricAlgorithm saa = null;
+					switch (privateKey [0]) {
+					case 0x02:
+						DSAParameters p = new DSAParameters (); // FIXME
+						saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+						break;
+					case 0x30:
+						saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+						break;
+					default:
+						Array.Clear (decrypted, 0, decrypted.Length);
+						Array.Clear (privateKey, 0, privateKey.Length);
+						throw new CryptographicException ("Unknown private key format");
+					}
+
+					Array.Clear (decrypted, 0, decrypted.Length);
+					Array.Clear (privateKey, 0, privateKey.Length);
+
+					if (CompareAsymmetricAlgorithm (aa , saa)) {
+						found = true;
+					}
+				}
+			}
+
+			if (!found) {
+				_safeBags.Add (new SafeBag (pkcs8ShroudedKeyBag, Pkcs8ShroudedKeyBagSafeBag (aa, attributes)));
+				_keyBagsChanged = true;
+			}
+		}
+
+		public void RemovePkcs8ShroudedKeyBag (AsymmetricAlgorithm aa)
+		{
+			int aaIndex = -1;
+
+			for (int i = 0; aaIndex == -1 && i < _safeBags.Count; i++) {
+				SafeBag sb = (SafeBag)_safeBags [i];
+
+				if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+					ASN1 bagValue = sb.ASN1 [1];
+					PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+					byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+					PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+					byte[] privateKey = pki.PrivateKey;
+
+					AsymmetricAlgorithm saa = null;
+					switch (privateKey [0]) {
+					case 0x02:
+						DSAParameters p = new DSAParameters (); // FIXME
+						saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+						break;
+					case 0x30:
+						saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+						break;
+					default:
+						Array.Clear (decrypted, 0, decrypted.Length);
+						Array.Clear (privateKey, 0, privateKey.Length);
+						throw new CryptographicException ("Unknown private key format");
+					}
+
+					Array.Clear (decrypted, 0, decrypted.Length);
+					Array.Clear (privateKey, 0, privateKey.Length);
+
+					if (CompareAsymmetricAlgorithm (aa, saa)) {
+						aaIndex = i;
+					}
+				}
+			}
+
+			if (aaIndex != -1) {
+				_safeBags.RemoveAt (aaIndex);
+				_keyBagsChanged = true;
+			}
+		}
+
+		public void AddKeyBag (AsymmetricAlgorithm aa)
+		{
+			AddKeyBag (aa, null);
+		}
+
+		public void AddKeyBag (AsymmetricAlgorithm aa, IDictionary attributes)
+		{
+			bool found = false;
+
+			for (int i = 0; !found && i < _safeBags.Count; i++) {
+				SafeBag sb = (SafeBag)_safeBags [i];
+
+				if (sb.BagOID.Equals (keyBag)) {
+					ASN1 bagValue = sb.ASN1 [1];
+					PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+					byte[] privateKey = pki.PrivateKey;
+
+					AsymmetricAlgorithm saa = null;
+					switch (privateKey [0]) {
+					case 0x02:
+						DSAParameters p = new DSAParameters (); // FIXME
+						saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+						break;
+					case 0x30:
+						saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+						break;
+					default:
+						Array.Clear (privateKey, 0, privateKey.Length);
+						throw new CryptographicException ("Unknown private key format");
+					}
+
+					Array.Clear (privateKey, 0, privateKey.Length);
+
+					if (CompareAsymmetricAlgorithm (aa, saa)) {
+						found = true;
+					}
+				}
+			}
+
+			if (!found) {
+				_safeBags.Add (new SafeBag (keyBag, KeyBagSafeBag (aa, attributes)));
+				_keyBagsChanged = true;
+			}
+		}
+
+		public void RemoveKeyBag (AsymmetricAlgorithm aa)
+		{
+			int aaIndex = -1;
+
+			for (int i = 0; aaIndex == -1 && i < _safeBags.Count; i++) {
+				SafeBag sb = (SafeBag)_safeBags [i];
+
+				if (sb.BagOID.Equals (keyBag)) {
+					ASN1 bagValue = sb.ASN1 [1];
+					PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+					byte[] privateKey = pki.PrivateKey;
+
+					AsymmetricAlgorithm saa = null;
+					switch (privateKey [0]) {
+					case 0x02:
+						DSAParameters p = new DSAParameters (); // FIXME
+						saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+						break;
+					case 0x30:
+						saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+						break;
+					default:
+						Array.Clear (privateKey, 0, privateKey.Length);
+						throw new CryptographicException ("Unknown private key format");
+					}
+
+					Array.Clear (privateKey, 0, privateKey.Length);
+
+					if (CompareAsymmetricAlgorithm (aa, saa)) {
+						aaIndex = i;
+					}
+				}
+			}
+
+			if (aaIndex != -1) {
+				_safeBags.RemoveAt (aaIndex);
+				_keyBagsChanged = true;
+			}
+		}
+
+		public void AddSecretBag (byte[] secret)
+		{
+			AddSecretBag (secret, null);
+		}
+
+		public void AddSecretBag (byte[] secret, IDictionary attributes)
+		{
+			bool found = false;
+
+			for (int i = 0; !found && i < _safeBags.Count; i++) {
+				SafeBag sb = (SafeBag)_safeBags [i];
+
+				if (sb.BagOID.Equals (secretBag)) {
+					ASN1 bagValue = sb.ASN1 [1];
+					byte[] ssecret = bagValue.Value;
+
+					if (Compare (secret, ssecret)) {
+						found = true;
+					}
+				}
+			}
+
+			if (!found) {
+				_safeBags.Add (new SafeBag (secretBag, SecretBagSafeBag (secret, attributes)));
+				_secretBagsChanged = true;
+			}
+		}
+
+		public void RemoveSecretBag (byte[] secret)
+		{
+			int sIndex = -1;
+
+			for (int i = 0; sIndex == -1 && i < _safeBags.Count; i++) {
+				SafeBag sb = (SafeBag)_safeBags [i];
+
+				if (sb.BagOID.Equals (secretBag)) {
+					ASN1 bagValue = sb.ASN1 [1];
+					byte[] ssecret = bagValue.Value;
+
+					if (Compare (secret, ssecret)) {
+						sIndex = i;
+					}
+				}
+			}
+
+			if (sIndex != -1) {
+				_safeBags.RemoveAt (sIndex);
+				_secretBagsChanged = true;
+			}
+		}
+
+		public AsymmetricAlgorithm GetAsymmetricAlgorithm (IDictionary attrs)
+		{
+			foreach (SafeBag sb in _safeBags) {
+				if (sb.BagOID.Equals (keyBag) || sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+					ASN1 safeBag = sb.ASN1;
+
+					if (safeBag.Count == 3) {
+						ASN1 bagAttributes = safeBag [2];
+
+						int bagAttributesFound = 0;
+						for (int i = 0; i < bagAttributes.Count; i++) {
+							ASN1 pkcs12Attribute = bagAttributes [i];
+							ASN1 attrId = pkcs12Attribute [0];
+							string ao = ASN1Convert.ToOid (attrId);
+							ArrayList dattrValues = (ArrayList)attrs [ao];
+
+							if (dattrValues != null) {
+								ASN1 attrValues = pkcs12Attribute [1];
+
+								if (dattrValues.Count == attrValues.Count) {
+									int attrValuesFound = 0;
+									for (int j = 0; j < attrValues.Count; j++) {
+										ASN1 attrValue = attrValues [j];
+										byte[] value = (byte[])dattrValues [j];
+									
+										if (Compare (value, attrValue.Value)) {
+											attrValuesFound += 1;
+										}
+									}
+									if (attrValuesFound == attrValues.Count) {
+										bagAttributesFound += 1;
+									}
+								}
+							}
+						}
+						if (bagAttributesFound == bagAttributes.Count) {
+							ASN1 bagValue = safeBag [1];
+							AsymmetricAlgorithm aa = null;
+							if (sb.BagOID.Equals (keyBag)) {
+								PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+								byte[] privateKey = pki.PrivateKey;
+								switch (privateKey [0]) {
+								case 0x02:
+									DSAParameters p = new DSAParameters (); // FIXME
+									aa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+									break;
+								case 0x30:
+									aa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+									break;
+								default:
+									break;
+								}
+								Array.Clear (privateKey, 0, privateKey.Length);
+							} else if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+								PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+								byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+								PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+								byte[] privateKey = pki.PrivateKey;
+								switch (privateKey [0]) {
+								case 0x02:
+									DSAParameters p = new DSAParameters (); // FIXME
+									aa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+									break;
+								case 0x30:
+									aa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+									break;
+								default:
+									break;
+								}
+								Array.Clear (privateKey, 0, privateKey.Length);
+								Array.Clear (decrypted, 0, decrypted.Length);
+							}
+							return aa;
+						}
+					}
+				}
+			}
+
+			return null;
+		}
+
+		public byte[] GetSecret (IDictionary attrs)
+		{
+			foreach (SafeBag sb in _safeBags) {
+				if (sb.BagOID.Equals (secretBag)) {
+					ASN1 safeBag = sb.ASN1;
+
+					if (safeBag.Count == 3) {
+						ASN1 bagAttributes = safeBag [2];
+
+						int bagAttributesFound = 0;
+						for (int i = 0; i < bagAttributes.Count; i++) {
+							ASN1 pkcs12Attribute = bagAttributes [i];
+							ASN1 attrId = pkcs12Attribute [0];
+							string ao = ASN1Convert.ToOid (attrId);
+							ArrayList dattrValues = (ArrayList)attrs [ao];
+
+							if (dattrValues != null) {
+								ASN1 attrValues = pkcs12Attribute [1];
+
+								if (dattrValues.Count == attrValues.Count) {
+									int attrValuesFound = 0;
+									for (int j = 0; j < attrValues.Count; j++) {
+										ASN1 attrValue = attrValues [j];
+										byte[] value = (byte[])dattrValues [j];
+									
+										if (Compare (value, attrValue.Value)) {
+											attrValuesFound += 1;
+										}
+									}
+									if (attrValuesFound == attrValues.Count) {
+										bagAttributesFound += 1;
+									}
+								}
+							}
+						}
+						if (bagAttributesFound == bagAttributes.Count) {
+							ASN1 bagValue = safeBag [1];
+							return bagValue.Value;
+						}
+					}
+				}
+			}
+
+			return null;
+		}
+
+		public X509Certificate GetCertificate (IDictionary attrs)
+		{
+			foreach (SafeBag sb in _safeBags) {
+				if (sb.BagOID.Equals (certBag)) {
+					ASN1 safeBag = sb.ASN1;
+
+					if (safeBag.Count == 3) {
+						ASN1 bagAttributes = safeBag [2];
+
+						int bagAttributesFound = 0;
+						for (int i = 0; i < bagAttributes.Count; i++) {
+							ASN1 pkcs12Attribute = bagAttributes [i];
+							ASN1 attrId = pkcs12Attribute [0];
+							string ao = ASN1Convert.ToOid (attrId);
+							ArrayList dattrValues = (ArrayList)attrs [ao];
+
+							if (dattrValues != null) {
+								ASN1 attrValues = pkcs12Attribute [1];
+								
+								if (dattrValues.Count == attrValues.Count) {
+									int attrValuesFound = 0;
+									for (int j = 0; j < attrValues.Count; j++) {
+										ASN1 attrValue = attrValues [j];
+										byte[] value = (byte[])dattrValues [j];
+									
+										if (Compare (value, attrValue.Value)) {
+											attrValuesFound += 1;
+										}
+									}
+									if (attrValuesFound == attrValues.Count) {
+										bagAttributesFound += 1;
+									}
+								}
+							}
+						}
+						if (bagAttributesFound == bagAttributes.Count) {
+							ASN1 bagValue = safeBag [1];
+							PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+							return new X509Certificate (crt.Content [0].Value);
+						}
+					}
+				}
+			}
+
+			return null;
+		}
+
+		public IDictionary GetAttributes (AsymmetricAlgorithm aa)
+		{
+			IDictionary result = new Hashtable ();
+
+			foreach (SafeBag sb in _safeBags) {
+				if (sb.BagOID.Equals (keyBag) || sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+					ASN1 safeBag = sb.ASN1;
+
+					ASN1 bagValue = safeBag [1];
+					AsymmetricAlgorithm saa = null;
+					if (sb.BagOID.Equals (keyBag)) {
+						PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+						byte[] privateKey = pki.PrivateKey;
+						switch (privateKey [0]) {
+						case 0x02:
+							DSAParameters p = new DSAParameters (); // FIXME
+							saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+							break;
+						case 0x30:
+							saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+							break;
+						default:
+							break;
+						}
+						Array.Clear (privateKey, 0, privateKey.Length);
+					} else if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+						PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+						byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+						PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+						byte[] privateKey = pki.PrivateKey;
+						switch (privateKey [0]) {
+						case 0x02:
+							DSAParameters p = new DSAParameters (); // FIXME
+							saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+							break;
+						case 0x30:
+							saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+							break;
+						default:
+							break;
+						}
+						Array.Clear (privateKey, 0, privateKey.Length);
+						Array.Clear (decrypted, 0, decrypted.Length);
+					}
+
+					if (saa != null && CompareAsymmetricAlgorithm (saa, aa)) {
+						if (safeBag.Count == 3) {
+							ASN1 bagAttributes = safeBag [2];
+							
+							for (int i = 0; i < bagAttributes.Count; i++) {
+								ASN1 pkcs12Attribute = bagAttributes [i];
+								ASN1 attrId = pkcs12Attribute [0];
+								string aOid = ASN1Convert.ToOid (attrId);
+								ArrayList aValues = new ArrayList ();
+
+								ASN1 attrValues = pkcs12Attribute [1];
+									
+								for (int j = 0; j < attrValues.Count; j++) {
+									ASN1 attrValue = attrValues [j];
+									aValues.Add (attrValue.Value);
+								}
+								result.Add (aOid, aValues);
+							}
+						}
+					}
+				}
+			}
+
+			return result;
+		}
+
+		public IDictionary GetAttributes (X509Certificate cert)
+		{
+			IDictionary result = new Hashtable ();
+
+			foreach (SafeBag sb in _safeBags) {
+				if (sb.BagOID.Equals (certBag)) {
+					ASN1 safeBag = sb.ASN1;
+					ASN1 bagValue = safeBag [1];
+					PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+					X509Certificate xc = new X509Certificate (crt.Content [0].Value);
+
+					if (Compare (cert.RawData, xc.RawData)) {
+						if (safeBag.Count == 3) {
+							ASN1 bagAttributes = safeBag [2];
+
+							for (int i = 0; i < bagAttributes.Count; i++) {
+								ASN1 pkcs12Attribute = bagAttributes [i];
+								ASN1 attrId = pkcs12Attribute [0];
+
+								string aOid = ASN1Convert.ToOid (attrId);
+								ArrayList aValues = new ArrayList ();
+
+								ASN1 attrValues = pkcs12Attribute [1];
+									
+								for (int j = 0; j < attrValues.Count; j++) {
+									ASN1 attrValue = attrValues [j];
+									aValues.Add (attrValue.Value);
+								}
+								result.Add (aOid, aValues);
+							}
+						}
+					}
+				}
+			}
+
+			return result;
+		}
+
+		public void SaveToFile (string filename)
+		{
+			if (filename == null)
+				throw new ArgumentNullException ("filename");
+
+			using (FileStream fs = File.Create (filename)) {
+				byte[] data = GetBytes ();
+				fs.Write (data, 0, data.Length);
+			}
+		}
+
+		public object Clone ()
+		{
+			PKCS12 clone = null;
+			if (_password != null) {
+				clone = new PKCS12 (GetBytes (), Encoding.BigEndianUnicode.GetString (_password));
+			} else {
+				clone = new PKCS12 (GetBytes ());
+			}
+			clone.IterationCount = this.IterationCount;
+
+			return clone;
+		}
+
+		// static
+
+		public const int CryptoApiPasswordLimit = 32;
+		
+		static private int password_max_length = Int32.MaxValue;
+
+		// static properties
+		
+		// MS CryptoAPI limits the password to a maximum of 31 characters
+		// other implementations, like OpenSSL, have no such limitation.
+		// Setting a maximum value will truncate the password length to 
+		// ensure compatibility with MS's PFXImportCertStore API.
+		static public int MaximumPasswordLength {
+			get { return password_max_length; }
+			set {
+				if (value < CryptoApiPasswordLimit) {
+					string msg = string.Format ("Maximum password length cannot be less than {0}.", CryptoApiPasswordLimit);
+					throw new ArgumentOutOfRangeException (msg);
+				}
+				password_max_length = value;
+			}
+		}
+
+		// static methods
+
+		static private byte[] LoadFile (string filename) 
+		{
+			byte[] data = null;
+			using (FileStream fs = File.OpenRead (filename)) {
+				data = new byte [fs.Length];
+				fs.Read (data, 0, data.Length);
+				fs.Close ();
+			}
+			return data;
+		}
+
+		static public PKCS12 LoadFromFile (string filename) 
+		{
+			if (filename == null)
+				throw new ArgumentNullException ("filename");
+
+			return new PKCS12 (LoadFile (filename));
+		}
+
+		static public PKCS12 LoadFromFile (string filename, string password) 
+		{
+			if (filename == null)
+				throw new ArgumentNullException ("filename");
+
+			return new PKCS12 (LoadFile (filename), password);
+		}
+	}
+}

+ 1018 - 0
MediaBrowser.Server.Mono/Security/PKCS7.cs

@@ -0,0 +1,1018 @@
+//
+// PKCS7.cs: PKCS #7 - Cryptographic Message Syntax Standard 
+//	http://www.rsasecurity.com/rsalabs/pkcs/pkcs-7/index.html
+//
+// Authors:
+//	Sebastien Pouliot <sebastien@ximian.com>
+//	Daniel Granath <dgranath#gmail.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2005 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.Collections;
+using System.Security.Cryptography;
+
+using Mono.Security.X509;
+
+namespace Mono.Security {
+
+#if INSIDE_CORLIB
+	internal
+#else
+	public
+#endif
+	sealed class PKCS7 {
+
+		public class Oid {
+			// pkcs 1
+			public const string rsaEncryption = "1.2.840.113549.1.1.1";
+			// pkcs 7
+			public const string data = "1.2.840.113549.1.7.1";
+			public const string signedData = "1.2.840.113549.1.7.2";
+			public const string envelopedData = "1.2.840.113549.1.7.3";
+			public const string signedAndEnvelopedData = "1.2.840.113549.1.7.4";
+			public const string digestedData = "1.2.840.113549.1.7.5";
+			public const string encryptedData = "1.2.840.113549.1.7.6";
+			// pkcs 9
+			public const string contentType = "1.2.840.113549.1.9.3";
+			public const string messageDigest  = "1.2.840.113549.1.9.4";
+			public const string signingTime = "1.2.840.113549.1.9.5";
+			public const string countersignature = "1.2.840.113549.1.9.6";
+
+			public Oid () 
+			{
+			}
+		}
+
+		private PKCS7 ()
+		{
+		}
+
+		static public ASN1 Attribute (string oid, ASN1 value) 
+		{
+			ASN1 attr = new ASN1 (0x30);
+			attr.Add (ASN1Convert.FromOid (oid));
+			ASN1 aset = attr.Add (new ASN1 (0x31));
+			aset.Add (value);
+			return attr;
+		}
+
+		static public ASN1 AlgorithmIdentifier (string oid)
+		{
+			ASN1 ai = new ASN1 (0x30);
+			ai.Add (ASN1Convert.FromOid (oid));
+			ai.Add (new ASN1 (0x05));	// NULL
+			return ai;
+		}
+
+		static public ASN1 AlgorithmIdentifier (string oid, ASN1 parameters) 
+		{
+			ASN1 ai = new ASN1 (0x30);
+			ai.Add (ASN1Convert.FromOid (oid));
+			ai.Add (parameters);
+			return ai;
+		}
+
+		/*
+		 * IssuerAndSerialNumber ::= SEQUENCE {
+		 *	issuer Name,
+		 *	serialNumber CertificateSerialNumber 
+		 * }
+		 */
+		static public ASN1 IssuerAndSerialNumber (X509Certificate x509) 
+		{
+			ASN1 issuer = null;
+			ASN1 serial = null;
+			ASN1 cert = new ASN1 (x509.RawData);
+			int tbs = 0;
+			bool flag = false;
+			while (tbs < cert[0].Count) {
+				ASN1 e = cert[0][tbs++];
+				if (e.Tag == 0x02)
+					serial = e;
+				else if (e.Tag == 0x30) {
+					if (flag) {
+						issuer = e;
+						break;
+					}
+					flag = true;
+				}
+			}
+			ASN1 iasn = new ASN1 (0x30);
+			iasn.Add (issuer);
+			iasn.Add (serial);
+			return iasn;
+		}
+
+		/*
+		 * ContentInfo ::= SEQUENCE {
+		 *	contentType ContentType,
+		 *	content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL 
+		 * }
+		 * ContentType ::= OBJECT IDENTIFIER
+		 */
+		public class ContentInfo {
+
+			private string contentType;
+			private ASN1 content;
+
+			public ContentInfo () 
+			{
+				content = new ASN1 (0xA0);
+			}
+
+			public ContentInfo (string oid) : this ()
+			{
+				contentType = oid;
+			}
+
+			public ContentInfo (byte[] data) 
+				: this (new ASN1 (data)) {}
+
+			public ContentInfo (ASN1 asn1) 
+			{
+				// SEQUENCE with 1 or 2 elements
+				if ((asn1.Tag != 0x30) || ((asn1.Count < 1) && (asn1.Count > 2)))
+					throw new ArgumentException ("Invalid ASN1");
+				if (asn1[0].Tag != 0x06)
+					throw new ArgumentException ("Invalid contentType");
+				contentType = ASN1Convert.ToOid (asn1[0]);
+				if (asn1.Count > 1) {
+					if (asn1[1].Tag != 0xA0)
+						throw new ArgumentException ("Invalid content");
+					content = asn1[1];
+				}
+			}
+
+			public ASN1 ASN1 {
+				get { return GetASN1(); }
+			}
+
+			public ASN1 Content {
+				get { return content; }
+				set { content = value; }
+			}
+
+			public string ContentType {
+				get { return contentType; }
+				set { contentType = value; }
+			}
+
+			internal ASN1 GetASN1 () 
+			{
+				// ContentInfo ::= SEQUENCE {
+				ASN1 contentInfo = new ASN1 (0x30);
+				// contentType ContentType, -> ContentType ::= OBJECT IDENTIFIER
+				contentInfo.Add (ASN1Convert.FromOid (contentType));
+				// content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL 
+				if ((content != null) && (content.Count > 0))
+					contentInfo.Add (content);
+				return contentInfo;
+			}
+
+			public byte[] GetBytes () 
+			{
+				return GetASN1 ().GetBytes ();
+			}
+		}
+
+		/*
+		 * EncryptedData ::= SEQUENCE {
+		 *	version		INTEGER {edVer0(0)} (edVer0),
+		 *	 encryptedContentInfo  EncryptedContentInfo
+		 * }
+		 */
+		public class EncryptedData {
+			private byte _version;
+			private ContentInfo _content;
+			private ContentInfo _encryptionAlgorithm;
+			private byte[] _encrypted;
+
+			public EncryptedData () 
+			{
+				_version = 0;
+			}
+
+			public EncryptedData (byte[] data) 
+				: this (new ASN1 (data))
+			{
+			}
+
+			public EncryptedData (ASN1 asn1) : this () 
+			{
+				if ((asn1.Tag != 0x30) || (asn1.Count < 2))
+					throw new ArgumentException ("Invalid EncryptedData");
+
+				if (asn1 [0].Tag != 0x02)
+					throw new ArgumentException ("Invalid version");
+				_version = asn1 [0].Value [0];
+
+				ASN1 encryptedContentInfo = asn1 [1];
+				if (encryptedContentInfo.Tag != 0x30)
+					throw new ArgumentException ("missing EncryptedContentInfo");
+
+				ASN1 contentType = encryptedContentInfo [0];
+				if (contentType.Tag != 0x06)
+					throw new ArgumentException ("missing EncryptedContentInfo.ContentType");
+				_content = new ContentInfo (ASN1Convert.ToOid (contentType));
+
+				ASN1 contentEncryptionAlgorithm = encryptedContentInfo [1];
+				if (contentEncryptionAlgorithm.Tag != 0x30)
+					throw new ArgumentException ("missing EncryptedContentInfo.ContentEncryptionAlgorithmIdentifier");
+				_encryptionAlgorithm = new ContentInfo (ASN1Convert.ToOid (contentEncryptionAlgorithm [0]));
+				_encryptionAlgorithm.Content = contentEncryptionAlgorithm [1];
+				
+				ASN1 encryptedContent = encryptedContentInfo [2];
+				if (encryptedContent.Tag != 0x80)
+					throw new ArgumentException ("missing EncryptedContentInfo.EncryptedContent");
+				_encrypted = encryptedContent.Value;
+			}
+
+			public ASN1 ASN1 {
+				get { return GetASN1(); }
+			}
+
+			public ContentInfo ContentInfo {
+				get { return _content; }
+			}
+
+			public ContentInfo EncryptionAlgorithm {
+				get { return _encryptionAlgorithm; }
+			}
+
+			public byte[] EncryptedContent {
+				get {
+					if (_encrypted == null)
+						return null;
+					return (byte[]) _encrypted.Clone ();
+				}
+			}
+
+			public byte Version {
+				get { return _version; }
+				set { _version = value; }
+			}
+
+			// methods
+
+			internal ASN1 GetASN1 () 
+			{
+				return null;
+			}
+
+			public byte[] GetBytes () 
+			{
+				return GetASN1 ().GetBytes ();
+			}
+		}
+
+		/*
+		 * EnvelopedData ::= SEQUENCE {
+		 *	version Version,
+		 *	recipientInfos RecipientInfos,
+		 *	encryptedContentInfo EncryptedContentInfo 
+		 * }
+		 * 
+		 * RecipientInfos ::= SET OF RecipientInfo
+		 * 
+		 * EncryptedContentInfo ::= SEQUENCE {
+		 *	contentType ContentType,
+		 *	contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
+		 *	encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL 
+		 * }
+		 * 
+		 * EncryptedContent ::= OCTET STRING
+		 * 
+		 */
+		public class EnvelopedData {
+			private byte _version;
+			private ContentInfo _content;
+			private ContentInfo _encryptionAlgorithm;
+			private ArrayList _recipientInfos;
+			private byte[] _encrypted;
+
+			public EnvelopedData () 
+			{
+				_version = 0;
+				_content = new ContentInfo ();
+				_encryptionAlgorithm = new ContentInfo ();
+				_recipientInfos = new ArrayList ();
+			}
+
+			public EnvelopedData (byte[] data) 
+				: this (new ASN1 (data))
+			{
+			}
+
+			public EnvelopedData (ASN1 asn1) : this ()
+			{
+				if ((asn1[0].Tag != 0x30) || (asn1[0].Count < 3))
+					throw new ArgumentException ("Invalid EnvelopedData");
+
+				if (asn1[0][0].Tag != 0x02)
+					throw new ArgumentException ("Invalid version");
+				_version = asn1[0][0].Value[0];
+
+				// recipientInfos
+
+				ASN1 recipientInfos = asn1 [0][1];
+				if (recipientInfos.Tag != 0x31)
+					throw new ArgumentException ("missing RecipientInfos");
+				for (int i=0; i < recipientInfos.Count; i++) {
+					ASN1 recipientInfo = recipientInfos [i];
+					_recipientInfos.Add (new RecipientInfo (recipientInfo));
+				}
+
+				ASN1 encryptedContentInfo = asn1[0][2];
+				if (encryptedContentInfo.Tag != 0x30)
+					throw new ArgumentException ("missing EncryptedContentInfo");
+
+				ASN1 contentType = encryptedContentInfo [0];
+				if (contentType.Tag != 0x06)
+					throw new ArgumentException ("missing EncryptedContentInfo.ContentType");
+				_content = new ContentInfo (ASN1Convert.ToOid (contentType));
+
+				ASN1 contentEncryptionAlgorithm = encryptedContentInfo [1];
+				if (contentEncryptionAlgorithm.Tag != 0x30)
+					throw new ArgumentException ("missing EncryptedContentInfo.ContentEncryptionAlgorithmIdentifier");
+				_encryptionAlgorithm = new ContentInfo (ASN1Convert.ToOid (contentEncryptionAlgorithm [0]));
+				_encryptionAlgorithm.Content = contentEncryptionAlgorithm [1];
+				
+				ASN1 encryptedContent = encryptedContentInfo [2];
+				if (encryptedContent.Tag != 0x80)
+					throw new ArgumentException ("missing EncryptedContentInfo.EncryptedContent");
+				_encrypted = encryptedContent.Value;
+			}
+
+			public ArrayList RecipientInfos {
+				  get { return _recipientInfos; }
+			}
+
+			public ASN1 ASN1 {
+				get { return GetASN1(); }
+			}
+
+			public ContentInfo ContentInfo {
+				get { return _content; }
+			}
+
+			public ContentInfo EncryptionAlgorithm {
+				get { return _encryptionAlgorithm; }
+			}
+
+			public byte[] EncryptedContent {
+				get { 
+					if (_encrypted == null)
+						return null;
+					return (byte[]) _encrypted.Clone ();
+				}
+			}
+
+			public byte Version {
+				get { return _version; }
+				set { _version = value; }
+			}
+
+			internal ASN1 GetASN1 () 
+			{
+				// SignedData ::= SEQUENCE {
+				ASN1 signedData = new ASN1 (0x30);
+				// version Version -> Version ::= INTEGER
+/*				byte[] ver = { _version };
+				signedData.Add (new ASN1 (0x02, ver));
+				// digestAlgorithms DigestAlgorithmIdentifiers -> DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
+				ASN1 digestAlgorithms = signedData.Add (new ASN1 (0x31));
+				if (hashAlgorithm != null) {
+					string hashOid = CryptoConfig.MapNameToOid (hashAlgorithm);
+					digestAlgorithms.Add (AlgorithmIdentifier (hashOid));
+				}
+
+				// contentInfo ContentInfo,
+				ASN1 ci = contentInfo.ASN1;
+				signedData.Add (ci);
+				if ((mda == null) && (hashAlgorithm != null)) {
+					// automatically add the messageDigest authenticated attribute
+					HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+					byte[] idcHash = ha.ComputeHash (ci[1][0].Value);
+					ASN1 md = new ASN1 (0x30);
+					mda = Attribute (messageDigest, md.Add (new ASN1 (0x04, idcHash)));
+					signerInfo.AuthenticatedAttributes.Add (mda);
+				}
+
+				// certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
+				if (certs.Count > 0) {
+					ASN1 a0 = signedData.Add (new ASN1 (0xA0));
+					foreach (X509Certificate x in certs)
+						a0.Add (new ASN1 (x.RawData));
+				}
+				// crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+				if (crls.Count > 0) {
+					ASN1 a1 = signedData.Add (new ASN1 (0xA1));
+					foreach (byte[] crl in crls)
+						a1.Add (new ASN1 (crl));
+				}
+				// signerInfos SignerInfos -> SignerInfos ::= SET OF SignerInfo
+				ASN1 signerInfos = signedData.Add (new ASN1 (0x31));
+				if (signerInfo.Key != null)
+					signerInfos.Add (signerInfo.ASN1);*/
+				return signedData;
+			}
+
+			public byte[] GetBytes () {
+				return GetASN1 ().GetBytes ();
+			}
+		}
+
+		/* RecipientInfo ::= SEQUENCE {
+		 *	version Version,
+		 *	issuerAndSerialNumber IssuerAndSerialNumber,
+		 *	keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+		 *	encryptedKey EncryptedKey 
+		 * }
+		 * 
+		 * KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+		 * 
+		 * EncryptedKey ::= OCTET STRING
+		 */
+		public class RecipientInfo {
+
+			private int _version;
+			private string _oid;
+			private byte[] _key;
+			private byte[] _ski;
+			private string _issuer;
+			private byte[] _serial;
+
+			public RecipientInfo () {}
+
+			public RecipientInfo (ASN1 data) 
+			{
+				if (data.Tag != 0x30)
+					throw new ArgumentException ("Invalid RecipientInfo");
+				
+				ASN1 version = data [0];
+				if (version.Tag != 0x02)
+					throw new ArgumentException ("missing Version");
+				_version = version.Value [0];
+
+				// issuerAndSerialNumber IssuerAndSerialNumber
+				ASN1 subjectIdentifierType = data [1];
+				if ((subjectIdentifierType.Tag == 0x80) && (_version == 3)) {
+					_ski = subjectIdentifierType.Value;
+				}
+				else {
+					_issuer = X501.ToString (subjectIdentifierType [0]);
+					_serial = subjectIdentifierType [1].Value;
+				}
+
+				ASN1 keyEncryptionAlgorithm = data [2];
+				_oid = ASN1Convert.ToOid (keyEncryptionAlgorithm [0]);
+
+				ASN1 encryptedKey = data [3];
+				_key = encryptedKey.Value;
+			}
+
+			public string Oid {
+				get { return _oid; }
+			}
+
+			public byte[] Key {
+				get { 
+					if (_key == null)
+						return null;
+                                        return (byte[]) _key.Clone ();
+				}
+			}
+
+			public byte[] SubjectKeyIdentifier {
+				get { 
+					if (_ski == null)
+						return null;
+					return (byte[]) _ski.Clone ();
+				}
+			}
+
+			public string Issuer {
+				get { return _issuer; }
+			}
+
+			public byte[] Serial {
+				get { 
+					if (_serial == null)
+						return null;
+					return (byte[]) _serial.Clone ();
+				}
+			}
+
+			public int Version {
+				get { return _version; }
+			}
+		}
+
+		/*
+		 * SignedData ::= SEQUENCE {
+		 *	version Version,
+		 *	digestAlgorithms DigestAlgorithmIdentifiers,
+		 *	contentInfo ContentInfo,
+		 *	certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
+		 *	crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+		 *	signerInfos SignerInfos 
+		 * }
+		 */
+		public class SignedData {
+			private byte version;
+			private string hashAlgorithm;
+			private ContentInfo contentInfo;
+			private X509CertificateCollection certs;
+			private ArrayList crls;
+			private SignerInfo signerInfo;
+			private bool mda;
+			private bool signed;
+
+			public SignedData () 
+			{
+				version = 1;
+				contentInfo = new ContentInfo ();
+				certs = new X509CertificateCollection ();
+				crls = new ArrayList ();
+				signerInfo = new SignerInfo ();
+				mda = true;
+				signed = false;
+			}
+
+			public SignedData (byte[] data) 
+				: this (new ASN1 (data)) 
+			{
+			}
+
+			public SignedData (ASN1 asn1) 
+			{
+				if ((asn1[0].Tag != 0x30) || (asn1[0].Count < 4))
+					throw new ArgumentException ("Invalid SignedData");
+
+				if (asn1[0][0].Tag != 0x02)
+					throw new ArgumentException ("Invalid version");
+				version = asn1[0][0].Value[0];
+
+				contentInfo = new ContentInfo (asn1[0][2]);
+
+				int n = 3;
+				certs = new X509CertificateCollection ();
+				if (asn1[0][n].Tag == 0xA0) {
+					for (int i=0; i < asn1[0][n].Count; i++)
+						certs.Add (new X509Certificate (asn1[0][n][i].GetBytes ()));
+					n++;
+				}
+
+				crls = new ArrayList ();
+				if (asn1[0][n].Tag == 0xA1) {
+					for (int i=0; i < asn1[0][n].Count; i++)
+						crls.Add (asn1[0][n][i].GetBytes ());
+					n++;
+				}
+
+				if (asn1[0][n].Count > 0)
+					signerInfo = new SignerInfo (asn1[0][n]);
+				else
+					signerInfo = new SignerInfo ();
+
+				// Exchange hash algorithm Oid from SignerInfo
+				if (signerInfo.HashName != null) {
+					HashName = OidToName(signerInfo.HashName);
+				}
+				
+				// Check if SignerInfo has authenticated attributes
+				mda = (signerInfo.AuthenticatedAttributes.Count > 0);
+			}
+
+			public ASN1 ASN1 {
+				get { return GetASN1(); }
+			}
+
+			public X509CertificateCollection Certificates {
+				get { return certs; }
+			}
+
+			public ContentInfo ContentInfo {
+				get { return contentInfo; }
+			}
+
+			public ArrayList Crls {
+				get { return crls; }
+			}
+
+			public string HashName {
+				get { return hashAlgorithm; }
+				// todo add validation
+				set { 
+					hashAlgorithm = value; 
+					signerInfo.HashName = value;
+				}
+			}
+
+			public SignerInfo SignerInfo {
+				get { return signerInfo; }
+			}
+
+			public byte Version {
+				get { return version; }
+				set { version = value; }
+			}
+
+			public bool UseAuthenticatedAttributes {
+				get { return mda; }
+				set { mda = value; }
+			}
+
+			public bool VerifySignature (AsymmetricAlgorithm aa)
+			{
+				if (aa == null) {
+					return false;
+				}
+
+				RSAPKCS1SignatureDeformatter r = new RSAPKCS1SignatureDeformatter (aa);
+				r.SetHashAlgorithm (hashAlgorithm);
+				HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+
+				byte[] signature = signerInfo.Signature;
+				byte[] hash = null;
+
+				if (mda) {
+					ASN1 asn = new ASN1 (0x31);
+					foreach (ASN1 attr in signerInfo.AuthenticatedAttributes)
+						asn.Add (attr);
+
+					hash = ha.ComputeHash (asn.GetBytes ());
+				} else {
+					hash = ha.ComputeHash (contentInfo.Content[0].Value);
+				}
+
+				if (hash != null && signature != null) {
+					return r.VerifySignature (hash, signature);
+				}
+				return false;
+			}
+
+			internal string OidToName (string oid)
+			{
+				switch (oid) {
+				case "1.3.14.3.2.26" :
+					return "SHA1";
+				case "1.2.840.113549.2.2" :
+					return "MD2";
+				case "1.2.840.113549.2.5" :
+					return "MD5";
+				case "2.16.840.1.101.3.4.1" :
+					return "SHA256";
+				case "2.16.840.1.101.3.4.2" :
+					return "SHA384";
+				case "2.16.840.1.101.3.4.3" :
+					return "SHA512";
+				default :
+					break;
+				}
+				// Unknown Oid
+				return oid;
+			}
+
+			internal ASN1 GetASN1 () 
+			{
+				// SignedData ::= SEQUENCE {
+				ASN1 signedData = new ASN1 (0x30);
+				// version Version -> Version ::= INTEGER
+				byte[] ver = { version };
+				signedData.Add (new ASN1 (0x02, ver));
+				// digestAlgorithms DigestAlgorithmIdentifiers -> DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
+				ASN1 digestAlgorithms = signedData.Add (new ASN1 (0x31));
+				if (hashAlgorithm != null) {
+					string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
+					digestAlgorithms.Add (AlgorithmIdentifier (hashOid));
+				}
+
+				// contentInfo ContentInfo,
+				ASN1 ci = contentInfo.ASN1;
+				signedData.Add (ci);
+				if (!signed && (hashAlgorithm != null)) {
+					if (mda) {
+						// Use authenticated attributes for signature
+						
+						// Automatically add the contentType authenticated attribute
+						ASN1 ctattr = Attribute (Oid.contentType, ci[0]);
+						signerInfo.AuthenticatedAttributes.Add (ctattr);
+						
+						// Automatically add the messageDigest authenticated attribute
+						HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+						byte[] idcHash = ha.ComputeHash (ci[1][0].Value);
+						ASN1 md = new ASN1 (0x30);
+						ASN1 mdattr = Attribute (Oid.messageDigest, md.Add (new ASN1 (0x04, idcHash)));
+						signerInfo.AuthenticatedAttributes.Add (mdattr);
+					} else {
+						// Don't use authenticated attributes for signature -- signature is content
+						RSAPKCS1SignatureFormatter r = new RSAPKCS1SignatureFormatter (signerInfo.Key);
+						r.SetHashAlgorithm (hashAlgorithm);
+						HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+						byte[] sig = ha.ComputeHash (ci[1][0].Value);
+						signerInfo.Signature = r.CreateSignature (sig);
+					}
+					signed = true;
+				}
+
+				// certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
+				if (certs.Count > 0) {
+					ASN1 a0 = signedData.Add (new ASN1 (0xA0));
+					foreach (X509Certificate x in certs)
+						a0.Add (new ASN1 (x.RawData));
+				}
+				// crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+				if (crls.Count > 0) {
+					ASN1 a1 = signedData.Add (new ASN1 (0xA1));
+					foreach (byte[] crl in crls)
+						a1.Add (new ASN1 (crl));
+				}
+				// signerInfos SignerInfos -> SignerInfos ::= SET OF SignerInfo
+				ASN1 signerInfos = signedData.Add (new ASN1 (0x31));
+				if (signerInfo.Key != null)
+					signerInfos.Add (signerInfo.ASN1);
+				return signedData;
+			}
+
+			public byte[] GetBytes () 
+			{
+				return GetASN1 ().GetBytes ();
+			}
+		}
+
+		/*
+		 * SignerInfo ::= SEQUENCE {
+		 *	version Version,
+		 * 	issuerAndSerialNumber IssuerAndSerialNumber,
+		 * 	digestAlgorithm DigestAlgorithmIdentifier,
+		 * 	authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
+		 * 	digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
+		 * 	encryptedDigest EncryptedDigest,
+		 * 	unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL 
+		 * }
+		 * 
+		 * For version == 3 issuerAndSerialNumber may be replaced by ...
+		 */
+		public class SignerInfo {
+
+			private byte version;
+			private X509Certificate x509;
+			private string hashAlgorithm;
+			private AsymmetricAlgorithm key;
+			private ArrayList authenticatedAttributes;
+			private ArrayList unauthenticatedAttributes;
+			private byte[] signature;
+			private string issuer;
+			private byte[] serial;
+			private byte[] ski;
+
+			public SignerInfo () 
+			{
+				version = 1;
+				authenticatedAttributes = new ArrayList ();
+				unauthenticatedAttributes = new ArrayList ();
+			}
+
+			public SignerInfo (byte[] data) 
+				: this (new ASN1 (data)) {}
+
+			// TODO: INCOMPLETE
+			public SignerInfo (ASN1 asn1) : this () 
+			{
+				if ((asn1[0].Tag != 0x30) || (asn1[0].Count < 5))
+					throw new ArgumentException ("Invalid SignedData");
+
+				// version Version
+				if (asn1[0][0].Tag != 0x02)
+					throw new ArgumentException ("Invalid version");
+				version = asn1[0][0].Value[0];
+
+				// issuerAndSerialNumber IssuerAndSerialNumber
+				ASN1 subjectIdentifierType = asn1 [0][1];
+				if ((subjectIdentifierType.Tag == 0x80) && (version == 3)) {
+					ski = subjectIdentifierType.Value;
+				}
+				else {
+					issuer = X501.ToString (subjectIdentifierType [0]);
+					serial = subjectIdentifierType [1].Value;
+				}
+
+				// digestAlgorithm DigestAlgorithmIdentifier
+				ASN1 digestAlgorithm = asn1 [0][2];
+				hashAlgorithm = ASN1Convert.ToOid (digestAlgorithm [0]);
+
+				// authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL
+				int n = 3;
+				ASN1 authAttributes = asn1 [0][n];
+				if (authAttributes.Tag == 0xA0) {
+					n++;
+					for (int i=0; i < authAttributes.Count; i++)
+						authenticatedAttributes.Add (authAttributes [i]);
+				}
+
+				// digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier
+				n++;
+				// ASN1 digestEncryptionAlgorithm = asn1 [0][n++];
+				// string digestEncryptionAlgorithmOid = ASN1Convert.ToOid (digestEncryptionAlgorithm [0]);
+
+				// encryptedDigest EncryptedDigest
+				ASN1 encryptedDigest = asn1 [0][n++];
+				if (encryptedDigest.Tag == 0x04)
+					signature = encryptedDigest.Value;
+
+				// unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
+				ASN1 unauthAttributes = asn1 [0][n];
+				if ((unauthAttributes != null) && (unauthAttributes.Tag == 0xA1)) {
+					for (int i=0; i < unauthAttributes.Count; i++)
+						unauthenticatedAttributes.Add (unauthAttributes [i]);
+				}
+			}
+
+			public string IssuerName {
+				get { return issuer; }
+			}
+
+			public byte[] SerialNumber {
+				get { 
+					if (serial == null)
+						return null;
+					return (byte[]) serial.Clone (); 
+				}
+			}
+
+			public byte[] SubjectKeyIdentifier {
+				get { 
+					if (ski == null)
+						return null;
+					return (byte[]) ski.Clone (); 
+				}
+			}
+
+			public ASN1 ASN1 {
+				get { return GetASN1(); }
+			}
+
+			public ArrayList AuthenticatedAttributes {
+				get { return authenticatedAttributes; }
+			}
+
+			public X509Certificate Certificate {
+				get { return x509; }
+				set { x509 = value; }
+			}
+
+			public string HashName {
+				get { return hashAlgorithm; }
+				set { hashAlgorithm = value; }
+			}
+
+			public AsymmetricAlgorithm Key {
+				get { return key; }
+				set { key = value; }
+			}
+
+			public byte[] Signature {
+				get { 
+					if (signature == null)
+						return null;
+					return (byte[]) signature.Clone (); 
+				}
+
+				set {
+					if (value != null) {
+						signature = (byte[]) value.Clone ();
+					}
+				}
+			}
+
+			public ArrayList UnauthenticatedAttributes {
+				get { return unauthenticatedAttributes; }
+			}
+
+			public byte Version {
+				get { return version; }
+				set { version = value; }
+			}
+
+			internal ASN1 GetASN1 () 
+			{
+				if ((key == null) || (hashAlgorithm == null))
+					return null;
+				byte[] ver = { version };
+				ASN1 signerInfo = new ASN1 (0x30);
+				// version Version -> Version ::= INTEGER
+				signerInfo.Add (new ASN1 (0x02, ver));
+				// issuerAndSerialNumber IssuerAndSerialNumber,
+				signerInfo.Add (PKCS7.IssuerAndSerialNumber (x509));
+				// digestAlgorithm DigestAlgorithmIdentifier,
+				string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
+				signerInfo.Add (AlgorithmIdentifier (hashOid));
+				// authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
+				ASN1 aa = null;
+				if (authenticatedAttributes.Count > 0) {
+					aa = signerInfo.Add (new ASN1 (0xA0));
+					authenticatedAttributes.Sort(new SortedSet ());
+					foreach (ASN1 attr in authenticatedAttributes)
+						aa.Add (attr);
+				}
+				// digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
+				if (key is RSA) {
+					signerInfo.Add (AlgorithmIdentifier (PKCS7.Oid.rsaEncryption));
+
+					if (aa != null) {
+						// Calculate the signature here; otherwise it must be set from SignedData
+						RSAPKCS1SignatureFormatter r = new RSAPKCS1SignatureFormatter (key);
+						r.SetHashAlgorithm (hashAlgorithm);
+						byte[] tbs = aa.GetBytes ();
+						tbs [0] = 0x31; // not 0xA0 for signature
+						HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+						byte[] tbsHash = ha.ComputeHash (tbs);
+						signature = r.CreateSignature (tbsHash);
+					}
+				}
+				else if (key is DSA) {
+					throw new NotImplementedException ("not yet");
+				}
+				else
+					throw new CryptographicException ("Unknown assymetric algorithm");
+				// encryptedDigest EncryptedDigest,
+				signerInfo.Add (new ASN1 (0x04, signature));
+				// unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL 
+				if (unauthenticatedAttributes.Count > 0) {
+					ASN1 ua = signerInfo.Add (new ASN1 (0xA1));
+					unauthenticatedAttributes.Sort(new SortedSet ());
+					foreach (ASN1 attr in unauthenticatedAttributes)
+						ua.Add (attr);
+				}
+				return signerInfo;
+			}
+
+			public byte[] GetBytes () 
+			{
+				return GetASN1 ().GetBytes ();
+			}
+		}
+
+		internal class SortedSet : IComparer {
+
+			public int Compare (object x, object y)
+			{
+				if (x == null)
+					return (y == null) ? 0 : -1;
+				else if (y == null)
+					return 1;
+
+				ASN1 xx = x as ASN1;
+				ASN1 yy = y as ASN1;
+				
+				if ((xx == null) || (yy == null)) {
+					throw new ArgumentException (("Invalid objects."));
+				}
+
+				byte[] xb = xx.GetBytes ();
+				byte[] yb = yy.GetBytes ();
+
+				for (int i = 0; i < xb.Length; i++) {
+					if (i == yb.Length)
+						break;
+
+					if (xb[i] == yb[i]) 
+						continue;
+						
+					return (xb[i] < yb[i]) ? -1 : 1; 
+				}
+
+				// The arrays are equal up to the shortest of them.
+				if (xb.Length > yb.Length)
+					return 1;
+				else if (xb.Length < yb.Length)
+					return -1;
+
+				return 0;
+			}
+		}
+	}
+}

+ 505 - 0
MediaBrowser.Server.Mono/Security/PKCS8.cs

@@ -0,0 +1,505 @@
+//
+// PKCS8.cs: PKCS #8 - Private-Key Information Syntax Standard
+//	ftp://ftp.rsasecurity.com/pub/pkcs/doc/pkcs-8.doc
+//
+// Author:
+//	Sebastien Pouliot <sebastien@xamarin.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.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.Collections;
+using System.Security.Cryptography;
+
+using Mono.Security.X509;
+
+namespace Mono.Security.Cryptography {
+
+#if !INSIDE_CORLIB
+	public 
+#endif
+	sealed class PKCS8 {
+
+		public enum KeyInfo {
+			PrivateKey,
+			EncryptedPrivateKey,
+			Unknown
+		}
+
+		private PKCS8 () 
+		{
+		}
+
+		static public KeyInfo GetType (byte[] data) 
+		{
+			if (data == null)
+				throw new ArgumentNullException ("data");
+
+			KeyInfo ki = KeyInfo.Unknown;
+			try {
+				ASN1 top = new ASN1 (data);
+				if ((top.Tag == 0x30) && (top.Count > 0)) {
+					ASN1 firstLevel = top [0];
+					switch (firstLevel.Tag) {
+						case 0x02:
+							ki = KeyInfo.PrivateKey;
+							break;
+						case 0x30:
+							ki = KeyInfo.EncryptedPrivateKey;
+							break;
+					}
+				}
+			}
+			catch {
+				throw new CryptographicException ("invalid ASN.1 data");
+			}
+			return ki;
+		}
+
+		/*
+		 * PrivateKeyInfo ::= SEQUENCE {
+		 *	version Version,
+		 *	privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+		 *	privateKey PrivateKey,
+		 *	attributes [0] IMPLICIT Attributes OPTIONAL 
+		 * }
+		 * 
+		 * Version ::= INTEGER
+		 * 
+		 * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+		 * 
+		 * PrivateKey ::= OCTET STRING
+		 * 
+		 * Attributes ::= SET OF Attribute
+		 */
+		public class PrivateKeyInfo {
+
+			private int _version;
+			private string _algorithm;
+			private byte[] _key;
+			private ArrayList _list;
+
+			public PrivateKeyInfo () 
+			{
+				_version = 0;
+				_list = new ArrayList ();
+			}
+
+			public PrivateKeyInfo (byte[] data) : this () 
+			{
+				Decode (data);
+			}
+
+			// properties
+
+			public string Algorithm {
+				get { return _algorithm; }
+				set { _algorithm = value; }
+			}
+
+			public ArrayList Attributes {
+				get { return _list; }
+			}
+
+			public byte[] PrivateKey {
+				get {
+					if (_key == null)
+						return null;
+					return (byte[]) _key.Clone (); 
+				}
+				set { 
+					if (value == null)
+						throw new ArgumentNullException ("PrivateKey");
+					_key = (byte[]) value.Clone (); 
+				}
+			}
+
+			public int Version {
+				get { return _version; }
+				set { 
+					if (value < 0)
+						throw new ArgumentOutOfRangeException ("negative version");
+					_version = value; 
+				}
+			}
+
+			// methods
+
+			private void Decode (byte[] data) 
+			{
+				ASN1 privateKeyInfo = new ASN1 (data);
+				if (privateKeyInfo.Tag != 0x30)
+					throw new CryptographicException ("invalid PrivateKeyInfo");
+
+				ASN1 version = privateKeyInfo [0];
+				if (version.Tag != 0x02)
+					throw new CryptographicException ("invalid version");
+				_version = version.Value [0];
+
+				ASN1 privateKeyAlgorithm = privateKeyInfo [1];
+				if (privateKeyAlgorithm.Tag != 0x30)
+					throw new CryptographicException ("invalid algorithm");
+				
+				ASN1 algorithm = privateKeyAlgorithm [0];
+				if (algorithm.Tag != 0x06)
+					throw new CryptographicException ("missing algorithm OID");
+				_algorithm = ASN1Convert.ToOid (algorithm);
+
+				ASN1 privateKey = privateKeyInfo [2];
+				_key = privateKey.Value;
+
+				// attributes [0] IMPLICIT Attributes OPTIONAL
+				if (privateKeyInfo.Count > 3) {
+					ASN1 attributes = privateKeyInfo [3];
+					for (int i=0; i < attributes.Count; i++) {
+						_list.Add (attributes [i]);
+					}
+				}
+			}
+
+			public byte[] GetBytes () 
+			{
+				ASN1 privateKeyAlgorithm = new ASN1 (0x30);
+				privateKeyAlgorithm.Add (ASN1Convert.FromOid (_algorithm));
+				privateKeyAlgorithm.Add (new ASN1 (0x05)); // ASN.1 NULL
+
+				ASN1 pki = new ASN1 (0x30);
+				pki.Add (new ASN1 (0x02, new byte [1] { (byte) _version }));
+				pki.Add (privateKeyAlgorithm);
+				pki.Add (new ASN1 (0x04, _key));
+
+				if (_list.Count > 0) {
+					ASN1 attributes = new ASN1 (0xA0);
+					foreach (ASN1 attribute in _list) {
+						attributes.Add (attribute);
+					}
+					pki.Add (attributes);
+				}
+
+				return pki.GetBytes ();
+			}
+
+			// static methods
+
+			static private byte[] RemoveLeadingZero (byte[] bigInt) 
+			{
+				int start = 0;
+				int length = bigInt.Length;
+				if (bigInt [0] == 0x00) {
+					start = 1;
+					length--;
+				}
+				byte[] bi = new byte [length];
+				Buffer.BlockCopy (bigInt, start, bi, 0, length);
+				return bi;
+			}
+
+			static private byte[] Normalize (byte[] bigInt, int length) 
+			{
+				if (bigInt.Length == length)
+					return bigInt;
+				else if (bigInt.Length > length)
+					return RemoveLeadingZero (bigInt);
+				else {
+					// pad with 0
+					byte[] bi = new byte [length];
+					Buffer.BlockCopy (bigInt, 0, bi, (length - bigInt.Length), bigInt.Length);
+					return bi;
+				}
+			}
+			
+			/*
+			 * RSAPrivateKey ::= SEQUENCE {
+			 *	version           Version, 
+			 *	modulus           INTEGER,  -- n
+			 *	publicExponent    INTEGER,  -- e
+			 *	privateExponent   INTEGER,  -- d
+			 *	prime1            INTEGER,  -- p
+			 *	prime2            INTEGER,  -- q
+			 *	exponent1         INTEGER,  -- d mod (p-1)
+			 *	exponent2         INTEGER,  -- d mod (q-1) 
+			 *	coefficient       INTEGER,  -- (inverse of q) mod p
+			 *	otherPrimeInfos   OtherPrimeInfos OPTIONAL 
+			 * }
+			 */
+			static public RSA DecodeRSA (byte[] keypair) 
+			{
+				ASN1 privateKey = new ASN1 (keypair);
+				if (privateKey.Tag != 0x30)
+					throw new CryptographicException ("invalid private key format");
+
+				ASN1 version = privateKey [0];
+				if (version.Tag != 0x02)
+					throw new CryptographicException ("missing version");
+
+				if (privateKey.Count < 9)
+					throw new CryptographicException ("not enough key parameters");
+
+				RSAParameters param = new RSAParameters ();
+				// note: MUST remove leading 0 - else MS wont import the key
+				param.Modulus = RemoveLeadingZero (privateKey [1].Value);
+				int keysize = param.Modulus.Length;
+				int keysize2 = (keysize >> 1); // half-size
+				// size must be normalized - else MS wont import the key
+				param.D = Normalize (privateKey [3].Value, keysize);
+				param.DP = Normalize (privateKey [6].Value, keysize2);
+				param.DQ = Normalize (privateKey [7].Value, keysize2);
+				param.Exponent = RemoveLeadingZero (privateKey [2].Value);
+				param.InverseQ = Normalize (privateKey [8].Value, keysize2);
+				param.P = Normalize (privateKey [4].Value, keysize2);
+				param.Q = Normalize (privateKey [5].Value, keysize2);
+
+				RSA rsa = null;
+				try {
+					rsa = RSA.Create ();
+					rsa.ImportParameters (param);
+				}
+				catch (CryptographicException) {
+#if MONOTOUCH
+					// there's no machine-wide store available for iOS so we can drop the dependency on
+					// CspParameters (which drops other things, like XML key persistance, unless used elsewhere)
+					throw;
+#else
+					// this may cause problem when this code is run under
+					// the SYSTEM identity on Windows (e.g. ASP.NET). See
+					// http://bugzilla.ximian.com/show_bug.cgi?id=77559
+					CspParameters csp = new CspParameters ();
+					csp.Flags = CspProviderFlags.UseMachineKeyStore;
+					rsa = new RSACryptoServiceProvider (csp);
+					rsa.ImportParameters (param);
+#endif
+				}
+				return rsa;
+			}
+
+			/*
+			 * RSAPrivateKey ::= SEQUENCE {
+			 *	version           Version, 
+			 *	modulus           INTEGER,  -- n
+			 *	publicExponent    INTEGER,  -- e
+			 *	privateExponent   INTEGER,  -- d
+			 *	prime1            INTEGER,  -- p
+			 *	prime2            INTEGER,  -- q
+			 *	exponent1         INTEGER,  -- d mod (p-1)
+			 *	exponent2         INTEGER,  -- d mod (q-1) 
+			 *	coefficient       INTEGER,  -- (inverse of q) mod p
+			 *	otherPrimeInfos   OtherPrimeInfos OPTIONAL 
+			 * }
+			 */
+			static public byte[] Encode (RSA rsa) 
+			{
+				RSAParameters param = rsa.ExportParameters (true);
+
+				ASN1 rsaPrivateKey = new ASN1 (0x30);
+				rsaPrivateKey.Add (new ASN1 (0x02, new byte [1] { 0x00 }));
+				rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Modulus));
+				rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Exponent));
+				rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.D));
+				rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.P));
+				rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Q));
+				rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.DP));
+				rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.DQ));
+				rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.InverseQ));
+
+				return rsaPrivateKey.GetBytes ();
+			}
+
+			// DSA only encode it's X private key inside an ASN.1 INTEGER (Hint: Tag == 0x02)
+			// which isn't enough for rebuilding the keypair. The other parameters
+			// can be found (98% of the time) in the X.509 certificate associated
+			// with the private key or (2% of the time) the parameters are in it's
+			// issuer X.509 certificate (not supported in the .NET framework).
+			static public DSA DecodeDSA (byte[] privateKey, DSAParameters dsaParameters) 
+			{
+				ASN1 pvk = new ASN1 (privateKey);
+				if (pvk.Tag != 0x02)
+					throw new CryptographicException ("invalid private key format");
+
+				// X is ALWAYS 20 bytes (no matter if the key length is 512 or 1024 bits)
+				dsaParameters.X = Normalize (pvk.Value, 20);
+				DSA dsa = DSA.Create ();
+				dsa.ImportParameters (dsaParameters);
+				return dsa;
+			}
+
+			static public byte[] Encode (DSA dsa) 
+			{
+				DSAParameters param = dsa.ExportParameters (true);
+				return ASN1Convert.FromUnsignedBigInteger (param.X).GetBytes ();
+			}
+
+			static public byte[] Encode (AsymmetricAlgorithm aa) 
+			{
+				if (aa is RSA)
+					return Encode ((RSA)aa);
+				else if (aa is DSA)
+					return Encode ((DSA)aa);
+				else
+					throw new CryptographicException ("Unknown asymmetric algorithm {0}", aa.ToString ());
+			}
+		}
+
+		/*
+		 * EncryptedPrivateKeyInfo ::= SEQUENCE {
+		 *	encryptionAlgorithm EncryptionAlgorithmIdentifier,
+		 *	encryptedData EncryptedData 
+		 * }
+		 * 
+		 * EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+		 * 
+		 * EncryptedData ::= OCTET STRING
+		 * 
+		 * --
+		 *  AlgorithmIdentifier  ::= SEQUENCE {
+		 *	algorithm  OBJECT IDENTIFIER,
+		 *	parameters ANY DEFINED BY algorithm OPTIONAL
+		 * }
+		 * 
+		 * -- from PKCS#5
+		 * PBEParameter ::= SEQUENCE {
+		 *	salt OCTET STRING SIZE(8),
+		 *	iterationCount INTEGER 
+		 * }
+		 */
+		public class EncryptedPrivateKeyInfo {
+
+			private string _algorithm;
+			private byte[] _salt;
+			private int _iterations;
+			private byte[] _data;
+
+			public EncryptedPrivateKeyInfo () {}
+
+			public EncryptedPrivateKeyInfo (byte[] data) : this () 
+			{
+				Decode (data);
+			}
+
+			// properties
+
+			public string Algorithm {
+				get { return _algorithm; }
+				set { _algorithm = value; }
+			}
+
+			public byte[] EncryptedData {
+				get { return (_data == null) ? null : (byte[]) _data.Clone (); }
+				set { _data = (value == null) ? null : (byte[]) value.Clone (); }
+			}
+
+			public byte[] Salt {
+				get { 
+					if (_salt == null) {
+						RandomNumberGenerator rng = RandomNumberGenerator.Create ();
+						_salt = new byte [8];
+						rng.GetBytes (_salt);
+					}
+					return (byte[]) _salt.Clone (); 
+				}
+				set { _salt = (byte[]) value.Clone (); }
+			}
+
+			public int IterationCount {
+				get { return _iterations; }
+				set { 
+					if (value < 0)
+						throw new ArgumentOutOfRangeException ("IterationCount", "Negative");
+					_iterations = value; 
+				}
+			}
+
+			// methods
+
+			private void Decode (byte[] data) 
+			{
+				ASN1 encryptedPrivateKeyInfo = new ASN1 (data);
+				if (encryptedPrivateKeyInfo.Tag != 0x30)
+					throw new CryptographicException ("invalid EncryptedPrivateKeyInfo");
+
+				ASN1 encryptionAlgorithm = encryptedPrivateKeyInfo [0];
+				if (encryptionAlgorithm.Tag != 0x30)
+					throw new CryptographicException ("invalid encryptionAlgorithm");
+				ASN1 algorithm = encryptionAlgorithm [0];
+				if (algorithm.Tag != 0x06)
+					throw new CryptographicException ("invalid algorithm");
+				_algorithm = ASN1Convert.ToOid (algorithm);
+				// parameters ANY DEFINED BY algorithm OPTIONAL
+				if (encryptionAlgorithm.Count > 1) {
+					ASN1 parameters = encryptionAlgorithm [1];
+					if (parameters.Tag != 0x30)
+						throw new CryptographicException ("invalid parameters");
+
+					ASN1 salt = parameters [0];
+					if (salt.Tag != 0x04)
+						throw new CryptographicException ("invalid salt");
+					_salt = salt.Value;
+
+					ASN1 iterationCount = parameters [1];
+					if (iterationCount.Tag != 0x02)
+						throw new CryptographicException ("invalid iterationCount");
+					_iterations = ASN1Convert.ToInt32 (iterationCount);
+				}
+
+				ASN1 encryptedData = encryptedPrivateKeyInfo [1];
+				if (encryptedData.Tag != 0x04)
+					throw new CryptographicException ("invalid EncryptedData");
+				_data = encryptedData.Value;
+			}
+
+			// Note: PKCS#8 doesn't define how to generate the key required for encryption
+			// so you're on your own. Just don't try to copy the big guys too much ;)
+			// Netscape:	http://www.cs.auckland.ac.nz/~pgut001/pubs/netscape.txt
+			// Microsoft:	http://www.cs.auckland.ac.nz/~pgut001/pubs/breakms.txt
+			public byte[] GetBytes ()
+			{
+				if (_algorithm == null)
+					throw new CryptographicException ("No algorithm OID specified");
+
+				ASN1 encryptionAlgorithm = new ASN1 (0x30);
+				encryptionAlgorithm.Add (ASN1Convert.FromOid (_algorithm));
+
+				// parameters ANY DEFINED BY algorithm OPTIONAL
+				if ((_iterations > 0) || (_salt != null)) {
+					ASN1 salt = new ASN1 (0x04, _salt);
+					ASN1 iterations = ASN1Convert.FromInt32 (_iterations);
+
+					ASN1 parameters = new ASN1 (0x30);
+					parameters.Add (salt);
+					parameters.Add (iterations);
+					encryptionAlgorithm.Add (parameters);
+				}
+
+				// encapsulates EncryptedData into an OCTET STRING
+				ASN1 encryptedData = new ASN1 (0x04, _data);
+
+				ASN1 encryptedPrivateKeyInfo = new ASN1 (0x30);
+				encryptedPrivateKeyInfo.Add (encryptionAlgorithm);
+				encryptedPrivateKeyInfo.Add (encryptedData);
+
+				return encryptedPrivateKeyInfo.GetBytes ();
+			}
+		}
+	}
+}

+ 400 - 0
MediaBrowser.Server.Mono/Security/X501Name.cs

@@ -0,0 +1,400 @@
+//
+// 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;
+
+using Mono.Security;
+using Mono.Security.Cryptography;
+
+namespace Mono.Security.X509 {
+
+	// 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
+	 */
+#if INSIDE_CORLIB
+	internal
+#else
+	public 
+#endif
+	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;
+		}
+	}
+}

+ 154 - 0
MediaBrowser.Server.Mono/Security/X509Builder.cs

@@ -0,0 +1,154 @@
+//
+// X509Builder.cs: Abstract builder class for X509 objects
+//
+// Author:
+//	Sebastien Pouliot  <sebastien@ximian.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 Novell (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.Security.Cryptography;
+
+using Mono.Security;
+
+namespace Mono.Security.X509 {
+
+	public abstract class X509Builder {
+
+		private const string defaultHash = "SHA1";
+		private string hashName;
+
+		protected X509Builder ()
+		{
+			hashName = defaultHash;
+		}
+
+		protected abstract ASN1 ToBeSigned (string hashName);
+
+		// move to PKCS1
+		protected string GetOid (string hashName) 
+		{
+			switch (hashName.ToLower (CultureInfo.InvariantCulture)) {
+				case "md2":
+					// md2withRSAEncryption (1 2 840 113549 1 1 2)
+					return "1.2.840.113549.1.1.2";
+				case "md4":
+					// md4withRSAEncryption (1 2 840 113549 1 1 3)
+					return "1.2.840.113549.1.1.3";
+				case "md5":
+					// md5withRSAEncryption (1 2 840 113549 1 1 4)
+					return "1.2.840.113549.1.1.4";
+				case "sha1":
+					// sha1withRSAEncryption (1 2 840 113549 1 1 5)
+					return "1.2.840.113549.1.1.5";
+				case "sha256":
+					// sha256WithRSAEncryption 	OBJECT IDENTIFIER ::= { pkcs-1 11 }
+					return "1.2.840.113549.1.1.11";
+				case "sha384":
+					// sha384WithRSAEncryption 	OBJECT IDENTIFIER ::= { pkcs-1 12 }
+					return "1.2.840.113549.1.1.12";
+				case "sha512":
+					// sha512WithRSAEncryption 	OBJECT IDENTIFIER ::= { pkcs-1 13 }
+					return "1.2.840.113549.1.1.13";
+				default:
+					throw new NotSupportedException ("Unknown hash algorithm " + hashName);
+			}
+		}
+
+		public string Hash {
+			get { return hashName; }
+			set { 
+				if (hashName == null)
+					hashName = defaultHash;
+				else
+					hashName = value;
+			}
+		}
+
+		public virtual byte[] Sign (AsymmetricAlgorithm aa) 
+		{
+			if (aa is RSA)
+				return Sign (aa as RSA);
+			else if (aa is DSA)
+				return Sign (aa as DSA);
+			else
+				throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString());
+		}
+
+		private byte[] Build (ASN1 tbs, string hashoid, byte[] signature) 
+		{
+			ASN1 builder = new ASN1 (0x30);
+			builder.Add (tbs);
+			builder.Add (PKCS7.AlgorithmIdentifier (hashoid));
+			// first byte of BITSTRING is the number of unused bits in the first byte
+			byte[] bitstring = new byte [signature.Length + 1];
+			Buffer.BlockCopy (signature, 0, bitstring, 1, signature.Length);
+			builder.Add (new ASN1 (0x03, bitstring));
+			return builder.GetBytes ();
+		}
+
+		public virtual byte[] Sign (RSA key)
+		{
+			string oid = GetOid (hashName);
+			ASN1 tbs = ToBeSigned (oid);
+			HashAlgorithm ha = HashAlgorithm.Create (hashName);
+			byte[] hash = ha.ComputeHash (tbs.GetBytes ());
+
+			RSAPKCS1SignatureFormatter pkcs1 = new RSAPKCS1SignatureFormatter (key);
+			pkcs1.SetHashAlgorithm (hashName);
+			byte[] signature = pkcs1.CreateSignature (hash);
+
+			return Build (tbs, oid, signature);
+		}
+
+		public virtual byte[] Sign (DSA key) 
+		{
+			string oid = "1.2.840.10040.4.3";
+			ASN1 tbs = ToBeSigned (oid);
+			HashAlgorithm ha = HashAlgorithm.Create (hashName);
+			if (!(ha is SHA1))
+				throw new NotSupportedException ("Only SHA-1 is supported for DSA");
+			byte[] hash = ha.ComputeHash (tbs.GetBytes ());
+
+			DSASignatureFormatter dsa = new DSASignatureFormatter (key);
+			dsa.SetHashAlgorithm (hashName);
+			byte[] rs = dsa.CreateSignature (hash);
+
+			// split R and S
+			byte[] r = new byte [20];
+			Buffer.BlockCopy (rs, 0, r, 0, 20);
+			byte[] s = new byte [20];
+			Buffer.BlockCopy (rs, 20, s, 0, 20);
+			ASN1 signature = new ASN1 (0x30);
+			signature.Add (new ASN1 (0x02, r));
+			signature.Add (new ASN1 (0x02, s));
+
+			// dsaWithSha1 (1 2 840 10040 4 3)
+			return Build (tbs, oid, signature.GetBytes ());
+		}
+	}
+}

+ 567 - 0
MediaBrowser.Server.Mono/Security/X509Certificate.cs

@@ -0,0 +1,567 @@
+//
+// X509Certificates.cs: Handles X.509 certificates.
+//
+// Author:
+//	Sebastien Pouliot  <sebastien@xamarin.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.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.Runtime.Serialization;
+using System.Security.Cryptography;
+using SSCX = System.Security.Cryptography.X509Certificates;
+using System.Security.Permissions;
+using System.Text;
+using Mono.Security.Cryptography;
+
+namespace Mono.Security.X509 {
+
+	// References:
+	// a.	Internet X.509 Public Key Infrastructure Certificate and CRL Profile
+	//	http://www.ietf.org/rfc/rfc3280.txt
+	// b.	ITU ASN.1 standards (free download)
+	//	http://www.itu.int/ITU-T/studygroups/com17/languages/
+
+#if INSIDE_CORLIB
+	internal class X509Certificate : ISerializable {
+#else
+	public class X509Certificate : ISerializable {
+#endif
+
+		private ASN1 decoder;
+
+		private byte[] m_encodedcert;
+		private DateTime m_from;
+		private DateTime m_until;
+		private ASN1 issuer;
+		private string m_issuername;
+		private string m_keyalgo;
+		private byte[] m_keyalgoparams;
+		private ASN1 subject;
+		private string m_subject;
+		private byte[] m_publickey;
+		private byte[] signature;
+		private string m_signaturealgo;
+		private byte[] m_signaturealgoparams;
+		private byte[] certhash;
+		private RSA _rsa;
+		private DSA _dsa;
+
+		// from http://msdn.microsoft.com/en-gb/library/ff635835.aspx
+		private const string OID_DSA = "1.2.840.10040.4.1";
+		private const string OID_RSA = "1.2.840.113549.1.1.1";
+		
+		// from http://www.ietf.org/rfc/rfc2459.txt
+		//
+		//Certificate  ::=  SEQUENCE  {
+		//     tbsCertificate       TBSCertificate,
+		//     signatureAlgorithm   AlgorithmIdentifier,
+		//     signature            BIT STRING  }
+		//
+		//TBSCertificate  ::=  SEQUENCE  {
+		//     version         [0]  Version DEFAULT v1,
+		//     serialNumber         CertificateSerialNumber,
+		//     signature            AlgorithmIdentifier,
+		//     issuer               Name,
+		//     validity             Validity,
+		//     subject              Name,
+		//     subjectPublicKeyInfo SubjectPublicKeyInfo,
+		//     issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
+		//                          -- If present, version shall be v2 or v3
+		//     subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
+		//                          -- If present, version shall be v2 or v3
+		//     extensions      [3]  Extensions OPTIONAL
+		//                          -- If present, version shall be v3 --  }
+		private int version;
+		private byte[] serialnumber;
+
+		private byte[] issuerUniqueID;
+		private byte[] subjectUniqueID;
+		private X509ExtensionCollection extensions;
+
+		private static string encoding_error = ("Input data cannot be coded as a valid certificate.");
+
+
+		// that's were the real job is!
+		private void Parse (byte[] data) 
+		{
+			try {
+				decoder = new ASN1 (data);
+				// Certificate 
+				if (decoder.Tag != 0x30)
+					throw new CryptographicException (encoding_error);
+				// Certificate / TBSCertificate
+				if (decoder [0].Tag != 0x30)
+					throw new CryptographicException (encoding_error);
+
+				ASN1 tbsCertificate = decoder [0];
+
+				int tbs = 0;
+				// Certificate / TBSCertificate / Version
+				ASN1 v = decoder [0][tbs];
+				version = 1;			// DEFAULT v1
+				if ((v.Tag == 0xA0) && (v.Count > 0)) {
+					// version (optional) is present only in v2+ certs
+					version += v [0].Value [0];	// zero based
+					tbs++;
+				}
+
+				// Certificate / TBSCertificate / CertificateSerialNumber
+				ASN1 sn = decoder [0][tbs++];
+				if (sn.Tag != 0x02) 
+					throw new CryptographicException (encoding_error);
+				serialnumber = sn.Value;
+				Array.Reverse (serialnumber, 0, serialnumber.Length);
+		
+				// Certificate / TBSCertificate / AlgorithmIdentifier
+				tbs++;
+				// ASN1 signatureAlgo = tbsCertificate.Element (tbs++, 0x30); 
+		
+				issuer = tbsCertificate.Element (tbs++, 0x30); 
+				m_issuername = X501.ToString (issuer);
+		
+				ASN1 validity = tbsCertificate.Element (tbs++, 0x30);
+				ASN1 notBefore = validity [0];
+				m_from = ASN1Convert.ToDateTime (notBefore);
+				ASN1 notAfter = validity [1];
+				m_until = ASN1Convert.ToDateTime (notAfter);
+		
+				subject = tbsCertificate.Element (tbs++, 0x30);
+				m_subject = X501.ToString (subject);
+		
+				ASN1 subjectPublicKeyInfo = tbsCertificate.Element (tbs++, 0x30);
+		
+				ASN1 algorithm = subjectPublicKeyInfo.Element (0, 0x30);
+				ASN1 algo = algorithm.Element (0, 0x06);
+				m_keyalgo = ASN1Convert.ToOid (algo);
+				// parameters ANY DEFINED BY algorithm OPTIONAL
+				// so we dont ask for a specific (Element) type and return DER
+				ASN1 parameters = algorithm [1];
+				m_keyalgoparams = ((algorithm.Count > 1) ? parameters.GetBytes () : null);
+		
+				ASN1 subjectPublicKey = subjectPublicKeyInfo.Element (1, 0x03); 
+				// we must drop th first byte (which is the number of unused bits
+				// in the BITSTRING)
+				int n = subjectPublicKey.Length - 1;
+				m_publickey = new byte [n];
+				Buffer.BlockCopy (subjectPublicKey.Value, 1, m_publickey, 0, n);
+
+				// signature processing
+				byte[] bitstring = decoder [2].Value;
+				// first byte contains unused bits in first byte
+				signature = new byte [bitstring.Length - 1];
+				Buffer.BlockCopy (bitstring, 1, signature, 0, signature.Length);
+
+				algorithm = decoder [1];
+				algo = algorithm.Element (0, 0x06);
+				m_signaturealgo = ASN1Convert.ToOid (algo);
+				parameters = algorithm [1];
+				if (parameters != null)
+					m_signaturealgoparams = parameters.GetBytes ();
+				else
+					m_signaturealgoparams = null;
+
+				// Certificate / TBSCertificate / issuerUniqueID
+				ASN1 issuerUID = tbsCertificate.Element (tbs, 0x81);
+				if (issuerUID != null) {
+					tbs++;
+					issuerUniqueID = issuerUID.Value;
+				}
+
+				// Certificate / TBSCertificate / subjectUniqueID
+				ASN1 subjectUID = tbsCertificate.Element (tbs, 0x82);
+				if (subjectUID != null) {
+					tbs++;
+					subjectUniqueID = subjectUID.Value;
+				}
+
+				// Certificate / TBSCertificate / Extensions
+				ASN1 extns = tbsCertificate.Element (tbs, 0xA3);
+				if ((extns != null) && (extns.Count == 1))
+					extensions = new X509ExtensionCollection (extns [0]);
+				else
+					extensions = new X509ExtensionCollection (null);
+
+				// keep a copy of the original data
+				m_encodedcert = (byte[]) data.Clone ();
+			}
+			catch (Exception ex) {
+				throw new CryptographicException (encoding_error, ex);
+			}
+		}
+
+		// constructors
+
+		public X509Certificate (byte[] data) 
+		{
+			if (data != null) {
+				// does it looks like PEM ?
+				if ((data.Length > 0) && (data [0] != 0x30)) {
+					try {
+						data = PEM ("CERTIFICATE", data);
+					}
+					catch (Exception ex) {
+						throw new CryptographicException (encoding_error, ex);
+					}
+				}
+				Parse (data);
+			}
+		}
+
+		private byte[] GetUnsignedBigInteger (byte[] integer) 
+		{
+			if (integer [0] == 0x00) {
+				// this first byte is added so we're sure it's an unsigned integer
+				// however we can't feed it into RSAParameters or DSAParameters
+				int length = integer.Length - 1;
+				byte[] uinteger = new byte [length];
+				Buffer.BlockCopy (integer, 1, uinteger, 0, length);
+				return uinteger;
+			}
+			else
+				return integer;
+		}
+
+		// public methods
+
+		public DSA DSA {
+			get {
+				if (m_keyalgoparams == null)
+					throw new CryptographicException ("Missing key algorithm parameters.");
+
+				if (_dsa == null && m_keyalgo == OID_DSA) {
+					DSAParameters dsaParams = new DSAParameters ();
+					// for DSA m_publickey contains 1 ASN.1 integer - Y
+					ASN1 pubkey = new ASN1 (m_publickey);
+					if ((pubkey == null) || (pubkey.Tag != 0x02))
+						return null;
+					dsaParams.Y = GetUnsignedBigInteger (pubkey.Value);
+
+					ASN1 param = new ASN1 (m_keyalgoparams);
+					if ((param == null) || (param.Tag != 0x30) || (param.Count < 3))
+						return null;
+					if ((param [0].Tag != 0x02) || (param [1].Tag != 0x02) || (param [2].Tag != 0x02))
+						return null;
+					dsaParams.P = GetUnsignedBigInteger (param [0].Value);
+					dsaParams.Q = GetUnsignedBigInteger (param [1].Value);
+					dsaParams.G = GetUnsignedBigInteger (param [2].Value);
+
+					// BUG: MS BCL 1.0 can't import a key which 
+					// isn't the same size as the one present in
+					// the container.
+					_dsa = (DSA) new DSACryptoServiceProvider (dsaParams.Y.Length << 3);
+					_dsa.ImportParameters (dsaParams);
+				}
+				return _dsa; 
+			}
+
+			set {
+				_dsa = value;
+				if (value != null)
+					_rsa = null;
+			}
+		}
+
+		public X509ExtensionCollection Extensions {
+			get { return extensions; }
+		}
+
+		public byte[] Hash {
+			get {
+				if (certhash == null) {
+					if ((decoder == null) || (decoder.Count < 1))
+						return null;
+					string algo = PKCS1.HashNameFromOid (m_signaturealgo, false);
+					if (algo == null)
+						return null;
+					byte[] toBeSigned = decoder [0].GetBytes ();
+					using (var hash = PKCS1.CreateFromName (algo))
+						certhash = hash.ComputeHash (toBeSigned, 0, toBeSigned.Length);
+				}
+				return (byte[]) certhash.Clone ();
+			}
+		}
+
+		public virtual string IssuerName {
+			get { return m_issuername; }
+		}
+
+		public virtual string KeyAlgorithm {
+			get { return m_keyalgo; }
+		}
+
+		public virtual byte[] KeyAlgorithmParameters {
+			get {
+				if (m_keyalgoparams == null)
+					return null;
+				return (byte[]) m_keyalgoparams.Clone (); 
+			}
+			set { m_keyalgoparams = value; }
+		}
+
+		public virtual byte[] PublicKey	{
+			get { 
+				if (m_publickey == null)
+					return null;
+				return (byte[]) m_publickey.Clone ();
+			}
+		}
+
+		public virtual RSA RSA {
+			get {
+				if (_rsa == null && m_keyalgo == OID_RSA) {
+					RSAParameters rsaParams = new RSAParameters ();
+					// for RSA m_publickey contains 2 ASN.1 integers
+					// the modulus and the public exponent
+					ASN1 pubkey = new ASN1 (m_publickey);
+					ASN1 modulus = pubkey [0];
+					if ((modulus == null) || (modulus.Tag != 0x02))
+						return null;
+					ASN1 exponent = pubkey [1];
+					if (exponent.Tag != 0x02)
+						return null;
+
+					rsaParams.Modulus = GetUnsignedBigInteger (modulus.Value);
+					rsaParams.Exponent = exponent.Value;
+
+					// BUG: MS BCL 1.0 can't import a key which 
+					// isn't the same size as the one present in
+					// the container.
+					int keySize = (rsaParams.Modulus.Length << 3);
+					_rsa = (RSA) new RSACryptoServiceProvider (keySize);
+					_rsa.ImportParameters (rsaParams);
+				}
+				return _rsa; 
+			}
+
+			set {
+				if (value != null)
+					_dsa = null;
+				_rsa = value;
+			}
+		}
+	        
+		public virtual byte[] RawData {
+			get {
+				if (m_encodedcert == null)
+					return null;
+				return (byte[]) m_encodedcert.Clone ();
+			}
+		}
+
+		public virtual byte[] SerialNumber {
+			get { 
+				if (serialnumber == null)
+					return null;
+				return (byte[]) serialnumber.Clone (); 
+			}
+		}
+
+		public virtual byte[] Signature {
+			get { 
+				if (signature == null)
+					return null;
+
+				switch (m_signaturealgo) {
+					case "1.2.840.113549.1.1.2":	// MD2 with RSA encryption 
+					case "1.2.840.113549.1.1.3":	// MD4 with RSA encryption 
+					case "1.2.840.113549.1.1.4":	// MD5 with RSA encryption 
+					case "1.2.840.113549.1.1.5":	// SHA-1 with RSA Encryption 
+					case "1.3.14.3.2.29":		// SHA1 with RSA signature
+					case "1.2.840.113549.1.1.11":	// SHA-256 with RSA Encryption
+					case "1.2.840.113549.1.1.12":	// SHA-384 with RSA Encryption
+					case "1.2.840.113549.1.1.13":	// SHA-512 with RSA Encryption
+					case "1.3.36.3.3.1.2":			// RIPEMD160 with RSA Encryption
+						return (byte[]) signature.Clone ();
+
+					case "1.2.840.10040.4.3":	// SHA-1 with DSA
+						ASN1 sign = new ASN1 (signature);
+						if ((sign == null) || (sign.Count != 2))
+							return null;
+						byte[] part1 = sign [0].Value;
+						byte[] part2 = sign [1].Value;
+						byte[] sig = new byte [40];
+						// parts may be less than 20 bytes (i.e. first bytes were 0x00)
+						// parts may be more than 20 bytes (i.e. first byte > 0x80, negative)
+						int s1 = System.Math.Max (0, part1.Length - 20);
+						int e1 = System.Math.Max (0, 20 - part1.Length);
+						Buffer.BlockCopy (part1, s1, sig, e1, part1.Length - s1);
+						int s2 = System.Math.Max (0, part2.Length - 20);
+						int e2 = System.Math.Max (20, 40 - part2.Length);
+						Buffer.BlockCopy (part2, s2, sig, e2, part2.Length - s2);
+						return sig;
+
+					default:
+						throw new CryptographicException ("Unsupported hash algorithm: " + m_signaturealgo);
+				}
+			}
+		}
+
+		public virtual string SignatureAlgorithm {
+			get { return m_signaturealgo; }
+		}
+
+		public virtual byte[] SignatureAlgorithmParameters {
+			get { 
+				if (m_signaturealgoparams == null)
+					return m_signaturealgoparams;
+				return (byte[]) m_signaturealgoparams.Clone ();
+			}
+		}
+
+		public virtual string SubjectName {
+			get { return m_subject; }
+		}
+
+		public virtual DateTime ValidFrom {
+			get { return m_from; }
+		}
+
+		public virtual DateTime ValidUntil {
+			get { return m_until; }
+		}
+
+		public int Version {
+			get { return version; }
+		}
+
+		public bool IsCurrent {
+			get { return WasCurrent (DateTime.UtcNow); }
+		}
+
+		public bool WasCurrent (DateTime instant) 
+		{
+			return ((instant > ValidFrom) && (instant <= ValidUntil));
+		}
+
+		// uncommon v2 "extension"
+		public byte[] IssuerUniqueIdentifier {
+			get {
+				if (issuerUniqueID == null)
+					return null;
+				return (byte[]) issuerUniqueID.Clone ();
+			}
+		}
+
+		// uncommon v2 "extension"
+		public byte[] SubjectUniqueIdentifier {
+			get {
+				if (subjectUniqueID == null)
+					return null;
+				return (byte[]) subjectUniqueID.Clone ();
+			}
+		}
+
+		internal bool VerifySignature (DSA dsa) 
+		{
+			// signatureOID is check by both this.Hash and this.Signature
+			DSASignatureDeformatter v = new DSASignatureDeformatter (dsa);
+			// only SHA-1 is supported
+			v.SetHashAlgorithm ("SHA1");
+			return v.VerifySignature (this.Hash, this.Signature);
+		}
+
+		internal bool VerifySignature (RSA rsa) 
+		{
+			// SHA1-1 with DSA
+			if (m_signaturealgo == "1.2.840.10040.4.3")
+				return false;
+			RSAPKCS1SignatureDeformatter v = new RSAPKCS1SignatureDeformatter (rsa);
+			v.SetHashAlgorithm (PKCS1.HashNameFromOid (m_signaturealgo));
+			return v.VerifySignature (this.Hash, this.Signature);
+		}
+
+		public bool VerifySignature (AsymmetricAlgorithm aa) 
+		{
+			if (aa == null)
+				throw new ArgumentNullException ("aa");
+
+			if (aa is RSA)
+				return VerifySignature (aa as RSA);
+			else if (aa is DSA)
+				return VerifySignature (aa as DSA);
+			else 
+				throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
+		}
+
+		public bool CheckSignature (byte[] hash, string hashAlgorithm, byte[] signature) 
+		{
+			RSACryptoServiceProvider r = (RSACryptoServiceProvider) RSA;
+			return r.VerifyHash (hash, hashAlgorithm, signature);
+		}
+
+		public bool IsSelfSigned {
+			get { 
+				if (m_issuername != m_subject)
+					return false;
+
+				try {
+					if (RSA != null)
+						return VerifySignature (RSA);
+					else if (DSA != null)
+						return VerifySignature (DSA);
+					else
+						return false; // e.g. a certificate with only DSA parameters
+				}
+				catch (CryptographicException) {
+					return false;
+				}
+			}
+		}
+
+		public ASN1 GetIssuerName ()
+		{
+			return issuer;
+		}
+
+		public ASN1 GetSubjectName ()
+		{
+			return subject;
+		}
+
+		protected X509Certificate (SerializationInfo info, StreamingContext context)
+		{
+			Parse ((byte[]) info.GetValue ("raw", typeof (byte[])));
+		}
+
+		[SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
+		public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
+		{
+			info.AddValue ("raw", m_encodedcert);
+			// note: we NEVER serialize the private key
+		}
+
+		static byte[] PEM (string type, byte[] data) 
+		{
+			string pem = Encoding.ASCII.GetString (data);
+			string header = String.Format ("-----BEGIN {0}-----", type);
+			string footer = String.Format ("-----END {0}-----", type);
+			int start = pem.IndexOf (header) + header.Length;
+			int end = pem.IndexOf (footer, start);
+			string base64 = pem.Substring (start, (end - start));
+			return Convert.FromBase64String (base64);
+		}
+	}
+}

+ 244 - 0
MediaBrowser.Server.Mono/Security/X509CertificateBuilder.cs

@@ -0,0 +1,244 @@
+//
+// X509CertificateBuilder.cs: Handles building of X.509 certificates.
+//
+// Author:
+//	Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 Novell (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.Security.Cryptography;
+
+namespace Mono.Security.X509 {
+	// From RFC3280
+	/*
+	 * Certificate  ::=  SEQUENCE  {
+	 *      tbsCertificate       TBSCertificate,
+	 *      signatureAlgorithm   AlgorithmIdentifier,
+	 *      signature            BIT STRING  
+	 * }
+	 * TBSCertificate  ::=  SEQUENCE  {
+	 *      version         [0]  Version DEFAULT v1,
+	 *      serialNumber         CertificateSerialNumber,
+	 *      signature            AlgorithmIdentifier,
+	 *      issuer               Name,
+	 *      validity             Validity,
+	 *      subject              Name,
+	 *      subjectPublicKeyInfo SubjectPublicKeyInfo,
+	 *      issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
+	 *                           -- If present, version MUST be v2 or v3
+	 *      subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
+	 *                           -- If present, version MUST be v2 or v3
+	 *      extensions      [3]  Extensions OPTIONAL
+	 *                           -- If present, version MUST be v3 --  
+	 * }
+	 * Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }
+	 * CertificateSerialNumber  ::=  INTEGER
+	 * Validity ::= SEQUENCE {
+	 *      notBefore      Time,
+	 *      notAfter       Time  
+	 * }
+	 * Time ::= CHOICE {
+	 *      utcTime        UTCTime,
+	 *      generalTime    GeneralizedTime 
+	 * }
+	 */
+	public class X509CertificateBuilder : X509Builder {
+ 
+		private byte version;
+		private byte[] sn;
+		private string issuer;
+		private DateTime notBefore;
+		private DateTime notAfter;
+		private string subject;
+		private AsymmetricAlgorithm aa;
+		private byte[] issuerUniqueID;
+		private byte[] subjectUniqueID;
+		private X509ExtensionCollection extensions;
+
+		public X509CertificateBuilder () : this (3) {}
+	
+		public X509CertificateBuilder (byte version) 
+		{
+			if (version > 3)
+				throw new ArgumentException ("Invalid certificate version");
+			this.version = version;
+			extensions = new X509ExtensionCollection ();
+		}
+
+		public byte Version {
+			get { return version; }
+			set { version = value; }
+		}
+
+		public byte[] SerialNumber {
+			get { return sn; }
+			set { sn = value; }
+		}
+
+		public string IssuerName {
+			get { return issuer; }
+			set { issuer = value; }
+		}
+
+		public DateTime NotBefore {
+			get { return notBefore; }
+			set { notBefore = value; }
+		}
+
+		public DateTime NotAfter {
+			get { return notAfter; }
+			set { notAfter = value; }
+		}
+
+		public string SubjectName {
+			get { return subject; }
+			set { subject = value; }
+		}
+
+		public AsymmetricAlgorithm SubjectPublicKey {
+			get { return aa; }
+			set { aa = value; }
+		}
+
+		public byte[] IssuerUniqueId {
+			get { return issuerUniqueID; }
+			set { issuerUniqueID = value; }
+		}
+
+		public byte[] SubjectUniqueId {
+			get { return subjectUniqueID; }
+			set { subjectUniqueID = value; }
+		}
+
+		public X509ExtensionCollection Extensions {
+			get { return extensions; }
+		}
+
+
+		/* SubjectPublicKeyInfo  ::=  SEQUENCE  {
+		 *      algorithm            AlgorithmIdentifier,
+		 *      subjectPublicKey     BIT STRING  }
+		 */
+		private ASN1 SubjectPublicKeyInfo () 
+		{
+			ASN1 keyInfo = new ASN1 (0x30);
+			if (aa is RSA) {
+				keyInfo.Add (PKCS7.AlgorithmIdentifier ("1.2.840.113549.1.1.1"));
+				RSAParameters p = (aa as RSA).ExportParameters (false);
+				/* RSAPublicKey ::= SEQUENCE {
+				 *       modulus            INTEGER,    -- n
+				 *       publicExponent     INTEGER  }  -- e
+				 */
+				ASN1 key = new ASN1 (0x30);
+				key.Add (ASN1Convert.FromUnsignedBigInteger (p.Modulus));
+				key.Add (ASN1Convert.FromUnsignedBigInteger (p.Exponent));
+				keyInfo.Add (new ASN1 (UniqueIdentifier (key.GetBytes ())));
+			}
+			else if (aa is DSA) {
+				DSAParameters p = (aa as DSA).ExportParameters (false);
+				/* Dss-Parms  ::=  SEQUENCE  {
+				 *       p             INTEGER,
+				 *       q             INTEGER,
+				 *       g             INTEGER  }
+				 */
+				ASN1 param = new ASN1 (0x30);
+				param.Add (ASN1Convert.FromUnsignedBigInteger (p.P));
+				param.Add (ASN1Convert.FromUnsignedBigInteger (p.Q));
+				param.Add (ASN1Convert.FromUnsignedBigInteger (p.G));
+				keyInfo.Add (PKCS7.AlgorithmIdentifier ("1.2.840.10040.4.1", param));
+				ASN1 key = keyInfo.Add (new ASN1 (0x03));
+				// DSAPublicKey ::= INTEGER  -- public key, y
+				key.Add (ASN1Convert.FromUnsignedBigInteger (p.Y));
+			}
+			else
+				throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
+			return keyInfo;
+		}
+
+		private byte[] UniqueIdentifier (byte[] id) 
+		{
+			// UniqueIdentifier  ::=  BIT STRING
+			ASN1 uid = new ASN1 (0x03);
+			// first byte in a BITSTRING is the number of unused bits in the first byte
+			byte[] v = new byte [id.Length + 1];
+			Buffer.BlockCopy (id, 0, v, 1, id.Length);
+			uid.Value = v;
+			return uid.GetBytes ();
+		}
+
+		protected override ASN1 ToBeSigned (string oid) 
+		{
+			// TBSCertificate
+			ASN1 tbsCert = new ASN1 (0x30);
+
+			if (version > 1) {
+				// TBSCertificate / [0] Version DEFAULT v1,
+				byte[] ver = { (byte)(version - 1) };
+				ASN1 v = tbsCert.Add (new ASN1 (0xA0));
+				v.Add (new ASN1 (0x02, ver));
+			}
+
+			// TBSCertificate / CertificateSerialNumber,
+			tbsCert.Add (new ASN1 (0x02, sn));
+
+			// TBSCertificate / AlgorithmIdentifier,
+                        tbsCert.Add (PKCS7.AlgorithmIdentifier (oid));
+
+			// TBSCertificate / Name
+			tbsCert.Add (X501.FromString (issuer));
+
+			// TBSCertificate / Validity
+			ASN1 validity = tbsCert.Add (new ASN1 (0x30));
+			// TBSCertificate / Validity / Time
+			validity.Add (ASN1Convert.FromDateTime (notBefore));
+			// TBSCertificate / Validity / Time
+			validity.Add (ASN1Convert.FromDateTime (notAfter));
+
+			// TBSCertificate / Name
+			tbsCert.Add (X501.FromString (subject));
+
+			// TBSCertificate / SubjectPublicKeyInfo
+			tbsCert.Add (SubjectPublicKeyInfo ());
+                        
+			if (version > 1) {
+				// TBSCertificate / [1]  IMPLICIT UniqueIdentifier OPTIONAL
+				if (issuerUniqueID != null)
+					tbsCert.Add (new ASN1 (0xA1, UniqueIdentifier (issuerUniqueID)));
+
+				// TBSCertificate / [2]  IMPLICIT UniqueIdentifier OPTIONAL
+				if (subjectUniqueID != null)
+					tbsCert.Add (new ASN1 (0xA1, UniqueIdentifier (subjectUniqueID)));
+
+				// TBSCertificate / [3]  Extensions OPTIONAL
+				if ((version > 2) &&  (extensions.Count > 0))
+					tbsCert.Add (new ASN1 (0xA3, extensions.GetBytes ()));
+			}
+
+			return tbsCert;
+		}
+	}
+}

+ 205 - 0
MediaBrowser.Server.Mono/Security/X509CertificateCollection.cs

@@ -0,0 +1,205 @@
+//
+// Based on System.Security.Cryptography.X509Certificates.X509CertificateCollection
+//	in System assembly
+//
+// Authors:
+//	Lawrence Pit (loz@cable.a2000.nl)
+//	Sebastien Pouliot <sebastien@ximian.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.Collections;
+
+namespace Mono.Security.X509 {
+
+	[Serializable]
+#if INSIDE_CORLIB
+	internal
+#else
+	public 
+#endif
+	class X509CertificateCollection : CollectionBase, IEnumerable {
+		
+		public X509CertificateCollection () 
+		{
+		}
+		
+		public X509CertificateCollection (X509Certificate [] value) 
+		{
+			AddRange (value);
+		}
+		
+		public X509CertificateCollection (X509CertificateCollection value)
+		{
+			AddRange (value);
+		}
+		
+		// Properties
+		
+		public X509Certificate this [int index] {
+			get { return (X509Certificate) InnerList [index]; }
+			set { InnerList [index] = value; }
+		}
+		
+		// Methods
+
+		public int Add (X509Certificate value)
+		{
+			if (value == null)
+				throw new ArgumentNullException ("value");
+			
+			return InnerList.Add (value);
+		}
+		
+		public void AddRange (X509Certificate [] value) 
+		{
+			if (value == null)
+				throw new ArgumentNullException ("value");
+
+			for (int i = 0; i < value.Length; i++) 
+				InnerList.Add (value [i]);
+		}
+		
+		public void AddRange (X509CertificateCollection value)
+		{
+			if (value == null)
+				throw new ArgumentNullException ("value");
+
+			for (int i = 0; i < value.InnerList.Count; i++) 
+				InnerList.Add (value [i]);
+		}
+		
+		public bool Contains (X509Certificate value) 
+		{
+			return (IndexOf (value) != -1);
+		}
+
+		public void CopyTo (X509Certificate[] array, int index)
+		{
+			InnerList.CopyTo (array, index);
+		}
+		
+		public new X509CertificateEnumerator GetEnumerator ()
+		{
+			return new X509CertificateEnumerator (this);
+		}
+		
+		IEnumerator IEnumerable.GetEnumerator ()
+		{
+			return InnerList.GetEnumerator ();
+		}
+		
+		public override int GetHashCode () 
+		{
+			return InnerList.GetHashCode ();
+		}
+		
+		public int IndexOf (X509Certificate value)
+		{
+			if (value == null)
+				throw new ArgumentNullException ("value");
+
+			byte[] hash = value.Hash;
+			for (int i=0; i < InnerList.Count; i++) {
+				X509Certificate x509 = (X509Certificate) InnerList [i];
+				if (Compare (x509.Hash, hash))
+					return i;
+			}
+			return -1;
+		}
+		
+		public void Insert (int index, X509Certificate value)
+		{
+			InnerList.Insert (index, value);
+		}
+		
+		public void Remove (X509Certificate value)
+		{
+			InnerList.Remove (value);
+		}
+
+		// private stuff
+
+		private bool Compare (byte[] array1, byte[] array2) 
+		{
+			if ((array1 == null) && (array2 == null))
+				return true;
+			if ((array1 == null) || (array2 == null))
+				return false;
+			if (array1.Length != array2.Length)
+				return false;
+			for (int i=0; i < array1.Length; i++) {
+				if (array1 [i] != array2 [i])
+					return false;
+			}
+			return true;
+		}
+
+		// Inner Class
+		
+		public class X509CertificateEnumerator : IEnumerator {
+
+			private IEnumerator enumerator;
+
+			// Constructors
+			
+			public X509CertificateEnumerator (X509CertificateCollection mappings)
+			{
+				enumerator = ((IEnumerable) mappings).GetEnumerator ();
+			}
+
+			// Properties
+			
+			public X509Certificate Current {
+				get { return (X509Certificate) enumerator.Current; }
+			}
+			
+			object IEnumerator.Current {
+				get { return enumerator.Current; }
+			}
+
+			// Methods
+			
+			bool IEnumerator.MoveNext ()
+			{
+				return enumerator.MoveNext ();
+			}
+			
+			void IEnumerator.Reset () 
+			{
+				enumerator.Reset ();
+			}
+			
+			public bool MoveNext () 
+			{
+				return enumerator.MoveNext ();
+			}
+			
+			public void Reset ()
+			{
+				enumerator.Reset ();
+			}
+		}		
+	}
+}

+ 214 - 0
MediaBrowser.Server.Mono/Security/X509Extension.cs

@@ -0,0 +1,214 @@
+//
+// X509Extension.cs: Base class for all X.509 extensions.
+//
+// Author:
+//	Sebastien Pouliot  <sebastien@ximian.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2005 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;
+
+using Mono.Security;
+
+namespace Mono.Security.X509 {
+	/*
+	 * Extension  ::=  SEQUENCE  {
+	 *	extnID      OBJECT IDENTIFIER,
+	 *	critical    BOOLEAN DEFAULT FALSE,
+	 *	extnValue   OCTET STRING  
+	 * }
+	 */
+#if INSIDE_CORLIB
+	internal
+#else
+	public 
+#endif
+	class X509Extension {
+
+		protected string extnOid;
+		protected bool extnCritical;
+		protected ASN1 extnValue;
+
+		protected X509Extension () 
+		{
+			extnCritical = false;
+		}
+
+		public X509Extension (ASN1 asn1) 
+		{
+			if ((asn1.Tag != 0x30) || (asn1.Count < 2))
+				throw new ArgumentException (("Invalid X.509 extension."));
+			if (asn1[0].Tag != 0x06)
+				throw new ArgumentException (("Invalid X.509 extension."));
+
+			extnOid = ASN1Convert.ToOid (asn1[0]);
+			extnCritical = ((asn1[1].Tag == 0x01) && (asn1[1].Value[0] == 0xFF));
+			// last element is an octet string which may need to be decoded
+			extnValue = asn1 [asn1.Count - 1];
+			if ((extnValue.Tag == 0x04) && (extnValue.Length > 0) && (extnValue.Count == 0)) {
+				try {
+					ASN1 encapsulated = new ASN1 (extnValue.Value);
+					extnValue.Value = null;
+					extnValue.Add (encapsulated);
+				}
+				catch {
+					// data isn't ASN.1
+				}
+			}
+			Decode ();
+		}
+
+		public X509Extension (X509Extension extension)
+		{
+			if (extension == null)
+				throw new ArgumentNullException ("extension");
+			if ((extension.Value == null) || (extension.Value.Tag != 0x04) || (extension.Value.Count != 1))
+				throw new ArgumentException (("Invalid X.509 extension."));
+
+			extnOid = extension.Oid;
+			extnCritical = extension.Critical;
+			extnValue = extension.Value;
+			Decode ();
+		}
+
+		// encode the extension *into* an OCTET STRING
+		protected virtual void Decode () 
+		{
+		}
+
+		// decode the extension from *inside* an OCTET STRING
+		protected virtual void Encode ()
+		{
+		}
+
+		public ASN1 ASN1 {
+			get {
+				ASN1 extension = new ASN1 (0x30);
+				extension.Add (ASN1Convert.FromOid (extnOid));
+				if (extnCritical)
+					extension.Add (new ASN1 (0x01, new byte [1] { 0xFF }));
+				Encode ();
+				extension.Add (extnValue);
+				return extension;
+			}
+		}
+
+		public string Oid {
+			get { return extnOid; }
+		}
+
+		public bool Critical {
+			get { return extnCritical; }
+			set { extnCritical = value; }
+		}
+
+		// this gets overrided with more meaningful names
+		public virtual string Name {
+			get { return extnOid; }
+		}
+
+		public ASN1 Value {
+			get {
+				if (extnValue == null) {
+					Encode ();
+				}
+				return extnValue;
+			}
+		}
+
+		public override bool Equals (object obj) 
+		{
+			if (obj == null)
+				return false;
+			
+			X509Extension ex = (obj as X509Extension);
+			if (ex == null)
+				return false;
+
+			if (extnCritical != ex.extnCritical)
+				return false;
+			if (extnOid != ex.extnOid)
+				return false;
+			if (extnValue.Length != ex.extnValue.Length)
+				return false;
+			
+                        for (int i=0; i < extnValue.Length; i++) {
+				if (extnValue [i] != ex.extnValue [i])
+					return false;
+			}
+			return true;
+		}
+
+		public byte[] GetBytes () 
+		{
+			return ASN1.GetBytes ();
+		}
+
+		public override int GetHashCode () 
+		{
+			// OID should be unique in a collection of extensions
+			return extnOid.GetHashCode ();
+		}
+
+		private void WriteLine (StringBuilder sb, int n, int pos) 
+		{
+			byte[] value = extnValue.Value;
+			int p = pos;
+			for (int j=0; j < 8; j++) {
+				if (j < n) {
+					sb.Append (value [p++].ToString ("X2", CultureInfo.InvariantCulture));
+					sb.Append (" ");
+				}
+				else
+					sb.Append ("   ");
+			}
+			sb.Append ("  ");
+			p = pos;
+			for (int j=0; j < n; j++) {
+				byte b = value [p++];
+				if (b < 0x20)
+					sb.Append (".");
+				else
+					sb.Append (Convert.ToChar (b));
+			}
+			sb.Append (Environment.NewLine);
+		}
+
+		public override string ToString () 
+		{
+			StringBuilder sb = new StringBuilder ();
+			int div = (extnValue.Length >> 3);
+			int rem = (extnValue.Length - (div << 3));
+			int x = 0;
+			for (int i=0; i < div; i++) {
+				WriteLine (sb, 8, x);
+				x += 8;
+			}
+			WriteLine (sb, rem, x);
+			return sb.ToString ();
+		}
+	}
+}

+ 201 - 0
MediaBrowser.Server.Mono/Security/X509Extensions.cs

@@ -0,0 +1,201 @@
+//
+// X509Extensions.cs: Handles X.509 extensions.
+//
+// Author:
+//	Sebastien Pouliot  <sebastien@ximian.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 Novell (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.Collections;
+
+using Mono.Security;
+
+namespace Mono.Security.X509 {
+	/*
+	 * Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension
+	 * 
+	 * Note: 1..MAX -> There shouldn't be 0 Extensions in the ASN1 structure
+	 */
+#if INSIDE_CORLIB
+	internal
+#else
+	public 
+#endif
+	sealed class X509ExtensionCollection : CollectionBase, IEnumerable {
+
+		private bool readOnly;
+
+		public X509ExtensionCollection () : base ()
+		{
+		}
+
+		public X509ExtensionCollection (ASN1 asn1) : this ()
+		{
+			readOnly = true;
+			if (asn1 == null)
+				return;
+			if (asn1.Tag != 0x30)
+				throw new Exception ("Invalid extensions format");
+			for (int i=0; i < asn1.Count; i++) {
+				X509Extension extension = new X509Extension (asn1 [i]);
+				InnerList.Add (extension);
+			}
+		}
+
+		public int Add (X509Extension extension) 
+		{
+			if (extension == null)
+				throw new ArgumentNullException ("extension");
+			if (readOnly)
+				throw new NotSupportedException ("Extensions are read only");
+		
+			return InnerList.Add (extension);
+		}
+
+		public void AddRange (X509Extension[] extension) 
+		{
+			if (extension == null)
+				throw new ArgumentNullException ("extension");
+			if (readOnly)
+				throw new NotSupportedException ("Extensions are read only");
+
+			for (int i = 0; i < extension.Length; i++) 
+				InnerList.Add (extension [i]);
+		}
+	
+		public void AddRange (X509ExtensionCollection collection) 
+		{
+			if (collection == null)
+				throw new ArgumentNullException ("collection");
+			if (readOnly)
+				throw new NotSupportedException ("Extensions are read only");
+
+			for (int i = 0; i < collection.InnerList.Count; i++) 
+				InnerList.Add (collection [i]);
+		}
+
+		public bool Contains (X509Extension extension) 
+		{
+			return (IndexOf (extension) != -1);
+		}
+
+		public bool Contains (string oid) 
+		{
+			return (IndexOf (oid) != -1);
+		}
+
+		public void CopyTo (X509Extension[] extensions, int index) 
+		{
+			if (extensions == null)
+				throw new ArgumentNullException ("extensions");
+
+			InnerList.CopyTo (extensions, index);
+		}
+
+		public int IndexOf (X509Extension extension) 
+		{
+			if (extension == null)
+				throw new ArgumentNullException ("extension");
+
+			for (int i=0; i < InnerList.Count; i++) {
+				X509Extension ex = (X509Extension) InnerList [i];
+				if (ex.Equals (extension))
+					return i;
+			}
+			return -1;
+		}
+
+		public int IndexOf (string oid) 
+		{
+			if (oid == null)
+				throw new ArgumentNullException ("oid");
+
+			for (int i=0; i < InnerList.Count; i++) {
+				X509Extension ex = (X509Extension) InnerList [i];
+				if (ex.Oid == oid)
+					return i;
+			}
+			return -1;
+		}
+
+		public void Insert (int index, X509Extension extension) 
+		{
+			if (extension == null)
+				throw new ArgumentNullException ("extension");
+
+			InnerList.Insert (index, extension);
+		}
+
+		public void Remove (X509Extension extension) 
+		{
+			if (extension == null)
+				throw new ArgumentNullException ("extension");
+
+			InnerList.Remove (extension);
+		}
+
+		public void Remove (string oid) 
+		{
+			if (oid == null)
+				throw new ArgumentNullException ("oid");
+
+			int index = IndexOf (oid);
+			if (index != -1)
+				InnerList.RemoveAt (index);
+		}
+
+		IEnumerator IEnumerable.GetEnumerator () 
+		{
+			return InnerList.GetEnumerator ();
+		}
+
+		public X509Extension this [int index] {
+			get { return (X509Extension) InnerList [index]; }
+		}
+
+		public X509Extension this [string oid] {
+			get {
+				int index = IndexOf (oid);
+				if (index == -1)
+					return null;
+				return (X509Extension) InnerList [index];
+			}
+		}
+
+		public byte[] GetBytes () 
+		{
+			if (InnerList.Count < 1)
+				return null;
+			ASN1 sequence = new ASN1 (0x30);
+			for (int i=0; i < InnerList.Count; i++) {
+				X509Extension x = (X509Extension) InnerList [i];
+				sequence.Add (x.ASN1);
+			}
+			return sequence.GetBytes ();
+		}
+	}
+}

+ 353 - 0
MediaBrowser.Server.Mono/Security/X520Attributes.cs

@@ -0,0 +1,353 @@
+//
+// X520.cs: X.520 related stuff (attributes, RDN)
+//
+// Author:
+//	Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2005 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;
+
+using Mono.Security;
+
+namespace Mono.Security.X509 {
+
+	// References:
+	// 1.	Information technology - Open Systems Interconnection - The Directory: Selected attribute types 
+	//	http://www.itu.int/rec/recommendation.asp?type=folders&lang=e&parent=T-REC-X.520 
+	// 2.	Internet X.509 Public Key Infrastructure Certificate and CRL Profile
+	//	http://www.ietf.org/rfc/rfc3280.txt
+	// 3.	A Summary of the X.500(96) User Schema for use with LDAPv3
+	//	http://www.faqs.org/rfcs/rfc2256.html
+	// 4.	RFC 2247 - Using Domains in LDAP/X.500 Distinguished Names
+	//	http://www.faqs.org/rfcs/rfc2247.html
+
+	/* 
+	 * AttributeTypeAndValue ::= SEQUENCE {
+	 * 	type     AttributeType,
+	 * 	value    AttributeValue 
+	 * }
+	 * 
+	 * AttributeType ::= OBJECT IDENTIFIER
+	 * 
+	 * AttributeValue ::= ANY DEFINED BY AttributeType
+	 */
+#if INSIDE_CORLIB
+	internal
+#else
+	public 
+#endif
+	class X520 {
+
+		public abstract class AttributeTypeAndValue {
+			private string oid;
+			private string attrValue;
+			private int upperBound;
+			private byte encoding;
+
+			protected AttributeTypeAndValue (string oid, int upperBound)
+			{
+				this.oid = oid;
+				this.upperBound = upperBound;
+				this.encoding = 0xFF;
+			}
+
+			protected AttributeTypeAndValue (string oid, int upperBound, byte encoding) 
+			{
+				this.oid = oid;
+				this.upperBound = upperBound;
+				this.encoding = encoding;
+			}
+
+			public string Value {
+				get { return attrValue; }
+				set { 
+					if ((attrValue != null) && (attrValue.Length > upperBound)) {
+						string msg = ("Value length bigger than upperbound ({0}).");
+						throw new FormatException (String.Format (msg, upperBound));
+					}
+					attrValue = value; 
+				}
+			}
+
+			public ASN1 ASN1 {
+				get { return GetASN1 (); }
+			}
+
+			internal ASN1 GetASN1 (byte encoding) 
+			{
+				byte encode = encoding;
+				if (encode == 0xFF)
+					encode = SelectBestEncoding ();
+					
+				ASN1 asn1 = new ASN1 (0x30);
+				asn1.Add (ASN1Convert.FromOid (oid));
+				switch (encode) {
+					case 0x13:
+						// PRINTABLESTRING
+						asn1.Add (new ASN1 (0x13, Encoding.ASCII.GetBytes (attrValue)));
+						break;
+					case 0x16:
+						// IA5STRING
+						asn1.Add (new ASN1 (0x16, Encoding.ASCII.GetBytes (attrValue)));
+						break;
+					case 0x1E:
+						// BMPSTRING
+						asn1.Add (new ASN1 (0x1E, Encoding.BigEndianUnicode.GetBytes (attrValue)));
+						break;
+				}
+				return asn1;
+			}
+
+			internal ASN1 GetASN1 () 
+			{
+				return GetASN1 (encoding);
+			}
+
+			public byte[] GetBytes (byte encoding) 
+			{
+				return GetASN1 (encoding) .GetBytes ();
+			}
+
+			public byte[] GetBytes () 
+			{
+				return GetASN1 () .GetBytes ();
+			}
+
+			private byte SelectBestEncoding ()
+			{
+				foreach (char c in attrValue) {
+					switch (c) {
+					case '@':
+					case '_':
+						return 0x1E; // BMPSTRING
+					default:
+						if (c > 127)
+							return 0x1E; // BMPSTRING
+						break;
+					}
+				}
+				return 0x13; // PRINTABLESTRING
+			}
+		}
+
+		public class Name : AttributeTypeAndValue {
+
+			public Name () : base ("2.5.4.41", 32768) 
+			{
+			}
+		}
+
+		public class CommonName : AttributeTypeAndValue {
+
+			public CommonName () : base ("2.5.4.3", 64) 
+			{
+			}
+		}
+
+		// RFC2256, Section 5.6
+		public class SerialNumber : AttributeTypeAndValue {
+
+			// max length 64 bytes, Printable String only
+			public SerialNumber ()
+				: base ("2.5.4.5", 64, 0x13)
+			{
+			}
+		}
+
+		public class LocalityName : AttributeTypeAndValue {
+
+			public LocalityName () : base ("2.5.4.7", 128)
+			{
+			}
+		}
+
+		public class StateOrProvinceName : AttributeTypeAndValue {
+
+			public StateOrProvinceName () : base ("2.5.4.8", 128) 
+			{
+			}
+		}
+		 
+		public class OrganizationName : AttributeTypeAndValue {
+
+			public OrganizationName () : base ("2.5.4.10", 64)
+			{
+			}
+		}
+		 
+		public class OrganizationalUnitName : AttributeTypeAndValue {
+
+			public OrganizationalUnitName () : base ("2.5.4.11", 64)
+			{
+			}
+		}
+
+		// NOTE: Not part of RFC2253
+		public class EmailAddress : AttributeTypeAndValue {
+
+			public EmailAddress () : base ("1.2.840.113549.1.9.1", 128, 0x16)
+			{
+			}
+		}
+
+		// RFC2247, Section 4
+		public class DomainComponent : AttributeTypeAndValue {
+
+			// no maximum length defined
+			public DomainComponent ()
+				: base ("0.9.2342.19200300.100.1.25", Int32.MaxValue, 0x16)
+			{
+			}
+		}
+
+		// RFC1274, Section 9.3.1
+		public class UserId : AttributeTypeAndValue {
+
+			public UserId ()
+				: base ("0.9.2342.19200300.100.1.1", 256)
+			{
+			}
+		}
+
+		public class Oid : AttributeTypeAndValue {
+
+			public Oid (string oid)
+				: base (oid, Int32.MaxValue)
+			{
+			}
+		}
+
+		/* -- Naming attributes of type X520Title
+		 * id-at-title             AttributeType ::= { id-at 12 }
+		 * 
+		 * X520Title ::= CHOICE {
+		 *       teletexString     TeletexString   (SIZE (1..ub-title)),
+		 *       printableString   PrintableString (SIZE (1..ub-title)),
+		 *       universalString   UniversalString (SIZE (1..ub-title)),
+		 *       utf8String        UTF8String      (SIZE (1..ub-title)),
+		 *       bmpString         BMPString       (SIZE (1..ub-title)) 
+		 * }
+		 */
+		public class Title : AttributeTypeAndValue {
+
+			public Title () : base ("2.5.4.12", 64)
+			{
+			}
+		}
+
+		public class CountryName : AttributeTypeAndValue {
+
+			// (0x13) PRINTABLESTRING
+			public CountryName () : base ("2.5.4.6", 2, 0x13) 
+			{
+			}
+		}
+
+		public class DnQualifier : AttributeTypeAndValue {
+
+			// (0x13) PRINTABLESTRING
+			public DnQualifier () : base ("2.5.4.46", 2, 0x13) 
+			{
+			}
+		}
+
+		public class Surname : AttributeTypeAndValue {
+
+			public Surname () : base ("2.5.4.4", 32768) 
+			{
+			}
+		}
+
+		public class GivenName : AttributeTypeAndValue {
+
+			public GivenName () : base ("2.5.4.42", 16) 
+			{
+			}
+		}
+
+		public class Initial : AttributeTypeAndValue {
+
+			public Initial () : base ("2.5.4.43", 5) 
+			{
+			}
+		}
+
+	}
+        
+	/* From RFC3280
+	 * --  specifications of Upper Bounds MUST be regarded as mandatory
+	 * --  from Annex B of ITU-T X.411 Reference Definition of MTS Parameter
+	 * 
+	 * --  Upper Bounds
+	 * 
+	 * ub-name INTEGER ::= 32768
+	 * ub-common-name INTEGER ::= 64
+	 * ub-locality-name INTEGER ::= 128
+	 * ub-state-name INTEGER ::= 128
+	 * ub-organization-name INTEGER ::= 64
+	 * ub-organizational-unit-name INTEGER ::= 64
+	 * ub-title INTEGER ::= 64
+	 * ub-serial-number INTEGER ::= 64
+	 * ub-match INTEGER ::= 128
+	 * ub-emailaddress-length INTEGER ::= 128
+	 * ub-common-name-length INTEGER ::= 64
+	 * ub-country-name-alpha-length INTEGER ::= 2
+	 * ub-country-name-numeric-length INTEGER ::= 3
+	 * ub-domain-defined-attributes INTEGER ::= 4
+	 * ub-domain-defined-attribute-type-length INTEGER ::= 8
+	 * ub-domain-defined-attribute-value-length INTEGER ::= 128
+	 * ub-domain-name-length INTEGER ::= 16
+	 * ub-extension-attributes INTEGER ::= 256
+	 * ub-e163-4-number-length INTEGER ::= 15
+	 * ub-e163-4-sub-address-length INTEGER ::= 40
+	 * ub-generation-qualifier-length INTEGER ::= 3
+	 * ub-given-name-length INTEGER ::= 16
+	 * ub-initials-length INTEGER ::= 5
+	 * ub-integer-options INTEGER ::= 256
+	 * ub-numeric-user-id-length INTEGER ::= 32
+	 * ub-organization-name-length INTEGER ::= 64
+	 * ub-organizational-unit-name-length INTEGER ::= 32
+	 * ub-organizational-units INTEGER ::= 4
+	 * ub-pds-name-length INTEGER ::= 16
+	 * ub-pds-parameter-length INTEGER ::= 30
+	 * ub-pds-physical-address-lines INTEGER ::= 6
+	 * ub-postal-code-length INTEGER ::= 16
+	 * ub-pseudonym INTEGER ::= 128
+	 * ub-surname-length INTEGER ::= 40
+	 * ub-terminal-id-length INTEGER ::= 24
+	 * ub-unformatted-address-length INTEGER ::= 180
+	 * ub-x121-address-length INTEGER ::= 16
+	 * 
+	 * -- Note - upper bounds on string types, such as TeletexString, are
+	 * -- measured in characters.  Excepting PrintableString or IA5String, a
+	 * -- significantly greater number of octets will be required to hold
+	 * -- such a value.  As a minimum, 16 octets, or twice the specified
+	 * -- upper bound, whichever is the larger, should be allowed for
+	 * -- TeletexString.  For UTF8String or UniversalString at least four
+	 * -- times the upper bound should be allowed.
+	 */
+}