Skip to content

Commit 0002723

Browse files
committed
WIP: Add Support for ECDSA Host- and Private-Keys
Also known as: - ecdsa-sha2-nistp256 - ecdsa-sha2-nistp384 - ecdsa-sha2-nistp521 Works for .NET 3.5 and 4.0 but needs some more testing and tests. Basically it translate between SSH-Data and Microsoft Crypto API. Tested with ECDSA-{256,384,521} Hostkeys and ECDSA-{256,384,521}-Pub/Private-Keys against OpenSSH_7.2p2 Ubuntu-4ubuntu2.4, OpenSSL 1.0.2g 1 Mar 2016. Keys generated with: - ssh-keygen -t ecdsa - ssh-keygen -t ecdsa -b 384 - ssh-keygen -t ecdsa -b 521
1 parent bd01d97 commit 0002723

15 files changed

Lines changed: 684 additions & 16 deletions

src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<DebugType>full</DebugType>
1919
<Optimize>false</Optimize>
2020
<OutputPath>bin\Debug\</OutputPath>
21-
<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>
21+
<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>
2222
<ErrorReport>prompt</ErrorReport>
2323
<WarningLevel>4</WarningLevel>
2424
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
@@ -29,7 +29,7 @@
2929
<DebugType>none</DebugType>
3030
<Optimize>true</Optimize>
3131
<OutputPath>bin\Release\</OutputPath>
32-
<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>
32+
<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>
3333
<ErrorReport>prompt</ErrorReport>
3434
<WarningLevel>4</WarningLevel>
3535
<DocumentationFile>bin\Release\Renci.SshNet.xml</DocumentationFile>
@@ -695,6 +695,12 @@
695695
<Compile Include="..\Renci.SshNet\Security\Cryptography\Key.cs">
696696
<Link>Security\Cryptography\Key.cs</Link>
697697
</Compile>
698+
<Compile Include="..\Renci.SshNet\Security\Cryptography\EcdsaDigitalSignature.cs">
699+
<Link>Security\Cryptography\EcdsaDigitalSignature.cs</Link>
700+
</Compile>
701+
<Compile Include="..\Renci.SshNet\Security\Cryptography\EcdsaKey.cs">
702+
<Link>Security\Cryptography\EcdsaKey.cs</Link>
703+
</Compile>
698704
<Compile Include="..\Renci.SshNet\Security\Cryptography\RsaDigitalSignature.cs">
699705
<Link>Security\Cryptography\RsaDigitalSignature.cs</Link>
700706
</Compile>

src/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,74 @@ public void Test_PrivateKey_RSA_DES_EDE3_CFB()
319319
}
320320
}
321321

