twitst4tz

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

utils.js (9831B)


      1 // Copyright 2015 Joyent, Inc.
      2 
      3 module.exports = {
      4 	bufferSplit: bufferSplit,
      5 	addRSAMissing: addRSAMissing,
      6 	calculateDSAPublic: calculateDSAPublic,
      7 	calculateED25519Public: calculateED25519Public,
      8 	calculateX25519Public: calculateX25519Public,
      9 	mpNormalize: mpNormalize,
     10 	mpDenormalize: mpDenormalize,
     11 	ecNormalize: ecNormalize,
     12 	countZeros: countZeros,
     13 	assertCompatible: assertCompatible,
     14 	isCompatible: isCompatible,
     15 	opensslKeyDeriv: opensslKeyDeriv,
     16 	opensshCipherInfo: opensshCipherInfo,
     17 	publicFromPrivateECDSA: publicFromPrivateECDSA,
     18 	zeroPadToLength: zeroPadToLength,
     19 	writeBitString: writeBitString,
     20 	readBitString: readBitString,
     21 	pbkdf2: pbkdf2
     22 };
     23 
     24 var assert = require('assert-plus');
     25 var Buffer = require('safer-buffer').Buffer;
     26 var PrivateKey = require('./private-key');
     27 var Key = require('./key');
     28 var crypto = require('crypto');
     29 var algs = require('./algs');
     30 var asn1 = require('asn1');
     31 
     32 var ec = require('ecc-jsbn/lib/ec');
     33 var jsbn = require('jsbn').BigInteger;
     34 var nacl = require('tweetnacl');
     35 
     36 var MAX_CLASS_DEPTH = 3;
     37 
     38 function isCompatible(obj, klass, needVer) {
     39 	if (obj === null || typeof (obj) !== 'object')
     40 		return (false);
     41 	if (needVer === undefined)
     42 		needVer = klass.prototype._sshpkApiVersion;
     43 	if (obj instanceof klass &&
     44 	    klass.prototype._sshpkApiVersion[0] == needVer[0])
     45 		return (true);
     46 	var proto = Object.getPrototypeOf(obj);
     47 	var depth = 0;
     48 	while (proto.constructor.name !== klass.name) {
     49 		proto = Object.getPrototypeOf(proto);
     50 		if (!proto || ++depth > MAX_CLASS_DEPTH)
     51 			return (false);
     52 	}
     53 	if (proto.constructor.name !== klass.name)
     54 		return (false);
     55 	var ver = proto._sshpkApiVersion;
     56 	if (ver === undefined)
     57 		ver = klass._oldVersionDetect(obj);
     58 	if (ver[0] != needVer[0] || ver[1] < needVer[1])
     59 		return (false);
     60 	return (true);
     61 }
     62 
     63 function assertCompatible(obj, klass, needVer, name) {
     64 	if (name === undefined)
     65 		name = 'object';
     66 	assert.ok(obj, name + ' must not be null');
     67 	assert.object(obj, name + ' must be an object');
     68 	if (needVer === undefined)
     69 		needVer = klass.prototype._sshpkApiVersion;
     70 	if (obj instanceof klass &&
     71 	    klass.prototype._sshpkApiVersion[0] == needVer[0])
     72 		return;
     73 	var proto = Object.getPrototypeOf(obj);
     74 	var depth = 0;
     75 	while (proto.constructor.name !== klass.name) {
     76 		proto = Object.getPrototypeOf(proto);
     77 		assert.ok(proto && ++depth <= MAX_CLASS_DEPTH,
     78 		    name + ' must be a ' + klass.name + ' instance');
     79 	}
     80 	assert.strictEqual(proto.constructor.name, klass.name,
     81 	    name + ' must be a ' + klass.name + ' instance');
     82 	var ver = proto._sshpkApiVersion;
     83 	if (ver === undefined)
     84 		ver = klass._oldVersionDetect(obj);
     85 	assert.ok(ver[0] == needVer[0] && ver[1] >= needVer[1],
     86 	    name + ' must be compatible with ' + klass.name + ' klass ' +
     87 	    'version ' + needVer[0] + '.' + needVer[1]);
     88 }
     89 
     90 var CIPHER_LEN = {
     91 	'des-ede3-cbc': { key: 24, iv: 8 },
     92 	'aes-128-cbc': { key: 16, iv: 16 },
     93 	'aes-256-cbc': { key: 32, iv: 16 }
     94 };
     95 var PKCS5_SALT_LEN = 8;
     96 
     97 function opensslKeyDeriv(cipher, salt, passphrase, count) {
     98 	assert.buffer(salt, 'salt');
     99 	assert.buffer(passphrase, 'passphrase');
    100 	assert.number(count, 'iteration count');
    101 
    102 	var clen = CIPHER_LEN[cipher];
    103 	assert.object(clen, 'supported cipher');
    104 
    105 	salt = salt.slice(0, PKCS5_SALT_LEN);
    106 
    107 	var D, D_prev, bufs;
    108 	var material = Buffer.alloc(0);
    109 	while (material.length < clen.key + clen.iv) {
    110 		bufs = [];
    111 		if (D_prev)
    112 			bufs.push(D_prev);
    113 		bufs.push(passphrase);
    114 		bufs.push(salt);
    115 		D = Buffer.concat(bufs);
    116 		for (var j = 0; j < count; ++j)
    117 			D = crypto.createHash('md5').update(D).digest();
    118 		material = Buffer.concat([material, D]);
    119 		D_prev = D;
    120 	}
    121 
    122 	return ({
    123 	    key: material.slice(0, clen.key),
    124 	    iv: material.slice(clen.key, clen.key + clen.iv)
    125 	});
    126 }
    127 
    128 /* See: RFC2898 */
    129 function pbkdf2(hashAlg, salt, iterations, size, passphrase) {
    130 	var hkey = Buffer.alloc(salt.length + 4);
    131 	salt.copy(hkey);
    132 
    133 	var gen = 0, ts = [];
    134 	var i = 1;
    135 	while (gen < size) {
    136 		var t = T(i++);
    137 		gen += t.length;
    138 		ts.push(t);
    139 	}
    140 	return (Buffer.concat(ts).slice(0, size));
    141 
    142 	function T(I) {
    143 		hkey.writeUInt32BE(I, hkey.length - 4);
    144 
    145 		var hmac = crypto.createHmac(hashAlg, passphrase);
    146 		hmac.update(hkey);
    147 
    148 		var Ti = hmac.digest();
    149 		var Uc = Ti;
    150 		var c = 1;
    151 		while (c++ < iterations) {
    152 			hmac = crypto.createHmac(hashAlg, passphrase);
    153 			hmac.update(Uc);
    154 			Uc = hmac.digest();
    155 			for (var x = 0; x < Ti.length; ++x)
    156 				Ti[x] ^= Uc[x];
    157 		}
    158 		return (Ti);
    159 	}
    160 }
    161 
    162 /* Count leading zero bits on a buffer */
    163 function countZeros(buf) {
    164 	var o = 0, obit = 8;
    165 	while (o < buf.length) {
    166 		var mask = (1 << obit);
    167 		if ((buf[o] & mask) === mask)
    168 			break;
    169 		obit--;
    170 		if (obit < 0) {
    171 			o++;
    172 			obit = 8;
    173 		}
    174 	}
    175 	return (o*8 + (8 - obit) - 1);
    176 }
    177 
    178 function bufferSplit(buf, chr) {
    179 	assert.buffer(buf);
    180 	assert.string(chr);
    181 
    182 	var parts = [];
    183 	var lastPart = 0;
    184 	var matches = 0;
    185 	for (var i = 0; i < buf.length; ++i) {
    186 		if (buf[i] === chr.charCodeAt(matches))
    187 			++matches;
    188 		else if (buf[i] === chr.charCodeAt(0))
    189 			matches = 1;
    190 		else
    191 			matches = 0;
    192 
    193 		if (matches >= chr.length) {
    194 			var newPart = i + 1;
    195 			parts.push(buf.slice(lastPart, newPart - matches));
    196 			lastPart = newPart;
    197 			matches = 0;
    198 		}
    199 	}
    200 	if (lastPart <= buf.length)
    201 		parts.push(buf.slice(lastPart, buf.length));
    202 
    203 	return (parts);
    204 }
    205 
    206 function ecNormalize(buf, addZero) {
    207 	assert.buffer(buf);
    208 	if (buf[0] === 0x00 && buf[1] === 0x04) {
    209 		if (addZero)
    210 			return (buf);
    211 		return (buf.slice(1));
    212 	} else if (buf[0] === 0x04) {
    213 		if (!addZero)
    214 			return (buf);
    215 	} else {
    216 		while (buf[0] === 0x00)
    217 			buf = buf.slice(1);
    218 		if (buf[0] === 0x02 || buf[0] === 0x03)
    219 			throw (new Error('Compressed elliptic curve points ' +
    220 			    'are not supported'));
    221 		if (buf[0] !== 0x04)
    222 			throw (new Error('Not a valid elliptic curve point'));
    223 		if (!addZero)
    224 			return (buf);
    225 	}
    226 	var b = Buffer.alloc(buf.length + 1);
    227 	b[0] = 0x0;
    228 	buf.copy(b, 1);
    229 	return (b);
    230 }
    231 
    232 function readBitString(der, tag) {
    233 	if (tag === undefined)
    234 		tag = asn1.Ber.BitString;
    235 	var buf = der.readString(tag, true);
    236 	assert.strictEqual(buf[0], 0x00, 'bit strings with unused bits are ' +
    237 	    'not supported (0x' + buf[0].toString(16) + ')');
    238 	return (buf.slice(1));
    239 }
    240 
    241 function writeBitString(der, buf, tag) {
    242 	if (tag === undefined)
    243 		tag = asn1.Ber.BitString;
    244 	var b = Buffer.alloc(buf.length + 1);
    245 	b[0] = 0x00;
    246 	buf.copy(b, 1);
    247 	der.writeBuffer(b, tag);
    248 }
    249 
    250 function mpNormalize(buf) {
    251 	assert.buffer(buf);
    252 	while (buf.length > 1 && buf[0] === 0x00 && (buf[1] & 0x80) === 0x00)
    253 		buf = buf.slice(1);
    254 	if ((buf[0] & 0x80) === 0x80) {
    255 		var b = Buffer.alloc(buf.length + 1);
    256 		b[0] = 0x00;
    257 		buf.copy(b, 1);
    258 		buf = b;
    259 	}
    260 	return (buf);
    261 }
    262 
    263 function mpDenormalize(buf) {
    264 	assert.buffer(buf);
    265 	while (buf.length > 1 && buf[0] === 0x00)
    266 		buf = buf.slice(1);
    267 	return (buf);
    268 }
    269 
    270 function zeroPadToLength(buf, len) {
    271 	assert.buffer(buf);
    272 	assert.number(len);
    273 	while (buf.length > len) {
    274 		assert.equal(buf[0], 0x00);
    275 		buf = buf.slice(1);
    276 	}
    277 	while (buf.length < len) {
    278 		var b = Buffer.alloc(buf.length + 1);
    279 		b[0] = 0x00;
    280 		buf.copy(b, 1);
    281 		buf = b;
    282 	}
    283 	return (buf);
    284 }
    285 
    286 function bigintToMpBuf(bigint) {
    287 	var buf = Buffer.from(bigint.toByteArray());
    288 	buf = mpNormalize(buf);
    289 	return (buf);
    290 }
    291 
    292 function calculateDSAPublic(g, p, x) {
    293 	assert.buffer(g);
    294 	assert.buffer(p);
    295 	assert.buffer(x);
    296 	g = new jsbn(g);
    297 	p = new jsbn(p);
    298 	x = new jsbn(x);
    299 	var y = g.modPow(x, p);
    300 	var ybuf = bigintToMpBuf(y);
    301 	return (ybuf);
    302 }
    303 
    304 function calculateED25519Public(k) {
    305 	assert.buffer(k);
    306 
    307 	var kp = nacl.sign.keyPair.fromSeed(new Uint8Array(k));
    308 	return (Buffer.from(kp.publicKey));
    309 }
    310 
    311 function calculateX25519Public(k) {
    312 	assert.buffer(k);
    313 
    314 	var kp = nacl.box.keyPair.fromSeed(new Uint8Array(k));
    315 	return (Buffer.from(kp.publicKey));
    316 }
    317 
    318 function addRSAMissing(key) {
    319 	assert.object(key);
    320 	assertCompatible(key, PrivateKey, [1, 1]);
    321 
    322 	var d = new jsbn(key.part.d.data);
    323 	var buf;
    324 
    325 	if (!key.part.dmodp) {
    326 		var p = new jsbn(key.part.p.data);
    327 		var dmodp = d.mod(p.subtract(1));
    328 
    329 		buf = bigintToMpBuf(dmodp);
    330 		key.part.dmodp = {name: 'dmodp', data: buf};
    331 		key.parts.push(key.part.dmodp);
    332 	}
    333 	if (!key.part.dmodq) {
    334 		var q = new jsbn(key.part.q.data);
    335 		var dmodq = d.mod(q.subtract(1));
    336 
    337 		buf = bigintToMpBuf(dmodq);
    338 		key.part.dmodq = {name: 'dmodq', data: buf};
    339 		key.parts.push(key.part.dmodq);
    340 	}
    341 }
    342 
    343 function publicFromPrivateECDSA(curveName, priv) {
    344 	assert.string(curveName, 'curveName');
    345 	assert.buffer(priv);
    346 	var params = algs.curves[curveName];
    347 	var p = new jsbn(params.p);
    348 	var a = new jsbn(params.a);
    349 	var b = new jsbn(params.b);
    350 	var curve = new ec.ECCurveFp(p, a, b);
    351 	var G = curve.decodePointHex(params.G.toString('hex'));
    352 
    353 	var d = new jsbn(mpNormalize(priv));
    354 	var pub = G.multiply(d);
    355 	pub = Buffer.from(curve.encodePointHex(pub), 'hex');
    356 
    357 	var parts = [];
    358 	parts.push({name: 'curve', data: Buffer.from(curveName)});
    359 	parts.push({name: 'Q', data: pub});
    360 
    361 	var key = new Key({type: 'ecdsa', curve: curve, parts: parts});
    362 	return (key);
    363 }
    364 
    365 function opensshCipherInfo(cipher) {
    366 	var inf = {};
    367 	switch (cipher) {
    368 	case '3des-cbc':
    369 		inf.keySize = 24;
    370 		inf.blockSize = 8;
    371 		inf.opensslName = 'des-ede3-cbc';
    372 		break;
    373 	case 'blowfish-cbc':
    374 		inf.keySize = 16;
    375 		inf.blockSize = 8;
    376 		inf.opensslName = 'bf-cbc';
    377 		break;
    378 	case 'aes128-cbc':
    379 	case 'aes128-ctr':
    380 	case 'aes128-gcm@openssh.com':
    381 		inf.keySize = 16;
    382 		inf.blockSize = 16;
    383 		inf.opensslName = 'aes-128-' + cipher.slice(7, 10);
    384 		break;
    385 	case 'aes192-cbc':
    386 	case 'aes192-ctr':
    387 	case 'aes192-gcm@openssh.com':
    388 		inf.keySize = 24;
    389 		inf.blockSize = 16;
    390 		inf.opensslName = 'aes-192-' + cipher.slice(7, 10);
    391 		break;
    392 	case 'aes256-cbc':
    393 	case 'aes256-ctr':
    394 	case 'aes256-gcm@openssh.com':
    395 		inf.keySize = 32;
    396 		inf.blockSize = 16;
    397 		inf.opensslName = 'aes-256-' + cipher.slice(7, 10);
    398 		break;
    399 	default:
    400 		throw (new Error(
    401 		    'Unsupported openssl cipher "' + cipher + '"'));
    402 	}
    403 	return (inf);
    404 }