Skip to content

Commit 8732d3d

Browse files
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>
1 parent 43329ee commit 8732d3d

34 files changed

+767
-210
lines changed

src/Renci.SshNet.IntegrationTests/Common/RemoteSshdConfigExtensions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ public static void Reset(this RemoteSshdConfig remoteSshdConfig)
2020
.ClearCiphers()
2121
.ClearKeyExchangeAlgorithms()
2222
.ClearHostKeyAlgorithms()
23-
.AddHostKeyAlgorithm(HostKeyAlgorithm.SshRsa)
2423
.ClearPublicKeyAcceptedAlgorithms()
25-
.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.SshRsa)
2624
.WithUsePAM(true)
2725
.Update()
2826
.Restart();

src/Renci.SshNet.IntegrationTests/Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ RUN apk update && apk upgrade --no-cache && \
1414
chmod 400 /etc/ssh/ssh*key && \
1515
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
1616
sed -i 's/#LogLevel\s*INFO/LogLevel DEBUG3/' /etc/ssh/sshd_config && \
17-
echo 'PubkeyAcceptedAlgorithms ssh-rsa' >> /etc/ssh/sshd_config && \
1817
chmod 646 /etc/ssh/sshd_config && \
1918
# install and configure sudo
2019
apk add --no-cache sudo && \

src/Renci.SshNet.IntegrationTests/HostKeyAlgorithmTests.cs

Lines changed: 25 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -22,63 +22,46 @@ public void TearDown()
2222
{
2323
_remoteSshdConfig?.Reset();
2424
}
25-
25+
2626
[TestMethod]
2727
[Ignore] // No longer supported in recent versions of OpenSSH
28+
// TODO: We should be able to enable some legacy settings to make it work
29+
// https://www.openssh.com/legacy.html e.g. PubkeyAcceptedKeyTypes / HostbasedAcceptedKeyTypes ?
2830
public void SshDsa()
2931
{
30-
_remoteSshdConfig.ClearHostKeyAlgorithms()
31-
.AddHostKeyAlgorithm(HostKeyAlgorithm.SshDsa)
32-
.ClearHostKeyFiles()
33-
.AddHostKeyFile(HostKeyFile.Dsa.FilePath)
34-
.Update()
35-
.Restart();
36-
37-
HostKeyEventArgs hostKeyEventsArgs = null;
38-
39-
using (var client = new SshClient(_connectionInfoFactory.Create()))
40-
{
41-
client.HostKeyReceived += (sender, e) => hostKeyEventsArgs = e;
42-
client.Connect();
43-
client.Disconnect();
44-
}
45-
46-
Assert.IsNotNull(hostKeyEventsArgs);
47-
Assert.AreEqual(HostKeyFile.Dsa.KeyName, hostKeyEventsArgs.HostKeyName);
48-
Assert.AreEqual(1024, hostKeyEventsArgs.KeyLength);
49-
Assert.IsTrue(hostKeyEventsArgs.FingerPrint.SequenceEqual(HostKeyFile.Dsa.FingerPrint));
32+
DoTest(HostKeyAlgorithm.SshDsa, HostKeyFile.Dsa, 1024);
5033
}
5134

5235
[TestMethod]
5336
public void SshRsa()
5437
{
55-
_remoteSshdConfig.ClearHostKeyAlgorithms()
56-
.AddHostKeyAlgorithm(HostKeyAlgorithm.SshRsa)
57-
.Update()
58-
.Restart();
59-
60-
HostKeyEventArgs hostKeyEventsArgs = null;
38+
DoTest(HostKeyAlgorithm.SshRsa, HostKeyFile.Rsa, 3072);
39+
}
6140

62-
using (var client = new SshClient(_connectionInfoFactory.Create()))
63-
{
64-
client.HostKeyReceived += (sender, e) => hostKeyEventsArgs = e;
65-
client.Connect();
66-
client.Disconnect();
67-
}
41+
[TestMethod]
42+
public void SshRsaSha256()
43+
{
44+
DoTest(HostKeyAlgorithm.RsaSha2256, HostKeyFile.Rsa, 3072);
45+
}
6846

