Răsfoiți Sursa

Add support for RSA SHA-2 public key algorithms (#1177)

* Abstract out the hash algorithm from RsaDigitalSignature

* Add integration tests

* Add DigitalSignature property to KeyHostAlgorithm

* Add IHostAlgorithmsProvider interface

* Verify the host signature

* Fix HostKeyEventArgsTest after merge

* Remove PubkeyAcceptedAlgorithms ssh-rsa

* Add test coverage for RSA keys in PrivateKeyFile

* Obsolete IPrivateKeySource

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>
Rob Hague 2 ani în urmă
părinte
comite
8732d3d7ef
34 a modificat fișierele cu 767 adăugiri și 210 ștergeri
  1. 0 2
      src/Renci.SshNet.IntegrationTests/Common/RemoteSshdConfigExtensions.cs
  2. 0 1
      src/Renci.SshNet.IntegrationTests/Dockerfile
  3. 25 47
      src/Renci.SshNet.IntegrationTests/HostKeyAlgorithmTests.cs
  4. 43 22
      src/Renci.SshNet.IntegrationTests/PrivateKeyAuthenticationTests.cs
  5. 1 0
      src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj
  6. 2 1
      src/Renci.SshNet.IntegrationTests/user/sshnet/authorized_keys
  7. 5 2
      src/Renci.SshNet.TestTools.OpenSSH/SshdConfig.cs
  8. 1 1
      src/Renci.SshNet.Tests/Classes/Common/HostKeyEventArgsTest.cs
  9. 26 10
      src/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs
  10. 154 10
      src/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs
  11. 215 0
      src/Renci.SshNet.Tests/Classes/Security/KeyAlgorithmTest.cs
  12. 2 2
      src/Renci.SshNet.Tests/Common/TestBase.cs
  13. 16 0
      src/Renci.SshNet/Common/ObjectIdentifier.cs
  14. 4 0
      src/Renci.SshNet/ConnectionInfo.cs
  15. 21 0
      src/Renci.SshNet/IHostAlgorithmsProvider.cs
  16. 14 3
      src/Renci.SshNet/IPrivateKeySource.cs
  17. 2 2
      src/Renci.SshNet/NetConfClient.cs
  18. 15 12
      src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs
  19. 9 9
      src/Renci.SshNet/PrivateKeyConnectionInfo.cs
  20. 59 7
      src/Renci.SshNet/PrivateKeyFile.cs
  21. 2 2
      src/Renci.SshNet/ScpClient.cs
  22. 3 4
      src/Renci.SshNet/Security/Cryptography/Ciphers/RsaCipher.cs
  23. 1 1
      src/Renci.SshNet/Security/Cryptography/DsaKey.cs
  24. 1 1
      src/Renci.SshNet/Security/Cryptography/ED25519Key.cs
  25. 1 1
      src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs
  26. 2 2
      src/Renci.SshNet/Security/Cryptography/Key.cs
  27. 13 4
      src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs
  28. 6 2
      src/Renci.SshNet/Security/Cryptography/RsaKey.cs
  29. 22 0
      src/Renci.SshNet/Security/KeyExchange.cs
  30. 1 15
      src/Renci.SshNet/Security/KeyExchangeDiffieHellman.cs
  31. 2 18
      src/Renci.SshNet/Security/KeyExchangeEC.cs
  32. 95 25
      src/Renci.SshNet/Security/KeyHostAlgorithm.cs
  33. 2 2
      src/Renci.SshNet/SftpClient.cs
  34. 2 2
      src/Renci.SshNet/SshClient.cs

+ 0 - 2
src/Renci.SshNet.IntegrationTests/Common/RemoteSshdConfigExtensions.cs

@@ -20,9 +20,7 @@ namespace Renci.SshNet.IntegrationTests.Common
                             .ClearCiphers()
                             .ClearKeyExchangeAlgorithms()
                             .ClearHostKeyAlgorithms()
-                            .AddHostKeyAlgorithm(HostKeyAlgorithm.SshRsa)
                             .ClearPublicKeyAcceptedAlgorithms()
-                            .AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.SshRsa)
                             .WithUsePAM(true)
                             .Update()
                             .Restart();

+ 0 - 1
src/Renci.SshNet.IntegrationTests/Dockerfile

@@ -14,7 +14,6 @@ RUN apk update && apk upgrade --no-cache && \
     chmod 400 /etc/ssh/ssh*key && \
     sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
     sed -i 's/#LogLevel\s*INFO/LogLevel DEBUG3/' /etc/ssh/sshd_config && \
-    echo 'PubkeyAcceptedAlgorithms ssh-rsa' >> /etc/ssh/sshd_config && \
     chmod 646 /etc/ssh/sshd_config && \
     # install and configure sudo
     apk add --no-cache sudo && \

+ 25 - 47
src/Renci.SshNet.IntegrationTests/HostKeyAlgorithmTests.cs

@@ -22,63 +22,46 @@ namespace Renci.SshNet.IntegrationTests
         {
             _remoteSshdConfig?.Reset();
         }
-        
+
         [TestMethod]
         [Ignore] // No longer supported in recent versions of OpenSSH
+        // TODO: We should be able to enable some legacy settings to make it work
+        // https://www.openssh.com/legacy.html e.g. PubkeyAcceptedKeyTypes / HostbasedAcceptedKeyTypes ?
         public void SshDsa()
         {
-            _remoteSshdConfig.ClearHostKeyAlgorithms()
-                             .AddHostKeyAlgorithm(HostKeyAlgorithm.SshDsa)
-                             .ClearHostKeyFiles()
-                             .AddHostKeyFile(HostKeyFile.Dsa.FilePath)
-                             .Update()
-                             .Restart();
-
-            HostKeyEventArgs hostKeyEventsArgs = null;
-
-            using (var client = new SshClient(_connectionInfoFactory.Create()))
-            {
-                client.HostKeyReceived += (sender, e) => hostKeyEventsArgs = e;
-                client.Connect();
-                client.Disconnect();
-            }
-
-            Assert.IsNotNull(hostKeyEventsArgs);
-            Assert.AreEqual(HostKeyFile.Dsa.KeyName, hostKeyEventsArgs.HostKeyName);
-            Assert.AreEqual(1024, hostKeyEventsArgs.KeyLength);
-            Assert.IsTrue(hostKeyEventsArgs.FingerPrint.SequenceEqual(HostKeyFile.Dsa.FingerPrint));
+            DoTest(HostKeyAlgorithm.SshDsa, HostKeyFile.Dsa, 1024);
         }
 
         [TestMethod]
         public void SshRsa()
         {
-            _remoteSshdConfig.ClearHostKeyAlgorithms()
-                             .AddHostKeyAlgorithm(HostKeyAlgorithm.SshRsa)
-                             .Update()
-                             .Restart();
-
-            HostKeyEventArgs hostKeyEventsArgs = null;
+            DoTest(HostKeyAlgorithm.SshRsa, HostKeyFile.Rsa, 3072);
+        }
 
-            using (var client = new SshClient(_connectionInfoFactory.Create()))
-            {
-                client.HostKeyReceived += (sender, e) => hostKeyEventsArgs = e;
-                client.Connect();
-                client.Disconnect();
-            }
+        [TestMethod]
+        public void SshRsaSha256()
+        {
+            DoTest(HostKeyAlgorithm.RsaSha2256, HostKeyFile.Rsa, 3072);
+        }
 
-            Assert.IsNotNull(hostKeyEventsArgs);
-            Assert.AreEqual(HostKeyFile.Rsa.KeyName, hostKeyEventsArgs.HostKeyName);
-            Assert.AreEqual(3072, hostKeyEventsArgs.KeyLength);
-            Assert.IsTrue(hostKeyEventsArgs.FingerPrint.SequenceEqual(HostKeyFile.Rsa.FingerPrint));
+        [TestMethod]
+        public void SshRsaSha512()
+        {
+            DoTest(HostKeyAlgorithm.RsaSha2512, HostKeyFile.Rsa, 3072);
         }
 
         [TestMethod]
         public void SshEd25519()
+        {
+            DoTest(HostKeyAlgorithm.SshEd25519, HostKeyFile.Ed25519, 256);
+        }
+
+        private void DoTest(HostKeyAlgorithm hostKeyAlgorithm, HostKeyFile hostKeyFile, int keyLength)
         {
             _remoteSshdConfig.ClearHostKeyAlgorithms()
-                             .AddHostKeyAlgorithm(HostKeyAlgorithm.SshEd25519)
+                             .AddHostKeyAlgorithm(hostKeyAlgorithm)
                              .ClearHostKeyFiles()
-                             .AddHostKeyFile(HostKeyFile.Ed25519.FilePath)
+                             .AddHostKeyFile(hostKeyFile.FilePath)
                              .Update()
                              .Restart();
 
@@ -92,14 +75,9 @@ namespace Renci.SshNet.IntegrationTests
             }
 
             Assert.IsNotNull(hostKeyEventsArgs);
-            Assert.AreEqual(HostKeyFile.Ed25519.KeyName, hostKeyEventsArgs.HostKeyName);
-            Assert.AreEqual(256, hostKeyEventsArgs.KeyLength);
-            Assert.IsTrue(hostKeyEventsArgs.FingerPrint.SequenceEqual(HostKeyFile.Ed25519.FingerPrint));
-        }
-
-        private void Client_HostKeyReceived(object sender, HostKeyEventArgs e)
-        {
-            throw new NotImplementedException();
+            Assert.AreEqual(hostKeyAlgorithm.Name, hostKeyEventsArgs.HostKeyName);
+            Assert.AreEqual(keyLength, hostKeyEventsArgs.KeyLength);
+            CollectionAssert.AreEqual(hostKeyFile.FingerPrint, hostKeyEventsArgs.FingerPrint);
         }
     }
 }

+ 43 - 22
src/Renci.SshNet.IntegrationTests/PrivateKeyAuthenticationTests.cs

@@ -5,7 +5,7 @@ namespace Renci.SshNet.IntegrationTests
 {
     [TestClass]
     public class PrivateKeyAuthenticationTests : TestBase
-        {
+    {
         private IConnectionInfoFactory _connectionInfoFactory;
         private RemoteSshdConfig _remoteSshdConfig;
 
@@ -23,43 +23,64 @@ namespace Renci.SshNet.IntegrationTests
         }
 
         [TestMethod]
-        public void Ecdsa256()
+        [Ignore] // No longer supported in recent versions of OpenSSH
+        // TODO: We should be able to enable some legacy settings to make it work
+        // https://www.openssh.com/legacy.html e.g. PubkeyAcceptedKeyTypes / HostbasedAcceptedKeyTypes ?
+        public void SshDsa()
         {
-            _remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp256)
-                             .Update()
-                             .Restart();
+            DoTest(PublicKeyAlgorithm.SshDss, "id_dsa");
+        }
 
-            var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod("key_ecdsa_256_openssh"));
+        [TestMethod]
+        public void SshRsa()
+        {
+            DoTest(PublicKeyAlgorithm.SshRsa, "id_rsa");
+        }
 
-            using (var client = new SshClient(connectionInfo))
-            {
-                client.Connect();
-            }
+        [TestMethod]
+        public void SshRsaSha256()
+        {
+            DoTest(PublicKeyAlgorithm.RsaSha2256, "id_rsa");
         }
 
         [TestMethod]
-        public void Ecdsa384()
+        public void SshRsaSha512()
         {
-            _remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp384)
-                             .Update()
-                             .Restart();
+            DoTest(PublicKeyAlgorithm.RsaSha2512, "id_rsa");
+        }
 
