mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 17:25:51 +00:00
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>
This commit is contained in:
@@ -20,9 +20,7 @@ namespace Renci.SshNet.IntegrationTests.Common
|
||||
.ClearCiphers()
|
||||
.ClearKeyExchangeAlgorithms()
|
||||
.ClearHostKeyAlgorithms()
|
||||
.AddHostKeyAlgorithm(HostKeyAlgorithm.SshRsa)
|
||||
.ClearPublicKeyAcceptedAlgorithms()
|
||||
.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.SshRsa)
|
||||
.WithUsePAM(true)
|
||||
.Update()
|
||||
.Restart();
|
||||
|
||||
@@ -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 && \
|
||||
|
||||
@@ -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();
|
||||
DoTest(HostKeyAlgorithm.SshRsa, HostKeyFile.Rsa, 3072);
|
||||
}
|
||||
|
||||
HostKeyEventArgs hostKeyEventsArgs = null;
|
||||
[TestMethod]
|
||||
public void SshRsaSha256()
|
||||
{
|
||||
DoTest(HostKeyAlgorithm.RsaSha2256, HostKeyFile.Rsa, 3072);
|
||||
}
|
||||
|
||||
using (var client = new SshClient(_connectionInfoFactory.Create()))
|
||||
{
|
||||
client.HostKeyReceived += (sender, e) => hostKeyEventsArgs = e;
|
||||
client.Connect();
|
||||
client.Disconnect();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Renci.SshNet.IntegrationTests
|
||||
{
|
||||
[TestClass]
|
||||
public class PrivateKeyAuthenticationTests : TestBase
|
||||
{
|
||||
{
|
||||
private IConnectionInfoFactory _connectionInfoFactory;
|
||||
private RemoteSshdConfig _remoteSshdConfig;
|
||||
|
||||
@@ -22,44 +22,65 @@ 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()
|
||||
{
|
||||
DoTest(PublicKeyAlgorithm.SshDss, "id_dsa");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SshRsa()
|
||||
{
|
||||
DoTest(PublicKeyAlgorithm.SshRsa, "id_rsa");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SshRsaSha256()
|
||||
{
|
||||
DoTest(PublicKeyAlgorithm.RsaSha2256, "id_rsa");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SshRsaSha512()
|
||||
{
|
||||
DoTest(PublicKeyAlgorithm.RsaSha2512, "id_rsa");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ecdsa256()
|
||||
{
|
||||
_remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp256)
|
||||
.Update()
|
||||
.Restart();
|
||||
|
||||
var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod("key_ecdsa_256_openssh"));
|
||||
|
||||
using (var client = new SshClient(connectionInfo))
|
||||
{
|
||||
client.Connect();
|
||||
}
|
||||
DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp256, "key_ecdsa_256_openssh");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ecdsa384()
|
||||
{
|
||||
_remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp384)
|
||||
.Update()
|
||||
.Restart();
|
||||
|
||||
var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod("key_ecdsa_384_openssh"));
|
||||
|
||||
using (var client = new SshClient(connectionInfo))
|
||||
{
|
||||
client.Connect();
|
||||
}
|
||||
DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp384, "key_ecdsa_384_openssh");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EcdsaA521()
|
||||
public void Ecdsa521()
|
||||
{
|
||||
_remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp521)
|
||||
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.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))
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
///A test for RsaDigitalSignature Constructor
|
||||
///</summary>
|
||||
[TestMethod]
|
||||
[Ignore] // placeholder for actual test
|
||||
public void RsaDigitalSignatureConstructorTest()
|
||||
public void Sha1_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); // 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));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Sha256_SignAndVerify()
|
||||
{
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) },
|
||||
};
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}}")
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace Renci.SshNet.Security
|
||||
/// <summary>
|
||||
/// Gets the digital signature.
|
||||
/// </summary>
|
||||
protected override DigitalSignature DigitalSignature
|
||||
protected internal override DigitalSignature DigitalSignature
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Renci.SshNet.Security
|
||||
/// <summary>
|
||||
/// Gets the digital signature.
|
||||
/// </summary>
|
||||
protected override DigitalSignature DigitalSignature
|
||||
protected internal override DigitalSignature DigitalSignature
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Renci.SshNet.Security
|
||||
/// <summary>
|
||||
/// Gets the digital signature.
|
||||
/// </summary>
|
||||
protected override DigitalSignature DigitalSignature
|
||||
protected internal override DigitalSignature DigitalSignature
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user