diff --git a/src/Renci.SshNet/Security/KeyExchangeEC.cs b/src/Renci.SshNet/Security/KeyExchangeEC.cs index a36bb7bc5..f0269a6b8 100644 --- a/src/Renci.SshNet/Security/KeyExchangeEC.cs +++ b/src/Renci.SshNet/Security/KeyExchangeEC.cs @@ -1,4 +1,6 @@ -using Renci.SshNet.Messages.Transport; +using System; + +using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Security { @@ -76,5 +78,23 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool _serverPayload = message.GetBytes(); _clientPayload = Session.ClientInitMessage.GetBytes(); } + + protected abstract class Impl : IDisposable + { + public abstract byte[] GenerateClientPublicKey(); + + public abstract byte[] CalculateAgreement(byte[] serverPublicKey); + + 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.BclImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BclImpl.cs new file mode 100644 index 000000000..de3ab55e2 --- /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 partial class KeyExchangeECCurve25519 + { + protected sealed class BclImpl : Impl + { + private readonly ECCurve _curve; + private readonly ECDiffieHellman _clientECDH; + + public BclImpl() + { + _curve = ECCurve.CreateFromFriendlyName("Curve25519"); + _clientECDH = ECDiffieHellman.Create(); + } + + public override byte[] GenerateClientPublicKey() + { + _clientECDH.GenerateKey(_curve); + + var q = _clientECDH.PublicKey.ExportParameters().Q; + + return q.X; + } + + public override byte[] CalculateAgreement(byte[] serverPublicKey) + { + var parameters = new ECParameters + { + Curve = _curve, + Q = new ECPoint + { + X = serverPublicKey, + Y = new byte[serverPublicKey.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/KeyExchangeECCurve25519.BouncyCastleImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.BouncyCastleImpl.cs new file mode 100644 index 000000000..0e58a47c1 --- /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 + { + protected sealed class BouncyCastleImpl : Impl + { + private X25519Agreement _keyAgreement; + + public override byte[] GenerateClientPublicKey() + { + 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[] serverPublicKey) + { + var publicKey = new X25519PublicKeyParameters(serverPublicKey); + + 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..cbeccd2af 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs @@ -1,16 +1,18 @@ -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 partial class KeyExchangeECCurve25519 : KeyExchangeEC { - private X25519Agreement _keyAgreement; +#pragma warning disable SA1401 // Fields should be private +#if NET + protected Impl _impl; +#else + protected BouncyCastleImpl _impl; +#endif +#pragma warning restore SA1401 // Fields should be private /// /// Gets algorithm name. @@ -35,29 +37,46 @@ protected override int HashSize public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) { base.Start(session, message, sendClientInitMessage); +#if NET + if (System.OperatingSystem.IsWindowsVersionAtLeast(10)) + { + _impl = new BclImpl(); + } + else +#endif + { + _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; - var g = new X25519KeyPairGenerator(); - g.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom)); - - var aKeyPair = g.GenerateKeyPair(); - _keyAgreement = new X25519Agreement(); - _keyAgreement.Init(aKeyPair.Private); - _clientExchangeValue = ((X25519PublicKeyParameters)aKeyPair.Public).GetEncoded(); + _clientExchangeValue = _impl.GenerateClientPublicKey(); 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; } @@ -98,11 +117,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.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 35ef3b5c0..c697ee1e0 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); } @@ -51,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)); } @@ -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); - } - } } } diff --git a/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs b/src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs index 469a53738..606e3a250 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,9 @@ namespace Renci.SshNet.Security { - internal sealed class KeyExchangeMLKem768X25519Sha256 : KeyExchangeEC + internal sealed class KeyExchangeMLKem768X25519Sha256 : KeyExchangeECCurve25519 { private MLKemDecapsulator _mlkemDecapsulator; - private X25519Agreement _x25519Agreement; /// /// Gets algorithm name. @@ -37,10 +35,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; @@ -52,28 +48,18 @@ 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(); + + var x25519PublicKey = _impl.GenerateClientPublicKey(); _clientExchangeValue = mlkem768PublicKey.Concat(x25519PublicKey); SendMessage(new KeyExchangeHybridInitMessage(_clientExchangeValue)); } - /// - /// Finishes key exchange algorithm. - /// - public override void Finish() + /// + protected override void FinishImpl() { - base.Finish(); - Session.KeyExchangeHybridReplyMessageReceived -= Session_KeyExchangeHybridReplyMessageReceived; } @@ -114,21 +100,20 @@ 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)); } } } diff --git a/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs b/src/Renci.SshNet/Security/KeyExchangeSNtruP761X25519Sha512.cs index 1b327f56c..903082262 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,9 @@ namespace Renci.SshNet.Security { - internal sealed class KeyExchangeSNtruP761X25519Sha512 : KeyExchangeEC + internal sealed class KeyExchangeSNtruP761X25519Sha512 : KeyExchangeECCurve25519 { private SNtruPrimeKemExtractor _sntrup761Extractor; - private X25519Agreement _x25519Agreement; /// /// Gets algorithm name. @@ -38,10 +34,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; @@ -52,28 +46,18 @@ 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(); - - _x25519Agreement = new X25519Agreement(); - _x25519Agreement.Init(x25519KeyPair.Private); - var sntrup761PublicKey = ((SNtruPrimePublicKeyParameters)sntrup761KeyPair.Public).GetEncoded(); - var x25519PublicKey = ((X25519PublicKeyParameters)x25519KeyPair.Public).GetEncoded(); + + var x25519PublicKey = _impl.GenerateClientPublicKey(); _clientExchangeValue = sntrup761PublicKey.Concat(x25519PublicKey); SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue)); } - /// - /// Finishes key exchange algorithm. - /// - public override void Finish() + /// + protected override void FinishImpl() { - base.Finish(); - Session.KeyExchangeEcdhReplyMessageReceived -= Session_KeyExchangeEcdhReplyMessageReceived; } @@ -122,14 +106,12 @@ 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)); } } }