322+
#if FEATURE_ECDSA
323+
[TestMethod]
324+
[Owner("darinkes")]
325+
[TestCategory("PrivateKey")]
326+
public void Test_PrivateKey_ECDSA()
327+
{
328+
using (var stream = GetData("Key.ECDSA.txt"))
329+
{
330+
new PrivateKeyFile(stream);
331+
}
332+
}
333+
334+
[TestMethod]
335+
[Owner("darinkes")]
336+
[TestCategory("PrivateKey")]
337+
public void Test_PrivateKey_ECDSA384()
338+
{
339+
using (var stream = GetData("Key.ECDSA384.txt"))
340+
{
341+
new PrivateKeyFile(stream);
342+
}
343+
}
344+
345+
[TestMethod]
346+
[Owner("darinkes")]
347+
[TestCategory("PrivateKey")]
348+
public void Test_PrivateKey_ECDSA521()
349+
{
350+
using (var stream = GetData("Key.ECDSA521.txt"))
351+
{
352+
new PrivateKeyFile(stream);
353+
}
354+
}
355+
356+
[TestMethod]
357+
[Owner("darinkes")]
358+
[TestCategory("PrivateKey")]
359+
public void Test_PrivateKey_ECDSA_Encrypted()
360+
{
361+
using (var stream = GetData("Key.ECDSA.Encrypted.txt"))
362+
{
363+
new PrivateKeyFile(stream, "12345");
364+
}
365+
}
366+
367+
[TestMethod]
368+
[Owner("darinkes")]
369+
[TestCategory("PrivateKey")]
370+
public void Test_PrivateKey_ECDSA384_Encrypted()
371+
{
372+
using (var stream = GetData("Key.ECDSA384.Encrypted.txt"))
373+
{
374+
new PrivateKeyFile(stream, "12345");
375+
}
376+
}
377+
378+
[TestMethod]
379+
[Owner("darinkes")]
380+
[TestCategory("PrivateKey")]
381+
public void Test_PrivateKey_ECDSA521_Encrypted()
382+
{
383+
using (var stream = GetData("Key.ECDSA521.Encrypted.txt"))
384+
{
385+
new PrivateKeyFile(stream, "12345");
386+
}
387+
}
388+
#endif
389+
322390
/// <summary>
323391
///A test for Dispose
324392
///</summary>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
Proc-Type: 4,ENCRYPTED
3+
DEK-Info: AES-128-CBC,54D46F498C989115AAE14FEA21E3AF11
4+
5+
IQdFnndcbzz10d7YQIgEE1TzuzJrm7uYJr4Hvdfz/FshVxMRqxqaqtEgo2vAHHik
6+
BOcPkm+84ERlTNPslcJqLSkKzCdxb7Rz5hfwHuN3Y6Lf01qGakDlzAUEjEyDor+4
7+
zQtAne+f+gRUJnBvLLoVhH4xdeQFC55GECNUFQpEmos=
8+
-----END EC PRIVATE KEY-----
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEIEdqaFKgJBIibVjyUh1v7Y35LwIQJrocdTaYFLwl7iB0oAoGCCqGSM49
3+
AwEHoUQDQgAEQD5MO/n9yqSDTszwzVpApLx5SQFecE5ZfDkgxqVdHQecm1BAPozZ
4+
4eKGNhKn72hT79mLlp9HXX+oNEcuVT83Hw==
5+
-----END EC PRIVATE KEY-----
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
Proc-Type: 4,ENCRYPTED
3+
DEK-Info: AES-128-CBC,1D64653C5E18C2AACB0B17E3FE43C219
4+
5+
lCtRmcvKSeIACwqTtsf/ei1brtCZ386rsk/j7bSXdkZBpvzcmzbeo6w6CYm206Km
6+
hV9TMl2dIO/I1/ov5/2VIR3ZkaElyDOJD/+Be0e3aus4EZj1H1YM/Dv+4QJId+is
7+
Cw4ycWjfudYPPejGdiyjzt5qjaIJwrrEvGtMg7sWVAqDpjcAjS9KuaCu5nOgdItL
8+
s7oHuz+DTGdJQNfUHAlUnz1JaMRWzpP0MwtxdcaRY+w=
9+
-----END EC PRIVATE KEY-----
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MIGkAgEBBDCQawHdHLR7NvKa2vPV0sVkbzOE8c0enp95iEysGcGV66RXE1EH//nh
3+
gu5UzeTR4KigBwYFK4EEACKhZANiAAQUk4rVvoOPI1hQzWpNx09Uo6qG+srGcbvB
4+
q15eFK0GnK/T0UBKxdbZ2+//KAYI6SeDHM9t3ORF1aX5EpjTEBI4d7ZY/lV9jX6M
5+
nJ4XuGteJselM2iMmy+p9ZYw83BYB1Y=
6+
-----END EC PRIVATE KEY-----
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
Proc-Type: 4,ENCRYPTED
3+
DEK-Info: AES-128-CBC,F995028237EBD79C928530CC6C3E957F
4+
5+
wT+iajbte4MnpCipVy/7W9t2I8OgwbMjNBw9PB5xmXR1NQX+yWa81DXMTgjHi8++
6+
6tp+Vlftkr7mY1yvZCVo1Sy4VgcvZeMhtpVKtvYdMCmHJC6gaDOTYX3yee8DJ4FL
7+
fG+IQz0wFyZZ26NFrHiwbufW9z6pXhGNCQZK0KLbFxI9iKwVA0llc7uzTEcmBBpn
8+
0/Snp0CVvX+i6AP9Xj0bBdrFCsvcoT+ZHzS8YWJUfu3m6cpAJksCAy0PXR3ifvus
9+
edTfDpkMxd4/b+DtPB6SMekIAjnQyzbyaTwJCujm8iU=
10+
-----END EC PRIVATE KEY-----
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MIHcAgEBBEIBn2DAme7AU8sCA+/sd6s3c2FNW26IiPvulGd3FC8k5q+fjBZ5LUWR
3+
iJMGrsf2rJLO8hXMGJYoF9tjZEGaabQ8KVagBwYFK4EEACOhgYkDgYYABABrpVjs
4+
ANqcvqMUo1wo0I1uVCXQ6xrauy4iU86FiOwFmkYRrle4w3oYdRJwniC3TwGMuBuM
5+
PMIoCTXr0UtUzn1vkQESNR/J/jAxVseLlVe+KDfZHKvsvk2+O4XaSa1qMfLwN3sp
6+
wlj08+ylKjlO6V3g0hbz4ZaSVwuiRS7Xsv8W2MV6rg==
7+
-----END EC PRIVATE KEY-----

