From d0961a7767de94fa36aabf1c9c679a16db73cb1b Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Tue, 25 Aug 2026 22:36:26 +0800 Subject: [PATCH] Add support for X25519DiffieHellman from BCL on .NET 11.0+ and .NET Framework --- Directory.Packages.props | 2 +- .../KeyExchangeECCurve25519.BclImpl.cs | 2 +- .../KeyExchangeECCurve25519.BclImpl2.cs | 41 +++++++++++++++++++ .../Security/KeyExchangeECCurve25519.cs | 14 +++++-- 4 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl2.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 0ffc049d3..c3af9d182 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ - + diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs index de3ab55e2..83badc0e4 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs @@ -1,4 +1,4 @@ -#if NET +#if NET && !NET11_0_OR_GREATER using System.Security.Cryptography; namespace Renci.SshNet.Security diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl2.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl2.cs new file mode 100644 index 000000000..95e05a713 --- /dev/null +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl2.cs @@ -0,0 +1,41 @@ +#if NETFRAMEWORK || NET11_0_OR_GREATER +using System.Security.Cryptography; + +namespace Renci.SshNet.Security +{ + internal partial class KeyExchangeECCurve25519 + { + protected sealed class BclImpl : Impl + { + private readonly X25519DiffieHellman _clientX25519DH; + + public BclImpl() + { + _clientX25519DH = X25519DiffieHellman.GenerateKey(); + } + + public override byte[] GenerateClientPublicKey() + { + return _clientX25519DH.ExportPublicKey(); + } + + public override byte[] CalculateAgreement(byte[] serverPublicKey) + { + using var serverX25519DH = X25519DiffieHellman.ImportPublicKey(serverPublicKey); + + return _clientX25519DH.DeriveRawSecretAgreement(serverX25519DH.ExportPublicKey()); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _clientX25519DH.Dispose(); + } + } + } + } +} +#endif diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs index 3ce8d1f81..873a46103 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs @@ -8,10 +8,10 @@ namespace Renci.SshNet.Security internal partial class KeyExchangeECCurve25519 : KeyExchangeEC { #pragma warning disable SA1401 // Fields should be private -#if NET - protected Impl _impl; -#else +#if NETSTANDARD protected BouncyCastleImpl _impl; +#else + protected Impl _impl; #endif #pragma warning restore SA1401 // Fields should be private @@ -38,12 +38,18 @@ protected override int HashSize public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) { base.Start(session, message, sendClientInitMessage); -#if NET +#if NET && !NET11_0_OR_GREATER if (System.OperatingSystem.IsWindowsVersionAtLeast(10)) { _impl = new BclImpl(); } else +#elif NETFRAMEWORK || NET11_0_OR_GREATER + if (X25519DiffieHellman.IsSupported) + { + _impl = new BclImpl(); + } + else #endif { _impl = new BouncyCastleImpl();