Skip to content

FingerPrints improvements #1186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 changes: 47 additions & 0 deletions src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,53 @@ public void Common_HostKeyValidation_Success()
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>
Expand Down
4 changes: 3 additions & 1 deletion src/Renci.SshNet.IntegrationTests/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 && \
Expand Down Expand Up @@ -45,4 +47,4 @@ RUN apk update && apk upgrade --no-cache && \

EXPOSE 22 22

ENTRYPOINT ["/opt/sshnet/start.sh"]
ENTRYPOINT ["/opt/sshnet/start.sh"]
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public void HostKeyEventArgsConstructorTest_VerifyMD5()
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>
Expand Down
57 changes: 47 additions & 10 deletions src/Renci.SshNet/Common/HostKeyEventArgs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;

using Renci.SshNet.Abstractions;
using Renci.SshNet.Security;

Expand All @@ -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>
Expand All @@ -33,15 +38,42 @@ public class HostKeyEventArgs : EventArgs
/// <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.
Expand All @@ -61,16 +93,21 @@ public HostKeyEventArgs(KeyHostAlgorithm host)
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());
}
}
}