Skip to content

Commit 8fc109b

Browse files
committed
Support for Ed25519 Host- and Private-Keys
1 parent 56d2c9e commit 8fc109b

File tree

5 files changed

+404
-1
lines changed

5 files changed

+404
-1
lines changed

src/Renci.SshNet/ConnectionInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
378378

379379
HostKeyAlgorithms = new Dictionary<string, Func<byte[], KeyHostAlgorithm>>
380380
{
381+
{"ssh-ed25519", data => new KeyHostAlgorithm("ssh-ed25519", new ED25519Key(), data)},
381382
#if FEATURE_ECDSA
382383
{"ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(), data)},
383384
{"ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(), data)},

src/Renci.SshNet/Renci.SshNet.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@
376376
<Compile Include="Security\BouncyCastle\src\util\Strings.cs" />
377377
<Compile Include="Security\BouncyCastle\src\util\Times.cs" />
378378
<Compile Include="Security\Chaos.NaCl\CryptoBytes.cs" />
379+
<Compile Include="Security\Chaos.NaCl\Ed25519.cs" />
379380
<Compile Include="Security\Chaos.NaCl\Internal\Array16.cs" />
380381
<Compile Include="Security\Chaos.NaCl\Internal\Array8.cs" />
381382
<Compile Include="Security\Chaos.NaCl\Internal\ByteIntegerConverter.cs" />
@@ -435,8 +436,10 @@
435436
<Compile Include="Security\Chaos.NaCl\Internal\Sha512Internal.cs" />
436437
<Compile Include="Security\Chaos.NaCl\MontgomeryCurve25519.cs" />
437438
<Compile Include="Security\Chaos.NaCl\Sha512.cs" />
439+
<Compile Include="Security\Cryptography\ED25519DigitalSignature.cs" />
438440
<Compile Include="Security\Cryptography\EcdsaDigitalSignature.cs" />
439441
<Compile Include="Security\Cryptography\EcdsaKey.cs" />
442+
<Compile Include="Security\Cryptography\ED25519Key.cs" />
440443
<Compile Include="Security\Cryptography\HMACMD5.cs" />
441444
<Compile Include="Security\Cryptography\HMACSHA1.cs" />
442445
<Compile Include="Security\Cryptography\HMACSHA256.cs" />
@@ -753,4 +756,4 @@
753756
<Target Name="AfterBuild">
754757
</Target>
755758
-->
756-
</Project>
759+
</Project>
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
using System;
2+
using Chaos.NaCl.Internal.Ed25519Ref10;
3+
4+
namespace Chaos.NaCl
5+
{
6+
public static class Ed25519
7+
{
8+
public static readonly int PublicKeySizeInBytes = 32;
9+
public static readonly int SignatureSizeInBytes = 64;
10+
public static readonly int ExpandedPrivateKeySizeInBytes = 32 * 2;
11+
public static readonly int PrivateKeySeedSizeInBytes = 32;
12+
public static readonly int SharedKeySizeInBytes = 32;
13+
14+
public static bool Verify(ArraySegment<byte> signature, ArraySegment<byte> message, ArraySegment<byte> publicKey)
15+
{
16+
if (signature.Count != SignatureSizeInBytes)
17+
throw new ArgumentException(string.Format("Signature size must be {0}", SignatureSizeInBytes), "signature.Count");
18+
if (publicKey.Count != PublicKeySizeInBytes)
19+
throw new ArgumentException(string.Format("Public key size must be {0}", PublicKeySizeInBytes), "publicKey.Count");
20+
return Ed25519Operations.crypto_sign_verify(signature.Array, signature.Offset, message.Array, message.Offset, message.Count, publicKey.Array, publicKey.Offset);
21+
}
22+
23+
public static bool Verify(byte[] signature, byte[] message, byte[] publicKey)
24+
{
25+
if (signature == null)
26+
throw new ArgumentNullException("signature");
27+
if (message == null)
28+
throw new ArgumentNullException("message");
29+
if (publicKey == null)
30+
throw new ArgumentNullException("publicKey");
31+
if (signature.Length != SignatureSizeInBytes)
32+
throw new ArgumentException(string.Format("Signature size must be {0}", SignatureSizeInBytes), "signature.Length");
33+
if (publicKey.Length != PublicKeySizeInBytes)
34+
throw new ArgumentException(string.Format("Public key size must be {0}", PublicKeySizeInBytes), "publicKey.Length");
35+
return Ed25519Operations.crypto_sign_verify(signature, 0, message, 0, message.Length, publicKey, 0);
36+
}
37+
38+
public static void Sign(ArraySegment<byte> signature, ArraySegment<byte> message, ArraySegment<byte> expandedPrivateKey)
39+
{
40+
if (signature.Array == null)
41+
throw new ArgumentNullException("signature.Array");
42+
if (signature.Count != SignatureSizeInBytes)
43+
throw new ArgumentException("signature.Count");
44+
if (expandedPrivateKey.Array == null)
45+
throw new ArgumentNullException("expandedPrivateKey.Array");
46+
if (expandedPrivateKey.Count != ExpandedPrivateKeySizeInBytes)
47+
throw new ArgumentException("expandedPrivateKey.Count");
48+
if (message.Array == null)
49+
throw new ArgumentNullException("message.Array");
50+
Ed25519Operations.crypto_sign2(signature.Array, signature.Offset, message.Array, message.Offset, message.Count, expandedPrivateKey.Array, expandedPrivateKey.Offset);
51+
}
52+
53+
public static byte[] Sign(byte[] message, byte[] expandedPrivateKey)
54+
{
55+
var signature = new byte[SignatureSizeInBytes];
56+
Sign(new ArraySegment<byte>(signature), new ArraySegment<byte>(message), new ArraySegment<byte>(expandedPrivateKey));
57+
return signature;
58+
}
59+
60+
public static byte[] PublicKeyFromSeed(byte[] privateKeySeed)
61+
{
62+
byte[] privateKey;
63+
byte[] publicKey;
64+
KeyPairFromSeed(out publicKey, out privateKey, privateKeySeed);
65+
CryptoBytes.Wipe(privateKey);
66+
return publicKey;
67+
}
68+
69+
public static byte[] ExpandedPrivateKeyFromSeed(byte[] privateKeySeed)
70+
{
71+
byte[] privateKey;
72+
byte[] publicKey;
73+
KeyPairFromSeed(out publicKey, out privateKey, privateKeySeed);
74+
CryptoBytes.Wipe(publicKey);
75+
return privateKey;
76+
}
77+
78+
public static void KeyPairFromSeed(out byte[] publicKey, out byte[] expandedPrivateKey, byte[] privateKeySeed)
79+
{
80+
if (privateKeySeed == null)
81+
throw new ArgumentNullException("privateKeySeed");
82+
if (privateKeySeed.Length != PrivateKeySeedSizeInBytes)
83+
throw new ArgumentException("privateKeySeed");
84+
var pk = new byte[PublicKeySizeInBytes];
85+
var sk = new byte[ExpandedPrivateKeySizeInBytes];
86+
Ed25519Operations.crypto_sign_keypair(pk, 0, sk, 0, privateKeySeed, 0);
87+
publicKey = pk;
88+
expandedPrivateKey = sk;
89+
}
90+
91+
public static void KeyPairFromSeed(ArraySegment<byte> publicKey, ArraySegment<byte> expandedPrivateKey, ArraySegment<byte> privateKeySeed)
92+
{
93+
if (publicKey.Array == null)
94+
throw new ArgumentNullException("publicKey.Array");
95+
if (expandedPrivateKey.Array == null)
96+
throw new ArgumentNullException("expandedPrivateKey.Array");
97+
if (privateKeySeed.Array == null)
98+
throw new ArgumentNullException("privateKeySeed.Array");
99+
if (publicKey.Count != PublicKeySizeInBytes)
100+
throw new ArgumentException("publicKey.Count");
101+
if (expandedPrivateKey.Count != ExpandedPrivateKeySizeInBytes)
102+
throw new ArgumentException("expandedPrivateKey.Count");
103+
if (privateKeySeed.Count != PrivateKeySeedSizeInBytes)
104+
throw new ArgumentException("privateKeySeed.Count");
105+
Ed25519Operations.crypto_sign_keypair(
106+
publicKey.Array, publicKey.Offset,
107+
expandedPrivateKey.Array, expandedPrivateKey.Offset,
108+
privateKeySeed.Array, privateKeySeed.Offset);
109+
}
110+
111+
[Obsolete("Needs more testing")]
112+
public static byte[] KeyExchange(byte[] publicKey, byte[] privateKey)
113+
{
114+
var sharedKey = new byte[SharedKeySizeInBytes];
115+
KeyExchange(new ArraySegment<byte>(sharedKey), new ArraySegment<byte>(publicKey), new ArraySegment<byte>(privateKey));
116+
return sharedKey;
117+
}
118+
119+
[Obsolete("Needs more testing")]
120+
public static void KeyExchange(ArraySegment<byte> sharedKey, ArraySegment<byte> publicKey, ArraySegment<byte> privateKey)
121+
{
122+
if (sharedKey.Array == null)
123+
throw new ArgumentNullException("sharedKey.Array");
124+
if (publicKey.Array == null)
125+
throw new ArgumentNullException("publicKey.Array");
126+
if (privateKey.Array == null)
127+
throw new ArgumentNullException("privateKey");
128+
if (sharedKey.Count != 32)
129+
throw new ArgumentException("sharedKey.Count != 32");
130+
if (publicKey.Count != 32)
131+
throw new ArgumentException("publicKey.Count != 32");
132+
if (privateKey.Count != 64)
133+
throw new ArgumentException("privateKey.Count != 64");
134+
135+
FieldElement montgomeryX, edwardsY, edwardsZ, sharedMontgomeryX;
136+
FieldOperations.fe_frombytes(out edwardsY, publicKey.Array, publicKey.Offset);
137+
FieldOperations.fe_1(out edwardsZ);
138+
MontgomeryCurve25519.EdwardsToMontgomeryX(out montgomeryX, ref edwardsY, ref edwardsZ);
139+
byte[] h = Sha512.Hash(privateKey.Array, privateKey.Offset, 32);//ToDo: Remove alloc
140+
ScalarOperations.sc_clamp(h, 0);
141+
MontgomeryOperations.scalarmult(out sharedMontgomeryX, h, 0, ref montgomeryX);
142+
CryptoBytes.Wipe(h);
143+
FieldOperations.fe_tobytes(sharedKey.Array, sharedKey.Offset, ref sharedMontgomeryX);
144+
MontgomeryCurve25519.KeyExchangeOutputHashNaCl(sharedKey.Array, sharedKey.Offset);
145+
}
146+
}
147+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using System;
2+
using Renci.SshNet.Common;
3+
using Chaos.NaCl;
4+
5+
namespace Renci.SshNet.Security.Cryptography
6+
{
7+
/// <summary>
8+
/// Implements ECDSA digital signature algorithm.
9+
/// </summary>
10+
public class ED25519DigitalSignature : DigitalSignature, IDisposable
11+
{
12+
private readonly ED25519Key _key;
13+
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="ED25519DigitalSignature" /> class.
16+
/// </summary>
17+
/// <param name="key">The ED25519Key key.</param>
18+
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <c>null</c>.</exception>
19+
public ED25519DigitalSignature(ED25519Key key)
20+
{
21+
if (key == null)
22+
throw new ArgumentNullException("key");
23+
24+
_key = key;
25+
}
26+
27+
/// <summary>
28+
/// Verifies the signature.
29+
/// </summary>
30+
/// <param name="input">The input.</param>
31+
/// <param name="signature">The signature.</param>
32+
/// <returns>
33+
/// <c>true</c> if signature was successfully verified; otherwise <c>false</c>.
34+
/// </returns>
35+
/// <exception cref="InvalidOperationException">Invalid signature.</exception>
36+
public override bool Verify(byte[] input, byte[] signature)
37+
{
38+
return Ed25519.Verify(signature, input, _key.PublicKey);
39+
}
40+
41+
/// <summary>
42+
/// Creates the signature.
43+
/// </summary>
44+
/// <param name="input">The input.</param>
45+
/// <returns>
46+
/// Signed input data.
47+
/// </returns>
48+
/// <exception cref="SshException">Invalid ED25519Key key.</exception>
49+
public override byte[] Sign(byte[] input)
50+
{
51+
return Ed25519.Sign(input, _key.PrivateKey);
52+
}
53+
54+
#region IDisposable Members
55+
56+
private bool _isDisposed;
57+
58+
/// <summary>
59+
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
60+
/// </summary>
61+
public void Dispose()
62+
{
63+
Dispose(true);
64+
GC.SuppressFinalize(this);
65+
}
66+
67+
/// <summary>
68+
/// Releases unmanaged and - optionally - managed resources
69+
/// </summary>
70+
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
71+
protected virtual void Dispose(bool disposing)
72+
{
73+
if (_isDisposed)
74+
return;
75+
76+
if (disposing)
77+
{
78+
_isDisposed = true;
79+
}
80+
}
81+
82+
/// <summary>
83+
/// Releases unmanaged resources and performs other cleanup operations before the
84+
/// <see cref="ED25519DigitalSignature"/> is reclaimed by garbage collection.
85+
/// </summary>
86+
~ED25519DigitalSignature()
87+
{
88+
Dispose(false);
89+
}
90+
91+
#endregion
92+
}
93+
}

0 commit comments

Comments
 (0)