69-
Assert.IsNotNull(hostKeyEventsArgs);
70-
Assert.AreEqual(HostKeyFile.Rsa.KeyName, hostKeyEventsArgs.HostKeyName);
71-
Assert.AreEqual(3072, hostKeyEventsArgs.KeyLength);
72-
Assert.IsTrue(hostKeyEventsArgs.FingerPrint.SequenceEqual(HostKeyFile.Rsa.FingerPrint));
47+
[TestMethod]
48+
public void SshRsaSha512()
49+
{
50+
DoTest(HostKeyAlgorithm.RsaSha2512, HostKeyFile.Rsa, 3072);
7351
}
7452

7553
[TestMethod]
7654
public void SshEd25519()
55+
{
56+
DoTest(HostKeyAlgorithm.SshEd25519, HostKeyFile.Ed25519, 256);
57+
}
58+
59+
private void DoTest(HostKeyAlgorithm hostKeyAlgorithm, HostKeyFile hostKeyFile, int keyLength)
7760
{
7861
_remoteSshdConfig.ClearHostKeyAlgorithms()
79-
.AddHostKeyAlgorithm(HostKeyAlgorithm.SshEd25519)
62+
.AddHostKeyAlgorithm(hostKeyAlgorithm)
8063
.ClearHostKeyFiles()
81-
.AddHostKeyFile(HostKeyFile.Ed25519.FilePath)
64+
.AddHostKeyFile(hostKeyFile.FilePath)
8265
.Update()
8366
.Restart();
8467

@@ -92,14 +75,9 @@ public void SshEd25519()
9275
}
9376

9477
Assert.IsNotNull(hostKeyEventsArgs);
95-
Assert.AreEqual(HostKeyFile.Ed25519.KeyName, hostKeyEventsArgs.HostKeyName);
96-
Assert.AreEqual(256, hostKeyEventsArgs.KeyLength);
97-
Assert.IsTrue(hostKeyEventsArgs.FingerPrint.SequenceEqual(HostKeyFile.Ed25519.FingerPrint));
98-
}
99-
100-
private void Client_HostKeyReceived(object sender, HostKeyEventArgs e)
101-
{
102-
throw new NotImplementedException();
78+
Assert.AreEqual(hostKeyAlgorithm.Name, hostKeyEventsArgs.HostKeyName);
79+
Assert.AreEqual(keyLength, hostKeyEventsArgs.KeyLength);
80+
CollectionAssert.AreEqual(hostKeyFile.FingerPrint, hostKeyEventsArgs.FingerPrint);
10381
}
10482
}
10583
}

src/Renci.SshNet.IntegrationTests/PrivateKeyAuthenticationTests.cs

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Renci.SshNet.IntegrationTests
55
{
66
[TestClass]
77
public class PrivateKeyAuthenticationTests : TestBase
8-
{
8+
{
99
private IConnectionInfoFactory _connectionInfoFactory;
1010
private RemoteSshdConfig _remoteSshdConfig;
1111

@@ -23,43 +23,64 @@ public void TearDown()
2323
}
2424

2525
[TestMethod]
26-
public void Ecdsa256()
26+
[Ignore] // No longer supported in recent versions of OpenSSH
27+
// TODO: We should be able to enable some legacy settings to make it work
28+
// https://www.openssh.com/legacy.html e.g. PubkeyAcceptedKeyTypes / HostbasedAcceptedKeyTypes ?
29+
public void SshDsa()
2730
{
28-
_remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp256)
29-
.Update()
30-
.Restart();
31+
DoTest(PublicKeyAlgorithm.SshDss, "id_dsa");
32+
}
3133

32-
var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod("key_ecdsa_256_openssh"));
34+
[TestMethod]
35+
public void SshRsa()
36+
{
37+
DoTest(PublicKeyAlgorithm.SshRsa, "id_rsa");
38+
}
3339

