Wojciech Nagórski 2 år sedan
förälder
incheckning
4ba591ee0b

+ 66 - 0
src/Renci.SshNet.Benchmarks/Common/HostKeyEventArgsBenchmarks.cs

@@ -0,0 +1,66 @@
+using BenchmarkDotNet.Attributes;
+
+using Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers;
+using Renci.SshNet.Common;
+using Renci.SshNet.Security;
+
+namespace Renci.SshNet.Benchmarks.Common
+{
+    [MemoryDiagnoser]
+    [ShortRunJob]
+    public class HostKeyEventArgsBenchmarks
+    {
+        private readonly KeyHostAlgorithm _keyHostAlgorithm;
+
+        public HostKeyEventArgsBenchmarks()
+        {
+            _keyHostAlgorithm = GetKeyHostAlgorithm();
+        }
+        private static KeyHostAlgorithm GetKeyHostAlgorithm()
+        {
+            using (var s = typeof(RsaCipherBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.RSA.txt"))
+            {
+                var privateKey = new PrivateKeyFile(s);
+                return (KeyHostAlgorithm) privateKey.HostKeyAlgorithms.First();
+            }
+        }
+
+        [Benchmark()]
+        public HostKeyEventArgs Constructor()
+        {
+            return new HostKeyEventArgs(_keyHostAlgorithm);
+        }
+
+        [Benchmark()]
+        public (string, string) CalculateFingerPrintSHA256AndMD5()
+        {
+            var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+            return (test.FingerPrintSHA256, test.FingerPrintMD5);
+        }
+
+        [Benchmark()]
+        public string CalculateFingerPrintSHA256()
+        {
+            var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+            return test.FingerPrintSHA256;
+        }
+
+        [Benchmark()]
+        public byte[] CalculateFingerPrint()
+        {
+            var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+            return test.FingerPrint;
+        }
+
+        [Benchmark()]
+        public string CalculateFingerPrintMD5()
+        {
+            var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+            return test.FingerPrintSHA256;
+        }
+    }
+}

+ 47 - 0
src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs

@@ -380,6 +380,53 @@ namespace Renci.SshNet.IntegrationTests
             Assert.IsTrue(hostValidationSuccessful);
         }
 
+        [TestMethod]
+        public void Common_HostKeyValidationSHA256_Success()
+        {
+            var hostValidationSuccessful = false;
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.HostKeyReceived += (sender, e) =>
+                {
+                    if (e.FingerPrintSHA256 == "9fa6vbz64gimzsGZ/xZi3aaYE1o7E96iU2NjcfQNGwI")
+                    {
+                        hostValidationSuccessful = e.CanTrust;
+                    }
+                    else
+                    {
+                        e.CanTrust = false;
+                    }
+                };
+                client.Connect();
+            }
+
+            Assert.IsTrue(hostValidationSuccessful);
+        }
+
+        [TestMethod]
+        public void Common_HostKeyValidationMD5_Success()
+        {
+            var hostValidationSuccessful = false;
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.HostKeyReceived += (sender, e) =>
+                {
+                    if (e.FingerPrintMD5 == "3d:90:d8:0d:d5:e0:b6:13:42:7c:78:1e:19:a3:99:2b")
+                    {
+                        hostValidationSuccessful = e.CanTrust;
+                    }
+                    else
+                    {
+                        e.CanTrust = false;
+                    }
+                };
+                client.Connect();
+            }
+
+            Assert.IsTrue(hostValidationSuccessful);
+        }
         /// <summary>
         /// Verifies whether we handle a disconnect initiated by the SSH server (through a SSH_MSG_DISCONNECT message).
         /// </summary>

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

@@ -14,6 +14,8 @@ 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 && \
+    # Set the default RSA key
+    echo 'HostKey /etc/ssh/ssh_host_rsa_key' >> /etc/ssh/sshd_config && \
     chmod 646 /etc/ssh/sshd_config && \
     # install and configure sudo
     apk add --no-cache sudo && \
@@ -45,4 +47,4 @@ RUN apk update && apk upgrade --no-cache && \
 
 EXPOSE 22 22
 
-ENTRYPOINT ["/opt/sshnet/start.sh"]
+ENTRYPOINT ["/opt/sshnet/start.sh"]

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

