Skip to content

Commit

Permalink
[ECDsa] Falls back to use BouncyCastle if BCL (Mono) doesn't support (#…
Browse files Browse the repository at this point in the history
…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]>
  • Loading branch information
3 people authored Aug 31, 2024
1 parent 3dda5c9 commit fe827a5
Show file tree
Hide file tree
Showing 16 changed files with 494 additions and 338 deletions.
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

0 comments on commit fe827a5

Please sign in to comment.