Skip to content

Commit 22cffd9

Browse files
committed
Use BouncyCastle ECDsa when runtime is Mono
1 parent 889f4f6 commit 22cffd9

File tree

3 files changed

+200
-64
lines changed

3 files changed

+200
-64
lines changed

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ for:
2828
- 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
2929
- sh: echo "Run integration tests"
3030
- 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
31-
- 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\!~ECDsa test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj
31+
- 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\~ECDsa test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj
3232

3333
-
3434
matrix:

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

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Globalization;
33

4+
using Org.BouncyCastle.Crypto.Signers;
5+
46
using Renci.SshNet.Common;
57

68
namespace Renci.SshNet.Security.Cryptography
@@ -41,6 +43,16 @@ public override bool Verify(byte[] input, byte[] signature)
4143
// for 521 sig_size is 132
4244
var sig_size = _key.KeyLength == 521 ? 132 : _key.KeyLength / 4;
4345
var ssh_data = new SshDataSignature(signature, sig_size);
46+
47+
if (_key.PublicKeyParameters != null)
48+
{
49+
var signer = new DsaDigestSigner(new ECDsaSigner(), _key.Digest, PlainDsaEncoding.Instance);
50+
signer.Init(forSigning: false, _key.PublicKeyParameters);
51+
signer.BlockUpdate(input, 0, input.Length);
52+
53+
return signer.VerifySignature(ssh_data.Signature);
54+
}
55+
4456
#if NETFRAMEWORK
4557
var ecdsa = _key.Ecdsa;
4658
ecdsa.HashAlgorithm = _key.HashAlgorithm;
@@ -59,13 +71,27 @@ public override bool Verify(byte[] input, byte[] signature)
5971
/// </returns>
6072
public override byte[] Sign(byte[] input)
6173
{
74+
byte[] signed = null;
75+
76+
if (_key.PrivateKeyParameters != null)
77+
{
78+
var signer = new DsaDigestSigner(new ECDsaSigner(), _key.Digest, PlainDsaEncoding.Instance);
79+
signer.Init(forSigning: true, _key.PrivateKeyParameters);
80+
signer.BlockUpdate(input, 0, input.Length);
81+
82+
signed = signer.GenerateSignature();
83+
}
84+
else
85+
{
6286
#if NETFRAMEWORK
63-
var ecdsa = _key.Ecdsa;
64-
ecdsa.HashAlgorithm = _key.HashAlgorithm;
65-
var signed = ecdsa.SignData(input);
87+
var ecdsa = _key.Ecdsa;
88+
ecdsa.HashAlgorithm = _key.HashAlgorithm;
89+
signed = ecdsa.SignData(input);
6690
#else
67-
var signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm);
91+
signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm);
6892
#endif
93+
}
94+
6995
var ssh_data = new SshDataSignature(signed.Length) { Signature = signed };
7096
return ssh_data.GetBytes();
7197
}

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

Lines changed: 169 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
using System.Security.Cryptography;
88
using System.Text;
99

10+
using Org.BouncyCastle.Asn1;
11+
using Org.BouncyCastle.Asn1.Sec;
12+
using Org.BouncyCastle.Crypto;
13+
using Org.BouncyCastle.Crypto.Digests;
14+
using Org.BouncyCastle.Crypto.Parameters;
15+
1016
using Renci.SshNet.Common;
1117
using Renci.SshNet.Security.Cryptography;
1218

@@ -23,6 +29,7 @@ public class EcdsaKey : Key, IDisposable
2329
private const string ECDSA_P521_OID_VALUE = "1.3.132.0.35"; // Also called nistP521or secP521r1
2430
#pragma warning restore SA1310 // Field names should not contain underscore
2531

32+
private int _keySize;
2633
private EcdsaDigitalSignature _digitalSignature;
2734
private bool _isDisposed;
2835

@@ -68,6 +75,27 @@ public override string ToString()
6875
return string.Format("ecdsa-sha2-nistp{0}", KeyLength);
6976
}
7077

78+
/// <summary>
79+
/// Gets the Digest to use.
80+
/// </summary>
81+
public IDigest Digest
82+
{
83+
get
84+
{
85+
switch (KeyLength)
86+
{
87+
case 256:
88+
return new Sha256Digest();
89+
case 384:
90+
return new Sha384Digest();
91+
case 521:
92+
return new Sha512Digest();
93+
default:
94+
throw new SshException("Unknown KeySize: " + KeyLength.ToString());
95+
}
96+
}
97+
}
98+
7199
#if NETFRAMEWORK
72100
/// <summary>
73101
/// Gets the HashAlgorithm to use.
@@ -122,7 +150,7 @@ public override int KeyLength
122150
{
123151
get
124152
{
125-
return Ecdsa.KeySize;
153+
return _keySize;
126154
}
127155
}
128156