src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,24 @@
709709
<Name>Renci.SshNet</Name>
710710
</ProjectReference>
711711
</ItemGroup>
712+
<ItemGroup>
713+
<EmbeddedResource Include="Data\Key.ECDSA.txt" />
714+
</ItemGroup>
715+
<ItemGroup>
716+
<EmbeddedResource Include="Data\Key.ECDSA384.txt" />
717+
</ItemGroup>
718+
<ItemGroup>
719+
<EmbeddedResource Include="Data\Key.ECDSA521.txt" />
720+
</ItemGroup>
721+
<ItemGroup>
722+
<EmbeddedResource Include="Data\Key.ECDSA.Encrypted.txt" />
723+
</ItemGroup>
724+
<ItemGroup>
725+
<EmbeddedResource Include="Data\Key.ECDSA384.Encrypted.txt" />
726+
</ItemGroup>
727+
<ItemGroup>
728+
<EmbeddedResource Include="Data\Key.ECDSA521.Encrypted.txt" />
729+
</ItemGroup>
712730
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
713731
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
714732
Other similar extension points exist, see Microsoft.Common.targets.

src/Renci.SshNet/Common/DerData.cs

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class DerData
1212

1313
private const byte Boolean = 0x01;
1414
private const byte Integer = 0x02;
15-
//private const byte BITSTRING = 0x03;
15+
private const byte BITSTRING = 0x03;
1616
private const byte Octetstring = 0x04;
1717
private const byte Null = 0x05;
1818
private const byte Objectidentifier = 0x06;
@@ -101,7 +101,7 @@ public BigInteger ReadBigInteger()
101101
{
102102
var type = ReadByte();
103103
if (type != Integer)
104-
throw new InvalidOperationException("Invalid data type, INTEGER(02) is expected.");
104+
throw new InvalidOperationException(string.Format("Invalid data type, INTEGER(02) is expected, got {0}", type));
105105

106106
var length = ReadLength();
107107

@@ -118,7 +118,7 @@ public int ReadInteger()
118118
{
119119
var type = ReadByte();
120120
if (type != Integer)
121-
throw new InvalidOperationException("Invalid data type, INTEGER(02) is expected.");
121+
throw new InvalidOperationException(string.Format("Invalid data type, INTEGER(02) is expected, got {0}", type));
122122

123123
var length = ReadLength();
124124

@@ -140,6 +140,36 @@ public int ReadInteger()
140140
return result;
141141
}
142142

143+
/// <summary>
144+
/// Reads next octetstring data type from internal buffer.
145+
/// </summary>
146+
/// <returns>data read.</returns>
147+
public byte[] ReadOctetString()
148+
{
149+
var type = ReadByte();
150+
if (type != Octetstring)
151+
throw new InvalidOperationException(string.Format("Invalid data type, OCTETSTRING(04) is expected, got {0}", type));
152+
153+
var length = ReadLength();
154+
var data = ReadBytes(length);
155+
return data;
156+
}
157+
158+
/// <summary>
159+
/// Reads next object data type from internal buffer.
160+
/// </summary>
161+
/// <returns>data read.</returns>
162+
public byte[] ReadObject()
163+
{
164+
var type = ReadByte();
165+
if (type != Objectidentifier)
166+
throw new InvalidOperationException(string.Format("Invalid data type, OBJECT(06) is expected, got {0}", type));
167+
168+
var length = ReadLength();
169+
var data = ReadBytes(length);
170+
return data;
171+
}
172+
143173
/// <summary>
144174
/// Writes BOOLEAN data into internal buffer.
145175
/// </summary>
@@ -189,6 +219,18 @@ public void Write(byte[] data)
189219
WriteBytes(data);
190220
}
191221

