Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Use BouncyCastle ECDsa when runtime is Mono
  • Loading branch information
scott-xu committed Aug 4, 2024
commit 01ac3fce846d9a96e15063c1b6c8e0f6a2db7084
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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\!~ECDsa 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~ECDsa test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj

-
matrix:
Expand Down
34 changes: 30 additions & 4 deletions src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Globalization;

using Org.BouncyCastle.Crypto.Signers;

using Renci.SshNet.Common;

namespace Renci.SshNet.Security.Cryptography
Expand Down Expand Up @@ -41,6 +43,16 @@ public override bool Verify(byte[] input, byte[] signature)
// for 521 sig_size is 132
var sig_size = _key.KeyLength == 521 ? 132 : _key.KeyLength / 4;
var ssh_data = new SshDataSignature(signature, sig_size);

if (_key.PublicKeyParameters != null)
{
var signer = new DsaDigestSigner(new ECDsaSigner(), _key.Digest, PlainDsaEncoding.Instance);
signer.Init(forSigning: false, _key.PublicKeyParameters);
signer.BlockUpdate(input, 0, input.Length);

return signer.VerifySignature(ssh_data.Signature);
}

#if NETFRAMEWORK
var ecdsa = _key.Ecdsa;
ecdsa.HashAlgorithm = _key.HashAlgorithm;
Expand All @@ -59,13 +71,27 @@ public override bool Verify(byte[] input, byte[] signature)
/// </returns>
public override byte[] Sign(byte[] input)
{
byte[] signed = null;

if (_key.PrivateKeyParameters != null)
{
var signer = new DsaDigestSigner(new ECDsaSigner(), _key.Digest, PlainDsaEncoding.Instance);
signer.Init(forSigning: true, _key.PrivateKeyParameters);
signer.BlockUpdate(input, 0, input.Length);

signed = signer.GenerateSignature();
}
else
{
#if NETFRAMEWORK
var ecdsa = _key.Ecdsa;
ecdsa.HashAlgorithm = _key.HashAlgorithm;
var signed = ecdsa.SignData(input);
var ecdsa = _key.Ecdsa;
ecdsa.HashAlgorithm = _key.HashAlgorithm;
signed = ecdsa.SignData(input);
#else
var signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm);
signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm);
#endif
}

var ssh_data = new SshDataSignature(signed.Length) { Signature = signed };
return ssh_data.GetBytes();
}
Expand Down
228 changes: 169 additions & 59 deletions src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
using System.Security.Cryptography;
using System.Text;

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Parameters;

using Renci.SshNet.Common;
using Renci.SshNet.Security.Cryptography;

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

private int _keySize;
private EcdsaDigitalSignature _digitalSignature;
private bool _isDisposed;

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

/// <summary>
/// Gets the Digest to use.
/// </summary>
public IDigest Digest
{
get
{
switch (KeyLength)
{
case 256:
return new Sha256Digest();
case 384:
return new Sha384Digest();
case 521:
return new Sha512Digest();
default:
throw new SshException("Unknown KeySize: " + KeyLength.ToString());
}
}
}

#if NETFRAMEWORK
/// <summary>
/// Gets the HashAlgorithm to use.
Expand Down Expand Up @@ -122,7 +150,7 @@ public override int KeyLength
{
get
{
return Ecdsa.KeySize;
return _keySize;
}
}

Expand Down Expand Up @@ -153,56 +181,81 @@ public override BigInteger[] Public
byte[] curve;
byte[] qx;
byte[] qy;
#if NETFRAMEWORK
var blob = _key.Export(CngKeyBlobFormat.EccPublicBlob);

KeyBlobMagicNumber magic;
using (var br = new BinaryReader(new MemoryStream(blob)))
if (PublicKeyParameters != null)
{
magic = (KeyBlobMagicNumber)br.ReadInt32();
var cbKey = br.ReadInt32();
qx = br.ReadBytes(cbKey);
qy = br.ReadBytes(cbKey);
var oid = PublicKeyParameters.PublicKeyParamSet.GetID();
switch (oid)
{
case ECDSA_P256_OID_VALUE:
curve = Encoding.ASCII.GetBytes("nistp256");
break;
case ECDSA_P384_OID_VALUE:
curve = Encoding.ASCII.GetBytes("nistp384");
break;
case ECDSA_P521_OID_VALUE:
curve = Encoding.ASCII.GetBytes("nistp521");
break;
default:
throw new SshException("Unexpected OID: " + oid);
}

qx = PublicKeyParameters.Q.XCoord.GetEncoded();
qy = PublicKeyParameters.Q.YCoord.GetEncoded();
}
else
{
#if NETFRAMEWORK
var blob = _key.Export(CngKeyBlobFormat.EccPublicBlob);

KeyBlobMagicNumber magic;
using (var br = new BinaryReader(new MemoryStream(blob)))
{
magic = (KeyBlobMagicNumber)br.ReadInt32();
var cbKey = br.ReadInt32();
qx = br.ReadBytes(cbKey);
qy = br.ReadBytes(cbKey);
}

#pragma warning disable IDE0010 // Add missing cases
switch (magic)
{
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P256_MAGIC:
curve = Encoding.ASCII.GetBytes("nistp256");
break;
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P384_MAGIC:
curve = Encoding.ASCII.GetBytes("nistp384");
break;
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P521_MAGIC:
curve = Encoding.ASCII.GetBytes("nistp521");
break;
default:
throw new SshException("Unexpected Curve Magic: " + magic);
}
switch (magic)
{
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P256_MAGIC:
curve = Encoding.ASCII.GetBytes("nistp256");
break;
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P384_MAGIC:
curve = Encoding.ASCII.GetBytes("nistp384");
break;
case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P521_MAGIC:
curve = Encoding.ASCII.GetBytes("nistp521");
break;
default:
throw new SshException("Unexpected Curve Magic: " + magic);
}
#pragma warning restore IDE0010 // Add missing cases
#else
var parameter = Ecdsa.ExportParameters(includePrivateParameters: false);
qx = parameter.Q.X;
qy = parameter.Q.Y;
switch (parameter.Curve.Oid.FriendlyName)
{
case "ECDSA_P256":
case "nistP256":
curve = Encoding.ASCII.GetBytes("nistp256");
break;
case "ECDSA_P384":
case "nistP384":
curve = Encoding.ASCII.GetBytes("nistp384");
break;
case "ECDSA_P521":
case "nistP521":
curve = Encoding.ASCII.GetBytes("nistp521");
break;
default:
throw new SshException("Unexpected Curve Name: " + parameter.Curve.Oid.FriendlyName);
}
var parameter = Ecdsa.ExportParameters(includePrivateParameters: false);
qx = parameter.Q.X;
qy = parameter.Q.Y;
switch (parameter.Curve.Oid.FriendlyName)
{
case "ECDSA_P256":
case "nistP256":
curve = Encoding.ASCII.GetBytes("nistp256");
break;
case "ECDSA_P384":
case "nistP384":
curve = Encoding.ASCII.GetBytes("nistp384");
break;
case "ECDSA_P521":
case "nistP521":
curve = Encoding.ASCII.GetBytes("nistp521");
break;
default:
throw new SshException("Unexpected Curve Name: " + parameter.Curve.Oid.FriendlyName);
}
#endif
}

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

