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.
		
		
		
		
		
			
		
			
				
					295 lines
				
				7.9 KiB
			
		
		
			
		
	
	
					295 lines
				
				7.9 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								// 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]);
							 | 
						||
| 
								 | 
							
								};
							 |