-            var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod("key_ecdsa_384_openssh"));
+        [TestMethod]
+        public void Ecdsa256()
+        {
+            DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp256, "key_ecdsa_256_openssh");
+        }
 
-            using (var client = new SshClient(connectionInfo))
-            {
-                client.Connect();
-            }
+        [TestMethod]
+        public void Ecdsa384()
+        {
+            DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp384, "key_ecdsa_384_openssh");
         }
 
         [TestMethod]
-        public void EcdsaA521()
+        public void Ecdsa521()
+        {
+            DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp521, "key_ecdsa_521_openssh");
+        }
+
+        [TestMethod]
+        public void Ed25519()
+        {
+            DoTest(PublicKeyAlgorithm.SshEd25519, "key_ed25519_openssh");
+        }
+
+        private void DoTest(PublicKeyAlgorithm publicKeyAlgorithm, string keyResource)
         {
-            _remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp521)
+            _remoteSshdConfig.ClearPublicKeyAcceptedAlgorithms()
+                             .AddPublicKeyAcceptedAlgorithms(publicKeyAlgorithm)
                              .Update()
                              .Restart();
 
-            var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod("key_ecdsa_521_openssh"));
+            var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod(keyResource));
 
             using (var client = new SshClient(connectionInfo))
             {

+ 1 - 0
src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj

@@ -60,6 +60,7 @@
     <EmbeddedResource Include="resources\client\key_ecdsa_256_openssh" />
     <EmbeddedResource Include="resources\client\key_ecdsa_384_openssh" />
     <EmbeddedResource Include="resources\client\key_ecdsa_521_openssh" />
+    <EmbeddedResource Include="resources\client\key_ed25519_openssh" />
     <EmbeddedResource Include="resources\issue #70.png" />
   </ItemGroup>
 </Project>

+ 2 - 1
src/Renci.SshNet.IntegrationTests/user/sshnet/authorized_keys

@@ -1,4 +1,5 @@
 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4N0T6VopnwPSYQUw0waQNhBjz0DYFQwvkv4OwWYSf//fJF3M6bH42Tn2J+IlQ+4/fCFnE3m99seV5T1myEj7fsupNteY2sKFGXENLGtAD/76FM0iBmXx76xlSTyZSSmNDIRU4xUR23cfc+al84F5mO2lEk+5Zr3Qn5JUpucBfis4vtu0sMDgZ4w1d0tcuXkT/MEJn2iX2cnxbSy5qNAPHu7b+LEfXBv2OrMDqPrx/X6QREgi3w5RxL5kz7bvitWsIwIvb3ST2ARAArBwb8pEyp2A/w5p22rkQtL+3ibZ8fkmpgn33f31AZPgtM++iJPBmPKFjArcWEJ9fIVB/6DAj
 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPzzrPpItEjNG7tU0DpJJ4pkI01E9d6K61OKTVPdFQSyGCdMj9XdP93lC6sJA+9/ahvf5F3gWEKxUJL2CKUiFWw=
 ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLSsu/HNKiaALhQ26UDv+N0AFdMb26fMVrOKe866CGu6ajSf9HUOhJFdjhseihB2rTalMPr8MrcXNLufii4mL8u4l9fUQXFgwnM/ZpiVPSs6C+8i4u/ZDg7Nx2NXybNIgQ==
-ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACB4WgRgGBRo6Uk+cRgg8tJPCbEtGURRWlUA7PDDerXR+P9O6mm3L99Etxsyh5XNYqXyaMNtH5c51ooMajrFwcayAHIhPPb8X3CsTwEfIUQ96aDyHQMotbRfnkn6uefeUTRrSNcqeAndUtVyAqBdqbsq2mgJYXHrz2NUKlPFYgauQi+WQ==
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACB4WgRgGBRo6Uk+cRgg8tJPCbEtGURRWlUA7PDDerXR+P9O6mm3L99Etxsyh5XNYqXyaMNtH5c51ooMajrFwcayAHIhPPb8X3CsTwEfIUQ96aDyHQMotbRfnkn6uefeUTRrSNcqeAndUtVyAqBdqbsq2mgJYXHrz2NUKlPFYgauQi+WQ==
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAkNGPVOTuzuKTgGfHcve2MRj57yXhmZgkUyi9RpmJrl

+ 5 - 2
src/Renci.SshNet.TestTools.OpenSSH/SshdConfig.cs

@@ -211,8 +211,11 @@ namespace Renci.SshNet.TestTools.OpenSSH
                 writer.WriteLine("MACs " + string.Join(",", MessageAuthenticationCodeAlgorithms.Select(c => c.Name).ToArray()));
             }
 
-            writer.WriteLine("PubkeyAcceptedAlgorithms " + string.Join(",", PublicKeyAcceptedAlgorithms.Select(c => c.Name).ToArray()));
-
+            if (PublicKeyAcceptedAlgorithms.Count > 0)
+            {
+                writer.WriteLine("PubkeyAcceptedAlgorithms " + string.Join(",", PublicKeyAcceptedAlgorithms.Select(c => c.Name).ToArray()));
+            }
+            
             foreach (var match in Matches)
             {
                 _matchFormatter.Format(match, writer);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Common/HostKeyEventArgsTest.cs

@@ -42,7 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Common
                 0xa0, 0x23, 0xaf, 0xff, 0x9c, 0x0f, 0x8c, 0x83, 0x7c, 0xf8, 0xe1, 0x8e, 0x32, 0x8e, 0x61, 0xfc,
                 0x5b, 0xbd, 0xd4, 0x46, 0xe1
             }.SequenceEqual(target.HostKey));
-            Assert.AreEqual("ssh-rsa", target.HostKeyName);
+            Assert.AreEqual("rsa-sha2-512", target.HostKeyName);
             Assert.AreEqual(2048, target.KeyLength);
         }
 

+ 26 - 10
src/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs

@@ -1,8 +1,11 @@
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Renci.SshNet.Common;
+using Renci.SshNet.Security;
 using Renci.SshNet.Tests.Common;
 using System;
+using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 
 namespace Renci.SshNet.Tests.Classes
 {
@@ -144,7 +147,7 @@ namespace Renci.SshNet.Tests.Classes
         {
             using (var stream = GetData("Key.RSA.txt"))
             {
-                _ = new PrivateKeyFile(stream);
+                TestRsaKeyFile(new PrivateKeyFile(stream));
             }
         }
 
@@ -166,7 +169,7 @@ namespace Renci.SshNet.Tests.Classes
         {
             using (var stream = GetData("Key.SSH2.RSA.txt"))
             {
-                _ = new PrivateKeyFile(stream);
+                TestRsaKeyFile(new PrivateKeyFile(stream));
             }
         }
 
@@ -188,7 +191,7 @@ namespace Renci.SshNet.Tests.Classes
         {
             using (var stream = GetData("Key.SSH2.RSA.Encrypted.Des.CBC.12345.txt"))
             {
-                _ = new PrivateKeyFile(stream, "12345");
+                TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
             }
         }
 
@@ -262,7 +265,7 @@ namespace Renci.SshNet.Tests.Classes
         {
             using (var stream = GetData("Key.RSA.Encrypted.Des.CBC.12345.txt"))
             {
-                _ = new PrivateKeyFile(stream, "12345");
+                TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
             }
         }
 
@@ -284,7 +287,7 @@ namespace Renci.SshNet.Tests.Classes
         {
             using (var stream = GetData("Key.RSA.Encrypted.Aes.128.CBC.12345.txt"))
             {
-                _ = new PrivateKeyFile(stream, "12345");
+                TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
             }
         }
 
@@ -295,7 +298,7 @@ namespace Renci.SshNet.Tests.Classes
         {
             using (var stream = GetData("Key.RSA.Encrypted.Aes.192.CBC.12345.txt"))
             {
-                _ = new PrivateKeyFile(stream, "12345");
+                TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
             }
         }
 
@@ -306,7 +309,7 @@ namespace Renci.SshNet.Tests.Classes
         {
             using (var stream = GetData("Key.RSA.Encrypted.Aes.256.CBC.12345.txt"))
             {
-                _ = new PrivateKeyFile(stream, "12345");
+                TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
             }
         }
 
@@ -317,7 +320,7 @@ namespace Renci.SshNet.Tests.Classes
         {
             using (var stream = GetData("Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt"))
             {
-                _ = new PrivateKeyFile(stream, "1234567890");
+                TestRsaKeyFile(new PrivateKeyFile(stream, "1234567890"));
             }
         }
 
@@ -576,7 +579,7 @@ namespace Renci.SshNet.Tests.Classes
         {
             using (var stream = GetData("Key.OPENSSH.RSA.txt"))
             {
-                _ = new PrivateKeyFile(stream);
+                TestRsaKeyFile(new PrivateKeyFile(stream));
             }
         }
 
@@ -587,7 +590,7 @@ namespace Renci.SshNet.Tests.Classes
         {
             using (var stream = GetData("Key.OPENSSH.RSA.Encrypted.txt"))
             {
-                _ = new PrivateKeyFile(stream, "12345");
+                TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
             }
         }
 
@@ -678,5 +681,18 @@ namespace Renci.SshNet.Tests.Classes
             File.Delete(tempFile);
             return tempFile;
         }
+
+        private static void TestRsaKeyFile(PrivateKeyFile rsaPrivateKeyFile)
+        {
+            Assert.AreEqual(3, rsaPrivateKeyFile.HostAlgorithms.Count);
+
+            List<KeyHostAlgorithm> algorithms = rsaPrivateKeyFile.HostAlgorithms.Cast<KeyHostAlgorithm>().ToList();
+
+            Assert.AreEqual("rsa-sha2-512", algorithms[0].Name);
+            Assert.AreEqual("rsa-sha2-256", algorithms[1].Name);
+            Assert.AreEqual("ssh-rsa", algorithms[2].Name);
+
+            Assert.AreSame(algorithms[0], rsaPrivateKeyFile.HostKey);
+        }
     }
 }

+ 154 - 10
src/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs

@@ -1,4 +1,9 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Security.Cryptography;
+using System.Text;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
 using Renci.SshNet.Security;
 using Renci.SshNet.Security.Cryptography;
 using Renci.SshNet.Tests.Common;
