mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 09:15:47 +00:00
Add Support for ECDSA Host- and Private-Keys
Also known as: - ecdsa-sha2-nistp256 - ecdsa-sha2-nistp384 - ecdsa-sha2-nistp521 Basically it translate between SSH-Data and Microsoft Crypto API.
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>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</DefineConstants>
|
||||
<DefineConstants>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</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
@@ -29,7 +29,7 @@
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>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</DefineConstants>
|
||||
<DefineConstants>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</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Release\Renci.SshNet.xml</DocumentationFile>
|
||||
@@ -911,6 +911,12 @@
|
||||
<Compile Include="..\Renci.SshNet\Security\Cryptography\Key.cs">
|
||||
<Link>Security\Cryptography\Key.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Renci.SshNet\Security\Cryptography\EcdsaDigitalSignature.cs">
|
||||
<Link>Security\Cryptography\EcdsaDigitalSignature.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Renci.SshNet\Security\Cryptography\EcdsaKey.cs">
|
||||
<Link>Security\Cryptography\EcdsaKey.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Renci.SshNet\Security\Cryptography\RsaDigitalSignature.cs">
|
||||
<Link>Security\Cryptography\RsaDigitalSignature.cs</Link>
|
||||
</Compile>
|
||||
|
||||
@@ -27,11 +27,12 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<PackageReference Include="SshNet.Security.Cryptography" Version="[1.3.0]" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="4.4.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
|
||||
<DefineConstants>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</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<DefineConstants>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</DefineConstants>
|
||||
<DefineConstants>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</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -354,6 +354,9 @@
|
||||
<Compile Include="..\Renci.SshNet.Tests\Classes\Common\ExtensionsTest_ToBigInteger2.cs">
|
||||
<Link>Classes\Common\ExtensionsTest_ToBigInteger2.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Renci.SshNet.Tests\Classes\Common\ExtensionsTest_Pad.cs">
|
||||
<Link>Classes\Common\ExtensionsTest_Pad.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Renci.SshNet.Tests\Classes\Common\ExtensionsTest_TrimLeadingZeros.cs">
|
||||
<Link>Classes\Common\ExtensionsTest_TrimLeadingZeros.cs</Link>
|
||||
</Compile>
|
||||
@@ -1740,6 +1743,26 @@
|
||||
<Link>Data\Key.SSH2.RSA.txt</Link>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.ECDSA.txt">
|
||||
<Link>Data\Key.ECDSA.txt</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.ECDSA384.txt">
|
||||
<Link>Data\Key.ECDSA384.txt</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.ECDSA521.txt">
|
||||
<Link>Data\Key.ECDSA521.txt</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.ECDSA.Encrypted.txt">
|
||||
<Link>Data\Key.ECDSA.Encrypted.txt</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.ECDSA384.Encrypted.txt">
|
||||
<Link>Data\Key.ECDSA384.Encrypted.txt</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.ECDSA521.Encrypted.txt">
|
||||
<Link>Data\Key.ECDSA521.Encrypted.txt</Link>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,6 +319,72 @@ namespace Renci.SshNet.Tests.Classes
|
||||
}
|
||||
}
|
||||
|
||||
[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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///A test for Dispose
|
||||
///</summary>
|
||||
|
||||
@@ -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-----
|
||||
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIEdqaFKgJBIibVjyUh1v7Y35LwIQJrocdTaYFLwl7iB0oAoGCCqGSM49
|
||||
AwEHoUQDQgAEQD5MO/n9yqSDTszwzVpApLx5SQFecE5ZfDkgxqVdHQecm1BAPozZ
|
||||
4eKGNhKn72hT79mLlp9HXX+oNEcuVT83Hw==
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -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-----
|
||||
@@ -0,0 +1,6 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDCQawHdHLR7NvKa2vPV0sVkbzOE8c0enp95iEysGcGV66RXE1EH//nh
|
||||
gu5UzeTR4KigBwYFK4EEACKhZANiAAQUk4rVvoOPI1hQzWpNx09Uo6qG+srGcbvB
|
||||
q15eFK0GnK/T0UBKxdbZ2+//KAYI6SeDHM9t3ORF1aX5EpjTEBI4d7ZY/lV9jX6M
|
||||
nJ4XuGteJselM2iMmy+p9ZYw83BYB1Y=
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -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-----
|
||||
@@ -0,0 +1,7 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIHcAgEBBEIBn2DAme7AU8sCA+/sd6s3c2FNW26IiPvulGd3FC8k5q+fjBZ5LUWR
|
||||
iJMGrsf2rJLO8hXMGJYoF9tjZEGaabQ8KVagBwYFK4EEACOhgYkDgYYABABrpVjs
|
||||
ANqcvqMUo1wo0I1uVCXQ6xrauy4iU86FiOwFmkYRrle4w3oYdRJwniC3TwGMuBuM
|
||||
PMIoCTXr0UtUzn1vkQESNR/J/jAxVseLlVe+KDfZHKvsvk2+O4XaSa1qMfLwN3sp
|
||||
wlj08+ylKjlO6V3g0hbz4ZaSVwuiRS7Xsv8W2MV6rg==
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -170,6 +170,7 @@
|
||||
<Compile Include="Classes\Common\ExtensionsTest_Take_Count.cs" />
|
||||
<Compile Include="Classes\Common\ExtensionsTest_Take_OffsetAndCount.cs" />
|
||||
<Compile Include="Classes\Common\ExtensionsTest_ToBigInteger2.cs" />
|
||||
<Compile Include="Classes\Common\ExtensionsTest_Pad.cs" />
|
||||
<Compile Include="Classes\Common\ExtensionsTest_TrimLeadingZeros.cs" />
|
||||
<Compile Include="Classes\Common\PackTest.cs" />
|
||||
<Compile Include="Classes\Common\PosixPathTest_GetFileName.cs" />
|
||||
@@ -710,6 +711,14 @@
|
||||
<Name>Renci.SshNet</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Data\Key.ECDSA.txt" />
|
||||
<EmbeddedResource Include="Data\Key.ECDSA384.txt" />
|
||||
<EmbeddedResource Include="Data\Key.ECDSA521.txt" />
|
||||
<EmbeddedResource Include="Data\Key.ECDSA.Encrypted.txt" />
|
||||
<EmbeddedResource Include="Data\Key.ECDSA384.Encrypted.txt" />
|
||||
<EmbeddedResource Include="Data\Key.ECDSA521.Encrypted.txt" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
@@ -718,4 +727,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Renci.SshNet.Common
|
||||
|
||||
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 @@ namespace Renci.SshNet.Common
|
||||
/// Initializes a new instance of the <see cref="DerData"/> class.
|
||||
/// </summary>
|
||||
/// <param name="data">DER encoded data.</param>
|
||||
public DerData(byte[] data)
|
||||
/// <param name="construct">its a construct</param>
|
||||
public DerData(byte[] data, bool construct = false)
|
||||
{
|
||||
_data = new List<byte>(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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -101,7 +109,7 @@ namespace Renci.SshNet.Common
|
||||
{
|
||||
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 @@ namespace Renci.SshNet.Common
|
||||
{
|
||||
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 @@ namespace Renci.SshNet.Common
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads next octetstring data type from internal buffer.
|
||||
/// </summary>
|
||||
/// <returns>data read.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads next bitstring data type from internal buffer.
|
||||
/// </summary>
|
||||
/// <returns>data read.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads next object data type from internal buffer.
|
||||
/// </summary>
|
||||
/// <returns>data read.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes BOOLEAN data into internal buffer.
|
||||
/// </summary>
|
||||
@@ -189,6 +242,18 @@ namespace Renci.SshNet.Common
|
||||
WriteBytes(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes BITSTRING data into internal buffer.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
public void WriteBitstring(byte[] data)
|
||||
{
|
||||
_data.Add(BITSTRING);
|
||||
var length = GetLength(data.Length);
|
||||
WriteBytes(length);
|
||||
WriteBytes(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes OBJECTIDENTIFIER data into internal buffer.
|
||||
/// </summary>
|
||||
@@ -229,6 +294,18 @@ namespace Renci.SshNet.Common
|
||||
WriteBytes(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes OBJECTIDENTIFIER data into internal buffer.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes.</param>
|
||||
public void WriteObjectIdentifier(byte[] bytes)
|
||||
{
|
||||
_data.Add(Objectidentifier);
|
||||
var length = GetLength(bytes.Length);
|
||||
WriteBytes(length);
|
||||
WriteBytes(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes NULL data into internal buffer.
|
||||
/// </summary>
|
||||
@@ -268,10 +345,13 @@ namespace Renci.SshNet.Common
|
||||
|
||||
return data;
|
||||
}
|
||||
return new[] {(byte) length};
|
||||
return new[] { (byte)length };
|
||||
}
|
||||
|
||||
private int ReadLength()
|
||||
/// <summary>
|
||||
/// Gets Data Length
|
||||
/// </summary>
|
||||
/// <returns>length</returns>
|
||||
public int ReadLength()
|
||||
{
|
||||
int length = ReadByte();
|
||||
|
||||
@@ -306,12 +386,19 @@ namespace Renci.SshNet.Common
|
||||
return length;
|
||||
}
|
||||
|
||||
private void WriteBytes(IEnumerable<byte> data)
|
||||
/// <summary>
|
||||
/// Write Byte data into internal buffer.
|
||||
/// </summary>
|
||||
public void WriteBytes(IEnumerable<byte> data)
|
||||
{
|
||||
_data.AddRange(data);
|
||||
}
|
||||
|
||||
private byte ReadByte()
|
||||
/// <summary>
|
||||
/// Reads Byte data into internal buffer.
|
||||
/// </summary>
|
||||
/// <returns>data read</returns>
|
||||
public byte ReadByte()
|
||||
{
|
||||
if (_readerIndex > _data.Count)
|
||||
throw new InvalidOperationException("Read out of boundaries.");
|
||||
@@ -319,7 +406,12 @@ namespace Renci.SshNet.Common
|
||||
return _data[_readerIndex++];
|
||||
}
|
||||
|
||||
private byte[] ReadBytes(int length)
|
||||
/// <summary>
|
||||
/// Reads lengths Bytes data into internal buffer.
|
||||
/// </summary>
|
||||
/// <returns>data read</returns>
|
||||
/// <param name="length">amount of data to read.</param>
|
||||
public byte[] ReadBytes(int length)
|
||||
{
|
||||
if (_readerIndex + length > _data.Count)
|
||||
throw new InvalidOperationException("Read out of boundaries.");
|
||||
@@ -330,4 +422,4 @@ namespace Renci.SshNet.Common
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,6 +261,20 @@ namespace Renci.SshNet.Common
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pads with leading zeros if needd
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="length">The length to pad to.</param>
|
||||
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)
|
||||
|
||||
@@ -378,9 +378,13 @@ namespace Renci.SshNet
|
||||
|
||||
HostKeyAlgorithms = new Dictionary<string, Func<byte[], KeyHostAlgorithm>>
|
||||
{
|
||||
#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", () => { ... },
|
||||
|
||||
@@ -22,7 +22,18 @@ namespace Renci.SshNet
|
||||
/// </example>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Supports RSA and DSA private key in both <c>OpenSSH</c> and <c>ssh.com</c> format.
|
||||
/// The following private keys are supported:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>RSA in OpenSSH and ssh.com format</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>DSA in OpenSSH and ssh.com format</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>ECDSA 256/384/521 in OpenSSH format</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The following encryption algorithms are supported:
|
||||
@@ -197,6 +208,12 @@ namespace Renci.SshNet
|
||||
_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();
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>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</DefineConstants>
|
||||
<DefineConstants>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</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Debug\Renci.SshNet.xml</DocumentationFile>
|
||||
@@ -29,7 +29,7 @@
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>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</DefineConstants>
|
||||
<DefineConstants>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</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Release\Renci.SshNet.xml</DocumentationFile>
|
||||
@@ -301,6 +301,8 @@
|
||||
<Compile Include="Security\Chaos.NaCl\Internal\Sha512Internal.cs" />
|
||||
<Compile Include="Security\Chaos.NaCl\MontgomeryCurve25519.cs" />
|
||||
<Compile Include="Security\Chaos.NaCl\Sha512.cs" />
|
||||
<Compile Include="Security\Cryptography\EcdsaDigitalSignature.cs" />
|
||||
<Compile Include="Security\Cryptography\EcdsaKey.cs" />
|
||||
<Compile Include="Security\Cryptography\HMACMD5.cs" />
|
||||
<Compile Include="Security\Cryptography\HMACSHA1.cs" />
|
||||
<Compile Include="Security\Cryptography\HMACSHA256.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
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements ECDSA digital signature algorithm.
|
||||
/// </summary>
|
||||
public class EcdsaDigitalSignature : DigitalSignature, IDisposable
|
||||
{
|
||||
private readonly EcdsaKey _key;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EcdsaDigitalSignature" /> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The ECDSA key.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <c>null</c>.</exception>
|
||||
public EcdsaDigitalSignature(EcdsaKey key)
|
||||
{
|
||||
if (key == null)
|
||||
throw new ArgumentNullException("key");
|
||||
|
||||
_key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the signature.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="signature">The signature.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if signature was successfully verified; otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the signature.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <returns>
|
||||
/// Signed input data.
|
||||
/// </returns>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged resources and performs other cleanup operations before the
|
||||
/// <see cref="EcdsaDigitalSignature"/> is reclaimed by garbage collection.
|
||||
/// </summary>
|
||||
~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
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains ECDSA (ecdsa-sha2-nistp{256,384,521}) private and public key
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SSH name of the ECDSA Key
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("ecdsa-sha2-nistp{0}", KeyLength);
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
/// <summary>
|
||||
/// Gets the HashAlgorithm to use
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>
|
||||
/// Gets the HashAlgorithm to use
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the key.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The length of the key.
|
||||
/// </value>
|
||||
public override int KeyLength
|
||||
{
|
||||
get
|
||||
{
|
||||
return Ecdsa.KeySize;
|
||||
}
|
||||
}
|
||||
|
||||
private EcdsaDigitalSignature _digitalSignature;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the digital signature.
|
||||
/// </summary>
|
||||
protected override DigitalSignature DigitalSignature
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_digitalSignature == null)
|
||||
{
|
||||
_digitalSignature = new EcdsaDigitalSignature(this);
|
||||
}
|
||||
return _digitalSignature;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the public.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The public.
|
||||
/// </value>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets ECDsa Object
|
||||
/// </summary>
|
||||
public ECDsa Ecdsa { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EcdsaKey"/> class.
|
||||
/// </summary>
|
||||
public EcdsaKey()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EcdsaKey"/> class.
|
||||
/// </summary>
|
||||
/// <param name="curve">The curve name</param>
|
||||
/// <param name="publickey">Value of publickey</param>
|
||||
/// <param name="privatekey">Value of privatekey</param>
|
||||
public EcdsaKey(string curve, byte[] publickey, byte[] privatekey)
|
||||
{
|
||||
Import(GetCurveOid(curve), publickey, privatekey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EcdsaKey"/> class.
|
||||
/// </summary>
|
||||
/// <param name="data">DER encoded private key data.</param>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged resources and performs other cleanup operations before the
|
||||
/// <see cref="DsaKey"/> is reclaimed by garbage collection.
|
||||
/// </summary>
|
||||
~EcdsaKey()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user