Skip to content

[ECDsa] Falls back to use BouncyCastle if BCL (Mono) doesn't support #1461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ for:
# some coverage until a proper solution for running the .NET Framework integration tests in CI is found.
# Running all the tests causes problems which are not worth solving in this rare configuration.
# See https://github.com/sshnet/SSH.NET/pull/1462 and related links
- 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
- 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

-
matrix:
Expand Down
2 changes: 1 addition & 1 deletion src/Renci.SshNet/Common/SshDataStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public bool IsEndOfData
}
}

#if NET462 || NETSTANDARD2_0
#if NETFRAMEWORK || NETSTANDARD2_0
private int Read(Span<byte> buffer)
{
var sharedBuffer = System.Buffers.ArrayPool<byte>.Shared.Rent(buffer.Length);
Expand Down
7 changes: 1 addition & 6 deletions src/Renci.SshNet/Security/Cryptography/DsaKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,7 @@ public class DsaKey : Key, IDisposable
/// </summary>
public BigInteger X { get; }

/// <summary>
/// Gets the length of the key in bits.
/// </summary>
/// <value>
/// The bit-length of the key.
/// </value>
/// <inheritdoc/>
public override int KeyLength
{
get
Expand Down
7 changes: 1 addition & 6 deletions src/Renci.SshNet/Security/Cryptography/ED25519Key.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,7 @@ public override BigInteger[] Public
}
}

/// <summary>
/// Gets the length of the key.
/// </summary>
/// <value>
/// The length of the key.
/// </value>
/// <inheritdoc/>
public override int KeyLength
{
get
Expand Down
49 changes: 4 additions & 45 deletions src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Globalization;

using Renci.SshNet.Common;

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

/// <summary>
/// Initializes a new instance of the <see cref="EcdsaDigitalSignature" /> class.
Expand Down Expand Up @@ -41,13 +39,8 @@ 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 NETFRAMEWORK
var ecdsa = _key.Ecdsa;
ecdsa.HashAlgorithm = _key.HashAlgorithm;
return ecdsa.VerifyData(input, ssh_data.Signature);
#else
return _key.Ecdsa.VerifyData(input, ssh_data.Signature, _key.HashAlgorithm);
#endif

return _key._impl.Verify(input, ssh_data.Signature);
}

/// <summary>
Expand All @@ -59,13 +52,8 @@ public override bool Verify(byte[] input, byte[] signature)
/// </returns>
public override byte[] Sign(byte[] input)
{
#if NETFRAMEWORK
var ecdsa = _key.Ecdsa;
ecdsa.HashAlgorithm = _key.HashAlgorithm;
var signed = ecdsa.SignData(input);
#else
var signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm);
#endif
var signed = _key._impl.Sign(input);

var ssh_data = new SshDataSignature(signed.Length) { Signature = signed };
return ssh_data.GetBytes();
}
Expand All @@ -85,23 +73,6 @@ public void Dispose()
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}

if (disposing)
{
_isDisposed = true;
}
}

/// <summary>
/// Finalizes an instance of the <see cref="EcdsaDigitalSignature"/> class.
/// </summary>
~EcdsaDigitalSignature()
{
Dispose(disposing: false);
}

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

public new byte[] ReadBinary()
{
var length = ReadUInt32();

if (length > int.MaxValue)
{
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue));
}

return ReadBytes((int)length);
}

protected override int BufferCapacity
{
get
Expand Down
82 changes: 82 additions & 0 deletions src/Renci.SshNet/Security/Cryptography/EcdsaKey.BclImpl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#if !NET462
#nullable enable
using System;
using System.Security.Cryptography;

using Renci.SshNet.Common;

namespace Renci.SshNet.Security
{
public partial class EcdsaKey : Key, IDisposable
{
private sealed class BclImpl : Impl
{
private readonly HashAlgorithmName _hashAlgorithmName;

public BclImpl(string curve_oid, int cord_size, byte[] qx, byte[] qy, byte[]? privatekey)
{
var curve = ECCurve.CreateFromValue(curve_oid);
var parameter = new ECParameters
{
Curve = curve
};

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

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

Ecdsa = ECDsa.Create(parameter);

_hashAlgorithmName = KeyLength switch
{
<= 256 => HashAlgorithmName.SHA256,
<= 384 => HashAlgorithmName.SHA384,
_ => HashAlgorithmName.SHA512,
};
}

public override byte[]? PrivateKey { get; }

public override ECDsa Ecdsa { get; }

public override int KeyLength
{
get
{
return Ecdsa.KeySize;
}
}

public override byte[] Sign(byte[] input)
{
return Ecdsa.SignData(input, _hashAlgorithmName);
}

public override bool Verify(byte[] input, byte[] signature)
{
return Ecdsa.VerifyData(input, signature, _hashAlgorithmName);
}

public override void Export(out byte[] qx, out byte[] qy)
{
var parameter = Ecdsa.ExportParameters(includePrivateParameters: false);
qx = parameter.Q.X!;
qy = parameter.Q.Y!;
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
Ecdsa.Dispose();
}
}
}
}
}
#endif
108 changes: 108 additions & 0 deletions src/Renci.SshNet/Security/Cryptography/EcdsaKey.BouncyCastleImpl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#if !NET
#nullable enable
using System.Security.Cryptography;

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

using Renci.SshNet.Common;

namespace Renci.SshNet.Security
{
public partial class EcdsaKey
{
private sealed class BouncyCastleImpl : Impl
{
private readonly ECPublicKeyParameters _publicKeyParameters;
private readonly ECPrivateKeyParameters? _privateKeyParameters;
private readonly DsaDigestSigner _signer;

public BouncyCastleImpl(string curve_oid, byte[] qx, byte[] qy, byte[]? privatekey)
{
DerObjectIdentifier oid;
IDigest digest;
switch (curve_oid)
{
case ECDSA_P256_OID_VALUE:
oid = SecObjectIdentifiers.SecP256r1;
digest = new Sha256Digest();
KeyLength = 256;
break;
case ECDSA_P384_OID_VALUE:
oid = SecObjectIdentifiers.SecP384r1;
digest = new Sha384Digest();
KeyLength = 384;
break;
case ECDSA_P521_OID_VALUE:
oid = SecObjectIdentifiers.SecP521r1;
digest = new Sha512Digest();
KeyLength = 521;
break;
default:
throw new SshException("Unexpected OID: " + curve_oid);
}

_signer = new DsaDigestSigner(new ECDsaSigner(), digest, PlainDsaEncoding.Instance);

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

if (privatekey != null)
{
_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);
}
}

public override byte[]? PrivateKey { get; }

public override ECDsa? Ecdsa { get; }

public override int KeyLength { get; }

public override byte[] Sign(byte[] input)
{
_signer.Init(forSigning: true, _privateKeyParameters);
_signer.BlockUpdate(input, 0, input.Length);

return _signer.GenerateSignature();
}

public override bool Verify(byte[] input, byte[] signature)
{
_signer.Init(forSigning: false, _publicKeyParameters);
_signer.BlockUpdate(input, 0, input.Length);

return _signer.VerifySignature(signature);
}

public override void Export(out byte[] qx, out byte[] qy)
{
qx = _publicKeyParameters.Q.XCoord.GetEncoded();
qy = _publicKeyParameters.Q.YCoord.GetEncoded();
}

protected override void Dispose(bool disposing)
{
}
}
}
}
#endif
Loading