Skip to content

Commit 66c26e4

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 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 66c26e4

File tree

14 files changed

+674
-14
lines changed

14 files changed

+674
-14
lines changed

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

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

322+
[TestMethod]
323+
[Owner("darinkes")]
324+
[TestCategory("PrivateKey")]
325+
public void Test_PrivateKey_ECDSA()
326+
{
327+
using (var stream = GetData("Key.ECDSA.txt"))
328+
{
329+
new PrivateKeyFile(stream);
330+
}
331+
}
332+
333+
[TestMethod]
334+
[Owner("darinkes")]
335+
[TestCategory("PrivateKey")]
336+
public void Test_PrivateKey_ECDSA384()
337+
{
338+
using (var stream = GetData("Key.ECDSA384.txt"))
339+
{
340+
new PrivateKeyFile(stream);
341+
}
342+
}
343+
344+
[TestMethod]
345+
[Owner("darinkes")]
346+
[TestCategory("PrivateKey")]
347+
public void Test_PrivateKey_ECDSA521()
348+
{
349+
using (var stream = GetData("Key.ECDSA521.txt"))
350+
{
351+
new PrivateKeyFile(stream);
352+
}
353+
}
354+
355+
[TestMethod]
356+
[Owner("darinkes")]
357+
[TestCategory("PrivateKey")]
358+
public void Test_PrivateKey_ECDSA_Encrypted()
359+
{
360+
using (var stream = GetData("Key.ECDSA.Encrypted.txt"))
361+
{
362+
new PrivateKeyFile(stream, "12345");
363+
}
364+
}
365+
366+
[TestMethod]
367+
[Owner("darinkes")]
368+
[TestCategory("PrivateKey")]
369+
public void Test_PrivateKey_ECDSA384_Encrypted()
370+
{
371+
using (var stream = GetData("Key.ECDSA384.Encrypted.txt"))
372+
{
373+
new PrivateKeyFile(stream, "12345");
374+
}
375+
}
376+
377+
[TestMethod]
378+
[Owner("darinkes")]
379+
[TestCategory("PrivateKey")]
380+
public void Test_PrivateKey_ECDSA521_Encrypted()
381+
{
382+
using (var stream = GetData("Key.ECDSA521.Encrypted.txt"))
383+
{
384+
new PrivateKeyFile(stream, "12345");
385+
}
386+
}
387+
322388
/// <summary>
323389
///A test for Dispose
324390
///</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.");

src/Renci.SshNet/ConnectionInfo.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,11 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
379379
{
380380
{"ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(), data)},
381381
{"ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(), data)},
382-
//{"ecdsa-sha2-nistp256 "}
382+
#if FEATURE_ECDSA
383+
{"ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(), data)},
384+
{"ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(), data)},
385+
{"ecdsa-sha2-nistp521", data => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(), data)},
386+
#endif
383387
//{"x509v3-sign-rsa", () => { ... },
384388
//{"x509v3-sign-dss", () => { ... },
385389
//{"spki-sign-rsa", () => { ... },

0 commit comments

Comments
 (0)