Skip to content

Commit 9f21a39

Browse files
committed
Add support for sntrup761x25519Sha512 key exchange method (sshnet#1562)
1 parent 579b27c commit 9f21a39

File tree

5 files changed

+171
-1
lines changed

5 files changed

+171
-1
lines changed

src/Renci.SshNet/ConnectionInfo.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,8 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
350350
KeyExchangeAlgorithms = new Dictionary<string, Func<IKeyExchange>>
351351
{
352352
{ "mlkem768x25519-sha256", () => new KeyExchangeMLKem768X25519Sha256() },
353+
{ "sntrup761x25519-sha512", () => new KeyExchangeSNtruP761X25519Sha512() },
354+
{ "[email protected]", () => new KeyExchangeSNtruP761X25519Sha512() },
353355
{ "curve25519-sha256", () => new KeyExchangeECCurve25519() },
354356
{ "[email protected]", () => new KeyExchangeECCurve25519() },
355357
{ "ecdh-sha2-nistp256", () => new KeyExchangeECDH256() },

src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ private void HandleServerHybridReply(byte[] hostKey, byte[] serverExchangeValue,
114114
_hostKey = hostKey;
115115
_signature = signature;
116116

117-
if (serverExchangeValue.Length != _mlkemDecapsulator.EncapsulationLength + _x25519Agreement.AgreementSize)
117+
if (serverExchangeValue.Length != _mlkemDecapsulator.EncapsulationLength + X25519PublicKeyParameters.KeySize)
118118
{
119119
throw new SshConnectionException(
120120
string.Format(CultureInfo.CurrentCulture, "Bad S_Reply length: {0}.", serverExchangeValue.Length),
Lines changed: 135 additions & 0 deletions
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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,37 @@ public void MLKem768X25519Sha256()
3838
}
3939
}
4040

41+
[TestMethod]
42+
[Ignore]
43+
public void SNtruP761X25519Sha512()
44+
{
45+
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
46+
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.SNtruP761X25519Sha512)
47+
.Update()
48+
.Restart();
49+
50+
using (var client = new SshClient(_connectionInfoFactory.Create()))
51+
{
52+
client.Connect();
53+
client.Disconnect();
54+
}
55+
}
56+
57+
[TestMethod]
58+
public void SNtruP761X25519Sha512OpenSsh()
59+
{
60+
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
61+
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.SNtruP761X25519Sha512OpenSsh)
62+
.Update()
63+
.Restart();
64+
65+
using (var client = new SshClient(_connectionInfoFactory.Create()))
66+
{
67+
client.Connect();
68+
client.Disconnect();
69+
}
70+
}
71+
4172
[TestMethod]
4273
public void Curve25519Sha256()
4374
{

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +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("[email protected]");
17+
public static readonly KeyExchangeAlgorithm SNtruP761X25519Sha512 = new KeyExchangeAlgorithm("sntrup761x25519-sha512");
18+
public static readonly KeyExchangeAlgorithm SNtruP761X25519Sha512OpenSsh = new KeyExchangeAlgorithm("[email protected]");
1719
public static readonly KeyExchangeAlgorithm MLKem768X25519Sha256 = new KeyExchangeAlgorithm("mlkem768x25519-sha256");
1820

1921
public KeyExchangeAlgorithm(string name)

0 commit comments

Comments
 (0)