@@ -11,17 +16,156 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
     [TestClass]
     public class RsaDigitalSignatureTest : TestBase
     {
+        [TestMethod]
+        public void Sha1_SignAndVerify()
+        {
+            byte[] data = Encoding.UTF8.GetBytes("hello world");
+
+            RsaKey rsaKey = GetRsaKey();
+
+            var digitalSignature = new RsaDigitalSignature(rsaKey); // Verify SHA-1 is the default
+
+            byte[] signedBytes = digitalSignature.Sign(data);
+
+            byte[] expectedSignedBytes = new byte[]
+            {
+                // echo -n 'hello world' | openssl dgst -sha1 -sign Key.RSA.txt -out test.signed
+                0x41, 0x50, 0x12, 0x14, 0xd3, 0x7c, 0xe0, 0x40, 0x50, 0x65, 0xfb, 0x33, 0xd9, 0x17, 0x89, 0xbf,
+                0xb2, 0x4b, 0x85, 0x15, 0xbf, 0x9e, 0x57, 0x3b, 0x01, 0x15, 0x2b, 0x99, 0xfa, 0x62, 0x9b, 0x2a,
+                0x05, 0xa0, 0x73, 0xc7, 0xb7, 0x5b, 0xd9, 0x01, 0xaa, 0x56, 0x73, 0x95, 0x13, 0x41, 0x33, 0x0d,
+                0x7f, 0x83, 0x8a, 0x60, 0x4d, 0x19, 0xdc, 0x9b, 0xba, 0x8e, 0x61, 0xed, 0xd0, 0x8a, 0x3e, 0x38,
+                0x71, 0xee, 0x34, 0xc3, 0x55, 0x0f, 0x55, 0x65, 0x89, 0xbb, 0x3e, 0x41, 0xee, 0xdf, 0xf5, 0x2f,
+                0xab, 0x9e, 0x89, 0x37, 0x68, 0x1f, 0x9f, 0x38, 0x00, 0x81, 0x29, 0x93, 0xeb, 0x61, 0x37, 0xad,
+                0x8d, 0x35, 0xf1, 0x3d, 0x4b, 0x9b, 0x99, 0x74, 0x7b, 0xeb, 0xf4, 0xfb, 0x76, 0xb4, 0xb6, 0xb4,
+                0x09, 0x33, 0x5c, 0xfa, 0x6a, 0xad, 0x1e, 0xed, 0x1c, 0xe1, 0xb4, 0x4d, 0xf2, 0xa5, 0xc3, 0x64,
+                0x9a, 0x45, 0x81, 0xee, 0x1b, 0xa6, 0x1d, 0x01, 0x3c, 0x4d, 0xb5, 0x62, 0x9e, 0xff, 0x8e, 0xff,
+                0x6c, 0x18, 0xed, 0xe9, 0x8e, 0x03, 0x2c, 0xc5, 0x94, 0x81, 0xca, 0x8b, 0x18, 0x3f, 0x25, 0xcd,
+                0xe5, 0x42, 0x49, 0x43, 0x23, 0x1f, 0xdc, 0x3f, 0xa2, 0x43, 0xbc, 0xbd, 0x42, 0xf5, 0x60, 0xfb,
+                0x01, 0xd3, 0x67, 0x0d, 0x8d, 0x85, 0x7b, 0x51, 0x14, 0xec, 0x26, 0x53, 0x00, 0x61, 0x25, 0x16,
+                0x19, 0x10, 0x3c, 0x86, 0x16, 0x59, 0x84, 0x08, 0xd1, 0xf9, 0x1e, 0x05, 0x88, 0xbd, 0x4a, 0x01,
+                0x43, 0x4e, 0xec, 0x76, 0x0b, 0xd7, 0x2c, 0xe9, 0x98, 0xb1, 0x4c, 0x0a, 0x13, 0xc6, 0x95, 0xf9,
+                0x8f, 0x95, 0x5c, 0x98, 0x4c, 0x8f, 0x97, 0x4a, 0xad, 0x0d, 0xfe, 0x84, 0xf0, 0x56, 0xc3, 0x29,
+                0x73, 0x75, 0x55, 0x3c, 0xd9, 0x5e, 0x5b, 0x6f, 0xf9, 0x81, 0xbc, 0xbc, 0x50, 0x75, 0x7d, 0xa8
+            };
+
+            CollectionAssert.AreEqual(expectedSignedBytes, signedBytes);
+
+            // Also verify RsaKey uses SHA-1 by default
+            CollectionAssert.AreEqual(expectedSignedBytes, rsaKey.Sign(data));
+
+            // The following fails due to the _isPrivate decision in RsaCipher.Transform. Is that really correct?
+            //Assert.IsTrue(digitalSignature.Verify(data, signedBytes));
+
+            // 'Workaround': use a key with no private key information
+            var digitalSignaturePublic = new RsaDigitalSignature(new RsaKey()
+            {
+                Public = rsaKey.Public
+            });
+            Assert.IsTrue(digitalSignaturePublic.Verify(data, signedBytes));
+        }
 
-        /// <summary>
-        ///A test for RsaDigitalSignature Constructor
-        ///</summary>
         [TestMethod]
-        [Ignore] // placeholder for actual test
-        public void RsaDigitalSignatureConstructorTest()
+        public void Sha256_SignAndVerify()
         {
-            RsaKey rsaKey = null; // TODO: Initialize to an appropriate value
-            RsaDigitalSignature target = new RsaDigitalSignature(rsaKey);
-            Assert.Inconclusive("TODO: Implement code to verify target");
+            byte[] data = Encoding.UTF8.GetBytes("hello world");
+
+            RsaKey rsaKey = GetRsaKey();
+
+            var digitalSignature = new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA256);
+
+            byte[] signedBytes = digitalSignature.Sign(data);
+
+            CollectionAssert.AreEqual(new byte[]
+            {
+                // echo -n 'hello world' | openssl dgst -sha256 -sign Key.RSA.txt -out test.signed
+                0x2e, 0xef, 0x01, 0x49, 0x5c, 0x66, 0x37, 0x56, 0xc2, 0xfb, 0x7b, 0xfa, 0x80, 0x2f, 0xdb, 0xaa,
+                0x0d, 0x15, 0xd9, 0x8d, 0xa9, 0xad, 0x81, 0x4f, 0x09, 0x2e, 0x53, 0x9e, 0xce, 0x5d, 0x68, 0x07,
+                0xae, 0xb9, 0xc0, 0x45, 0xfa, 0x30, 0xd0, 0xf7, 0xd6, 0xa6, 0x8d, 0x19, 0x24, 0x3a, 0xea, 0x91,
+                0x3e, 0xa2, 0x4a, 0x42, 0x2e, 0x21, 0xf1, 0x48, 0x57, 0xca, 0x2b, 0x6c, 0x9f, 0x79, 0x54, 0x91,
+                0x3e, 0x3a, 0x4d, 0xd1, 0x70, 0x87, 0x3d, 0xbe, 0x22, 0x97, 0xc9, 0xb0, 0x02, 0xf0, 0xa2, 0xae,
+                0x7a, 0xbb, 0x8b, 0xaf, 0xc0, 0x3b, 0xab, 0x71, 0xe8, 0x29, 0x1c, 0x18, 0x88, 0xca, 0x74, 0x1b,
+                0x34, 0x4f, 0xd1, 0x83, 0x39, 0x6e, 0x8f, 0x69, 0x3d, 0x7e, 0xef, 0xef, 0x57, 0x7c, 0xff, 0x21,
+                0x9c, 0x10, 0x2b, 0xd1, 0x4f, 0x26, 0xbe, 0xaa, 0xd2, 0xd9, 0x03, 0x14, 0x75, 0x97, 0x11, 0xaf,
+                0xf0, 0x28, 0xf2, 0xd3, 0x07, 0x79, 0x5b, 0x27, 0xdc, 0x97, 0xd8, 0xce, 0x4e, 0x78, 0x89, 0x16,
+                0x91, 0x2a, 0xb2, 0x47, 0x53, 0x94, 0xe9, 0xa1, 0x15, 0x98, 0x29, 0x0c, 0xa1, 0xf5, 0xe2, 0x8e,
+                0x11, 0xdc, 0x0c, 0x1c, 0x10, 0xa4, 0xf2, 0x46, 0x5c, 0x78, 0x0c, 0xc1, 0x4a, 0x65, 0x21, 0x8a,
+                0x2e, 0x32, 0x6c, 0x72, 0x06, 0xf9, 0x7f, 0xa1, 0x6c, 0x2e, 0x13, 0x06, 0x41, 0xaa, 0x23, 0xdd,
+                0xc8, 0x1c, 0x61, 0xb6, 0x96, 0x87, 0xc4, 0x84, 0xc8, 0x61, 0xec, 0x4e, 0xdd, 0x49, 0x9e, 0x4f,
+                0x0d, 0x8c, 0xf1, 0x7f, 0xf2, 0x6c, 0x73, 0x5a, 0xa6, 0x3b, 0xbf, 0x4e, 0xba, 0x57, 0x6b, 0xb3,
+                0x1e, 0x6c, 0x57, 0x76, 0x87, 0x9f, 0xb4, 0x3b, 0xcb, 0xcd, 0xe5, 0x10, 0x7a, 0x4c, 0xeb, 0xc0,
+                0xc4, 0xc3, 0x75, 0x51, 0x5f, 0xb7, 0x7c, 0xbc, 0x55, 0x8d, 0x05, 0xc7, 0xed, 0xc7, 0x52, 0x4a
+            }, signedBytes);
+
+
+            // The following fails due to the _isPrivate decision in RsaCipher.Transform. Is that really correct?
+            //Assert.IsTrue(digitalSignature.Verify(data, signedBytes));
+
+            // 'Workaround': use a key with no private key information
+            var digitalSignaturePublic = new RsaDigitalSignature(new RsaKey()
+            {
+                Public = rsaKey.Public
+            }, HashAlgorithmName.SHA256);
+            Assert.IsTrue(digitalSignaturePublic.Verify(data, signedBytes));
+        }
+
+        [TestMethod]
+        public void Sha512_SignAndVerify()
+        {
+            byte[] data = Encoding.UTF8.GetBytes("hello world");
+
+            RsaKey rsaKey = GetRsaKey();
+
+            var digitalSignature = new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA512);
+
+            byte[] signedBytes = digitalSignature.Sign(data);
+
+            CollectionAssert.AreEqual(new byte[]
+            {
+                // echo -n 'hello world' | openssl dgst -sha512 -sign Key.RSA.txt -out test.signed
+                0x69, 0x70, 0xb5, 0x9f, 0x32, 0x86, 0x3b, 0xae, 0xc0, 0x79, 0x6e, 0xdb, 0x35, 0xd5, 0xa6, 0x22,
+                0xcd, 0x2b, 0x4b, 0xd2, 0x68, 0x1a, 0x65, 0x41, 0xa6, 0xd9, 0x20, 0x54, 0x31, 0x9a, 0xb1, 0x44,
+                0x6e, 0x8f, 0x56, 0x4b, 0xfc, 0x27, 0x7f, 0x3f, 0xe7, 0x47, 0xcb, 0x78, 0x03, 0x05, 0x79, 0x8a,
+                0x16, 0x7b, 0x12, 0x01, 0x3a, 0xa2, 0xd5, 0x0d, 0x2b, 0x16, 0x38, 0xef, 0x84, 0x6b, 0xd7, 0x19,
+                0xeb, 0xac, 0x54, 0x01, 0x9d, 0xa6, 0x80, 0x74, 0x43, 0xa8, 0x6e, 0x5e, 0x33, 0x05, 0x06, 0x1d,
+                0x6d, 0xfe, 0x32, 0x4f, 0xe3, 0xcb, 0x3e, 0x2d, 0x4e, 0xe1, 0x47, 0x03, 0x69, 0xb4, 0x59, 0x80,
+                0x59, 0x05, 0x15, 0xa0, 0x11, 0x34, 0x47, 0x58, 0xd7, 0x93, 0x2d, 0x40, 0xf2, 0x2c, 0x37, 0x48,
+                0x6b, 0x3c, 0xd3, 0x03, 0x09, 0x32, 0x74, 0xa0, 0x2d, 0x33, 0x11, 0x99, 0x10, 0xb4, 0x09, 0x31,
+                0xec, 0xa3, 0x2c, 0x63, 0xba, 0x50, 0xd1, 0x02, 0x45, 0xae, 0xb5, 0x75, 0x7e, 0xfa, 0xfc, 0x06,
+                0xb6, 0x6a, 0xb2, 0xa1, 0x73, 0x14, 0xa5, 0xaa, 0x17, 0x88, 0x03, 0x19, 0x14, 0x9b, 0xe1, 0x10,
+                0xf8, 0x2f, 0x73, 0x01, 0xc7, 0x8d, 0x37, 0xef, 0x98, 0x69, 0xc2, 0xe2, 0x7a, 0x11, 0xd5, 0xb8,
+                0xc9, 0x35, 0x45, 0xcb, 0x56, 0x4b, 0x92, 0x4a, 0xe0, 0x4c, 0xd6, 0x82, 0xae, 0xad, 0x5b, 0xe9,
+                0x40, 0x7e, 0x2a, 0x48, 0x7d, 0x57, 0xc5, 0xfd, 0xe9, 0x98, 0xe0, 0xbb, 0x09, 0xa1, 0xf5, 0x48,
+                0x45, 0xcb, 0xee, 0xb9, 0x99, 0x81, 0x44, 0x15, 0x2e, 0x50, 0x39, 0x64, 0x58, 0x4c, 0x34, 0x86,
+                0xf8, 0x81, 0x9e, 0x1d, 0xb6, 0x97, 0xe0, 0xce, 0x16, 0xca, 0x20, 0x46, 0xe9, 0x49, 0x8f, 0xe6,
+                0xa0, 0x23, 0x08, 0x80, 0xa6, 0x37, 0x70, 0x06, 0xcc, 0x8f, 0xf4, 0xa0, 0x74, 0x53, 0x26, 0x38
+            }, signedBytes);
+
+            // The following fails due to the _isPrivate decision in RsaCipher.Transform. Is that really correct?
+            //Assert.IsTrue(digitalSignature.Verify(data, signedBytes));
+
+            // 'Workaround': use a key with no private key information
+            var digitalSignaturePublic = new RsaDigitalSignature(new RsaKey()
+            {
+                Public = rsaKey.Public
+            }, HashAlgorithmName.SHA512);
+            Assert.IsTrue(digitalSignaturePublic.Verify(data, signedBytes));
+        }
+
+        [TestMethod]
+        public void Constructor_InvalidHashAlgorithm_ThrowsArgumentException()
+        {
+            ArgumentException exception = Assert.ThrowsException<ArgumentException>(
+                () => new RsaDigitalSignature(new RsaKey(), new HashAlgorithmName("invalid")));
+
+            Assert.AreEqual("hashAlgorithmName", exception.ParamName);
+        }
+
+        private static RsaKey GetRsaKey()
+        {
+            using (var stream = GetData("Key.RSA.txt"))
+            {
+                return (RsaKey) ((KeyHostAlgorithm) new PrivateKeyFile(stream).HostKey).Key;
+            }
         }
 
         /// <summary>
