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