Skip to content

Commit 4ba591e

Browse files
FingerPrints (#1186)
1 parent fdd1130 commit 4ba591e

File tree

5 files changed

+165
-11
lines changed

5 files changed

+165
-11
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using BenchmarkDotNet.Attributes;
2+
3+
using Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers;
4+
using Renci.SshNet.Common;
5+
using Renci.SshNet.Security;
6+
7+
namespace Renci.SshNet.Benchmarks.Common
8+
{
9+
[MemoryDiagnoser]
10+
[ShortRunJob]
11+
public class HostKeyEventArgsBenchmarks
12+
{
13+
private readonly KeyHostAlgorithm _keyHostAlgorithm;
14+
15+
public HostKeyEventArgsBenchmarks()
16+
{
17+
_keyHostAlgorithm = GetKeyHostAlgorithm();
18+
}
19+
private static KeyHostAlgorithm GetKeyHostAlgorithm()
20+
{
21+
using (var s = typeof(RsaCipherBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.RSA.txt"))
22+
{
23+
var privateKey = new PrivateKeyFile(s);
24+
return (KeyHostAlgorithm) privateKey.HostKeyAlgorithms.First();
25+
}
26+
}
27+
28+
[Benchmark()]
29+
public HostKeyEventArgs Constructor()
30+
{
31+
return new HostKeyEventArgs(_keyHostAlgorithm);
32+
}
33+
34+
[Benchmark()]
35+
public (string, string) CalculateFingerPrintSHA256AndMD5()
36+
{
37+
var test = new HostKeyEventArgs(_keyHostAlgorithm);
38+
39+
return (test.FingerPrintSHA256, test.FingerPrintMD5);
40+
}
41+
42+
[Benchmark()]
43+
public string CalculateFingerPrintSHA256()
44+
{
45+
var test = new HostKeyEventArgs(_keyHostAlgorithm);
46+
47+
return test.FingerPrintSHA256;
48+
}
49+
50+
[Benchmark()]
51+
public byte[] CalculateFingerPrint()
52+
{
53+
var test = new HostKeyEventArgs(_keyHostAlgorithm);
54+
55+
return test.FingerPrint;
56+
}
57+
58+
[Benchmark()]
59+
public string CalculateFingerPrintMD5()
60+
{
61+
var test = new HostKeyEventArgs(_keyHostAlgorithm);
62+
63+
return test.FingerPrintSHA256;
64+
}
65+
}
66+
}

src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,53 @@ public void Common_HostKeyValidation_Success()
380380
Assert.IsTrue(hostValidationSuccessful);
381381
}
382382

383+
[TestMethod]
384+
public void Common_HostKeyValidationSHA256_Success()
385+
{
386+
var hostValidationSuccessful = false;
387+
388+
using (var client = new SshClient(_connectionInfoFactory.Create()))
389+
{
390+
client.HostKeyReceived += (sender, e) =>
391+
{
392+
if (e.FingerPrintSHA256 == "9fa6vbz64gimzsGZ/xZi3aaYE1o7E96iU2NjcfQNGwI")
393+
{
394+
hostValidationSuccessful = e.CanTrust;
395+
}
396+
else
397+
{
398+
e.CanTrust = false;
399+
}
400+
};
401+
client.Connect();
402+
}
403+
404+
Assert.IsTrue(hostValidationSuccessful);
405+
}
406+
407+
[TestMethod]
408+
public void Common_HostKeyValidationMD5_Success()
409+
{
410+
var hostValidationSuccessful = false;
411+
412+
using (var client = new SshClient(_connectionInfoFactory.Create()))
413+
{
414+
client.HostKeyReceived += (sender, e) =>
415+
{
416+
if (e.FingerPrintMD5 == "3d:90:d8:0d:d5:e0:b6:13:42:7c:78:1e:19:a3:99:2b")
417+
{
418+
hostValidationSuccessful = e.CanTrust;
419+
}
420+
else
421+
{
422+
e.CanTrust = false;
423+
}
424+
};
425+
client.Connect();
426+
}
427+
428+
Assert.IsTrue(hostValidationSuccessful);
429+
}
383430
/// <summary>
384431
/// Verifies whether we handle a disconnect initiated by the SSH server (through a SSH_MSG_DISCONNECT message).
385432
/// </summary>

src/Renci.SshNet.IntegrationTests/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ RUN apk update && apk upgrade --no-cache && \
1414
chmod 400 /etc/ssh/ssh*key && \
1515
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
1616
sed -i 's/#LogLevel\s*INFO/LogLevel DEBUG3/' /etc/ssh/sshd_config && \
17+
# Set the default RSA key
18+
echo 'HostKey /etc/ssh/ssh_host_rsa_key' >> /etc/ssh/sshd_config && \
1719
chmod 646 /etc/ssh/sshd_config && \
1820
# install and configure sudo
1921
apk add --no-cache sudo && \
@@ -45,4 +47,4 @@ RUN apk update && apk upgrade --no-cache && \
4547

4648
EXPOSE 22 22
4749

48-
ENTRYPOINT ["/opt/sshnet/start.sh"]
50+
ENTRYPOINT ["/opt/sshnet/start.sh"]

src/Renci.SshNet.Tests/Classes/Common/HostKeyEventArgsTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public void HostKeyEventArgsConstructorTest_VerifyMD5()
5656
Assert.IsTrue(new byte[] {
5757
0x92, 0xea, 0x54, 0xa1, 0x01, 0xf9, 0x95, 0x9c, 0x71, 0xd9, 0xbb, 0x51, 0xb2, 0x55, 0xf8, 0xd9
5858
}.SequenceEqual(target.FingerPrint));
59+
Assert.AreEqual("92:ea:54:a1:01:f9:95:9c:71:d9:bb:51:b2:55:f8:d9", target.FingerPrintMD5);
60+
5961
}
6062

6163
/// <summary>

src/Renci.SshNet/Common/HostKeyEventArgs.cs

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
23
using Renci.SshNet.Abstractions;
34
using Renci.SshNet.Security;
45

@@ -9,6 +10,10 @@ namespace Renci.SshNet.Common
910
/// </summary>
1011
public class HostKeyEventArgs : EventArgs
1112
{
13+
private readonly Lazy<byte[]> _lazyFingerPrint;
14+
private readonly Lazy<string> _lazyFingerPrintSHA256;
15+
private readonly Lazy<string> _lazyFingerPrintMD5;
16+
1217
/// <summary>
1318
/// Gets or sets a value indicating whether host key can be trusted.
1419
/// </summary>
@@ -33,15 +38,42 @@ public class HostKeyEventArgs : EventArgs
3338
/// <value>
3439
/// MD5 fingerprint as byte array.
3540
/// </value>
36-
public byte[] FingerPrint { get; private set; }
41+
public byte[] FingerPrint
42+
{
43+
get
44+
{
45+
return _lazyFingerPrint.Value;
46+
}
47+
}
3748

3849
/// <summary>
39-
/// Gets the SHA256 fingerprint.
50+
/// Gets the SHA256 fingerprint of the host key in the same format as the ssh command,
51+
/// i.e. non-padded base64, but without the <c>SHA256:</c> prefix.
4052
/// </summary>
53+
/// <example><c>ohD8VZEXGWo6Ez8GSEJQ9WpafgLFsOfLOtGGQCQo6Og</c></example>
4154
/// <value>
4255
/// Base64 encoded SHA256 fingerprint with padding (equals sign) removed.
4356
/// </value>
44-
public string FingerPrintSHA256 { get; private set; }
57+
public string FingerPrintSHA256
58+
{
59+
get
60+
{
61+
return _lazyFingerPrintSHA256.Value;
62+
}
63+
}
64+
65+
/// <summary>
66+
/// Gets the MD5 fingerprint of the host key in the same format as the ssh command,
67+
/// i.e. hexadecimal bytes separated by colons, but without the <c>MD5:</c> prefix.
68+
/// </summary>
69+
/// <example><c>97:70:33:82:fd:29:3a:73:39:af:6a:07:ad:f8:80:49</c></example>
70+
public string FingerPrintMD5
71+
{
72+
get
73+
{
74+
return _lazyFingerPrintMD5.Value;
75+
}
76+
}
4577

4678
/// <summary>
4779
/// Gets the length of the key in bits.
@@ -61,16 +93,21 @@ public HostKeyEventArgs(KeyHostAlgorithm host)
6193
HostKey = host.Data;
6294
HostKeyName = host.Name;
6395
KeyLength = host.Key.KeyLength;
64-
65-
using (var md5 = CryptoAbstraction.CreateMD5())
96+
97+
_lazyFingerPrint = new Lazy<byte[]>(() =>
6698
{
67-
FingerPrint = md5.ComputeHash(host.Data);
68-
}
99+
using var md5 = CryptoAbstraction.CreateMD5();
100+
return md5.ComputeHash(HostKey);
101+
});
69102

70-
using (var sha256 = CryptoAbstraction.CreateSHA256())
103+
_lazyFingerPrintSHA256 = new Lazy<string>(() =>
71104
{
72-
FingerPrintSHA256 = Convert.ToBase64String(sha256.ComputeHash(host.Data)).Replace("=", "");
73-
}
105+
using var sha256 = CryptoAbstraction.CreateSHA256();
106+
return Convert.ToBase64String(sha256.ComputeHash(HostKey)).Replace("=", "");
107+
});
108+
109+
_lazyFingerPrintMD5 = new Lazy<string>(() =>
110+
BitConverter.ToString(FingerPrint).Replace("-", ":").ToLowerInvariant());
74111
}
75112
}
76113
}

0 commit comments

Comments
 (0)