From f6e2486784d3495a7670d0fcbc7604424044222e Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Mon, 22 Sep 2025 19:59:31 +0800 Subject: [PATCH 01/11] Use BCL Curve25519 when possible --- ...DH.BclImpl.cs => KeyExchangeEC.BclImpl.cs} | 4 +- src/Renci.SshNet/Security/KeyExchangeEC.cs | 24 +++++++++- ...eyExchangeECCurve25519.BouncyCastleImpl.cs | 38 +++++++++++++++ .../Security/KeyExchangeECCurve25519.cs | 48 ++++++++++++------- src/Renci.SshNet/Security/KeyExchangeECDH.cs | 24 +--------- 5 files changed, 95 insertions(+), 43 deletions(-) rename src/Renci.SshNet/Security/{KeyExchangeECDH.BclImpl.cs => KeyExchangeEC.BclImpl.cs} (95%) create mode 100644 src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs b/src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs similarity index 95% rename from src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs rename to src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs index 0cd1dd346..c61ac77b5 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs @@ -4,9 +4,9 @@ namespace Renci.SshNet.Security { - internal abstract partial class KeyExchangeECDH + internal abstract partial class KeyExchangeEC { - private sealed class BclImpl : Impl + protected internal sealed class BclImpl : Impl { private readonly ECCurve _curve; private readonly ECDiffieHellman _clientECDH; diff --git a/src/Renci.SshNet/Security/KeyExchangeEC.cs b/src/Renci.SshNet/Security/KeyExchangeEC.cs index a36bb7bc5..154cff0e1 100644 --- a/src/Renci.SshNet/Security/KeyExchangeEC.cs +++ b/src/Renci.SshNet/Security/KeyExchangeEC.cs @@ -1,8 +1,10 @@ -using Renci.SshNet.Messages.Transport; +using System; + +using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Security { - internal abstract class KeyExchangeEC : KeyExchange + internal abstract partial class KeyExchangeEC : KeyExchange { #pragma warning disable SA1401 // Fields should be private /// @@ -76,5 +78,23 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool _serverPayload = message.GetBytes(); _clientPayload = Session.ClientInitMessage.GetBytes(); } + + protected internal abstract class Impl : IDisposable + { + public abstract byte[] GenerateClientECPoint(); + + public abstract byte[] CalculateAgreement(byte[] serverECPoint); + + protected virtual void Dispose(bool disposing) + { + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } } } diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs new file mode 100644 index 000000000..cde94fb90 --- /dev/null +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs @@ -0,0 +1,38 @@ +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; + +using Renci.SshNet.Abstractions; + +namespace Renci.SshNet.Security +{ + internal partial class KeyExchangeECCurve25519 + { + private sealed class BouncyCastleImpl : Impl + { + private X25519Agreement _keyAgreement; + + public override byte[] GenerateClientECPoint() + { + var g = new X25519KeyPairGenerator(); + g.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom)); + + var aKeyPair = g.GenerateKeyPair(); + _keyAgreement = new X25519Agreement(); + _keyAgreement.Init(aKeyPair.Private); + + return ((X25519PublicKeyParameters)aKeyPair.Public).GetEncoded(); + } + + public override byte[] CalculateAgreement(byte[] serverECPoint) + { + var publicKey = new X25519PublicKeyParameters(serverECPoint); + + var k1 = new byte[_keyAgreement.AgreementSize]; + _keyAgreement.CalculateAgreement(publicKey, k1, 0); + + return k1; + } + } + } +} diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs index 558278873..2488af423 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs @@ -1,16 +1,16 @@ -using Org.BouncyCastle.Crypto.Agreement; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Parameters; - -using Renci.SshNet.Abstractions; +using Renci.SshNet.Abstractions; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Security { - internal sealed class KeyExchangeECCurve25519 : KeyExchangeEC + internal sealed partial class KeyExchangeECCurve25519 : KeyExchangeEC { - private X25519Agreement _keyAgreement; +#if NET + private Impl _impl; +#else + private BouncyCastleImpl _impl; +#endif /// /// Gets algorithm name. @@ -40,13 +40,19 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived; - var g = new X25519KeyPairGenerator(); - g.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom)); +#if NET + if (System.OperatingSystem.IsWindowsVersionAtLeast(10)) + { + var curve = System.Security.Cryptography.ECCurve.CreateFromFriendlyName("Curve25519"); + _impl = new BclImpl(curve); + } + else +#endif + { + _impl = new BouncyCastleImpl(); + } - var aKeyPair = g.GenerateKeyPair(); - _keyAgreement = new X25519Agreement(); - _keyAgreement.Init(aKeyPair.Private); - _clientExchangeValue = ((X25519PublicKeyParameters)aKeyPair.Public).GetEncoded(); + _clientExchangeValue = _impl.GenerateClientECPoint(); SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue)); } @@ -98,11 +104,19 @@ private void HandleServerEcdhReply(byte[] hostKey, byte[] serverExchangeValue, b _hostKey = hostKey; _signature = signature; - var publicKey = new X25519PublicKeyParameters(serverExchangeValue); - - var k1 = new byte[_keyAgreement.AgreementSize]; - _keyAgreement.CalculateAgreement(publicKey, k1, 0); + var k1 = _impl.CalculateAgreement(serverExchangeValue); SharedKey = k1.ToBigInteger2().ToByteArray(isBigEndian: true); } + + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _impl?.Dispose(); + } + } } } diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH.cs b/src/Renci.SshNet/Security/KeyExchangeECDH.cs index 35ef3b5c0..6948cc4ae 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH.cs @@ -1,6 +1,4 @@ -using System; - -using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Asn1.X9; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; @@ -41,7 +39,7 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived; #if NET - if (!OperatingSystem.IsWindows() || OperatingSystem.IsWindowsVersionAtLeast(10)) + if (!System.OperatingSystem.IsWindows() || System.OperatingSystem.IsWindowsVersionAtLeast(10)) { _impl = new BclImpl(Curve); } @@ -106,23 +104,5 @@ protected override void Dispose(bool disposing) _impl?.Dispose(); } } - - private abstract class Impl : IDisposable - { - public abstract byte[] GenerateClientECPoint(); - - public abstract byte[] CalculateAgreement(byte[] serverECPoint); - - protected virtual void Dispose(bool disposing) - { - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } } } From 9d151162660feebad5086121f4f63e6511402b5d Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Mon, 22 Sep 2025 21:44:14 +0800 Subject: [PATCH 02/11] Update KeyExchangeMLKem768X25519Sha256 and KeyExchangeSNtruP761X25519Sha512 --- ...eyExchangeECCurve25519.BouncyCastleImpl.cs | 2 +- .../Security/KeyExchangeECCurve25519.cs | 2 +- .../KeyExchangeMLKem768X25519Sha256.cs | 52 ++++++++++++------ .../KeyExchangeSNtruP761X25519Sha512.cs | 55 ++++++++++++------- 4 files changed, 73 insertions(+), 38 deletions(-) diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs index cde94fb90..8718a3044 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs @@ -8,7 +8,7 @@ namespace Renci.SshNet.Security { internal partial class KeyExchangeECCurve25519 { - private sealed class BouncyCastleImpl : Impl + protected internal sealed class BouncyCastleImpl : Impl { private X25519Agreement _keyAgreement; diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs index 2488af423..ac08c28be 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs @@ -4,7 +4,7 @@ namespace Renci.SshNet.Security { - internal sealed partial class KeyExchangeECCurve25519 : KeyExchangeEC + internal partial class KeyExchangeECCurve25519 : KeyExchangeEC { #if NET private Impl _impl; diff --git a/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs b/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs index 469a53738..b67d87c7b 100644 --- a/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs +++ b/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs @@ -1,7 +1,6 @@ using System.Globalization; using System.Linq; -using Org.BouncyCastle.Crypto.Agreement; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Kems; using Org.BouncyCastle.Crypto.Parameters; @@ -12,10 +11,14 @@ namespace Renci.SshNet.Security { - internal sealed class KeyExchangeMLKem768X25519Sha256 : KeyExchangeEC + internal sealed class KeyExchangeMLKem768X25519Sha256 : KeyExchangeECCurve25519 { private MLKemDecapsulator _mlkemDecapsulator; - private X25519Agreement _x25519Agreement; +#if NET + private Impl _impl; +#else + private BouncyCastleImpl _impl; +#endif /// /// Gets algorithm name. @@ -52,15 +55,20 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool _mlkemDecapsulator = new MLKemDecapsulator(MLKemParameters.ml_kem_768); _mlkemDecapsulator.Init(mlkem768KeyPair.Private); - var x25519KeyPairGenerator = new X25519KeyPairGenerator(); - x25519KeyPairGenerator.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom)); - var x25519KeyPair = x25519KeyPairGenerator.GenerateKeyPair(); - - _x25519Agreement = new X25519Agreement(); - _x25519Agreement.Init(x25519KeyPair.Private); - var mlkem768PublicKey = ((MLKemPublicKeyParameters)mlkem768KeyPair.Public).GetEncoded(); - var x25519PublicKey = ((X25519PublicKeyParameters)x25519KeyPair.Public).GetEncoded(); +#if NET + if (System.OperatingSystem.IsWindowsVersionAtLeast(10)) + { + var curve = System.Security.Cryptography.ECCurve.CreateFromFriendlyName("Curve25519"); + _impl = new BclImpl(curve); + } + else +#endif + { + _impl = new BouncyCastleImpl(); + } + + var x25519PublicKey = _impl.GenerateClientECPoint(); _clientExchangeValue = mlkem768PublicKey.Concat(x25519PublicKey); @@ -114,21 +122,31 @@ private void HandleServerHybridReply(byte[] hostKey, byte[] serverExchangeValue, _hostKey = hostKey; _signature = signature; - if (serverExchangeValue.Length != _mlkemDecapsulator.EncapsulationLength + _x25519Agreement.AgreementSize) + if (serverExchangeValue.Length != _mlkemDecapsulator.EncapsulationLength + X25519PublicKeyParameters.KeySize) { throw new SshConnectionException( string.Format(CultureInfo.CurrentCulture, "Bad S_Reply length: {0}.", serverExchangeValue.Length), DisconnectReason.KeyExchangeFailed); } - var secret = new byte[_mlkemDecapsulator.SecretLength + _x25519Agreement.AgreementSize]; + var mlkemSecret = new byte[_mlkemDecapsulator.SecretLength]; - _mlkemDecapsulator.Decapsulate(serverExchangeValue, 0, _mlkemDecapsulator.EncapsulationLength, secret, 0, _mlkemDecapsulator.SecretLength); + _mlkemDecapsulator.Decapsulate(serverExchangeValue, 0, _mlkemDecapsulator.EncapsulationLength, mlkemSecret, 0, _mlkemDecapsulator.SecretLength); - var x25519PublicKey = new X25519PublicKeyParameters(serverExchangeValue, _mlkemDecapsulator.EncapsulationLength); - _x25519Agreement.CalculateAgreement(x25519PublicKey, secret, _mlkemDecapsulator.SecretLength); + var x25519Agreement = _impl.CalculateAgreement(serverExchangeValue.Take(_mlkemDecapsulator.EncapsulationLength, X25519PublicKeyParameters.KeySize)); - SharedKey = CryptoAbstraction.HashSHA256(secret); + SharedKey = CryptoAbstraction.HashSHA256(mlkemSecret.Concat(x25519Agreement)); + } + + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _impl?.Dispose(); + } } } } diff --git a/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs b/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs index 1b327f56c..708505ae3 100644 --- a/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs +++ b/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs @@ -1,9 +1,6 @@ -using System; -using System.Globalization; +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; @@ -13,10 +10,14 @@ namespace Renci.SshNet.Security { - internal sealed class KeyExchangeSNtruP761X25519Sha512 : KeyExchangeEC + internal sealed class KeyExchangeSNtruP761X25519Sha512 : KeyExchangeECCurve25519 { private SNtruPrimeKemExtractor _sntrup761Extractor; - private X25519Agreement _x25519Agreement; +#if NET + private Impl _impl; +#else + private BouncyCastleImpl _impl; +#endif /// /// Gets algorithm name. @@ -52,15 +53,22 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool _sntrup761Extractor = new SNtruPrimeKemExtractor((SNtruPrimePrivateKeyParameters)sntrup761KeyPair.Private); - var x25519KeyPairGenerator = new X25519KeyPairGenerator(); - x25519KeyPairGenerator.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom)); - var x25519KeyPair = x25519KeyPairGenerator.GenerateKeyPair(); + var sntrup761PublicKey = ((SNtruPrimePublicKeyParameters)sntrup761KeyPair.Public).GetEncoded(); +#if NET + if (System.OperatingSystem.IsWindowsVersionAtLeast(10)) + { + var curve = System.Security.Cryptography.ECCurve.CreateFromFriendlyName("Curve25519"); + _impl = new BclImpl(curve); + } + else +#endif + { + _impl = new BouncyCastleImpl(); + } - _x25519Agreement = new X25519Agreement(); - _x25519Agreement.Init(x25519KeyPair.Private); + var x25519PublicKey = _impl.GenerateClientECPoint(); - var sntrup761PublicKey = ((SNtruPrimePublicKeyParameters)sntrup761KeyPair.Public).GetEncoded(); - var x25519PublicKey = ((X25519PublicKeyParameters)x25519KeyPair.Public).GetEncoded(); + _clientExchangeValue = sntrup761PublicKey.Concat(x25519PublicKey); _clientExchangeValue = sntrup761PublicKey.Concat(x25519PublicKey); @@ -122,14 +130,23 @@ private void HandleServerEcdhReply(byte[] hostKey, byte[] serverExchangeValue, b } 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); + var sntrup761Secret = _sntrup761Extractor.ExtractSecret(sntrup761CipherText); + + var x25519Agreement = _impl.CalculateAgreement(serverExchangeValue.Take(_sntrup761Extractor.EncapsulationLength, X25519PublicKeyParameters.KeySize)); - SharedKey = CryptoAbstraction.HashSHA512(secret); + SharedKey = CryptoAbstraction.HashSHA512(sntrup761Secret.Concat(x25519Agreement)); + } + + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _impl?.Dispose(); + } } } } From bd9db162fb0024eb606781a7ed97355edea283c9 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Mon, 22 Sep 2025 22:29:19 +0800 Subject: [PATCH 03/11] Split Start and Finish methods for inheritance --- .../Security/KeyExchangeECCurve25519.cs | 19 +++++++++++++++---- .../KeyExchangeMLKem768X25519Sha256.cs | 12 +++--------- .../KeyExchangeSNtruP761X25519Sha512.cs | 12 +++--------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs index ac08c28be..e0e9ad060 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs @@ -35,11 +35,17 @@ protected override int HashSize public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) { base.Start(session, message, sendClientInitMessage); + StartImpl(); + } + /// + /// The implementation of start key exchange algorithm. + /// + protected virtual void StartImpl() + { Session.RegisterMessage("SSH_MSG_KEX_ECDH_REPLY"); Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived; - #if NET if (System.OperatingSystem.IsWindowsVersionAtLeast(10)) { @@ -57,13 +63,18 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue)); } - /// - /// Finishes key exchange algorithm. - /// + /// public override void Finish() { base.Finish(); + FinishImpl(); + } + /// + /// The implementation of finish key exchange algorithm. + /// + protected virtual void FinishImpl() + { Session.KeyExchangeEcdhReplyMessageReceived -= Session_KeyExchangeEcdhReplyMessageReceived; } diff --git a/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs b/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs index b67d87c7b..05b1dcc5f 100644 --- a/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs +++ b/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs @@ -40,10 +40,8 @@ protected override int HashSize } /// - public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) + protected override void StartImpl() { - base.Start(session, message, sendClientInitMessage); - Session.RegisterMessage("SSH_MSG_KEX_HYBRID_REPLY"); Session.KeyExchangeHybridReplyMessageReceived += Session_KeyExchangeHybridReplyMessageReceived; @@ -75,13 +73,9 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool SendMessage(new KeyExchangeHybridInitMessage(_clientExchangeValue)); } - /// - /// Finishes key exchange algorithm. - /// - public override void Finish() + /// + protected override void FinishImpl() { - base.Finish(); - Session.KeyExchangeHybridReplyMessageReceived -= Session_KeyExchangeHybridReplyMessageReceived; } diff --git a/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs b/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs index 708505ae3..f283f0afc 100644 --- a/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs +++ b/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs @@ -39,10 +39,8 @@ protected override int HashSize } /// - public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) + protected override void StartImpl() { - base.Start(session, message, sendClientInitMessage); - Session.RegisterMessage("SSH_MSG_KEX_ECDH_REPLY"); Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived; @@ -75,13 +73,9 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue)); } - /// - /// Finishes key exchange algorithm. - /// - public override void Finish() + /// + protected override void FinishImpl() { - base.Finish(); - Session.KeyExchangeEcdhReplyMessageReceived -= Session_KeyExchangeEcdhReplyMessageReceived; } From 79932f3377d4275bc11cd89c020c130db3504656 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Tue, 23 Sep 2025 19:46:16 +0800 Subject: [PATCH 04/11] Some refactor --- .../Security/KeyExchangeECCurve25519.cs | 29 ++++++++++--------- .../KeyExchangeECDH.BouncyCastleImpl.cs | 4 +-- .../KeyExchangeMLKem768X25519Sha256.cs | 27 ----------------- .../KeyExchangeSNtruP761X25519Sha512.cs | 29 ------------------- 4 files changed, 18 insertions(+), 71 deletions(-) diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs index e0e9ad060..d4abb9f59 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs @@ -6,11 +6,13 @@ namespace Renci.SshNet.Security { internal partial class KeyExchangeECCurve25519 : KeyExchangeEC { +#pragma warning disable SA1401 // Fields should be private #if NET - private Impl _impl; + protected Impl _impl; #else - private BouncyCastleImpl _impl; + protected BouncyCastleImpl _impl; #endif +#pragma warning restore SA1401 // Fields should be private /// /// Gets algorithm name. @@ -35,17 +37,6 @@ protected override int HashSize public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) { base.Start(session, message, sendClientInitMessage); - StartImpl(); - } - - /// - /// The implementation of start key exchange algorithm. - /// - protected virtual void StartImpl() - { - Session.RegisterMessage("SSH_MSG_KEX_ECDH_REPLY"); - - Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived; #if NET if (System.OperatingSystem.IsWindowsVersionAtLeast(10)) { @@ -58,6 +49,18 @@ protected virtual void StartImpl() _impl = new BouncyCastleImpl(); } + StartImpl(); + } + + /// + /// The implementation of start key exchange algorithm. + /// + protected virtual void StartImpl() + { + Session.RegisterMessage("SSH_MSG_KEX_ECDH_REPLY"); + + Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived; + _clientExchangeValue = _impl.GenerateClientECPoint(); SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue)); diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs index 4d0208efe..38ed9a4a9 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs @@ -12,12 +12,12 @@ internal abstract partial class KeyExchangeECDH private sealed class BouncyCastleImpl : Impl { private readonly ECDomainParameters _domainParameters; - private readonly ECDHCBasicAgreement _keyAgreement; + private readonly ECDHBasicAgreement _keyAgreement; public BouncyCastleImpl(X9ECParameters curveParameters) { _domainParameters = new ECDomainParameters(curveParameters); - _keyAgreement = new ECDHCBasicAgreement(); + _keyAgreement = new ECDHBasicAgreement(); } public override byte[] GenerateClientECPoint() diff --git a/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs b/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs index 05b1dcc5f..63f75b4e3 100644 --- a/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs +++ b/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs @@ -14,11 +14,6 @@ namespace Renci.SshNet.Security internal sealed class KeyExchangeMLKem768X25519Sha256 : KeyExchangeECCurve25519 { private MLKemDecapsulator _mlkemDecapsulator; -#if NET - private Impl _impl; -#else - private BouncyCastleImpl _impl; -#endif /// /// Gets algorithm name. @@ -54,17 +49,6 @@ protected override void StartImpl() _mlkemDecapsulator.Init(mlkem768KeyPair.Private); var mlkem768PublicKey = ((MLKemPublicKeyParameters)mlkem768KeyPair.Public).GetEncoded(); -#if NET - if (System.OperatingSystem.IsWindowsVersionAtLeast(10)) - { - var curve = System.Security.Cryptography.ECCurve.CreateFromFriendlyName("Curve25519"); - _impl = new BclImpl(curve); - } - else -#endif - { - _impl = new BouncyCastleImpl(); - } var x25519PublicKey = _impl.GenerateClientECPoint(); @@ -131,16 +115,5 @@ private void HandleServerHybridReply(byte[] hostKey, byte[] serverExchangeValue, SharedKey = CryptoAbstraction.HashSHA256(mlkemSecret.Concat(x25519Agreement)); } - - /// - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - { - _impl?.Dispose(); - } - } } } diff --git a/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs b/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs index f283f0afc..330a6d0d9 100644 --- a/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs +++ b/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs @@ -13,11 +13,6 @@ namespace Renci.SshNet.Security internal sealed class KeyExchangeSNtruP761X25519Sha512 : KeyExchangeECCurve25519 { private SNtruPrimeKemExtractor _sntrup761Extractor; -#if NET - private Impl _impl; -#else - private BouncyCastleImpl _impl; -#endif /// /// Gets algorithm name. @@ -52,24 +47,11 @@ protected override void StartImpl() _sntrup761Extractor = new SNtruPrimeKemExtractor((SNtruPrimePrivateKeyParameters)sntrup761KeyPair.Private); var sntrup761PublicKey = ((SNtruPrimePublicKeyParameters)sntrup761KeyPair.Public).GetEncoded(); -#if NET - if (System.OperatingSystem.IsWindowsVersionAtLeast(10)) - { - var curve = System.Security.Cryptography.ECCurve.CreateFromFriendlyName("Curve25519"); - _impl = new BclImpl(curve); - } - else -#endif - { - _impl = new BouncyCastleImpl(); - } var x25519PublicKey = _impl.GenerateClientECPoint(); _clientExchangeValue = sntrup761PublicKey.Concat(x25519PublicKey); - _clientExchangeValue = sntrup761PublicKey.Concat(x25519PublicKey); - SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue)); } @@ -131,16 +113,5 @@ private void HandleServerEcdhReply(byte[] hostKey, byte[] serverExchangeValue, b SharedKey = CryptoAbstraction.HashSHA512(sntrup761Secret.Concat(x25519Agreement)); } - - /// - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - { - _impl?.Dispose(); - } - } } } From 5087d4d972593699fe13b42b9b170cbdcd44ba20 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Sat, 27 Sep 2025 08:54:31 +0800 Subject: [PATCH 05/11] Update src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs Co-authored-by: Rob Hague --- src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs b/src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs index c61ac77b5..547577199 100644 --- a/src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs @@ -6,7 +6,7 @@ namespace Renci.SshNet.Security { internal abstract partial class KeyExchangeEC { - protected internal sealed class BclImpl : Impl + protected sealed class BclImpl : Impl { private readonly ECCurve _curve; private readonly ECDiffieHellman _clientECDH; From 8abad813e03dab593b57ae01ee459af9a10fd03e Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Sat, 27 Sep 2025 09:24:08 +0800 Subject: [PATCH 06/11] revert --- src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs index 38ed9a4a9..4d0208efe 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs @@ -12,12 +12,12 @@ internal abstract partial class KeyExchangeECDH private sealed class BouncyCastleImpl : Impl { private readonly ECDomainParameters _domainParameters; - private readonly ECDHBasicAgreement _keyAgreement; + private readonly ECDHCBasicAgreement _keyAgreement; public BouncyCastleImpl(X9ECParameters curveParameters) { _domainParameters = new ECDomainParameters(curveParameters); - _keyAgreement = new ECDHBasicAgreement(); + _keyAgreement = new ECDHCBasicAgreement(); } public override byte[] GenerateClientECPoint() From 05ff68968e486f0dd7d59887f7ad1f93a93e9741 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Sun, 28 Sep 2025 07:48:08 +0800 Subject: [PATCH 07/11] Create dedicated KeyExchangeECCurve25519 BclImpl --- .../KeyExchangeECCurve25519.BclImpl.cs | 57 +++++++++++++++++++ ....BclImpl.cs => KeyExchangeECDH.BclImpl.cs} | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs rename src/Renci.SshNet/Security/{KeyExchangeEC.BclImpl.cs => KeyExchangeECDH.BclImpl.cs} (97%) diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs new file mode 100644 index 000000000..63d540293 --- /dev/null +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs @@ -0,0 +1,57 @@ +#if NET +using System.Security.Cryptography; + +namespace Renci.SshNet.Security +{ + internal abstract partial class KeyExchangeECCurve25519 + { + protected sealed class BclImpl : Impl + { + private readonly ECCurve _curve; + private readonly ECDiffieHellman _clientECDH; + + public BclImpl(ECCurve curve) + { + _curve = curve; + _clientECDH = ECDiffieHellman.Create(); + } + + public override byte[] GenerateClientECPoint() + { + _clientECDH.GenerateKey(_curve); + + var q = _clientECDH.PublicKey.ExportParameters().Q; + + return q.X; + } + + public override byte[] CalculateAgreement(byte[] serverECPoint) + { + var parameters = new ECParameters + { + Curve = _curve, + Q = new ECPoint + { + X = serverECPoint, + Y = new byte[serverECPoint.Length] + }, + }; + + using var serverECDH = ECDiffieHellman.Create(parameters); + + return _clientECDH.DeriveRawSecretAgreement(serverECDH.PublicKey); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _clientECDH.Dispose(); + } + } + } + } +} +#endif diff --git a/src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs similarity index 97% rename from src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs rename to src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs index 547577199..90ebbd42f 100644 --- a/src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs @@ -4,7 +4,7 @@ namespace Renci.SshNet.Security { - internal abstract partial class KeyExchangeEC + internal abstract partial class KeyExchangeECDH { protected sealed class BclImpl : Impl { From 295b2b5cedbad4ec739fbff3c82ea537b54f0891 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Sun, 28 Sep 2025 07:52:02 +0800 Subject: [PATCH 08/11] cleanup --- src/Renci.SshNet/Security/KeyExchangeEC.cs | 2 +- src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs | 2 +- .../Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs | 2 +- src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Renci.SshNet/Security/KeyExchangeEC.cs b/src/Renci.SshNet/Security/KeyExchangeEC.cs index 154cff0e1..8afd61edf 100644 --- a/src/Renci.SshNet/Security/KeyExchangeEC.cs +++ b/src/Renci.SshNet/Security/KeyExchangeEC.cs @@ -4,7 +4,7 @@ namespace Renci.SshNet.Security { - internal abstract partial class KeyExchangeEC : KeyExchange + internal abstract class KeyExchangeEC : KeyExchange { #pragma warning disable SA1401 // Fields should be private /// diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs index 63d540293..3a735906d 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs @@ -3,7 +3,7 @@ namespace Renci.SshNet.Security { - internal abstract partial class KeyExchangeECCurve25519 + internal partial class KeyExchangeECCurve25519 { protected sealed class BclImpl : Impl { diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs index 8718a3044..b155da92e 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs @@ -8,7 +8,7 @@ namespace Renci.SshNet.Security { internal partial class KeyExchangeECCurve25519 { - protected internal sealed class BouncyCastleImpl : Impl + protected sealed class BouncyCastleImpl : Impl { private X25519Agreement _keyAgreement; diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs index 90ebbd42f..0cd1dd346 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs @@ -6,7 +6,7 @@ namespace Renci.SshNet.Security { internal abstract partial class KeyExchangeECDH { - protected sealed class BclImpl : Impl + private sealed class BclImpl : Impl { private readonly ECCurve _curve; private readonly ECDiffieHellman _clientECDH; From ab67b2bfd5c229f15c16e1df7347ca4f5281700c Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Sun, 28 Sep 2025 18:27:52 +0800 Subject: [PATCH 09/11] minor code refactor --- src/Renci.SshNet/Security/KeyExchangeEC.cs | 6 +++--- .../Security/KeyExchangeECCurve25519.BclImpl.cs | 12 ++++++------ .../KeyExchangeECCurve25519.BouncyCastleImpl.cs | 6 +++--- src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs | 5 ++--- src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs | 6 +++--- .../Security/KeyExchangeECDH.BouncyCastleImpl.cs | 6 +++--- src/Renci.SshNet/Security/KeyExchangeECDH.cs | 2 +- .../Security/KeyExchangeMLKem768X25519Sha256.cs | 2 +- .../Security/KeyExchangeSNtruP761X25519Sha512.cs | 2 +- 9 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/Renci.SshNet/Security/KeyExchangeEC.cs b/src/Renci.SshNet/Security/KeyExchangeEC.cs index 8afd61edf..f0269a6b8 100644 --- a/src/Renci.SshNet/Security/KeyExchangeEC.cs +++ b/src/Renci.SshNet/Security/KeyExchangeEC.cs @@ -79,11 +79,11 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool _clientPayload = Session.ClientInitMessage.GetBytes(); } - protected internal abstract class Impl : IDisposable + protected abstract class Impl : IDisposable { - public abstract byte[] GenerateClientECPoint(); + public abstract byte[] GenerateClientPublicKey(); - public abstract byte[] CalculateAgreement(byte[] serverECPoint); + public abstract byte[] CalculateAgreement(byte[] serverPublicKey); protected virtual void Dispose(bool disposing) { diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs index 3a735906d..de3ab55e2 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs @@ -10,13 +10,13 @@ protected sealed class BclImpl : Impl private readonly ECCurve _curve; private readonly ECDiffieHellman _clientECDH; - public BclImpl(ECCurve curve) + public BclImpl() { - _curve = curve; + _curve = ECCurve.CreateFromFriendlyName("Curve25519"); _clientECDH = ECDiffieHellman.Create(); } - public override byte[] GenerateClientECPoint() + public override byte[] GenerateClientPublicKey() { _clientECDH.GenerateKey(_curve); @@ -25,15 +25,15 @@ public override byte[] GenerateClientECPoint() return q.X; } - public override byte[] CalculateAgreement(byte[] serverECPoint) + public override byte[] CalculateAgreement(byte[] serverPublicKey) { var parameters = new ECParameters { Curve = _curve, Q = new ECPoint { - X = serverECPoint, - Y = new byte[serverECPoint.Length] + X = serverPublicKey, + Y = new byte[serverPublicKey.Length] }, }; diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs index b155da92e..0e58a47c1 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs @@ -12,7 +12,7 @@ protected sealed class BouncyCastleImpl : Impl { private X25519Agreement _keyAgreement; - public override byte[] GenerateClientECPoint() + public override byte[] GenerateClientPublicKey() { var g = new X25519KeyPairGenerator(); g.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom)); @@ -24,9 +24,9 @@ public override byte[] GenerateClientECPoint() return ((X25519PublicKeyParameters)aKeyPair.Public).GetEncoded(); } - public override byte[] CalculateAgreement(byte[] serverECPoint) + public override byte[] CalculateAgreement(byte[] serverPublicKey) { - var publicKey = new X25519PublicKeyParameters(serverECPoint); + var publicKey = new X25519PublicKeyParameters(serverPublicKey); var k1 = new byte[_keyAgreement.AgreementSize]; _keyAgreement.CalculateAgreement(publicKey, k1, 0); diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs index d4abb9f59..cbeccd2af 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs @@ -40,8 +40,7 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool #if NET if (System.OperatingSystem.IsWindowsVersionAtLeast(10)) { - var curve = System.Security.Cryptography.ECCurve.CreateFromFriendlyName("Curve25519"); - _impl = new BclImpl(curve); + _impl = new BclImpl(); } else #endif @@ -61,7 +60,7 @@ protected virtual void StartImpl() Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived; - _clientExchangeValue = _impl.GenerateClientECPoint(); + _clientExchangeValue = _impl.GenerateClientPublicKey(); SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue)); } diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs index 0cd1dd346..508f0a2af 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs @@ -17,7 +17,7 @@ public BclImpl(ECCurve curve) _clientECDH = ECDiffieHellman.Create(); } - public override byte[] GenerateClientECPoint() + public override byte[] GenerateClientPublicKey() { _clientECDH.GenerateKey(_curve); @@ -26,9 +26,9 @@ public override byte[] GenerateClientECPoint() return EncodeECPoint(q); } - public override byte[] CalculateAgreement(byte[] serverECPoint) + public override byte[] CalculateAgreement(byte[] serverPublicKey) { - var q = DecodeECPoint(serverECPoint); + var q = DecodeECPoint(serverPublicKey); var parameters = new ECParameters { diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs index 4d0208efe..9f38c4ab0 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs @@ -20,7 +20,7 @@ public BouncyCastleImpl(X9ECParameters curveParameters) _keyAgreement = new ECDHCBasicAgreement(); } - public override byte[] GenerateClientECPoint() + public override byte[] GenerateClientPublicKey() { var g = new ECKeyPairGenerator(); g.Init(new ECKeyGenerationParameters(_domainParameters, CryptoAbstraction.SecureRandom)); @@ -31,10 +31,10 @@ public override byte[] GenerateClientECPoint() return ((ECPublicKeyParameters)aKeyPair.Public).Q.GetEncoded(); } - public override byte[] CalculateAgreement(byte[] serverECPoint) + public override byte[] CalculateAgreement(byte[] serverPublicKey) { var c = _domainParameters.Curve; - var q = c.DecodePoint(serverECPoint); + var q = c.DecodePoint(serverPublicKey); var publicKey = new ECPublicKeyParameters("ECDH", q, _domainParameters); return _keyAgreement.CalculateAgreement(publicKey).ToByteArray(); diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH.cs b/src/Renci.SshNet/Security/KeyExchangeECDH.cs index 6948cc4ae..c697ee1e0 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH.cs @@ -49,7 +49,7 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool _impl = new BouncyCastleImpl(CurveParameter); } - _clientExchangeValue = _impl.GenerateClientECPoint(); + _clientExchangeValue = _impl.GenerateClientPublicKey(); SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue)); } diff --git a/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs b/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs index 63f75b4e3..606e3a250 100644 --- a/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs +++ b/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs @@ -50,7 +50,7 @@ protected override void StartImpl() var mlkem768PublicKey = ((MLKemPublicKeyParameters)mlkem768KeyPair.Public).GetEncoded(); - var x25519PublicKey = _impl.GenerateClientECPoint(); + var x25519PublicKey = _impl.GenerateClientPublicKey(); _clientExchangeValue = mlkem768PublicKey.Concat(x25519PublicKey); diff --git a/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs b/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs index 330a6d0d9..903082262 100644 --- a/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs +++ b/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs @@ -48,7 +48,7 @@ protected override void StartImpl() var sntrup761PublicKey = ((SNtruPrimePublicKeyParameters)sntrup761KeyPair.Public).GetEncoded(); - var x25519PublicKey = _impl.GenerateClientECPoint(); + var x25519PublicKey = _impl.GenerateClientPublicKey(); _clientExchangeValue = sntrup761PublicKey.Concat(x25519PublicKey); From 326962664c70b148b71f80063a227d9e5430ba3b Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Mon, 29 Sep 2025 19:38:39 +0800 Subject: [PATCH 10/11] integration test --- .github/workflows/build.yml | 12 ++++++++++++ .../TestsFixtures/InfrastructureFixture.cs | 13 ++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 63d0021f4..74ce42371 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -128,6 +128,18 @@ jobs: podman build -t renci-ssh-tests-server-image -f test/Renci.SshNet.IntegrationTests/Dockerfile test/Renci.SshNet.IntegrationTests/ podman run --rm -h renci-ssh-tests-server -d -p 2222:22 renci-ssh-tests-server-image + - name: Run Integration Tests .NET + run: + dotnet test ` + -f net9.0 ` + --logger "console;verbosity=normal" ` + --logger GitHubActions ` + --filter "Name~Curve25519|Name~X25519" ` + -p:CollectCoverage=true ` + -p:CoverletOutputFormat=cobertura ` + -p:CoverletOutput=..\..\coverlet\windows_integration_test_net_9_coverage.xml ` + test\Renci.SshNet.IntegrationTests\ + - name: Run Integration Tests .NET Framework run: dotnet test ` diff --git a/test/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs b/test/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs index bcdb00d76..f26fdb362 100644 --- a/test/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs +++ b/test/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs @@ -1,4 +1,6 @@ -using DotNet.Testcontainers.Builders; +using System.Runtime.InteropServices; + +using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; using DotNet.Testcontainers.Images; @@ -38,16 +40,17 @@ private InfrastructureFixture() public async Task InitializeAsync() { - // for the .NET Framework Tests in CI, the Container is set up in WSL2 with Podman -#if NETFRAMEWORK - if (Environment.GetEnvironmentVariable("CI") == "true") +#pragma warning disable MA0144 // use System.OperatingSystem to check the current OS + // for the Windows Tests in CI, the Container is set up in WSL2 with Podman + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Environment.GetEnvironmentVariable("CI") == "true") +#pragma warning restore MA0144 // use System.OperatingSystem to check the current OS { SshServerPort = 2222; SshServerHostName = "localhost"; await Task.Delay(1_000); return; } -#endif var containerLogger = _loggerFactory.CreateLogger("testcontainers"); From 8971520d4ccd78cbc5d6f3ddf7f0965b4ab8d501 Mon Sep 17 00:00:00 2001 From: Rob Hague Date: Wed, 1 Oct 2025 20:15:34 +0200 Subject: [PATCH 11/11] Revert "integration test" This reverts commit 326962664c70b148b71f80063a227d9e5430ba3b. --- .github/workflows/build.yml | 12 ------------ .../TestsFixtures/InfrastructureFixture.cs | 13 +++++-------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 74ce42371..63d0021f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -128,18 +128,6 @@ jobs: podman build -t renci-ssh-tests-server-image -f test/Renci.SshNet.IntegrationTests/Dockerfile test/Renci.SshNet.IntegrationTests/ podman run --rm -h renci-ssh-tests-server -d -p 2222:22 renci-ssh-tests-server-image - - name: Run Integration Tests .NET - run: - dotnet test ` - -f net9.0 ` - --logger "console;verbosity=normal" ` - --logger GitHubActions ` - --filter "Name~Curve25519|Name~X25519" ` - -p:CollectCoverage=true ` - -p:CoverletOutputFormat=cobertura ` - -p:CoverletOutput=..\..\coverlet\windows_integration_test_net_9_coverage.xml ` - test\Renci.SshNet.IntegrationTests\ - - name: Run Integration Tests .NET Framework run: dotnet test ` diff --git a/test/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs b/test/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs index f26fdb362..bcdb00d76 100644 --- a/test/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs +++ b/test/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs @@ -1,6 +1,4 @@ -using System.Runtime.InteropServices; - -using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; using DotNet.Testcontainers.Images; @@ -40,17 +38,16 @@ private InfrastructureFixture() public async Task InitializeAsync() { -#pragma warning disable MA0144 // use System.OperatingSystem to check the current OS - // for the Windows Tests in CI, the Container is set up in WSL2 with Podman - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && - Environment.GetEnvironmentVariable("CI") == "true") -#pragma warning restore MA0144 // use System.OperatingSystem to check the current OS + // for the .NET Framework Tests in CI, the Container is set up in WSL2 with Podman +#if NETFRAMEWORK + if (Environment.GetEnvironmentVariable("CI") == "true") { SshServerPort = 2222; SshServerHostName = "localhost"; await Task.Delay(1_000); return; } +#endif var containerLogger = _loggerFactory.CreateLogger("testcontainers");