295 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			295 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | // 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]); | ||
|  | }; |