diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
index 45071c9d9e..af2df60566 100644
--- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
+++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
@@ -23,6 +23,7 @@
4
x86
true
+ true
full
@@ -60,11 +61,6 @@
False
..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll
-
- False
- ..\ThirdParty\Mono.Security\Mono.Security.dll
- False
-
False
..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll
@@ -95,6 +91,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MediaBrowser.Server.Mono/Security/ASN1.cs b/MediaBrowser.Server.Mono/Security/ASN1.cs
new file mode 100644
index 0000000000..751a2ece4e
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/ASN1.cs
@@ -0,0 +1,344 @@
+//
+// ASN1.cs: Abstract Syntax Notation 1 - micro-parser and generator
+//
+// Authors:
+// Sebastien Pouliot
+// Jesper Pedersen
+//
+// (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);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/ASN1Convert.cs b/MediaBrowser.Server.Mono/Security/ASN1Convert.cs
new file mode 100644
index 0000000000..3a1cf93018
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/ASN1Convert.cs
@@ -0,0 +1,212 @@
+//
+// ASN1Convert.cs: Abstract Syntax Notation 1 convertion routines
+//
+// Authors:
+// Sebastien Pouliot
+// Jesper Pedersen
+//
+// (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);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/BitConverterLE.cs b/MediaBrowser.Server.Mono/Security/BitConverterLE.cs
new file mode 100644
index 0000000000..a787f5015c
--- /dev/null
+++ b/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;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/CryptoConvert.cs b/MediaBrowser.Server.Mono/Security/CryptoConvert.cs
new file mode 100644
index 0000000000..3f06114dd0
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/CryptoConvert.cs
@@ -0,0 +1,754 @@
+//
+// CryptoConvert.cs - Crypto Convertion Routines
+//
+// Author:
+// Sebastien Pouliot
+//
+// (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;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/PKCS1.cs b/MediaBrowser.Server.Mono/Security/PKCS1.cs
new file mode 100644
index 0000000000..4e579eee94
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/PKCS1.cs
@@ -0,0 +1,495 @@
+//
+// PKCS1.cs - Implements PKCS#1 primitives.
+//
+// Author:
+// Sebastien Pouliot
+//
+// (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
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/PKCS12.cs b/MediaBrowser.Server.Mono/Security/PKCS12.cs
new file mode 100644
index 0000000000..fe39b70365
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/PKCS12.cs
@@ -0,0 +1,2001 @@
+//
+// PKCS12.cs: PKCS 12 - Personal Information Exchange Syntax
+//
+// Author:
+// Sebastien Pouliot
+//
+// (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);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/PKCS7.cs b/MediaBrowser.Server.Mono/Security/PKCS7.cs
new file mode 100644
index 0000000000..d7e93569d8
--- /dev/null
+++ b/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
+// Daniel Granath
+//
+// (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;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/PKCS8.cs b/MediaBrowser.Server.Mono/Security/PKCS8.cs
new file mode 100644
index 0000000000..352e4649b0
--- /dev/null
+++ b/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
+//
+// (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 ();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X501Name.cs b/MediaBrowser.Server.Mono/Security/X501Name.cs
new file mode 100644
index 0000000000..54279c1d25
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X501Name.cs
@@ -0,0 +1,400 @@
+//
+// X501Name.cs: X.501 Distinguished Names stuff
+//
+// Author:
+// Sebastien Pouliot
+//
+// (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;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509Builder.cs b/MediaBrowser.Server.Mono/Security/X509Builder.cs
new file mode 100644
index 0000000000..95926ae3fd
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Builder.cs
@@ -0,0 +1,154 @@
+//
+// X509Builder.cs: Abstract builder class for X509 objects
+//
+// Author:
+// Sebastien Pouliot
+//
+// (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 ());
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509Certificate.cs b/MediaBrowser.Server.Mono/Security/X509Certificate.cs
new file mode 100644
index 0000000000..5b356e2e2c
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Certificate.cs
@@ -0,0 +1,567 @@
+//
+// X509Certificates.cs: Handles X.509 certificates.
+//
+// Author:
+// Sebastien Pouliot
+//
+// (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);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509CertificateBuilder.cs b/MediaBrowser.Server.Mono/Security/X509CertificateBuilder.cs
new file mode 100644
index 0000000000..3ee2816283
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509CertificateBuilder.cs
@@ -0,0 +1,244 @@
+//
+// X509CertificateBuilder.cs: Handles building of X.509 certificates.
+//
+// Author:
+// Sebastien Pouliot
+//
+// (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;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509CertificateCollection.cs b/MediaBrowser.Server.Mono/Security/X509CertificateCollection.cs
new file mode 100644
index 0000000000..3fd834b0cf
--- /dev/null
+++ b/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
+//
+
+//
+// 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 ();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509Extension.cs b/MediaBrowser.Server.Mono/Security/X509Extension.cs
new file mode 100644
index 0000000000..202be51320
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Extension.cs
@@ -0,0 +1,214 @@
+//
+// X509Extension.cs: Base class for all X.509 extensions.
+//
+// Author:
+// Sebastien Pouliot
+//
+// (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 ();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509Extensions.cs b/MediaBrowser.Server.Mono/Security/X509Extensions.cs
new file mode 100644
index 0000000000..cb8e15defa
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Extensions.cs
@@ -0,0 +1,201 @@
+//
+// X509Extensions.cs: Handles X.509 extensions.
+//
+// Author:
+// Sebastien Pouliot
+//
+// (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 ();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X520Attributes.cs b/MediaBrowser.Server.Mono/Security/X520Attributes.cs
new file mode 100644
index 0000000000..53ad424b51
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X520Attributes.cs
@@ -0,0 +1,353 @@
+//
+// X520.cs: X.520 related stuff (attributes, RDN)
+//
+// Author:
+// Sebastien Pouliot
+//
+// (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.
+ */
+}