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 }