34-
using (var client = new SshClient(connectionInfo))
35-
{
36-
client.Connect();
37-
}
40+
[TestMethod]
41+
public void SshRsaSha256()
42+
{
43+
DoTest(PublicKeyAlgorithm.RsaSha2256, "id_rsa");
3844
}
3945

4046
[TestMethod]
41-
public void Ecdsa384()
47+
public void SshRsaSha512()
4248
{
43-
_remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp384)
44-
.Update()
45-
.Restart();
49+
DoTest(PublicKeyAlgorithm.RsaSha2512, "id_rsa");
50+
}
4651

47-
var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod("key_ecdsa_384_openssh"));
52+
[TestMethod]
53+
public void Ecdsa256()
54+
{
55+
DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp256, "key_ecdsa_256_openssh");
56+
}
4857

49-
using (var client = new SshClient(connectionInfo))
50-
{
51-
client.Connect();
52-
}
58+
[TestMethod]
59+
public void Ecdsa384()
60+
{
61+
DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp384, "key_ecdsa_384_openssh");
5362
}
5463

5564
[TestMethod]
56-
public void EcdsaA521()
65+
public void Ecdsa521()
66+
{
67+
DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp521, "key_ecdsa_521_openssh");
68+
}
69+
70+
[TestMethod]
71+
public void Ed25519()
72+
{
73+
DoTest(PublicKeyAlgorithm.SshEd25519, "key_ed25519_openssh");
74+
}
75+
76+
private void DoTest(PublicKeyAlgorithm publicKeyAlgorithm, string keyResource)
5777
{
58-
_remoteSshdConfig.AddPublicKeyAcceptedAlgorithms(PublicKeyAlgorithm.EcdsaSha2Nistp521)
78+
_remoteSshdConfig.ClearPublicKeyAcceptedAlgorithms()
79+
.AddPublicKeyAcceptedAlgorithms(publicKeyAlgorithm)
5980
.Update()
6081
.Restart();
6182

62-
var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod("key_ecdsa_521_openssh"));
83+
var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod(keyResource));
6384

6485
using (var client = new SshClient(connectionInfo))
6586
{

src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
<EmbeddedResource Include="resources\client\key_ecdsa_256_openssh" />
6161
<EmbeddedResource Include="resources\client\key_ecdsa_384_openssh" />
6262
<EmbeddedResource Include="resources\client\key_ecdsa_521_openssh" />
63+
<EmbeddedResource Include="resources\client\key_ed25519_openssh" />
6364
<EmbeddedResource Include="resources\issue #70.png" />
6465
</ItemGroup>
6566
</Project>
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4N0T6VopnwPSYQUw0waQNhBjz0DYFQwvkv4OwWYSf//fJF3M6bH42Tn2J+IlQ+4/fCFnE3m99seV5T1myEj7fsupNteY2sKFGXENLGtAD/76FM0iBmXx76xlSTyZSSmNDIRU4xUR23cfc+al84F5mO2lEk+5Zr3Qn5JUpucBfis4vtu0sMDgZ4w1d0tcuXkT/MEJn2iX2cnxbSy5qNAPHu7b+LEfXBv2OrMDqPrx/X6QREgi3w5RxL5kz7bvitWsIwIvb3ST2ARAArBwb8pEyp2A/w5p22rkQtL+3ibZ8fkmpgn33f31AZPgtM++iJPBmPKFjArcWEJ9fIVB/6DAj
22
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPzzrPpItEjNG7tU0DpJJ4pkI01E9d6K61OKTVPdFQSyGCdMj9XdP93lC6sJA+9/ahvf5F3gWEKxUJL2CKUiFWw=
33
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLSsu/HNKiaALhQ26UDv+N0AFdMb26fMVrOKe866CGu6ajSf9HUOhJFdjhseihB2rTalMPr8MrcXNLufii4mL8u4l9fUQXFgwnM/ZpiVPSs6C+8i4u/ZDg7Nx2NXybNIgQ==
4-
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACB4WgRgGBRo6Uk+cRgg8tJPCbEtGURRWlUA7PDDerXR+P9O6mm3L99Etxsyh5XNYqXyaMNtH5c51ooMajrFwcayAHIhPPb8X3CsTwEfIUQ96aDyHQMotbRfnkn6uefeUTRrSNcqeAndUtVyAqBdqbsq2mgJYXHrz2NUKlPFYgauQi+WQ==
4+
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACB4WgRgGBRo6Uk+cRgg8tJPCbEtGURRWlUA7PDDerXR+P9O6mm3L99Etxsyh5XNYqXyaMNtH5c51ooMajrFwcayAHIhPPb8X3CsTwEfIUQ96aDyHQMotbRfnkn6uefeUTRrSNcqeAndUtVyAqBdqbsq2mgJYXHrz2NUKlPFYgauQi+WQ==
5+
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAkNGPVOTuzuKTgGfHcve2MRj57yXhmZgkUyi9RpmJrl

src/Renci.SshNet.TestTools.OpenSSH/SshdConfig.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,11 @@ public void SaveTo(TextWriter writer)
211211
writer.WriteLine("MACs " + string.Join(",", MessageAuthenticationCodeAlgorithms.Select(c => c.Name).ToArray()));
212212
}
213213

