EcdsaKey.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. #nullable enable
  2. using System;
  3. using System.Diagnostics;
  4. using System.Security.Cryptography;
  5. using System.Text;
  6. using Renci.SshNet.Common;
  7. using Renci.SshNet.Security.Cryptography;
  8. namespace Renci.SshNet.Security
  9. {
  10. /// <summary>
  11. /// Contains ECDSA (ecdsa-sha2-nistp{256,384,521}) private and public key.
  12. /// </summary>
  13. public partial class EcdsaKey : Key, IDisposable
  14. {
  15. #pragma warning disable SA1310 // Field names should not contain underscore
  16. private const string ECDSA_P256_OID_VALUE = "1.2.840.10045.3.1.7"; // Also called nistP256 or secP256r1
  17. private const string ECDSA_P384_OID_VALUE = "1.3.132.0.34"; // Also called nistP384 or secP384r1
  18. private const string ECDSA_P521_OID_VALUE = "1.3.132.0.35"; // Also called nistP521or secP521r1
  19. #pragma warning restore SA1310 // Field names should not contain underscore
  20. private static readonly BigInteger Encoded256 = new BigInteger("nistp256"u8.ToArray().Reverse());
  21. private static readonly BigInteger Encoded384 = new BigInteger("nistp384"u8.ToArray().Reverse());
  22. private static readonly BigInteger Encoded521 = new BigInteger("nistp521"u8.ToArray().Reverse());
  23. private EcdsaDigitalSignature? _digitalSignature;
  24. #pragma warning disable SA1401 // Fields should be private; internal readonly
  25. internal readonly Impl _impl;
  26. #pragma warning restore SA1401 // Fields should be private
  27. /// <summary>
  28. /// Gets the SSH name of the ECDSA Key.
  29. /// </summary>
  30. /// <returns>
  31. /// The SSH name of the ECDSA Key.
  32. /// </returns>
  33. public override string ToString()
  34. {
  35. return string.Format("ecdsa-sha2-nistp{0}", KeyLength);
  36. }
  37. /// <summary>
  38. /// Gets the HashAlgorithm to use.
  39. /// </summary>
  40. public HashAlgorithmName HashAlgorithm
  41. {
  42. get
  43. {
  44. switch (KeyLength)
  45. {
  46. case 256:
  47. return HashAlgorithmName.SHA256;
  48. case 384:
  49. return HashAlgorithmName.SHA384;
  50. case 521:
  51. return HashAlgorithmName.SHA512;
  52. default:
  53. return HashAlgorithmName.SHA256;
  54. }
  55. }
  56. }
  57. internal abstract class Impl : IDisposable
  58. {
  59. public abstract int KeyLength { get; }
  60. public abstract byte[]? PrivateKey { get; }
  61. #if NET
  62. public abstract ECDsa Ecdsa { get; }
  63. #else
  64. public abstract ECDsa? Ecdsa { get; }
  65. #endif
  66. public abstract bool Verify(byte[] input, byte[] signature);
  67. public abstract byte[] Sign(byte[] input);
  68. public abstract void Export(out byte[] qx, out byte[] qy);
  69. protected abstract void Dispose(bool disposing);
  70. public void Dispose()
  71. {
  72. Dispose(disposing: true);
  73. GC.SuppressFinalize(this);
  74. }
  75. }
  76. /// <inheritdoc/>
  77. public override int KeyLength
  78. {
  79. get
  80. {
  81. return _impl.KeyLength;
  82. }
  83. }
  84. /// <inheritdoc/>
  85. protected internal override DigitalSignature DigitalSignature
  86. {
  87. get
  88. {
  89. _digitalSignature ??= new EcdsaDigitalSignature(this);
  90. return _digitalSignature;
  91. }
  92. }
  93. /// <summary>
  94. /// Gets the ECDSA public key.
  95. /// </summary>
  96. /// <value>
  97. /// An array with the ASCII-encoded curve identifier (e.g. "nistp256")
  98. /// at index 0, and the public curve point Q at index 1.
  99. /// </value>
  100. public override BigInteger[] Public
  101. {
  102. get
  103. {
  104. BigInteger curve;
  105. switch (KeyLength)
  106. {
  107. case 256:
  108. curve = Encoded256;
  109. break;
  110. case 384:
  111. curve = Encoded384;
  112. break;
  113. default:
  114. Debug.Assert(KeyLength == 521);
  115. curve = Encoded521;
  116. break;
  117. }
  118. _impl.Export(out var qx, out var qy);
  119. // Make ECPoint from x and y
  120. // Prepend 04 (uncompressed format) + qx-bytes + qy-bytes
  121. var q = new byte[1 + qx.Length + qy.Length];
  122. q[0] = 0x4;
  123. Buffer.BlockCopy(qx, 0, q, 1, qx.Length);
  124. Buffer.BlockCopy(qy, 0, q, qx.Length + 1, qy.Length);
  125. // returns Curve-Name and x/y as ECPoint
  126. return new[] { curve, new BigInteger(q.Reverse()) };
  127. }
  128. }
  129. /// <summary>
  130. /// Gets the PrivateKey Bytes.
  131. /// </summary>
  132. public byte[]? PrivateKey
  133. {
  134. get
  135. {
  136. return _impl.PrivateKey;
  137. }
  138. }
  139. /// <summary>
  140. /// Gets the <see cref="ECDsa"/> object.
  141. /// </summary>
  142. #if NET
  143. public ECDsa Ecdsa
  144. #else
  145. public ECDsa? Ecdsa
  146. #endif
  147. {
  148. get
  149. {
  150. return _impl.Ecdsa;
  151. }
  152. }
  153. /// <summary>
  154. /// Initializes a new instance of the <see cref="EcdsaKey"/> class.
  155. /// </summary>
  156. /// <param name="publicKeyData">The encoded public key data.</param>
  157. public EcdsaKey(SshKeyData publicKeyData)
  158. {
  159. if (publicKeyData is null)
  160. {
  161. throw new ArgumentNullException(nameof(publicKeyData));
  162. }
  163. if (!publicKeyData.Name.StartsWith("ecdsa-sha2-", StringComparison.Ordinal) || publicKeyData.Keys.Length != 2)
  164. {
  165. throw new ArgumentException($"Invalid ECDSA public key data. ({publicKeyData.Name}, {publicKeyData.Keys.Length}).", nameof(publicKeyData));
  166. }
  167. var curve_s = Encoding.ASCII.GetString(publicKeyData.Keys[0].ToByteArray().Reverse());
  168. var curve_oid = GetCurveOid(curve_s);
  169. var publickey = publicKeyData.Keys[1].ToByteArray().Reverse();
  170. _impl = Import(curve_oid, publickey, privatekey: null);
  171. }
  172. /// <summary>
  173. /// Initializes a new instance of the <see cref="EcdsaKey"/> class.
  174. /// </summary>
  175. /// <param name="curve">The curve name.</param>
  176. /// <param name="publickey">Value of publickey.</param>
  177. /// <param name="privatekey">Value of privatekey.</param>
  178. public EcdsaKey(string curve, byte[] publickey, byte[] privatekey)
  179. {
  180. _impl = Import(GetCurveOid(curve), publickey, privatekey);
  181. }
  182. /// <summary>
  183. /// Initializes a new instance of the <see cref="EcdsaKey"/> class.
  184. /// </summary>
  185. /// <param name="data">DER encoded private key data.</param>
  186. public EcdsaKey(byte[] data)
  187. {
  188. var der = new DerData(data);
  189. _ = der.ReadBigInteger(); // skip version
  190. // PrivateKey
  191. var privatekey = der.ReadOctetString().TrimLeadingZeros();
  192. // Construct
  193. var s0 = der.ReadByte();
  194. if ((s0 & 0xe0) != 0xa0)
  195. {
  196. throw new SshException(string.Format("UnexpectedDER: wanted constructed tag (0xa0-0xbf), got: {0:X}", s0));
  197. }
  198. var tag = s0 & 0x1f;
  199. if (tag != 0)
  200. {
  201. throw new SshException(string.Format("expected tag 0 in DER privkey, got: {0}", tag));
  202. }
  203. var construct = der.ReadBytes(der.ReadLength()); // object length
  204. // curve OID
  205. var curve_der = new DerData(construct, construct: true);
  206. var curve = curve_der.ReadObject();
  207. // Construct
  208. s0 = der.ReadByte();
  209. if ((s0 & 0xe0) != 0xa0)
  210. {
  211. throw new SshException(string.Format("UnexpectedDER: wanted constructed tag (0xa0-0xbf), got: {0:X}", s0));
  212. }
  213. tag = s0 & 0x1f;
  214. if (tag != 1)
  215. {
  216. throw new SshException(string.Format("expected tag 1 in DER privkey, got: {0}", tag));
  217. }
  218. construct = der.ReadBytes(der.ReadLength()); // object length
  219. // PublicKey
  220. var pubkey_der = new DerData(construct, construct: true);
  221. var pubkey = pubkey_der.ReadBitString().TrimLeadingZeros();
  222. _impl = Import(OidByteArrayToString(curve), pubkey, privatekey);
  223. }
  224. #pragma warning disable CA1859 // Use concrete types when possible for improved performance
  225. private static Impl Import(string curve_oid, byte[] publickey, byte[]? privatekey)
  226. #pragma warning restore CA1859 // Use concrete types when possible for improved performance
  227. {
  228. // ECPoint as BigInteger(2)
  229. var cord_size = (publickey.Length - 1) / 2;
  230. var qx = new byte[cord_size];
  231. Buffer.BlockCopy(publickey, 1, qx, 0, qx.Length);
  232. var qy = new byte[cord_size];
  233. Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length);
  234. #if NET
  235. return new BclImpl(curve_oid, cord_size, qx, qy, privatekey);
  236. #else
  237. try
  238. {
  239. #if NET462
  240. return new CngImpl(curve_oid, cord_size, qx, qy, privatekey);
  241. #else
  242. return new BclImpl(curve_oid, cord_size, qx, qy, privatekey);
  243. #endif
  244. }
  245. catch (NotImplementedException)
  246. {
  247. // Mono doesn't implement ECDsa.Create()
  248. // See https://github.com/mono/mono/blob/main/mcs/class/referencesource/System.Core/System/Security/Cryptography/ECDsa.cs#L32
  249. return new BouncyCastleImpl(curve_oid, qx, qy, privatekey);
  250. }
  251. #endif
  252. }
  253. private static string GetCurveOid(string curve_s)
  254. {
  255. if (string.Equals(curve_s, "nistp256", StringComparison.OrdinalIgnoreCase))
  256. {
  257. return ECDSA_P256_OID_VALUE;
  258. }
  259. if (string.Equals(curve_s, "nistp384", StringComparison.OrdinalIgnoreCase))
  260. {
  261. return ECDSA_P384_OID_VALUE;
  262. }
  263. if (string.Equals(curve_s, "nistp521", StringComparison.OrdinalIgnoreCase))
  264. {
  265. return ECDSA_P521_OID_VALUE;
  266. }
  267. throw new SshException("Unexpected Curve Name: " + curve_s);
  268. }
  269. private static string OidByteArrayToString(byte[] oid)
  270. {
  271. var retVal = new StringBuilder();
  272. for (var i = 0; i < oid.Length; i++)
  273. {
  274. if (i == 0)
  275. {
  276. var b = oid[0] % 40;
  277. var a = (oid[0] - b) / 40;
  278. _ = retVal.AppendFormat("{0}.{1}", a, b);
  279. }
  280. else
  281. {
  282. if (oid[i] < 128)
  283. {
  284. _ = retVal.AppendFormat(".{0}", oid[i]);
  285. }
  286. else
  287. {
  288. _ = retVal.AppendFormat(".{0}", ((oid[i] - 128) * 128) + oid[i + 1]);
  289. i++;
  290. }
  291. }
  292. }
  293. return retVal.ToString();
  294. }
  295. /// <summary>
  296. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  297. /// </summary>
  298. public void Dispose()
  299. {
  300. Dispose(disposing: true);
  301. GC.SuppressFinalize(this);
  302. }
  303. /// <summary>
  304. /// Releases unmanaged and - optionally - managed resources.
  305. /// </summary>
  306. /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
  307. protected virtual void Dispose(bool disposing)
  308. {
  309. if (disposing)
  310. {
  311. _digitalSignature?.Dispose();
  312. _impl.Dispose();
  313. }
  314. }
  315. }
  316. }