parent
5b0b6e1d3f
commit
76e6c220d6
@ -0,0 +1,344 @@
|
||||
//
|
||||
// ASN1.cs: Abstract Syntax Notation 1 - micro-parser and generator
|
||||
//
|
||||
// Authors:
|
||||
// Sebastien Pouliot <sebastien@ximian.com>
|
||||
// Jesper Pedersen <jep@itplus.dk>
|
||||
//
|
||||
// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
|
||||
// (C) 2004 IT+ A/S (http://www.itplus.dk)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Mono.Security {
|
||||
|
||||
// References:
|
||||
// a. ITU ASN.1 standards (free download)
|
||||
// http://www.itu.int/ITU-T/studygroups/com17/languages/
|
||||
|
||||
#if INSIDE_CORLIB
|
||||
internal
|
||||
#else
|
||||
public
|
||||
#endif
|
||||
class ASN1 {
|
||||
|
||||
private byte m_nTag;
|
||||
private byte[] m_aValue;
|
||||
private ArrayList elist;
|
||||
|
||||
public ASN1 () : this (0x00, null) {}
|
||||
|
||||
public ASN1 (byte tag) : this (tag, null) {}
|
||||
|
||||
public ASN1 (byte tag, byte[] data)
|
||||
{
|
||||
m_nTag = tag;
|
||||
m_aValue = data;
|
||||
}
|
||||
|
||||
public ASN1 (byte[] data)
|
||||
{
|
||||
m_nTag = data [0];
|
||||
|
||||
int nLenLength = 0;
|
||||
int nLength = data [1];
|
||||
|
||||
if (nLength > 0x80) {
|
||||
// composed length
|
||||
nLenLength = nLength - 0x80;
|
||||
nLength = 0;
|
||||
for (int i = 0; i < nLenLength; i++) {
|
||||
nLength *= 256;
|
||||
nLength += data [i + 2];
|
||||
}
|
||||
}
|
||||
else if (nLength == 0x80) {
|
||||
// undefined length encoding
|
||||
throw new NotSupportedException ("Undefined length encoding.");
|
||||
}
|
||||
|
||||
m_aValue = new byte [nLength];
|
||||
Buffer.BlockCopy (data, (2 + nLenLength), m_aValue, 0, nLength);
|
||||
|
||||
if ((m_nTag & 0x20) == 0x20) {
|
||||
int nStart = (2 + nLenLength);
|
||||
Decode (data, ref nStart, data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count {
|
||||
get {
|
||||
if (elist == null)
|
||||
return 0;
|
||||
return elist.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public byte Tag {
|
||||
get { return m_nTag; }
|
||||
}
|
||||
|
||||
public int Length {
|
||||
get {
|
||||
if (m_aValue != null)
|
||||
return m_aValue.Length;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Value {
|
||||
get {
|
||||
if (m_aValue == null)
|
||||
GetBytes ();
|
||||
return (byte[]) m_aValue.Clone ();
|
||||
}
|
||||
set {
|
||||
if (value != null)
|
||||
m_aValue = (byte[]) value.Clone ();
|
||||
}
|
||||
}
|
||||
|
||||
private bool CompareArray (byte[] array1, byte[] array2)
|
||||
{
|
||||
bool bResult = (array1.Length == array2.Length);
|
||||
if (bResult) {
|
||||
for (int i = 0; i < array1.Length; i++) {
|
||||
if (array1[i] != array2[i])
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return bResult;
|
||||
}
|
||||
|
||||
public bool Equals (byte[] asn1)
|
||||
{
|
||||
return CompareArray (this.GetBytes (), asn1);
|
||||
}
|
||||
|
||||
public bool CompareValue (byte[] value)
|
||||
{
|
||||
return CompareArray (m_aValue, value);
|
||||
}
|
||||
|
||||
public ASN1 Add (ASN1 asn1)
|
||||
{
|
||||
if (asn1 != null) {
|
||||
if (elist == null)
|
||||
elist = new ArrayList ();
|
||||
elist.Add (asn1);
|
||||
}
|
||||
return asn1;
|
||||
}
|
||||
|
||||
public virtual byte[] GetBytes ()
|
||||
{
|
||||
byte[] val = null;
|
||||
|
||||
if (Count > 0) {
|
||||
int esize = 0;
|
||||
ArrayList al = new ArrayList ();
|
||||
foreach (ASN1 a in elist) {
|
||||
byte[] item = a.GetBytes ();
|
||||
al.Add (item);
|
||||
esize += item.Length;
|
||||
}
|
||||
val = new byte [esize];
|
||||
int pos = 0;
|
||||
for (int i=0; i < elist.Count; i++) {
|
||||
byte[] item = (byte[]) al[i];
|
||||
Buffer.BlockCopy (item, 0, val, pos, item.Length);
|
||||
pos += item.Length;
|
||||
}
|
||||
} else if (m_aValue != null) {
|
||||
val = m_aValue;
|
||||
}
|
||||
|
||||
byte[] der;
|
||||
int nLengthLen = 0;
|
||||
|
||||
if (val != null) {
|
||||
int nLength = val.Length;
|
||||
// special for length > 127
|
||||
if (nLength > 127) {
|
||||
if (nLength <= Byte.MaxValue) {
|
||||
der = new byte [3 + nLength];
|
||||
Buffer.BlockCopy (val, 0, der, 3, nLength);
|
||||
nLengthLen = 0x81;
|
||||
der[2] = (byte)(nLength);
|
||||
}
|
||||
else if (nLength <= UInt16.MaxValue) {
|
||||
der = new byte [4 + nLength];
|
||||
Buffer.BlockCopy (val, 0, der, 4, nLength);
|
||||
nLengthLen = 0x82;
|
||||
der[2] = (byte)(nLength >> 8);
|
||||
der[3] = (byte)(nLength);
|
||||
}
|
||||
else if (nLength <= 0xFFFFFF) {
|
||||
// 24 bits
|
||||
der = new byte [5 + nLength];
|
||||
Buffer.BlockCopy (val, 0, der, 5, nLength);
|
||||
nLengthLen = 0x83;
|
||||
der [2] = (byte)(nLength >> 16);
|
||||
der [3] = (byte)(nLength >> 8);
|
||||
der [4] = (byte)(nLength);
|
||||
}
|
||||
else {
|
||||
// max (Length is an integer) 32 bits
|
||||
der = new byte [6 + nLength];
|
||||
Buffer.BlockCopy (val, 0, der, 6, nLength);
|
||||
nLengthLen = 0x84;
|
||||
der [2] = (byte)(nLength >> 24);
|
||||
der [3] = (byte)(nLength >> 16);
|
||||
der [4] = (byte)(nLength >> 8);
|
||||
der [5] = (byte)(nLength);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// basic case (no encoding)
|
||||
der = new byte [2 + nLength];
|
||||
Buffer.BlockCopy (val, 0, der, 2, nLength);
|
||||
nLengthLen = nLength;
|
||||
}
|
||||
if (m_aValue == null)
|
||||
m_aValue = val;
|
||||
}
|
||||
else
|
||||
der = new byte[2];
|
||||
|
||||
der[0] = m_nTag;
|
||||
der[1] = (byte)nLengthLen;
|
||||
|
||||
return der;
|
||||
}
|
||||
|
||||
// Note: Recursive
|
||||
protected void Decode (byte[] asn1, ref int anPos, int anLength)
|
||||
{
|
||||
byte nTag;
|
||||
int nLength;
|
||||
byte[] aValue;
|
||||
|
||||
// minimum is 2 bytes (tag + length of 0)
|
||||
while (anPos < anLength - 1) {
|
||||
DecodeTLV (asn1, ref anPos, out nTag, out nLength, out aValue);
|
||||
// sometimes we get trailing 0
|
||||
if (nTag == 0)
|
||||
continue;
|
||||
|
||||
ASN1 elm = Add (new ASN1 (nTag, aValue));
|
||||
|
||||
if ((nTag & 0x20) == 0x20) {
|
||||
int nConstructedPos = anPos;
|
||||
elm.Decode (asn1, ref nConstructedPos, nConstructedPos + nLength);
|
||||
}
|
||||
anPos += nLength; // value length
|
||||
}
|
||||
}
|
||||
|
||||
// TLV : Tag - Length - Value
|
||||
protected void DecodeTLV (byte[] asn1, ref int pos, out byte tag, out int length, out byte[] content)
|
||||
{
|
||||
tag = asn1 [pos++];
|
||||
length = asn1 [pos++];
|
||||
|
||||
// special case where L contains the Length of the Length + 0x80
|
||||
if ((length & 0x80) == 0x80) {
|
||||
int nLengthLen = length & 0x7F;
|
||||
length = 0;
|
||||
for (int i = 0; i < nLengthLen; i++)
|
||||
length = length * 256 + asn1 [pos++];
|
||||
}
|
||||
|
||||
content = new byte [length];
|
||||
Buffer.BlockCopy (asn1, pos, content, 0, length);
|
||||
}
|
||||
|
||||
public ASN1 this [int index] {
|
||||
get {
|
||||
try {
|
||||
if ((elist == null) || (index >= elist.Count))
|
||||
return null;
|
||||
return (ASN1) elist [index];
|
||||
}
|
||||
catch (ArgumentOutOfRangeException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ASN1 Element (int index, byte anTag)
|
||||
{
|
||||
try {
|
||||
if ((elist == null) || (index >= elist.Count))
|
||||
return null;
|
||||
|
||||
ASN1 elm = (ASN1) elist [index];
|
||||
if (elm.Tag == anTag)
|
||||
return elm;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
catch (ArgumentOutOfRangeException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder hexLine = new StringBuilder ();
|
||||
|
||||
// Add tag
|
||||
hexLine.AppendFormat ("Tag: {0} {1}", m_nTag.ToString ("X2"), Environment.NewLine);
|
||||
|
||||
// Add length
|
||||
hexLine.AppendFormat ("Length: {0} {1}", Value.Length, Environment.NewLine);
|
||||
|
||||
// Add value
|
||||
hexLine.Append ("Value: ");
|
||||
hexLine.Append (Environment.NewLine);
|
||||
for (int i = 0; i < Value.Length; i++) {
|
||||
hexLine.AppendFormat ("{0} ", Value [i].ToString ("X2"));
|
||||
if ((i+1) % 16 == 0)
|
||||
hexLine.AppendFormat (Environment.NewLine);
|
||||
}
|
||||
return hexLine.ToString ();
|
||||
}
|
||||
|
||||
public void SaveToFile (string filename)
|
||||
{
|
||||
if (filename == null)
|
||||
throw new ArgumentNullException ("filename");
|
||||
|
||||
using (FileStream fs = File.Create (filename)) {
|
||||
byte[] data = GetBytes ();
|
||||
fs.Write (data, 0, data.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
//
|
||||
// ASN1Convert.cs: Abstract Syntax Notation 1 convertion routines
|
||||
//
|
||||
// Authors:
|
||||
// Sebastien Pouliot <sebastien@ximian.com>
|
||||
// Jesper Pedersen <jep@itplus.dk>
|
||||
//
|
||||
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// (C) 2004 IT+ A/S (http://www.itplus.dk)
|
||||
// Copyright (C) 2004-2007 Novell, Inc (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Mono.Security {
|
||||
|
||||
// References:
|
||||
// a. ITU ASN.1 standards (free download)
|
||||
// http://www.itu.int/ITU-T/studygroups/com17/languages/
|
||||
|
||||
#if INSIDE_CORLIB
|
||||
internal
|
||||
#else
|
||||
public
|
||||
#endif
|
||||
static class ASN1Convert {
|
||||
// RFC3280, section 4.2.1.5
|
||||
// CAs conforming to this profile MUST always encode certificate
|
||||
// validity dates through the year 2049 as UTCTime; certificate validity
|
||||
// dates in 2050 or later MUST be encoded as GeneralizedTime.
|
||||
|
||||
// Under 1.x this API requires a Local datetime to be provided
|
||||
// Under 2.0 it will also accept a Utc datetime
|
||||
static public ASN1 FromDateTime (DateTime dt)
|
||||
{
|
||||
if (dt.Year < 2050) {
|
||||
// UTCTIME
|
||||
return new ASN1 (0x17, Encoding.ASCII.GetBytes (
|
||||
dt.ToUniversalTime ().ToString ("yyMMddHHmmss",
|
||||
CultureInfo.InvariantCulture) + "Z"));
|
||||
}
|
||||
else {
|
||||
// GENERALIZEDTIME
|
||||
return new ASN1 (0x18, Encoding.ASCII.GetBytes (
|
||||
dt.ToUniversalTime ().ToString ("yyyyMMddHHmmss",
|
||||
CultureInfo.InvariantCulture) + "Z"));
|
||||
}
|
||||
}
|
||||
|
||||
static public ASN1 FromInt32 (Int32 value)
|
||||
{
|
||||
byte[] integer = BitConverterLE.GetBytes (value);
|
||||
Array.Reverse (integer);
|
||||
int x = 0;
|
||||
while ((x < integer.Length) && (integer [x] == 0x00))
|
||||
x++;
|
||||
ASN1 asn1 = new ASN1 (0x02);
|
||||
switch (x) {
|
||||
case 0:
|
||||
asn1.Value = integer;
|
||||
break;
|
||||
case 4:
|
||||
asn1.Value = new byte [1];
|
||||
break;
|
||||
default:
|
||||
byte[] smallerInt = new byte [4 - x];
|
||||
Buffer.BlockCopy (integer, x, smallerInt, 0, smallerInt.Length);
|
||||
asn1.Value = smallerInt;
|
||||
break;
|
||||
}
|
||||
return asn1;
|
||||
}
|
||||
|
||||
static public ASN1 FromOid (string oid)
|
||||
{
|
||||
if (oid == null)
|
||||
throw new ArgumentNullException ("oid");
|
||||
|
||||
return new ASN1 (CryptoConfig.EncodeOID (oid));
|
||||
}
|
||||
|
||||
static public ASN1 FromUnsignedBigInteger (byte[] big)
|
||||
{
|
||||
if (big == null)
|
||||
throw new ArgumentNullException ("big");
|
||||
|
||||
// check for numbers that could be interpreted as negative (first bit)
|
||||
if (big [0] >= 0x80) {
|
||||
// in thie cas we add a new, empty, byte (position 0) so we're
|
||||
// sure this will always be interpreted an unsigned integer.
|
||||
// However we can't feed it into RSAParameters or DSAParameters
|
||||
int length = big.Length + 1;
|
||||
byte[] uinteger = new byte [length];
|
||||
Buffer.BlockCopy (big, 0, uinteger, 1, length - 1);
|
||||
big = uinteger;
|
||||
}
|
||||
return new ASN1 (0x02, big);
|
||||
}
|
||||
|
||||
static public int ToInt32 (ASN1 asn1)
|
||||
{
|
||||
if (asn1 == null)
|
||||
throw new ArgumentNullException ("asn1");
|
||||
if (asn1.Tag != 0x02)
|
||||
throw new FormatException ("Only integer can be converted");
|
||||
|
||||
int x = 0;
|
||||
for (int i=0; i < asn1.Value.Length; i++)
|
||||
x = (x << 8) + asn1.Value [i];
|
||||
return x;
|
||||
}
|
||||
|
||||
// Convert a binary encoded OID to human readable string representation of
|
||||
// an OID (IETF style). Based on DUMPASN1.C from Peter Gutmann.
|
||||
static public string ToOid (ASN1 asn1)
|
||||
{
|
||||
if (asn1 == null)
|
||||
throw new ArgumentNullException ("asn1");
|
||||
|
||||
byte[] aOID = asn1.Value;
|
||||
StringBuilder sb = new StringBuilder ();
|
||||
// Pick apart the OID
|
||||
byte x = (byte) (aOID[0] / 40);
|
||||
byte y = (byte) (aOID[0] % 40);
|
||||
if (x > 2) {
|
||||
// Handle special case for large y if x = 2
|
||||
y += (byte) ((x - 2) * 40);
|
||||
x = 2;
|
||||
}
|
||||
sb.Append (x.ToString (CultureInfo.InvariantCulture));
|
||||
sb.Append (".");
|
||||
sb.Append (y.ToString (CultureInfo.InvariantCulture));
|
||||
ulong val = 0;
|
||||
for (x = 1; x < aOID.Length; x++) {
|
||||
val = ((val << 7) | ((byte) (aOID [x] & 0x7F)));
|
||||
if ( !((aOID [x] & 0x80) == 0x80)) {
|
||||
sb.Append (".");
|
||||
sb.Append (val.ToString (CultureInfo.InvariantCulture));
|
||||
val = 0;
|
||||
}
|
||||
}
|
||||
return sb.ToString ();
|
||||
}
|
||||
|
||||
static public DateTime ToDateTime (ASN1 time)
|
||||
{
|
||||
if (time == null)
|
||||
throw new ArgumentNullException ("time");
|
||||
|
||||
string t = Encoding.ASCII.GetString (time.Value);
|
||||
// to support both UTCTime and GeneralizedTime (and not so common format)
|
||||
string mask = null;
|
||||
int year;
|
||||
switch (t.Length) {
|
||||
case 11:
|
||||
// illegal format, still it's supported for compatibility
|
||||
mask = "yyMMddHHmmZ";
|
||||
break;
|
||||
case 13:
|
||||
// RFC3280: 4.1.2.5.1 UTCTime
|
||||
year = Convert.ToInt16 (t.Substring (0, 2), CultureInfo.InvariantCulture);
|
||||
// Where YY is greater than or equal to 50, the
|
||||
// year SHALL be interpreted as 19YY; and
|
||||
// Where YY is less than 50, the year SHALL be
|
||||
// interpreted as 20YY.
|
||||
if (year >= 50)
|
||||
t = "19" + t;
|
||||
else
|
||||
t = "20" + t;
|
||||
mask = "yyyyMMddHHmmssZ";
|
||||
break;
|
||||
case 15:
|
||||
mask = "yyyyMMddHHmmssZ"; // GeneralizedTime
|
||||
break;
|
||||
case 17:
|
||||
// another illegal format (990630000000+1000), again supported for compatibility
|
||||
year = Convert.ToInt16 (t.Substring (0, 2), CultureInfo.InvariantCulture);
|
||||
string century = (year >= 50) ? "19" : "20";
|
||||
// ASN.1 (see ITU X.680 section 43.3) deals with offset differently than .NET
|
||||
char sign = (t[12] == '+') ? '-' : '+';
|
||||
t = String.Format ("{0}{1}{2}{3}{4}:{5}{6}", century, t.Substring (0, 12), sign,
|
||||
t[13], t[14], t[15], t[16]);
|
||||
mask = "yyyyMMddHHmmsszzz";
|
||||
break;
|
||||
}
|
||||
return DateTime.ParseExact (t, mask, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,754 @@
|
||||
//
|
||||
// CryptoConvert.cs - Crypto Convertion Routines
|
||||
//
|
||||
// Author:
|
||||
// Sebastien Pouliot <sebastien@ximian.com>
|
||||
//
|
||||
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Mono.Security.Cryptography {
|
||||
|
||||
#if INSIDE_CORLIB
|
||||
internal
|
||||
#else
|
||||
public
|
||||
#endif
|
||||
sealed class CryptoConvert {
|
||||
|
||||
private CryptoConvert ()
|
||||
{
|
||||
}
|
||||
|
||||
static private int ToInt32LE (byte [] bytes, int offset)
|
||||
{
|
||||
return (bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset];
|
||||
}
|
||||
|
||||
static private uint ToUInt32LE (byte [] bytes, int offset)
|
||||
{
|
||||
return (uint)((bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset]);
|
||||
}
|
||||
|
||||
static private byte [] GetBytesLE (int val)
|
||||
{
|
||||
return new byte [] {
|
||||
(byte) (val & 0xff),
|
||||
(byte) ((val >> 8) & 0xff),
|
||||
(byte) ((val >> 16) & 0xff),
|
||||
(byte) ((val >> 24) & 0xff)
|
||||
};
|
||||
}
|
||||
|
||||
static private byte[] Trim (byte[] array)
|
||||
{
|
||||
for (int i=0; i < array.Length; i++) {
|
||||
if (array [i] != 0x00) {
|
||||
byte[] result = new byte [array.Length - i];
|
||||
Buffer.BlockCopy (array, i, result, 0, result.Length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// convert the key from PRIVATEKEYBLOB to RSA
|
||||
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/security/Security/private_key_blobs.asp
|
||||
// e.g. SNK files, PVK files
|
||||
static public RSA FromCapiPrivateKeyBlob (byte[] blob)
|
||||
{
|
||||
return FromCapiPrivateKeyBlob (blob, 0);
|
||||
}
|
||||
|
||||
static public RSA FromCapiPrivateKeyBlob (byte[] blob, int offset)
|
||||
{
|
||||
if (blob == null)
|
||||
throw new ArgumentNullException ("blob");
|
||||
if (offset >= blob.Length)
|
||||
throw new ArgumentException ("blob is too small.");
|
||||
|
||||
RSAParameters rsap = new RSAParameters ();
|
||||
try {
|
||||
if ((blob [offset] != 0x07) || // PRIVATEKEYBLOB (0x07)
|
||||
(blob [offset+1] != 0x02) || // Version (0x02)
|
||||
(blob [offset+2] != 0x00) || // Reserved (word)
|
||||
(blob [offset+3] != 0x00) ||
|
||||
(ToUInt32LE (blob, offset+8) != 0x32415352)) // DWORD magic = RSA2
|
||||
throw new CryptographicException ("Invalid blob header");
|
||||
|
||||
// ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
|
||||
// int algId = ToInt32LE (blob, offset+4);
|
||||
|
||||
// DWORD bitlen
|
||||
int bitLen = ToInt32LE (blob, offset+12);
|
||||
|
||||
// DWORD public exponent
|
||||
byte[] exp = new byte [4];
|
||||
Buffer.BlockCopy (blob, offset+16, exp, 0, 4);
|
||||
Array.Reverse (exp);
|
||||
rsap.Exponent = Trim (exp);
|
||||
|
||||
int pos = offset+20;
|
||||
// BYTE modulus[rsapubkey.bitlen/8];
|
||||
int byteLen = (bitLen >> 3);
|
||||
rsap.Modulus = new byte [byteLen];
|
||||
Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
|
||||
Array.Reverse (rsap.Modulus);
|
||||
pos += byteLen;
|
||||
|
||||
// BYTE prime1[rsapubkey.bitlen/16];
|
||||
int byteHalfLen = (byteLen >> 1);
|
||||
rsap.P = new byte [byteHalfLen];
|
||||
Buffer.BlockCopy (blob, pos, rsap.P, 0, byteHalfLen);
|
||||
Array.Reverse (rsap.P);
|
||||
pos += byteHalfLen;
|
||||
|
||||
// BYTE prime2[rsapubkey.bitlen/16];
|
||||
rsap.Q = new byte [byteHalfLen];
|
||||
Buffer.BlockCopy (blob, pos, rsap.Q, 0, byteHalfLen);
|
||||
Array.Reverse (rsap.Q);
|
||||
pos += byteHalfLen;
|
||||
|
||||
// BYTE exponent1[rsapubkey.bitlen/16];
|
||||
rsap.DP = new byte [byteHalfLen];
|
||||
Buffer.BlockCopy (blob, pos, rsap.DP, 0, byteHalfLen);
|
||||
Array.Reverse (rsap.DP);
|
||||
pos += byteHalfLen;
|
||||
|
||||
// BYTE exponent2[rsapubkey.bitlen/16];
|
||||
rsap.DQ = new byte [byteHalfLen];
|
||||
Buffer.BlockCopy (blob, pos, rsap.DQ, 0, byteHalfLen);
|
||||
Array.Reverse (rsap.DQ);
|
||||
pos += byteHalfLen;
|
||||
|
||||
// BYTE coefficient[rsapubkey.bitlen/16];
|
||||
rsap.InverseQ = new byte [byteHalfLen];
|
||||
Buffer.BlockCopy (blob, pos, rsap.InverseQ, 0, byteHalfLen);
|
||||
Array.Reverse (rsap.InverseQ);
|
||||
pos += byteHalfLen;
|
||||
|
||||
// ok, this is hackish but CryptoAPI support it so...
|
||||
// note: only works because CRT is used by default
|
||||
// http://bugzilla.ximian.com/show_bug.cgi?id=57941
|
||||
rsap.D = new byte [byteLen]; // must be allocated
|
||||
if (pos + byteLen + offset <= blob.Length) {
|
||||
// BYTE privateExponent[rsapubkey.bitlen/8];
|
||||
Buffer.BlockCopy (blob, pos, rsap.D, 0, byteLen);
|
||||
Array.Reverse (rsap.D);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new CryptographicException ("Invalid blob.", e);
|
||||
}
|
||||
|
||||
#if INSIDE_CORLIB && MOBILE
|
||||
RSA rsa = RSA.Create ();
|
||||
rsa.ImportParameters (rsap);
|
||||
#else
|
||||
RSA rsa = null;
|
||||
try {
|
||||
rsa = RSA.Create ();
|
||||
rsa.ImportParameters (rsap);
|
||||
}
|
||||
catch (CryptographicException ce) {
|
||||
// this may cause problem when this code is run under
|
||||
// the SYSTEM identity on Windows (e.g. ASP.NET). See
|
||||
// http://bugzilla.ximian.com/show_bug.cgi?id=77559
|
||||
try {
|
||||
CspParameters csp = new CspParameters ();
|
||||
csp.Flags = CspProviderFlags.UseMachineKeyStore;
|
||||
rsa = new RSACryptoServiceProvider (csp);
|
||||
rsa.ImportParameters (rsap);
|
||||
}
|
||||
catch {
|
||||
// rethrow original, not the later, exception if this fails
|
||||
throw ce;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return rsa;
|
||||
}
|
||||
|
||||
static public DSA FromCapiPrivateKeyBlobDSA (byte[] blob)
|
||||
{
|
||||
return FromCapiPrivateKeyBlobDSA (blob, 0);
|
||||
}
|
||||
|
||||
static public DSA FromCapiPrivateKeyBlobDSA (byte[] blob, int offset)
|
||||
{
|
||||
if (blob == null)
|
||||
throw new ArgumentNullException ("blob");
|
||||
if (offset >= blob.Length)
|
||||
throw new ArgumentException ("blob is too small.");
|
||||
|
||||
DSAParameters dsap = new DSAParameters ();
|
||||
try {
|
||||
if ((blob [offset] != 0x07) || // PRIVATEKEYBLOB (0x07)
|
||||
(blob [offset + 1] != 0x02) || // Version (0x02)
|
||||
(blob [offset + 2] != 0x00) || // Reserved (word)
|
||||
(blob [offset + 3] != 0x00) ||
|
||||
(ToUInt32LE (blob, offset + 8) != 0x32535344)) // DWORD magic
|
||||
throw new CryptographicException ("Invalid blob header");
|
||||
|
||||
int bitlen = ToInt32LE (blob, offset + 12);
|
||||
int bytelen = bitlen >> 3;
|
||||
int pos = offset + 16;
|
||||
|
||||
dsap.P = new byte [bytelen];
|
||||
Buffer.BlockCopy (blob, pos, dsap.P, 0, bytelen);
|
||||
Array.Reverse (dsap.P);
|
||||
pos += bytelen;
|
||||
|
||||
dsap.Q = new byte [20];
|
||||
Buffer.BlockCopy (blob, pos, dsap.Q, 0, 20);
|
||||
Array.Reverse (dsap.Q);
|
||||
pos += 20;
|
||||
|
||||
dsap.G = new byte [bytelen];
|
||||
Buffer.BlockCopy (blob, pos, dsap.G, 0, bytelen);
|
||||
Array.Reverse (dsap.G);
|
||||
pos += bytelen;
|
||||
|
||||
dsap.X = new byte [20];
|
||||
Buffer.BlockCopy (blob, pos, dsap.X, 0, 20);
|
||||
Array.Reverse (dsap.X);
|
||||
pos += 20;
|
||||
|
||||
dsap.Counter = ToInt32LE (blob, pos);
|
||||
pos += 4;
|
||||
|
||||
dsap.Seed = new byte [20];
|
||||
Buffer.BlockCopy (blob, pos, dsap.Seed, 0, 20);
|
||||
Array.Reverse (dsap.Seed);
|
||||
pos += 20;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new CryptographicException ("Invalid blob.", e);
|
||||
}
|
||||
|
||||
#if INSIDE_CORLIB && MOBILE
|
||||
DSA dsa = (DSA)DSA.Create ();
|
||||
dsa.ImportParameters (dsap);
|
||||
#else
|
||||
DSA dsa = null;
|
||||
try {
|
||||
dsa = (DSA)DSA.Create ();
|
||||
dsa.ImportParameters (dsap);
|
||||
}
|
||||
catch (CryptographicException ce) {
|
||||
// this may cause problem when this code is run under
|
||||
// the SYSTEM identity on Windows (e.g. ASP.NET). See
|
||||
// http://bugzilla.ximian.com/show_bug.cgi?id=77559
|
||||
try {
|
||||
CspParameters csp = new CspParameters ();
|
||||
csp.Flags = CspProviderFlags.UseMachineKeyStore;
|
||||
dsa = new DSACryptoServiceProvider (csp);
|
||||
dsa.ImportParameters (dsap);
|
||||
}
|
||||
catch {
|
||||
// rethrow original, not the later, exception if this fails
|
||||
throw ce;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return dsa;
|
||||
}
|
||||
|
||||
static public byte[] ToCapiPrivateKeyBlob (RSA rsa)
|
||||
{
|
||||
RSAParameters p = rsa.ExportParameters (true);
|
||||
int keyLength = p.Modulus.Length; // in bytes
|
||||
byte[] blob = new byte [20 + (keyLength << 2) + (keyLength >> 1)];
|
||||
|
||||
blob [0] = 0x07; // Type - PRIVATEKEYBLOB (0x07)
|
||||
blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
|
||||
// [2], [3] // RESERVED - Always 0
|
||||
blob [5] = 0x24; // ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
|
||||
blob [8] = 0x52; // Magic - RSA2 (ASCII in hex)
|
||||
blob [9] = 0x53;
|
||||
blob [10] = 0x41;
|
||||
blob [11] = 0x32;
|
||||
|
||||
byte[] bitlen = GetBytesLE (keyLength << 3);
|
||||
blob [12] = bitlen [0]; // bitlen
|
||||
blob [13] = bitlen [1];
|
||||
blob [14] = bitlen [2];
|
||||
blob [15] = bitlen [3];
|
||||
|
||||
// public exponent (DWORD)
|
||||
int pos = 16;
|
||||
int n = p.Exponent.Length;
|
||||
while (n > 0)
|
||||
blob [pos++] = p.Exponent [--n];
|
||||
// modulus
|
||||
pos = 20;
|
||||
byte[] part = p.Modulus;
|
||||
int len = part.Length;
|
||||
Array.Reverse (part, 0, len);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, len);
|
||||
pos += len;
|
||||
// private key
|
||||
part = p.P;
|
||||
len = part.Length;
|
||||
Array.Reverse (part, 0, len);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, len);
|
||||
pos += len;
|
||||
|
||||
part = p.Q;
|
||||
len = part.Length;
|
||||
Array.Reverse (part, 0, len);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, len);
|
||||
pos += len;
|
||||
|
||||
part = p.DP;
|
||||
len = part.Length;
|
||||
Array.Reverse (part, 0, len);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, len);
|
||||
pos += len;
|
||||
|
||||
part = p.DQ;
|
||||
len = part.Length;
|
||||
Array.Reverse (part, 0, len);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, len);
|
||||
pos += len;
|
||||
|
||||
part = p.InverseQ;
|
||||
len = part.Length;
|
||||
Array.Reverse (part, 0, len);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, len);
|
||||
pos += len;
|
||||
|
||||
part = p.D;
|
||||
len = part.Length;
|
||||
Array.Reverse (part, 0, len);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, len);
|
||||
|
||||
return blob;
|
||||
}
|
||||
|
||||
static public byte[] ToCapiPrivateKeyBlob (DSA dsa)
|
||||
{
|
||||
DSAParameters p = dsa.ExportParameters (true);
|
||||
int keyLength = p.P.Length; // in bytes
|
||||
|
||||
// header + P + Q + G + X + count + seed
|
||||
byte[] blob = new byte [16 + keyLength + 20 + keyLength + 20 + 4 + 20];
|
||||
|
||||
blob [0] = 0x07; // Type - PRIVATEKEYBLOB (0x07)
|
||||
blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
|
||||
// [2], [3] // RESERVED - Always 0
|
||||
blob [5] = 0x22; // ALGID
|
||||
blob [8] = 0x44; // Magic
|
||||
blob [9] = 0x53;
|
||||
blob [10] = 0x53;
|
||||
blob [11] = 0x32;
|
||||
|
||||
byte[] bitlen = GetBytesLE (keyLength << 3);
|
||||
blob [12] = bitlen [0];
|
||||
blob [13] = bitlen [1];
|
||||
blob [14] = bitlen [2];
|
||||
blob [15] = bitlen [3];
|
||||
|
||||
int pos = 16;
|
||||
byte[] part = p.P;
|
||||
Array.Reverse (part);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, keyLength);
|
||||
pos += keyLength;
|
||||
|
||||
part = p.Q;
|
||||
Array.Reverse (part);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, 20);
|
||||
pos += 20;
|
||||
|
||||
part = p.G;
|
||||
Array.Reverse (part);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, keyLength);
|
||||
pos += keyLength;
|
||||
|
||||
part = p.X;
|
||||
Array.Reverse (part);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, 20);
|
||||
pos += 20;
|
||||
|
||||
Buffer.BlockCopy (GetBytesLE (p.Counter), 0, blob, pos, 4);
|
||||
pos += 4;
|
||||
|
||||
part = p.Seed;
|
||||
Array.Reverse (part);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, 20);
|
||||
|
||||
return blob;
|
||||
}
|
||||
|
||||
static public RSA FromCapiPublicKeyBlob (byte[] blob)
|
||||
{
|
||||
return FromCapiPublicKeyBlob (blob, 0);
|
||||
}
|
||||
|
||||
static public RSA FromCapiPublicKeyBlob (byte[] blob, int offset)
|
||||
{
|
||||
if (blob == null)
|
||||
throw new ArgumentNullException ("blob");
|
||||
if (offset >= blob.Length)
|
||||
throw new ArgumentException ("blob is too small.");
|
||||
|
||||
try {
|
||||
if ((blob [offset] != 0x06) || // PUBLICKEYBLOB (0x06)
|
||||
(blob [offset+1] != 0x02) || // Version (0x02)
|
||||
(blob [offset+2] != 0x00) || // Reserved (word)
|
||||
(blob [offset+3] != 0x00) ||
|
||||
(ToUInt32LE (blob, offset+8) != 0x31415352)) // DWORD magic = RSA1
|
||||
throw new CryptographicException ("Invalid blob header");
|
||||
|
||||
// ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
|
||||
// int algId = ToInt32LE (blob, offset+4);
|
||||
|
||||
// DWORD bitlen
|
||||
int bitLen = ToInt32LE (blob, offset+12);
|
||||
|
||||
// DWORD public exponent
|
||||
RSAParameters rsap = new RSAParameters ();
|
||||
rsap.Exponent = new byte [3];
|
||||
rsap.Exponent [0] = blob [offset+18];
|
||||
rsap.Exponent [1] = blob [offset+17];
|
||||
rsap.Exponent [2] = blob [offset+16];
|
||||
|
||||
int pos = offset+20;
|
||||
// BYTE modulus[rsapubkey.bitlen/8];
|
||||
int byteLen = (bitLen >> 3);
|
||||
rsap.Modulus = new byte [byteLen];
|
||||
Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
|
||||
Array.Reverse (rsap.Modulus);
|
||||
#if INSIDE_CORLIB && MOBILE
|
||||
RSA rsa = RSA.Create ();
|
||||
rsa.ImportParameters (rsap);
|
||||
#else
|
||||
RSA rsa = null;
|
||||
try {
|
||||
rsa = RSA.Create ();
|
||||
rsa.ImportParameters (rsap);
|
||||
}
|
||||
catch (CryptographicException) {
|
||||
// this may cause problem when this code is run under
|
||||
// the SYSTEM identity on Windows (e.g. ASP.NET). See
|
||||
// http://bugzilla.ximian.com/show_bug.cgi?id=77559
|
||||
CspParameters csp = new CspParameters ();
|
||||
csp.Flags = CspProviderFlags.UseMachineKeyStore;
|
||||
rsa = new RSACryptoServiceProvider (csp);
|
||||
rsa.ImportParameters (rsap);
|
||||
}
|
||||
#endif
|
||||
return rsa;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new CryptographicException ("Invalid blob.", e);
|
||||
}
|
||||
}
|
||||
|
||||
static public DSA FromCapiPublicKeyBlobDSA (byte[] blob)
|
||||
{
|
||||
return FromCapiPublicKeyBlobDSA (blob, 0);
|
||||
}
|
||||
|
||||
static public DSA FromCapiPublicKeyBlobDSA (byte[] blob, int offset)
|
||||
{
|
||||
if (blob == null)
|
||||
throw new ArgumentNullException ("blob");
|
||||
if (offset >= blob.Length)
|
||||
throw new ArgumentException ("blob is too small.");
|
||||
|
||||
try {
|
||||
if ((blob [offset] != 0x06) || // PUBLICKEYBLOB (0x06)
|
||||
(blob [offset + 1] != 0x02) || // Version (0x02)
|
||||
(blob [offset + 2] != 0x00) || // Reserved (word)
|
||||
(blob [offset + 3] != 0x00) ||
|
||||
(ToUInt32LE (blob, offset + 8) != 0x31535344)) // DWORD magic
|
||||
throw new CryptographicException ("Invalid blob header");
|
||||
|
||||
int bitlen = ToInt32LE (blob, offset + 12);
|
||||
DSAParameters dsap = new DSAParameters ();
|
||||
int bytelen = bitlen >> 3;
|
||||
int pos = offset + 16;
|
||||
|
||||
dsap.P = new byte [bytelen];
|
||||
Buffer.BlockCopy (blob, pos, dsap.P, 0, bytelen);
|
||||
Array.Reverse (dsap.P);
|
||||
pos += bytelen;
|
||||
|
||||
dsap.Q = new byte [20];
|
||||
Buffer.BlockCopy (blob, pos, dsap.Q, 0, 20);
|
||||
Array.Reverse (dsap.Q);
|
||||
pos += 20;
|
||||
|
||||
dsap.G = new byte [bytelen];
|
||||
Buffer.BlockCopy (blob, pos, dsap.G, 0, bytelen);
|
||||
Array.Reverse (dsap.G);
|
||||
pos += bytelen;
|
||||
|
||||
dsap.Y = new byte [bytelen];
|
||||
Buffer.BlockCopy (blob, pos, dsap.Y, 0, bytelen);
|
||||
Array.Reverse (dsap.Y);
|
||||
pos += bytelen;
|
||||
|
||||
dsap.Counter = ToInt32LE (blob, pos);
|
||||
pos += 4;
|
||||
|
||||
dsap.Seed = new byte [20];
|
||||
Buffer.BlockCopy (blob, pos, dsap.Seed, 0, 20);
|
||||
Array.Reverse (dsap.Seed);
|
||||
pos += 20;
|
||||
|
||||
DSA dsa = (DSA)DSA.Create ();
|
||||
dsa.ImportParameters (dsap);
|
||||
return dsa;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new CryptographicException ("Invalid blob.", e);
|
||||
}
|
||||
}
|
||||
|
||||
static public byte[] ToCapiPublicKeyBlob (RSA rsa)
|
||||
{
|
||||
RSAParameters p = rsa.ExportParameters (false);
|
||||
int keyLength = p.Modulus.Length; // in bytes
|
||||
byte[] blob = new byte [20 + keyLength];
|
||||
|
||||
blob [0] = 0x06; // Type - PUBLICKEYBLOB (0x06)
|
||||
blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
|
||||
// [2], [3] // RESERVED - Always 0
|
||||
blob [5] = 0x24; // ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
|
||||
blob [8] = 0x52; // Magic - RSA1 (ASCII in hex)
|
||||
blob [9] = 0x53;
|
||||
blob [10] = 0x41;
|
||||
blob [11] = 0x31;
|
||||
|
||||
byte[] bitlen = GetBytesLE (keyLength << 3);
|
||||
blob [12] = bitlen [0]; // bitlen
|
||||
blob [13] = bitlen [1];
|
||||
blob [14] = bitlen [2];
|
||||
blob [15] = bitlen [3];
|
||||
|
||||
// public exponent (DWORD)
|
||||
int pos = 16;
|
||||
int n = p.Exponent.Length;
|
||||
while (n > 0)
|
||||
blob [pos++] = p.Exponent [--n];
|
||||
// modulus
|
||||
pos = 20;
|
||||
byte[] part = p.Modulus;
|
||||
int len = part.Length;
|
||||
Array.Reverse (part, 0, len);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, len);
|
||||
pos += len;
|
||||
return blob;
|
||||
}
|
||||
|
||||
static public byte[] ToCapiPublicKeyBlob (DSA dsa)
|
||||
{
|
||||
DSAParameters p = dsa.ExportParameters (false);
|
||||
int keyLength = p.P.Length; // in bytes
|
||||
|
||||
// header + P + Q + G + Y + count + seed
|
||||
byte[] blob = new byte [16 + keyLength + 20 + keyLength + keyLength + 4 + 20];
|
||||
|
||||
blob [0] = 0x06; // Type - PUBLICKEYBLOB (0x06)
|
||||
blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
|
||||
// [2], [3] // RESERVED - Always 0
|
||||
blob [5] = 0x22; // ALGID
|
||||
blob [8] = 0x44; // Magic
|
||||
blob [9] = 0x53;
|
||||
blob [10] = 0x53;
|
||||
blob [11] = 0x31;
|
||||
|
||||
byte[] bitlen = GetBytesLE (keyLength << 3);
|
||||
blob [12] = bitlen [0];
|
||||
blob [13] = bitlen [1];
|
||||
blob [14] = bitlen [2];
|
||||
blob [15] = bitlen [3];
|
||||
|
||||
int pos = 16;
|
||||
byte[] part;
|
||||
|
||||
part = p.P;
|
||||
Array.Reverse (part);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, keyLength);
|
||||
pos += keyLength;
|
||||
|
||||
part = p.Q;
|
||||
Array.Reverse (part);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, 20);
|
||||
pos += 20;
|
||||
|
||||
part = p.G;
|
||||
Array.Reverse (part);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, keyLength);
|
||||
pos += keyLength;
|
||||
|
||||
part = p.Y;
|
||||
Array.Reverse (part);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, keyLength);
|
||||
pos += keyLength;
|
||||
|
||||
Buffer.BlockCopy (GetBytesLE (p.Counter), 0, blob, pos, 4);
|
||||
pos += 4;
|
||||
|
||||
part = p.Seed;
|
||||
Array.Reverse (part);
|
||||
Buffer.BlockCopy (part, 0, blob, pos, 20);
|
||||
|
||||
return blob;
|
||||
}
|
||||
|
||||
// PRIVATEKEYBLOB
|
||||
// PUBLICKEYBLOB
|
||||
static public RSA FromCapiKeyBlob (byte[] blob)
|
||||
{
|
||||
return FromCapiKeyBlob (blob, 0);
|
||||
}
|
||||
|
||||
static public RSA FromCapiKeyBlob (byte[] blob, int offset)
|
||||
{
|
||||
if (blob == null)
|
||||
throw new ArgumentNullException ("blob");
|
||||
if (offset >= blob.Length)
|
||||
throw new ArgumentException ("blob is too small.");
|
||||
|
||||
switch (blob [offset]) {
|
||||
case 0x00:
|
||||
// this could be a public key inside an header
|
||||
// like "sn -e" would produce
|
||||
if (blob [offset + 12] == 0x06) {
|
||||
return FromCapiPublicKeyBlob (blob, offset + 12);
|
||||
}
|
||||
break;
|
||||
case 0x06:
|
||||
return FromCapiPublicKeyBlob (blob, offset);
|
||||
case 0x07:
|
||||
return FromCapiPrivateKeyBlob (blob, offset);
|
||||
}
|
||||
throw new CryptographicException ("Unknown blob format.");
|
||||
}
|
||||
|
||||
static public DSA FromCapiKeyBlobDSA (byte[] blob)
|
||||
{
|
||||
return FromCapiKeyBlobDSA (blob, 0);
|
||||
}
|
||||
|
||||
static public DSA FromCapiKeyBlobDSA (byte[] blob, int offset)
|
||||
{
|
||||
if (blob == null)
|
||||
throw new ArgumentNullException ("blob");
|
||||
if (offset >= blob.Length)
|
||||
throw new ArgumentException ("blob is too small.");
|
||||
|
||||
switch (blob [offset]) {
|
||||
case 0x06:
|
||||
return FromCapiPublicKeyBlobDSA (blob, offset);
|
||||
case 0x07:
|
||||
return FromCapiPrivateKeyBlobDSA (blob, offset);
|
||||
}
|
||||
throw new CryptographicException ("Unknown blob format.");
|
||||
}
|
||||
|
||||
static public byte[] ToCapiKeyBlob (AsymmetricAlgorithm keypair, bool includePrivateKey)
|
||||
{
|
||||
if (keypair == null)
|
||||
throw new ArgumentNullException ("keypair");
|
||||
|
||||
// check between RSA and DSA (and potentially others like DH)
|
||||
if (keypair is RSA)
|
||||
return ToCapiKeyBlob ((RSA)keypair, includePrivateKey);
|
||||
else if (keypair is DSA)
|
||||
return ToCapiKeyBlob ((DSA)keypair, includePrivateKey);
|
||||
else
|
||||
return null; // TODO
|
||||
}
|
||||
|
||||
static public byte[] ToCapiKeyBlob (RSA rsa, bool includePrivateKey)
|
||||
{
|
||||
if (rsa == null)
|
||||
throw new ArgumentNullException ("rsa");
|
||||
|
||||
if (includePrivateKey)
|
||||
return ToCapiPrivateKeyBlob (rsa);
|
||||
else
|
||||
return ToCapiPublicKeyBlob (rsa);
|
||||
}
|
||||
|
||||
static public byte[] ToCapiKeyBlob (DSA dsa, bool includePrivateKey)
|
||||
{
|
||||
if (dsa == null)
|
||||
throw new ArgumentNullException ("dsa");
|
||||
|
||||
if (includePrivateKey)
|
||||
return ToCapiPrivateKeyBlob (dsa);
|
||||
else
|
||||
return ToCapiPublicKeyBlob (dsa);
|
||||
}
|
||||
|
||||
static public string ToHex (byte[] input)
|
||||
{
|
||||
if (input == null)
|
||||
return null;
|
||||
|
||||
StringBuilder sb = new StringBuilder (input.Length * 2);
|
||||
foreach (byte b in input) {
|
||||
sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
|
||||
}
|
||||
return sb.ToString ();
|
||||
}
|
||||
|
||||
static private byte FromHexChar (char c)
|
||||
{
|
||||
if ((c >= 'a') && (c <= 'f'))
|
||||
return (byte) (c - 'a' + 10);
|
||||
if ((c >= 'A') && (c <= 'F'))
|
||||
return (byte) (c - 'A' + 10);
|
||||
if ((c >= '0') && (c <= '9'))
|
||||
return (byte) (c - '0');
|
||||
throw new ArgumentException ("invalid hex char");
|
||||
}
|
||||
|
||||
static public byte[] FromHex (string hex)
|
||||
{
|
||||
if (hex == null)
|
||||
return null;
|
||||
if ((hex.Length & 0x1) == 0x1)
|
||||
throw new ArgumentException ("Length must be a multiple of 2");
|
||||
|
||||
byte[] result = new byte [hex.Length >> 1];
|
||||
int n = 0;
|
||||
int i = 0;
|
||||
while (n < result.Length) {
|
||||
result [n] = (byte) (FromHexChar (hex [i++]) << 4);
|
||||
result [n++] += FromHexChar (hex [i++]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,495 @@
|
||||
//
|
||||
// PKCS1.cs - Implements PKCS#1 primitives.
|
||||
//
|
||||
// Author:
|
||||
// Sebastien Pouliot <sebastien@xamarin.com>
|
||||
//
|
||||
// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
|
||||
// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Mono.Security.Cryptography {
|
||||
|
||||
// References:
|
||||
// a. PKCS#1: RSA Cryptography Standard
|
||||
// http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/index.html
|
||||
|
||||
#if INSIDE_CORLIB
|
||||
internal
|
||||
#else
|
||||
public
|
||||
#endif
|
||||
sealed class PKCS1 {
|
||||
|
||||
private PKCS1 ()
|
||||
{
|
||||
}
|
||||
|
||||
private static bool Compare (byte[] array1, byte[] array2)
|
||||
{
|
||||
bool result = (array1.Length == array2.Length);
|
||||
if (result) {
|
||||
for (int i=0; i < array1.Length; i++)
|
||||
if (array1[i] != array2[i])
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] xor (byte[] array1, byte[] array2)
|
||||
{
|
||||
byte[] result = new byte [array1.Length];
|
||||
for (int i=0; i < result.Length; i++)
|
||||
result[i] = (byte) (array1[i] ^ array2[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] emptySHA1 = { 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 };
|
||||
private static byte[] emptySHA256 = { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 };
|
||||
private static byte[] emptySHA384 = { 0x38, 0xb0, 0x60, 0xa7, 0x51, 0xac, 0x96, 0x38, 0x4c, 0xd9, 0x32, 0x7e, 0xb1, 0xb1, 0xe3, 0x6a, 0x21, 0xfd, 0xb7, 0x11, 0x14, 0xbe, 0x07, 0x43, 0x4c, 0x0c, 0xc7, 0xbf, 0x63, 0xf6, 0xe1, 0xda, 0x27, 0x4e, 0xde, 0xbf, 0xe7, 0x6f, 0x65, 0xfb, 0xd5, 0x1a, 0xd2, 0xf1, 0x48, 0x98, 0xb9, 0x5b };
|
||||
private static byte[] emptySHA512 = { 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce, 0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0, 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f, 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81, 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e };
|
||||
|
||||
private static byte[] GetEmptyHash (HashAlgorithm hash)
|
||||
{
|
||||
if (hash is SHA1)
|
||||
return emptySHA1;
|
||||
else if (hash is SHA256)
|
||||
return emptySHA256;
|
||||
else if (hash is SHA384)
|
||||
return emptySHA384;
|
||||
else if (hash is SHA512)
|
||||
return emptySHA512;
|
||||
else
|
||||
return hash.ComputeHash ((byte[])null);
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 4.1
|
||||
// I2OSP converts a non-negative integer to an octet string of a specified length.
|
||||
public static byte[] I2OSP (int x, int size)
|
||||
{
|
||||
byte[] array = BitConverterLE.GetBytes (x);
|
||||
Array.Reverse (array, 0, array.Length);
|
||||
return I2OSP (array, size);
|
||||
}
|
||||
|
||||
public static byte[] I2OSP (byte[] x, int size)
|
||||
{
|
||||
byte[] result = new byte [size];
|
||||
Buffer.BlockCopy (x, 0, result, (result.Length - x.Length), x.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 4.2
|
||||
// OS2IP converts an octet string to a nonnegative integer.
|
||||
public static byte[] OS2IP (byte[] x)
|
||||
{
|
||||
int i = 0;
|
||||
while ((x [i++] == 0x00) && (i < x.Length)) {
|
||||
// confuse compiler into reporting a warning with {}
|
||||
}
|
||||
i--;
|
||||
if (i > 0) {
|
||||
byte[] result = new byte [x.Length - i];
|
||||
Buffer.BlockCopy (x, i, result, 0, result.Length);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
return x;
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 5.1.1
|
||||
public static byte[] RSAEP (RSA rsa, byte[] m)
|
||||
{
|
||||
// c = m^e mod n
|
||||
return rsa.EncryptValue (m);
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 5.1.2
|
||||
public static byte[] RSADP (RSA rsa, byte[] c)
|
||||
{
|
||||
// m = c^d mod n
|
||||
// Decrypt value may apply CRT optimizations
|
||||
return rsa.DecryptValue (c);
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 5.2.1
|
||||
public static byte[] RSASP1 (RSA rsa, byte[] m)
|
||||
{
|
||||
// first form: s = m^d mod n
|
||||
// Decrypt value may apply CRT optimizations
|
||||
return rsa.DecryptValue (m);
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 5.2.2
|
||||
public static byte[] RSAVP1 (RSA rsa, byte[] s)
|
||||
{
|
||||
// m = s^e mod n
|
||||
return rsa.EncryptValue (s);
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 7.1.1
|
||||
// RSAES-OAEP-ENCRYPT ((n, e), M, L)
|
||||
public static byte[] Encrypt_OAEP (RSA rsa, HashAlgorithm hash, RandomNumberGenerator rng, byte[] M)
|
||||
{
|
||||
int size = rsa.KeySize / 8;
|
||||
int hLen = hash.HashSize / 8;
|
||||
if (M.Length > size - 2 * hLen - 2)
|
||||
throw new CryptographicException ("message too long");
|
||||
// empty label L SHA1 hash
|
||||
byte[] lHash = GetEmptyHash (hash);
|
||||
int PSLength = (size - M.Length - 2 * hLen - 2);
|
||||
// DB = lHash || PS || 0x01 || M
|
||||
byte[] DB = new byte [lHash.Length + PSLength + 1 + M.Length];
|
||||
Buffer.BlockCopy (lHash, 0, DB, 0, lHash.Length);
|
||||
DB [(lHash.Length + PSLength)] = 0x01;
|
||||
Buffer.BlockCopy (M, 0, DB, (DB.Length - M.Length), M.Length);
|
||||
|
||||
byte[] seed = new byte [hLen];
|
||||
rng.GetBytes (seed);
|
||||
|
||||
byte[] dbMask = MGF1 (hash, seed, size - hLen - 1);
|
||||
byte[] maskedDB = xor (DB, dbMask);
|
||||
byte[] seedMask = MGF1 (hash, maskedDB, hLen);
|
||||
byte[] maskedSeed = xor (seed, seedMask);
|
||||
// EM = 0x00 || maskedSeed || maskedDB
|
||||
byte[] EM = new byte [maskedSeed.Length + maskedDB.Length + 1];
|
||||
Buffer.BlockCopy (maskedSeed, 0, EM, 1, maskedSeed.Length);
|
||||
Buffer.BlockCopy (maskedDB, 0, EM, maskedSeed.Length + 1, maskedDB.Length);
|
||||
|
||||
byte[] m = OS2IP (EM);
|
||||
byte[] c = RSAEP (rsa, m);
|
||||
return I2OSP (c, size);
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 7.1.2
|
||||
// RSAES-OAEP-DECRYPT (K, C, L)
|
||||
public static byte[] Decrypt_OAEP (RSA rsa, HashAlgorithm hash, byte[] C)
|
||||
{
|
||||
int size = rsa.KeySize / 8;
|
||||
int hLen = hash.HashSize / 8;
|
||||
if ((size < (2 * hLen + 2)) || (C.Length != size))
|
||||
throw new CryptographicException ("decryption error");
|
||||
|
||||
byte[] c = OS2IP (C);
|
||||
byte[] m = RSADP (rsa, c);
|
||||
byte[] EM = I2OSP (m, size);
|
||||
|
||||
// split EM = Y || maskedSeed || maskedDB
|
||||
byte[] maskedSeed = new byte [hLen];
|
||||
Buffer.BlockCopy (EM, 1, maskedSeed, 0, maskedSeed.Length);
|
||||
byte[] maskedDB = new byte [size - hLen - 1];
|
||||
Buffer.BlockCopy (EM, (EM.Length - maskedDB.Length), maskedDB, 0, maskedDB.Length);
|
||||
|
||||
byte[] seedMask = MGF1 (hash, maskedDB, hLen);
|
||||
byte[] seed = xor (maskedSeed, seedMask);
|
||||
byte[] dbMask = MGF1 (hash, seed, size - hLen - 1);
|
||||
byte[] DB = xor (maskedDB, dbMask);
|
||||
|
||||
byte[] lHash = GetEmptyHash (hash);
|
||||
// split DB = lHash' || PS || 0x01 || M
|
||||
byte[] dbHash = new byte [lHash.Length];
|
||||
Buffer.BlockCopy (DB, 0, dbHash, 0, dbHash.Length);
|
||||
bool h = Compare (lHash, dbHash);
|
||||
|
||||
// find separator 0x01
|
||||
int nPos = lHash.Length;
|
||||
while (DB[nPos] == 0)
|
||||
nPos++;
|
||||
|
||||
int Msize = DB.Length - nPos - 1;
|
||||
byte[] M = new byte [Msize];
|
||||
Buffer.BlockCopy (DB, (nPos + 1), M, 0, Msize);
|
||||
|
||||
// we could have returned EM[0] sooner but would be helping a timing attack
|
||||
if ((EM[0] != 0) || (!h) || (DB[nPos] != 0x01))
|
||||
return null;
|
||||
return M;
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 7.2.1
|
||||
// RSAES-PKCS1-V1_5-ENCRYPT ((n, e), M)
|
||||
public static byte[] Encrypt_v15 (RSA rsa, RandomNumberGenerator rng, byte[] M)
|
||||
{
|
||||
int size = rsa.KeySize / 8;
|
||||
if (M.Length > size - 11)
|
||||
throw new CryptographicException ("message too long");
|
||||
int PSLength = System.Math.Max (8, (size - M.Length - 3));
|
||||
byte[] PS = new byte [PSLength];
|
||||
rng.GetNonZeroBytes (PS);
|
||||
byte[] EM = new byte [size];
|
||||
EM [1] = 0x02;
|
||||
Buffer.BlockCopy (PS, 0, EM, 2, PSLength);
|
||||
Buffer.BlockCopy (M, 0, EM, (size - M.Length), M.Length);
|
||||
|
||||
byte[] m = OS2IP (EM);
|
||||
byte[] c = RSAEP (rsa, m);
|
||||
byte[] C = I2OSP (c, size);
|
||||
return C;
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 7.2.2
|
||||
// RSAES-PKCS1-V1_5-DECRYPT (K, C)
|
||||
public static byte[] Decrypt_v15 (RSA rsa, byte[] C)
|
||||
{
|
||||
int size = rsa.KeySize >> 3; // div by 8
|
||||
if ((size < 11) || (C.Length > size))
|
||||
throw new CryptographicException ("decryption error");
|
||||
byte[] c = OS2IP (C);
|
||||
byte[] m = RSADP (rsa, c);
|
||||
byte[] EM = I2OSP (m, size);
|
||||
|
||||
if ((EM [0] != 0x00) || (EM [1] != 0x02))
|
||||
return null;
|
||||
|
||||
int mPos = 10;
|
||||
// PS is a minimum of 8 bytes + 2 bytes for header
|
||||
while ((EM [mPos] != 0x00) && (mPos < EM.Length))
|
||||
mPos++;
|
||||
if (EM [mPos] != 0x00)
|
||||
return null;
|
||||
mPos++;
|
||||
byte[] M = new byte [EM.Length - mPos];
|
||||
Buffer.BlockCopy (EM, mPos, M, 0, M.Length);
|
||||
return M;
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 8.2.1
|
||||
// RSASSA-PKCS1-V1_5-SIGN (K, M)
|
||||
public static byte[] Sign_v15 (RSA rsa, HashAlgorithm hash, byte[] hashValue)
|
||||
{
|
||||
int size = (rsa.KeySize >> 3); // div 8
|
||||
byte[] EM = Encode_v15 (hash, hashValue, size);
|
||||
byte[] m = OS2IP (EM);
|
||||
byte[] s = RSASP1 (rsa, m);
|
||||
byte[] S = I2OSP (s, size);
|
||||
return S;
|
||||
}
|
||||
|
||||
internal static byte[] Sign_v15 (RSA rsa, string hashName, byte[] hashValue)
|
||||
{
|
||||
using (var hash = CreateFromName (hashName))
|
||||
return Sign_v15 (rsa, hash, hashValue);
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 8.2.2
|
||||
// RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S)
|
||||
public static bool Verify_v15 (RSA rsa, HashAlgorithm hash, byte[] hashValue, byte[] signature)
|
||||
{
|
||||
return Verify_v15 (rsa, hash, hashValue, signature, false);
|
||||
}
|
||||
|
||||
internal static bool Verify_v15 (RSA rsa, string hashName, byte[] hashValue, byte[] signature)
|
||||
{
|
||||
using (var hash = CreateFromName (hashName))
|
||||
return Verify_v15 (rsa, hash, hashValue, signature, false);
|
||||
}
|
||||
|
||||
// DO NOT USE WITHOUT A VERY GOOD REASON
|
||||
public static bool Verify_v15 (RSA rsa, HashAlgorithm hash, byte [] hashValue, byte [] signature, bool tryNonStandardEncoding)
|
||||
{
|
||||
int size = (rsa.KeySize >> 3); // div 8
|
||||
byte[] s = OS2IP (signature);
|
||||
byte[] m = RSAVP1 (rsa, s);
|
||||
byte[] EM2 = I2OSP (m, size);
|
||||
byte[] EM = Encode_v15 (hash, hashValue, size);
|
||||
bool result = Compare (EM, EM2);
|
||||
if (result || !tryNonStandardEncoding)
|
||||
return result;
|
||||
|
||||
// NOTE: some signatures don't include the hash OID (pretty lame but real)
|
||||
// and compatible with MS implementation. E.g. Verisign Authenticode Timestamps
|
||||
|
||||
// we're making this "as safe as possible"
|
||||
if ((EM2 [0] != 0x00) || (EM2 [1] != 0x01))
|
||||
return false;
|
||||
int i;
|
||||
for (i = 2; i < EM2.Length - hashValue.Length - 1; i++) {
|
||||
if (EM2 [i] != 0xFF)
|
||||
return false;
|
||||
}
|
||||
if (EM2 [i++] != 0x00)
|
||||
return false;
|
||||
|
||||
byte [] decryptedHash = new byte [hashValue.Length];
|
||||
Buffer.BlockCopy (EM2, i, decryptedHash, 0, decryptedHash.Length);
|
||||
return Compare (decryptedHash, hashValue);
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section 9.2
|
||||
// EMSA-PKCS1-v1_5-Encode
|
||||
public static byte[] Encode_v15 (HashAlgorithm hash, byte[] hashValue, int emLength)
|
||||
{
|
||||
if (hashValue.Length != (hash.HashSize >> 3))
|
||||
throw new CryptographicException ("bad hash length for " + hash.ToString ());
|
||||
|
||||
// DigestInfo ::= SEQUENCE {
|
||||
// digestAlgorithm AlgorithmIdentifier,
|
||||
// digest OCTET STRING
|
||||
// }
|
||||
|
||||
byte[] t = null;
|
||||
|
||||
string oid = CryptoConfig.MapNameToOID (hash.ToString ());
|
||||
if (oid != null)
|
||||
{
|
||||
ASN1 digestAlgorithm = new ASN1 (0x30);
|
||||
digestAlgorithm.Add (new ASN1 (CryptoConfig.EncodeOID (oid)));
|
||||
digestAlgorithm.Add (new ASN1 (0x05)); // NULL
|
||||
ASN1 digest = new ASN1 (0x04, hashValue);
|
||||
ASN1 digestInfo = new ASN1 (0x30);
|
||||
digestInfo.Add (digestAlgorithm);
|
||||
digestInfo.Add (digest);
|
||||
|
||||
t = digestInfo.GetBytes ();
|
||||
}
|
||||
else
|
||||
{
|
||||
// There are no valid OID, in this case t = hashValue
|
||||
// This is the case of the MD5SHA hash algorithm
|
||||
t = hashValue;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy (hashValue, 0, t, t.Length - hashValue.Length, hashValue.Length);
|
||||
|
||||
int PSLength = System.Math.Max (8, emLength - t.Length - 3);
|
||||
// PS = PSLength of 0xff
|
||||
|
||||
// EM = 0x00 | 0x01 | PS | 0x00 | T
|
||||
byte[] EM = new byte [PSLength + t.Length + 3];
|
||||
EM [1] = 0x01;
|
||||
for (int i=2; i < PSLength + 2; i++)
|
||||
EM[i] = 0xff;
|
||||
Buffer.BlockCopy (t, 0, EM, PSLength + 3, t.Length);
|
||||
|
||||
return EM;
|
||||
}
|
||||
|
||||
// PKCS #1 v.2.1, Section B.2.1
|
||||
public static byte[] MGF1 (HashAlgorithm hash, byte[] mgfSeed, int maskLen)
|
||||
{
|
||||
// 1. If maskLen > 2^32 hLen, output "mask too long" and stop.
|
||||
// easy - this is impossible by using a int (31bits) as parameter ;-)
|
||||
// BUT with a signed int we do have to check for negative values!
|
||||
if (maskLen < 0)
|
||||
throw new OverflowException();
|
||||
|
||||
int mgfSeedLength = mgfSeed.Length;
|
||||
int hLen = (hash.HashSize >> 3); // from bits to bytes
|
||||
int iterations = (maskLen / hLen);
|
||||
if (maskLen % hLen != 0)
|
||||
iterations++;
|
||||
// 2. Let T be the empty octet string.
|
||||
byte[] T = new byte [iterations * hLen];
|
||||
|
||||
byte[] toBeHashed = new byte [mgfSeedLength + 4];
|
||||
int pos = 0;
|
||||
// 3. For counter from 0 to \ceil (maskLen / hLen) - 1, do the following:
|
||||
for (int counter = 0; counter < iterations; counter++) {
|
||||
// a. Convert counter to an octet string C of length 4 octets
|
||||
byte[] C = I2OSP (counter, 4);
|
||||
|
||||
// b. Concatenate the hash of the seed mgfSeed and C to the octet string T:
|
||||
// T = T || Hash (mgfSeed || C)
|
||||
Buffer.BlockCopy (mgfSeed, 0, toBeHashed, 0, mgfSeedLength);
|
||||
Buffer.BlockCopy (C, 0, toBeHashed, mgfSeedLength, 4);
|
||||
byte[] output = hash.ComputeHash (toBeHashed);
|
||||
Buffer.BlockCopy (output, 0, T, pos, hLen);
|
||||
pos += hLen;
|
||||
}
|
||||
|
||||
// 4. Output the leading maskLen octets of T as the octet string mask.
|
||||
byte[] mask = new byte [maskLen];
|
||||
Buffer.BlockCopy (T, 0, mask, 0, maskLen);
|
||||
return mask;
|
||||
}
|
||||
|
||||
static internal string HashNameFromOid (string oid, bool throwOnError = true)
|
||||
{
|
||||
switch (oid) {
|
||||
case "1.2.840.113549.1.1.2": // MD2 with RSA encryption
|
||||
return "MD2";
|
||||
case "1.2.840.113549.1.1.3": // MD4 with RSA encryption
|
||||
return "MD4";
|
||||
case "1.2.840.113549.1.1.4": // MD5 with RSA encryption
|
||||
return "MD5";
|
||||
case "1.2.840.113549.1.1.5": // SHA-1 with RSA Encryption
|
||||
case "1.3.14.3.2.29": // SHA1 with RSA signature
|
||||
case "1.2.840.10040.4.3": // SHA1-1 with DSA
|
||||
return "SHA1";
|
||||
case "1.2.840.113549.1.1.11": // SHA-256 with RSA Encryption
|
||||
return "SHA256";
|
||||
case "1.2.840.113549.1.1.12": // SHA-384 with RSA Encryption
|
||||
return "SHA384";
|
||||
case "1.2.840.113549.1.1.13": // SHA-512 with RSA Encryption
|
||||
return "SHA512";
|
||||
case "1.3.36.3.3.1.2":
|
||||
return "RIPEMD160";
|
||||
default:
|
||||
if (throwOnError)
|
||||
throw new CryptographicException ("Unsupported hash algorithm: " + oid);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static internal HashAlgorithm CreateFromOid (string oid)
|
||||
{
|
||||
return CreateFromName (HashNameFromOid (oid));
|
||||
}
|
||||
|
||||
static internal HashAlgorithm CreateFromName (string name)
|
||||
{
|
||||
#if FULL_AOT_RUNTIME
|
||||
switch (name) {
|
||||
case "MD2":
|
||||
return MD2.Create ();
|
||||
case "MD4":
|
||||
return MD4.Create ();
|
||||
case "MD5":
|
||||
return MD5.Create ();
|
||||
case "SHA1":
|
||||
return SHA1.Create ();
|
||||
case "SHA256":
|
||||
return SHA256.Create ();
|
||||
case "SHA384":
|
||||
return SHA384.Create ();
|
||||
case "SHA512":
|
||||
return SHA512.Create ();
|
||||
case "RIPEMD160":
|
||||
return RIPEMD160.Create ();
|
||||
default:
|
||||
try {
|
||||
return (HashAlgorithm) Activator.CreateInstance (Type.GetType (name));
|
||||
}
|
||||
catch {
|
||||
throw new CryptographicException ("Unsupported hash algorithm: " + name);
|
||||
}
|
||||
}
|
||||
#else
|
||||
return HashAlgorithm.Create (name);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,505 @@
|
||||
//
|
||||
// PKCS8.cs: PKCS #8 - Private-Key Information Syntax Standard
|
||||
// ftp://ftp.rsasecurity.com/pub/pkcs/doc/pkcs-8.doc
|
||||
//
|
||||
// Author:
|
||||
// Sebastien Pouliot <sebastien@xamarin.com>
|
||||
//
|
||||
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
|
||||
// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
using Mono.Security.X509;
|
||||
|
||||
namespace Mono.Security.Cryptography {
|
||||
|
||||
#if !INSIDE_CORLIB
|
||||
public
|
||||
#endif
|
||||
sealed class PKCS8 {
|
||||
|
||||
public enum KeyInfo {
|
||||
PrivateKey,
|
||||
EncryptedPrivateKey,
|
||||
Unknown
|
||||
}
|
||||
|
||||
private PKCS8 ()
|
||||
{
|
||||
}
|
||||
|
||||
static public KeyInfo GetType (byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException ("data");
|
||||
|
||||
KeyInfo ki = KeyInfo.Unknown;
|
||||
try {
|
||||
ASN1 top = new ASN1 (data);
|
||||
if ((top.Tag == 0x30) && (top.Count > 0)) {
|
||||
ASN1 firstLevel = top [0];
|
||||
switch (firstLevel.Tag) {
|
||||
case 0x02:
|
||||
ki = KeyInfo.PrivateKey;
|
||||
break;
|
||||
case 0x30:
|
||||
ki = KeyInfo.EncryptedPrivateKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
throw new CryptographicException ("invalid ASN.1 data");
|
||||
}
|
||||
return ki;
|
||||
}
|
||||
|
||||
/*
|
||||
* PrivateKeyInfo ::= SEQUENCE {
|
||||
* version Version,
|
||||
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
|
||||
* privateKey PrivateKey,
|
||||
* attributes [0] IMPLICIT Attributes OPTIONAL
|
||||
* }
|
||||
*
|
||||
* Version ::= INTEGER
|
||||
*
|
||||
* PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
*
|
||||
* PrivateKey ::= OCTET STRING
|
||||
*
|
||||
* Attributes ::= SET OF Attribute
|
||||
*/
|
||||
public class PrivateKeyInfo {
|
||||
|
||||
private int _version;
|
||||
private string _algorithm;
|
||||
private byte[] _key;
|
||||
private ArrayList _list;
|
||||
|
||||
public PrivateKeyInfo ()
|
||||
{
|
||||
_version = 0;
|
||||
_list = new ArrayList ();
|
||||
}
|
||||
|
||||
public PrivateKeyInfo (byte[] data) : this ()
|
||||
{
|
||||
Decode (data);
|
||||
}
|
||||
|
||||
// properties
|
||||
|
||||
public string Algorithm {
|
||||
get { return _algorithm; }
|
||||
set { _algorithm = value; }
|
||||
}
|
||||
|
||||
public ArrayList Attributes {
|
||||
get { return _list; }
|
||||
}
|
||||
|
||||
public byte[] PrivateKey {
|
||||
get {
|
||||
if (_key == null)
|
||||
return null;
|
||||
return (byte[]) _key.Clone ();
|
||||
}
|
||||
set {
|
||||
if (value == null)
|
||||
throw new ArgumentNullException ("PrivateKey");
|
||||
_key = (byte[]) value.Clone ();
|
||||
}
|
||||
}
|
||||
|
||||
public int Version {
|
||||
get { return _version; }
|
||||
set {
|
||||
if (value < 0)
|
||||
throw new ArgumentOutOfRangeException ("negative version");
|
||||
_version = value;
|
||||
}
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
private void Decode (byte[] data)
|
||||
{
|
||||
ASN1 privateKeyInfo = new ASN1 (data);
|
||||
if (privateKeyInfo.Tag != 0x30)
|
||||
throw new CryptographicException ("invalid PrivateKeyInfo");
|
||||
|
||||
ASN1 version = privateKeyInfo [0];
|
||||
if (version.Tag != 0x02)
|
||||
throw new CryptographicException ("invalid version");
|
||||
_version = version.Value [0];
|
||||
|
||||
ASN1 privateKeyAlgorithm = privateKeyInfo [1];
|
||||
if (privateKeyAlgorithm.Tag != 0x30)
|
||||
throw new CryptographicException ("invalid algorithm");
|
||||
|
||||
ASN1 algorithm = privateKeyAlgorithm [0];
|
||||
if (algorithm.Tag != 0x06)
|
||||
throw new CryptographicException ("missing algorithm OID");
|
||||
_algorithm = ASN1Convert.ToOid (algorithm);
|
||||
|
||||
ASN1 privateKey = privateKeyInfo [2];
|
||||
_key = privateKey.Value;
|
||||
|
||||
// attributes [0] IMPLICIT Attributes OPTIONAL
|
||||
if (privateKeyInfo.Count > 3) {
|
||||
ASN1 attributes = privateKeyInfo [3];
|
||||
for (int i=0; i < attributes.Count; i++) {
|
||||
_list.Add (attributes [i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetBytes ()
|
||||
{
|
||||
ASN1 privateKeyAlgorithm = new ASN1 (0x30);
|
||||
privateKeyAlgorithm.Add (ASN1Convert.FromOid (_algorithm));
|
||||
privateKeyAlgorithm.Add (new ASN1 (0x05)); // ASN.1 NULL
|
||||
|
||||
ASN1 pki = new ASN1 (0x30);
|
||||
pki.Add (new ASN1 (0x02, new byte [1] { (byte) _version }));
|
||||
pki.Add (privateKeyAlgorithm);
|
||||
pki.Add (new ASN1 (0x04, _key));
|
||||
|
||||
if (_list.Count > 0) {
|
||||
ASN1 attributes = new ASN1 (0xA0);
|
||||
foreach (ASN1 attribute in _list) {
|
||||
attributes.Add (attribute);
|
||||
}
|
||||
pki.Add (attributes);
|
||||
}
|
||||
|
||||
return pki.GetBytes ();
|
||||
}
|
||||
|
||||
// static methods
|
||||
|
||||
static private byte[] RemoveLeadingZero (byte[] bigInt)
|
||||
{
|
||||
int start = 0;
|
||||
int length = bigInt.Length;
|
||||
if (bigInt [0] == 0x00) {
|
||||
start = 1;
|
||||
length--;
|
||||
}
|
||||
byte[] bi = new byte [length];
|
||||
Buffer.BlockCopy (bigInt, start, bi, 0, length);
|
||||
return bi;
|
||||
}
|
||||
|
||||
static private byte[] Normalize (byte[] bigInt, int length)
|
||||
{
|
||||
if (bigInt.Length == length)
|
||||
return bigInt;
|
||||
else if (bigInt.Length > length)
|
||||
return RemoveLeadingZero (bigInt);
|
||||
else {
|
||||
// pad with 0
|
||||
byte[] bi = new byte [length];
|
||||
Buffer.BlockCopy (bigInt, 0, bi, (length - bigInt.Length), bigInt.Length);
|
||||
return bi;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* RSAPrivateKey ::= SEQUENCE {
|
||||
* version Version,
|
||||
* modulus INTEGER, -- n
|
||||
* publicExponent INTEGER, -- e
|
||||
* privateExponent INTEGER, -- d
|
||||
* prime1 INTEGER, -- p
|
||||
* prime2 INTEGER, -- q
|
||||
* exponent1 INTEGER, -- d mod (p-1)
|
||||
* exponent2 INTEGER, -- d mod (q-1)
|
||||
* coefficient INTEGER, -- (inverse of q) mod p
|
||||
* otherPrimeInfos OtherPrimeInfos OPTIONAL
|
||||
* }
|
||||
*/
|
||||
static public RSA DecodeRSA (byte[] keypair)
|
||||
{
|
||||
ASN1 privateKey = new ASN1 (keypair);
|
||||
if (privateKey.Tag != 0x30)
|
||||
throw new CryptographicException ("invalid private key format");
|
||||
|
||||
ASN1 version = privateKey [0];
|
||||
if (version.Tag != 0x02)
|
||||
throw new CryptographicException ("missing version");
|
||||
|
||||
if (privateKey.Count < 9)
|
||||
throw new CryptographicException ("not enough key parameters");
|
||||
|
||||
RSAParameters param = new RSAParameters ();
|
||||
// note: MUST remove leading 0 - else MS wont import the key
|
||||
param.Modulus = RemoveLeadingZero (privateKey [1].Value);
|
||||
int keysize = param.Modulus.Length;
|
||||
int keysize2 = (keysize >> 1); // half-size
|
||||
// size must be normalized - else MS wont import the key
|
||||
param.D = Normalize (privateKey [3].Value, keysize);
|
||||
param.DP = Normalize (privateKey [6].Value, keysize2);
|
||||
param.DQ = Normalize (privateKey [7].Value, keysize2);
|
||||
param.Exponent = RemoveLeadingZero (privateKey [2].Value);
|
||||
param.InverseQ = Normalize (privateKey [8].Value, keysize2);
|
||||
param.P = Normalize (privateKey [4].Value, keysize2);
|
||||
param.Q = Normalize (privateKey [5].Value, keysize2);
|
||||
|
||||
RSA rsa = null;
|
||||
try {
|
||||
rsa = RSA.Create ();
|
||||
rsa.ImportParameters (param);
|
||||
}
|
||||
catch (CryptographicException) {
|
||||
#if MONOTOUCH
|
||||
// there's no machine-wide store available for iOS so we can drop the dependency on
|
||||
// CspParameters (which drops other things, like XML key persistance, unless used elsewhere)
|
||||
throw;
|
||||
#else
|
||||
// this may cause problem when this code is run under
|
||||
// the SYSTEM identity on Windows (e.g. ASP.NET). See
|
||||
// http://bugzilla.ximian.com/show_bug.cgi?id=77559
|
||||
CspParameters csp = new CspParameters ();
|
||||
csp.Flags = CspProviderFlags.UseMachineKeyStore;
|
||||
rsa = new RSACryptoServiceProvider (csp);
|
||||
rsa.ImportParameters (param);
|
||||
#endif
|
||||
}
|
||||
return rsa;
|
||||
}
|
||||
|
||||
/*
|
||||
* RSAPrivateKey ::= SEQUENCE {
|
||||
* version Version,
|
||||
* modulus INTEGER, -- n
|
||||
* publicExponent INTEGER, -- e
|
||||
* privateExponent INTEGER, -- d
|
||||
* prime1 INTEGER, -- p
|
||||
* prime2 INTEGER, -- q
|
||||
* exponent1 INTEGER, -- d mod (p-1)
|
||||
* exponent2 INTEGER, -- d mod (q-1)
|
||||
* coefficient INTEGER, -- (inverse of q) mod p
|
||||
* otherPrimeInfos OtherPrimeInfos OPTIONAL
|
||||
* }
|
||||
*/
|
||||
static public byte[] Encode (RSA rsa)
|
||||
{
|
||||
RSAParameters param = rsa.ExportParameters (true);
|
||||
|
||||
ASN1 rsaPrivateKey = new ASN1 (0x30);
|
||||
rsaPrivateKey.Add (new ASN1 (0x02, new byte [1] { 0x00 }));
|
||||
rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Modulus));
|
||||
rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Exponent));
|
||||
rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.D));
|
||||
rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.P));
|
||||
rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Q));
|
||||
rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.DP));
|
||||
rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.DQ));
|
||||
rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.InverseQ));
|
||||
|
||||
return rsaPrivateKey.GetBytes ();
|
||||
}
|
||||
|
||||
// DSA only encode it's X private key inside an ASN.1 INTEGER (Hint: Tag == 0x02)
|
||||
// which isn't enough for rebuilding the keypair. The other parameters
|
||||
// can be found (98% of the time) in the X.509 certificate associated
|
||||
// with the private key or (2% of the time) the parameters are in it's
|
||||
// issuer X.509 certificate (not supported in the .NET framework).
|
||||
static public DSA DecodeDSA (byte[] privateKey, DSAParameters dsaParameters)
|
||||
{
|
||||
ASN1 pvk = new ASN1 (privateKey);
|
||||
if (pvk.Tag != 0x02)
|
||||
throw new CryptographicException ("invalid private key format");
|
||||
|
||||
// X is ALWAYS 20 bytes (no matter if the key length is 512 or 1024 bits)
|
||||
dsaParameters.X = Normalize (pvk.Value, 20);
|
||||
DSA dsa = DSA.Create ();
|
||||
dsa.ImportParameters (dsaParameters);
|
||||
return dsa;
|
||||
}
|
||||
|
||||
static public byte[] Encode (DSA dsa)
|
||||
{
|
||||
DSAParameters param = dsa.ExportParameters (true);
|
||||
return ASN1Convert.FromUnsignedBigInteger (param.X).GetBytes ();
|
||||
}
|
||||
|
||||
static public byte[] Encode (AsymmetricAlgorithm aa)
|
||||
{
|
||||
if (aa is RSA)
|
||||
return Encode ((RSA)aa);
|
||||
else if (aa is DSA)
|
||||
return Encode ((DSA)aa);
|
||||
else
|
||||
throw new CryptographicException ("Unknown asymmetric algorithm {0}", aa.ToString ());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* EncryptedPrivateKeyInfo ::= SEQUENCE {
|
||||
* encryptionAlgorithm EncryptionAlgorithmIdentifier,
|
||||
* encryptedData EncryptedData
|
||||
* }
|
||||
*
|
||||
* EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
*
|
||||
* EncryptedData ::= OCTET STRING
|
||||
*
|
||||
* --
|
||||
* AlgorithmIdentifier ::= SEQUENCE {
|
||||
* algorithm OBJECT IDENTIFIER,
|
||||
* parameters ANY DEFINED BY algorithm OPTIONAL
|
||||
* }
|
||||
*
|
||||
* -- from PKCS#5
|
||||
* PBEParameter ::= SEQUENCE {
|
||||
* salt OCTET STRING SIZE(8),
|
||||
* iterationCount INTEGER
|
||||
* }
|
||||
*/
|
||||
public class EncryptedPrivateKeyInfo {
|
||||
|
||||
private string _algorithm;
|
||||
private byte[] _salt;
|
||||
private int _iterations;
|
||||
private byte[] _data;
|
||||
|
||||
public EncryptedPrivateKeyInfo () {}
|
||||
|
||||
public EncryptedPrivateKeyInfo (byte[] data) : this ()
|
||||
{
|
||||
Decode (data);
|
||||
}
|
||||
|
||||
// properties
|
||||
|
||||
public string Algorithm {
|
||||
get { return _algorithm; }
|
||||
set { _algorithm = value; }
|
||||
}
|
||||
|
||||
public byte[] EncryptedData {
|
||||
get { return (_data == null) ? null : (byte[]) _data.Clone (); }
|
||||
set { _data = (value == null) ? null : (byte[]) value.Clone (); }
|
||||
}
|
||||
|
||||
public byte[] Salt {
|
||||
get {
|
||||
if (_salt == null) {
|
||||
RandomNumberGenerator rng = RandomNumberGenerator.Create ();
|
||||
_salt = new byte [8];
|
||||
rng.GetBytes (_salt);
|
||||
}
|
||||
return (byte[]) _salt.Clone ();
|
||||
}
|
||||
set { _salt = (byte[]) value.Clone (); }
|
||||
}
|
||||
|
||||
public int IterationCount {
|
||||
get { return _iterations; }
|
||||
set {
|
||||
if (value < 0)
|
||||
throw new ArgumentOutOfRangeException ("IterationCount", "Negative");
|
||||
_iterations = value;
|
||||
}
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
private void Decode (byte[] data)
|
||||
{
|
||||
ASN1 encryptedPrivateKeyInfo = new ASN1 (data);
|
||||
if (encryptedPrivateKeyInfo.Tag != 0x30)
|
||||
throw new CryptographicException ("invalid EncryptedPrivateKeyInfo");
|
||||
|
||||
ASN1 encryptionAlgorithm = encryptedPrivateKeyInfo [0];
|
||||
if (encryptionAlgorithm.Tag != 0x30)
|
||||
throw new CryptographicException ("invalid encryptionAlgorithm");
|
||||
ASN1 algorithm = encryptionAlgorithm [0];
|
||||
if (algorithm.Tag != 0x06)
|
||||
throw new CryptographicException ("invalid algorithm");
|
||||
_algorithm = ASN1Convert.ToOid (algorithm);
|
||||
// parameters ANY DEFINED BY algorithm OPTIONAL
|
||||
if (encryptionAlgorithm.Count > 1) {
|
||||
ASN1 parameters = encryptionAlgorithm [1];
|
||||
if (parameters.Tag != 0x30)
|
||||
throw new CryptographicException ("invalid parameters");
|
||||
|
||||
ASN1 salt = parameters [0];
|
||||
if (salt.Tag != 0x04)
|
||||
throw new CryptographicException ("invalid salt");
|
||||
_salt = salt.Value;
|
||||
|
||||
ASN1 iterationCount = parameters [1];
|
||||
if (iterationCount.Tag != 0x02)
|
||||
throw new CryptographicException ("invalid iterationCount");
|
||||
_iterations = ASN1Convert.ToInt32 (iterationCount);
|
||||
}
|
||||
|
||||
ASN1 encryptedData = encryptedPrivateKeyInfo [1];
|
||||
if (encryptedData.Tag != 0x04)
|
||||
throw new CryptographicException ("invalid EncryptedData");
|
||||
_data = encryptedData.Value;
|
||||
}
|
||||
|
||||
// Note: PKCS#8 doesn't define how to generate the key required for encryption
|
||||
// so you're on your own. Just don't try to copy the big guys too much ;)
|
||||
// Netscape: http://www.cs.auckland.ac.nz/~pgut001/pubs/netscape.txt
|
||||
// Microsoft: http://www.cs.auckland.ac.nz/~pgut001/pubs/breakms.txt
|
||||
public byte[] GetBytes ()
|
||||
{
|
||||
if (_algorithm == null)
|
||||
throw new CryptographicException ("No algorithm OID specified");
|
||||
|
||||
ASN1 encryptionAlgorithm = new ASN1 (0x30);
|
||||
encryptionAlgorithm.Add (ASN1Convert.FromOid (_algorithm));
|
||||
|
||||
// parameters ANY DEFINED BY algorithm OPTIONAL
|
||||
if ((_iterations > 0) || (_salt != null)) {
|
||||
ASN1 salt = new ASN1 (0x04, _salt);
|
||||
ASN1 iterations = ASN1Convert.FromInt32 (_iterations);
|
||||
|
||||
ASN1 parameters = new ASN1 (0x30);
|
||||
parameters.Add (salt);
|
||||
parameters.Add (iterations);
|
||||
encryptionAlgorithm.Add (parameters);
|
||||
}
|
||||
|
||||
// encapsulates EncryptedData into an OCTET STRING
|
||||
ASN1 encryptedData = new ASN1 (0x04, _data);
|
||||
|
||||
ASN1 encryptedPrivateKeyInfo = new ASN1 (0x30);
|
||||
encryptedPrivateKeyInfo.Add (encryptionAlgorithm);
|
||||
encryptedPrivateKeyInfo.Add (encryptedData);
|
||||
|
||||
return encryptedPrivateKeyInfo.GetBytes ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,400 @@
|
||||
//
|
||||
// X501Name.cs: X.501 Distinguished Names stuff
|
||||
//
|
||||
// Author:
|
||||
// Sebastien Pouliot <sebastien@ximian.com>
|
||||
//
|
||||
// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
using Mono.Security;
|
||||
using Mono.Security.Cryptography;
|
||||
|
||||
namespace Mono.Security.X509 {
|
||||
|
||||
// References:
|
||||
// 1. Information technology - Open Systems Interconnection - The Directory: Models
|
||||
// http://www.itu.int/rec/recommendation.asp?type=items&lang=e&parent=T-REC-X.501-200102-I
|
||||
// 2. RFC2253: Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names
|
||||
// http://www.ietf.org/rfc/rfc2253.txt
|
||||
|
||||
/*
|
||||
* Name ::= CHOICE { RDNSequence }
|
||||
*
|
||||
* RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
|
||||
*
|
||||
* RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
|
||||
*/
|
||||
#if INSIDE_CORLIB
|
||||
internal
|
||||
#else
|
||||
public
|
||||
#endif
|
||||
sealed class X501 {
|
||||
|
||||
static byte[] countryName = { 0x55, 0x04, 0x06 };
|
||||
static byte[] organizationName = { 0x55, 0x04, 0x0A };
|
||||
static byte[] organizationalUnitName = { 0x55, 0x04, 0x0B };
|
||||
static byte[] commonName = { 0x55, 0x04, 0x03 };
|
||||
static byte[] localityName = { 0x55, 0x04, 0x07 };
|
||||
static byte[] stateOrProvinceName = { 0x55, 0x04, 0x08 };
|
||||
static byte[] streetAddress = { 0x55, 0x04, 0x09 };
|
||||
//static byte[] serialNumber = { 0x55, 0x04, 0x05 };
|
||||
static byte[] domainComponent = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 };
|
||||
static byte[] userid = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x01 };
|
||||
static byte[] email = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 };
|
||||
static byte[] dnQualifier = { 0x55, 0x04, 0x2E };
|
||||
static byte[] title = { 0x55, 0x04, 0x0C };
|
||||
static byte[] surname = { 0x55, 0x04, 0x04 };
|
||||
static byte[] givenName = { 0x55, 0x04, 0x2A };
|
||||
static byte[] initial = { 0x55, 0x04, 0x2B };
|
||||
|
||||
private X501 ()
|
||||
{
|
||||
}
|
||||
|
||||
static public string ToString (ASN1 seq)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder ();
|
||||
for (int i = 0; i < seq.Count; i++) {
|
||||
ASN1 entry = seq [i];
|
||||
AppendEntry (sb, entry, true);
|
||||
|
||||
// separator (not on last iteration)
|
||||
if (i < seq.Count - 1)
|
||||
sb.Append (", ");
|
||||
}
|
||||
return sb.ToString ();
|
||||
}
|
||||
|
||||
static public string ToString (ASN1 seq, bool reversed, string separator, bool quotes)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder ();
|
||||
|
||||
if (reversed) {
|
||||
for (int i = seq.Count - 1; i >= 0; i--) {
|
||||
ASN1 entry = seq [i];
|
||||
AppendEntry (sb, entry, quotes);
|
||||
|
||||
// separator (not on last iteration)
|
||||
if (i > 0)
|
||||
sb.Append (separator);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < seq.Count; i++) {
|
||||
ASN1 entry = seq [i];
|
||||
AppendEntry (sb, entry, quotes);
|
||||
|
||||
// separator (not on last iteration)
|
||||
if (i < seq.Count - 1)
|
||||
sb.Append (separator);
|
||||
}
|
||||
}
|
||||
return sb.ToString ();
|
||||
}
|
||||
|
||||
static private void AppendEntry (StringBuilder sb, ASN1 entry, bool quotes)
|
||||
{
|
||||
// multiple entries are valid
|
||||
for (int k = 0; k < entry.Count; k++) {
|
||||
ASN1 pair = entry [k];
|
||||
ASN1 s = pair [1];
|
||||
if (s == null)
|
||||
continue;
|
||||
|
||||
ASN1 poid = pair [0];
|
||||
if (poid == null)
|
||||
continue;
|
||||
|
||||
if (poid.CompareValue (countryName))
|
||||
sb.Append ("C=");
|
||||
else if (poid.CompareValue (organizationName))
|
||||
sb.Append ("O=");
|
||||
else if (poid.CompareValue (organizationalUnitName))
|
||||
sb.Append ("OU=");
|
||||
else if (poid.CompareValue (commonName))
|
||||
sb.Append ("CN=");
|
||||
else if (poid.CompareValue (localityName))
|
||||
sb.Append ("L=");
|
||||
else if (poid.CompareValue (stateOrProvinceName))
|
||||
sb.Append ("S="); // NOTE: RFC2253 uses ST=
|
||||
else if (poid.CompareValue (streetAddress))
|
||||
sb.Append ("STREET=");
|
||||
else if (poid.CompareValue (domainComponent))
|
||||
sb.Append ("DC=");
|
||||
else if (poid.CompareValue (userid))
|
||||
sb.Append ("UID=");
|
||||
else if (poid.CompareValue (email))
|
||||
sb.Append ("E="); // NOTE: Not part of RFC2253
|
||||
else if (poid.CompareValue (dnQualifier))
|
||||
sb.Append ("dnQualifier=");
|
||||
else if (poid.CompareValue (title))
|
||||
sb.Append ("T=");
|
||||
else if (poid.CompareValue (surname))
|
||||
sb.Append ("SN=");
|
||||
else if (poid.CompareValue (givenName))
|
||||
sb.Append ("G=");
|
||||
else if (poid.CompareValue (initial))
|
||||
sb.Append ("I=");
|
||||
else {
|
||||
// unknown OID
|
||||
sb.Append ("OID."); // NOTE: Not present as RFC2253
|
||||
sb.Append (ASN1Convert.ToOid (poid));
|
||||
sb.Append ("=");
|
||||
}
|
||||
|
||||
string sValue = null;
|
||||
// 16bits or 8bits string ? TODO not complete (+special chars!)
|
||||
if (s.Tag == 0x1E) {
|
||||
// BMPSTRING
|
||||
StringBuilder sb2 = new StringBuilder ();
|
||||
for (int j = 1; j < s.Value.Length; j += 2)
|
||||
sb2.Append ((char)s.Value[j]);
|
||||
sValue = sb2.ToString ();
|
||||
} else {
|
||||
if (s.Tag == 0x14)
|
||||
sValue = Encoding.UTF7.GetString (s.Value);
|
||||
else
|
||||
sValue = Encoding.UTF8.GetString (s.Value);
|
||||
// in some cases we must quote (") the value
|
||||
// Note: this doesn't seems to conform to RFC2253
|
||||
char[] specials = { ',', '+', '"', '\\', '<', '>', ';' };
|
||||
if (quotes) {
|
||||
if ((sValue.IndexOfAny (specials, 0, sValue.Length) > 0) ||
|
||||
sValue.StartsWith (" ") || (sValue.EndsWith (" ")))
|
||||
sValue = "\"" + sValue + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append (sValue);
|
||||
|
||||
// separator (not on last iteration)
|
||||
if (k < entry.Count - 1)
|
||||
sb.Append (", ");
|
||||
}
|
||||
}
|
||||
|
||||
static private X520.AttributeTypeAndValue GetAttributeFromOid (string attributeType)
|
||||
{
|
||||
string s = attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ();
|
||||
switch (s) {
|
||||
case "C":
|
||||
return new X520.CountryName ();
|
||||
case "O":
|
||||
return new X520.OrganizationName ();
|
||||
case "OU":
|
||||
return new X520.OrganizationalUnitName ();
|
||||
case "CN":
|
||||
return new X520.CommonName ();
|
||||
case "L":
|
||||
return new X520.LocalityName ();
|
||||
case "S": // Microsoft
|
||||
case "ST": // RFC2253
|
||||
return new X520.StateOrProvinceName ();
|
||||
case "E": // NOTE: Not part of RFC2253
|
||||
return new X520.EmailAddress ();
|
||||
case "DC": // RFC2247
|
||||
return new X520.DomainComponent ();
|
||||
case "UID": // RFC1274
|
||||
return new X520.UserId ();
|
||||
case "DNQUALIFIER":
|
||||
return new X520.DnQualifier ();
|
||||
case "T":
|
||||
return new X520.Title ();
|
||||
case "SN":
|
||||
return new X520.Surname ();
|
||||
case "G":
|
||||
return new X520.GivenName ();
|
||||
case "I":
|
||||
return new X520.Initial ();
|
||||
default:
|
||||
if (s.StartsWith ("OID.")) {
|
||||
// MUST support it but it OID may be without it
|
||||
return new X520.Oid (s.Substring (4));
|
||||
} else {
|
||||
if (IsOid (s))
|
||||
return new X520.Oid (s);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private bool IsOid (string oid)
|
||||
{
|
||||
try {
|
||||
ASN1 asn = ASN1Convert.FromOid (oid);
|
||||
return (asn.Tag == 0x06);
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// no quote processing
|
||||
static private X520.AttributeTypeAndValue ReadAttribute (string value, ref int pos)
|
||||
{
|
||||
while ((value[pos] == ' ') && (pos < value.Length))
|
||||
pos++;
|
||||
|
||||
// get '=' position in substring
|
||||
int equal = value.IndexOf ('=', pos);
|
||||
if (equal == -1) {
|
||||
string msg = ("No attribute found.");
|
||||
throw new FormatException (msg);
|
||||
}
|
||||
|
||||
string s = value.Substring (pos, equal - pos);
|
||||
X520.AttributeTypeAndValue atv = GetAttributeFromOid (s);
|
||||
if (atv == null) {
|
||||
string msg = ("Unknown attribute '{0}'.");
|
||||
throw new FormatException (String.Format (msg, s));
|
||||
}
|
||||
pos = equal + 1; // skip the '='
|
||||
return atv;
|
||||
}
|
||||
|
||||
static private bool IsHex (char c)
|
||||
{
|
||||
if (Char.IsDigit (c))
|
||||
return true;
|
||||
char up = Char.ToUpper (c, CultureInfo.InvariantCulture);
|
||||
return ((up >= 'A') && (up <= 'F'));
|
||||
}
|
||||
|
||||
static string ReadHex (string value, ref int pos)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder ();
|
||||
// it is (at least an) 8 bits char
|
||||
sb.Append (value[pos++]);
|
||||
sb.Append (value[pos]);
|
||||
// look ahead for a 16 bits char
|
||||
if ((pos < value.Length - 4) && (value[pos+1] == '\\') && IsHex (value[pos+2])) {
|
||||
pos += 2; // pass last char and skip \
|
||||
sb.Append (value[pos++]);
|
||||
sb.Append (value[pos]);
|
||||
}
|
||||
byte[] data = CryptoConvert.FromHex (sb.ToString ());
|
||||
return Encoding.UTF8.GetString (data);
|
||||
}
|
||||
|
||||
static private int ReadEscaped (StringBuilder sb, string value, int pos)
|
||||
{
|
||||
switch (value[pos]) {
|
||||
case '\\':
|
||||
case '"':
|
||||
case '=':
|
||||
case ';':
|
||||
case '<':
|
||||
case '>':
|
||||
case '+':
|
||||
case '#':
|
||||
case ',':
|
||||
sb.Append (value[pos]);
|
||||
return pos;
|
||||
default:
|
||||
if (pos >= value.Length - 2) {
|
||||
string msg = ("Malformed escaped value '{0}'.");
|
||||
throw new FormatException (string.Format (msg, value.Substring (pos)));
|
||||
}
|
||||
// it's either a 8 bits or 16 bits char
|
||||
sb.Append (ReadHex (value, ref pos));
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
static private int ReadQuoted (StringBuilder sb, string value, int pos)
|
||||
{
|
||||
int original = pos;
|
||||
while (pos <= value.Length) {
|
||||
switch (value[pos]) {
|
||||
case '"':
|
||||
return pos;
|
||||
case '\\':
|
||||
return ReadEscaped (sb, value, pos);
|
||||
default:
|
||||
sb.Append (value[pos]);
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
string msg = ("Malformed quoted value '{0}'.");
|
||||
throw new FormatException (string.Format (msg, value.Substring (original)));
|
||||
}
|
||||
|
||||
static private string ReadValue (string value, ref int pos)
|
||||
{
|
||||
int original = pos;
|
||||
StringBuilder sb = new StringBuilder ();
|
||||
while (pos < value.Length) {
|
||||
switch (value [pos]) {
|
||||
case '\\':
|
||||
pos = ReadEscaped (sb, value, ++pos);
|
||||
break;
|
||||
case '"':
|
||||
pos = ReadQuoted (sb, value, ++pos);
|
||||
break;
|
||||
case '=':
|
||||
case ';':
|
||||
case '<':
|
||||
case '>':
|
||||
string msg =("Malformed value '{0}' contains '{1}' outside quotes.");
|
||||
throw new FormatException (string.Format (msg, value.Substring (original), value[pos]));
|
||||
case '+':
|
||||
case '#':
|
||||
throw new NotImplementedException ();
|
||||
case ',':
|
||||
pos++;
|
||||
return sb.ToString ();
|
||||
default:
|
||||
sb.Append (value[pos]);
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return sb.ToString ();
|
||||
}
|
||||
|
||||
static public ASN1 FromString (string rdn)
|
||||
{
|
||||
if (rdn == null)
|
||||
throw new ArgumentNullException ("rdn");
|
||||
|
||||
int pos = 0;
|
||||
ASN1 asn1 = new ASN1 (0x30);
|
||||
while (pos < rdn.Length) {
|
||||
X520.AttributeTypeAndValue atv = ReadAttribute (rdn, ref pos);
|
||||
atv.Value = ReadValue (rdn, ref pos);
|
||||
|
||||
ASN1 sequence = new ASN1 (0x31);
|
||||
sequence.Add (atv.GetASN1 ());
|
||||
asn1.Add (sequence);
|
||||
}
|
||||
return asn1;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
//
|
||||
// X509Builder.cs: Abstract builder class for X509 objects
|
||||
//
|
||||
// Author:
|
||||
// Sebastien Pouliot <sebastien@ximian.com>
|
||||
//
|
||||
// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// (C) 2004 Novell (http://www.novell.com)
|
||||
//
|
||||
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
using Mono.Security;
|
||||
|
||||
namespace Mono.Security.X509 {
|
||||
|
||||
public abstract class X509Builder {
|
||||
|
||||
private const string defaultHash = "SHA1";
|
||||
private string hashName;
|
||||
|
||||
protected X509Builder ()
|
||||
{
|
||||
hashName = defaultHash;
|
||||
}
|
||||
|
||||
protected abstract ASN1 ToBeSigned (string hashName);
|
||||
|
||||
// move to PKCS1
|
||||
protected string GetOid (string hashName)
|
||||
{
|
||||
switch (hashName.ToLower (CultureInfo.InvariantCulture)) {
|
||||
case "md2":
|
||||
// md2withRSAEncryption (1 2 840 113549 1 1 2)
|
||||
return "1.2.840.113549.1.1.2";
|
||||
case "md4":
|
||||
// md4withRSAEncryption (1 2 840 113549 1 1 3)
|
||||
return "1.2.840.113549.1.1.3";
|
||||
case "md5":
|
||||
// md5withRSAEncryption (1 2 840 113549 1 1 4)
|
||||
return "1.2.840.113549.1.1.4";
|
||||
case "sha1":
|
||||
// sha1withRSAEncryption (1 2 840 113549 1 1 5)
|
||||
return "1.2.840.113549.1.1.5";
|
||||
case "sha256":
|
||||
// sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 }
|
||||
return "1.2.840.113549.1.1.11";
|
||||
case "sha384":
|
||||
// sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 }
|
||||
return "1.2.840.113549.1.1.12";
|
||||
case "sha512":
|
||||
// sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 }
|
||||
return "1.2.840.113549.1.1.13";
|
||||
default:
|
||||
throw new NotSupportedException ("Unknown hash algorithm " + hashName);
|
||||
}
|
||||
}
|
||||
|
||||
public string Hash {
|
||||
get { return hashName; }
|
||||
set {
|
||||
if (hashName == null)
|
||||
hashName = defaultHash;
|
||||
else
|
||||
hashName = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual byte[] Sign (AsymmetricAlgorithm aa)
|
||||
{
|
||||
if (aa is RSA)
|
||||
return Sign (aa as RSA);
|
||||
else if (aa is DSA)
|
||||
return Sign (aa as DSA);
|
||||
else
|
||||
throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString());
|
||||
}
|
||||
|
||||
private byte[] Build (ASN1 tbs, string hashoid, byte[] signature)
|
||||
{
|
||||
ASN1 builder = new ASN1 (0x30);
|
||||
builder.Add (tbs);
|
||||
builder.Add (PKCS7.AlgorithmIdentifier (hashoid));
|
||||
// first byte of BITSTRING is the number of unused bits in the first byte
|
||||
byte[] bitstring = new byte [signature.Length + 1];
|
||||
Buffer.BlockCopy (signature, 0, bitstring, 1, signature.Length);
|
||||
builder.Add (new ASN1 (0x03, bitstring));
|
||||
return builder.GetBytes ();
|
||||
}
|
||||
|
||||
public virtual byte[] Sign (RSA key)
|
||||
{
|
||||
string oid = GetOid (hashName);
|
||||
ASN1 tbs = ToBeSigned (oid);
|
||||
HashAlgorithm ha = HashAlgorithm.Create (hashName);
|
||||
byte[] hash = ha.ComputeHash (tbs.GetBytes ());
|
||||
|
||||
RSAPKCS1SignatureFormatter pkcs1 = new RSAPKCS1SignatureFormatter (key);
|
||||
pkcs1.SetHashAlgorithm (hashName);
|
||||
byte[] signature = pkcs1.CreateSignature (hash);
|
||||
|
||||
return Build (tbs, oid, signature);
|
||||
}
|
||||
|
||||
public virtual byte[] Sign (DSA key)
|
||||
{
|
||||
string oid = "1.2.840.10040.4.3";
|
||||
ASN1 tbs = ToBeSigned (oid);
|
||||
HashAlgorithm ha = HashAlgorithm.Create (hashName);
|
||||
if (!(ha is SHA1))
|
||||
throw new NotSupportedException ("Only SHA-1 is supported for DSA");
|
||||
byte[] hash = ha.ComputeHash (tbs.GetBytes ());
|
||||
|
||||
DSASignatureFormatter dsa = new DSASignatureFormatter (key);
|
||||
dsa.SetHashAlgorithm (hashName);
|
||||
byte[] rs = dsa.CreateSignature (hash);
|
||||
|
||||
// split R and S
|
||||
byte[] r = new byte [20];
|
||||
Buffer.BlockCopy (rs, 0, r, 0, 20);
|
||||
byte[] s = new byte [20];
|
||||
Buffer.BlockCopy (rs, 20, s, 0, 20);
|
||||
ASN1 signature = new ASN1 (0x30);
|
||||
signature.Add (new ASN1 (0x02, r));
|
||||
signature.Add (new ASN1 (0x02, s));
|
||||
|
||||
// dsaWithSha1 (1 2 840 10040 4 3)
|
||||
return Build (tbs, oid, signature.GetBytes ());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,567 @@
|
||||
//
|
||||
// X509Certificates.cs: Handles X.509 certificates.
|
||||
//
|
||||
// Author:
|
||||
// Sebastien Pouliot <sebastien@xamarin.com>
|
||||
//
|
||||
// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
|
||||
// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Security.Cryptography;
|
||||
using SSCX = System.Security.Cryptography.X509Certificates;
|
||||
using System.Security.Permissions;
|
||||
using System.Text;
|
||||
using Mono.Security.Cryptography;
|
||||
|
||||
namespace Mono.Security.X509 {
|
||||
|
||||
// References:
|
||||
// a. Internet X.509 Public Key Infrastructure Certificate and CRL Profile
|
||||
// http://www.ietf.org/rfc/rfc3280.txt
|
||||
// b. ITU ASN.1 standards (free download)
|
||||
// http://www.itu.int/ITU-T/studygroups/com17/languages/
|
||||
|
||||
#if INSIDE_CORLIB
|
||||
internal class X509Certificate : ISerializable {
|
||||
#else
|
||||
public class X509Certificate : ISerializable {
|
||||
#endif
|
||||
|
||||
private ASN1 decoder;
|
||||
|
||||
private byte[] m_encodedcert;
|
||||
private DateTime m_from;
|
||||
private DateTime m_until;
|
||||
private ASN1 issuer;
|
||||
private string m_issuername;
|
||||
private string m_keyalgo;
|
||||
private byte[] m_keyalgoparams;
|
||||
private ASN1 subject;
|
||||
private string m_subject;
|
||||
private byte[] m_publickey;
|
||||
private byte[] signature;
|
||||
private string m_signaturealgo;
|
||||
private byte[] m_signaturealgoparams;
|
||||
private byte[] certhash;
|
||||
private RSA _rsa;
|
||||
private DSA _dsa;
|
||||
|
||||
// from http://msdn.microsoft.com/en-gb/library/ff635835.aspx
|
||||
private const string OID_DSA = "1.2.840.10040.4.1";
|
||||
private const string OID_RSA = "1.2.840.113549.1.1.1";
|
||||
|
||||
// from http://www.ietf.org/rfc/rfc2459.txt
|
||||
//
|
||||
//Certificate ::= SEQUENCE {
|
||||
// tbsCertificate TBSCertificate,
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
// signature BIT STRING }
|
||||
//
|
||||
//TBSCertificate ::= SEQUENCE {
|
||||
// version [0] Version DEFAULT v1,
|
||||
// serialNumber CertificateSerialNumber,
|
||||
// signature AlgorithmIdentifier,
|
||||
// issuer Name,
|
||||
// validity Validity,
|
||||
// subject Name,
|
||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version shall be v2 or v3
|
||||
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version shall be v2 or v3
|
||||
// extensions [3] Extensions OPTIONAL
|
||||
// -- If present, version shall be v3 -- }
|
||||
private int version;
|
||||
private byte[] serialnumber;
|
||||
|
||||
private byte[] issuerUniqueID;
|
||||
private byte[] subjectUniqueID;
|
||||
private X509ExtensionCollection extensions;
|
||||
|
||||
private static string encoding_error = ("Input data cannot be coded as a valid certificate.");
|
||||
|
||||
|
||||
// that's were the real job is!
|
||||
private void Parse (byte[] data)
|
||||
{
|
||||
try {
|
||||
decoder = new ASN1 (data);
|
||||
// Certificate
|
||||
if (decoder.Tag != 0x30)
|
||||
throw new CryptographicException (encoding_error);
|
||||
// Certificate / TBSCertificate
|
||||
if (decoder [0].Tag != 0x30)
|
||||
throw new CryptographicException (encoding_error);
|
||||
|
||||
ASN1 tbsCertificate = decoder [0];
|
||||
|
||||
int tbs = 0;
|
||||
// Certificate / TBSCertificate / Version
|
||||
ASN1 v = decoder [0][tbs];
|
||||
version = 1; // DEFAULT v1
|
||||
if ((v.Tag == 0xA0) && (v.Count > 0)) {
|
||||
// version (optional) is present only in v2+ certs
|
||||
version += v [0].Value [0]; // zero based
|
||||
tbs++;
|
||||
}
|
||||
|
||||
// Certificate / TBSCertificate / CertificateSerialNumber
|
||||
ASN1 sn = decoder [0][tbs++];
|
||||
if (sn.Tag != 0x02)
|
||||
throw new CryptographicException (encoding_error);
|
||||
serialnumber = sn.Value;
|
||||
Array.Reverse (serialnumber, 0, serialnumber.Length);
|
||||
|
||||
// Certificate / TBSCertificate / AlgorithmIdentifier
|
||||
tbs++;
|
||||
// ASN1 signatureAlgo = tbsCertificate.Element (tbs++, 0x30);
|
||||
|
||||
issuer = tbsCertificate.Element (tbs++, 0x30);
|
||||
m_issuername = X501.ToString (issuer);
|
||||
|
||||
ASN1 validity = tbsCertificate.Element (tbs++, 0x30);
|
||||
ASN1 notBefore = validity [0];
|
||||
m_from = ASN1Convert.ToDateTime (notBefore);
|
||||
ASN1 notAfter = validity [1];
|
||||
m_until = ASN1Convert.ToDateTime (notAfter);
|
||||
|
||||
subject = tbsCertificate.Element (tbs++, 0x30);
|
||||
m_subject = X501.ToString (subject);
|
||||
|
||||
ASN1 subjectPublicKeyInfo = tbsCertificate.Element (tbs++, 0x30);
|
||||
|
||||
ASN1 algorithm = subjectPublicKeyInfo.Element (0, 0x30);
|
||||
ASN1 algo = algorithm.Element (0, 0x06);
|
||||
m_keyalgo = ASN1Convert.ToOid (algo);
|
||||
// parameters ANY DEFINED BY algorithm OPTIONAL
|
||||
// so we dont ask for a specific (Element) type and return DER
|
||||
ASN1 parameters = algorithm [1];
|
||||
m_keyalgoparams = ((algorithm.Count > 1) ? parameters.GetBytes () : null);
|
||||
|
||||
ASN1 subjectPublicKey = subjectPublicKeyInfo.Element (1, 0x03);
|
||||
// we must drop th first byte (which is the number of unused bits
|
||||
// in the BITSTRING)
|
||||
int n = subjectPublicKey.Length - 1;
|
||||
m_publickey = new byte [n];
|
||||
Buffer.BlockCopy (subjectPublicKey.Value, 1, m_publickey, 0, n);
|
||||
|
||||
// signature processing
|
||||
byte[] bitstring = decoder [2].Value;
|
||||
// first byte contains unused bits in first byte
|
||||
signature = new byte [bitstring.Length - 1];
|
||||
Buffer.BlockCopy (bitstring, 1, signature, 0, signature.Length);
|
||||
|
||||
algorithm = decoder [1];
|
||||
algo = algorithm.Element (0, 0x06);
|
||||
m_signaturealgo = ASN1Convert.ToOid (algo);
|
||||
parameters = algorithm [1];
|
||||
if (parameters != null)
|
||||
m_signaturealgoparams = parameters.GetBytes ();
|
||||
else
|
||||
m_signaturealgoparams = null;
|
||||
|
||||
// Certificate / TBSCertificate / issuerUniqueID
|
||||
ASN1 issuerUID = tbsCertificate.Element (tbs, 0x81);
|
||||
if (issuerUID != null) {
|
||||
tbs++;
|
||||
issuerUniqueID = issuerUID.Value;
|
||||
}
|
||||
|
||||
// Certificate / TBSCertificate / subjectUniqueID
|
||||
ASN1 subjectUID = tbsCertificate.Element (tbs, 0x82);
|
||||
if (subjectUID != null) {
|
||||
tbs++;
|
||||
subjectUniqueID = subjectUID.Value;
|
||||
}
|
||||
|
||||
// Certificate / TBSCertificate / Extensions
|
||||
ASN1 extns = tbsCertificate.Element (tbs, 0xA3);
|
||||
if ((extns != null) && (extns.Count == 1))
|
||||
extensions = new X509ExtensionCollection (extns [0]);
|
||||
else
|
||||
extensions = new X509ExtensionCollection (null);
|
||||
|
||||
// keep a copy of the original data
|
||||
m_encodedcert = (byte[]) data.Clone ();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new CryptographicException (encoding_error, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// constructors
|
||||
|
||||
public X509Certificate (byte[] data)
|
||||
{
|
||||
if (data != null) {
|
||||
// does it looks like PEM ?
|
||||
if ((data.Length > 0) && (data [0] != 0x30)) {
|
||||
try {
|
||||
data = PEM ("CERTIFICATE", data);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new CryptographicException (encoding_error, ex);
|
||||
}
|
||||
}
|
||||
Parse (data);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GetUnsignedBigInteger (byte[] integer)
|
||||
{
|
||||
if (integer [0] == 0x00) {
|
||||
// this first byte is added so we're sure it's an unsigned integer
|
||||
// however we can't feed it into RSAParameters or DSAParameters
|
||||
int length = integer.Length - 1;
|
||||
byte[] uinteger = new byte [length];
|
||||
Buffer.BlockCopy (integer, 1, uinteger, 0, length);
|
||||
return uinteger;
|
||||
}
|
||||
else
|
||||
return integer;
|
||||
}
|
||||
|
||||
// public methods
|
||||
|
||||
public DSA DSA {
|
||||
get {
|
||||
if (m_keyalgoparams == null)
|
||||
throw new CryptographicException ("Missing key algorithm parameters.");
|
||||
|
||||
if (_dsa == null && m_keyalgo == OID_DSA) {
|
||||
DSAParameters dsaParams = new DSAParameters ();
|
||||
// for DSA m_publickey contains 1 ASN.1 integer - Y
|
||||
ASN1 pubkey = new ASN1 (m_publickey);
|
||||
if ((pubkey == null) || (pubkey.Tag != 0x02))
|
||||
return null;
|
||||
dsaParams.Y = GetUnsignedBigInteger (pubkey.Value);
|
||||
|
||||
ASN1 param = new ASN1 (m_keyalgoparams);
|
||||
if ((param == null) || (param.Tag != 0x30) || (param.Count < 3))
|
||||
return null;
|
||||
if ((param [0].Tag != 0x02) || (param [1].Tag != 0x02) || (param [2].Tag != 0x02))
|
||||
return null;
|
||||
dsaParams.P = GetUnsignedBigInteger (param [0].Value);
|
||||
dsaParams.Q = GetUnsignedBigInteger (param [1].Value);
|
||||
dsaParams.G = GetUnsignedBigInteger (param [2].Value);
|
||||
|
||||
// BUG: MS BCL 1.0 can't import a key which
|
||||
// isn't the same size as the one present in
|
||||
// the container.
|
||||
_dsa = (DSA) new DSACryptoServiceProvider (dsaParams.Y.Length << 3);
|
||||
_dsa.ImportParameters (dsaParams);
|
||||
}
|
||||
return _dsa;
|
||||
}
|
||||
|
||||
set {
|
||||
_dsa = value;
|
||||
if (value != null)
|
||||
_rsa = null;
|
||||
}
|
||||
}
|
||||
|
||||
public X509ExtensionCollection Extensions {
|
||||
get { return extensions; }
|
||||
}
|
||||
|
||||
public byte[] Hash {
|
||||
get {
|
||||
if (certhash == null) {
|
||||
if ((decoder == null) || (decoder.Count < 1))
|
||||
return null;
|
||||
string algo = PKCS1.HashNameFromOid (m_signaturealgo, false);
|
||||
if (algo == null)
|
||||
return null;
|
||||
byte[] toBeSigned = decoder [0].GetBytes ();
|
||||
using (var hash = PKCS1.CreateFromName (algo))
|
||||
certhash = hash.ComputeHash (toBeSigned, 0, toBeSigned.Length);
|
||||
}
|
||||
return (byte[]) certhash.Clone ();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string IssuerName {
|
||||
get { return m_issuername; }
|
||||
}
|
||||
|
||||
public virtual string KeyAlgorithm {
|
||||
get { return m_keyalgo; }
|
||||
}
|
||||
|
||||
public virtual byte[] KeyAlgorithmParameters {
|
||||
get {
|
||||
if (m_keyalgoparams == null)
|
||||
return null;
|
||||
return (byte[]) m_keyalgoparams.Clone ();
|
||||
}
|
||||
set { m_keyalgoparams = value; }
|
||||
}
|
||||
|
||||
public virtual byte[] PublicKey {
|
||||
get {
|
||||
if (m_publickey == null)
|
||||
return null;
|
||||
return (byte[]) m_publickey.Clone ();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual RSA RSA {
|
||||
get {
|
||||
if (_rsa == null && m_keyalgo == OID_RSA) {
|
||||
RSAParameters rsaParams = new RSAParameters ();
|
||||
// for RSA m_publickey contains 2 ASN.1 integers
|
||||
// the modulus and the public exponent
|
||||
ASN1 pubkey = new ASN1 (m_publickey);
|
||||
ASN1 modulus = pubkey [0];
|
||||
if ((modulus == null) || (modulus.Tag != 0x02))
|
||||
return null;
|
||||
ASN1 exponent = pubkey [1];
|
||||
if (exponent.Tag != 0x02)
|
||||
return null;
|
||||
|
||||
rsaParams.Modulus = GetUnsignedBigInteger (modulus.Value);
|
||||
rsaParams.Exponent = exponent.Value;
|
||||
|
||||
// BUG: MS BCL 1.0 can't import a key which
|
||||
// isn't the same size as the one present in
|
||||
// the container.
|
||||
int keySize = (rsaParams.Modulus.Length << 3);
|
||||
_rsa = (RSA) new RSACryptoServiceProvider (keySize);
|
||||
_rsa.ImportParameters (rsaParams);
|
||||
}
|
||||
return _rsa;
|
||||
}
|
||||
|
||||
set {
|
||||
if (value != null)
|
||||
_dsa = null;
|
||||
_rsa = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual byte[] RawData {
|
||||
get {
|
||||
if (m_encodedcert == null)
|
||||
return null;
|
||||
return (byte[]) m_encodedcert.Clone ();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual byte[] SerialNumber {
|
||||
get {
|
||||
if (serialnumber == null)
|
||||
return null;
|
||||
return (byte[]) serialnumber.Clone ();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual byte[] Signature {
|
||||
get {
|
||||
if (signature == null)
|
||||
return null;
|
||||
|
||||
switch (m_signaturealgo) {
|
||||
case "1.2.840.113549.1.1.2": // MD2 with RSA encryption
|
||||
case "1.2.840.113549.1.1.3": // MD4 with RSA encryption
|
||||
case "1.2.840.113549.1.1.4": // MD5 with RSA encryption
|
||||
case "1.2.840.113549.1.1.5": // SHA-1 with RSA Encryption
|
||||
case "1.3.14.3.2.29": // SHA1 with RSA signature
|
||||
case "1.2.840.113549.1.1.11": // SHA-256 with RSA Encryption
|
||||
case "1.2.840.113549.1.1.12": // SHA-384 with RSA Encryption
|
||||
case "1.2.840.113549.1.1.13": // SHA-512 with RSA Encryption
|
||||
case "1.3.36.3.3.1.2": // RIPEMD160 with RSA Encryption
|
||||
return (byte[]) signature.Clone ();
|
||||
|
||||
case "1.2.840.10040.4.3": // SHA-1 with DSA
|
||||
ASN1 sign = new ASN1 (signature);
|
||||
if ((sign == null) || (sign.Count != 2))
|
||||
return null;
|
||||
byte[] part1 = sign [0].Value;
|
||||
byte[] part2 = sign [1].Value;
|
||||
byte[] sig = new byte [40];
|
||||
// parts may be less than 20 bytes (i.e. first bytes were 0x00)
|
||||
// parts may be more than 20 bytes (i.e. first byte > 0x80, negative)
|
||||
int s1 = System.Math.Max (0, part1.Length - 20);
|
||||
int e1 = System.Math.Max (0, 20 - part1.Length);
|
||||
Buffer.BlockCopy (part1, s1, sig, e1, part1.Length - s1);
|
||||
int s2 = System.Math.Max (0, part2.Length - 20);
|
||||
int e2 = System.Math.Max (20, 40 - part2.Length);
|
||||
Buffer.BlockCopy (part2, s2, sig, e2, part2.Length - s2);
|
||||
return sig;
|
||||
|
||||
default:
|
||||
throw new CryptographicException ("Unsupported hash algorithm: " + m_signaturealgo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string SignatureAlgorithm {
|
||||
get { return m_signaturealgo; }
|
||||
}
|
||||
|
||||
public virtual byte[] SignatureAlgorithmParameters {
|
||||
get {
|
||||
if (m_signaturealgoparams == null)
|
||||
return m_signaturealgoparams;
|
||||
return (byte[]) m_signaturealgoparams.Clone ();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string SubjectName {
|
||||
get { return m_subject; }
|
||||
}
|
||||
|
||||
public virtual DateTime ValidFrom {
|
||||
get { return m_from; }
|
||||
}
|
||||
|
||||
public virtual DateTime ValidUntil {
|
||||
get { return m_until; }
|
||||
}
|
||||
|
||||
public int Version {
|
||||
get { return version; }
|
||||
}
|
||||
|
||||
public bool IsCurrent {
|
||||
get { return WasCurrent (DateTime.UtcNow); }
|
||||
}
|
||||
|
||||
public bool WasCurrent (DateTime instant)
|
||||
{
|
||||
return ((instant > ValidFrom) && (instant <= ValidUntil));
|
||||
}
|
||||
|
||||
// uncommon v2 "extension"
|
||||
public byte[] IssuerUniqueIdentifier {
|
||||
get {
|
||||
if (issuerUniqueID == null)
|
||||
return null;
|
||||
return (byte[]) issuerUniqueID.Clone ();
|
||||
}
|
||||
}
|
||||
|
||||
// uncommon v2 "extension"
|
||||
public byte[] SubjectUniqueIdentifier {
|
||||
get {
|
||||
if (subjectUniqueID == null)
|
||||
return null;
|
||||
return (byte[]) subjectUniqueID.Clone ();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool VerifySignature (DSA dsa)
|
||||
{
|
||||
// signatureOID is check by both this.Hash and this.Signature
|
||||
DSASignatureDeformatter v = new DSASignatureDeformatter (dsa);
|
||||
// only SHA-1 is supported
|
||||
v.SetHashAlgorithm ("SHA1");
|
||||
return v.VerifySignature (this.Hash, this.Signature);
|
||||
}
|
||||
|
||||
internal bool VerifySignature (RSA rsa)
|
||||
{
|
||||
// SHA1-1 with DSA
|
||||
if (m_signaturealgo == "1.2.840.10040.4.3")
|
||||
return false;
|
||||
RSAPKCS1SignatureDeformatter v = new RSAPKCS1SignatureDeformatter (rsa);
|
||||
v.SetHashAlgorithm (PKCS1.HashNameFromOid (m_signaturealgo));
|
||||
return v.VerifySignature (this.Hash, this.Signature);
|
||||
}
|
||||
|
||||
public bool VerifySignature (AsymmetricAlgorithm aa)
|
||||
{
|
||||
if (aa == null)
|
||||
throw new ArgumentNullException ("aa");
|
||||
|
||||
if (aa is RSA)
|
||||
return VerifySignature (aa as RSA);
|
||||
else if (aa is DSA)
|
||||
return VerifySignature (aa as DSA);
|
||||
else
|
||||
throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
|
||||
}
|
||||
|
||||
public bool CheckSignature (byte[] hash, string hashAlgorithm, byte[] signature)
|
||||
{
|
||||
RSACryptoServiceProvider r = (RSACryptoServiceProvider) RSA;
|
||||
return r.VerifyHash (hash, hashAlgorithm, signature);
|
||||
}
|
||||
|
||||
public bool IsSelfSigned {
|
||||
get {
|
||||
if (m_issuername != m_subject)
|
||||
return false;
|
||||
|
||||
try {
|
||||
if (RSA != null)
|
||||
return VerifySignature (RSA);
|
||||
else if (DSA != null)
|
||||
return VerifySignature (DSA);
|
||||
else
|
||||
return false; // e.g. a certificate with only DSA parameters
|
||||
}
|
||||
catch (CryptographicException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ASN1 GetIssuerName ()
|
||||
{
|
||||
return issuer;
|
||||
}
|
||||
|
||||
public ASN1 GetSubjectName ()
|
||||
{
|
||||
return subject;
|
||||
}
|
||||
|
||||
protected X509Certificate (SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
Parse ((byte[]) info.GetValue ("raw", typeof (byte[])));
|
||||
}
|
||||
|
||||
[SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
|
||||
public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
info.AddValue ("raw", m_encodedcert);
|
||||
// note: we NEVER serialize the private key
|
||||
}
|
||||
|
||||
static byte[] PEM (string type, byte[] data)
|
||||
{
|
||||
string pem = Encoding.ASCII.GetString (data);
|
||||
string header = String.Format ("-----BEGIN {0}-----", type);
|
||||
string footer = String.Format ("-----END {0}-----", type);
|
||||
int start = pem.IndexOf (header) + header.Length;
|
||||
int end = pem.IndexOf (footer, start);
|
||||
string base64 = pem.Substring (start, (end - start));
|
||||
return Convert.FromBase64String (base64);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,244 @@
|
||||
//
|
||||
// X509CertificateBuilder.cs: Handles building of X.509 certificates.
|
||||
//
|
||||
// Author:
|
||||
// Sebastien Pouliot <sebastien@ximian.com>
|
||||
//
|
||||
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// (C) 2004 Novell (http://www.novell.com)
|
||||
//
|
||||
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Mono.Security.X509 {
|
||||
// From RFC3280
|
||||
/*
|
||||
* Certificate ::= SEQUENCE {
|
||||
* tbsCertificate TBSCertificate,
|
||||
* signatureAlgorithm AlgorithmIdentifier,
|
||||
* signature BIT STRING
|
||||
* }
|
||||
* TBSCertificate ::= SEQUENCE {
|
||||
* version [0] Version DEFAULT v1,
|
||||
* serialNumber CertificateSerialNumber,
|
||||
* signature AlgorithmIdentifier,
|
||||
* issuer Name,
|
||||
* validity Validity,
|
||||
* subject Name,
|
||||
* subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
* issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
* -- If present, version MUST be v2 or v3
|
||||
* subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
* -- If present, version MUST be v2 or v3
|
||||
* extensions [3] Extensions OPTIONAL
|
||||
* -- If present, version MUST be v3 --
|
||||
* }
|
||||
* Version ::= INTEGER { v1(0), v2(1), v3(2) }
|
||||
* CertificateSerialNumber ::= INTEGER
|
||||
* Validity ::= SEQUENCE {
|
||||
* notBefore Time,
|
||||
* notAfter Time
|
||||
* }
|
||||
* Time ::= CHOICE {
|
||||
* utcTime UTCTime,
|
||||
* generalTime GeneralizedTime
|
||||
* }
|
||||
*/
|
||||
public class X509CertificateBuilder : X509Builder {
|
||||
|
||||
private byte version;
|
||||
private byte[] sn;
|
||||
private string issuer;
|
||||
private DateTime notBefore;
|
||||
private DateTime notAfter;
|
||||
private string subject;
|
||||
private AsymmetricAlgorithm aa;
|
||||
private byte[] issuerUniqueID;
|
||||
private byte[] subjectUniqueID;
|
||||
private X509ExtensionCollection extensions;
|
||||
|
||||
public X509CertificateBuilder () : this (3) {}
|
||||
|
||||
public X509CertificateBuilder (byte version)
|
||||
{
|
||||
if (version > 3)
|
||||
throw new ArgumentException ("Invalid certificate version");
|
||||
this.version = version;
|
||||
extensions = new X509ExtensionCollection ();
|
||||
}
|
||||
|
||||
public byte Version {
|
||||
get { return version; }
|
||||
set { version = value; }
|
||||
}
|
||||
|
||||
public byte[] SerialNumber {
|
||||
get { return sn; }
|
||||
set { sn = value; }
|
||||
}
|
||||
|
||||
public string IssuerName {
|
||||
get { return issuer; }
|
||||
set { issuer = value; }
|
||||
}
|
||||
|
||||
public DateTime NotBefore {
|
||||
get { return notBefore; }
|
||||
set { notBefore = value; }
|
||||
}
|
||||
|
||||
public DateTime NotAfter {
|
||||
get { return notAfter; }
|
||||
set { notAfter = value; }
|
||||
}
|
||||
|
||||
public string SubjectName {
|
||||
get { return subject; }
|
||||
set { subject = value; }
|
||||
}
|
||||
|
||||
public AsymmetricAlgorithm SubjectPublicKey {
|
||||
get { return aa; }
|
||||
set { aa = value; }
|
||||
}
|
||||
|
||||
public byte[] IssuerUniqueId {
|
||||
get { return issuerUniqueID; }
|
||||
set { issuerUniqueID = value; }
|
||||
}
|
||||
|
||||
public byte[] SubjectUniqueId {
|
||||
get { return subjectUniqueID; }
|
||||
set { subjectUniqueID = value; }
|
||||
}
|
||||
|
||||
public X509ExtensionCollection Extensions {
|
||||
get { return extensions; }
|
||||
}
|
||||
|
||||
|
||||
/* SubjectPublicKeyInfo ::= SEQUENCE {
|
||||
* algorithm AlgorithmIdentifier,
|
||||
* subjectPublicKey BIT STRING }
|
||||
*/
|
||||
private ASN1 SubjectPublicKeyInfo ()
|
||||
{
|
||||
ASN1 keyInfo = new ASN1 (0x30);
|
||||
if (aa is RSA) {
|
||||
keyInfo.Add (PKCS7.AlgorithmIdentifier ("1.2.840.113549.1.1.1"));
|
||||
RSAParameters p = (aa as RSA).ExportParameters (false);
|
||||
/* RSAPublicKey ::= SEQUENCE {
|
||||
* modulus INTEGER, -- n
|
||||
* publicExponent INTEGER } -- e
|
||||
*/
|
||||
ASN1 key = new ASN1 (0x30);
|
||||
key.Add (ASN1Convert.FromUnsignedBigInteger (p.Modulus));
|
||||
key.Add (ASN1Convert.FromUnsignedBigInteger (p.Exponent));
|
||||
keyInfo.Add (new ASN1 (UniqueIdentifier (key.GetBytes ())));
|
||||
}
|
||||
else if (aa is DSA) {
|
||||
DSAParameters p = (aa as DSA).ExportParameters (false);
|
||||
/* Dss-Parms ::= SEQUENCE {
|
||||
* p INTEGER,
|
||||
* q INTEGER,
|
||||
* g INTEGER }
|
||||
*/
|
||||
ASN1 param = new ASN1 (0x30);
|
||||
param.Add (ASN1Convert.FromUnsignedBigInteger (p.P));
|
||||
param.Add (ASN1Convert.FromUnsignedBigInteger (p.Q));
|
||||
param.Add (ASN1Convert.FromUnsignedBigInteger (p.G));
|
||||
keyInfo.Add (PKCS7.AlgorithmIdentifier ("1.2.840.10040.4.1", param));
|
||||
ASN1 key = keyInfo.Add (new ASN1 (0x03));
|
||||
// DSAPublicKey ::= INTEGER -- public key, y
|
||||
key.Add (ASN1Convert.FromUnsignedBigInteger (p.Y));
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
|
||||
return keyInfo;
|
||||
}
|
||||
|
||||
private byte[] UniqueIdentifier (byte[] id)
|
||||
{
|
||||
// UniqueIdentifier ::= BIT STRING
|
||||
ASN1 uid = new ASN1 (0x03);
|
||||
// first byte in a BITSTRING is the number of unused bits in the first byte
|
||||
byte[] v = new byte [id.Length + 1];
|
||||
Buffer.BlockCopy (id, 0, v, 1, id.Length);
|
||||
uid.Value = v;
|
||||
return uid.GetBytes ();
|
||||
}
|
||||
|
||||
protected override ASN1 ToBeSigned (string oid)
|
||||
{
|
||||
// TBSCertificate
|
||||
ASN1 tbsCert = new ASN1 (0x30);
|
||||
|
||||
if (version > 1) {
|
||||
// TBSCertificate / [0] Version DEFAULT v1,
|
||||
byte[] ver = { (byte)(version - 1) };
|
||||
ASN1 v = tbsCert.Add (new ASN1 (0xA0));
|
||||
v.Add (new ASN1 (0x02, ver));
|
||||
}
|
||||
|
||||
// TBSCertificate / CertificateSerialNumber,
|
||||
tbsCert.Add (new ASN1 (0x02, sn));
|
||||
|
||||
// TBSCertificate / AlgorithmIdentifier,
|
||||
tbsCert.Add (PKCS7.AlgorithmIdentifier (oid));
|
||||
|
||||
// TBSCertificate / Name
|
||||
tbsCert.Add (X501.FromString (issuer));
|
||||
|
||||
// TBSCertificate / Validity
|
||||
ASN1 validity = tbsCert.Add (new ASN1 (0x30));
|
||||
// TBSCertificate / Validity / Time
|
||||
validity.Add (ASN1Convert.FromDateTime (notBefore));
|
||||
// TBSCertificate / Validity / Time
|
||||
validity.Add (ASN1Convert.FromDateTime (notAfter));
|
||||
|
||||
// TBSCertificate / Name
|
||||
tbsCert.Add (X501.FromString (subject));
|
||||
|
||||
// TBSCertificate / SubjectPublicKeyInfo
|
||||
tbsCert.Add (SubjectPublicKeyInfo ());
|
||||
|
||||
if (version > 1) {
|
||||
// TBSCertificate / [1] IMPLICIT UniqueIdentifier OPTIONAL
|
||||
if (issuerUniqueID != null)
|
||||
tbsCert.Add (new ASN1 (0xA1, UniqueIdentifier (issuerUniqueID)));
|
||||
|
||||
// TBSCertificate / [2] IMPLICIT UniqueIdentifier OPTIONAL
|
||||
if (subjectUniqueID != null)
|
||||
tbsCert.Add (new ASN1 (0xA1, UniqueIdentifier (subjectUniqueID)));
|
||||
|
||||
// TBSCertificate / [3] Extensions OPTIONAL
|
||||
if ((version > 2) && (extensions.Count > 0))
|
||||
tbsCert.Add (new ASN1 (0xA3, extensions.GetBytes ()));
|
||||
}
|
||||
|
||||
return tbsCert;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
//
|
||||
// Based on System.Security.Cryptography.X509Certificates.X509CertificateCollection
|
||||
// in System assembly
|
||||
//
|
||||
// Authors:
|
||||
// Lawrence Pit (loz@cable.a2000.nl)
|
||||
// Sebastien Pouliot <sebastien@ximian.com>
|
||||
//
|
||||
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Mono.Security.X509 {
|
||||
|
||||
[Serializable]
|
||||
#if INSIDE_CORLIB
|
||||
internal
|
||||
#else
|
||||
public
|
||||
#endif
|
||||
class X509CertificateCollection : CollectionBase, IEnumerable {
|
||||
|
||||
public X509CertificateCollection ()
|
||||
{
|
||||
}
|
||||
|
||||
public X509CertificateCollection (X509Certificate [] value)
|
||||
{
|
||||
AddRange (value);
|
||||
}
|
||||
|
||||
public X509CertificateCollection (X509CertificateCollection value)
|
||||
{
|
||||
AddRange (value);
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
public X509Certificate this [int index] {
|
||||
get { return (X509Certificate) InnerList [index]; }
|
||||
set { InnerList [index] = value; }
|
||||
}
|
||||
|
||||
// Methods
|
||||
|
||||
public int Add (X509Certificate value)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException ("value");
|
||||
|
||||
return InnerList.Add (value);
|
||||
}
|
||||
|
||||
public void AddRange (X509Certificate [] value)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException ("value");
|
||||
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
InnerList.Add (value [i]);
|
||||
}
|
||||
|
||||
public void AddRange (X509CertificateCollection value)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException ("value");
|
||||
|
||||
for (int i = 0; i < value.InnerList.Count; i++)
|
||||
InnerList.Add (value [i]);
|
||||
}
|
||||
|
||||
public bool Contains (X509Certificate value)
|
||||
{
|
||||
return (IndexOf (value) != -1);
|
||||
}
|
||||
|
||||
public void CopyTo (X509Certificate[] array, int index)
|
||||
{
|
||||
InnerList.CopyTo (array, index);
|
||||
}
|
||||
|
||||
public new X509CertificateEnumerator GetEnumerator ()
|
||||
{
|
||||
return new X509CertificateEnumerator (this);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator ()
|
||||
{
|
||||
return InnerList.GetEnumerator ();
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return InnerList.GetHashCode ();
|
||||
}
|
||||
|
||||
public int IndexOf (X509Certificate value)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException ("value");
|
||||
|
||||
byte[] hash = value.Hash;
|
||||
for (int i=0; i < InnerList.Count; i++) {
|
||||
X509Certificate x509 = (X509Certificate) InnerList [i];
|
||||
if (Compare (x509.Hash, hash))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Insert (int index, X509Certificate value)
|
||||
{
|
||||
InnerList.Insert (index, value);
|
||||
}
|
||||
|
||||
public void Remove (X509Certificate value)
|
||||
{
|
||||
InnerList.Remove (value);
|
||||
}
|
||||
|
||||
// private stuff
|
||||
|
||||
private bool Compare (byte[] array1, byte[] array2)
|
||||
{
|
||||
if ((array1 == null) && (array2 == null))
|
||||
return true;
|
||||
if ((array1 == null) || (array2 == null))
|
||||
return false;
|
||||
if (array1.Length != array2.Length)
|
||||
return false;
|
||||
for (int i=0; i < array1.Length; i++) {
|
||||
if (array1 [i] != array2 [i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Inner Class
|
||||
|
||||
public class X509CertificateEnumerator : IEnumerator {
|
||||
|
||||
private IEnumerator enumerator;
|
||||
|
||||
// Constructors
|
||||
|
||||
public X509CertificateEnumerator (X509CertificateCollection mappings)
|
||||
{
|
||||
enumerator = ((IEnumerable) mappings).GetEnumerator ();
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
public X509Certificate Current {
|
||||
get { return (X509Certificate) enumerator.Current; }
|
||||
}
|
||||
|
||||
object IEnumerator.Current {
|
||||
get { return enumerator.Current; }
|
||||
}
|
||||
|
||||
// Methods
|
||||
|
||||
bool IEnumerator.MoveNext ()
|
||||
{
|
||||
return enumerator.MoveNext ();
|
||||
}
|
||||
|
||||
void IEnumerator.Reset ()
|
||||
{
|
||||
enumerator.Reset ();
|
||||
}
|
||||
|
||||
public bool MoveNext ()
|
||||
{
|
||||
return enumerator.MoveNext ();
|
||||
}
|
||||
|
||||
public void Reset ()
|
||||
{
|
||||
enumerator.Reset ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
//
|
||||
// X509Extension.cs: Base class for all X.509 extensions.
|
||||
//
|
||||
// Author:
|
||||
// Sebastien Pouliot <sebastien@ximian.com>
|
||||
//
|
||||
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
using Mono.Security;
|
||||
|
||||
namespace Mono.Security.X509 {
|
||||
/*
|
||||
* Extension ::= SEQUENCE {
|
||||
* extnID OBJECT IDENTIFIER,
|
||||
* critical BOOLEAN DEFAULT FALSE,
|
||||
* extnValue OCTET STRING
|
||||
* }
|
||||
*/
|
||||
#if INSIDE_CORLIB
|
||||
internal
|
||||
#else
|
||||
public
|
||||
#endif
|
||||
class X509Extension {
|
||||
|
||||
protected string extnOid;
|
||||
protected bool extnCritical;
|
||||
protected ASN1 extnValue;
|
||||
|
||||
protected X509Extension ()
|
||||
{
|
||||
extnCritical = false;
|
||||
}
|
||||
|
||||
public X509Extension (ASN1 asn1)
|
||||
{
|
||||
if ((asn1.Tag != 0x30) || (asn1.Count < 2))
|
||||
throw new ArgumentException (("Invalid X.509 extension."));
|
||||
if (asn1[0].Tag != 0x06)
|
||||
throw new ArgumentException (("Invalid X.509 extension."));
|
||||
|
||||
extnOid = ASN1Convert.ToOid (asn1[0]);
|
||||
extnCritical = ((asn1[1].Tag == 0x01) && (asn1[1].Value[0] == 0xFF));
|
||||
// last element is an octet string which may need to be decoded
|
||||
extnValue = asn1 [asn1.Count - 1];
|
||||
if ((extnValue.Tag == 0x04) && (extnValue.Length > 0) && (extnValue.Count == 0)) {
|
||||
try {
|
||||
ASN1 encapsulated = new ASN1 (extnValue.Value);
|
||||
extnValue.Value = null;
|
||||
extnValue.Add (encapsulated);
|
||||
}
|
||||
catch {
|
||||
// data isn't ASN.1
|
||||
}
|
||||
}
|
||||
Decode ();
|
||||
}
|
||||
|
||||
public X509Extension (X509Extension extension)
|
||||
{
|
||||
if (extension == null)
|
||||
throw new ArgumentNullException ("extension");
|
||||
if ((extension.Value == null) || (extension.Value.Tag != 0x04) || (extension.Value.Count != 1))
|
||||
throw new ArgumentException (("Invalid X.509 extension."));
|
||||
|
||||
extnOid = extension.Oid;
|
||||
extnCritical = extension.Critical;
|
||||
extnValue = extension.Value;
|
||||
Decode ();
|
||||
}
|
||||
|
||||
// encode the extension *into* an OCTET STRING
|
||||
protected virtual void Decode ()
|
||||
{
|
||||
}
|
||||
|
||||
// decode the extension from *inside* an OCTET STRING
|
||||
protected virtual void Encode ()
|
||||
{
|
||||
}
|
||||
|
||||
public ASN1 ASN1 {
|
||||
get {
|
||||
ASN1 extension = new ASN1 (0x30);
|
||||
extension.Add (ASN1Convert.FromOid (extnOid));
|
||||
if (extnCritical)
|
||||
extension.Add (new ASN1 (0x01, new byte [1] { 0xFF }));
|
||||
Encode ();
|
||||
extension.Add (extnValue);
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
|
||||
public string Oid {
|
||||
get { return extnOid; }
|
||||
}
|
||||
|
||||
public bool Critical {
|
||||
get { return extnCritical; }
|
||||
set { extnCritical = value; }
|
||||
}
|
||||
|
||||
// this gets overrided with more meaningful names
|
||||
public virtual string Name {
|
||||
get { return extnOid; }
|
||||
}
|
||||
|
||||
public ASN1 Value {
|
||||
get {
|
||||
if (extnValue == null) {
|
||||
Encode ();
|
||||
}
|
||||
return extnValue;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals (object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
X509Extension ex = (obj as X509Extension);
|
||||
if (ex == null)
|
||||
return false;
|
||||
|
||||
if (extnCritical != ex.extnCritical)
|
||||
return false;
|
||||
if (extnOid != ex.extnOid)
|
||||
return false;
|
||||
if (extnValue.Length != ex.extnValue.Length)
|
||||
return false;
|
||||
|
||||
for (int i=0; i < extnValue.Length; i++) {
|
||||
if (extnValue [i] != ex.extnValue [i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public byte[] GetBytes ()
|
||||
{
|
||||
return ASN1.GetBytes ();
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
// OID should be unique in a collection of extensions
|
||||
return extnOid.GetHashCode ();
|
||||
}
|
||||
|
||||
private void WriteLine (StringBuilder sb, int n, int pos)
|
||||
{
|
||||
byte[] value = extnValue.Value;
|
||||
int p = pos;
|
||||
for (int j=0; j < 8; j++) {
|
||||
if (j < n) {
|
||||
sb.Append (value [p++].ToString ("X2", CultureInfo.InvariantCulture));
|
||||
sb.Append (" ");
|
||||
}
|
||||
else
|
||||
sb.Append (" ");
|
||||
}
|
||||
sb.Append (" ");
|
||||
p = pos;
|
||||
for (int j=0; j < n; j++) {
|
||||
byte b = value [p++];
|
||||
if (b < 0x20)
|
||||
sb.Append (".");
|
||||
else
|
||||
sb.Append (Convert.ToChar (b));
|
||||
}
|
||||
sb.Append (Environment.NewLine);
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder ();
|
||||
int div = (extnValue.Length >> 3);
|
||||
int rem = (extnValue.Length - (div << 3));
|
||||
int x = 0;
|
||||
for (int i=0; i < div; i++) {
|
||||
WriteLine (sb, 8, x);
|
||||
x += 8;
|
||||
}
|
||||
WriteLine (sb, rem, x);
|
||||
return sb.ToString ();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
//
|
||||
// X509Extensions.cs: Handles X.509 extensions.
|
||||
//
|
||||
// Author:
|
||||
// Sebastien Pouliot <sebastien@ximian.com>
|
||||
//
|
||||
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// (C) 2004 Novell (http://www.novell.com)
|
||||
//
|
||||
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
using Mono.Security;
|
||||
|
||||
namespace Mono.Security.X509 {
|
||||
/*
|
||||
* Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
||||
*
|
||||
* Note: 1..MAX -> There shouldn't be 0 Extensions in the ASN1 structure
|
||||
*/
|
||||
#if INSIDE_CORLIB
|
||||
internal
|
||||
#else
|
||||
public
|
||||
#endif
|
||||
sealed class X509ExtensionCollection : CollectionBase, IEnumerable {
|
||||
|
||||
private bool readOnly;
|
||||
|
||||
public X509ExtensionCollection () : base ()
|
||||
{
|
||||
}
|
||||
|
||||
public X509ExtensionCollection (ASN1 asn1) : this ()
|
||||
{
|
||||
readOnly = true;
|
||||
if (asn1 == null)
|
||||
return;
|
||||
if (asn1.Tag != 0x30)
|
||||
throw new Exception ("Invalid extensions format");
|
||||
for (int i=0; i < asn1.Count; i++) {
|
||||
X509Extension extension = new X509Extension (asn1 [i]);
|
||||
InnerList.Add (extension);
|
||||
}
|
||||
}
|
||||
|
||||
public int Add (X509Extension extension)
|
||||
{
|
||||
if (extension == null)
|
||||
throw new ArgumentNullException ("extension");
|
||||
if (readOnly)
|
||||
throw new NotSupportedException ("Extensions are read only");
|
||||
|
||||
return InnerList.Add (extension);
|
||||
}
|
||||
|
||||
public void AddRange (X509Extension[] extension)
|
||||
{
|
||||
if (extension == null)
|
||||
throw new ArgumentNullException ("extension");
|
||||
if (readOnly)
|
||||
throw new NotSupportedException ("Extensions are read only");
|
||||
|
||||
for (int i = 0; i < extension.Length; i++)
|
||||
InnerList.Add (extension [i]);
|
||||
}
|
||||
|
||||
public void AddRange (X509ExtensionCollection collection)
|
||||
{
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException ("collection");
|
||||
if (readOnly)
|
||||
throw new NotSupportedException ("Extensions are read only");
|
||||
|
||||
for (int i = 0; i < collection.InnerList.Count; i++)
|
||||
InnerList.Add (collection [i]);
|
||||
}
|
||||
|
||||
public bool Contains (X509Extension extension)
|
||||
{
|
||||
return (IndexOf (extension) != -1);
|
||||
}
|
||||
|
||||
public bool Contains (string oid)
|
||||
{
|
||||
return (IndexOf (oid) != -1);
|
||||
}
|
||||
|
||||
public void CopyTo (X509Extension[] extensions, int index)
|
||||
{
|
||||
if (extensions == null)
|
||||
throw new ArgumentNullException ("extensions");
|
||||
|
||||
InnerList.CopyTo (extensions, index);
|
||||
}
|
||||
|
||||
public int IndexOf (X509Extension extension)
|
||||
{
|
||||
if (extension == null)
|
||||
throw new ArgumentNullException ("extension");
|
||||
|
||||
for (int i=0; i < InnerList.Count; i++) {
|
||||
X509Extension ex = (X509Extension) InnerList [i];
|
||||
if (ex.Equals (extension))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int IndexOf (string oid)
|
||||
{
|
||||
if (oid == null)
|
||||
throw new ArgumentNullException ("oid");
|
||||
|
||||
for (int i=0; i < InnerList.Count; i++) {
|
||||
X509Extension ex = (X509Extension) InnerList [i];
|
||||
if (ex.Oid == oid)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Insert (int index, X509Extension extension)
|
||||
{
|
||||
if (extension == null)
|
||||
throw new ArgumentNullException ("extension");
|
||||
|
||||
InnerList.Insert (index, extension);
|
||||
}
|
||||
|
||||
public void Remove (X509Extension extension)
|
||||
{
|
||||
if (extension == null)
|
||||
throw new ArgumentNullException ("extension");
|
||||
|
||||
InnerList.Remove (extension);
|
||||
}
|
||||
|
||||
public void Remove (string oid)
|
||||
{
|
||||
if (oid == null)
|
||||
throw new ArgumentNullException ("oid");
|
||||
|
||||
int index = IndexOf (oid);
|
||||
if (index != -1)
|
||||
InnerList.RemoveAt (index);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator ()
|
||||
{
|
||||
return InnerList.GetEnumerator ();
|
||||
}
|
||||
|
||||
public X509Extension this [int index] {
|
||||
get { return (X509Extension) InnerList [index]; }
|
||||
}
|
||||
|
||||
public X509Extension this [string oid] {
|
||||
get {
|
||||
int index = IndexOf (oid);
|
||||
if (index == -1)
|
||||
return null;
|
||||
return (X509Extension) InnerList [index];
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetBytes ()
|
||||
{
|
||||
if (InnerList.Count < 1)
|
||||
return null;
|
||||
ASN1 sequence = new ASN1 (0x30);
|
||||
for (int i=0; i < InnerList.Count; i++) {
|
||||
X509Extension x = (X509Extension) InnerList [i];
|
||||
sequence.Add (x.ASN1);
|
||||
}
|
||||
return sequence.GetBytes ();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,353 @@
|
||||
//
|
||||
// X520.cs: X.520 related stuff (attributes, RDN)
|
||||
//
|
||||
// Author:
|
||||
// Sebastien Pouliot <sebastien@ximian.com>
|
||||
//
|
||||
// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
|
||||
// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
using Mono.Security;
|
||||
|
||||
namespace Mono.Security.X509 {
|
||||
|
||||
// References:
|
||||
// 1. Information technology - Open Systems Interconnection - The Directory: Selected attribute types
|
||||
// http://www.itu.int/rec/recommendation.asp?type=folders&lang=e&parent=T-REC-X.520
|
||||
// 2. Internet X.509 Public Key Infrastructure Certificate and CRL Profile
|
||||
// http://www.ietf.org/rfc/rfc3280.txt
|
||||
// 3. A Summary of the X.500(96) User Schema for use with LDAPv3
|
||||
// http://www.faqs.org/rfcs/rfc2256.html
|
||||
// 4. RFC 2247 - Using Domains in LDAP/X.500 Distinguished Names
|
||||
// http://www.faqs.org/rfcs/rfc2247.html
|
||||
|
||||
/*
|
||||
* AttributeTypeAndValue ::= SEQUENCE {
|
||||
* type AttributeType,
|
||||
* value AttributeValue
|
||||
* }
|
||||
*
|
||||
* AttributeType ::= OBJECT IDENTIFIER
|
||||
*
|
||||
* AttributeValue ::= ANY DEFINED BY AttributeType
|
||||
*/
|
||||
#if INSIDE_CORLIB
|
||||
internal
|
||||
#else
|
||||
public
|
||||
#endif
|
||||
class X520 {
|
||||
|
||||
public abstract class AttributeTypeAndValue {
|
||||
private string oid;
|
||||
private string attrValue;
|
||||
private int upperBound;
|
||||
private byte encoding;
|
||||
|
||||
protected AttributeTypeAndValue (string oid, int upperBound)
|
||||
{
|
||||
this.oid = oid;
|
||||
this.upperBound = upperBound;
|
||||
this.encoding = 0xFF;
|
||||
}
|
||||
|
||||
protected AttributeTypeAndValue (string oid, int upperBound, byte encoding)
|
||||
{
|
||||
this.oid = oid;
|
||||
this.upperBound = upperBound;
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public string Value {
|
||||
get { return attrValue; }
|
||||
set {
|
||||
if ((attrValue != null) && (attrValue.Length > upperBound)) {
|
||||
string msg = ("Value length bigger than upperbound ({0}).");
|
||||
throw new FormatException (String.Format (msg, upperBound));
|
||||
}
|
||||
attrValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ASN1 ASN1 {
|
||||
get { return GetASN1 (); }
|
||||
}
|
||||
|
||||
internal ASN1 GetASN1 (byte encoding)
|
||||
{
|
||||
byte encode = encoding;
|
||||
if (encode == 0xFF)
|
||||
encode = SelectBestEncoding ();
|
||||
|
||||
ASN1 asn1 = new ASN1 (0x30);
|
||||
asn1.Add (ASN1Convert.FromOid (oid));
|
||||
switch (encode) {
|
||||
case 0x13:
|
||||
// PRINTABLESTRING
|
||||
asn1.Add (new ASN1 (0x13, Encoding.ASCII.GetBytes (attrValue)));
|
||||
break;
|
||||
case 0x16:
|
||||
// IA5STRING
|
||||
asn1.Add (new ASN1 (0x16, Encoding.ASCII.GetBytes (attrValue)));
|
||||
break;
|
||||
case 0x1E:
|
||||
// BMPSTRING
|
||||
asn1.Add (new ASN1 (0x1E, Encoding.BigEndianUnicode.GetBytes (attrValue)));
|
||||
break;
|
||||
}
|
||||
return asn1;
|
||||
}
|
||||
|
||||
internal ASN1 GetASN1 ()
|
||||
{
|
||||
return GetASN1 (encoding);
|
||||
}
|
||||
|
||||
public byte[] GetBytes (byte encoding)
|
||||
{
|
||||
return GetASN1 (encoding) .GetBytes ();
|
||||
}
|
||||
|
||||
public byte[] GetBytes ()
|
||||
{
|
||||
return GetASN1 () .GetBytes ();
|
||||
}
|
||||
|
||||
private byte SelectBestEncoding ()
|
||||
{
|
||||
foreach (char c in attrValue) {
|
||||
switch (c) {
|
||||
case '@':
|
||||
case '_':
|
||||
return 0x1E; // BMPSTRING
|
||||
default:
|
||||
if (c > 127)
|
||||
return 0x1E; // BMPSTRING
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0x13; // PRINTABLESTRING
|
||||
}
|
||||
}
|
||||
|
||||
public class Name : AttributeTypeAndValue {
|
||||
|
||||
public Name () : base ("2.5.4.41", 32768)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class CommonName : AttributeTypeAndValue {
|
||||
|
||||
public CommonName () : base ("2.5.4.3", 64)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// RFC2256, Section 5.6
|
||||
public class SerialNumber : AttributeTypeAndValue {
|
||||
|
||||
// max length 64 bytes, Printable String only
|
||||
public SerialNumber ()
|
||||
: base ("2.5.4.5", 64, 0x13)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalityName : AttributeTypeAndValue {
|
||||
|
||||
public LocalityName () : base ("2.5.4.7", 128)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class StateOrProvinceName : AttributeTypeAndValue {
|
||||
|
||||
public StateOrProvinceName () : base ("2.5.4.8", 128)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class OrganizationName : AttributeTypeAndValue {
|
||||
|
||||
public OrganizationName () : base ("2.5.4.10", 64)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class OrganizationalUnitName : AttributeTypeAndValue {
|
||||
|
||||
public OrganizationalUnitName () : base ("2.5.4.11", 64)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Not part of RFC2253
|
||||
public class EmailAddress : AttributeTypeAndValue {
|
||||
|
||||
public EmailAddress () : base ("1.2.840.113549.1.9.1", 128, 0x16)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// RFC2247, Section 4
|
||||
public class DomainComponent : AttributeTypeAndValue {
|
||||
|
||||
// no maximum length defined
|
||||
public DomainComponent ()
|
||||
: base ("0.9.2342.19200300.100.1.25", Int32.MaxValue, 0x16)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// RFC1274, Section 9.3.1
|
||||
public class UserId : AttributeTypeAndValue {
|
||||
|
||||
public UserId ()
|
||||
: base ("0.9.2342.19200300.100.1.1", 256)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class Oid : AttributeTypeAndValue {
|
||||
|
||||
public Oid (string oid)
|
||||
: base (oid, Int32.MaxValue)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/* -- Naming attributes of type X520Title
|
||||
* id-at-title AttributeType ::= { id-at 12 }
|
||||
*
|
||||
* X520Title ::= CHOICE {
|
||||
* teletexString TeletexString (SIZE (1..ub-title)),
|
||||
* printableString PrintableString (SIZE (1..ub-title)),
|
||||
* universalString UniversalString (SIZE (1..ub-title)),
|
||||
* utf8String UTF8String (SIZE (1..ub-title)),
|
||||
* bmpString BMPString (SIZE (1..ub-title))
|
||||
* }
|
||||
*/
|
||||
public class Title : AttributeTypeAndValue {
|
||||
|
||||
public Title () : base ("2.5.4.12", 64)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class CountryName : AttributeTypeAndValue {
|
||||
|
||||
// (0x13) PRINTABLESTRING
|
||||
public CountryName () : base ("2.5.4.6", 2, 0x13)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class DnQualifier : AttributeTypeAndValue {
|
||||
|
||||
// (0x13) PRINTABLESTRING
|
||||
public DnQualifier () : base ("2.5.4.46", 2, 0x13)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class Surname : AttributeTypeAndValue {
|
||||
|
||||
public Surname () : base ("2.5.4.4", 32768)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class GivenName : AttributeTypeAndValue {
|
||||
|
||||
public GivenName () : base ("2.5.4.42", 16)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class Initial : AttributeTypeAndValue {
|
||||
|
||||
public Initial () : base ("2.5.4.43", 5)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* From RFC3280
|
||||
* -- specifications of Upper Bounds MUST be regarded as mandatory
|
||||
* -- from Annex B of ITU-T X.411 Reference Definition of MTS Parameter
|
||||
*
|
||||
* -- Upper Bounds
|
||||
*
|
||||
* ub-name INTEGER ::= 32768
|
||||
* ub-common-name INTEGER ::= 64
|
||||
* ub-locality-name INTEGER ::= 128
|
||||
* ub-state-name INTEGER ::= 128
|
||||
* ub-organization-name INTEGER ::= 64
|
||||
* ub-organizational-unit-name INTEGER ::= 64
|
||||
* ub-title INTEGER ::= 64
|
||||
* ub-serial-number INTEGER ::= 64
|
||||
* ub-match INTEGER ::= 128
|
||||
* ub-emailaddress-length INTEGER ::= 128
|
||||
* ub-common-name-length INTEGER ::= 64
|
||||
* ub-country-name-alpha-length INTEGER ::= 2
|
||||
* ub-country-name-numeric-length INTEGER ::= 3
|
||||
* ub-domain-defined-attributes INTEGER ::= 4
|
||||
* ub-domain-defined-attribute-type-length INTEGER ::= 8
|
||||
* ub-domain-defined-attribute-value-length INTEGER ::= 128
|
||||
* ub-domain-name-length INTEGER ::= 16
|
||||
* ub-extension-attributes INTEGER ::= 256
|
||||
* ub-e163-4-number-length INTEGER ::= 15
|
||||
* ub-e163-4-sub-address-length INTEGER ::= 40
|
||||
* ub-generation-qualifier-length INTEGER ::= 3
|
||||
* ub-given-name-length INTEGER ::= 16
|
||||
* ub-initials-length INTEGER ::= 5
|
||||
* ub-integer-options INTEGER ::= 256
|
||||
* ub-numeric-user-id-length INTEGER ::= 32
|
||||
* ub-organization-name-length INTEGER ::= 64
|
||||
* ub-organizational-unit-name-length INTEGER ::= 32
|
||||
* ub-organizational-units INTEGER ::= 4
|
||||
* ub-pds-name-length INTEGER ::= 16
|
||||
* ub-pds-parameter-length INTEGER ::= 30
|
||||
* ub-pds-physical-address-lines INTEGER ::= 6
|
||||
* ub-postal-code-length INTEGER ::= 16
|
||||
* ub-pseudonym INTEGER ::= 128
|
||||
* ub-surname-length INTEGER ::= 40
|
||||
* ub-terminal-id-length INTEGER ::= 24
|
||||
* ub-unformatted-address-length INTEGER ::= 180
|
||||
* ub-x121-address-length INTEGER ::= 16
|
||||
*
|
||||
* -- Note - upper bounds on string types, such as TeletexString, are
|
||||
* -- measured in characters. Excepting PrintableString or IA5String, a
|
||||
* -- significantly greater number of octets will be required to hold
|
||||
* -- such a value. As a minimum, 16 octets, or twice the specified
|
||||
* -- upper bound, whichever is the larger, should be allowed for
|
||||
* -- TeletexString. For UTF8String or UniversalString at least four
|
||||
* -- times the upper bound should be allowed.
|
||||
*/
|
||||
}
|
Loading…
Reference in new issue