@@ -37,4 +181,4 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
             Assert.Inconclusive("A method that does not return a value cannot be verified.");
         }
     }
-}
+}

+ 215 - 0
src/Renci.SshNet.Tests/Classes/Security/KeyAlgorithmTest.cs

@@ -0,0 +1,215 @@
+using System.Security.Cryptography;
+using System.Text;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Renci.SshNet.Security;
+using Renci.SshNet.Security.Cryptography;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes.Security
+{
+    [TestClass]
+    public class KeyHostAlgorithmTest : TestBase
+    {
+        [TestMethod]
+        public void NoSuppliedDigitalSignature_PropertyIsKeyDigitalSignature()
+        {
+            RsaKey rsaKey = GetRsaKey();
+
+            KeyHostAlgorithm keyHostAlgorithm = new KeyHostAlgorithm("ssh-rsa", rsaKey);
+
+            Assert.AreEqual("ssh-rsa", keyHostAlgorithm.Name);
+            Assert.AreSame(rsaKey, keyHostAlgorithm.Key);
+            Assert.AreSame(rsaKey.DigitalSignature, keyHostAlgorithm.DigitalSignature);
+        }
+
+        [TestMethod]
+        public void SuppliedDigitalSignature_PropertyIsSuppliedDigitalSignature()
+        {
+            RsaKey rsaKey = GetRsaKey();
+            RsaDigitalSignature rsaDigitalSignature = new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA256);
+            KeyHostAlgorithm keyHostAlgorithm = new KeyHostAlgorithm("rsa-sha2-256", rsaKey, rsaDigitalSignature);
+
+            Assert.AreEqual("rsa-sha2-256", keyHostAlgorithm.Name);
+            Assert.AreSame(rsaKey, keyHostAlgorithm.Key);
+            Assert.AreSame(rsaDigitalSignature, keyHostAlgorithm.DigitalSignature);
+        }
+
+        [TestMethod]
+        public void RsaPublicKeyDataDoesNotDependOnSignatureAlgorithm()
+        {
+            TestRsaPublicKeyData("ssh-rsa", HashAlgorithmName.SHA1);
+            TestRsaPublicKeyData("rsa-sha2-256", HashAlgorithmName.SHA256);
+        }
+
+        private void TestRsaPublicKeyData(string signatureIdentifier, HashAlgorithmName hashAlgorithmName)
+        {
+            RsaKey key = GetRsaKey();
+            KeyHostAlgorithm keyAlgorithm = new KeyHostAlgorithm(signatureIdentifier, key, new RsaDigitalSignature(key, hashAlgorithmName));
+
+            CollectionAssert.AreEqual(GetRsaPublicKeyBytes(), keyAlgorithm.Data);
+        }
+
+        [TestMethod]
+        public void SshRsa_SignAndVerify()
+        {
+            byte[] data = Encoding.UTF8.GetBytes("hello world");
+
+            RsaKey key = GetRsaKey();
+            KeyHostAlgorithm keyAlgorithm = new KeyHostAlgorithm("ssh-rsa", key);
+
+            byte[] expectedEncodedSignatureBytes = new byte[]
+            {
+                0, 0, 0, 7, // byte count of "ssh-rsa"
+                (byte)'s', (byte)'s', (byte)'h', (byte)'-', (byte)'r', (byte)'s', (byte)'a', // ssh-rsa
+                0, 0, 1, 0, // byte count of signature (=256)
+                
+                // echo -n 'hello world' | openssl dgst -sha1 -sign Key.RSA.txt -out test.signed
+                0x41, 0x50, 0x12, 0x14, 0xd3, 0x7c, 0xe0, 0x40, 0x50, 0x65, 0xfb, 0x33, 0xd9, 0x17, 0x89, 0xbf,
+                0xb2, 0x4b, 0x85, 0x15, 0xbf, 0x9e, 0x57, 0x3b, 0x01, 0x15, 0x2b, 0x99, 0xfa, 0x62, 0x9b, 0x2a,
+                0x05, 0xa0, 0x73, 0xc7, 0xb7, 0x5b, 0xd9, 0x01, 0xaa, 0x56, 0x73, 0x95, 0x13, 0x41, 0x33, 0x0d,
+                0x7f, 0x83, 0x8a, 0x60, 0x4d, 0x19, 0xdc, 0x9b, 0xba, 0x8e, 0x61, 0xed, 0xd0, 0x8a, 0x3e, 0x38,
+                0x71, 0xee, 0x34, 0xc3, 0x55, 0x0f, 0x55, 0x65, 0x89, 0xbb, 0x3e, 0x41, 0xee, 0xdf, 0xf5, 0x2f,
+                0xab, 0x9e, 0x89, 0x37, 0x68, 0x1f, 0x9f, 0x38, 0x00, 0x81, 0x29, 0x93, 0xeb, 0x61, 0x37, 0xad,
+                0x8d, 0x35, 0xf1, 0x3d, 0x4b, 0x9b, 0x99, 0x74, 0x7b, 0xeb, 0xf4, 0xfb, 0x76, 0xb4, 0xb6, 0xb4,
+                0x09, 0x33, 0x5c, 0xfa, 0x6a, 0xad, 0x1e, 0xed, 0x1c, 0xe1, 0xb4, 0x4d, 0xf2, 0xa5, 0xc3, 0x64,
+                0x9a, 0x45, 0x81, 0xee, 0x1b, 0xa6, 0x1d, 0x01, 0x3c, 0x4d, 0xb5, 0x62, 0x9e, 0xff, 0x8e, 0xff,
+                0x6c, 0x18, 0xed, 0xe9, 0x8e, 0x03, 0x2c, 0xc5, 0x94, 0x81, 0xca, 0x8b, 0x18, 0x3f, 0x25, 0xcd,
+                0xe5, 0x42, 0x49, 0x43, 0x23, 0x1f, 0xdc, 0x3f, 0xa2, 0x43, 0xbc, 0xbd, 0x42, 0xf5, 0x60, 0xfb,
+                0x01, 0xd3, 0x67, 0x0d, 0x8d, 0x85, 0x7b, 0x51, 0x14, 0xec, 0x26, 0x53, 0x00, 0x61, 0x25, 0x16,
+                0x19, 0x10, 0x3c, 0x86, 0x16, 0x59, 0x84, 0x08, 0xd1, 0xf9, 0x1e, 0x05, 0x88, 0xbd, 0x4a, 0x01,
+                0x43, 0x4e, 0xec, 0x76, 0x0b, 0xd7, 0x2c, 0xe9, 0x98, 0xb1, 0x4c, 0x0a, 0x13, 0xc6, 0x95, 0xf9,
+                0x8f, 0x95, 0x5c, 0x98, 0x4c, 0x8f, 0x97, 0x4a, 0xad, 0x0d, 0xfe, 0x84, 0xf0, 0x56, 0xc3, 0x29,
+                0x73, 0x75, 0x55, 0x3c, 0xd9, 0x5e, 0x5b, 0x6f, 0xf9, 0x81, 0xbc, 0xbc, 0x50, 0x75, 0x7d, 0xa8
+            };
+
+            CollectionAssert.AreEqual(expectedEncodedSignatureBytes, keyAlgorithm.Sign(data));
+
+            keyAlgorithm = new KeyHostAlgorithm("ssh-rsa", new RsaKey(), GetRsaPublicKeyBytes());
+            Assert.IsTrue(keyAlgorithm.VerifySignature(data, expectedEncodedSignatureBytes));
+        }
+
+        [TestMethod]
+        public void RsaSha256_SignAndVerify()
+        {
+            byte[] data = Encoding.UTF8.GetBytes("hello world");
+
+            RsaKey key = GetRsaKey();
+            KeyHostAlgorithm keyAlgorithm = new KeyHostAlgorithm("rsa-sha2-256", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA256));
+
+            byte[] expectedEncodedSignatureBytes = new byte[]
+            {
+                0, 0, 0, 12, // byte count of "rsa-sha2-256"
+                (byte)'r', (byte)'s', (byte)'a', (byte)'-', (byte)'s', (byte)'h', (byte)'a', (byte)'2',
+                (byte)'-', (byte)'2', (byte)'5', (byte)'6',
+                0, 0, 1, 0, // byte count of signature (=256)
+                
+                // echo -n 'hello world' | openssl dgst -sha256 -sign Key.RSA.txt -out test.signed
+                0x2e, 0xef, 0x01, 0x49, 0x5c, 0x66, 0x37, 0x56, 0xc2, 0xfb, 0x7b, 0xfa, 0x80, 0x2f, 0xdb, 0xaa,
+                0x0d, 0x15, 0xd9, 0x8d, 0xa9, 0xad, 0x81, 0x4f, 0x09, 0x2e, 0x53, 0x9e, 0xce, 0x5d, 0x68, 0x07,
+                0xae, 0xb9, 0xc0, 0x45, 0xfa, 0x30, 0xd0, 0xf7, 0xd6, 0xa6, 0x8d, 0x19, 0x24, 0x3a, 0xea, 0x91,
+                0x3e, 0xa2, 0x4a, 0x42, 0x2e, 0x21, 0xf1, 0x48, 0x57, 0xca, 0x2b, 0x6c, 0x9f, 0x79, 0x54, 0x91,
+                0x3e, 0x3a, 0x4d, 0xd1, 0x70, 0x87, 0x3d, 0xbe, 0x22, 0x97, 0xc9, 0xb0, 0x02, 0xf0, 0xa2, 0xae,
+                0x7a, 0xbb, 0x8b, 0xaf, 0xc0, 0x3b, 0xab, 0x71, 0xe8, 0x29, 0x1c, 0x18, 0x88, 0xca, 0x74, 0x1b,
+                0x34, 0x4f, 0xd1, 0x83, 0x39, 0x6e, 0x8f, 0x69, 0x3d, 0x7e, 0xef, 0xef, 0x57, 0x7c, 0xff, 0x21,
+                0x9c, 0x10, 0x2b, 0xd1, 0x4f, 0x26, 0xbe, 0xaa, 0xd2, 0xd9, 0x03, 0x14, 0x75, 0x97, 0x11, 0xaf,
+                0xf0, 0x28, 0xf2, 0xd3, 0x07, 0x79, 0x5b, 0x27, 0xdc, 0x97, 0xd8, 0xce, 0x4e, 0x78, 0x89, 0x16,
+                0x91, 0x2a, 0xb2, 0x47, 0x53, 0x94, 0xe9, 0xa1, 0x15, 0x98, 0x29, 0x0c, 0xa1, 0xf5, 0xe2, 0x8e,
+                0x11, 0xdc, 0x0c, 0x1c, 0x10, 0xa4, 0xf2, 0x46, 0x5c, 0x78, 0x0c, 0xc1, 0x4a, 0x65, 0x21, 0x8a,
+                0x2e, 0x32, 0x6c, 0x72, 0x06, 0xf9, 0x7f, 0xa1, 0x6c, 0x2e, 0x13, 0x06, 0x41, 0xaa, 0x23, 0xdd,
+                0xc8, 0x1c, 0x61, 0xb6, 0x96, 0x87, 0xc4, 0x84, 0xc8, 0x61, 0xec, 0x4e, 0xdd, 0x49, 0x9e, 0x4f,
+                0x0d, 0x8c, 0xf1, 0x7f, 0xf2, 0x6c, 0x73, 0x5a, 0xa6, 0x3b, 0xbf, 0x4e, 0xba, 0x57, 0x6b, 0xb3,
+                0x1e, 0x6c, 0x57, 0x76, 0x87, 0x9f, 0xb4, 0x3b, 0xcb, 0xcd, 0xe5, 0x10, 0x7a, 0x4c, 0xeb, 0xc0,
+                0xc4, 0xc3, 0x75, 0x51, 0x5f, 0xb7, 0x7c, 0xbc, 0x55, 0x8d, 0x05, 0xc7, 0xed, 0xc7, 0x52, 0x4a
+            };
+
+            CollectionAssert.AreEqual(expectedEncodedSignatureBytes, keyAlgorithm.Sign(data));
+
+            key = new RsaKey();
+            keyAlgorithm = new KeyHostAlgorithm("rsa-sha2-256", key, GetRsaPublicKeyBytes(), new RsaDigitalSignature(key, HashAlgorithmName.SHA256));
+            Assert.IsTrue(keyAlgorithm.VerifySignature(data, expectedEncodedSignatureBytes));
+        }
+
+        [TestMethod]
+        public void RsaSha512_SignAndVerify()
+        {
+            byte[] data = Encoding.UTF8.GetBytes("hello world");
+
+            RsaKey key = GetRsaKey();
+            KeyHostAlgorithm keyAlgorithm = new KeyHostAlgorithm("rsa-sha2-512", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA512));
+
+            byte[] expectedEncodedSignatureBytes = new byte[]
+            {
+                0, 0, 0, 12, // byte count of "rsa-sha2-512"
+                (byte)'r', (byte)'s', (byte)'a', (byte)'-', (byte)'s', (byte)'h', (byte)'a', (byte)'2',
+                (byte)'-', (byte)'5', (byte)'1', (byte)'2',
+                0, 0, 1, 0, // byte count of signature (=256)
+                
+                // echo -n 'hello world' | openssl dgst -sha512 -sign Key.RSA.txt -out test.signed
+                0x69, 0x70, 0xb5, 0x9f, 0x32, 0x86, 0x3b, 0xae, 0xc0, 0x79, 0x6e, 0xdb, 0x35, 0xd5, 0xa6, 0x22,
+                0xcd, 0x2b, 0x4b, 0xd2, 0x68, 0x1a, 0x65, 0x41, 0xa6, 0xd9, 0x20, 0x54, 0x31, 0x9a, 0xb1, 0x44,
+                0x6e, 0x8f, 0x56, 0x4b, 0xfc, 0x27, 0x7f, 0x3f, 0xe7, 0x47, 0xcb, 0x78, 0x03, 0x05, 0x79, 0x8a,
+                0x16, 0x7b, 0x12, 0x01, 0x3a, 0xa2, 0xd5, 0x0d, 0x2b, 0x16, 0x38, 0xef, 0x84, 0x6b, 0xd7, 0x19,
+                0xeb, 0xac, 0x54, 0x01, 0x9d, 0xa6, 0x80, 0x74, 0x43, 0xa8, 0x6e, 0x5e, 0x33, 0x05, 0x06, 0x1d,
+                0x6d, 0xfe, 0x32, 0x4f, 0xe3, 0xcb, 0x3e, 0x2d, 0x4e, 0xe1, 0x47, 0x03, 0x69, 0xb4, 0x59, 0x80,
+                0x59, 0x05, 0x15, 0xa0, 0x11, 0x34, 0x47, 0x58, 0xd7, 0x93, 0x2d, 0x40, 0xf2, 0x2c, 0x37, 0x48,
+                0x6b, 0x3c, 0xd3, 0x03, 0x09, 0x32, 0x74, 0xa0, 0x2d, 0x33, 0x11, 0x99, 0x10, 0xb4, 0x09, 0x31,
+                0xec, 0xa3, 0x2c, 0x63, 0xba, 0x50, 0xd1, 0x02, 0x45, 0xae, 0xb5, 0x75, 0x7e, 0xfa, 0xfc, 0x06,
+                0xb6, 0x6a, 0xb2, 0xa1, 0x73, 0x14, 0xa5, 0xaa, 0x17, 0x88, 0x03, 0x19, 0x14, 0x9b, 0xe1, 0x10,
+                0xf8, 0x2f, 0x73, 0x01, 0xc7, 0x8d, 0x37, 0xef, 0x98, 0x69, 0xc2, 0xe2, 0x7a, 0x11, 0xd5, 0xb8,
+                0xc9, 0x35, 0x45, 0xcb, 0x56, 0x4b, 0x92, 0x4a, 0xe0, 0x4c, 0xd6, 0x82, 0xae, 0xad, 0x5b, 0xe9,
+                0x40, 0x7e, 0x2a, 0x48, 0x7d, 0x57, 0xc5, 0xfd, 0xe9, 0x98, 0xe0, 0xbb, 0x09, 0xa1, 0xf5, 0x48,
+                0x45, 0xcb, 0xee, 0xb9, 0x99, 0x81, 0x44, 0x15, 0x2e, 0x50, 0x39, 0x64, 0x58, 0x4c, 0x34, 0x86,
+                0xf8, 0x81, 0x9e, 0x1d, 0xb6, 0x97, 0xe0, 0xce, 0x16, 0xca, 0x20, 0x46, 0xe9, 0x49, 0x8f, 0xe6,
+                0xa0, 0x23, 0x08, 0x80, 0xa6, 0x37, 0x70, 0x06, 0xcc, 0x8f, 0xf4, 0xa0, 0x74, 0x53, 0x26, 0x38
+            };
+
+            CollectionAssert.AreEqual(expectedEncodedSignatureBytes, keyAlgorithm.Sign(data));
+
+            key = new RsaKey();
+            keyAlgorithm = new KeyHostAlgorithm("rsa-sha2-512", key, GetRsaPublicKeyBytes(), new RsaDigitalSignature(key, HashAlgorithmName.SHA512));
+            Assert.IsTrue(keyAlgorithm.VerifySignature(data, expectedEncodedSignatureBytes));
+        }
+
+        private static RsaKey GetRsaKey()
+        {
+            using (var stream = GetData("Key.RSA.txt"))
+            {
+                return (RsaKey) ((KeyHostAlgorithm) new PrivateKeyFile(stream).HostKey).Key;
+            }
+        }
+
+        private static byte[] GetRsaPublicKeyBytes()
+        {
+            return new byte[]
+            {
+                0, 0, 0, 7, // byte count of "ssh-rsa"
+                (byte)'s', (byte)'s', (byte)'h', (byte)'-', (byte)'r', (byte)'s', (byte)'a', // ssh-rsa
+                0, 0, 0, 1, // byte count of exponent
+                35, // exponent
+                0, 0, 1, 1, // byte count of modulus (=257)
+
+                // openssl rsa -in Key.RSA.txt -text
+                0x00, 0xb9, 0x3b, 0x57, 0x9f, 0xe0, 0x5a, 0xb5, 0x7d, 0x68, 0x26, 0xeb, 0xe1, 0xa9, 0xf2,
+                0x59, 0xc3, 0x98, 0xdc, 0xfe, 0x97, 0x08, 0xc4, 0x95, 0x0f, 0x9a, 0xea, 0x05, 0x08, 0x7d,
+                0xfe, 0x6d, 0x77, 0xca, 0x04, 0x9f, 0xfd, 0xe2, 0x2c, 0x4d, 0x11, 0x3c, 0xd9, 0x05, 0xab,
+                0x32, 0xbd, 0x3f, 0xe8, 0xcd, 0xba, 0x00, 0x6c, 0x21, 0xb7, 0xa9, 0xc2, 0x4e, 0x63, 0x17,
+                0xf6, 0x04, 0x47, 0x93, 0x00, 0x85, 0xde, 0xd6, 0x32, 0xc0, 0xa1, 0x37, 0x75, 0x18, 0xa0,
+                0xb0, 0x32, 0xf6, 0x4e, 0xca, 0x39, 0xec, 0x3c, 0xdf, 0x79, 0xfe, 0x50, 0xa1, 0xc1, 0xf7,
+                0x67, 0x05, 0xb3, 0x33, 0xa5, 0x96, 0x13, 0x19, 0xfa, 0x14, 0xca, 0x55, 0xe6, 0x7b, 0xf9,
+                0xb3, 0x8e, 0x32, 0xee, 0xfc, 0x9d, 0x2a, 0x5e, 0x04, 0x79, 0x97, 0x29, 0x3d, 0x1c, 0x54,
+                0xfe, 0xc7, 0x96, 0x04, 0xb5, 0x19, 0x7c, 0x55, 0x21, 0xe2, 0x0e, 0x42, 0xca, 0x4d, 0x9d,
+                0xfb, 0x77, 0x08, 0x6c, 0xaa, 0x07, 0x2c, 0xf8, 0xf9, 0x1f, 0xbd, 0x83, 0x14, 0x2b, 0xe0,
+                0xbc, 0x7a, 0xf9, 0xdf, 0x13, 0x4b, 0x60, 0x5a, 0x02, 0x99, 0x93, 0x41, 0x1a, 0xb6, 0x5f,
+                0x3b, 0x9c, 0xb5, 0xb2, 0x55, 0x70, 0x78, 0x2f, 0x38, 0x52, 0x0e, 0xd1, 0x8a, 0x2c, 0x23,
+                0xc0, 0x3a, 0x0a, 0xd7, 0xed, 0xf6, 0x1f, 0xa6, 0x50, 0xf0, 0x27, 0x65, 0x8a, 0xd4, 0xde,
+                0xa7, 0x1b, 0x41, 0x67, 0xc5, 0x6d, 0x47, 0x84, 0x37, 0x92, 0x2b, 0xb7, 0xb6, 0x4d, 0xb0,
+                0x1a, 0xda, 0xf6, 0x50, 0x82, 0xf1, 0x57, 0x31, 0x69, 0xce, 0xe0, 0xef, 0xcd, 0x64, 0xaa,
+                0x78, 0x08, 0xea, 0x4e, 0x45, 0xec, 0xa5, 0x89, 0x68, 0x5d, 0xb4, 0xa0, 0x23, 0xaf, 0xff,
+                0x9c, 0x0f, 0x8c, 0x83, 0x7c, 0xf8, 0xe1, 0x8e, 0x32, 0x8e, 0x61, 0xfc, 0x5b, 0xbd, 0xd4,
+                0x46, 0xe1
+            };
+        }
+    }
+}

