Skip to content

Commit 081d305

Browse files
scott-xuRob-Hague
andauthored
Use BCL Curve25519 for Windows 10+ (#1702)
* Use BCL Curve25519 when possible * Update KeyExchangeMLKem768X25519Sha256 and KeyExchangeSNtruP761X25519Sha512 * Split Start and Finish methods for inheritance * Some refactor * Update src/Renci.SshNet/Security/KeyExchangeEC.BclImpl.cs Co-authored-by: Rob Hague <rob.hague00@gmail.com> * revert * Create dedicated KeyExchangeECCurve25519 BclImpl * cleanup * minor code refactor * integration test * Revert "integration test" This reverts commit 3269626. --------- Co-authored-by: Rob Hague <rob.hague00@gmail.com>
1 parent 2f0ae31 commit 081d305

9 files changed

+195
-106
lines changed

src/Renci.SshNet/Security/KeyExchangeEC.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using Renci.SshNet.Messages.Transport;
1+
using System;
2+
3+
using Renci.SshNet.Messages.Transport;
24

35
namespace Renci.SshNet.Security
46
{
@@ -76,5 +78,23 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool
7678
_serverPayload = message.GetBytes();
7779
_clientPayload = Session.ClientInitMessage.GetBytes();
7880
}
81+
82+
protected abstract class Impl : IDisposable
83+
{
84+
public abstract byte[] GenerateClientPublicKey();
85+
86+
public abstract byte[] CalculateAgreement(byte[] serverPublicKey);
87+
88+
protected virtual void Dispose(bool disposing)
89+
{
90+
}
91+
92+
public void Dispose()
93+
{
94+
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
95+
Dispose(disposing: true);
96+
GC.SuppressFinalize(this);
97+
}
98+
}
7999
}
80100
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#if NET
2+
using System.Security.Cryptography;
3+
4+
namespace Renci.SshNet.Security
5+
{
6+
internal partial class KeyExchangeECCurve25519
7+
{
8+
protected sealed class BclImpl : Impl
9+
{
10+
private readonly ECCurve _curve;
11+
private readonly ECDiffieHellman _clientECDH;
12+
13+
public BclImpl()
14+
{
15+
_curve = ECCurve.CreateFromFriendlyName("Curve25519");
16+
_clientECDH = ECDiffieHellman.Create();
17+
}
18+
19+
public override byte[] GenerateClientPublicKey()
20+
{
21+
_clientECDH.GenerateKey(_curve);
22+
23+
var q = _clientECDH.PublicKey.ExportParameters().Q;
24+
25+
return q.X;
26+
}
27+
28+
public override byte[] CalculateAgreement(byte[] serverPublicKey)
29+
{
30+
var parameters = new ECParameters
31+
{
32+
Curve = _curve,
33+
Q = new ECPoint
34+
{
35+
X = serverPublicKey,
36+
Y = new byte[serverPublicKey.Length]
37+
},
38+
};
39+
40+
using var serverECDH = ECDiffieHellman.Create(parameters);
41+
42+
return _clientECDH.DeriveRawSecretAgreement(serverECDH.PublicKey);
43+
}
44+
45+
protected override void Dispose(bool disposing)
46+
{
47+
base.Dispose(disposing);
48+
49+
if (disposing)
50+
{
51+
_clientECDH.Dispose();
52+
}
53+
}
54+
}
55+
}
56+
}
57+
#endif
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using Org.BouncyCastle.Crypto.Agreement;
2+
using Org.BouncyCastle.Crypto.Generators;
3+
using Org.BouncyCastle.Crypto.Parameters;
4+
5+
using Renci.SshNet.Abstractions;
6+
7+
namespace Renci.SshNet.Security
8+
{
9+
internal partial class KeyExchangeECCurve25519
10+
{
11+
protected sealed class BouncyCastleImpl : Impl
12+
{
13+
private X25519Agreement _keyAgreement;
14+
15+
public override byte[] GenerateClientPublicKey()
16+
{
17+
var g = new X25519KeyPairGenerator();
18+
g.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom));
19+
20+
var aKeyPair = g.GenerateKeyPair();
21+
_keyAgreement = new X25519Agreement();
22+
_keyAgreement.Init(aKeyPair.Private);
23+
24+
return ((X25519PublicKeyParameters)aKeyPair.Public).GetEncoded();
25+
}
26+
27+
public override byte[] CalculateAgreement(byte[] serverPublicKey)
28+
{
29+
var publicKey = new X25519PublicKeyParameters(serverPublicKey);
30+
31+
var k1 = new byte[_keyAgreement.AgreementSize];
32+
_keyAgreement.CalculateAgreement(publicKey, k1, 0);
33+
34+
return k1;
35+
}
36+
}
37+
}
38+
}

