diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs index 26ca884df..d76882c25 100644 --- a/src/Renci.SshNet/ConnectionInfo.cs +++ b/src/Renci.SshNet/ConnectionInfo.cs @@ -378,6 +378,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy HostKeyAlgorithms = new Dictionary> { + {"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)}, diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index 930187538..1983732fb 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -375,6 +375,7 @@ + @@ -434,8 +435,10 @@ + + @@ -753,4 +756,4 @@ --> - + \ No newline at end of file diff --git a/src/Renci.SshNet/Security/Chaos.NaCl/Ed25519.cs b/src/Renci.SshNet/Security/Chaos.NaCl/Ed25519.cs new file mode 100644 index 000000000..5a085225b --- /dev/null +++ b/src/Renci.SshNet/Security/Chaos.NaCl/Ed25519.cs @@ -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 signature, ArraySegment message, ArraySegment 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 signature, ArraySegment message, ArraySegment 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(signature), new ArraySegment(message), new ArraySegment(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 publicKey, ArraySegment expandedPrivateKey, ArraySegment 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(sharedKey), new ArraySegment(publicKey), new ArraySegment(privateKey)); + return sharedKey; + } + + [Obsolete("Needs more testing")] + public static void KeyExchange(ArraySegment sharedKey, ArraySegment publicKey, ArraySegment 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); + } + } +} \ No newline at end of file diff --git a/src/Renci.SshNet/Security/Cryptography/ED25519DigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/ED25519DigitalSignature.cs new file mode 100644 index 000000000..be68fd481 --- /dev/null +++ b/src/Renci.SshNet/Security/Cryptography/ED25519DigitalSignature.cs @@ -0,0 +1,93 @@ +using System; +using Renci.SshNet.Common; +using Renci.SshNet.Security.Chaos.NaCl; + +namespace Renci.SshNet.Security.Cryptography +{ + /// + /// Implements ECDSA digital signature algorithm. + /// + public class ED25519DigitalSignature : DigitalSignature, IDisposable + { + private readonly ED25519Key _key; + + /// + /// Initializes a new instance of the class. + /// + /// The ED25519Key key. + /// is null. + public ED25519DigitalSignature(ED25519Key key) + { + if (key == null) + throw new ArgumentNullException("key"); + + _key = key; + } + + /// + /// Verifies the signature. + /// + /// The input. + /// The signature. + /// + /// true if signature was successfully verified; otherwise false. + /// + /// Invalid signature. + public override bool Verify(byte[] input, byte[] signature) + { + return Ed25519.Verify(signature, input, _key.PublicKey); + } + + /// + /// Creates the signature. + /// + /// The input. + /// + /// Signed input data. + /// + /// Invalid ED25519Key key. + public override byte[] Sign(byte[] input) + { + return Ed25519.Sign(input, _key.PrivateKey); + } + + #region IDisposable Members + + private bool _isDisposed; + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_isDisposed) + return; + + if (disposing) + { + _isDisposed = true; + } + } + + /// + /// Releases unmanaged resources and performs other cleanup operations before the + /// is reclaimed by garbage collection. + /// + ~ED25519DigitalSignature() + { + Dispose(false); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Renci.SshNet/Security/Cryptography/ED25519Key.cs b/src/Renci.SshNet/Security/Cryptography/ED25519Key.cs new file mode 100644 index 000000000..1434c47a4 --- /dev/null +++ b/src/Renci.SshNet/Security/Cryptography/ED25519Key.cs @@ -0,0 +1,159 @@ +using System; +using Renci.SshNet.Common; +using Renci.SshNet.Security.Cryptography; +using Renci.SshNet.Security.Chaos.NaCl; + +namespace Renci.SshNet.Security +{ + /// + /// Contains ED25519 private and public key + /// + public class ED25519Key : Key, IDisposable + { + private ED25519DigitalSignature _digitalSignature; + + private byte[] SK; + private byte[] PK; + private byte[] publicKey = new byte[Ed25519.PublicKeySizeInBytes]; + private byte[] privateKey = new byte[Ed25519.ExpandedPrivateKeySizeInBytes]; + + /// + /// Gets the Key String. + /// + public override string ToString() + { + return "ssh-ed25519"; + } + + /// + /// Gets or sets the public. + /// + /// + /// The public. + /// + public override BigInteger[] Public + { + get + { + if (PK != null) + return new BigInteger[] { new BigInteger(PK) }; + return new BigInteger[] { new BigInteger(publicKey) }; + } + set + { + publicKey = value[0].ToByteArray().Reverse(); + } + } + + /// + /// Gets the length of the key. + /// + /// + /// The length of the key. + /// + public override int KeyLength + { + get + { + return publicKey.Length; + } + } + + /// + /// Gets the digital signature. + /// + protected override DigitalSignature DigitalSignature + { + get + { + if (_digitalSignature == null) + { + _digitalSignature = new ED25519DigitalSignature(this); + } + return _digitalSignature; + } + } + + /// + /// Gets the PublicKey Bytes + /// + public byte[] PublicKey + { + get + { + return publicKey; + } + } + + /// + /// Gets the PrivateKey Bytes + /// + public byte[] PrivateKey + { + get + { + return privateKey; + } + } + + /// + /// Initializes a new instance of the class. + /// + public ED25519Key() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// pk data. + /// sk data. + public ED25519Key(byte[] pk, byte[] sk) + { + PK = pk; + SK = sk; + var seed = new byte[Ed25519.PrivateKeySeedSizeInBytes]; + Buffer.BlockCopy(sk, 0, seed, 0, seed.Length); + Ed25519.KeyPairFromSeed(out publicKey, out privateKey, seed); + } + + #region IDisposable Members + + private bool _isDisposed; + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_isDisposed) + return; + + if (disposing) + { + _isDisposed = true; + } + } + + /// + /// Releases unmanaged resources and performs other cleanup operations before the + /// is reclaimed by garbage collection. + /// + ~ED25519Key() + { + Dispose(false); + } + + #endregion + } +}