222+
/// <summary>
223+
/// Writes BITSTRING data into internal buffer.
224+
/// </summary>
225+
/// <param name="data">The data.</param>
226+
public void WriteBitstring(byte[] data)
227+
{
228+
_data.Add(BITSTRING);
229+
var length = GetLength(data.Length);
230+
WriteBytes(length);
231+
WriteBytes(data);
232+
}
233+
192234
/// <summary>
193235
/// Writes OBJECTIDENTIFIER data into internal buffer.
194236
/// </summary>
@@ -229,6 +271,18 @@ public void Write(ObjectIdentifier identifier)
229271
WriteBytes(bytes);
230272
}
231273

274+
/// <summary>
275+
/// Writes OBJECTIDENTIFIER data into internal buffer.
276+
/// </summary>
277+
/// <param name="bytes">The bytes.</param>
278+
public void WriteObjectIdentifier(byte[] bytes)
279+
{
280+
_data.Add(Objectidentifier);
281+
var length = GetLength(bytes.Length);
282+
WriteBytes(length);
283+
WriteBytes(bytes);
284+
}
285+
232286
/// <summary>
233287
/// Writes NULL data into internal buffer.
234288
/// </summary>
@@ -270,8 +324,11 @@ private static IEnumerable<byte> GetLength(int length)
270324
}
271325
return new[] {(byte) length};
272326
}
273-
274-
private int ReadLength()
327+
/// <summary>
328+
/// Gets Data Length
329+
/// </summary>
330+
/// <returns>length</returns>
331+
public int ReadLength()
275332
{
276333
int length = ReadByte();
277334

@@ -306,20 +363,32 @@ private int ReadLength()
306363
return length;
307364
}
308365

309-
private void WriteBytes(IEnumerable<byte> data)
366+
/// <summary>
367+
/// Write Byte data into internal buffer.
368+
/// </summary>
369+
public void WriteBytes(IEnumerable<byte> data)
310370
{
311371
_data.AddRange(data);
312372
}
313373

314-
private byte ReadByte()
374+
/// <summary>
375+
/// Reads Byte data into internal buffer.
376+
/// </summary>
377+
/// <returns>data read</returns>
378+
public byte ReadByte()
315379
{
316380
if (_readerIndex > _data.Count)
317381
throw new InvalidOperationException("Read out of boundaries.");
318382

319383
return _data[_readerIndex++];
320384
}
321385

322-
private byte[] ReadBytes(int length)
386+
/// <summary>
387+
/// Reads lengths Bytes data into internal buffer.
388+
/// </summary>
389+
/// <returns>data read</returns>
390+
/// <param name="length">amount of data to read.</param>
391+
public byte[] ReadBytes(int length)
323392
{
324393
if (_readerIndex + length > _data.Count)
325394
throw new InvalidOperationException("Read out of boundaries.");

0 commit comments

Comments
 (0)