From bb442b8b5de182ced5018c67dc33ea9c6fd881d3 Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Thu, 23 Jun 2022 17:33:08 +0100 Subject: [PATCH] Implement support for rsa-sha2-256 key exchange. There doesn't appear to be any way to select different exchange methods depending on what the server supports for the same key, so this commit allows IPrivateKeySource to expose multiple key exchange methods for the same key and will try each one. Tested on Ubuntu 18.04 and 22.04 with default SSH server config and with only ssh-rsa and rsa-sha2-256 key exchange enabled. --- src/Renci.SshNet/ConnectionInfo.cs | 1 + src/Renci.SshNet/IPrivateKeySource.cs | 2 +- .../PrivateKeyAuthenticationMethod.cs | 31 +++++-- src/Renci.SshNet/PrivateKeyFile.cs | 20 +++-- .../Cryptography/RsaSha256DigitalSignature.cs | 84 +++++++++++++++++++ .../Cryptography/RsaWithSha256SignatureKey.cs | 69 +++++++++++++++ 6 files changed, 189 insertions(+), 18 deletions(-) create mode 100644 src/Renci.SshNet/Security/Cryptography/RsaSha256DigitalSignature.cs create mode 100644 src/Renci.SshNet/Security/Cryptography/RsaWithSha256SignatureKey.cs diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs index af8e1ca5c..b92f6ded1 100644 --- a/src/Renci.SshNet/ConnectionInfo.cs +++ b/src/Renci.SshNet/ConnectionInfo.cs @@ -389,6 +389,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy #endif {"ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(), data)}, {"ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(), data)}, + {"rsa-sha2-256", data => new KeyHostAlgorithm("rsa-sha2-256", new RsaWithSha256SignatureKey(), data)}, //{"x509v3-sign-rsa", () => { ... }, //{"x509v3-sign-dss", () => { ... }, //{"spki-sign-rsa", () => { ... }, diff --git a/src/Renci.SshNet/IPrivateKeySource.cs b/src/Renci.SshNet/IPrivateKeySource.cs index fc3405462..8e75251b2 100644 --- a/src/Renci.SshNet/IPrivateKeySource.cs +++ b/src/Renci.SshNet/IPrivateKeySource.cs @@ -10,6 +10,6 @@ public interface IPrivateKeySource /// /// Gets the host key. /// - HostAlgorithm HostKey { get; } + HostAlgorithm[] HostKeys { get; } } } \ No newline at end of file diff --git a/src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs b/src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs index 43d80b506..58ceaffdd 100644 --- a/src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs +++ b/src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs @@ -4,6 +4,7 @@ using Renci.SshNet.Messages.Authentication; using Renci.SshNet.Messages; using Renci.SshNet.Common; +using Renci.SshNet.Security; using System.Threading; namespace Renci.SshNet @@ -60,24 +61,38 @@ public override AuthenticationResult Authenticate(Session session) session.RegisterMessage("SSH_MSG_USERAUTH_PK_OK"); + HostAlgorithm[] hostKeys = new HostAlgorithm[] { }; + + foreach (var keyFile in KeyFiles) + { + var idx = hostKeys.Length; + + Array.Resize(ref hostKeys, idx + keyFile.HostKeys.Length); + + for(int i = 0; i < keyFile.HostKeys.Length; i++) + { + hostKeys[idx++] = keyFile.HostKeys[i]; + } + } + try { - foreach (var keyFile in KeyFiles) + foreach (var hostKey in hostKeys) { _authenticationCompleted.Reset(); _isSignatureRequired = false; var message = new RequestMessagePublicKey(ServiceName.Connection, Username, - keyFile.HostKey.Name, - keyFile.HostKey.Data); + hostKey.Name, + hostKey.Data); - if (KeyFiles.Count < 2) + if (hostKeys.Length < 2) { // If only one key file provided then send signature for very first request var signatureData = new SignatureData(message, session.SessionId).GetBytes(); - message.Signature = keyFile.HostKey.Sign(signatureData); + message.Signature = hostKey.Sign(signatureData); } // Send public key authentication request @@ -91,12 +106,12 @@ public override AuthenticationResult Authenticate(Session session) var signatureMessage = new RequestMessagePublicKey(ServiceName.Connection, Username, - keyFile.HostKey.Name, - keyFile.HostKey.Data); + hostKey.Name, + hostKey.Data); var signatureData = new SignatureData(message, session.SessionId).GetBytes(); - signatureMessage.Signature = keyFile.HostKey.Sign(signatureData); + signatureMessage.Signature = hostKey.Sign(signatureData); // Send public key authentication request with signature session.SendMessage(signatureMessage); diff --git a/src/Renci.SshNet/PrivateKeyFile.cs b/src/Renci.SshNet/PrivateKeyFile.cs index f29b3e958..f7e9c5a00 100644 --- a/src/Renci.SshNet/PrivateKeyFile.cs +++ b/src/Renci.SshNet/PrivateKeyFile.cs @@ -77,7 +77,7 @@ public class PrivateKeyFile : IPrivateKeySource, IDisposable /// /// Gets the host key. /// - public HostAlgorithm HostKey { get; private set; } + public HostAlgorithm[] HostKeys { get; private set; } /// /// Initializes a new instance of the class. @@ -85,7 +85,7 @@ public class PrivateKeyFile : IPrivateKeySource, IDisposable /// The key. public PrivateKeyFile(Key key) { - HostKey = new KeyHostAlgorithm(key.ToString(), key); + HostKeys = new KeyHostAlgorithm[] { new KeyHostAlgorithm(key.ToString(), key) }; } /// @@ -214,22 +214,24 @@ private void Open(Stream privateKey, string passPhrase) switch (keyName) { case "RSA": - _key = new RsaKey(decryptedData); - HostKey = new KeyHostAlgorithm("ssh-rsa", _key); + HostKeys = new KeyHostAlgorithm[] { + new KeyHostAlgorithm("rsa-sha2-256", new RsaWithSha256SignatureKey(decryptedData)), + new KeyHostAlgorithm("ssh-rsa", new RsaKey(decryptedData)), + }; break; case "DSA": _key = new DsaKey(decryptedData); - HostKey = new KeyHostAlgorithm("ssh-dss", _key); + HostKeys = new KeyHostAlgorithm[] { new KeyHostAlgorithm("ssh-dss", _key) }; break; #if FEATURE_ECDSA case "EC": _key = new EcdsaKey(decryptedData); - HostKey = new KeyHostAlgorithm(_key.ToString(), _key); + HostKeys = new KeyHostAlgorithm[] { new KeyHostAlgorithm(_key.ToString(), _key) }; break; #endif case "OPENSSH": _key = ParseOpenSshV1Key(decryptedData, passPhrase); - HostKey = new KeyHostAlgorithm(_key.ToString(), _key); + HostKeys = new KeyHostAlgorithm[] { new KeyHostAlgorithm(_key.ToString(), _key) }; break; case "SSH2 ENCRYPTED": var reader = new SshDataReader(decryptedData); @@ -281,7 +283,7 @@ private void Open(Stream privateKey, string passPhrase) var q = reader.ReadBigIntWithBits();//p var p = reader.ReadBigIntWithBits();//q _key = new RsaKey(modulus, exponent, d, p, q, inverseQ); - HostKey = new KeyHostAlgorithm("ssh-rsa", _key); + HostKeys = new KeyHostAlgorithm[] { new KeyHostAlgorithm("ssh-rsa", _key) }; } else if (keyType == "dl-modp{sign{dsa-nist-sha1},dh{plain}}") { @@ -296,7 +298,7 @@ private void Open(Stream privateKey, string passPhrase) var y = reader.ReadBigIntWithBits(); var x = reader.ReadBigIntWithBits(); _key = new DsaKey(p, q, g, y, x); - HostKey = new KeyHostAlgorithm("ssh-dss", _key); + HostKeys = new KeyHostAlgorithm[] { new KeyHostAlgorithm("ssh-dss", _key) }; } else { diff --git a/src/Renci.SshNet/Security/Cryptography/RsaSha256DigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/RsaSha256DigitalSignature.cs new file mode 100644 index 000000000..2da484ae1 --- /dev/null +++ b/src/Renci.SshNet/Security/Cryptography/RsaSha256DigitalSignature.cs @@ -0,0 +1,84 @@ +using System; +using System.Security.Cryptography; +using Renci.SshNet.Abstractions; +using Renci.SshNet.Common; +using Renci.SshNet.Security.Cryptography.Ciphers; + +namespace Renci.SshNet.Security.Cryptography +{ + /// + /// Implements RSA digital signature algorithm. + /// + public class RsaSha256DigitalSignature : CipherDigitalSignature, IDisposable + { + private HashAlgorithm _hash; + + /// + /// Initializes a new instance of the class. + /// + /// The RSA key. + public RsaSha256DigitalSignature(RsaWithSha256SignatureKey rsaKey) + : base(new ObjectIdentifier(2, 16, 840, 1, 101, 3, 4, 2, 1), new RsaCipher(rsaKey)) + { + _hash = SHA256.Create(); + } + + /// + /// Hashes the specified input. + /// + /// The input. + /// + /// Hashed data. + /// + protected override byte[] Hash(byte[] input) + { + return _hash.ComputeHash(input); + } + + #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) + { + var hash = _hash; + if (hash != null) + { + hash.Dispose(); + _hash = null; + } + + _isDisposed = true; + } + } + + /// + /// Releases unmanaged resources and performs other cleanup operations before the + /// is reclaimed by garbage collection. + /// + ~RsaSha256DigitalSignature() + { + Dispose(false); + } + + #endregion + } +} diff --git a/src/Renci.SshNet/Security/Cryptography/RsaWithSha256SignatureKey.cs b/src/Renci.SshNet/Security/Cryptography/RsaWithSha256SignatureKey.cs new file mode 100644 index 000000000..2fa6ee366 --- /dev/null +++ b/src/Renci.SshNet/Security/Cryptography/RsaWithSha256SignatureKey.cs @@ -0,0 +1,69 @@ +using System; +using Renci.SshNet.Common; +using Renci.SshNet.Security.Cryptography; + +namespace Renci.SshNet.Security +{ + /// + /// Contains RSA private and public key + /// + public class RsaWithSha256SignatureKey : RsaKey + { + /// + /// Initializes a new instance of the class. + /// + public RsaWithSha256SignatureKey() + { } + + /// + /// Initializes a new instance of the class. + /// + /// DER encoded private key data. + public RsaWithSha256SignatureKey(byte[] data) + : base(data) + { + if (_privateKey.Length != 8) + throw new InvalidOperationException("Invalid private key."); + } + + /// + /// Initializes a new instance of the class. + /// + /// The modulus. + /// The exponent. + /// The d. + /// The p. + /// The q. + /// The inverse Q. + public RsaWithSha256SignatureKey(BigInteger modulus, BigInteger exponent, BigInteger d, BigInteger p, BigInteger q, + BigInteger inverseQ) : base(modulus, exponent, d, p, q, inverseQ) + { + } + + private RsaSha256DigitalSignature _digitalSignature; + + /// + /// Gets the digital signature. + /// + protected override DigitalSignature DigitalSignature + { + get + { + if (_digitalSignature == null) + { + _digitalSignature = new RsaSha256DigitalSignature(this); + } + + return _digitalSignature; + } + } + + /// + /// Gets the Key String. + /// + public override string ToString() + { + return "rsa-sha2-256"; + } + } +}