key.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // Copyright 2017 Joyent, Inc.
  2. module.exports = Key;
  3. var assert = require('assert-plus');
  4. var algs = require('./algs');
  5. var crypto = require('crypto');
  6. var Fingerprint = require('./fingerprint');
  7. var Signature = require('./signature');
  8. var DiffieHellman = require('./dhe').DiffieHellman;
  9. var errs = require('./errors');
  10. var utils = require('./utils');
  11. var PrivateKey = require('./private-key');
  12. var edCompat;
  13. try {
  14. edCompat = require('./ed-compat');
  15. } catch (e) {
  16. /* Just continue through, and bail out if we try to use it. */
  17. }
  18. var InvalidAlgorithmError = errs.InvalidAlgorithmError;
  19. var KeyParseError = errs.KeyParseError;
  20. var formats = {};
  21. formats['auto'] = require('./formats/auto');
  22. formats['pem'] = require('./formats/pem');
  23. formats['pkcs1'] = require('./formats/pkcs1');
  24. formats['pkcs8'] = require('./formats/pkcs8');
  25. formats['rfc4253'] = require('./formats/rfc4253');
  26. formats['ssh'] = require('./formats/ssh');
  27. formats['ssh-private'] = require('./formats/ssh-private');
  28. formats['openssh'] = formats['ssh-private'];
  29. formats['dnssec'] = require('./formats/dnssec');
  30. function Key(opts) {
  31. assert.object(opts, 'options');
  32. assert.arrayOfObject(opts.parts, 'options.parts');
  33. assert.string(opts.type, 'options.type');
  34. assert.optionalString(opts.comment, 'options.comment');
  35. var algInfo = algs.info[opts.type];
  36. if (typeof (algInfo) !== 'object')
  37. throw (new InvalidAlgorithmError(opts.type));
  38. var partLookup = {};
  39. for (var i = 0; i < opts.parts.length; ++i) {
  40. var part = opts.parts[i];
  41. partLookup[part.name] = part;
  42. }
  43. this.type = opts.type;
  44. this.parts = opts.parts;
  45. this.part = partLookup;
  46. this.comment = undefined;
  47. this.source = opts.source;
  48. /* for speeding up hashing/fingerprint operations */
  49. this._rfc4253Cache = opts._rfc4253Cache;
  50. this._hashCache = {};
  51. var sz;
  52. this.curve = undefined;
  53. if (this.type === 'ecdsa') {
  54. var curve = this.part.curve.data.toString();
  55. this.curve = curve;
  56. sz = algs.curves[curve].size;
  57. } else if (this.type === 'ed25519' || this.type === 'curve25519') {
  58. sz = 256;
  59. this.curve = 'curve25519';
  60. } else {
  61. var szPart = this.part[algInfo.sizePart];
  62. sz = szPart.data.length;
  63. sz = sz * 8 - utils.countZeros(szPart.data);
  64. }
  65. this.size = sz;
  66. }
  67. Key.formats = formats;
  68. Key.prototype.toBuffer = function (format, options) {
  69. if (format === undefined)
  70. format = 'ssh';
  71. assert.string(format, 'format');
  72. assert.object(formats[format], 'formats[format]');
  73. assert.optionalObject(options, 'options');
  74. if (format === 'rfc4253') {
  75. if (this._rfc4253Cache === undefined)
  76. this._rfc4253Cache = formats['rfc4253'].write(this);
  77. return (this._rfc4253Cache);
  78. }
  79. return (formats[format].write(this, options));
  80. };
  81. Key.prototype.toString = function (format, options) {
  82. return (this.toBuffer(format, options).toString());
  83. };
  84. Key.prototype.hash = function (algo) {
  85. assert.string(algo, 'algorithm');
  86. algo = algo.toLowerCase();
  87. if (algs.hashAlgs[algo] === undefined)
  88. throw (new InvalidAlgorithmError(algo));
  89. if (this._hashCache[algo])
  90. return (this._hashCache[algo]);
  91. var hash = crypto.createHash(algo).
  92. update(this.toBuffer('rfc4253')).digest();
  93. this._hashCache[algo] = hash;
  94. return (hash);
  95. };
  96. Key.prototype.fingerprint = function (algo) {
  97. if (algo === undefined)
  98. algo = 'sha256';
  99. assert.string(algo, 'algorithm');
  100. var opts = {
  101. type: 'key',
  102. hash: this.hash(algo),
  103. algorithm: algo
  104. };
  105. return (new Fingerprint(opts));
  106. };
  107. Key.prototype.defaultHashAlgorithm = function () {
  108. var hashAlgo = 'sha1';
  109. if (this.type === 'rsa')
  110. hashAlgo = 'sha256';
  111. if (this.type === 'dsa' && this.size > 1024)
  112. hashAlgo = 'sha256';
  113. if (this.type === 'ed25519')
  114. hashAlgo = 'sha512';
  115. if (this.type === 'ecdsa') {
  116. if (this.size <= 256)
  117. hashAlgo = 'sha256';
  118. else if (this.size <= 384)
  119. hashAlgo = 'sha384';
  120. else
  121. hashAlgo = 'sha512';
  122. }
  123. return (hashAlgo);
  124. };
  125. Key.prototype.createVerify = function (hashAlgo) {
  126. if (hashAlgo === undefined)
  127. hashAlgo = this.defaultHashAlgorithm();
  128. assert.string(hashAlgo, 'hash algorithm');
  129. /* ED25519 is not supported by OpenSSL, use a javascript impl. */
  130. if (this.type === 'ed25519' && edCompat !== undefined)
  131. return (new edCompat.Verifier(this, hashAlgo));
  132. if (this.type === 'curve25519')
  133. throw (new Error('Curve25519 keys are not suitable for ' +
  134. 'signing or verification'));
  135. var v, nm, err;
  136. try {
  137. nm = hashAlgo.toUpperCase();
  138. v = crypto.createVerify(nm);
  139. } catch (e) {
  140. err = e;
  141. }
  142. if (v === undefined || (err instanceof Error &&
  143. err.message.match(/Unknown message digest/))) {
  144. nm = 'RSA-';
  145. nm += hashAlgo.toUpperCase();
  146. v = crypto.createVerify(nm);
  147. }
  148. assert.ok(v, 'failed to create verifier');
  149. var oldVerify = v.verify.bind(v);
  150. var key = this.toBuffer('pkcs8');
  151. var curve = this.curve;
  152. var self = this;
  153. v.verify = function (signature, fmt) {
  154. if (Signature.isSignature(signature, [2, 0])) {
  155. if (signature.type !== self.type)
  156. return (false);
  157. if (signature.hashAlgorithm &&
  158. signature.hashAlgorithm !== hashAlgo)
  159. return (false);
  160. if (signature.curve && self.type === 'ecdsa' &&
  161. signature.curve !== curve)
  162. return (false);
  163. return (oldVerify(key, signature.toBuffer('asn1')));
  164. } else if (typeof (signature) === 'string' ||
  165. Buffer.isBuffer(signature)) {
  166. return (oldVerify(key, signature, fmt));
  167. /*
  168. * Avoid doing this on valid arguments, walking the prototype
  169. * chain can be quite slow.
  170. */
  171. } else if (Signature.isSignature(signature, [1, 0])) {
  172. throw (new Error('signature was created by too old ' +
  173. 'a version of sshpk and cannot be verified'));
  174. } else {
  175. throw (new TypeError('signature must be a string, ' +
  176. 'Buffer, or Signature object'));
  177. }
  178. };
  179. return (v);
  180. };
  181. Key.prototype.createDiffieHellman = function () {
  182. if (this.type === 'rsa')
  183. throw (new Error('RSA keys do not support Diffie-Hellman'));
  184. return (new DiffieHellman(this));
  185. };
  186. Key.prototype.createDH = Key.prototype.createDiffieHellman;
  187. Key.parse = function (data, format, options) {
  188. if (typeof (data) !== 'string')
  189. assert.buffer(data, 'data');
  190. if (format === undefined)
  191. format = 'auto';
  192. assert.string(format, 'format');
  193. if (typeof (options) === 'string')
  194. options = { filename: options };
  195. assert.optionalObject(options, 'options');
  196. if (options === undefined)
  197. options = {};
  198. assert.optionalString(options.filename, 'options.filename');
  199. if (options.filename === undefined)
  200. options.filename = '(unnamed)';
  201. assert.object(formats[format], 'formats[format]');
  202. try {
  203. var k = formats[format].read(data, options);
  204. if (k instanceof PrivateKey)
  205. k = k.toPublic();
  206. if (!k.comment)
  207. k.comment = options.filename;
  208. return (k);
  209. } catch (e) {
  210. if (e.name === 'KeyEncryptedError')
  211. throw (e);
  212. throw (new KeyParseError(options.filename, format, e));
  213. }
  214. };
  215. Key.isKey = function (obj, ver) {
  216. return (utils.isCompatible(obj, Key, ver));
  217. };
  218. /*
  219. * API versions for Key:
  220. * [1,0] -- initial ver, may take Signature for createVerify or may not
  221. * [1,1] -- added pkcs1, pkcs8 formats
  222. * [1,2] -- added auto, ssh-private, openssh formats
  223. * [1,3] -- added defaultHashAlgorithm
  224. * [1,4] -- added ed support, createDH
  225. * [1,5] -- first explicitly tagged version
  226. * [1,6] -- changed ed25519 part names
  227. */
  228. Key.prototype._sshpkApiVersion = [1, 6];
  229. Key._oldVersionDetect = function (obj) {
  230. assert.func(obj.toBuffer);
  231. assert.func(obj.fingerprint);
  232. if (obj.createDH)
  233. return ([1, 4]);
  234. if (obj.defaultHashAlgorithm)
  235. return ([1, 3]);
  236. if (obj.formats['auto'])
  237. return ([1, 2]);
  238. if (obj.formats['pkcs1'])
  239. return ([1, 1]);
  240. return ([1, 0]);
  241. };