diff --git a/src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj b/src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj index a41e7bcb3..0df2f4f50 100644 --- a/src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj +++ b/src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj @@ -18,7 +18,7 @@ full false bin\Debug\ - TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII + TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII;FEATURE_ECDSA prompt 4 true @@ -29,7 +29,7 @@ none true bin\Release\ - TRACE;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII + TRACE;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII;FEATURE_ECDSA prompt 4 bin\Release\Renci.SshNet.xml @@ -911,6 +911,12 @@ Security\Cryptography\Key.cs + + Security\Cryptography\EcdsaDigitalSignature.cs + + + Security\Cryptography\EcdsaKey.cs + Security\Cryptography\RsaDigitalSignature.cs diff --git a/src/Renci.SshNet.NETCore/Renci.SshNet.NETCore.csproj b/src/Renci.SshNet.NETCore/Renci.SshNet.NETCore.csproj index bf001750e..b288064e6 100644 --- a/src/Renci.SshNet.NETCore/Renci.SshNet.NETCore.csproj +++ b/src/Renci.SshNet.NETCore/Renci.SshNet.NETCore.csproj @@ -27,11 +27,12 @@ + FEATURE_ENCODING_ASCII;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_DIRECTORYINFO_ENUMERATEFILES;FEATURE_MEMORYSTREAM_TRYGETBUFFER;FEATURE_REFLECTION_TYPEINFO;FEATURE_RNG_CREATE;FEATURE_SOCKET_TAP;FEATURE_SOCKET_EAP;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_DNS_TAP;FEATURE_STREAM_TAP;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_TAP;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512 - FEATURE_ENCODING_ASCII;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_DIRECTORYINFO_ENUMERATEFILES;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_MEMORYSTREAM_TRYGETBUFFER;FEATURE_RNG_CREATE;FEATURE_SOCKET_TAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_EAP;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_DNS_SYNC;FEATURE_DNS_APM;FEATURE_DNS_TAP;FEATURE_STREAM_APM;FEATURE_STREAM_TAP;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_TAP;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512 + FEATURE_ENCODING_ASCII;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_DIRECTORYINFO_ENUMERATEFILES;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_MEMORYSTREAM_TRYGETBUFFER;FEATURE_RNG_CREATE;FEATURE_SOCKET_TAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_EAP;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_DNS_SYNC;FEATURE_DNS_APM;FEATURE_DNS_TAP;FEATURE_STREAM_APM;FEATURE_STREAM_TAP;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_TAP;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_ECDSA diff --git a/src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj b/src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj index 058ec2ae5..ec5feb95e 100644 --- a/src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj +++ b/src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj @@ -354,6 +354,9 @@ Classes\Common\ExtensionsTest_ToBigInteger2.cs + + Classes\Common\ExtensionsTest_Pad.cs + Classes\Common\ExtensionsTest_TrimLeadingZeros.cs @@ -1740,6 +1743,26 @@ Data\Key.SSH2.RSA.txt + + + Data\Key.ECDSA.txt + + + Data\Key.ECDSA384.txt + + + Data\Key.ECDSA521.txt + + + Data\Key.ECDSA.Encrypted.txt + + + Data\Key.ECDSA384.Encrypted.txt + + + Data\Key.ECDSA521.Encrypted.txt + + diff --git a/src/Renci.SshNet.Tests/Classes/Common/ExtensionsTest_Pad.cs b/src/Renci.SshNet.Tests/Classes/Common/ExtensionsTest_Pad.cs new file mode 100644 index 000000000..f39c45ded --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Common/ExtensionsTest_Pad.cs @@ -0,0 +1,32 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Renci.SshNet.Common; + +namespace Renci.SshNet.Tests.Classes.Common +{ + [TestClass] + [SuppressMessage("ReSharper", "InvokeAsExtensionMethod")] + public class ExtensionsTest_Pad + { + [TestMethod] + public void ShouldReturnNotPadded() + { + byte[] value = {0x0a, 0x0d}; + byte[] padded = value.Pad(2); + Assert.AreEqual(value, padded); + Assert.AreEqual(value.Length, padded.Length); + } + + [TestMethod] + public void ShouldReturnPadded() + { + byte[] value = { 0x0a, 0x0d }; + byte[] padded = value.Pad(3); + Assert.AreEqual(value.Length + 1, padded.Length); + Assert.AreEqual(0x00, padded[0]); + Assert.AreEqual(0x0a, padded[1]); + Assert.AreEqual(0x0d, padded[2]); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs b/src/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs index 21bd97853..3c944c8d4 100644 --- a/src/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs +++ b/src/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs @@ -319,6 +319,72 @@ public void Test_PrivateKey_RSA_DES_EDE3_CFB() } } + [TestMethod] + [Owner("darinkes")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_ECDSA() + { + using (var stream = GetData("Key.ECDSA.txt")) + { + new PrivateKeyFile(stream); + } + } + + [TestMethod] + [Owner("darinkes")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_ECDSA384() + { + using (var stream = GetData("Key.ECDSA384.txt")) + { + new PrivateKeyFile(stream); + } + } + + [TestMethod] + [Owner("darinkes")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_ECDSA521() + { + using (var stream = GetData("Key.ECDSA521.txt")) + { + new PrivateKeyFile(stream); + } + } + + [TestMethod] + [Owner("darinkes")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_ECDSA_Encrypted() + { + using (var stream = GetData("Key.ECDSA.Encrypted.txt")) + { + new PrivateKeyFile(stream, "12345"); + } + } + + [TestMethod] + [Owner("darinkes")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_ECDSA384_Encrypted() + { + using (var stream = GetData("Key.ECDSA384.Encrypted.txt")) + { + new PrivateKeyFile(stream, "12345"); + } + } + + [TestMethod] + [Owner("darinkes")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_ECDSA521_Encrypted() + { + using (var stream = GetData("Key.ECDSA521.Encrypted.txt")) + { + new PrivateKeyFile(stream, "12345"); + } + } + /// ///A test for Dispose /// diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA.Encrypted.txt b/src/Renci.SshNet.Tests/Data/Key.ECDSA.Encrypted.txt new file mode 100644 index 000000000..f0af5ba7d --- /dev/null +++ b/src/Renci.SshNet.Tests/Data/Key.ECDSA.Encrypted.txt @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,54D46F498C989115AAE14FEA21E3AF11 + +IQdFnndcbzz10d7YQIgEE1TzuzJrm7uYJr4Hvdfz/FshVxMRqxqaqtEgo2vAHHik +BOcPkm+84ERlTNPslcJqLSkKzCdxb7Rz5hfwHuN3Y6Lf01qGakDlzAUEjEyDor+4 +zQtAne+f+gRUJnBvLLoVhH4xdeQFC55GECNUFQpEmos= +-----END EC PRIVATE KEY----- \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA.txt b/src/Renci.SshNet.Tests/Data/Key.ECDSA.txt new file mode 100644 index 000000000..13ac9fb49 --- /dev/null +++ b/src/Renci.SshNet.Tests/Data/Key.ECDSA.txt @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIEdqaFKgJBIibVjyUh1v7Y35LwIQJrocdTaYFLwl7iB0oAoGCCqGSM49 +AwEHoUQDQgAEQD5MO/n9yqSDTszwzVpApLx5SQFecE5ZfDkgxqVdHQecm1BAPozZ +4eKGNhKn72hT79mLlp9HXX+oNEcuVT83Hw== +-----END EC PRIVATE KEY----- \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA384.Encrypted.txt b/src/Renci.SshNet.Tests/Data/Key.ECDSA384.Encrypted.txt new file mode 100644 index 000000000..00072ce24 --- /dev/null +++ b/src/Renci.SshNet.Tests/Data/Key.ECDSA384.Encrypted.txt @@ -0,0 +1,9 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,1D64653C5E18C2AACB0B17E3FE43C219 + +lCtRmcvKSeIACwqTtsf/ei1brtCZ386rsk/j7bSXdkZBpvzcmzbeo6w6CYm206Km +hV9TMl2dIO/I1/ov5/2VIR3ZkaElyDOJD/+Be0e3aus4EZj1H1YM/Dv+4QJId+is +Cw4ycWjfudYPPejGdiyjzt5qjaIJwrrEvGtMg7sWVAqDpjcAjS9KuaCu5nOgdItL +s7oHuz+DTGdJQNfUHAlUnz1JaMRWzpP0MwtxdcaRY+w= +-----END EC PRIVATE KEY----- \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA384.txt b/src/Renci.SshNet.Tests/Data/Key.ECDSA384.txt new file mode 100644 index 000000000..f2d658ea4 --- /dev/null +++ b/src/Renci.SshNet.Tests/Data/Key.ECDSA384.txt @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCQawHdHLR7NvKa2vPV0sVkbzOE8c0enp95iEysGcGV66RXE1EH//nh +gu5UzeTR4KigBwYFK4EEACKhZANiAAQUk4rVvoOPI1hQzWpNx09Uo6qG+srGcbvB +q15eFK0GnK/T0UBKxdbZ2+//KAYI6SeDHM9t3ORF1aX5EpjTEBI4d7ZY/lV9jX6M +nJ4XuGteJselM2iMmy+p9ZYw83BYB1Y= +-----END EC PRIVATE KEY----- \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA521.Encrypted.txt b/src/Renci.SshNet.Tests/Data/Key.ECDSA521.Encrypted.txt new file mode 100644 index 000000000..381b30be8 --- /dev/null +++ b/src/Renci.SshNet.Tests/Data/Key.ECDSA521.Encrypted.txt @@ -0,0 +1,10 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,F995028237EBD79C928530CC6C3E957F + +wT+iajbte4MnpCipVy/7W9t2I8OgwbMjNBw9PB5xmXR1NQX+yWa81DXMTgjHi8++ +6tp+Vlftkr7mY1yvZCVo1Sy4VgcvZeMhtpVKtvYdMCmHJC6gaDOTYX3yee8DJ4FL +fG+IQz0wFyZZ26NFrHiwbufW9z6pXhGNCQZK0KLbFxI9iKwVA0llc7uzTEcmBBpn +0/Snp0CVvX+i6AP9Xj0bBdrFCsvcoT+ZHzS8YWJUfu3m6cpAJksCAy0PXR3ifvus +edTfDpkMxd4/b+DtPB6SMekIAjnQyzbyaTwJCujm8iU= +-----END EC PRIVATE KEY----- \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA521.txt b/src/Renci.SshNet.Tests/Data/Key.ECDSA521.txt new file mode 100644 index 000000000..31abe917a --- /dev/null +++ b/src/Renci.SshNet.Tests/Data/Key.ECDSA521.txt @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIBn2DAme7AU8sCA+/sd6s3c2FNW26IiPvulGd3FC8k5q+fjBZ5LUWR +iJMGrsf2rJLO8hXMGJYoF9tjZEGaabQ8KVagBwYFK4EEACOhgYkDgYYABABrpVjs +ANqcvqMUo1wo0I1uVCXQ6xrauy4iU86FiOwFmkYRrle4w3oYdRJwniC3TwGMuBuM +PMIoCTXr0UtUzn1vkQESNR/J/jAxVseLlVe+KDfZHKvsvk2+O4XaSa1qMfLwN3sp +wlj08+ylKjlO6V3g0hbz4ZaSVwuiRS7Xsv8W2MV6rg== +-----END EC PRIVATE KEY----- \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj index bb99b0c5a..f32beeed3 100644 --- a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj +++ b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj @@ -170,6 +170,7 @@ + @@ -710,6 +711,14 @@ Renci.SshNet + + + + + + + + - \ No newline at end of file + diff --git a/src/Renci.SshNet/Common/DerData.cs b/src/Renci.SshNet/Common/DerData.cs index c84c0b176..35178798a 100644 --- a/src/Renci.SshNet/Common/DerData.cs +++ b/src/Renci.SshNet/Common/DerData.cs @@ -12,7 +12,7 @@ public class DerData private const byte Boolean = 0x01; private const byte Integer = 0x02; - //private const byte BITSTRING = 0x03; + private const byte BITSTRING = 0x03; private const byte Octetstring = 0x04; private const byte Null = 0x05; private const byte Objectidentifier = 0x06; @@ -70,12 +70,20 @@ public DerData() /// Initializes a new instance of the class. /// /// DER encoded data. - public DerData(byte[] data) + /// its a construct + public DerData(byte[] data, bool construct = false) { _data = new List(data); - ReadByte(); // skip dataType - var length = ReadLength(); - _lastIndex = _readerIndex + length; + if (construct) + { + _lastIndex = _readerIndex + data.Length; + } + else + { + ReadByte(); // skip dataType + var length = ReadLength(); + _lastIndex = _readerIndex + length; + } } /// @@ -101,7 +109,7 @@ public BigInteger ReadBigInteger() { var type = ReadByte(); if (type != Integer) - throw new InvalidOperationException("Invalid data type, INTEGER(02) is expected."); + throw new InvalidOperationException(string.Format("Invalid data type, INTEGER(02) is expected, but was {0}", type.ToString("X2"))); var length = ReadLength(); @@ -118,7 +126,7 @@ public int ReadInteger() { var type = ReadByte(); if (type != Integer) - throw new InvalidOperationException("Invalid data type, INTEGER(02) is expected."); + throw new InvalidOperationException(string.Format("Invalid data type, INTEGER(02) is expected, but was {0}", type.ToString("X2"))); var length = ReadLength(); @@ -140,6 +148,51 @@ public int ReadInteger() return result; } + /// + /// Reads next octetstring data type from internal buffer. + /// + /// data read. + public byte[] ReadOctetString() + { + var type = ReadByte(); + if (type != Octetstring) + throw new InvalidOperationException(string.Format("Invalid data type, OCTETSTRING(04) is expected, but was {0}", type.ToString("X2"))); + + var length = ReadLength(); + var data = ReadBytes(length); + return data; + } + + /// + /// Reads next bitstring data type from internal buffer. + /// + /// data read. + public byte[] ReadBitString() + { + var type = ReadByte(); + if (type != BITSTRING) + throw new InvalidOperationException(string.Format("Invalid data type, BITSTRING(03) is expected, but was {0}", type.ToString("X2"))); + + var length = ReadLength(); + var data = ReadBytes(length); + return data; + } + + /// + /// Reads next object data type from internal buffer. + /// + /// data read. + public byte[] ReadObject() + { + var type = ReadByte(); + if (type != Objectidentifier) + throw new InvalidOperationException(string.Format("Invalid data type, OBJECT(06) is expected, but was {0}", type.ToString("X2"))); + + var length = ReadLength(); + var data = ReadBytes(length); + return data; + } + /// /// Writes BOOLEAN data into internal buffer. /// @@ -189,6 +242,18 @@ public void Write(byte[] data) WriteBytes(data); } + /// + /// Writes BITSTRING data into internal buffer. + /// + /// The data. + public void WriteBitstring(byte[] data) + { + _data.Add(BITSTRING); + var length = GetLength(data.Length); + WriteBytes(length); + WriteBytes(data); + } + /// /// Writes OBJECTIDENTIFIER data into internal buffer. /// @@ -229,6 +294,18 @@ public void Write(ObjectIdentifier identifier) WriteBytes(bytes); } + /// + /// Writes OBJECTIDENTIFIER data into internal buffer. + /// + /// The bytes. + public void WriteObjectIdentifier(byte[] bytes) + { + _data.Add(Objectidentifier); + var length = GetLength(bytes.Length); + WriteBytes(length); + WriteBytes(bytes); + } + /// /// Writes NULL data into internal buffer. /// @@ -268,10 +345,13 @@ private static IEnumerable GetLength(int length) return data; } - return new[] {(byte) length}; + return new[] { (byte)length }; } - - private int ReadLength() + /// + /// Gets Data Length + /// + /// length + public int ReadLength() { int length = ReadByte(); @@ -306,12 +386,19 @@ private int ReadLength() return length; } - private void WriteBytes(IEnumerable data) + /// + /// Write Byte data into internal buffer. + /// + public void WriteBytes(IEnumerable data) { _data.AddRange(data); } - private byte ReadByte() + /// + /// Reads Byte data into internal buffer. + /// + /// data read + public byte ReadByte() { if (_readerIndex > _data.Count) throw new InvalidOperationException("Read out of boundaries."); @@ -319,7 +406,12 @@ private byte ReadByte() return _data[_readerIndex++]; } - private byte[] ReadBytes(int length) + /// + /// Reads lengths Bytes data into internal buffer. + /// + /// data read + /// amount of data to read. + public byte[] ReadBytes(int length) { if (_readerIndex + length > _data.Count) throw new InvalidOperationException("Read out of boundaries."); @@ -330,4 +422,4 @@ private byte[] ReadBytes(int length) return result; } } -} +} \ No newline at end of file diff --git a/src/Renci.SshNet/Common/Extensions.cs b/src/Renci.SshNet/Common/Extensions.cs index 784139ab3..b2c2e7d75 100644 --- a/src/Renci.SshNet/Common/Extensions.cs +++ b/src/Renci.SshNet/Common/Extensions.cs @@ -261,6 +261,20 @@ public static byte[] TrimLeadingZeros(this byte[] value) return value; } + /// + /// Pads with leading zeros if needd + /// + /// The data. + /// The length to pad to. + public static byte[] Pad(this byte[] data, int length) + { + if (length <= data.Length) + return data; + var newData = new byte[length]; + Buffer.BlockCopy(data, 0, newData, newData.Length - data.Length, data.Length); + return newData; + } + public static byte[] Concat(this byte[] first, byte[] second) { if (first == null || first.Length == 0) diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs index a8317909a..26ca884df 100644 --- a/src/Renci.SshNet/ConnectionInfo.cs +++ b/src/Renci.SshNet/ConnectionInfo.cs @@ -378,9 +378,13 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy HostKeyAlgorithms = new Dictionary> { +#if FEATURE_ECDSA + {"ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(), data)}, + {"ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(), data)}, + {"ecdsa-sha2-nistp521", data => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(), data)}, +#endif {"ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(), data)}, {"ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(), data)}, - //{"ecdsa-sha2-nistp256 "} //{"x509v3-sign-rsa", () => { ... }, //{"x509v3-sign-dss", () => { ... }, //{"spki-sign-rsa", () => { ... }, diff --git a/src/Renci.SshNet/PrivateKeyFile.cs b/src/Renci.SshNet/PrivateKeyFile.cs index f44672347..83834d80b 100644 --- a/src/Renci.SshNet/PrivateKeyFile.cs +++ b/src/Renci.SshNet/PrivateKeyFile.cs @@ -22,7 +22,18 @@ namespace Renci.SshNet /// /// /// - /// Supports RSA and DSA private key in both OpenSSH and ssh.com format. + /// The following private keys are supported: + /// + /// + /// RSA in OpenSSH and ssh.com format + /// + /// + /// DSA in OpenSSH and ssh.com format + /// + /// + /// ECDSA 256/384/521 in OpenSSH format + /// + /// /// /// /// The following encryption algorithms are supported: @@ -197,6 +208,12 @@ private void Open(Stream privateKey, string passPhrase) _key = new DsaKey(decryptedData); HostKey = new KeyHostAlgorithm("ssh-dss", _key); break; +#if FEATURE_ECDSA + case "EC": + _key = new EcdsaKey(decryptedData); + HostKey = new KeyHostAlgorithm(_key.ToString(), _key); + break; +#endif case "SSH2 ENCRYPTED": var reader = new SshDataReader(decryptedData); var magicNumber = reader.ReadUInt32(); diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index 625080160..25d882f60 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -18,7 +18,7 @@ full false bin\Debug\ - TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII + TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII;FEATURE_ECDSA prompt 4 bin\Debug\Renci.SshNet.xml @@ -29,7 +29,7 @@ none true bin\Release\ - FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII + FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII;FEATURE_ECDSA prompt 4 bin\Release\Renci.SshNet.xml @@ -301,6 +301,8 @@ + + diff --git a/src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs new file mode 100644 index 000000000..38d60966e --- /dev/null +++ b/src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs @@ -0,0 +1,189 @@ +#if FEATURE_ECDSA +using System; +using Renci.SshNet.Common; +using System.Globalization; +using System.Security.Cryptography; + +namespace Renci.SshNet.Security.Cryptography +{ + /// + /// Implements ECDSA digital signature algorithm. + /// + public class EcdsaDigitalSignature : DigitalSignature, IDisposable + { + private readonly EcdsaKey _key; + + /// + /// Initializes a new instance of the class. + /// + /// The ECDSA key. + /// is null. + public EcdsaDigitalSignature(EcdsaKey key) + { + if (key == null) + throw new ArgumentNullException("key"); + + _key = key; + } + + /// + /// Verifies the signature. + /// + /// The input. + /// The signature. + /// + /// true if signature was successfully verified; otherwise false. + /// + public override bool Verify(byte[] input, byte[] signature) + { + // for 521 sig_size is 132 + var sig_size = _key.KeyLength == 521 ? 132 : _key.KeyLength / 4; + var ssh_data = new SshDataSignature(signature, sig_size); +#if NETSTANDARD2_0 + return _key.Ecdsa.VerifyData(input, ssh_data.Signature, _key.HashAlgorithm); +#else + var ecdsa = (ECDsaCng)_key.Ecdsa; + ecdsa.HashAlgorithm = _key.HashAlgorithm; + return ecdsa.VerifyData(input, ssh_data.Signature); +#endif + } + + /// + /// Creates the signature. + /// + /// The input. + /// + /// Signed input data. + /// + public override byte[] Sign(byte[] input) + { +#if NETSTANDARD2_0 + var signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm); +#else + var ecdsa = (ECDsaCng)_key.Ecdsa; + ecdsa.HashAlgorithm = _key.HashAlgorithm; + var signed = ecdsa.SignData(input); +#endif + var ssh_data = new SshDataSignature(signed.Length); + ssh_data.Signature = signed; + return ssh_data.GetBytes(); + } + + #region IDisposable Members + + private bool _isDisposed; + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_isDisposed) + return; + + if (disposing) + { + _isDisposed = true; + } + } + + /// + /// Releases unmanaged resources and performs other cleanup operations before the + /// is reclaimed by garbage collection. + /// + ~EcdsaDigitalSignature() + { + Dispose(false); + } + + #endregion + } + + class SshDataSignature : SshData + { + private int _signature_size; + + private byte[] _signature_r; + private byte[] _signature_s; + + public byte[] Signature + { + get + { + var signature = new byte[_signature_size]; + Buffer.BlockCopy(_signature_r, 0, signature, 0, _signature_r.Length); + Buffer.BlockCopy(_signature_s, 0, signature, _signature_r.Length, _signature_s.Length); + return signature; + } + set + { + var signed_r = new byte[_signature_size / 2]; + Buffer.BlockCopy(value, 0, signed_r, 0, signed_r.Length); + _signature_r = signed_r.ToBigInteger2().ToByteArray().Reverse(); + + var signed_s = new byte[_signature_size / 2]; + Buffer.BlockCopy(value, signed_r.Length, signed_s, 0, signed_s.Length); + _signature_s = signed_s.ToBigInteger2().ToByteArray().Reverse(); + } + } + + public SshDataSignature(int sig_size) + { + _signature_size = sig_size; + } + + public SshDataSignature(byte[] data, int sig_size) + { + _signature_size = sig_size; + Load(data); + } + + protected override void LoadData() + { + _signature_r = ReadBinary().TrimLeadingZeros().Pad(_signature_size / 2); + _signature_s = ReadBinary().TrimLeadingZeros().Pad(_signature_size / 2); + } + + protected override void SaveData() + { + WriteBinaryString(_signature_r.ToBigInteger2().ToByteArray().Reverse()); + WriteBinaryString(_signature_s.ToBigInteger2().ToByteArray().Reverse()); + } + + public new byte[] ReadBinary() + { + var length = ReadUInt32(); + + if (length > int.MaxValue) + { + throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue)); + } + + return ReadBytes((int)length); + } + + protected override int BufferCapacity + { + get + { + var capacity = base.BufferCapacity; + capacity += 4; // r length + capacity += _signature_r.Length; // signature r + capacity += 4; // s length + capacity += _signature_s.Length; // signature s + return capacity; + } + } + } +} +#endif \ No newline at end of file diff --git a/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs b/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs new file mode 100644 index 000000000..58861f020 --- /dev/null +++ b/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs @@ -0,0 +1,459 @@ +#if FEATURE_ECDSA +using System; +using System.IO; +using System.Text; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Renci.SshNet.Common; +using Renci.SshNet.Security.Cryptography; + +namespace Renci.SshNet.Security +{ + /// + /// Contains ECDSA (ecdsa-sha2-nistp{256,384,521}) private and public key + /// + public class EcdsaKey : Key, IDisposable + { + internal const string ECDSA_P256_OID_VALUE = "1.2.840.10045.3.1.7"; // Also called nistP256 or secP256r1 + internal const string ECDSA_P384_OID_VALUE = "1.3.132.0.34"; // Also called nistP384 or secP384r1 + internal const string ECDSA_P521_OID_VALUE = "1.3.132.0.35"; // Also called nistP521or secP521r1 + +#if !NETSTANDARD2_0 + internal enum KeyBlobMagicNumber : int + { + BCRYPT_ECDSA_PUBLIC_P256_MAGIC = 0x31534345, + BCRYPT_ECDSA_PRIVATE_P256_MAGIC = 0x32534345, + BCRYPT_ECDSA_PUBLIC_P384_MAGIC = 0x33534345, + BCRYPT_ECDSA_PRIVATE_P384_MAGIC = 0x34534345, + BCRYPT_ECDSA_PUBLIC_P521_MAGIC = 0x35534345, + BCRYPT_ECDSA_PRIVATE_P521_MAGIC = 0x36534345, + + BCRYPT_ECDH_PUBLIC_P256_MAGIC = 0x314B4345, + BCRYPT_ECDH_PRIVATE_P256_MAGIC = 0x324B4345, + BCRYPT_ECDH_PUBLIC_P384_MAGIC = 0x334B4345, + BCRYPT_ECDH_PRIVATE_P384_MAGIC = 0x344B4345, + BCRYPT_ECDH_PUBLIC_P521_MAGIC = 0x354B4345, + BCRYPT_ECDH_PRIVATE_P521_MAGIC = 0x364B4345, + + BCRYPT_ECDH_PUBLIC_GENERIC_MAGIC = 0x504B4345, + BCRYPT_ECDH_PRIVATE_GENERIC_MAGIC = 0x564B4345, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BCRYPT_ECCKEY_BLOB + { + internal KeyBlobMagicNumber Magic; + internal int cbKey; + } + + private CngKey key; +#endif + + /// + /// Gets the SSH name of the ECDSA Key + /// + public override string ToString() + { + return string.Format("ecdsa-sha2-nistp{0}", KeyLength); + } + +#if NETSTANDARD2_0 + /// + /// Gets the HashAlgorithm to use + /// + public HashAlgorithmName HashAlgorithm + { + get + { + switch (KeyLength) + { + case 256: + return HashAlgorithmName.SHA256; + case 384: + return HashAlgorithmName.SHA384; + case 521: + return HashAlgorithmName.SHA512; + } + return HashAlgorithmName.SHA256; + } + } +#else + /// + /// Gets the HashAlgorithm to use + /// + public CngAlgorithm HashAlgorithm + { + get + { + switch (Ecdsa.KeySize) + { + case 256: + return CngAlgorithm.Sha256; + case 384: + return CngAlgorithm.Sha384; + case 521: + return CngAlgorithm.Sha512; + default: + throw new SshException("Unknown KeySize: " + Ecdsa.KeySize); + } + } + } +#endif + + /// + /// Gets the length of the key. + /// + /// + /// The length of the key. + /// + public override int KeyLength + { + get + { + return Ecdsa.KeySize; + } + } + + private EcdsaDigitalSignature _digitalSignature; + + /// + /// Gets the digital signature. + /// + protected override DigitalSignature DigitalSignature + { + get + { + if (_digitalSignature == null) + { + _digitalSignature = new EcdsaDigitalSignature(this); + } + return _digitalSignature; + } + } + + /// + /// Gets or sets the public. + /// + /// + /// The public. + /// + public override BigInteger[] Public + { + get + { + byte[] curve; + byte[] qx; + byte[] qy; +#if NETSTANDARD2_0 + var parameter = Ecdsa.ExportParameters(false); + qx = parameter.Q.X; + qy = parameter.Q.Y; + switch (parameter.Curve.Oid.FriendlyName) + { + case "ECDSA_P256": + case "nistP256": + curve = Encoding.ASCII.GetBytes("nistp256"); + break; + case "ECDSA_P384": + case "nistP384": + curve = Encoding.ASCII.GetBytes("nistp384"); + break; + case "ECDSA_P521": + case "nistP521": + curve = Encoding.ASCII.GetBytes("nistp521"); + break; + default: + throw new SshException("Unexpected Curve Name: " + parameter.Curve.Oid.FriendlyName); + } +#else + var blob = key.Export(CngKeyBlobFormat.EccPublicBlob); + + KeyBlobMagicNumber magic; + using (var br = new BinaryReader(new MemoryStream(blob))) + { + magic = (KeyBlobMagicNumber)br.ReadInt32(); + int cbKey = br.ReadInt32(); + qx = br.ReadBytes(cbKey); + qy = br.ReadBytes(cbKey); + } + + switch (magic) + { + case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P256_MAGIC: + curve = Encoding.ASCII.GetBytes("nistp256"); + break; + case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P384_MAGIC: + curve = Encoding.ASCII.GetBytes("nistp384"); + break; + case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P521_MAGIC: + curve = Encoding.ASCII.GetBytes("nistp521"); + break; + default: + throw new SshException("Unexpected Curve Magic: " + magic); + } +#endif + // Make ECPoint from x and y + // Prepend 04 (uncompressed format) + qx-bytes + qy-bytes + var q = new byte[1 + qx.Length + qy.Length]; + Buffer.SetByte(q, 0, 4); + Buffer.BlockCopy(qx, 0, q, 1, qx.Length); + Buffer.BlockCopy(qy, 0, q, qx.Length + 1, qy.Length); + + // returns Curve-Name and x/y as ECPoint + return new[] { new BigInteger(curve.Reverse()), new BigInteger(q.Reverse()) }; + } + set + { + var curve_s = Encoding.ASCII.GetString(value[0].ToByteArray().Reverse()); + string curve_oid = GetCurveOid(curve_s); + + var publickey = value[1].ToByteArray().Reverse(); + Import(curve_oid, publickey, null); + } + } + + /// + /// Gets ECDsa Object + /// + public ECDsa Ecdsa { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + public EcdsaKey() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The curve name + /// Value of publickey + /// Value of privatekey + public EcdsaKey(string curve, byte[] publickey, byte[] privatekey) + { + Import(GetCurveOid(curve), publickey, privatekey); + } + + /// + /// Initializes a new instance of the class. + /// + /// DER encoded private key data. + public EcdsaKey(byte[] data) + { + var der = new DerData(data); + var version = der.ReadBigInteger(); // skip version + + // PrivateKey + var privatekey = der.ReadOctetString().TrimLeadingZeros(); + + // Construct + var s0 = der.ReadByte(); + if ((s0 & 0xe0) != 0xa0) + throw new SshException(string.Format("UnexpectedDER: wanted constructed tag (0xa0-0xbf), got: {0:X}", s0)); + var tag = s0 & 0x1f; + if (tag != 0) + throw new SshException(string.Format("expected tag 0 in DER privkey, got: {0}", tag)); + var construct = der.ReadBytes(der.ReadLength()); // object length + + // curve OID + var curve_der = new DerData(construct, true); + var curve = curve_der.ReadObject(); + + // Construct + s0 = der.ReadByte(); + if ((s0 & 0xe0) != 0xa0) + throw new SshException(string.Format("UnexpectedDER: wanted constructed tag (0xa0-0xbf), got: {0:X}", s0)); + tag = s0 & 0x1f; + if (tag != 1) + throw new SshException(string.Format("expected tag 1 in DER privkey, got: {0}", tag)); + construct = der.ReadBytes(der.ReadLength()); // object length + + // PublicKey + var pubkey_der = new DerData(construct, true); + var pubkey = pubkey_der.ReadBitString().TrimLeadingZeros(); + + Import(OidByteArrayToString(curve), pubkey, privatekey); + } + + private void Import(string curve_oid, byte[] publickey, byte[] privatekey) + { +#if NETSTANDARD2_0 + var curve = ECCurve.CreateFromValue(curve_oid); + var parameter = new ECParameters + { + Curve = curve + }; + + // ECPoint as BigInteger(2) + var cord_size = (publickey.Length - 1) / 2; + var qx = new byte[cord_size]; + Buffer.BlockCopy(publickey, 1, qx, 0, qx.Length); + + var qy = new byte[cord_size]; + Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length); + + parameter.Q.X = qx; + parameter.Q.Y = qy; + + if (privatekey != null) + parameter.D = privatekey.TrimLeadingZeros().Pad(cord_size); + + Ecdsa = ECDsa.Create(parameter); +#else + var curve_magic = KeyBlobMagicNumber.BCRYPT_ECDH_PRIVATE_GENERIC_MAGIC; + switch (GetCurveName(curve_oid)) + { + case "nistp256": + if (privatekey != null) + curve_magic = KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P256_MAGIC; + else + curve_magic = KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P256_MAGIC; + break; + case "nistp384": + if (privatekey != null) + curve_magic = KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P384_MAGIC; + else + curve_magic = KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P384_MAGIC; + break; + case "nistp521": + if (privatekey != null) + curve_magic = KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P521_MAGIC; + else + curve_magic = KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P521_MAGIC; + break; + default: + throw new SshException("Unknown: " + curve_oid); + } + + // ECPoint as BigInteger(2) + var cord_size = (publickey.Length - 1) / 2; + var qx = new byte[cord_size]; + Buffer.BlockCopy(publickey, 1, qx, 0, qx.Length); + + var qy = new byte[cord_size]; + Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length); + + if (privatekey != null) + privatekey = privatekey.Pad(cord_size); + + int headerSize = Marshal.SizeOf(typeof(BCRYPT_ECCKEY_BLOB)); + int blobSize = headerSize + qx.Length + qy.Length; + if (privatekey != null) + blobSize += privatekey.Length; + + byte[] blob = new byte[blobSize]; + using (var bw = new BinaryWriter(new MemoryStream(blob))) + { + bw.Write((int)curve_magic); + bw.Write(cord_size); + bw.Write(qx); // q.x + bw.Write(qy); // q.y + if (privatekey != null) + bw.Write(privatekey); // d + } + key = CngKey.Import(blob, privatekey == null ? CngKeyBlobFormat.EccPublicBlob : CngKeyBlobFormat.EccPrivateBlob); + + Ecdsa = new ECDsaCng(key); +#endif + } + + private string GetCurveOid(string curve_s) + { + switch (curve_s.ToLower()) + { + case "nistp256": + return ECDSA_P256_OID_VALUE; + case "nistp384": + return ECDSA_P384_OID_VALUE; + case "nistp521": + return ECDSA_P521_OID_VALUE; + default: + throw new SshException("Unexpected Curve Name: " + curve_s); + } + } + + private string GetCurveName(string oid) + { + switch (oid) + { + case ECDSA_P256_OID_VALUE: + return "nistp256"; + case ECDSA_P384_OID_VALUE: + return "nistp384"; + case ECDSA_P521_OID_VALUE: + return "nistp521"; + default: + throw new SshException("Unexpected OID: " + oid); + } + } + + private string OidByteArrayToString(byte[] oid) + { + StringBuilder retVal = new StringBuilder(); + + for (int i = 0; i < oid.Length; i++) + { + if (i == 0) + { + int b = oid[0] % 40; + int a = (oid[0] - b) / 40; + retVal.AppendFormat("{0}.{1}", a, b); + } + else + { + if (oid[i] < 128) + retVal.AppendFormat(".{0}", oid[i]); + else + { + retVal.AppendFormat(".{0}", + ((oid[i] - 128) * 128) + oid[i + 1]); + i++; + } + } + } + + return retVal.ToString(); + } + + #region IDisposable Members + + private bool _isDisposed; + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_isDisposed) + return; + + if (disposing) + { + _isDisposed = true; + } + } + + /// + /// Releases unmanaged resources and performs other cleanup operations before the + /// is reclaimed by garbage collection. + /// + ~EcdsaKey() + { + Dispose(false); + } + + #endregion + } +} +#endif \ No newline at end of file