214-
writer.WriteLine("PubkeyAcceptedAlgorithms " + string.Join(",", PublicKeyAcceptedAlgorithms.Select(c => c.Name).ToArray()));
215-
214+
if (PublicKeyAcceptedAlgorithms.Count > 0)
215+
{
216+
writer.WriteLine("PubkeyAcceptedAlgorithms " + string.Join(",", PublicKeyAcceptedAlgorithms.Select(c => c.Name).ToArray()));
217+
}
218+
216219
foreach (var match in Matches)
217220
{
218221
_matchFormatter.Format(match, writer);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public void HostKeyEventArgsConstructorTest()
4242
0xa0, 0x23, 0xaf, 0xff, 0x9c, 0x0f, 0x8c, 0x83, 0x7c, 0xf8, 0xe1, 0x8e, 0x32, 0x8e, 0x61, 0xfc,
4343
0x5b, 0xbd, 0xd4, 0x46, 0xe1
4444
}.SequenceEqual(target.HostKey));
45-
Assert.AreEqual("ssh-rsa", target.HostKeyName);
45+
Assert.AreEqual("rsa-sha2-512", target.HostKeyName);
4646
Assert.AreEqual(2048, target.KeyLength);
4747
}
4848

src/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
using Microsoft.VisualStudio.TestTools.UnitTesting;
22
using Renci.SshNet.Common;
3+
using Renci.SshNet.Security;
34
using Renci.SshNet.Tests.Common;
45
using System;
6+
using System.Collections.Generic;
57
using System.IO;
8+
using System.Linq;
69

