<PackageReference Include="SSH.NET" Version="2025.0.0" />

KeyExchangeMLKem768X25519Sha256

using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Agreement; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Kems; using Org.BouncyCastle.Crypto.Parameters; using Renci.SshNet.Abstractions; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; using System.Globalization; namespace Renci.SshNet.Security { internal sealed class KeyExchangeMLKem768X25519Sha256 : KeyExchangeEC { private MLKemDecapsulator _mlkemDecapsulator; private X25519Agreement _x25519Agreement; public override string Name => "mlkem768x25519-sha256"; protected override int HashSize => 256; public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) { base.Start(session, message, sendClientInitMessage); base.Session.RegisterMessage("SSH_MSG_KEX_HYBRID_REPLY"); base.Session.KeyExchangeHybridReplyMessageReceived += Session_KeyExchangeHybridReplyMessageReceived; MLKemKeyPairGenerator mLKemKeyPairGenerator = new MLKemKeyPairGenerator(); mLKemKeyPairGenerator.Init(new MLKemKeyGenerationParameters(CryptoAbstraction.SecureRandom, MLKemParameters.ml_kem_768)); AsymmetricCipherKeyPair asymmetricCipherKeyPair = mLKemKeyPairGenerator.GenerateKeyPair(); _mlkemDecapsulator = new MLKemDecapsulator(MLKemParameters.ml_kem_768); _mlkemDecapsulator.Init(asymmetricCipherKeyPair.Private); X25519KeyPairGenerator x25519KeyPairGenerator = new X25519KeyPairGenerator(); x25519KeyPairGenerator.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom)); AsymmetricCipherKeyPair asymmetricCipherKeyPair2 = x25519KeyPairGenerator.GenerateKeyPair(); _x25519Agreement = new X25519Agreement(); _x25519Agreement.Init(asymmetricCipherKeyPair2.Private); byte[] encoded = ((MLKemPublicKeyParameters)asymmetricCipherKeyPair.Public).GetEncoded(); byte[] encoded2 = ((X25519PublicKeyParameters)asymmetricCipherKeyPair2.Public).GetEncoded(); _clientExchangeValue = encoded.Concat(encoded2); SendMessage(new KeyExchangeHybridInitMessage(_clientExchangeValue)); } public override void Finish() { base.Finish(); base.Session.KeyExchangeHybridReplyMessageReceived -= Session_KeyExchangeHybridReplyMessageReceived; } protected override byte[] Hash(byte[] hashData) { return CryptoAbstraction.HashSHA256(hashData); } private void Session_KeyExchangeHybridReplyMessageReceived(object sender, MessageEventArgs<KeyExchangeHybridReplyMessage> e) { KeyExchangeHybridReplyMessage message = e.Message; base.Session.UnRegisterMessage("SSH_MSG_KEX_HYBRID_REPLY"); HandleServerHybridReply(message.KS, message.SReply, message.Signature); Finish(); } private void HandleServerHybridReply(byte[] hostKey, byte[] serverExchangeValue, byte[] signature) { _serverExchangeValue = serverExchangeValue; _hostKey = hostKey; _signature = signature; if (serverExchangeValue.Length != _mlkemDecapsulator.EncapsulationLength + _x25519Agreement.AgreementSize) throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Bad S_Reply length: {0}.", serverExchangeValue.Length), DisconnectReason.KeyExchangeFailed); byte[] array = new byte[_mlkemDecapsulator.SecretLength + _x25519Agreement.AgreementSize]; _mlkemDecapsulator.Decapsulate(serverExchangeValue, 0, _mlkemDecapsulator.EncapsulationLength, array, 0, _mlkemDecapsulator.SecretLength); X25519PublicKeyParameters publicKey = new X25519PublicKeyParameters(serverExchangeValue, _mlkemDecapsulator.EncapsulationLength); _x25519Agreement.CalculateAgreement(publicKey, array, _mlkemDecapsulator.SecretLength); base.SharedKey = CryptoAbstraction.HashSHA256(array); } } }