twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

key.js (8117B)


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