diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs index 3a60ad3ec..bedb5d9d5 100644 --- a/src/Renci.SshNet/ConnectionInfo.cs +++ b/src/Renci.SshNet/ConnectionInfo.cs @@ -349,6 +349,8 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy KeyExchangeAlgorithms = new Dictionary> { + { "sntrup761x25519-sha512", () => new KeyExchangeSNtruP761X25519Sha512() }, + { "sntrup761x25519-sha512@openssh.com", () => new KeyExchangeSNtruP761X25519Sha512() }, { "curve25519-sha256", () => new KeyExchangeECCurve25519() }, { "curve25519-sha256@libssh.org", () => new KeyExchangeECCurve25519() }, { "ecdh-sha2-nistp256", () => new KeyExchangeECDH256() }, diff --git a/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs b/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs new file mode 100644 index 000000000..1b327f56c --- /dev/null +++ b/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs @@ -0,0 +1,135 @@ +using System; +using System.Globalization; +using System.Linq; + +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Pqc.Crypto.NtruPrime; + +using Renci.SshNet.Abstractions; +using Renci.SshNet.Common; +using Renci.SshNet.Messages.Transport; + +namespace Renci.SshNet.Security +{ + internal sealed class KeyExchangeSNtruP761X25519Sha512 : KeyExchangeEC + { + private SNtruPrimeKemExtractor _sntrup761Extractor; + private X25519Agreement _x25519Agreement; + + /// + /// Gets algorithm name. + /// + public override string Name + { + get { return "sntrup761x25519-sha512"; } + } + + /// + /// Gets the size, in bits, of the computed hash code. + /// + /// + /// The size, in bits, of the computed hash code. + /// + protected override int HashSize + { + get { return 512; } + } + + /// + public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) + { + base.Start(session, message, sendClientInitMessage); + + Session.RegisterMessage("SSH_MSG_KEX_ECDH_REPLY"); + + Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived; + + var sntrup761KeyPairGenerator = new SNtruPrimeKeyPairGenerator(); + sntrup761KeyPairGenerator.Init(new SNtruPrimeKeyGenerationParameters(CryptoAbstraction.SecureRandom, SNtruPrimeParameters.sntrup761)); + var sntrup761KeyPair = sntrup761KeyPairGenerator.GenerateKeyPair(); + + _sntrup761Extractor = new SNtruPrimeKemExtractor((SNtruPrimePrivateKeyParameters)sntrup761KeyPair.Private); + + var x25519KeyPairGenerator = new X25519KeyPairGenerator(); + x25519KeyPairGenerator.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom)); + var x25519KeyPair = x25519KeyPairGenerator.GenerateKeyPair(); + + _x25519Agreement = new X25519Agreement(); + _x25519Agreement.Init(x25519KeyPair.Private); + + var sntrup761PublicKey = ((SNtruPrimePublicKeyParameters)sntrup761KeyPair.Public).GetEncoded(); + var x25519PublicKey = ((X25519PublicKeyParameters)x25519KeyPair.Public).GetEncoded(); + + _clientExchangeValue = sntrup761PublicKey.Concat(x25519PublicKey); + + SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue)); + } + + /// + /// Finishes key exchange algorithm. + /// + public override void Finish() + { + base.Finish(); + + Session.KeyExchangeEcdhReplyMessageReceived -= Session_KeyExchangeEcdhReplyMessageReceived; + } + + /// + /// Hashes the specified data bytes. + /// + /// The hash data. + /// + /// The hash of the data. + /// + protected override byte[] Hash(byte[] hashData) + { + return CryptoAbstraction.HashSHA512(hashData); + } + + private void Session_KeyExchangeEcdhReplyMessageReceived(object sender, MessageEventArgs e) + { + var message = e.Message; + + // Unregister message once received + Session.UnRegisterMessage("SSH_MSG_KEX_ECDH_REPLY"); + + HandleServerEcdhReply(message.KS, message.QS, message.Signature); + + // When SSH_MSG_KEX_ECDH_REPLY received key exchange is completed + Finish(); + } + + /// + /// Handles the server DH reply message. + /// + /// The host key. + /// The server exchange value. + /// The signature. + private void HandleServerEcdhReply(byte[] hostKey, byte[] serverExchangeValue, byte[] signature) + { + _serverExchangeValue = serverExchangeValue; + _hostKey = hostKey; + _signature = signature; + + if (serverExchangeValue.Length != _sntrup761Extractor.EncapsulationLength + X25519PublicKeyParameters.KeySize) + { + throw new SshConnectionException( + string.Format(CultureInfo.CurrentCulture, "Bad Q_S length: {0}.", serverExchangeValue.Length), + DisconnectReason.KeyExchangeFailed); + } + + var sntrup761CipherText = serverExchangeValue.Take(_sntrup761Extractor.EncapsulationLength); + var secret = _sntrup761Extractor.ExtractSecret(sntrup761CipherText); + var sntrup761SecretLength = secret.Length; + + var x25519PublicKey = new X25519PublicKeyParameters(serverExchangeValue, _sntrup761Extractor.EncapsulationLength); + Array.Resize(ref secret, sntrup761SecretLength + _x25519Agreement.AgreementSize); + _x25519Agreement.CalculateAgreement(x25519PublicKey, secret, sntrup761SecretLength); + + SharedKey = CryptoAbstraction.HashSHA512(secret); + } + } +} diff --git a/test/Renci.SshNet.IntegrationTests/KeyExchangeAlgorithmTests.cs b/test/Renci.SshNet.IntegrationTests/KeyExchangeAlgorithmTests.cs index dfd6b6394..d88c9cc7d 100644 --- a/test/Renci.SshNet.IntegrationTests/KeyExchangeAlgorithmTests.cs +++ b/test/Renci.SshNet.IntegrationTests/KeyExchangeAlgorithmTests.cs @@ -22,6 +22,37 @@ public void TearDown() _remoteSshdConfig?.Reset(); } + [TestMethod] + [Ignore] + public void SNtruP761X25519Sha512() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.SNtruP761X25519Sha512) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + + [TestMethod] + public void SNtruP761X25519Sha512OpenSsh() + { + _remoteSshdConfig.ClearKeyExchangeAlgorithms() + .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.SNtruP761X25519Sha512OpenSsh) + .Update() + .Restart(); + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + client.Connect(); + client.Disconnect(); + } + } + [TestMethod] public void Curve25519Sha256() { diff --git a/test/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs b/test/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs index e63d8ea17..6d882ec12 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs @@ -14,7 +14,8 @@ public sealed class KeyExchangeAlgorithm public static readonly KeyExchangeAlgorithm EcdhSha2Nistp521 = new KeyExchangeAlgorithm("ecdh-sha2-nistp521"); public static readonly KeyExchangeAlgorithm Curve25519Sha256 = new KeyExchangeAlgorithm("curve25519-sha256"); public static readonly KeyExchangeAlgorithm Curve25519Sha256Libssh = new KeyExchangeAlgorithm("curve25519-sha256@libssh.org"); - public static readonly KeyExchangeAlgorithm Sntrup4591761x25519Sha512 = new KeyExchangeAlgorithm("sntrup4591761x25519-sha512@tinyssh.org"); + public static readonly KeyExchangeAlgorithm SNtruP761X25519Sha512 = new KeyExchangeAlgorithm("sntrup761x25519-sha512"); + public static readonly KeyExchangeAlgorithm SNtruP761X25519Sha512OpenSsh = new KeyExchangeAlgorithm("sntrup761x25519-sha512@openssh.com"); public KeyExchangeAlgorithm(string name) {