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"; + } + } +}