Skip to content

Commit 8280d02

Browse files
committed
Add support for sntrup761x25519Sha512 key exchange method
1 parent 29997ae commit 8280d02

File tree

4 files changed

+170
-1
lines changed

4 files changed

+170
-1
lines changed

Diff for: src/Renci.SshNet/ConnectionInfo.cs

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

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

Diff for: test/Renci.SshNet.IntegrationTests/KeyExchangeAlgorithmTests.cs

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

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

Diff for: 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("[email protected]");
17-
public static readonly KeyExchangeAlgorithm Sntrup4591761x25519Sha512 = new KeyExchangeAlgorithm("[email protected]");
17+
public static readonly KeyExchangeAlgorithm Sntrup761x25519Sha512 = new KeyExchangeAlgorithm("sntrup761x25519-sha512");
18+
public static readonly KeyExchangeAlgorithm Sntrup761x25519Sha512OpenSsh = new KeyExchangeAlgorithm("[email protected]");
1819

1920
public KeyExchangeAlgorithm(string name)
2021
{

0 commit comments

Comments
 (0)