+ 2 - 2
src/Renci.SshNet.Tests/Common/TestBase.cs

@@ -49,9 +49,9 @@ namespace Renci.SshNet.Tests.Common
             }
         }
 
-        protected Stream GetData(string name)
+        protected static Stream GetData(string name)
         {
             return ExecutingAssembly.GetManifestResourceStream(string.Format("Renci.SshNet.Tests.Data.{0}", name));
         }
     }
-}
+}

+ 16 - 0
src/Renci.SshNet/Common/ObjectIdentifier.cs

@@ -1,4 +1,6 @@
 using System;
+using System.Linq;
+using System.Security.Cryptography;
 
 namespace Renci.SshNet.Common
 {
@@ -32,5 +34,19 @@ namespace Renci.SshNet.Common
 
             Identifiers = identifiers;
         }
+
+        internal static ObjectIdentifier FromHashAlgorithmName(HashAlgorithmName hashAlgorithmName)
+        {
+            var oid = CryptoConfig.MapNameToOID(hashAlgorithmName.Name);
+
+            if (oid is null)
+            {
+                throw new ArgumentException($"Could not map `{hashAlgorithmName}` to OID.", nameof(hashAlgorithmName));
+            }
+
+            var identifiers = oid.Split('.').Select(ulong.Parse).ToArray();
+
+            return new ObjectIdentifier(identifiers);
+        }
     }
 }

