Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 22 additions & 2 deletions src/Renci.SshNet/Security/KeyExchangeEC.cs
Original file line number Diff line number Diff line change
@@ -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
/// <summary>
Expand Down Expand Up @@ -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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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 internal 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;
}
}
}
}
70 changes: 49 additions & 21 deletions src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs
Original file line number Diff line number Diff line change
@@ -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

/// <summary>
/// Gets algorithm name.
Expand All @@ -35,29 +37,47 @@ 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))
{
var curve = System.Security.Cryptography.ECCurve.CreateFromFriendlyName("Curve25519");
_impl = new BclImpl(curve);
}
else
#endif
{
_impl = new BouncyCastleImpl();
}

StartImpl();
}

/// <summary>
/// The implementation of start key exchange algorithm.
/// </summary>
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.GenerateClientECPoint();

SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue));
}

/// <summary>
/// Finishes key exchange algorithm.
/// </summary>
/// <inheritdoc/>
public override void Finish()
{
base.Finish();
FinishImpl();
}

/// <summary>
/// The implementation of finish key exchange algorithm.
/// </summary>
protected virtual void FinishImpl()
{
Session.KeyExchangeEcdhReplyMessageReceived -= Session_KeyExchangeEcdhReplyMessageReceived;
}

Expand Down Expand Up @@ -98,11 +118,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);
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

if (disposing)
{
_impl?.Dispose();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
24 changes: 2 additions & 22 deletions src/Renci.SshNet/Security/KeyExchangeECDH.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
}
}
}
37 changes: 11 additions & 26 deletions src/Renci.SshNet/Security/KeyExchangeMLKem768X25519Sha256.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -12,10 +11,9 @@

namespace Renci.SshNet.Security
{
internal sealed class KeyExchangeMLKem768X25519Sha256 : KeyExchangeEC
internal sealed class KeyExchangeMLKem768X25519Sha256 : KeyExchangeECCurve25519
{
private MLKemDecapsulator _mlkemDecapsulator;
private X25519Agreement _x25519Agreement;

/// <summary>
/// Gets algorithm name.
Expand All @@ -37,10 +35,8 @@ protected override int HashSize
}

/// <inheritdoc/>
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;
Expand All @@ -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.GenerateClientECPoint();

_clientExchangeValue = mlkem768PublicKey.Concat(x25519PublicKey);

SendMessage(new KeyExchangeHybridInitMessage(_clientExchangeValue));
}

/// <summary>
/// Finishes key exchange algorithm.
/// </summary>
public override void Finish()
/// <inheritdoc/>
protected override void FinishImpl()
{
base.Finish();

Session.KeyExchangeHybridReplyMessageReceived -= Session_KeyExchangeHybridReplyMessageReceived;
}

Expand Down Expand Up @@ -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));
}
}
}
Loading