#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(); } } } }