Skip to content

Commit fe827a5

Browse files
scott-xuRob-Hague
andauthored
[ECDsa] Falls back to use BouncyCastle if BCL (Mono) doesn't support (#1461)
* Use BouncyCastle ECDsa when runtime is Mono * Falls back to use BouncyCastle if CngKey.Import throws NotImplementedException (in Mono) * Take NETStandard into consideration * Adjust some comments * Change #if NETFRAEWORK to #if NET462 for CngKey * Separate implementations * Consolidate Ecdsa property and HashAlgorithm property * Rename Import_Cng and Import_Bcl to Import; Rename Export_Cng and Export_Bcl to Export; * Add comments * refactor * add host key tests --------- Co-authored-by: Robert Hague <[email protected]> Co-authored-by: Rob Hague <[email protected]>
1 parent 3dda5c9 commit fe827a5

File tree

16 files changed

+494
-338
lines changed

16 files changed

+494
-338
lines changed

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ for:
3232
# some coverage until a proper solution for running the .NET Framework integration tests in CI is found.
3333
# Running all the tests causes problems which are not worth solving in this rare configuration.
3434
# See https://github.com/sshnet/SSH.NET/pull/1462 and related links
35-
- 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~Ecdh|Name~Zlib|Name~Gcm" test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj
35+
- 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~Ecdh|Name~ECDsa|Name~Zlib|Name~Gcm" test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj
3636

3737
-
3838
matrix:

src/Renci.SshNet/Common/SshDataStream.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public bool IsEndOfData
5656
}
5757
}
5858

59-
#if NET462 || NETSTANDARD2_0
59+
#if NETFRAMEWORK || NETSTANDARD2_0
6060
private int Read(Span<byte> buffer)
6161
{
6262
var sharedBuffer = System.Buffers.ArrayPool<byte>.Shared.Rent(buffer.Length);

src/Renci.SshNet/Security/Cryptography/DsaKey.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,7 @@ public class DsaKey : Key, IDisposable
4141
/// </summary>
4242
public BigInteger X { get; }
4343

44-
/// <summary>
45-
/// Gets the length of the key in bits.
46-
/// </summary>
47-
/// <value>
48-
/// The bit-length of the key.
49-
/// </value>
44+
/// <inheritdoc/>
5045
public override int KeyLength
5146
{
5247
get

src/Renci.SshNet/Security/Cryptography/ED25519Key.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,7 @@ public override BigInteger[] Public
4040
}
4141
}
4242

43-
/// <summary>
44-
/// Gets the length of the key.
45-
/// </summary>
46-
/// <value>
47-
/// The length of the key.
48-
/// </value>
43+
/// <inheritdoc/>
4944
public override int KeyLength
5045
{
5146
get

src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs

Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Globalization;
32

43
using Renci.SshNet.Common;
54

@@ -11,7 +10,6 @@ namespace Renci.SshNet.Security.Cryptography
1110
public class EcdsaDigitalSignature : DigitalSignature, IDisposable
1211
{
1312
private readonly EcdsaKey _key;
14-
private bool _isDisposed;
1513

1614
/// <summary>
1715
/// Initializes a new instance of the <see cref="EcdsaDigitalSignature" /> class.
@@ -41,13 +39,8 @@ public override bool Verify(byte[] input, byte[] signature)
4139
// for 521 sig_size is 132
4240
var sig_size = _key.KeyLength == 521 ? 132 : _key.KeyLength / 4;
4341
var ssh_data = new SshDataSignature(signature, sig_size);
44-
#if NETFRAMEWORK
45-
var ecdsa = _key.Ecdsa;
46-
ecdsa.HashAlgorithm = _key.HashAlgorithm;
47-
return ecdsa.VerifyData(input, ssh_data.Signature);
48-
#else
49-
return _key.Ecdsa.VerifyData(input, ssh_data.Signature, _key.HashAlgorithm);
50-
#endif
42+
43+
return _key._impl.Verify(input, ssh_data.Signature);
5144
}
5245

5346
/// <summary>
@@ -59,13 +52,8 @@ public override bool Verify(byte[] input, byte[] signature)
5952
/// </returns>
6053
public override byte[] Sign(byte[] input)
6154
{
62-
#if NETFRAMEWORK
63-
var ecdsa = _key.Ecdsa;
64-
ecdsa.HashAlgorithm = _key.HashAlgorithm;
65-
var signed = ecdsa.SignData(input);
66-
#else
67-
var signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm);
68-
#endif
55+
var signed = _key._impl.Sign(input);
56+
6957
var ssh_data = new SshDataSignature(signed.Length) { Signature = signed };
7058
return ssh_data.GetBytes();
7159
}
@@ -85,23 +73,6 @@ public void Dispose()
8573
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
8674
protected virtual void Dispose(bool disposing)
8775
{
88-
if (_isDisposed)
89-
{
90-
return;
91-
}
92-
93-
if (disposing)
94-
{
95-
_isDisposed = true;
96-
}
97-
}
98-
99-
/// <summary>
100-
/// Finalizes an instance of the <see cref="EcdsaDigitalSignature"/> class.
101-
/// </summary>
102-
~EcdsaDigitalSignature()
103-
{
104-
Dispose(disposing: false);
10576
}
10677