+ 4 - 0
src/Renci.SshNet/ConnectionInfo.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Net;
+using System.Security.Cryptography;
 using System.Text;
 
 using Renci.SshNet.Abstractions;
@@ -9,6 +10,7 @@ using Renci.SshNet.Common;
 using Renci.SshNet.Messages.Authentication;
 using Renci.SshNet.Messages.Connection;
 using Renci.SshNet.Security;
+using Renci.SshNet.Security.Cryptography;
 using Renci.SshNet.Security.Cryptography.Ciphers;
 using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
 
@@ -396,6 +398,8 @@ namespace Renci.SshNet
                     { "ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(), data) },
                     { "ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(), data) },
                     { "ecdsa-sha2-nistp521", data => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(), data) },
+                    { "rsa-sha2-512", data => { var key = new RsaKey(); return new KeyHostAlgorithm("rsa-sha2-512", key, data, new RsaDigitalSignature(key, HashAlgorithmName.SHA512)); }},
+                    { "rsa-sha2-256", data => { var key = new RsaKey(); return new KeyHostAlgorithm("rsa-sha2-256", key, data, new RsaDigitalSignature(key, HashAlgorithmName.SHA256)); }},
                     { "ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(), data) },
                     { "ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(), data) },
                 };

+ 21 - 0
src/Renci.SshNet/IHostAlgorithmsProvider.cs

@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+
+using Renci.SshNet.Security;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// Represents a collection of host algorithms.
+    /// </summary>
+    public interface IHostAlgorithmsProvider
+    {
+        /// <summary>
+        /// The host algorithms provided by this <see cref="IHostAlgorithmsProvider"/>.
+        /// </summary>
+        /// <remarks>
+        /// In situations where there is a preferred order of usage of the host algorithms,
+        /// the collection should be ordered from most preferred to least.
+        /// </remarks>
+        IReadOnlyCollection<HostAlgorithm> HostAlgorithms { get; }
+    }
+}

+ 14 - 3
src/Renci.SshNet/IPrivateKeySource.cs

@@ -1,15 +1,26 @@
-using Renci.SshNet.Security;
+using System;
+using System.ComponentModel;
+
+using Renci.SshNet.Security;
 
 namespace Renci.SshNet
 {
     /// <summary>
     /// Represents private key source interface.
     /// </summary>
-    public interface IPrivateKeySource
+    /// <remarks>
+    /// This interface has been replaced by <see cref="IHostAlgorithmsProvider"/>
+    /// and is obsolete.
+    /// </remarks>
+    [Obsolete($"Use {nameof(IHostAlgorithmsProvider)} instead. " +
+        $"{nameof(IPrivateKeySource)} may be removed in a future release. " +
+        $"See https://github.com/sshnet/SSH.NET/issues/1174 for details.")]
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public interface IPrivateKeySource : IHostAlgorithmsProvider
     {
         /// <summary>
         /// Gets the host key.
         /// </summary>
         HostAlgorithm HostKey { get; }
     }
-}
+}

+ 2 - 2
src/Renci.SshNet/NetConfClient.cs

@@ -107,7 +107,7 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
-        public NetConfClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
+        public NetConfClient(string host, int port, string username, params IHostAlgorithmsProvider[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true)
         {
         }
@@ -120,7 +120,7 @@ namespace Renci.SshNet
         /// <param name="keyFiles">Authentication private key file(s) .</param>
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <c>null</c>.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
-        public NetConfClient(string host, string username, params IPrivateKeySource[] keyFiles)
+        public NetConfClient(string host, string username, params IHostAlgorithmsProvider[] keyFiles)
             : this(host, ConnectionInfo.DefaultPort, username, keyFiles)
         {
         }

+ 15 - 12
src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Linq;
 using System.Threading;
 
 using Renci.SshNet.Common;
@@ -30,7 +31,7 @@ namespace Renci.SshNet
         /// <summary>
         /// Gets the key files used for authentication.
         /// </summary>
-        public ICollection<IPrivateKeySource> KeyFiles { get; private set; }
+        public ICollection<IHostAlgorithmsProvider> KeyFiles { get; private set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="PrivateKeyAuthenticationMethod"/> class.
@@ -38,7 +39,7 @@ namespace Renci.SshNet
         /// <param name="username">The username.</param>
         /// <param name="keyFiles">The key files.</param>
         /// <exception cref="ArgumentException"><paramref name="username"/> is whitespace or <c>null</c>.</exception>
-        public PrivateKeyAuthenticationMethod(string username, params IPrivateKeySource[] keyFiles)
+        public PrivateKeyAuthenticationMethod(string username, params IHostAlgorithmsProvider[] keyFiles)
             : base(username)
         {
             if (keyFiles is null)
@@ -46,7 +47,7 @@ namespace Renci.SshNet
                 throw new ArgumentNullException(nameof(keyFiles));
             }
 
-            KeyFiles = new Collection<IPrivateKeySource>(keyFiles);
+            KeyFiles = new Collection<IHostAlgorithmsProvider>(keyFiles);
         }
 
         /// <summary>
@@ -64,24 +65,26 @@ namespace Renci.SshNet
 
             session.RegisterMessage("SSH_MSG_USERAUTH_PK_OK");
 
+            var hostAlgorithms = KeyFiles.SelectMany(x => x.HostAlgorithms).ToList();
+
             try
             {
-                foreach (var keyFile in KeyFiles)
+                foreach (var hostAlgorithm in hostAlgorithms)
                 {
                     _ = _authenticationCompleted.Reset();
                     _isSignatureRequired = false;
 
                     var message = new RequestMessagePublicKey(ServiceName.Connection,
                                                               Username,
-                                                              keyFile.HostKey.Name,
-                                                              keyFile.HostKey.Data);
+                                                              hostAlgorithm.Name,
+                                                              hostAlgorithm.Data);
 
-                    if (KeyFiles.Count < 2)
+                    if (hostAlgorithms.Count == 1)
                     {
                         // 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 = hostAlgorithm.Sign(signatureData);
                     }
 
                     // Send public key authentication request
@@ -95,12 +98,12 @@ namespace Renci.SshNet
 
                         var signatureMessage = new RequestMessagePublicKey(ServiceName.Connection,
                                                                            Username,
-                                                                           keyFile.HostKey.Name,
-                                                                           keyFile.HostKey.Data);
+                                                                           hostAlgorithm.Name,
+                                                                           hostAlgorithm.Data);
 
                         var signatureData = new SignatureData(message, session.SessionId).GetBytes();
 
-                        signatureMessage.Signature = keyFile.HostKey.Sign(signatureData);
+                        signatureMessage.Signature = hostAlgorithm.Sign(signatureData);
 
                         // Send public key authentication request with signature
                         session.SendMessage(signatureMessage);
@@ -108,7 +111,7 @@ namespace Renci.SshNet
 
                     session.WaitOnHandle(_authenticationCompleted);
 
-                    if (_authenticationResult == AuthenticationResult.Success)
+                    if (_authenticationResult is AuthenticationResult.Success or AuthenticationResult.PartialSuccess)
                     {
                         break;
                     }

+ 9 - 9
src/Renci.SshNet/PrivateKeyConnectionInfo.cs

@@ -17,7 +17,7 @@ namespace Renci.SshNet
         /// <summary>
         /// Gets the key files used for authentication.
         /// </summary>
-        public ICollection<IPrivateKeySource> KeyFiles { get; private set; }
+        public ICollection<IHostAlgorithmsProvider> KeyFiles { get; private set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="PrivateKeyConnectionInfo"/> class.
@@ -41,7 +41,7 @@ namespace Renci.SshNet
         /// <param name="port">Connection port.</param>
         /// <param name="username">Connection username.</param>
         /// <param name="keyFiles">Connection key files.</param>
-        public PrivateKeyConnectionInfo(string host, int port, string username, params IPrivateKeySource[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, int port, string username, params IHostAlgorithmsProvider[] keyFiles)
             : this(host, port, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty, keyFiles)
         {
         }
@@ -56,7 +56,7 @@ namespace Renci.SshNet
         /// <param name="proxyHost">The proxy host.</param>
         /// <param name="proxyPort">The proxy port.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IPrivateKeySource[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IHostAlgorithmsProvider[] keyFiles)
             : this(host, port, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, keyFiles)
         {
         }
@@ -72,7 +72,7 @@ namespace Renci.SshNet
         /// <param name="proxyPort">The proxy port.</param>
         /// <param name="proxyUsername">The proxy username.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IPrivateKeySource[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IHostAlgorithmsProvider[] keyFiles)
             : this(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, keyFiles)
         {
         }
@@ -86,7 +86,7 @@ namespace Renci.SshNet
         /// <param name="proxyHost">The proxy host.</param>
         /// <param name="proxyPort">The proxy port.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IPrivateKeySource[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IHostAlgorithmsProvider[] keyFiles)
             : this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, keyFiles)
         {
         }
@@ -101,7 +101,7 @@ namespace Renci.SshNet
         /// <param name="proxyPort">The proxy port.</param>
         /// <param name="proxyUsername">The proxy username.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IPrivateKeySource[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IHostAlgorithmsProvider[] keyFiles)
             : this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, keyFiles)
         {
         }
@@ -117,7 +117,7 @@ namespace Renci.SshNet
         /// <param name="proxyUsername">The proxy username.</param>
         /// <param name="proxyPassword">The proxy password.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IPrivateKeySource[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IHostAlgorithmsProvider[] keyFiles)
             : this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, keyFiles)
         {
         }
@@ -134,10 +134,10 @@ namespace Renci.SshNet
         /// <param name="proxyUsername">The proxy username.</param>
         /// <param name="proxyPassword">The proxy password.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IPrivateKeySource[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IHostAlgorithmsProvider[] keyFiles)
             : base(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, new PrivateKeyAuthenticationMethod(username, keyFiles))
         {
-            KeyFiles = new Collection<IPrivateKeySource>(keyFiles);
+            KeyFiles = new Collection<IHostAlgorithmsProvider>(keyFiles);
         }
 
         /// <summary>

