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