10778
private sealed class SshDataSignature : SshData
@@ -155,18 +126,6 @@ protected override void SaveData()
155126
WriteBinaryString(_signature_s.ToBigInteger2().ToByteArray().Reverse());
156127
}
157128

158-
public new byte[] ReadBinary()
159-
{
160-
var length = ReadUInt32();
161-
162-
if (length > int.MaxValue)
163-
{
164-
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue));
165-
}
166-
167-
return ReadBytes((int)length);
168-
}
169-
170129
protected override int BufferCapacity
171130
{
172131
get
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#if !NET462
2+
#nullable enable
3+
using System;
4+
using System.Security.Cryptography;
5+
6+
using Renci.SshNet.Common;
7+
8+
namespace Renci.SshNet.Security
9+
{
10+
public partial class EcdsaKey : Key, IDisposable
11+
{
12+
private sealed class BclImpl : Impl
13+
{
14+
private readonly HashAlgorithmName _hashAlgorithmName;
15+
16+
public BclImpl(string curve_oid, int cord_size, byte[] qx, byte[] qy, byte[]? privatekey)
17+
{
18+
var curve = ECCurve.CreateFromValue(curve_oid);
19+
var parameter = new ECParameters
20+
{
21+
Curve = curve
22+
};
23+
24+
parameter.Q.X = qx;
25+
parameter.Q.Y = qy;
26+
27+
if (privatekey != null)
28+
{
29+
parameter.D = privatekey.TrimLeadingZeros().Pad(cord_size);
30+
PrivateKey = parameter.D;
31+
}
32+
33+
Ecdsa = ECDsa.Create(parameter);
34+
35+
_hashAlgorithmName = KeyLength switch
36+
{
37+
<= 256 => HashAlgorithmName.SHA256,
38+
<= 384 => HashAlgorithmName.SHA384,
39+
_ => HashAlgorithmName.SHA512,
40+
};
41+
}
42+
43+
public override byte[]? PrivateKey { get; }
44+
45+
public override ECDsa Ecdsa { get; }
46+
47+
public override int KeyLength
48+
{
49+
get
50+
{
51+
return Ecdsa.KeySize;
52+
}
53+
}
54+
55+
public override byte[] Sign(byte[] input)
56+
{
57+
return Ecdsa.SignData(input, _hashAlgorithmName);
58+
}
59+
60+
public override bool Verify(byte[] input, byte[] signature)
61+
{
62+
return Ecdsa.VerifyData(input, signature, _hashAlgorithmName);
63+
}
64+
65+
public override void Export(out byte[] qx, out byte[] qy)
66+
{
67+
var parameter = Ecdsa.ExportParameters(includePrivateParameters: false);
68+
qx = parameter.Q.X!;
69+
qy = parameter.Q.Y!;
70+
}
71+
72+
protected override void Dispose(bool disposing)
73+
{
74+
if (disposing)
75+
{
76+
Ecdsa.Dispose();
77+
}
78+
}
79+
}
80+
}
81+
}
82+
#endif
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#if !NET
2+
#nullable enable
3+
using System.Security.Cryptography;
4+
5+
using Org.BouncyCastle.Asn1;
6+
using Org.BouncyCastle.Asn1.Sec;
7+
using Org.BouncyCastle.Crypto;
8+
using Org.BouncyCastle.Crypto.Digests;
9+
using Org.BouncyCastle.Crypto.Parameters;
10+
using Org.BouncyCastle.Crypto.Signers;
11+
12+
using Renci.SshNet.Common;
13+
14+
namespace Renci.SshNet.Security
15+
{
16+
public partial class EcdsaKey
17+
{
18+
private sealed class BouncyCastleImpl : Impl
19+
{
20+
private readonly ECPublicKeyParameters _publicKeyParameters;
21+
private readonly ECPrivateKeyParameters? _privateKeyParameters;
22+
private readonly DsaDigestSigner _signer;
23+
24+
public BouncyCastleImpl(string curve_oid, byte[] qx, byte[] qy, byte[]? privatekey)
25+
{
26+
DerObjectIdentifier oid;
27+
IDigest digest;
28+
switch (curve_oid)
29+
{
30+
case ECDSA_P256_OID_VALUE:
31+
oid = SecObjectIdentifiers.SecP256r1;
32+
digest = new Sha256Digest();
33+
KeyLength = 256;
34+
break;
35+
case ECDSA_P384_OID_VALUE:
36+
oid = SecObjectIdentifiers.SecP384r1;
37+
digest = new Sha384Digest();
38+
KeyLength = 384;
39+
break;
40+
case ECDSA_P521_OID_VALUE:
41+
oid = SecObjectIdentifiers.SecP521r1;
42+
digest = new Sha512Digest();
43+
KeyLength = 521;
44+
break;
45+
default:
46+
throw new SshException("Unexpected OID: " + curve_oid);
47+
}
48+
49+
_signer = new DsaDigestSigner(new ECDsaSigner(), digest, PlainDsaEncoding.Instance);
50+
51+
var x9ECParameters = SecNamedCurves.GetByOid(oid);
52+
var domainParameter = new ECNamedDomainParameters(oid, x9ECParameters);
53+
54+
if (privatekey != null)
55+
{
56+
_privateKeyParameters = new ECPrivateKeyParameters(
57+
new Org.BouncyCastle.Math.BigInteger(1, privatekey),
58+
domainParameter);
59+
60+
_publicKeyParameters = new ECPublicKeyParameters(
61+
domainParameter.G.Multiply(_privateKeyParameters.D).Normalize(),
62+
domainParameter);
63+
}
64+
else
65+
{
66+
_publicKeyParameters = new ECPublicKeyParameters(
67+
x9ECParameters.Curve.CreatePoint(
68+
new Org.BouncyCastle.Math.BigInteger(1, qx),
69+
new Org.BouncyCastle.Math.BigInteger(1, qy)),
70+
domainParameter);
71+
}
72+
}
73+
74+
public override byte[]? PrivateKey { get; }
75+
76+
public override ECDsa? Ecdsa { get; }
77+
78+
public override int KeyLength { get; }
79+
80+
public override byte[] Sign(byte[] input)
81+
{
82+
_signer.Init(forSigning: true, _privateKeyParameters);
83+
_signer.BlockUpdate(input, 0, input.Length);
84+
85+
return _signer.GenerateSignature();
86+
}
87+
88+
public override bool Verify(byte[] input, byte[] signature)
89+
{
90+
_signer.Init(forSigning: false, _publicKeyParameters);
91+
_signer.BlockUpdate(input, 0, input.Length);
92+
93+
return _signer.VerifySignature(signature);
94+
}
95+
96+
public override void Export(out byte[] qx, out byte[] qy)
97+
{
98+
qx = _publicKeyParameters.Q.XCoord.GetEncoded();
99+
qy = _publicKeyParameters.Q.YCoord.GetEncoded();
100+
}
101+
102+
protected override void Dispose(bool disposing)
103+
{
104+
}
105+
}
106+
}
107+
}
108+
#endif

0 commit comments

Comments
 (0)