Skip to content

Commit 2e68828

Browse files
authoredJan 2, 2025··
Add support for sntrup761x25519Sha512 key exchange method (#1562)
1 parent 14c652c commit 2e68828

File tree

4 files changed

+170
-1
lines changed

4 files changed

+170
-1
lines changed
 

‎src/Renci.SshNet/ConnectionInfo.cs

+2
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,8 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
349349

350350
KeyExchangeAlgorithms = new Dictionary<string, Func<IKeyExchange>>
351351
{
352+
{ "sntrup761x25519-sha512", () => new KeyExchangeSNtruP761X25519Sha512() },
353+
{ "sntrup761x25519-sha512@openssh.com", () => new KeyExchangeSNtruP761X25519Sha512() },
352354
{ "curve25519-sha256", () => new KeyExchangeECCurve25519() },
353355
{ "curve25519-sha256@libssh.org", () => new KeyExchangeECCurve25519() },
354356
{ "ecdh-sha2-nistp256", () => new KeyExchangeECDH256() },
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Linq;
4+
5+
using Org.BouncyCastle.Crypto.Agreement;
6+
using Org.BouncyCastle.Crypto.Generators;
7+
using Org.BouncyCastle.Crypto.Parameters;
8+
using Org.BouncyCastle.Pqc.Crypto.NtruPrime;
9+
10+
using Renci.SshNet.Abstractions;
11+
using Renci.SshNet.Common;
12+
using Renci.SshNet.Messages.Transport;
13+
14+
namespace Renci.SshNet.Security
15+
{
16+
internal sealed class KeyExchangeSNtruP761X25519Sha512 : KeyExchangeEC
17+
{
18+
private SNtruPrimeKemExtractor _sntrup761Extractor;
19+
private X25519Agreement _x25519Agreement;
20+
21+
/// <summary>
22+
/// Gets algorithm name.
23+
/// </summary>
24+
public override string Name
25+
{
26+
get { return "sntrup761x25519-sha512"; }
27+
}
28+
29+
/// <summary>
30+
/// Gets the size, in bits, of the computed hash code.
31+
/// </summary>
32+
/// <value>
33+
/// The size, in bits, of the computed hash code.
34+
/// </value>
35+
protected override int HashSize
36+
{
37+
get { return 512; }
38+
}
39+
40+
/// <inheritdoc/>
41+
public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage)
42+
{
43+
base.Start(session, message, sendClientInitMessage);
44+
45+
Session.RegisterMessage("SSH_MSG_KEX_ECDH_REPLY");
46+
47+
Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived;
48+
49+
var sntrup761KeyPairGenerator = new SNtruPrimeKeyPairGenerator();
50+
sntrup761KeyPairGenerator.Init(new SNtruPrimeKeyGenerationParameters(CryptoAbstraction.SecureRandom, SNtruPrimeParameters.sntrup761));
51+
var sntrup761KeyPair = sntrup761KeyPairGenerator.GenerateKeyPair();
52+
53+
_sntrup761Extractor = new SNtruPrimeKemExtractor((SNtruPrimePrivateKeyParameters)sntrup761KeyPair.Private);
54+
55+
var x25519KeyPairGenerator = new X25519KeyPairGenerator();
56+
x25519KeyPairGenerator.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom));
57+
var x25519KeyPair = x25519KeyPairGenerator.GenerateKeyPair();
58+
59+
_x25519Agreement = new X25519Agreement();
60+
_x25519Agreement.Init(x25519KeyPair.Private);
61+
62+
var sntrup761PublicKey = ((SNtruPrimePublicKeyParameters)sntrup761KeyPair.Public).GetEncoded();
63+
var x25519PublicKey = ((X25519PublicKeyParameters)x25519KeyPair.Public).GetEncoded();
64+
65+
_clientExchangeValue = sntrup761PublicKey.Concat(x25519PublicKey);
66+
67+
SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue));
68+
}
69+
70+
/// <summary>
71+
/// Finishes key exchange algorithm.
72+
/// </summary>
73+
public override void Finish()
74+
{
75+
base.Finish();
76+
77+
Session.KeyExchangeEcdhReplyMessageReceived -= Session_KeyExchangeEcdhReplyMessageReceived;
78+
}
79+
80+
/// <summary>
81+
/// Hashes the specified data bytes.
82+
/// </summary>
83+
/// <param name="hashData">The hash data.</param>
84+
/// <returns>
85+
/// The hash of the data.
86+
/// </returns>
87+
protected override byte[] Hash(byte[] hashData)
88+
{
89+
return CryptoAbstraction.HashSHA512(hashData);
90+
}
91+
92+
private void Session_KeyExchangeEcdhReplyMessageReceived(object sender, MessageEventArgs<KeyExchangeEcdhReplyMessage> e)
93+
{
94+
var message = e.Message;
95+
96+
// Unregister message once received
97+
Session.UnRegisterMessage("SSH_MSG_KEX_ECDH_REPLY");
98+
99+
HandleServerEcdhReply(message.KS, message.QS, message.Signature);
100+
101+
// When SSH_MSG_KEX_ECDH_REPLY received key exchange is completed
102+
Finish();
103+
}
104+
105+
/// <summary>
106+
/// Handles the server DH reply message.
107+
/// </summary>
108+
/// <param name="hostKey">The host key.</param>
109+
/// <param name="serverExchangeValue">The server exchange value.</param>
110+
/// <param name="signature">The signature.</param>
111+
private void HandleServerEcdhReply(byte[] hostKey, byte[] serverExchangeValue, byte[] signature)
112+
{
113+
_serverExchangeValue = serverExchangeValue;
114+
_hostKey = hostKey;
115+
_signature = signature;
116+
117+
if (serverExchangeValue.Length != _sntrup761Extractor.EncapsulationLength + X25519PublicKeyParameters.KeySize)
118+
{
119+
throw new SshConnectionException(
120+
string.Format(CultureInfo.CurrentCulture, "Bad Q_S length: {0}.", serverExchangeValue.Length),
121+
DisconnectReason.KeyExchangeFailed);
122+
}
123+
124+
var sntrup761CipherText = serverExchangeValue.Take(_sntrup761Extractor.EncapsulationLength);
125+
var secret = _sntrup761Extractor.ExtractSecret(sntrup761CipherText);
126+
var sntrup761SecretLength = secret.Length;
127+
128+
var x25519PublicKey = new X25519PublicKeyParameters(serverExchangeValue, _sntrup761Extractor.EncapsulationLength);
129+
Array.Resize(ref secret, sntrup761SecretLength + _x25519Agreement.AgreementSize);
130+
_x25519Agreement.CalculateAgreement(x25519PublicKey, secret, sntrup761SecretLength);
131+
132+
SharedKey = CryptoAbstraction.HashSHA512(secret);
133+
}
134+
}
135+
}

