123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- // Copyright 2018 Joyent, Inc.
- module.exports = Key;
- var assert = require('assert-plus');
- var algs = require('./algs');
- var crypto = require('crypto');
- var Fingerprint = require('./fingerprint');
- var Signature = require('./signature');
- var DiffieHellman = require('./dhe').DiffieHellman;
- var errs = require('./errors');
- var utils = require('./utils');
- var PrivateKey = require('./private-key');
- var edCompat;
- try {
- edCompat = require('./ed-compat');
- } catch (e) {
- /* Just continue through, and bail out if we try to use it. */
- }
- var InvalidAlgorithmError = errs.InvalidAlgorithmError;
- var KeyParseError = errs.KeyParseError;
- var formats = {};
- formats['auto'] = require('./formats/auto');
- formats['pem'] = require('./formats/pem');
- formats['pkcs1'] = require('./formats/pkcs1');
- formats['pkcs8'] = require('./formats/pkcs8');
- formats['rfc4253'] = require('./formats/rfc4253');
- formats['ssh'] = require('./formats/ssh');
- formats['ssh-private'] = require('./formats/ssh-private');
- formats['openssh'] = formats['ssh-private'];
- formats['dnssec'] = require('./formats/dnssec');
- formats['putty'] = require('./formats/putty');
- formats['ppk'] = formats['putty'];
- function Key(opts) {
- assert.object(opts, 'options');
- assert.arrayOfObject(opts.parts, 'options.parts');
- assert.string(opts.type, 'options.type');
- assert.optionalString(opts.comment, 'options.comment');
- var algInfo = algs.info[opts.type];
- if (typeof (algInfo) !== 'object')
- throw (new InvalidAlgorithmError(opts.type));
- var partLookup = {};
- for (var i = 0; i < opts.parts.length; ++i) {
- var part = opts.parts[i];
- partLookup[part.name] = part;
- }
- this.type = opts.type;
- this.parts = opts.parts;
- this.part = partLookup;
- this.comment = undefined;
- this.source = opts.source;
- /* for speeding up hashing/fingerprint operations */
- this._rfc4253Cache = opts._rfc4253Cache;
- this._hashCache = {};
- var sz;
- this.curve = undefined;
- if (this.type === 'ecdsa') {
- var curve = this.part.curve.data.toString();
- this.curve = curve;
- sz = algs.curves[curve].size;
- } else if (this.type === 'ed25519' || this.type === 'curve25519') {
- sz = 256;
- this.curve = 'curve25519';
- } else {
- var szPart = this.part[algInfo.sizePart];
- sz = szPart.data.length;
- sz = sz * 8 - utils.countZeros(szPart.data);
- }
- this.size = sz;
- }
- Key.formats = formats;
- Key.prototype.toBuffer = function (format, options) {
- if (format === undefined)
- format = 'ssh';
- assert.string(format, 'format');
- assert.object(formats[format], 'formats[format]');
- assert.optionalObject(options, 'options');
- if (format === 'rfc4253') {
- if (this._rfc4253Cache === undefined)
- this._rfc4253Cache = formats['rfc4253'].write(this);
- return (this._rfc4253Cache);
- }
- return (formats[format].write(this, options));
- };
- Key.prototype.toString = function (format, options) {
- return (this.toBuffer(format, options).toString());
- };
- Key.prototype.hash = function (algo, type) {
- assert.string(algo, 'algorithm');
- assert.optionalString(type, 'type');
- if (type === undefined)
- type = 'ssh';
- algo = algo.toLowerCase();
- if (algs.hashAlgs[algo] === undefined)
- throw (new InvalidAlgorithmError(algo));
- var cacheKey = algo + '||' + type;
- if (this._hashCache[cacheKey])
- return (this._hashCache[cacheKey]);
- var buf;
- if (type === 'ssh') {
- buf = this.toBuffer('rfc4253');
- } else if (type === 'spki') {
- buf = formats.pkcs8.pkcs8ToBuffer(this);
- } else {
- throw (new Error('Hash type ' + type + ' not supported'));
- }
- var hash = crypto.createHash(algo).update(buf).digest();
- this._hashCache[cacheKey] = hash;
- return (hash);
- };
- Key.prototype.fingerprint = function (algo, type) {
- if (algo === undefined)
- algo = 'sha256';
- if (type === undefined)
- type = 'ssh';
- assert.string(algo, 'algorithm');
- assert.string(type, 'type');
- var opts = {
- type: 'key',
- hash: this.hash(algo, type),
- algorithm: algo,
- hashType: type
- };
- return (new Fingerprint(opts));
- };
- Key.prototype.defaultHashAlgorithm = function () {
- var hashAlgo = 'sha1';
- if (this.type === 'rsa')
- hashAlgo = 'sha256';
- if (this.type === 'dsa' && this.size > 1024)
- hashAlgo = 'sha256';
- if (this.type === 'ed25519')
- hashAlgo = 'sha512';
- if (this.type === 'ecdsa') {
- if (this.size <= 256)
- hashAlgo = 'sha256';
- else if (this.size <= 384)
- hashAlgo = 'sha384';
- else
- hashAlgo = 'sha512';
- }
- return (hashAlgo);
- };
- Key.prototype.createVerify = function (hashAlgo) {
- if (hashAlgo === undefined)
- hashAlgo = this.defaultHashAlgorithm();
- assert.string(hashAlgo, 'hash algorithm');
- /* ED25519 is not supported by OpenSSL, use a javascript impl. */
- if (this.type === 'ed25519' && edCompat !== undefined)
- return (new edCompat.Verifier(this, hashAlgo));
- if (this.type === 'curve25519')
- throw (new Error('Curve25519 keys are not suitable for ' +
- 'signing or verification'));
- var v, nm, err;
- try {
- nm = hashAlgo.toUpperCase();
- v = crypto.createVerify(nm);
- } catch (e) {
- err = e;
- }
- if (v === undefined || (err instanceof Error &&
- err.message.match(/Unknown message digest/))) {
- nm = 'RSA-';
- nm += hashAlgo.toUpperCase();
- v = crypto.createVerify(nm);
- }
- assert.ok(v, 'failed to create verifier');
- var oldVerify = v.verify.bind(v);
- var key = this.toBuffer('pkcs8');
- var curve = this.curve;
- var self = this;
- v.verify = function (signature, fmt) {
- if (Signature.isSignature(signature, [2, 0])) {
- if (signature.type !== self.type)
- return (false);
- if (signature.hashAlgorithm &&
- signature.hashAlgorithm !== hashAlgo)
- return (false);
- if (signature.curve && self.type === 'ecdsa' &&
- signature.curve !== curve)
- return (false);
- return (oldVerify(key, signature.toBuffer('asn1')));
- } else if (typeof (signature) === 'string' ||
- Buffer.isBuffer(signature)) {
- return (oldVerify(key, signature, fmt));
- /*
- * Avoid doing this on valid arguments, walking the prototype
- * chain can be quite slow.
- */
- } else if (Signature.isSignature(signature, [1, 0])) {
- throw (new Error('signature was created by too old ' +
- 'a version of sshpk and cannot be verified'));
- } else {
- throw (new TypeError('signature must be a string, ' +
- 'Buffer, or Signature object'));
- }
- };
- return (v);
- };
- Key.prototype.createDiffieHellman = function () {
- if (this.type === 'rsa')
- throw (new Error('RSA keys do not support Diffie-Hellman'));
- return (new DiffieHellman(this));
- };
- Key.prototype.createDH = Key.prototype.createDiffieHellman;
- Key.parse = function (data, format, options) {
- if (typeof (data) !== 'string')
- assert.buffer(data, 'data');
- if (format === undefined)
- format = 'auto';
- assert.string(format, 'format');
- if (typeof (options) === 'string')
- options = { filename: options };
- assert.optionalObject(options, 'options');
- if (options === undefined)
- options = {};
- assert.optionalString(options.filename, 'options.filename');
- if (options.filename === undefined)
- options.filename = '(unnamed)';
- assert.object(formats[format], 'formats[format]');
- try {
- var k = formats[format].read(data, options);
- if (k instanceof PrivateKey)
- k = k.toPublic();
- if (!k.comment)
- k.comment = options.filename;
- return (k);
- } catch (e) {
- if (e.name === 'KeyEncryptedError')
- throw (e);
- throw (new KeyParseError(options.filename, format, e));
- }
- };
- Key.isKey = function (obj, ver) {
- return (utils.isCompatible(obj, Key, ver));
- };
- /*
- * API versions for Key:
- * [1,0] -- initial ver, may take Signature for createVerify or may not
- * [1,1] -- added pkcs1, pkcs8 formats
- * [1,2] -- added auto, ssh-private, openssh formats
- * [1,3] -- added defaultHashAlgorithm
- * [1,4] -- added ed support, createDH
- * [1,5] -- first explicitly tagged version
- * [1,6] -- changed ed25519 part names
- * [1,7] -- spki hash types
- */
- Key.prototype._sshpkApiVersion = [1, 7];
- Key._oldVersionDetect = function (obj) {
- assert.func(obj.toBuffer);
- assert.func(obj.fingerprint);
- if (obj.createDH)
- return ([1, 4]);
- if (obj.defaultHashAlgorithm)
- return ([1, 3]);
- if (obj.formats['auto'])
- return ([1, 2]);
- if (obj.formats['pkcs1'])
- return ([1, 1]);
- return ([1, 0]);
- };
|