@@ -153,56 +181,81 @@ public override BigInteger[] Public
153181
byte[] curve;
154182
byte[] qx;
155183
byte[] qy;
156-
#if NETFRAMEWORK
157-
var blob = _key.Export(CngKeyBlobFormat.EccPublicBlob);
158184

159-
KeyBlobMagicNumber magic;
160-
using (var br = new BinaryReader(new MemoryStream(blob)))
185+
if (PublicKeyParameters != null)
161186
{
162-
magic = (KeyBlobMagicNumber)br.ReadInt32();
163-
var cbKey = br.ReadInt32();
164-
qx = br.ReadBytes(cbKey);
165-
qy = br.ReadBytes(cbKey);
187+
var oid = PublicKeyParameters.PublicKeyParamSet.GetID();
188+
switch (oid)
189+
{
190+
case ECDSA_P256_OID_VALUE:
191+
curve = Encoding.ASCII.GetBytes("nistp256");
192+
break;
193+
case ECDSA_P384_OID_VALUE:
194+
curve = Encoding.ASCII.GetBytes("nistp384");
195+
break;
196+
case ECDSA_P521_OID_VALUE:
197+
curve = Encoding.ASCII.GetBytes("nistp521");
198+
break;
199+
default:
200+
throw new SshException("Unexpected OID: " + oid);
201+
}
202+
203+
qx = PublicKeyParameters.Q.XCoord.GetEncoded();
204+
qy = PublicKeyParameters.Q.YCoord.GetEncoded();
166205
}
206+
else
207+
{
208+
#if NETFRAMEWORK
209+
var blob = _key.Export(CngKeyBlobFormat.EccPublicBlob);
210+
211+
KeyBlobMagicNumber magic;
212+
using (var br = new BinaryReader(new MemoryStream(blob)))
213+
{
214+
magic = (KeyBlobMagicNumber)br.ReadInt32();
215+
var cbKey = br.ReadInt32();
216+
qx = br.ReadBytes(cbKey);
217+
qy = br.ReadBytes(cbKey);
218+
}
167219

168220
#pragma warning disable IDE0010 // Add missing cases
169-
switch (magic)
170-
{
171-
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P256_MAGIC:
172-
curve = Encoding.ASCII.GetBytes("nistp256");
173-
break;
174-
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P384_MAGIC:
175-
curve = Encoding.ASCII.GetBytes("nistp384");
176-
break;
177-
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P521_MAGIC:
178-
curve = Encoding.ASCII.GetBytes("nistp521");
179-
break;
180-
default:
181-
throw new SshException("Unexpected Curve Magic: " + magic);
182-
}
221+
switch (magic)
222+
{
223+
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P256_MAGIC:
224+
curve = Encoding.ASCII.GetBytes("nistp256");
225+
break;
226+
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P384_MAGIC:
227+
curve = Encoding.ASCII.GetBytes("nistp384");
228+
break;
229+
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P521_MAGIC:
230+
curve = Encoding.ASCII.GetBytes("nistp521");
231+
break;
232+
default:
233+
throw new SshException("Unexpected Curve Magic: " + magic);
234+
}
183235
#pragma warning restore IDE0010 // Add missing cases
184236
#else
185-
var parameter = Ecdsa.ExportParameters(includePrivateParameters: false);
186-
qx = parameter.Q.X;
187-
qy = parameter.Q.Y;
188-
switch (parameter.Curve.Oid.FriendlyName)
189-
{
190-
case "ECDSA_P256":
191-
case "nistP256":
192-
curve = Encoding.ASCII.GetBytes("nistp256");
193-
break;
194-
case "ECDSA_P384":
195-
case "nistP384":
196-
curve = Encoding.ASCII.GetBytes("nistp384");
197-
break;
198-
case "ECDSA_P521":
199-
case "nistP521":
200-
curve = Encoding.ASCII.GetBytes("nistp521");
201-
break;
202-
default:
203-
throw new SshException("Unexpected Curve Name: " + parameter.Curve.Oid.FriendlyName);
204-
}
237+
var parameter = Ecdsa.ExportParameters(includePrivateParameters: false);
238+
qx = parameter.Q.X;
239+
qy = parameter.Q.Y;
240+
switch (parameter.Curve.Oid.FriendlyName)
241+
{
242+
case "ECDSA_P256":
243+
case "nistP256":
244+
curve = Encoding.ASCII.GetBytes("nistp256");
245+
break;
246+
case "ECDSA_P384":
247+
case "nistP384":
248+
curve = Encoding.ASCII.GetBytes("nistp384");
249+
break;
250+
case "ECDSA_P521":
251+
case "nistP521":
252+
curve = Encoding.ASCII.GetBytes("nistp521");
253+
break;
254+
default:
255+
throw new SshException("Unexpected Curve Name: " + parameter.Curve.Oid.FriendlyName);
256+
}
205257
#endif
258+
}
206259

207260
// Make ECPoint from x and y
208261
// Prepend 04 (uncompressed format) + qx-bytes + qy-bytes
@@ -216,6 +269,18 @@ public override BigInteger[] Public
216269
}
217270
}
218271

