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:
Rob Hague
2023-09-23 07:09:41 +02:00
committed by GitHub
parent 43329eef28
commit 8732d3d7ef
34 changed files with 770 additions and 213 deletions
@@ -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
};
}
}
}
+2 -2
View File
@@ -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);
}
}
}
+4
View File
@@ -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; }
}
}
+14 -3
View File
@@ -1,15 +1,26 @@
using Renci.SshNet.Security;
using System;
using System.ComponentModel;
using Renci.SshNet.Security;
namespace Renci.SshNet
{
/// <summary>
/// Represents private key source interface.
/// </summary>
public interface IPrivateKeySource
/// <remarks>
/// This interface has been replaced by <see cref="IHostAlgorithmsProvider"/>
/// and is obsolete.
/// </remarks>
[Obsolete($"Use {nameof(IHostAlgorithmsProvider)} instead. " +
$"{nameof(IPrivateKeySource)} may be removed in a future release. " +
$"See https://github.com/sshnet/SSH.NET/issues/1174 for details.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IPrivateKeySource : IHostAlgorithmsProvider
{
/// <summary>
/// Gets the host key.
/// </summary>
HostAlgorithm HostKey { get; }
}
}
}
+2 -2
View File
@@ -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;
}
+9 -9
View File
@@ -17,7 +17,7 @@ namespace Renci.SshNet
/// <summary>
/// Gets the key files used for authentication.
/// </summary>
public ICollection<IPrivateKeySource> KeyFiles { get; private set; }
public ICollection<IHostAlgorithmsProvider> KeyFiles { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="PrivateKeyConnectionInfo"/> class.
@@ -41,7 +41,7 @@ namespace Renci.SshNet
/// <param name="port">Connection port.</param>
/// <param name="username">Connection username.</param>
/// <param name="keyFiles">Connection key files.</param>
public PrivateKeyConnectionInfo(string host, int port, string username, params IPrivateKeySource[] keyFiles)
public PrivateKeyConnectionInfo(string host, int port, string username, params IHostAlgorithmsProvider[] keyFiles)
: this(host, port, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty, keyFiles)
{
}
@@ -56,7 +56,7 @@ namespace Renci.SshNet
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
/// <param name="keyFiles">The key files.</param>
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IPrivateKeySource[] keyFiles)
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IHostAlgorithmsProvider[] keyFiles)
: this(host, port, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, keyFiles)
{
}
@@ -72,7 +72,7 @@ namespace Renci.SshNet
/// <param name="proxyPort">The proxy port.</param>
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="keyFiles">The key files.</param>
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IPrivateKeySource[] keyFiles)
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IHostAlgorithmsProvider[] keyFiles)
: this(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, keyFiles)
{
}
@@ -86,7 +86,7 @@ namespace Renci.SshNet
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
/// <param name="keyFiles">The key files.</param>
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IPrivateKeySource[] keyFiles)
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IHostAlgorithmsProvider[] keyFiles)
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, keyFiles)
{
}
@@ -101,7 +101,7 @@ namespace Renci.SshNet
/// <param name="proxyPort">The proxy port.</param>
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="keyFiles">The key files.</param>
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IPrivateKeySource[] keyFiles)
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IHostAlgorithmsProvider[] keyFiles)
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, keyFiles)
{
}
@@ -117,7 +117,7 @@ namespace Renci.SshNet
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="proxyPassword">The proxy password.</param>
/// <param name="keyFiles">The key files.</param>
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IPrivateKeySource[] keyFiles)
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IHostAlgorithmsProvider[] keyFiles)
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, keyFiles)
{
}
@@ -134,10 +134,10 @@ namespace Renci.SshNet
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="proxyPassword">The proxy password.</param>
/// <param name="keyFiles">The key files.</param>
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IPrivateKeySource[] keyFiles)
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IHostAlgorithmsProvider[] keyFiles)
: base(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, new PrivateKeyAuthenticationMethod(username, keyFiles))
{
KeyFiles = new Collection<IPrivateKeySource>(keyFiles);
KeyFiles = new Collection<IHostAlgorithmsProvider>(keyFiles);
}
/// <summary>
+59 -7
View File
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
@@ -63,18 +65,48 @@ namespace Renci.SshNet
/// </list>
/// </para>
/// </remarks>
public class PrivateKeyFile : IPrivateKeySource, IDisposable
public class PrivateKeyFile : IHostAlgorithmsProvider,
#pragma warning disable CS0618 // Type or member is obsolete
IPrivateKeySource,
#pragma warning restore CS0618 // Type or member is obsolete
IDisposable
{
private static readonly Regex PrivateKeyRegex = new Regex(@"^-+ *BEGIN (?<keyName>\w+( \w+)*) PRIVATE KEY *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[A-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?<data>([a-zA-Z0-9/+=]{1,80}\r?\n)+)-+ *END \k<keyName> PRIVATE KEY *-+",
RegexOptions.Compiled | RegexOptions.Multiline);
private readonly List<HostAlgorithm> _hostAlgorithms = new List<HostAlgorithm>();
private Key _key;
private bool _isDisposed;
/// <summary>
/// Gets the host key.
/// </summary>
public HostAlgorithm HostKey { get; private set; }
/// <remarks>
/// This property returns the first item in <see cref="HostAlgorithms"/>.
/// </remarks>
public HostAlgorithm HostKey
{
get
{
return _hostAlgorithms[0];
}
private set
{
Debug.Assert(_hostAlgorithms.Count == 0, $"Only expected to set {nameof(HostKey)} at most once.");
_hostAlgorithms.Add(value);
}
}
/// <summary>
/// The supported host algorithms for this key file.
/// </summary>
public IReadOnlyCollection<HostAlgorithm> HostAlgorithms
{
get
{
return _hostAlgorithms;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
@@ -92,6 +124,7 @@ namespace Renci.SshNet
public PrivateKeyFile(Stream privateKey)
{
Open(privateKey, passPhrase: null);
Debug.Assert(_hostAlgorithms.Count > 0, $"{nameof(HostKey)} is not set.");
}
/// <summary>
@@ -127,6 +160,8 @@ namespace Renci.SshNet
{
Open(keyFile, passPhrase);
}
Debug.Assert(_hostAlgorithms.Count > 0, $"{nameof(HostKey)} is not set.");
}
/// <summary>
@@ -138,6 +173,7 @@ namespace Renci.SshNet
public PrivateKeyFile(Stream privateKey, string passPhrase)
{
Open(privateKey, passPhrase);
Debug.Assert(_hostAlgorithms.Count > 0, $"{nameof(HostKey)} is not set.");
}
/// <summary>
@@ -222,8 +258,11 @@ namespace Renci.SshNet
switch (keyName)
{
case "RSA":
_key = new RsaKey(decryptedData);
HostKey = new KeyHostAlgorithm("ssh-rsa", _key);
var rsaKey = new RsaKey(decryptedData);
_key = rsaKey;
_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA512)));
_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA256)));
_hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key));
break;
case "DSA":
_key = new DsaKey(decryptedData);
@@ -235,7 +274,17 @@ namespace Renci.SshNet
break;
case "OPENSSH":
_key = ParseOpenSshV1Key(decryptedData, passPhrase);
HostKey = new KeyHostAlgorithm(_key.ToString(), _key);
if (_key is RsaKey parsedRsaKey)
{
_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(parsedRsaKey, HashAlgorithmName.SHA512)));
_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(parsedRsaKey, HashAlgorithmName.SHA256)));
_hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key));
}
else
{
HostKey = new KeyHostAlgorithm(_key.ToString(), _key);
}
break;
case "SSH2 ENCRYPTED":
var reader = new SshDataReader(decryptedData);
@@ -290,8 +339,11 @@ namespace Renci.SshNet
var inverseQ = reader.ReadBigIntWithBits(); // u
var q = reader.ReadBigIntWithBits(); // p
var p = reader.ReadBigIntWithBits(); // q
_key = new RsaKey(modulus, exponent, d, p, q, inverseQ);
HostKey = new KeyHostAlgorithm("ssh-rsa", _key);
var decryptedRsaKey = new RsaKey(modulus, exponent, d, p, q, inverseQ);
_key = decryptedRsaKey;
_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(decryptedRsaKey, HashAlgorithmName.SHA512)));
_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(decryptedRsaKey, HashAlgorithmName.SHA256)));
_hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key));
}
else if (keyType == "dl-modp{sign{dsa-nist-sha1},dh{plain}}")
{
+2 -2
View File
@@ -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
{
+22
View File
@@ -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>
+2 -18
View File
@@ -1,7 +1,4 @@
using System.Text;
using Renci.SshNet.Common;
using Renci.SshNet.Messages.Transport;
using Renci.SshNet.Messages.Transport;
namespace Renci.SshNet.Security
{
@@ -76,20 +73,7 @@ namespace Renci.SshNet.Security
/// </returns>
protected override bool ValidateExchangeHash()
{
var exchangeHash = CalculateHash();
var length = Pack.BigEndianToUInt32(_hostKey);
var algorithmName = Encoding.UTF8.GetString(_hostKey, 4, (int)length);
var key = Session.ConnectionInfo.HostKeyAlgorithms[algorithmName](_hostKey);
Session.ConnectionInfo.CurrentHostKeyAlgorithm = algorithmName;
if (CanTrustHostKey(key))
{
return key.VerifySignature(exchangeHash, _signature);
}
return false;
return ValidateExchangeHash(_hostKey, _signature);
}
/// <summary>
+95 -25
View File
@@ -1,6 +1,9 @@
using System.Collections.Generic;
using System.Text;
using Renci.SshNet.Common;
using Renci.SshNet.Security.Chaos.NaCl;
using Renci.SshNet.Security.Cryptography;
namespace Renci.SshNet.Security
{
@@ -10,38 +13,76 @@ namespace Renci.SshNet.Security
public class KeyHostAlgorithm : HostAlgorithm
{
/// <summary>
/// Gets the key.
/// The key used in this host key algorithm.
/// </summary>
public Key Key { get; private set; }
/// <summary>
/// Gets the public key data.
/// The signature implementation used in this host key algorithm.
/// </summary>
public DigitalSignature DigitalSignature { get; private set; }
/// <summary>
/// Gets the encoded public key data.
/// </summary>
public override byte[] Data
{
get
{
return new SshKeyData(Name, Key.Public).GetBytes();
var keyFormatIdentifier = Key is RsaKey ? "ssh-rsa" : Name;
return new SshKeyData(keyFormatIdentifier, Key.Public).GetBytes();
}
}
/// <summary>
/// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class.
/// </summary>
/// <param name="name">Host key name.</param>
/// <param name="key">Host key.</param>
/// <param name="name">The signature format identifier.</param>
/// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
/// <remarks>
/// This constructor is typically passed a private key in order to create an encoded signature for later
/// verification by the host.
/// </remarks>
public KeyHostAlgorithm(string name, Key key)
: base(name)
{
Key = key;
DigitalSignature = key.DigitalSignature;
}
/// <summary>
/// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class.
/// </summary>
/// <param name="name">Host key name.</param>
/// <param name="key">Host key.</param>
/// <param name="name">The signature format identifier.</param>
/// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
/// <param name="digitalSignature"><inheritdoc cref="DigitalSignature" path="/summary"/></param>
/// <remarks>
/// <para>
/// This constructor is typically passed a private key in order to create an encoded signature for later
/// verification by the host.
/// </para>
/// The key used by <paramref name="digitalSignature"/> is intended to be equal to <paramref name="key"/>.
/// This is not verified.
/// </remarks>
public KeyHostAlgorithm(string name, Key key, DigitalSignature digitalSignature)
: base(name)
{
Key = key;
DigitalSignature = digitalSignature;
}
/// <summary>
/// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class
/// with the given encoded public key data. The data will be decoded into <paramref name="key"/>.
/// </summary>
/// <param name="name">The signature format identifier.</param>
/// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
/// <param name="data">Host key encoded data.</param>
/// <remarks>
/// This constructor is typically passed a new or reusable <see cref="Security.Key"/> instance in
/// order to verify an encoded signature sent by the host, created by the private counterpart
/// to the host's public key, which is encoded in <paramref name="data"/>.
/// </remarks>
public KeyHostAlgorithm(string name, Key key, byte[] data)
: base(name)
{
@@ -50,34 +91,66 @@ namespace Renci.SshNet.Security
var sshKey = new SshKeyData();
sshKey.Load(data);
Key.Public = sshKey.Keys;
DigitalSignature = key.DigitalSignature;
}
/// <summary>
/// Signs the specified data.
/// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class
/// with the given encoded public key data. The data will be decoded into <paramref name="key"/>.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="name">The signature format identifier.</param>
/// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
/// <param name="data">Host key encoded data.</param>
/// <param name="digitalSignature"><inheritdoc cref="DigitalSignature" path="/summary"/></param>
/// <remarks>
/// <para>
/// This constructor is typically passed a new or reusable <see cref="Security.Key"/> instance in
/// order to verify an encoded signature sent by the host, created by the private counterpart
/// to the host's public key, which is encoded in <paramref name="data"/>.
/// </para>
/// The key used by <paramref name="digitalSignature"/> is intended to be equal to <paramref name="key"/>.
/// This is not verified.
/// </remarks>
public KeyHostAlgorithm(string name, Key key, byte[] data, DigitalSignature digitalSignature)
: base(name)
{
Key = key;
var sshKey = new SshKeyData();
sshKey.Load(data);
Key.Public = sshKey.Keys;
DigitalSignature = digitalSignature;
}
/// <summary>
/// Signs and encodes the specified data.
/// </summary>
/// <param name="data">The data to be signed.</param>
/// <returns>
/// Signed data.
/// The encoded signature.
/// </returns>
public override byte[] Sign(byte[] data)
{
return new SignatureKeyData(Name, Key.Sign(data)).GetBytes();
return new SignatureKeyData(Name, DigitalSignature.Sign(data)).GetBytes();
}
/// <summary>
/// Verifies the signature.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="signature">The signature.</param>
/// <param name="data">The data to verify the signature against.</param>
/// <param name="signature">The encoded signature data.</param>
/// <returns>
/// <c>True</c> is signature was successfully verifies; otherwise <c>false</c>.
/// <see langword="true"/> if <paramref name="signature"/> is the result of signing <paramref name="data"/>
/// with the corresponding private key to <see cref="Key"/>.
/// </returns>
public override bool VerifySignature(byte[] data, byte[] signature)
{
var signatureData = new SignatureKeyData();
signatureData.Load(signature);
return Key.VerifySignature(data, signatureData.Signature);
return DigitalSignature.Verify(data, signatureData.Signature);
}
private sealed class SshKeyData : SshData
@@ -170,15 +243,12 @@ namespace Renci.SshNet.Security
}
}
private sealed class SignatureKeyData : SshData
internal sealed class SignatureKeyData : SshData
{
/// <summary>
/// Gets or sets the name of the algorithm as UTF-8 encoded byte array.
/// Gets or sets the signature format identifier
/// </summary>
/// <value>
/// The name of the algorithm.
/// </value>
private byte[] AlgorithmName { get; set; }
public string AlgorithmName { get; set; }
/// <summary>
/// Gets the signature.
@@ -200,7 +270,7 @@ namespace Renci.SshNet.Security
{
var capacity = base.BufferCapacity;
capacity += 4; // AlgorithmName length
capacity += AlgorithmName.Length; // AlgorithmName
capacity += Encoding.UTF8.GetByteCount(AlgorithmName); // AlgorithmName
capacity += 4; // Signature length
capacity += Signature.Length; // Signature
return capacity;
@@ -213,7 +283,7 @@ namespace Renci.SshNet.Security
public SignatureKeyData(string name, byte[] signature)
{
AlgorithmName = Utf8.GetBytes(name);
AlgorithmName = name;
Signature = signature;
}
@@ -222,7 +292,7 @@ namespace Renci.SshNet.Security
/// </summary>
protected override void LoadData()
{
AlgorithmName = ReadBinary();
AlgorithmName = Encoding.UTF8.GetString(ReadBinary());
Signature = ReadBinary();
}
@@ -231,7 +301,7 @@ namespace Renci.SshNet.Security
/// </summary>
protected override void SaveData()
{
WriteBinaryString(AlgorithmName);
WriteBinaryString(Encoding.UTF8.GetBytes(AlgorithmName));
WriteBinaryString(Signature);
}
}
+2 -2
View File
@@ -218,7 +218,7 @@ namespace Renci.SshNet
/// <exception cref="ArgumentException"><paramref name="host"/> is invalid. <para>-or-</para> <paramref name="username"/> is nu<b>null</b>ll or contains only whitespace characters.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
[SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
public SftpClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
public SftpClient(string host, int port, string username, params IHostAlgorithmsProvider[] keyFiles)
: this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true)
{
}
@@ -231,7 +231,7 @@ namespace Renci.SshNet
/// <param name="keyFiles">Authentication private key file(s) .</param>
/// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <b>null</b>.</exception>
/// <exception cref="ArgumentException"><paramref name="host"/> is invalid. <para>-or-</para> <paramref name="username"/> is <b>null</b> or contains only whitespace characters.</exception>
public SftpClient(string host, string username, params IPrivateKeySource[] keyFiles)
public SftpClient(string host, string username, params IHostAlgorithmsProvider[] keyFiles)
: this(host, ConnectionInfo.DefaultPort, username, keyFiles)
{
}
+2 -2
View File
@@ -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)
{
}