+ 59 - 7
src/Renci.SshNet/PrivateKeyFile.cs

@@ -1,7 +1,9 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Globalization;
 using System.IO;
+using System.Security.Cryptography;
 using System.Text;
 using System.Text.RegularExpressions;
 
@@ -63,18 +65,48 @@ namespace Renci.SshNet
     /// </list>
     /// </para>
     /// </remarks>
-    public class PrivateKeyFile : IPrivateKeySource, IDisposable
+    public class PrivateKeyFile : IHostAlgorithmsProvider,
+#pragma warning disable CS0618 // Type or member is obsolete
+        IPrivateKeySource,
+#pragma warning restore CS0618 // Type or member is obsolete
+        IDisposable
     {
         private static readonly Regex PrivateKeyRegex = new Regex(@"^-+ *BEGIN (?<keyName>\w+( \w+)*) PRIVATE KEY *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[A-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?<data>([a-zA-Z0-9/+=]{1,80}\r?\n)+)-+ *END \k<keyName> PRIVATE KEY *-+",
             RegexOptions.Compiled | RegexOptions.Multiline);
 
+        private readonly List<HostAlgorithm> _hostAlgorithms = new List<HostAlgorithm>();
         private Key _key;
         private bool _isDisposed;
 
         /// <summary>
         /// Gets the host key.
         /// </summary>
-        public HostAlgorithm HostKey { get; private set; }
+        /// <remarks>
+        /// This property returns the first item in <see cref="HostAlgorithms"/>.
+        /// </remarks>
+        public HostAlgorithm HostKey
+        {
+            get
+            {
+                return _hostAlgorithms[0];
+            }
+            private set
+            {
+                Debug.Assert(_hostAlgorithms.Count == 0, $"Only expected to set {nameof(HostKey)} at most once.");
+                _hostAlgorithms.Add(value);
+            }
+        }
+
+        /// <summary>
+        /// The supported host algorithms for this key file.
+        /// </summary>
+        public IReadOnlyCollection<HostAlgorithm> HostAlgorithms
+        {
+            get
+            {
+                return _hostAlgorithms;
+            }
+        }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
@@ -92,6 +124,7 @@ namespace Renci.SshNet
         public PrivateKeyFile(Stream privateKey)
         {
             Open(privateKey, passPhrase: null);
+            Debug.Assert(_hostAlgorithms.Count > 0, $"{nameof(HostKey)} is not set.");
         }
 
         /// <summary>
@@ -127,6 +160,8 @@ namespace Renci.SshNet
             {
                 Open(keyFile, passPhrase);
             }
+
+            Debug.Assert(_hostAlgorithms.Count > 0, $"{nameof(HostKey)} is not set.");
         }
 
         /// <summary>
@@ -138,6 +173,7 @@ namespace Renci.SshNet
         public PrivateKeyFile(Stream privateKey, string passPhrase)
         {
             Open(privateKey, passPhrase);
+            Debug.Assert(_hostAlgorithms.Count > 0, $"{nameof(HostKey)} is not set.");
         }
 
         /// <summary>
@@ -222,8 +258,11 @@ namespace Renci.SshNet
             switch (keyName)
             {
                 case "RSA":
-                    _key = new RsaKey(decryptedData);
-                    HostKey = new KeyHostAlgorithm("ssh-rsa", _key);
+                    var rsaKey = new RsaKey(decryptedData);
+                    _key = rsaKey;
+                    _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA512)));
+                    _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA256)));
+                    _hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key));
                     break;
                 case "DSA":
                     _key = new DsaKey(decryptedData);
@@ -235,7 +274,17 @@ namespace Renci.SshNet
                     break;
                 case "OPENSSH":
                     _key = ParseOpenSshV1Key(decryptedData, passPhrase);
-                    HostKey = new KeyHostAlgorithm(_key.ToString(), _key);
+                    if (_key is RsaKey parsedRsaKey)
+                    {
+                        _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(parsedRsaKey, HashAlgorithmName.SHA512)));
+                        _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(parsedRsaKey, HashAlgorithmName.SHA256)));
+                        _hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key));
+                    }
+                    else
+                    {
+                        HostKey = new KeyHostAlgorithm(_key.ToString(), _key);
+                    }
+
                     break;
                 case "SSH2 ENCRYPTED":
                     var reader = new SshDataReader(decryptedData);
@@ -290,8 +339,11 @@ namespace Renci.SshNet
                         var inverseQ = reader.ReadBigIntWithBits(); // u
                         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);
