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.

294 lines
7.9 KiB

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