From 1cc5a2015ab9cd9de677a61addb7a6cf63a6cf76 Mon Sep 17 00:00:00 2001 From: Stefan Rinkes Date: Tue, 27 Nov 2018 15:46:59 +0100 Subject: [PATCH] Support for Ed25519 Host- and Private-Keys --- src/Renci.SshNet/ConnectionInfo.cs | 1 + src/Renci.SshNet/Renci.SshNet.csproj | 2 + .../Cryptography/ED25519DigitalSignature.cs | 93 ++++++++++ .../Security/Cryptography/ED25519Key.cs | 159 ++++++++++++++++++ src/Renci.SshNet/Security/KeyHostAlgorithm.cs | 2 +- 5 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 src/Renci.SshNet/Security/Cryptography/ED25519DigitalSignature.cs create mode 100644 src/Renci.SshNet/Security/Cryptography/ED25519Key.cs 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 e2d94eee7..064a6f83e 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -434,8 +434,10 @@ + + diff --git a/src/Renci.SshNet/Security/Cryptography/ED25519DigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/ED25519DigitalSignature.cs new file mode 100644 index 000000000..4c0a0a3ed --- /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.TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes)); + } + + /// + /// 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 + } +} diff --git a/src/Renci.SshNet/Security/KeyHostAlgorithm.cs b/src/Renci.SshNet/Security/KeyHostAlgorithm.cs index 619bfc922..127490ebc 100644 --- a/src/Renci.SshNet/Security/KeyHostAlgorithm.cs +++ b/src/Renci.SshNet/Security/KeyHostAlgorithm.cs @@ -92,7 +92,7 @@ public BigInteger[] Keys for (var i = 0; i < _keys.Count; i++) { var key = _keys[i]; - keys[i] = key.ToBigInteger(); + keys[i] = BigInteger.SshFormatBignum2(key); } return keys; }