Skip to content

Commit

Permalink
Support for Ed25519 Host- and Private-Keys
Browse files Browse the repository at this point in the history
  • Loading branch information
darinkes committed Dec 3, 2018
1 parent a6108d8 commit 00387d4
Show file tree
Hide file tree
Showing 5 changed files with 404 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/Renci.SshNet/ConnectionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy

HostKeyAlgorithms = new Dictionary<string, Func<byte[], KeyHostAlgorithm>>
{
{"ssh-ed25519", data => new KeyHostAlgorithm("ssh-ed25519", new ED25519Key(), data)},
#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)},
Expand Down
5 changes: 4 additions & 1 deletion src/Renci.SshNet/Renci.SshNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
<Compile Include="Security\BouncyCastle\src\util\Strings.cs" />
<Compile Include="Security\BouncyCastle\src\util\Times.cs" />
<Compile Include="Security\Chaos.NaCl\CryptoBytes.cs" />
<Compile Include="Security\Chaos.NaCl\Ed25519.cs" />
<Compile Include="Security\Chaos.NaCl\Internal\Array16.cs" />
<Compile Include="Security\Chaos.NaCl\Internal\Array8.cs" />
<Compile Include="Security\Chaos.NaCl\Internal\ByteIntegerConverter.cs" />
Expand Down Expand Up @@ -434,8 +435,10 @@
<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\ED25519DigitalSignature.cs" />
<Compile Include="Security\Cryptography\EcdsaDigitalSignature.cs" />
<Compile Include="Security\Cryptography\EcdsaKey.cs" />
<Compile Include="Security\Cryptography\ED25519Key.cs" />
<Compile Include="Security\Cryptography\HMACMD5.cs" />
<Compile Include="Security\Cryptography\HMACSHA1.cs" />
<Compile Include="Security\Cryptography\HMACSHA256.cs" />
Expand Down Expand Up @@ -753,4 +756,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>
147 changes: 147 additions & 0 deletions src/Renci.SshNet/Security/Chaos.NaCl/Ed25519.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using System;
using Renci.SshNet.Security.Chaos.NaCl.Internal.Ed25519Ref10;

namespace Renci.SshNet.Security.Chaos.NaCl
{
internal static class Ed25519
{
public static readonly int PublicKeySizeInBytes = 32;
public static readonly int SignatureSizeInBytes = 64;
public static readonly int ExpandedPrivateKeySizeInBytes = 32 * 2;
public static readonly int PrivateKeySeedSizeInBytes = 32;
public static readonly int SharedKeySizeInBytes = 32;

public static bool Verify(ArraySegment<byte> signature, ArraySegment<byte> message, ArraySegment<byte> publicKey)
{
if (signature.Count != SignatureSizeInBytes)
throw new ArgumentException(string.Format("Signature size must be {0}", SignatureSizeInBytes), "signature.Count");
if (publicKey.Count != PublicKeySizeInBytes)
throw new ArgumentException(string.Format("Public key size must be {0}", PublicKeySizeInBytes), "publicKey.Count");
return Ed25519Operations.crypto_sign_verify(signature.Array, signature.Offset, message.Array, message.Offset, message.Count, publicKey.Array, publicKey.Offset);
}

public static bool Verify(byte[] signature, byte[] message, byte[] publicKey)
{
if (signature == null)
throw new ArgumentNullException("signature");
if (message == null)
throw new ArgumentNullException("message");
if (publicKey == null)
throw new ArgumentNullException("publicKey");
if (signature.Length != SignatureSizeInBytes)
throw new ArgumentException(string.Format("Signature size must be {0}", SignatureSizeInBytes), "signature.Length");
if (publicKey.Length != PublicKeySizeInBytes)
throw new ArgumentException(string.Format("Public key size must be {0}", PublicKeySizeInBytes), "publicKey.Length");
return Ed25519Operations.crypto_sign_verify(signature, 0, message, 0, message.Length, publicKey, 0);
}

public static void Sign(ArraySegment<byte> signature, ArraySegment<byte> message, ArraySegment<byte> expandedPrivateKey)
{
if (signature.Array == null)
throw new ArgumentNullException("signature.Array");
if (signature.Count != SignatureSizeInBytes)
throw new ArgumentException("signature.Count");
if (expandedPrivateKey.Array == null)
throw new ArgumentNullException("expandedPrivateKey.Array");
if (expandedPrivateKey.Count != ExpandedPrivateKeySizeInBytes)
throw new ArgumentException("expandedPrivateKey.Count");
if (message.Array == null)
throw new ArgumentNullException("message.Array");
Ed25519Operations.crypto_sign2(signature.Array, signature.Offset, message.Array, message.Offset, message.Count, expandedPrivateKey.Array, expandedPrivateKey.Offset);
}

public static byte[] Sign(byte[] message, byte[] expandedPrivateKey)
{
var signature = new byte[SignatureSizeInBytes];
Sign(new ArraySegment<byte>(signature), new ArraySegment<byte>(message), new ArraySegment<byte>(expandedPrivateKey));
return signature;
}

public static byte[] PublicKeyFromSeed(byte[] privateKeySeed)
{
byte[] privateKey;
byte[] publicKey;
KeyPairFromSeed(out publicKey, out privateKey, privateKeySeed);
CryptoBytes.Wipe(privateKey);
return publicKey;
}

public static byte[] ExpandedPrivateKeyFromSeed(byte[] privateKeySeed)
{
byte[] privateKey;
byte[] publicKey;
KeyPairFromSeed(out publicKey, out privateKey, privateKeySeed);
CryptoBytes.Wipe(publicKey);
return privateKey;
}

public static void KeyPairFromSeed(out byte[] publicKey, out byte[] expandedPrivateKey, byte[] privateKeySeed)
{
if (privateKeySeed == null)
throw new ArgumentNullException("privateKeySeed");
if (privateKeySeed.Length != PrivateKeySeedSizeInBytes)
throw new ArgumentException("privateKeySeed");
var pk = new byte[PublicKeySizeInBytes];
var sk = new byte[ExpandedPrivateKeySizeInBytes];
Ed25519Operations.crypto_sign_keypair(pk, 0, sk, 0, privateKeySeed, 0);
publicKey = pk;
expandedPrivateKey = sk;
}

public static void KeyPairFromSeed(ArraySegment<byte> publicKey, ArraySegment<byte> expandedPrivateKey, ArraySegment<byte> privateKeySeed)
{
if (publicKey.Array == null)
throw new ArgumentNullException("publicKey.Array");
if (expandedPrivateKey.Array == null)
throw new ArgumentNullException("expandedPrivateKey.Array");
if (privateKeySeed.Array == null)
throw new ArgumentNullException("privateKeySeed.Array");
if (publicKey.Count != PublicKeySizeInBytes)
throw new ArgumentException("publicKey.Count");
if (expandedPrivateKey.Count != ExpandedPrivateKeySizeInBytes)
throw new ArgumentException("expandedPrivateKey.Count");
if (privateKeySeed.Count != PrivateKeySeedSizeInBytes)
throw new ArgumentException("privateKeySeed.Count");
Ed25519Operations.crypto_sign_keypair(
publicKey.Array, publicKey.Offset,
expandedPrivateKey.Array, expandedPrivateKey.Offset,
privateKeySeed.Array, privateKeySeed.Offset);
}

[Obsolete("Needs more testing")]
public static byte[] KeyExchange(byte[] publicKey, byte[] privateKey)
{
var sharedKey = new byte[SharedKeySizeInBytes];
KeyExchange(new ArraySegment<byte>(sharedKey), new ArraySegment<byte>(publicKey), new ArraySegment<byte>(privateKey));
return sharedKey;
}

[Obsolete("Needs more testing")]
public static void KeyExchange(ArraySegment<byte> sharedKey, ArraySegment<byte> publicKey, ArraySegment<byte> privateKey)
{
if (sharedKey.Array == null)
throw new ArgumentNullException("sharedKey.Array");
if (publicKey.Array == null)
throw new ArgumentNullException("publicKey.Array");
if (privateKey.Array == null)
throw new ArgumentNullException("privateKey");
if (sharedKey.Count != 32)
throw new ArgumentException("sharedKey.Count != 32");
if (publicKey.Count != 32)
throw new ArgumentException("publicKey.Count != 32");
if (privateKey.Count != 64)
throw new ArgumentException("privateKey.Count != 64");

FieldElement montgomeryX, edwardsY, edwardsZ, sharedMontgomeryX;
FieldOperations.fe_frombytes(out edwardsY, publicKey.Array, publicKey.Offset);
FieldOperations.fe_1(out edwardsZ);
MontgomeryCurve25519.EdwardsToMontgomeryX(out montgomeryX, ref edwardsY, ref edwardsZ);
byte[] h = Sha512.Hash(privateKey.Array, privateKey.Offset, 32);//ToDo: Remove alloc
ScalarOperations.sc_clamp(h, 0);
MontgomeryOperations.scalarmult(out sharedMontgomeryX, h, 0, ref montgomeryX);
CryptoBytes.Wipe(h);
FieldOperations.fe_tobytes(sharedKey.Array, sharedKey.Offset, ref sharedMontgomeryX);
MontgomeryCurve25519.KeyExchangeOutputHashNaCl(sharedKey.Array, sharedKey.Offset);
}
}
}
93 changes: 93 additions & 0 deletions src/Renci.SshNet/Security/Cryptography/ED25519DigitalSignature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;
using Renci.SshNet.Common;
using Renci.SshNet.Security.Chaos.NaCl;

namespace Renci.SshNet.Security.Cryptography
{
/// <summary>
/// Implements ECDSA digital signature algorithm.
/// </summary>
public class ED25519DigitalSignature : DigitalSignature, IDisposable
{
private readonly ED25519Key _key;

/// <summary>
/// Initializes a new instance of the <see cref="ED25519DigitalSignature" /> class.
/// </summary>
/// <param name="key">The ED25519Key key.</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <c>null</c>.</exception>
public ED25519DigitalSignature(ED25519Key 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>
/// <exception cref="InvalidOperationException">Invalid signature.</exception>
public override bool Verify(byte[] input, byte[] signature)
{
return Ed25519.Verify(signature, input, _key.PublicKey);
}

/// <summary>
/// Creates the signature.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>
/// Signed input data.
/// </returns>
/// <exception cref="SshException">Invalid ED25519Key key.</exception>
public override byte[] Sign(byte[] input)
{
return Ed25519.Sign(input, _key.PrivateKey);
}

#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="ED25519DigitalSignature"/> is reclaimed by garbage collection.
/// </summary>
~ED25519DigitalSignature()
{
Dispose(false);
}

#endregion
}
}
Loading

0 comments on commit 00387d4

Please sign in to comment.