@@ -56,6 +56,8 @@ namespace Renci.SshNet.Tests.Classes.Common
             Assert.IsTrue(new byte[] {
                 0x92, 0xea, 0x54, 0xa1, 0x01, 0xf9, 0x95, 0x9c, 0x71, 0xd9, 0xbb, 0x51, 0xb2, 0x55, 0xf8, 0xd9
             }.SequenceEqual(target.FingerPrint));
+            Assert.AreEqual("92:ea:54:a1:01:f9:95:9c:71:d9:bb:51:b2:55:f8:d9", target.FingerPrintMD5);
+
         }
 
         /// <summary>

+ 47 - 10
src/Renci.SshNet/Common/HostKeyEventArgs.cs

@@ -1,4 +1,5 @@
 using System;
+
 using Renci.SshNet.Abstractions;
 using Renci.SshNet.Security;
 
@@ -9,6 +10,10 @@ namespace Renci.SshNet.Common
     /// </summary>
     public class HostKeyEventArgs : EventArgs
     {
+        private readonly Lazy<byte[]> _lazyFingerPrint;
+        private readonly Lazy<string> _lazyFingerPrintSHA256;
+        private readonly Lazy<string> _lazyFingerPrintMD5;
+
         /// <summary>
         /// Gets or sets a value indicating whether host key can be trusted.
         /// </summary>
@@ -33,15 +38,42 @@ namespace Renci.SshNet.Common
         /// <value>
         /// MD5 fingerprint as byte array.
         /// </value>
-        public byte[] FingerPrint { get; private set; }
+        public byte[] FingerPrint
+        {
+            get
+            {
+                return _lazyFingerPrint.Value;
+            }
+        }
 
         /// <summary>
-        /// Gets the SHA256 fingerprint.
+        /// Gets the SHA256 fingerprint of the host key in the same format as the ssh command,
+        /// i.e. non-padded base64, but without the <c>SHA256:</c> prefix.
         /// </summary>
+        /// <example><c>ohD8VZEXGWo6Ez8GSEJQ9WpafgLFsOfLOtGGQCQo6Og</c></example>
         /// <value>
         /// Base64 encoded SHA256 fingerprint with padding (equals sign) removed.
         /// </value>
-        public string FingerPrintSHA256 { get; private set; }
+        public string FingerPrintSHA256
+        {
+            get
+            {
+                return _lazyFingerPrintSHA256.Value;
+            }
+        }
+
+        /// <summary>
+        /// Gets the MD5 fingerprint of the host key in the same format as the ssh command,
+        /// i.e. hexadecimal bytes separated by colons, but without the <c>MD5:</c> prefix.
+        /// </summary>
+        /// <example><c>97:70:33:82:fd:29:3a:73:39:af:6a:07:ad:f8:80:49</c></example>
+        public string FingerPrintMD5
+        {
+            get
+            {
+                return _lazyFingerPrintMD5.Value;
+            }
+        }
 
         /// <summary>
         /// Gets the length of the key in bits.
@@ -61,16 +93,21 @@ namespace Renci.SshNet.Common
             HostKey = host.Data;
             HostKeyName = host.Name;
             KeyLength = host.Key.KeyLength;
-
-            using (var md5 = CryptoAbstraction.CreateMD5())
+            
+            _lazyFingerPrint = new Lazy<byte[]>(() =>
             {
-                FingerPrint = md5.ComputeHash(host.Data);
-            }
+                using var md5 = CryptoAbstraction.CreateMD5();
+                return md5.ComputeHash(HostKey);
+            });
 
-            using (var sha256 = CryptoAbstraction.CreateSHA256())
+            _lazyFingerPrintSHA256 = new Lazy<string>(() =>
             {
-                FingerPrintSHA256 = Convert.ToBase64String(sha256.ComputeHash(host.Data)).Replace("=", "");
-            }
+                using var sha256 = CryptoAbstraction.CreateSHA256();
+                return Convert.ToBase64String(sha256.ComputeHash(HostKey)).Replace("=", "");
+            });
+
+            _lazyFingerPrintMD5 = new Lazy<string>(() => 
+                BitConverter.ToString(FingerPrint).Replace("-", ":").ToLowerInvariant());
         }
     }
 }