internal ECPrivateKeyParameters PrivateKeyParameters
{
get;
private set;
}

internal ECPublicKeyParameters PublicKeyParameters
{
get;
private set;
}

/// <summary>
/// Gets the PrivateKey Bytes.
/// </summary>
Expand Down Expand Up @@ -322,6 +387,65 @@ public EcdsaKey(byte[] data)

private void Import(string curve_oid, byte[] publickey, byte[] privatekey)
{
// ECPoint as BigInteger(2)
var cord_size = (publickey.Length - 1) / 2;
var qx = new byte[cord_size];
Buffer.BlockCopy(publickey, 1, qx, 0, qx.Length);

var qy = new byte[cord_size];
Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length);

var isMono = Type.GetType("Mono.Runtime") != null;

if (isMono)
{
DerObjectIdentifier oid;
switch (curve_oid)
{
case ECDSA_P256_OID_VALUE:
oid = SecObjectIdentifiers.SecP256r1;
_keySize = 256;
break;
case ECDSA_P384_OID_VALUE:
oid = SecObjectIdentifiers.SecP384r1;
_keySize = 384;
break;
case ECDSA_P521_OID_VALUE:
oid = SecObjectIdentifiers.SecP521r1;
_keySize = 521;
break;
default:
throw new SshException("Unexpected OID: " + curve_oid);
}

var x9ECParameters = SecNamedCurves.GetByOid(oid);
var domainParameter = new ECNamedDomainParameters(oid, x9ECParameters);

if (privatekey != null)
{
privatekey = privatekey.TrimLeadingZeros().Pad(cord_size);
PrivateKey = privatekey;

PrivateKeyParameters = new ECPrivateKeyParameters(
new Org.BouncyCastle.Math.BigInteger(1, privatekey),
domainParameter);

PublicKeyParameters = new ECPublicKeyParameters(
domainParameter.G.Multiply(PrivateKeyParameters.D).Normalize(),
domainParameter);
}
else
{
PublicKeyParameters = new ECPublicKeyParameters(
x9ECParameters.Curve.CreatePoint(
new Org.BouncyCastle.Math.BigInteger(1, qx),
new Org.BouncyCastle.Math.BigInteger(1, qy)),
domainParameter);
}

return;
}

#if NETFRAMEWORK
KeyBlobMagicNumber curve_magic;

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

// ECPoint as BigInteger(2)
var cord_size = (publickey.Length - 1) / 2;
var qx = new byte[cord_size];
Buffer.BlockCopy(publickey, 1, qx, 0, qx.Length);

var qy = new byte[cord_size];
Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length);

if (privatekey != null)
{
privatekey = privatekey.Pad(cord_size);
Expand Down Expand Up @@ -401,21 +517,14 @@ private void Import(string curve_oid, byte[] publickey, byte[] privatekey)
_key = CngKey.Import(blob, privatekey is null ? CngKeyBlobFormat.EccPublicBlob : CngKeyBlobFormat.EccPrivateBlob);

Ecdsa = new ECDsaCng(_key);
_keySize = Ecdsa.KeySize;
#else
var curve = ECCurve.CreateFromValue(curve_oid);
var parameter = new ECParameters
{
Curve = curve
};

// ECPoint as BigInteger(2)
var cord_size = (publickey.Length - 1) / 2;
var qx = new byte[cord_size];
Buffer.BlockCopy(publickey, 1, qx, 0, qx.Length);

var qy = new byte[cord_size];
Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length);

parameter.Q.X = qx;
parameter.Q.Y = qy;

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

Ecdsa = ECDsa.Create(parameter);
_keySize = Ecdsa.KeySize;
#endif
}

Expand Down