You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

246 lines
6.6 KiB

  1. // Copyright 2017 Joyent, Inc.
  2. module.exports = PrivateKey;
  3. var assert = require('assert-plus');
  4. var Buffer = require('safer-buffer').Buffer;
  5. var algs = require('./algs');
  6. var crypto = require('crypto');
  7. var Fingerprint = require('./fingerprint');
  8. var Signature = require('./signature');
  9. var errs = require('./errors');
  10. var util = require('util');
  11. var utils = require('./utils');
  12. var dhe = require('./dhe');
  13. var generateECDSA = dhe.generateECDSA;
  14. var generateED25519 = dhe.generateED25519;
  15. var edCompat = require('./ed-compat');
  16. var nacl = require('tweetnacl');
  17. var Key = require('./key');
  18. var InvalidAlgorithmError = errs.InvalidAlgorithmError;
  19. var KeyParseError = errs.KeyParseError;
  20. var KeyEncryptedError = errs.KeyEncryptedError;
  21. var formats = {};
  22. formats['auto'] = require('./formats/auto');
  23. formats['pem'] = require('./formats/pem');
  24. formats['pkcs1'] = require('./formats/pkcs1');
  25. formats['pkcs8'] = require('./formats/pkcs8');
  26. formats['rfc4253'] = require('./formats/rfc4253');
  27. formats['ssh-private'] = require('./formats/ssh-private');
  28. formats['openssh'] = formats['ssh-private'];
  29. formats['ssh'] = formats['ssh-private'];
  30. formats['dnssec'] = require('./formats/dnssec');
  31. function PrivateKey(opts) {
  32. assert.object(opts, 'options');
  33. Key.call(this, opts);
  34. this._pubCache = undefined;
  35. }
  36. util.inherits(PrivateKey, Key);
  37. PrivateKey.formats = formats;
  38. PrivateKey.prototype.toBuffer = function (format, options) {
  39. if (format === undefined)
  40. format = 'pkcs1';
  41. assert.string(format, 'format');
  42. assert.object(formats[format], 'formats[format]');
  43. assert.optionalObject(options, 'options');
  44. return (formats[format].write(this, options));
  45. };
  46. PrivateKey.prototype.hash = function (algo, type) {
  47. return (this.toPublic().hash(algo, type));
  48. };
  49. PrivateKey.prototype.fingerprint = function (algo, type) {
  50. return (this.toPublic().fingerprint(algo, type));
  51. };
  52. PrivateKey.prototype.toPublic = function () {
  53. if (this._pubCache)
  54. return (this._pubCache);
  55. var algInfo = algs.info[this.type];
  56. var pubParts = [];
  57. for (var i = 0; i < algInfo.parts.length; ++i) {
  58. var p = algInfo.parts[i];
  59. pubParts.push(this.part[p]);
  60. }
  61. this._pubCache = new Key({
  62. type: this.type,
  63. source: this,
  64. parts: pubParts
  65. });
  66. if (this.comment)
  67. this._pubCache.comment = this.comment;
  68. return (this._pubCache);
  69. };
  70. PrivateKey.prototype.derive = function (newType) {
  71. assert.string(newType, 'type');
  72. var priv, pub, pair;
  73. if (this.type === 'ed25519' && newType === 'curve25519') {
  74. priv = this.part.k.data;
  75. if (priv[0] === 0x00)
  76. priv = priv.slice(1);
  77. pair = nacl.box.keyPair.fromSecretKey(new Uint8Array(priv));
  78. pub = Buffer.from(pair.publicKey);
  79. return (new PrivateKey({
  80. type: 'curve25519',
  81. parts: [
  82. { name: 'A', data: utils.mpNormalize(pub) },
  83. { name: 'k', data: utils.mpNormalize(priv) }
  84. ]
  85. }));
  86. } else if (this.type === 'curve25519' && newType === 'ed25519') {
  87. priv = this.part.k.data;
  88. if (priv[0] === 0x00)
  89. priv = priv.slice(1);
  90. pair = nacl.sign.keyPair.fromSeed(new Uint8Array(priv));
  91. pub = Buffer.from(pair.publicKey);
  92. return (new PrivateKey({
  93. type: 'ed25519',
  94. parts: [
  95. { name: 'A', data: utils.mpNormalize(pub) },
  96. { name: 'k', data: utils.mpNormalize(priv) }
  97. ]
  98. }));
  99. }
  100. throw (new Error('Key derivation not supported from ' + this.type +
  101. ' to ' + newType));
  102. };
  103. PrivateKey.prototype.createVerify = function (hashAlgo) {
  104. return (this.toPublic().createVerify(hashAlgo));
  105. };
  106. PrivateKey.prototype.createSign = function (hashAlgo) {
  107. if (hashAlgo === undefined)
  108. hashAlgo = this.defaultHashAlgorithm();
  109. assert.string(hashAlgo, 'hash algorithm');
  110. /* ED25519 is not supported by OpenSSL, use a javascript impl. */
  111. if (this.type === 'ed25519' && edCompat !== undefined)
  112. return (new edCompat.Signer(this, hashAlgo));
  113. if (this.type === 'curve25519')
  114. throw (new Error('Curve25519 keys are not suitable for ' +
  115. 'signing or verification'));
  116. var v, nm, err;
  117. try {
  118. nm = hashAlgo.toUpperCase();
  119. v = crypto.createSign(nm);
  120. } catch (e) {
  121. err = e;
  122. }
  123. if (v === undefined || (err instanceof Error &&
  124. err.message.match(/Unknown message digest/))) {
  125. nm = 'RSA-';
  126. nm += hashAlgo.toUpperCase();
  127. v = crypto.createSign(nm);
  128. }
  129. assert.ok(v, 'failed to create verifier');
  130. var oldSign = v.sign.bind(v);
  131. var key = this.toBuffer('pkcs1');
  132. var type = this.type;
  133. var curve = this.curve;
  134. v.sign = function () {
  135. var sig = oldSign(key);
  136. if (typeof (sig) === 'string')
  137. sig = Buffer.from(sig, 'binary');
  138. sig = Signature.parse(sig, type, 'asn1');
  139. sig.hashAlgorithm = hashAlgo;
  140. sig.curve = curve;
  141. return (sig);
  142. };
  143. return (v);
  144. };
  145. PrivateKey.parse = function (data, format, options) {
  146. if (typeof (data) !== 'string')
  147. assert.buffer(data, 'data');
  148. if (format === undefined)
  149. format = 'auto';
  150. assert.string(format, 'format');
  151. if (typeof (options) === 'string')
  152. options = { filename: options };
  153. assert.optionalObject(options, 'options');
  154. if (options === undefined)
  155. options = {};
  156. assert.optionalString(options.filename, 'options.filename');
  157. if (options.filename === undefined)
  158. options.filename = '(unnamed)';
  159. assert.object(formats[format], 'formats[format]');
  160. try {
  161. var k = formats[format].read(data, options);
  162. assert.ok(k instanceof PrivateKey, 'key is not a private key');
  163. if (!k.comment)
  164. k.comment = options.filename;
  165. return (k);
  166. } catch (e) {
  167. if (e.name === 'KeyEncryptedError')
  168. throw (e);
  169. throw (new KeyParseError(options.filename, format, e));
  170. }
  171. };
  172. PrivateKey.isPrivateKey = function (obj, ver) {
  173. return (utils.isCompatible(obj, PrivateKey, ver));
  174. };
  175. PrivateKey.generate = function (type, options) {
  176. if (options === undefined)
  177. options = {};
  178. assert.object(options, 'options');
  179. switch (type) {
  180. case 'ecdsa':
  181. if (options.curve === undefined)
  182. options.curve = 'nistp256';
  183. assert.string(options.curve, 'options.curve');
  184. return (generateECDSA(options.curve));
  185. case 'ed25519':
  186. return (generateED25519());
  187. default:
  188. throw (new Error('Key generation not supported with key ' +
  189. 'type "' + type + '"'));
  190. }
  191. };
  192. /*
  193. * API versions for PrivateKey:
  194. * [1,0] -- initial ver
  195. * [1,1] -- added auto, pkcs[18], openssh/ssh-private formats
  196. * [1,2] -- added defaultHashAlgorithm
  197. * [1,3] -- added derive, ed, createDH
  198. * [1,4] -- first tagged version
  199. * [1,5] -- changed ed25519 part names and format
  200. * [1,6] -- type arguments for hash() and fingerprint()
  201. */
  202. PrivateKey.prototype._sshpkApiVersion = [1, 6];
  203. PrivateKey._oldVersionDetect = function (obj) {
  204. assert.func(obj.toPublic);
  205. assert.func(obj.createSign);
  206. if (obj.derive)
  207. return ([1, 3]);
  208. if (obj.defaultHashAlgorithm)
  209. return ([1, 2]);
  210. if (obj.formats['auto'])
  211. return ([1, 1]);
  212. return ([1, 0]);
  213. };