diff --git a/appveyor.yml b/appveyor.yml index 375047aef..74f4f927b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -28,7 +28,7 @@ for: - sh: dotnet test -f net8.0 -c Debug --no-restore --no-build --results-directory artifacts --logger Appveyor --logger "console;verbosity=normal" --logger "liquid.md;LogFileName=linux_unit_test_net_8_report.md" -p:CollectCoverage=true -p:CoverletOutputFormat=cobertura -p:CoverletOutput=../../artifacts/linux_unit_test_net_8_coverage.xml test/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj - sh: echo "Run integration tests" - sh: dotnet test -f net8.0 -c Debug --no-restore --no-build --results-directory artifacts --logger Appveyor --logger "console;verbosity=normal" --logger "liquid.md;LogFileName=linux_integration_test_net_8_report.md" -p:CollectCoverage=true -p:CoverletOutputFormat=cobertura -p:CoverletOutput=../../artifacts/linux_integration_test_net_8_coverage.xml test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj - - sh: dotnet test -f net48 -c Debug --no-restore --no-build --results-directory artifacts --logger Appveyor --logger "console;verbosity=normal" --logger "liquid.md;LogFileName=linux_integration_test_net_48_report.md" -p:CollectCoverage=true -p:CoverletOutputFormat=cobertura -p:CoverletOutput=../../artifacts/linux_integration_test_net_48_coverage.xml --filter "Name=ChaCha20Poly1305|Name~Zlib" test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj + - sh: dotnet test -f net48 -c Debug --no-restore --no-build --results-directory artifacts --logger Appveyor --logger "console;verbosity=normal" --logger "liquid.md;LogFileName=linux_integration_test_net_48_report.md" -p:CollectCoverage=true -p:CoverletOutputFormat=cobertura -p:CoverletOutput=../../artifacts/linux_integration_test_net_48_coverage.xml --filter "Name=ChaCha20Poly1305|Name~Ecdh|Name~Zlib" test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj - matrix: diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs new file mode 100644 index 000000000..7c1a5fb51 --- /dev/null +++ b/src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs @@ -0,0 +1,77 @@ +#if NET8_0_OR_GREATER +using System; +using System.Security.Cryptography; + +namespace Renci.SshNet.Security +{ + internal abstract partial class KeyExchangeECDH + { + private 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 EncodeECPoint(q); + } + + public override byte[] CalculateAgreement(byte[] serverECPoint) + { + var q = DecodeECPoint(serverECPoint); + + var parameters = new ECParameters + { + Curve = _curve, + Q = q, + }; + + using var serverECDH = ECDiffieHellman.Create(parameters); + + return _clientECDH.DeriveRawSecretAgreement(serverECDH.PublicKey); + } + + private static byte[] EncodeECPoint(ECPoint point) + { + var q = new byte[1 + point.X.Length + point.Y.Length]; + q[0] = 0x04; + Buffer.BlockCopy(point.X, 0, q, 1, point.X.Length); + Buffer.BlockCopy(point.Y, 0, q, point.X.Length + 1, point.Y.Length); + + return q; + } + + private static ECPoint DecodeECPoint(byte[] q) + { + var cordSize = (q.Length - 1) / 2; + var x = new byte[cordSize]; + var y = new byte[cordSize]; + Buffer.BlockCopy(q, 1, x, 0, x.Length); + Buffer.BlockCopy(q, cordSize + 1, y, 0, y.Length); + + return new ECPoint { X = x, Y = y }; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _clientECDH.Dispose(); + } + } + } + } +} +#endif diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs b/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs new file mode 100644 index 000000000..4d0208efe --- /dev/null +++ b/src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs @@ -0,0 +1,44 @@ +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; + +using Renci.SshNet.Abstractions; + +namespace Renci.SshNet.Security +{ + internal abstract partial class KeyExchangeECDH + { + private sealed class BouncyCastleImpl : Impl + { + private readonly ECDomainParameters _domainParameters; + private readonly ECDHCBasicAgreement _keyAgreement; + + public BouncyCastleImpl(X9ECParameters curveParameters) + { + _domainParameters = new ECDomainParameters(curveParameters); + _keyAgreement = new ECDHCBasicAgreement(); + } + + public override byte[] GenerateClientECPoint() + { + var g = new ECKeyPairGenerator(); + g.Init(new ECKeyGenerationParameters(_domainParameters, CryptoAbstraction.SecureRandom)); + + var aKeyPair = g.GenerateKeyPair(); + _keyAgreement.Init(aKeyPair.Private); + + return ((ECPublicKeyParameters)aKeyPair.Public).Q.GetEncoded(); + } + + public override byte[] CalculateAgreement(byte[] serverECPoint) + { + var c = _domainParameters.Curve; + var q = c.DecodePoint(serverECPoint); + 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 acf958048..e5ec28c5d 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH.cs @@ -1,19 +1,28 @@ using System; using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Crypto.Agreement; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math.EC; -using Renci.SshNet.Abstractions; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Security { - internal abstract class KeyExchangeECDH : KeyExchangeEC + internal abstract partial class KeyExchangeECDH : KeyExchangeEC { +#if NET8_0_OR_GREATER + private Impl _impl; + + /// + /// Gets the curve. + /// + /// + /// The curve. + /// + protected abstract System.Security.Cryptography.ECCurve Curve { get; } +#else + private BouncyCastleImpl _impl; +#endif + /// /// Gets the parameter of the curve. /// @@ -22,9 +31,6 @@ internal abstract class KeyExchangeECDH : KeyExchangeEC /// protected abstract X9ECParameters CurveParameter { get; } - private ECDHCBasicAgreement _keyAgreement; - private ECDomainParameters _domainParameters; - /// public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) { @@ -34,19 +40,20 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived; - _domainParameters = new ECDomainParameters(CurveParameter.Curve, - CurveParameter.G, - CurveParameter.N, - CurveParameter.H, - CurveParameter.GetSeed()); - - var g = new ECKeyPairGenerator(); - g.Init(new ECKeyGenerationParameters(_domainParameters, CryptoAbstraction.SecureRandom)); - - var aKeyPair = g.GenerateKeyPair(); - _keyAgreement = new ECDHCBasicAgreement(); - _keyAgreement.Init(aKeyPair.Private); - _clientExchangeValue = ((ECPublicKeyParameters)aKeyPair.Public).Q.GetEncoded(); +#if NET8_0_OR_GREATER + if (!OperatingSystem.IsWindows() || OperatingSystem.IsWindowsVersionAtLeast(10)) + { + _impl = new BclImpl(Curve); + } + else + { + _impl = new BouncyCastleImpl(CurveParameter); + } +#else + _impl = new BouncyCastleImpl(CurveParameter); +#endif + + _clientExchangeValue = _impl.GenerateClientECPoint(); SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue)); } @@ -86,18 +93,38 @@ private void HandleServerEcdhReply(byte[] hostKey, byte[] serverExchangeValue, b _hostKey = hostKey; _signature = signature; - var cordSize = (serverExchangeValue.Length - 1) / 2; - var x = new byte[cordSize]; - Buffer.BlockCopy(serverExchangeValue, 1, x, 0, x.Length); // first byte is format. should be checked and passed to bouncy castle? - var y = new byte[cordSize]; - Buffer.BlockCopy(serverExchangeValue, cordSize + 1, y, 0, y.Length); + var agreement = _impl.CalculateAgreement(serverExchangeValue); + + SharedKey = agreement.ToBigInteger2().ToByteArray().Reverse(); + } + + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _impl?.Dispose(); + } + } + + private abstract class Impl : IDisposable + { + public abstract byte[] GenerateClientECPoint(); + + public abstract byte[] CalculateAgreement(byte[] serverECPoint); - var c = (FpCurve)_domainParameters.Curve; - var q = c.CreatePoint(new Org.BouncyCastle.Math.BigInteger(1, x), new Org.BouncyCastle.Math.BigInteger(1, y)); - var publicKey = new ECPublicKeyParameters("ECDH", q, _domainParameters); + protected virtual void Dispose(bool disposing) + { + } - var k1 = _keyAgreement.CalculateAgreement(publicKey); - SharedKey = k1.ToByteArray().ToBigInteger2().ToByteArray().Reverse(); + 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/KeyExchangeECDH256.cs b/src/Renci.SshNet/Security/KeyExchangeECDH256.cs index aabfba62f..2a3be0af8 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH256.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH256.cs @@ -1,4 +1,4 @@ -using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; using Renci.SshNet.Abstractions; @@ -15,6 +15,19 @@ public override string Name get { return "ecdh-sha2-nistp256"; } } +#if NET8_0_OR_GREATER + /// + /// Gets the curve. + /// + protected override System.Security.Cryptography.ECCurve Curve + { + get + { + return System.Security.Cryptography.ECCurve.NamedCurves.nistP256; + } + } +#endif + /// /// Gets Curve Parameter. /// @@ -22,7 +35,7 @@ protected override X9ECParameters CurveParameter { get { - return SecNamedCurves.GetByName("secp256r1"); + return SecNamedCurves.GetByOid(SecObjectIdentifiers.SecP256r1); } } diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH384.cs b/src/Renci.SshNet/Security/KeyExchangeECDH384.cs index 39fcf3bde..b9d440f7d 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH384.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH384.cs @@ -1,4 +1,4 @@ -using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; using Renci.SshNet.Abstractions; @@ -15,6 +15,19 @@ public override string Name get { return "ecdh-sha2-nistp384"; } } +#if NET8_0_OR_GREATER + /// + /// Gets the curve. + /// + protected override System.Security.Cryptography.ECCurve Curve + { + get + { + return System.Security.Cryptography.ECCurve.NamedCurves.nistP384; + } + } +#endif + /// /// Gets Curve Parameter. /// @@ -22,7 +35,7 @@ protected override X9ECParameters CurveParameter { get { - return SecNamedCurves.GetByName("secp384r1"); + return SecNamedCurves.GetByOid(SecObjectIdentifiers.SecP384r1); } } diff --git a/src/Renci.SshNet/Security/KeyExchangeECDH521.cs b/src/Renci.SshNet/Security/KeyExchangeECDH521.cs index 17fa89e7f..07d993cee 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECDH521.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECDH521.cs @@ -1,4 +1,4 @@ -using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; using Renci.SshNet.Abstractions; @@ -15,6 +15,19 @@ public override string Name get { return "ecdh-sha2-nistp521"; } } +#if NET8_0_OR_GREATER + /// + /// Gets the curve. + /// + protected override System.Security.Cryptography.ECCurve Curve + { + get + { + return System.Security.Cryptography.ECCurve.NamedCurves.nistP521; + } + } +#endif + /// /// Gets Curve Parameter. /// @@ -22,7 +35,7 @@ protected override X9ECParameters CurveParameter { get { - return SecNamedCurves.GetByName("secp521r1"); + return SecNamedCurves.GetByOid(SecObjectIdentifiers.SecP521r1); } }