+                        var decryptedRsaKey = new RsaKey(modulus, exponent, d, p, q, inverseQ);
+                        _key = decryptedRsaKey;
+                        _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(decryptedRsaKey, HashAlgorithmName.SHA512)));
+                        _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(decryptedRsaKey, HashAlgorithmName.SHA256)));
+                        _hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key));
                     }
                     else if (keyType == "dl-modp{sign{dsa-nist-sha1},dh{plain}}")
                     {

+ 2 - 2
src/Renci.SshNet/ScpClient.cs

@@ -146,7 +146,7 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
-        public ScpClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
+        public ScpClient(string host, int port, string username, params IHostAlgorithmsProvider[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true)
         {
         }
@@ -159,7 +159,7 @@ namespace Renci.SshNet
         /// <param name="keyFiles">Authentication private key file(s) .</param>
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <c>null</c>.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
-        public ScpClient(string host, string username, params IPrivateKeySource[] keyFiles)
+        public ScpClient(string host, string username, params IHostAlgorithmsProvider[] keyFiles)
             : this(host, ConnectionInfo.DefaultPort, username, keyFiles)
         {
         }

+ 3 - 4
src/Renci.SshNet/Security/Cryptography/Ciphers/RsaCipher.cs

@@ -8,8 +8,6 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
     /// </summary>
     public class RsaCipher : AsymmetricCipher
     {
-        private readonly bool _isPrivate;
-
         private readonly RsaKey _key;
 
         /// <summary>
@@ -24,7 +22,6 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
             }
 
             _key = key;
-            _isPrivate = !_key.D.IsZero;
         }
 
         /// <summary>
@@ -116,7 +113,9 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
 
             BigInteger result;
 
-            if (_isPrivate)
+            var isPrivate = !_key.D.IsZero;
+
+            if (isPrivate)
             {
                 var random = BigInteger.One;
                 var max = _key.Modulus - 1;

+ 1 - 1
src/Renci.SshNet/Security/Cryptography/DsaKey.cs

@@ -84,7 +84,7 @@ namespace Renci.SshNet.Security
         /// <summary>
         /// Gets the digital signature.
         /// </summary>
-        protected override DigitalSignature DigitalSignature
+        protected internal override DigitalSignature DigitalSignature
         {
             get
             {

+ 1 - 1
src/Renci.SshNet/Security/Cryptography/ED25519Key.cs

@@ -62,7 +62,7 @@ namespace Renci.SshNet.Security
         /// <summary>
         /// Gets the digital signature.
         /// </summary>
-        protected override DigitalSignature DigitalSignature
+        protected internal override DigitalSignature DigitalSignature
         {
             get
             {

+ 1 - 1
src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs

@@ -128,7 +128,7 @@ namespace Renci.SshNet.Security
         /// <summary>
         /// Gets the digital signature.
         /// </summary>
-        protected override DigitalSignature DigitalSignature
+        protected internal override DigitalSignature DigitalSignature
         {
             get
             {

+ 2 - 2
src/Renci.SshNet/Security/Cryptography/Key.cs

@@ -17,9 +17,9 @@ namespace Renci.SshNet.Security
         protected BigInteger[] _privateKey;
 
         /// <summary>
-        /// Gets the key specific digital signature.
+        /// Gets the default digital signature implementation for this key.
         /// </summary>
-        protected abstract DigitalSignature DigitalSignature { get; }
+        protected internal abstract DigitalSignature DigitalSignature { get; }
 
         /// <summary>
         /// Gets or sets the public key.

+ 13 - 4
src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Security.Cryptography;
-using Renci.SshNet.Abstractions;
 using Renci.SshNet.Common;
 using Renci.SshNet.Security.Cryptography.Ciphers;
 
@@ -14,13 +13,23 @@ namespace Renci.SshNet.Security.Cryptography
         private HashAlgorithm _hash;
 
         /// <summary>
-        /// Initializes a new instance of the <see cref="RsaDigitalSignature"/> class.
+        /// Initializes a new instance of the <see cref="RsaDigitalSignature"/> class with the SHA-1 hash algorithm.
         /// </summary>
         /// <param name="rsaKey">The RSA key.</param>
         public RsaDigitalSignature(RsaKey rsaKey)
-            : base(new ObjectIdentifier(1, 3, 14, 3, 2, 26), new RsaCipher(rsaKey))
+            : this(rsaKey, HashAlgorithmName.SHA1)
+        { }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RsaDigitalSignature"/> class.
+        /// </summary>
+        /// <param name="rsaKey">The RSA key.</param>
+        /// <param name="hashAlgorithmName">The hash algorithm to use in the digital signature.</param>
+        public RsaDigitalSignature(RsaKey rsaKey, HashAlgorithmName hashAlgorithmName)
+            : base(ObjectIdentifier.FromHashAlgorithmName(hashAlgorithmName), new RsaCipher(rsaKey))
         {
-            _hash = CryptoAbstraction.CreateSHA1();
+            _hash = CryptoConfig.CreateFromName(hashAlgorithmName.Name) as HashAlgorithm
+                ?? throw new ArgumentException($"Could not create {nameof(HashAlgorithm)} from `{hashAlgorithmName}`.", nameof(hashAlgorithmName));
         }
 
         /// <summary>

+ 6 - 2
src/Renci.SshNet/Security/Cryptography/RsaKey.cs

@@ -153,10 +153,14 @@ namespace Renci.SshNet.Security
         }
 
         private RsaDigitalSignature _digitalSignature;
+
         /// <summary>
-        /// Gets the digital signature.
+        /// <inheritdoc cref="Key.DigitalSignature"/>
         /// </summary>
-        protected override DigitalSignature DigitalSignature
+        /// <returns>
+        /// An implementation of an RSA digital signature using the SHA-1 hash algorithm.
+        /// </returns>
+        protected internal override DigitalSignature DigitalSignature
         {
             get
             {

+ 22 - 0
src/Renci.SshNet/Security/KeyExchange.cs

@@ -318,6 +318,28 @@ namespace Renci.SshNet.Security
         /// <returns>true if exchange hash is valid; otherwise false.</returns>
         protected abstract bool ValidateExchangeHash();
 
+        private protected bool ValidateExchangeHash(byte[] encodedKey, byte[] encodedSignature)
+        {
+            var exchangeHash = CalculateHash();
+
+            var signatureData = new KeyHostAlgorithm.SignatureKeyData();
+            signatureData.Load(encodedSignature);
+
+            var keyAlgorithm = Session.ConnectionInfo.HostKeyAlgorithms[signatureData.AlgorithmName](encodedKey);
+
+            Session.ConnectionInfo.CurrentHostKeyAlgorithm = signatureData.AlgorithmName;
+
+            if (CanTrustHostKey(keyAlgorithm))
+            {
+                // keyAlgorithm.VerifySignature decodes the signature data before verifying.
+                // But as we have already decoded the data to find the signature algorithm,
+                // we just verify the decoded data directly through the DigitalSignature.
+                return keyAlgorithm.DigitalSignature.Verify(exchangeHash, signatureData.Signature);
+            }
+
+            return false;
+        }
+
         /// <summary>
         /// Calculates key exchange hash value.
         /// </summary>

+ 1 - 15
src/Renci.SshNet/Security/KeyExchangeDiffieHellman.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Text;
 
 using Renci.SshNet.Common;
 using Renci.SshNet.Messages.Transport;
@@ -72,20 +71,7 @@ namespace Renci.SshNet.Security
         /// </returns>
         protected override bool ValidateExchangeHash()
         {
-            var exchangeHash = CalculateHash();
-
-            var length = Pack.BigEndianToUInt32(_hostKey);
-            var algorithmName = Encoding.UTF8.GetString(_hostKey, 4, (int)length);
-            var key = Session.ConnectionInfo.HostKeyAlgorithms[algorithmName](_hostKey);
-
-            Session.ConnectionInfo.CurrentHostKeyAlgorithm = algorithmName;
-
-            if (CanTrustHostKey(key))
-            {
-                return key.VerifySignature(exchangeHash, _signature);
-            }
-
-            return false;
+            return ValidateExchangeHash(_hostKey, _signature);
         }
 
         /// <summary>

+ 2 - 18
src/Renci.SshNet/Security/KeyExchangeEC.cs

@@ -1,7 +1,4 @@
-using System.Text;
-
-using Renci.SshNet.Common;
-using Renci.SshNet.Messages.Transport;
+using Renci.SshNet.Messages.Transport;
 
 namespace Renci.SshNet.Security
 {
@@ -76,20 +73,7 @@ namespace Renci.SshNet.Security
         /// </returns>
         protected override bool ValidateExchangeHash()
         {
-            var exchangeHash = CalculateHash();
-
-            var length = Pack.BigEndianToUInt32(_hostKey);
-            var algorithmName = Encoding.UTF8.GetString(_hostKey, 4, (int)length);
-            var key = Session.ConnectionInfo.HostKeyAlgorithms[algorithmName](_hostKey);
-
-            Session.ConnectionInfo.CurrentHostKeyAlgorithm = algorithmName;
-
-            if (CanTrustHostKey(key))
-            {
-                return key.VerifySignature(exchangeHash, _signature);
-            }
-
-            return false;
+            return ValidateExchangeHash(_hostKey, _signature);
         }
 
         /// <summary>

+ 95 - 25
src/Renci.SshNet/Security/KeyHostAlgorithm.cs

@@ -1,6 +1,9 @@
 using System.Collections.Generic;
+using System.Text;
+
 using Renci.SshNet.Common;
 using Renci.SshNet.Security.Chaos.NaCl;
+using Renci.SshNet.Security.Cryptography;
 
 namespace Renci.SshNet.Security
 {
@@ -10,38 +13,76 @@ namespace Renci.SshNet.Security
     public class KeyHostAlgorithm : HostAlgorithm
     {
         /// <summary>
-        /// Gets the key.
+        /// The key used in this host key algorithm.
         /// </summary>
         public Key Key { get; private set; }
 
         /// <summary>
-        /// Gets the public key data.
+        /// The signature implementation used in this host key algorithm.
+        /// </summary>
+        public DigitalSignature DigitalSignature { get; private set; }
+
+        /// <summary>
+        /// Gets the encoded public key data.
         /// </summary>
         public override byte[] Data
         {
             get
             {
-                return new SshKeyData(Name, Key.Public).GetBytes();
+                var keyFormatIdentifier = Key is RsaKey ? "ssh-rsa" : Name;
+                return new SshKeyData(keyFormatIdentifier, Key.Public).GetBytes();
             }
         }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class.
         /// </summary>
-        /// <param name="name">Host key name.</param>
-        /// <param name="key">Host key.</param>
+        /// <param name="name">The signature format identifier.</param>
+        /// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
+        /// <remarks>
+        /// This constructor is typically passed a private key in order to create an encoded signature for later
+        /// verification by the host.
+        /// </remarks>
         public KeyHostAlgorithm(string name, Key key)
             : base(name)
         {
             Key = key;
+            DigitalSignature = key.DigitalSignature;
         }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class.
         /// </summary>
-        /// <param name="name">Host key name.</param>
-        /// <param name="key">Host key.</param>
+        /// <param name="name">The signature format identifier.</param>
+        /// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
+        /// <param name="digitalSignature"><inheritdoc cref="DigitalSignature" path="/summary"/></param>
+        /// <remarks>
+        /// <para>
+        /// This constructor is typically passed a private key in order to create an encoded signature for later
+        /// verification by the host.
+        /// </para>
+        /// The key used by <paramref name="digitalSignature"/> is intended to be equal to <paramref name="key"/>.
+        /// This is not verified.
+        /// </remarks>
+        public KeyHostAlgorithm(string name, Key key, DigitalSignature digitalSignature)
+            : base(name)
+        {
+            Key = key;
+            DigitalSignature = digitalSignature;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class
+        /// with the given encoded public key data. The data will be decoded into <paramref name="key"/>.
+        /// </summary>
+        /// <param name="name">The signature format identifier.</param>
+        /// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
         /// <param name="data">Host key encoded data.</param>
+        /// <remarks>
+        /// This constructor is typically passed a new or reusable <see cref="Security.Key"/> instance in
+        /// order to verify an encoded signature sent by the host, created by the private counterpart
+        /// to the host's public key, which is encoded in <paramref name="data"/>.
+        /// </remarks>
         public KeyHostAlgorithm(string name, Key key, byte[] data)
             : base(name)
         {
@@ -50,34 +91,66 @@ namespace Renci.SshNet.Security
             var sshKey = new SshKeyData();
             sshKey.Load(data);
             Key.Public = sshKey.Keys;
+
+            DigitalSignature = key.DigitalSignature;
         }
 
         /// <summary>
-        /// Signs the specified data.
+        /// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class
+        /// with the given encoded public key data. The data will be decoded into <paramref name="key"/>.
         /// </summary>
-        /// <param name="data">The data.</param>
+        /// <param name="name">The signature format identifier.</param>
+        /// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
+        /// <param name="data">Host key encoded data.</param>
+        /// <param name="digitalSignature"><inheritdoc cref="DigitalSignature" path="/summary"/></param>
+        /// <remarks>
+        /// <para>
+        /// This constructor is typically passed a new or reusable <see cref="Security.Key"/> instance in
+        /// order to verify an encoded signature sent by the host, created by the private counterpart
+        /// to the host's public key, which is encoded in <paramref name="data"/>.
+        /// </para>
+        /// The key used by <paramref name="digitalSignature"/> is intended to be equal to <paramref name="key"/>.
+        /// This is not verified.
+        /// </remarks>
+        public KeyHostAlgorithm(string name, Key key, byte[] data, DigitalSignature digitalSignature)
+            : base(name)
+        {
+            Key = key;
+
+            var sshKey = new SshKeyData();
+            sshKey.Load(data);
+            Key.Public = sshKey.Keys;
+
+            DigitalSignature = digitalSignature;
+        }
+
+        /// <summary>
+        /// Signs and encodes the specified data.
+        /// </summary>
+        /// <param name="data">The data to be signed.</param>
         /// <returns>
-        /// Signed data.
+        /// The encoded signature.
         /// </returns>
         public override byte[] Sign(byte[] data)
         {
-            return new SignatureKeyData(Name, Key.Sign(data)).GetBytes();
+            return new SignatureKeyData(Name, DigitalSignature.Sign(data)).GetBytes();
         }
 
         /// <summary>
         /// Verifies the signature.
         /// </summary>
-        /// <param name="data">The data.</param>
-        /// <param name="signature">The signature.</param>
+        /// <param name="data">The data to verify the signature against.</param>
+        /// <param name="signature">The encoded signature data.</param>
         /// <returns>
-        ///   <c>True</c> is signature was successfully verifies; otherwise <c>false</c>.
+        /// <see langword="true"/> if <paramref name="signature"/> is the result of signing <paramref name="data"/>
+        /// with the corresponding private key to <see cref="Key"/>.
         /// </returns>
         public override bool VerifySignature(byte[] data, byte[] signature)
         {
             var signatureData = new SignatureKeyData();
             signatureData.Load(signature);
 
-            return Key.VerifySignature(data, signatureData.Signature);
+            return DigitalSignature.Verify(data, signatureData.Signature);
         }
 
         private sealed class SshKeyData : SshData
@@ -170,15 +243,12 @@ namespace Renci.SshNet.Security
             }
         }
 
-        private sealed class SignatureKeyData : SshData
+        internal sealed class SignatureKeyData : SshData
         {
             /// <summary>
-            /// Gets or sets the name of the algorithm as UTF-8 encoded byte array.
+            /// Gets or sets the signature format identifier
             /// </summary>
-            /// <value>
-            /// The name of the algorithm.
-            /// </value>
-            private byte[] AlgorithmName { get; set; }
+            public string AlgorithmName { get; set; }
 
             /// <summary>
             /// Gets the signature.
@@ -200,7 +270,7 @@ namespace Renci.SshNet.Security
                 {
                     var capacity = base.BufferCapacity;
                     capacity += 4; // AlgorithmName length
-                    capacity += AlgorithmName.Length; // AlgorithmName
+                    capacity += Encoding.UTF8.GetByteCount(AlgorithmName); // AlgorithmName
                     capacity += 4; // Signature length
                     capacity += Signature.Length; // Signature
                     return capacity;
@@ -213,7 +283,7 @@ namespace Renci.SshNet.Security
 
             public SignatureKeyData(string name, byte[] signature)
             {
-                AlgorithmName = Utf8.GetBytes(name);
+                AlgorithmName = name;
                 Signature = signature;
             }
 
@@ -222,7 +292,7 @@ namespace Renci.SshNet.Security
             /// </summary>
             protected override void LoadData()
             {
-                AlgorithmName = ReadBinary();
+                AlgorithmName = Encoding.UTF8.GetString(ReadBinary());
                 Signature = ReadBinary();
             }
 
@@ -231,7 +301,7 @@ namespace Renci.SshNet.Security
             /// </summary>
             protected override void SaveData()
             {
-                WriteBinaryString(AlgorithmName);
+                WriteBinaryString(Encoding.UTF8.GetBytes(AlgorithmName));
                 WriteBinaryString(Signature);
             }
         }

+ 2 - 2
src/Renci.SshNet/SftpClient.cs

@@ -218,7 +218,7 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid. <para>-or-</para> <paramref name="username"/> is nu<b>null</b>ll or contains only whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
-        public SftpClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
+        public SftpClient(string host, int port, string username, params IHostAlgorithmsProvider[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true)
         {
         }
@@ -231,7 +231,7 @@ namespace Renci.SshNet
         /// <param name="keyFiles">Authentication private key file(s) .</param>
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <b>null</b>.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid. <para>-or-</para> <paramref name="username"/> is <b>null</b> or contains only whitespace characters.</exception>
-        public SftpClient(string host, string username, params IPrivateKeySource[] keyFiles)
+        public SftpClient(string host, string username, params IHostAlgorithmsProvider[] keyFiles)
             : this(host, ConnectionInfo.DefaultPort, username, keyFiles)
         {
         }

+ 2 - 2
src/Renci.SshNet/SshClient.cs

@@ -103,7 +103,7 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
-        public SshClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
+        public SshClient(string host, int port, string username, params IHostAlgorithmsProvider[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true)
         {
         }
@@ -120,7 +120,7 @@ namespace Renci.SshNet
         /// </example>
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <c>null</c>.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
-        public SshClient(string host, string username, params IPrivateKeySource[] keyFiles)
+        public SshClient(string host, string username, params IHostAlgorithmsProvider[] keyFiles)
             : this(host, ConnectionInfo.DefaultPort, username, keyFiles)
         {
         }