| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 | // 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]);};
 |