src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
using Org.BouncyCastle.Crypto.Agreement;
2-
using Org.BouncyCastle.Crypto.Generators;
3-
using Org.BouncyCastle.Crypto.Parameters;
4-
5-
using Renci.SshNet.Abstractions;
1+
using Renci.SshNet.Abstractions;
62
using Renci.SshNet.Common;
73
using Renci.SshNet.Messages.Transport;
84

95
namespace Renci.SshNet.Security
106
{
11-
internal sealed class KeyExchangeECCurve25519 : KeyExchangeEC
7+
internal partial class KeyExchangeECCurve25519 : KeyExchangeEC
128
{
13-
private X25519Agreement _keyAgreement;
9+
#pragma warning disable SA1401 // Fields should be private
10+
#if NET
11+
protected Impl _impl;
12+
#else
13+
protected BouncyCastleImpl _impl;
14+
#endif
15+
#pragma warning restore SA1401 // Fields should be private
1416

1517
/// <summary>
1618
/// Gets algorithm name.
@@ -35,29 +37,46 @@ protected override int HashSize
3537
public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage)
3638
{
3739
base.Start(session, message, sendClientInitMessage);
40+
#if NET
41+
if (System.OperatingSystem.IsWindowsVersionAtLeast(10))
42+
{
43+
_impl = new BclImpl();
44+
}
45+
else
46+
#endif
47+
{
48+
_impl = new BouncyCastleImpl();
49+
}
50+
51+
StartImpl();
52+
}
3853

54+
/// <summary>
55+
/// The implementation of start key exchange algorithm.
56+
/// </summary>
57+
protected virtual void StartImpl()
58+
{
3959
Session.RegisterMessage("SSH_MSG_KEX_ECDH_REPLY");
4060

4161
Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived;
4262

43-
var g = new X25519KeyPairGenerator();
44-
g.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom));
45-
46-
var aKeyPair = g.GenerateKeyPair();
47-
_keyAgreement = new X25519Agreement();
48-
_keyAgreement.Init(aKeyPair.Private);
49-
_clientExchangeValue = ((X25519PublicKeyParameters)aKeyPair.Public).GetEncoded();
63+
_clientExchangeValue = _impl.GenerateClientPublicKey();
5064

5165
SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue));
5266
}
5367

54-
/// <summary>
55-
/// Finishes key exchange algorithm.
56-
/// </summary>
68+
/// <inheritdoc/>
5769
public override void Finish()
5870
{
5971
base.Finish();
72+
FinishImpl();
73+
}
6074

75+
/// <summary>
76+
/// The implementation of finish key exchange algorithm.
77+
/// </summary>
78+
protected virtual void FinishImpl()
79+
{
6180
Session.KeyExchangeEcdhReplyMessageReceived -= Session_KeyExchangeEcdhReplyMessageReceived;
6281
}
6382

@@ -98,11 +117,19 @@ private void HandleServerEcdhReply(byte[] hostKey, byte[] serverExchangeValue, b
98117
_hostKey = hostKey;
99118
_signature = signature;
100119

101-
var publicKey = new X25519PublicKeyParameters(serverExchangeValue);
102-
103-
var k1 = new byte[_keyAgreement.AgreementSize];
104-
_keyAgreement.CalculateAgreement(publicKey, k1, 0);
120+
var k1 = _impl.CalculateAgreement(serverExchangeValue);
105121
SharedKey = k1.ToBigInteger2().ToByteArray(isBigEndian: true);
106122
}
123+
124+
/// <inheritdoc/>
125+
protected override void Dispose(bool disposing)
126+
{
127+
base.Dispose(disposing);
128+
129+
if (disposing)
130+
{
131+
_impl?.Dispose();
132+
}
133+
}
107134
}
108135
}