‎test/Renci.SshNet.IntegrationTests/KeyExchangeAlgorithmTests.cs

+31
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,37 @@ public void TearDown()
2222
_remoteSshdConfig?.Reset();
2323
}
2424

25+
[TestMethod]
26+
[Ignore]
27+
public void SNtruP761X25519Sha512()
28+
{
29+
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
30+
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.SNtruP761X25519Sha512)
31+
.Update()
32+
.Restart();
33+
34+
using (var client = new SshClient(_connectionInfoFactory.Create()))
35+
{
36+
client.Connect();
37+
client.Disconnect();
38+
}
39+
}
40+
41+
[TestMethod]
42+
public void SNtruP761X25519Sha512OpenSsh()
43+
{
44+
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
45+
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.SNtruP761X25519Sha512OpenSsh)
46+
.Update()
47+
.Restart();
48+
49+
using (var client = new SshClient(_connectionInfoFactory.Create()))
50+
{
51+
client.Connect();
52+
client.Disconnect();
53+
}
54+
}
55+
2556
[TestMethod]
2657
public void Curve25519Sha256()
2758
{

‎test/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ public sealed class KeyExchangeAlgorithm
1414
public static readonly KeyExchangeAlgorithm EcdhSha2Nistp521 = new KeyExchangeAlgorithm("ecdh-sha2-nistp521");
1515
public static readonly KeyExchangeAlgorithm Curve25519Sha256 = new KeyExchangeAlgorithm("curve25519-sha256");
1616
public static readonly KeyExchangeAlgorithm Curve25519Sha256Libssh = new KeyExchangeAlgorithm("curve25519-sha256@libssh.org");
17-
public static readonly KeyExchangeAlgorithm Sntrup4591761x25519Sha512 = new KeyExchangeAlgorithm("sntrup4591761x25519-sha512@tinyssh.org");
17+
public static readonly KeyExchangeAlgorithm SNtruP761X25519Sha512 = new KeyExchangeAlgorithm("sntrup761x25519-sha512");
18+
public static readonly KeyExchangeAlgorithm SNtruP761X25519Sha512OpenSsh = new KeyExchangeAlgorithm("sntrup761x25519-sha512@openssh.com");
1819

1920
public KeyExchangeAlgorithm(string name)
2021
{

0 commit comments

Comments
 (0)
Please sign in to comment.