710
namespace Renci.SshNet.Tests.Classes
811
{
@@ -144,7 +147,7 @@ public void Test_PrivateKey_RSA()
144147
{
145148
using (var stream = GetData("Key.RSA.txt"))
146149
{
147-
_ = new PrivateKeyFile(stream);
150+
TestRsaKeyFile(new PrivateKeyFile(stream));
148151
}
149152
}
150153

@@ -166,7 +169,7 @@ public void Test_PrivateKey_SSH2_RSA()
166169
{
167170
using (var stream = GetData("Key.SSH2.RSA.txt"))
168171
{
169-
_ = new PrivateKeyFile(stream);
172+
TestRsaKeyFile(new PrivateKeyFile(stream));
170173
}
171174
}
172175

@@ -188,7 +191,7 @@ public void Test_PrivateKey_SSH2_Encrypted_RSA_DES_CBC()
188191
{
189192
using (var stream = GetData("Key.SSH2.RSA.Encrypted.Des.CBC.12345.txt"))
190193
{
191-
_ = new PrivateKeyFile(stream, "12345");
194+
TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
192195
}
193196
}
194197

@@ -262,7 +265,7 @@ public void Test_PrivateKey_RSA_DES_CBC()
262265
{
263266
using (var stream = GetData("Key.RSA.Encrypted.Des.CBC.12345.txt"))
264267
{
265-
_ = new PrivateKeyFile(stream, "12345");
268+
TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
266269
}
267270
}
268271

@@ -284,7 +287,7 @@ public void Test_PrivateKey_RSA_AES_128_CBC()
284287
{
285288
using (var stream = GetData("Key.RSA.Encrypted.Aes.128.CBC.12345.txt"))
286289
{
287-
_ = new PrivateKeyFile(stream, "12345");
290+
TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
288291
}
289292
}
290293

@@ -295,7 +298,7 @@ public void Test_PrivateKey_RSA_AES_192_CBC()
295298
{
296299
using (var stream = GetData("Key.RSA.Encrypted.Aes.192.CBC.12345.txt"))
297300
{
298-
_ = new PrivateKeyFile(stream, "12345");
301+
TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
299302
}
300303
}
301304

@@ -306,7 +309,7 @@ public void Test_PrivateKey_RSA_AES_256_CBC()
306309
{
307310
using (var stream = GetData("Key.RSA.Encrypted.Aes.256.CBC.12345.txt"))
308311
{
309-
_ = new PrivateKeyFile(stream, "12345");
312+
TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
310313
}
311314
}
312315

@@ -317,7 +320,7 @@ public void Test_PrivateKey_RSA_DES_EDE3_CFB()
317320
{
318321
using (var stream = GetData("Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt"))
319322
{
320-
_ = new PrivateKeyFile(stream, "1234567890");
323+
TestRsaKeyFile(new PrivateKeyFile(stream, "1234567890"));
321324
}
322325
}
323326

@@ -576,7 +579,7 @@ public void Test_PrivateKey_OPENSSH_RSA()
576579
{
577580
using (var stream = GetData("Key.OPENSSH.RSA.txt"))
578581
{
579-
_ = new PrivateKeyFile(stream);
582+
TestRsaKeyFile(new PrivateKeyFile(stream));
580583
}
581584
}
582585

@@ -587,7 +590,7 @@ public void Test_PrivateKey_OPENSSH_RSA_ENCRYPTED()
587590
{
588591
using (var stream = GetData("Key.OPENSSH.RSA.Encrypted.txt"))
589592
{
590-
_ = new PrivateKeyFile(stream, "12345");
593+
TestRsaKeyFile(new PrivateKeyFile(stream, "12345"));
591594
}
592595
}
593596

@@ -678,5 +681,18 @@ private string GetTempFileName()
678681
File.Delete(tempFile);
679682
return tempFile;
680683
}
684+
685+
private static void TestRsaKeyFile(PrivateKeyFile rsaPrivateKeyFile)
686+
{
687+
Assert.AreEqual(3, rsaPrivateKeyFile.HostAlgorithms.Count);
688+
689+
List<KeyHostAlgorithm> algorithms = rsaPrivateKeyFile.HostAlgorithms.Cast<KeyHostAlgorithm>().ToList();
690+
691+
Assert.AreEqual("rsa-sha2-512", algorithms[0].Name);
692+
Assert.AreEqual("rsa-sha2-256", algorithms[1].Name);
693+
Assert.AreEqual("ssh-rsa", algorithms[2].Name);
694+
695+
Assert.AreSame(algorithms[0], rsaPrivateKeyFile.HostKey);
696+
}
681697
}
682698
}

0 commit comments

Comments
 (0)