272+
internal ECPrivateKeyParameters PrivateKeyParameters
273+
{
274+
get;
275+
private set;
276+
}
277+
278+
internal ECPublicKeyParameters PublicKeyParameters
279+
{
280+
get;
281+
private set;
282+
}
283+
219284
/// <summary>
220285
/// Gets the PrivateKey Bytes.
221286
/// </summary>
@@ -322,6 +387,65 @@ public EcdsaKey(byte[] data)
322387

323388
private void Import(string curve_oid, byte[] publickey, byte[] privatekey)
324389
{
390+
// ECPoint as BigInteger(2)
391+
var cord_size = (publickey.Length - 1) / 2;
392+
var qx = new byte[cord_size];
393+
Buffer.BlockCopy(publickey, 1, qx, 0, qx.Length);
394+
395+
var qy = new byte[cord_size];
396+
Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length);
397+
398+
var isMono = Type.GetType("Mono.Runtime") == null;
399+
400+
if (isMono)
401+
{
402+
DerObjectIdentifier oid;
403+
switch (curve_oid)
404+
{
405+
case ECDSA_P256_OID_VALUE:
406+
oid = SecObjectIdentifiers.SecP256r1;
407+
_keySize = 256;
408+
break;
409+
case ECDSA_P384_OID_VALUE:
410+
oid = SecObjectIdentifiers.SecP384r1;
411+
_keySize = 384;
412+
break;
413+
case ECDSA_P521_OID_VALUE:
414+
oid = SecObjectIdentifiers.SecP521r1;
415+
_keySize = 521;
416+
break;
417+
default:
418+
throw new SshException("Unexpected OID: " + curve_oid);
419+
}
420+
421+
var x9ECParameters = SecNamedCurves.GetByOid(oid);
422+
var domainParameter = new ECNamedDomainParameters(oid, x9ECParameters);
423+
424+
if (privatekey != null)
425+
{
426+
privatekey = privatekey.TrimLeadingZeros().Pad(cord_size);
427+
PrivateKey = privatekey;
428+
429+
PrivateKeyParameters = new ECPrivateKeyParameters(
430+
new Org.BouncyCastle.Math.BigInteger(1, privatekey),
431+
domainParameter);
432+
433+
PublicKeyParameters = new ECPublicKeyParameters(
434+
domainParameter.G.Multiply(PrivateKeyParameters.D).Normalize(),
435+
domainParameter);
436+
}
437+
else
438+
{
439+
PublicKeyParameters = new ECPublicKeyParameters(
440+
x9ECParameters.Curve.CreatePoint(
441+
new Org.BouncyCastle.Math.BigInteger(1, qx),
442+
new Org.BouncyCastle.Math.BigInteger(1, qy)),
443+
domainParameter);
444+
}
445+
446+
return;
447+
}
448+
325449
#if NETFRAMEWORK
326450
KeyBlobMagicNumber curve_magic;
327451

@@ -364,14 +488,6 @@ private void Import(string curve_oid, byte[] publickey, byte[] privatekey)
364488
throw new SshException("Unknown: " + curve_oid);
365489
}
366490

367-
// ECPoint as BigInteger(2)
368-
var cord_size = (publickey.Length - 1) / 2;
369-
var qx = new byte[cord_size];
370-
Buffer.BlockCopy(publickey, 1, qx, 0, qx.Length);
371-
372-
var qy = new byte[cord_size];
373-
Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length);
374-
375491
if (privatekey != null)
376492
{
377493
privatekey = privatekey.Pad(cord_size);
@@ -401,21 +517,14 @@ private void Import(string curve_oid, byte[] publickey, byte[] privatekey)
401517
_key = CngKey.Import(blob, privatekey is null ? CngKeyBlobFormat.EccPublicBlob : CngKeyBlobFormat.EccPrivateBlob);
402518

403519
Ecdsa = new ECDsaCng(_key);
520+
_keySize = Ecdsa.KeySize;
404521
#else
405522
var curve = ECCurve.CreateFromValue(curve_oid);
406523
var parameter = new ECParameters
407524
{
408525
Curve = curve
409526
};
410527

411-
// ECPoint as BigInteger(2)
412-
var cord_size = (publickey.Length - 1) / 2;
413-
var qx = new byte[cord_size];
414-
Buffer.BlockCopy(publickey, 1, qx, 0, qx.Length);
415-
416-
var qy = new byte[cord_size];
417-
Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length);
418-
419528
parameter.Q.X = qx;
420529
parameter.Q.Y = qy;
421530

@@ -426,6 +535,7 @@ private void Import(string curve_oid, byte[] publickey, byte[] privatekey)
426535
}
427536

428537
Ecdsa = ECDsa.Create(parameter);
538+
_keySize = Ecdsa.KeySize;
429539
#endif
430540
}
431541

0 commit comments

Comments
 (0)