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

KeyExchangeMLKem768X25519Sha256

using Org.BouncyCastle.Crypto; 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 : KeyExchangeECCurve25519 { private MLKemDecapsulator _mlkemDecapsulator; public override string Name => "mlkem768x25519-sha256"; protected override int HashSize => 256; protected override void StartImpl() { 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); byte[] encoded = ((MLKemPublicKeyParameters)asymmetricCipherKeyPair.Public).GetEncoded(); byte[] second = _impl.GenerateClientPublicKey(); _clientExchangeValue = encoded.Concat(second); SendMessage(new KeyExchangeHybridInitMessage(_clientExchangeValue)); } protected override void FinishImpl() { 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 + X25519PublicKeyParameters.KeySize) throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Bad S_Reply length: {0}.", serverExchangeValue.Length), DisconnectReason.KeyExchangeFailed); byte[] array = new byte[_mlkemDecapsulator.SecretLength]; _mlkemDecapsulator.Decapsulate(serverExchangeValue, 0, _mlkemDecapsulator.EncapsulationLength, array, 0, _mlkemDecapsulator.SecretLength); byte[] second = _impl.CalculateAgreement(serverExchangeValue.Take(_mlkemDecapsulator.EncapsulationLength, X25519PublicKeyParameters.KeySize)); base.SharedKey = CryptoAbstraction.HashSHA256(array.Concat(second)); } } }