src/Renci.SshNet/Security/KeyExchangeECDH.BclImpl.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public BclImpl(ECCurve curve)
1717
_clientECDH = ECDiffieHellman.Create();
1818
}
1919

20-
public override byte[] GenerateClientECPoint()
20+
public override byte[] GenerateClientPublicKey()
2121
{
2222
_clientECDH.GenerateKey(_curve);
2323

@@ -26,9 +26,9 @@ public override byte[] GenerateClientECPoint()
2626
return EncodeECPoint(q);
2727
}
2828

29-
public override byte[] CalculateAgreement(byte[] serverECPoint)
29+
public override byte[] CalculateAgreement(byte[] serverPublicKey)
3030
{
31-
var q = DecodeECPoint(serverECPoint);
31+
var q = DecodeECPoint(serverPublicKey);
3232

3333
var parameters = new ECParameters
3434
{

src/Renci.SshNet/Security/KeyExchangeECDH.BouncyCastleImpl.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public BouncyCastleImpl(X9ECParameters curveParameters)
2020
_keyAgreement = new ECDHCBasicAgreement();
2121
}
2222

23-
public override byte[] GenerateClientECPoint()
23+
public override byte[] GenerateClientPublicKey()
2424
{
2525
var g = new ECKeyPairGenerator();
2626
g.Init(new ECKeyGenerationParameters(_domainParameters, CryptoAbstraction.SecureRandom));
@@ -31,10 +31,10 @@ public override byte[] GenerateClientECPoint()
3131
return ((ECPublicKeyParameters)aKeyPair.Public).Q.GetEncoded();
3232
}
3333

34-
public override byte[] CalculateAgreement(byte[] serverECPoint)
34+
public override byte[] CalculateAgreement(byte[] serverPublicKey)
3535
{
3636
var c = _domainParameters.Curve;
37-
var q = c.DecodePoint(serverECPoint);
37+
var q = c.DecodePoint(serverPublicKey);
3838
var publicKey = new ECPublicKeyParameters("ECDH", q, _domainParameters);
3939

4040
return _keyAgreement.CalculateAgreement(publicKey).ToByteArray();

src/Renci.SshNet/Security/KeyExchangeECDH.cs

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System;
2-
3-
using Org.BouncyCastle.Asn1.X9;
1+
using Org.BouncyCastle.Asn1.X9;
42

53
using Renci.SshNet.Common;
64
using Renci.SshNet.Messages.Transport;
@@ -41,7 +39,7 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool
4139
Session.KeyExchangeEcdhReplyMessageReceived += Session_KeyExchangeEcdhReplyMessageReceived;
4240

4341
#if NET
44-
if (!OperatingSystem.IsWindows() || OperatingSystem.IsWindowsVersionAtLeast(10))
42+
if (!System.OperatingSystem.IsWindows() || System.OperatingSystem.IsWindowsVersionAtLeast(10))
4543
{
4644
_impl = new BclImpl(Curve);
4745
}
@@ -51,7 +49,7 @@ public override void Start(Session session, KeyExchangeInitMessage message, bool
5149
_impl = new BouncyCastleImpl(CurveParameter);
5250
}
5351

54-
_clientExchangeValue = _impl.GenerateClientECPoint();
52+
_clientExchangeValue = _impl.GenerateClientPublicKey();
5553

5654
SendMessage(new KeyExchangeEcdhInitMessage(_clientExchangeValue));
5755
}
@@ -106,23 +104,5 @@ protected override void Dispose(bool disposing)
106104
_impl?.Dispose();
107105
}
108106
}
109-
110-
private abstract class Impl : IDisposable
111-
{
112-
public abstract byte[] GenerateClientECPoint();
113-
114-
public abstract byte[] CalculateAgreement(byte[] serverECPoint);
115-
116-
protected virtual void Dispose(bool disposing)
117-
{
118-
}
119-
120-
public void Dispose()
121-
{
122-
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
123-
Dispose(disposing: true);
124-
GC.SuppressFinalize(this);
125-
}
126-
}
127107
}
128108
}

0 commit comments

Comments
 (0)