#nullable enable
using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using Renci.SshNet.Common;
using Renci.SshNet.Security.Cryptography;
namespace Renci.SshNet.Security
{
///
/// Contains ECDSA (ecdsa-sha2-nistp{256,384,521}) private and public key.
///
public partial class EcdsaKey : Key, IDisposable
{
#pragma warning disable SA1310 // Field names should not contain underscore
private const string ECDSA_P256_OID_VALUE = "1.2.840.10045.3.1.7"; // Also called nistP256 or secP256r1
private const string ECDSA_P384_OID_VALUE = "1.3.132.0.34"; // Also called nistP384 or secP384r1
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 static readonly BigInteger Encoded256 = new BigInteger("nistp256"u8.ToArray().Reverse());
private static readonly BigInteger Encoded384 = new BigInteger("nistp384"u8.ToArray().Reverse());
private static readonly BigInteger Encoded521 = new BigInteger("nistp521"u8.ToArray().Reverse());
private EcdsaDigitalSignature? _digitalSignature;
#pragma warning disable SA1401 // Fields should be private; internal readonly
internal readonly Impl _impl;
#pragma warning restore SA1401 // Fields should be private
///
/// Gets the SSH name of the ECDSA Key.
///
///
/// The SSH name of the ECDSA Key.
///
public override string ToString()
{
return string.Format("ecdsa-sha2-nistp{0}", KeyLength);
}
///
/// Gets the HashAlgorithm to use.
///
public HashAlgorithmName HashAlgorithm
{
get
{
switch (KeyLength)
{
case 256:
return HashAlgorithmName.SHA256;
case 384:
return HashAlgorithmName.SHA384;
case 521:
return HashAlgorithmName.SHA512;
default:
return HashAlgorithmName.SHA256;
}
}
}
internal abstract class Impl : IDisposable
{
public abstract int KeyLength { get; }
public abstract byte[]? PrivateKey { get; }
#if NET
public abstract ECDsa Ecdsa { get; }
#else
public abstract ECDsa? Ecdsa { get; }
#endif
public abstract bool Verify(byte[] input, byte[] signature);
public abstract byte[] Sign(byte[] input);
public abstract void Export(out byte[] qx, out byte[] qy);
protected abstract void Dispose(bool disposing);
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
///
public override int KeyLength
{
get
{
return _impl.KeyLength;
}
}
///
protected internal override DigitalSignature DigitalSignature
{
get
{
_digitalSignature ??= new EcdsaDigitalSignature(this);
return _digitalSignature;
}
}
///
/// Gets the ECDSA public key.
///
///
/// An array with the ASCII-encoded curve identifier (e.g. "nistp256")
/// at index 0, and the public curve point Q at index 1.
///
public override BigInteger[] Public
{
get
{
BigInteger curve;
switch (KeyLength)
{
case 256:
curve = Encoded256;
break;
case 384:
curve = Encoded384;
break;
default:
Debug.Assert(KeyLength == 521);
curve = Encoded521;
break;
}
_impl.Export(out var qx, out var qy);
// Make ECPoint from x and y
// Prepend 04 (uncompressed format) + qx-bytes + qy-bytes
var q = new byte[1 + qx.Length + qy.Length];
q[0] = 0x4;
Buffer.BlockCopy(qx, 0, q, 1, qx.Length);
Buffer.BlockCopy(qy, 0, q, qx.Length + 1, qy.Length);
// returns Curve-Name and x/y as ECPoint
return new[] { curve, new BigInteger(q.Reverse()) };
}
}
///
/// Gets the PrivateKey Bytes.
///
public byte[]? PrivateKey
{
get
{
return _impl.PrivateKey;
}
}
///
/// Gets the object.
///
#if NET
public ECDsa Ecdsa
#else
public ECDsa? Ecdsa
#endif
{
get
{
return _impl.Ecdsa;
}
}
///
/// Initializes a new instance of the class.
///
/// The encoded public key data.
public EcdsaKey(SshKeyData publicKeyData)
{
if (publicKeyData is null)
{
throw new ArgumentNullException(nameof(publicKeyData));
}
if (!publicKeyData.Name.StartsWith("ecdsa-sha2-", StringComparison.Ordinal) || publicKeyData.Keys.Length != 2)
{
throw new ArgumentException($"Invalid ECDSA public key data. ({publicKeyData.Name}, {publicKeyData.Keys.Length}).", nameof(publicKeyData));
}
var curve_s = Encoding.ASCII.GetString(publicKeyData.Keys[0].ToByteArray().Reverse());
var curve_oid = GetCurveOid(curve_s);
var publickey = publicKeyData.Keys[1].ToByteArray().Reverse();
_impl = Import(curve_oid, publickey, privatekey: null);
}
///
/// Initializes a new instance of the class.
///
/// The curve name.
/// Value of publickey.
/// Value of privatekey.
public EcdsaKey(string curve, byte[] publickey, byte[] privatekey)
{
_impl = Import(GetCurveOid(curve), publickey, privatekey);
}
///
/// Initializes a new instance of the class.
///
/// DER encoded private key data.
public EcdsaKey(byte[] data)
{
var der = new DerData(data);
_ = der.ReadBigInteger(); // skip version
// PrivateKey
var privatekey = der.ReadOctetString().TrimLeadingZeros();
// Construct
var s0 = der.ReadByte();
if ((s0 & 0xe0) != 0xa0)
{
throw new SshException(string.Format("UnexpectedDER: wanted constructed tag (0xa0-0xbf), got: {0:X}", s0));
}
var tag = s0 & 0x1f;
if (tag != 0)
{
throw new SshException(string.Format("expected tag 0 in DER privkey, got: {0}", tag));
}
var construct = der.ReadBytes(der.ReadLength()); // object length
// curve OID
var curve_der = new DerData(construct, construct: true);
var curve = curve_der.ReadObject();
// Construct
s0 = der.ReadByte();
if ((s0 & 0xe0) != 0xa0)
{
throw new SshException(string.Format("UnexpectedDER: wanted constructed tag (0xa0-0xbf), got: {0:X}", s0));
}
tag = s0 & 0x1f;
if (tag != 1)
{
throw new SshException(string.Format("expected tag 1 in DER privkey, got: {0}", tag));
}
construct = der.ReadBytes(der.ReadLength()); // object length
// PublicKey
var pubkey_der = new DerData(construct, construct: true);
var pubkey = pubkey_der.ReadBitString().TrimLeadingZeros();
_impl = Import(OidByteArrayToString(curve), pubkey, privatekey);
}
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
private static Impl Import(string curve_oid, byte[] publickey, byte[]? privatekey)
#pragma warning restore CA1859 // Use concrete types when possible for improved performance
{
// 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 NET
return new BclImpl(curve_oid, cord_size, qx, qy, privatekey);
#else
try
{
#if NET462
return new CngImpl(curve_oid, cord_size, qx, qy, privatekey);
#else
return new BclImpl(curve_oid, cord_size, qx, qy, privatekey);
#endif
}
catch (NotImplementedException)
{
// Mono doesn't implement ECDsa.Create()
// See https://github.com/mono/mono/blob/main/mcs/class/referencesource/System.Core/System/Security/Cryptography/ECDsa.cs#L32
return new BouncyCastleImpl(curve_oid, qx, qy, privatekey);
}
#endif
}
private static string GetCurveOid(string curve_s)
{
if (string.Equals(curve_s, "nistp256", StringComparison.OrdinalIgnoreCase))
{
return ECDSA_P256_OID_VALUE;
}
if (string.Equals(curve_s, "nistp384", StringComparison.OrdinalIgnoreCase))
{
return ECDSA_P384_OID_VALUE;
}
if (string.Equals(curve_s, "nistp521", StringComparison.OrdinalIgnoreCase))
{
return ECDSA_P521_OID_VALUE;
}
throw new SshException("Unexpected Curve Name: " + curve_s);
}
private static string OidByteArrayToString(byte[] oid)
{
var retVal = new StringBuilder();
for (var i = 0; i < oid.Length; i++)
{
if (i == 0)
{
var b = oid[0] % 40;
var a = (oid[0] - b) / 40;
_ = retVal.AppendFormat("{0}.{1}", a, b);
}
else
{
if (oid[i] < 128)
{
_ = retVal.AppendFormat(".{0}", oid[i]);
}
else
{
_ = retVal.AppendFormat(".{0}", ((oid[i] - 128) * 128) + oid[i + 1]);
i++;
}
}
}
return retVal.ToString();
}
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
///
/// Releases unmanaged and - optionally - managed resources.
///
/// to release both managed and unmanaged resources; to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_digitalSignature?.Dispose();
_impl.Dispose();
}
}
}
}