'use strict'; var Enc = require('./encoding.js'); var PEM = require('./pem.js'); var SSH = module.exports; SSH.pack = function (opts) { if (opts.jwk.d && !opts.public) { return SSH._packPrivate(opts); } else { delete opts.jwk.d; return SSH._packPublic(opts); } }; // https://tools.ietf.org/html/rfc4253#section-6.6 SSH._packPublic = function (opts) { var els = SSH._packKey(opts); var hex = SSH._packElements(els); var typ = Enc.hexToBin(els[0]); var parts = [ typ, Enc.hexToBase64(hex) ]; if (opts.comment) { parts.push(opts.comment); } return parts.join(' '); }; SSH._packPrivate = function (opts) { var pubjwk = JSON.parse(JSON.stringify(opts.jwk)); delete pubjwk.d; var pubels = SSH._packKey({ jwk: pubjwk }); var pubhex = SSH._packElements(pubels); var els = SSH._packKey(opts); var hex = SSH._packElements(els); var privlen = hex.length/2; var padlen = (privlen % 8) && (8 - (privlen % 8)) || 0; // blocksize is 8 (no cipher) var bin = "openssh-key-v1" + String.fromCharCode(0) + Enc.hexToBin( SSH._packElements([ Enc.binToHex("none") // ciphername , Enc.binToHex("none") // kdfname , "" // empty kdf ]) + SSH._numToUint32Hex(1) // number of keys (always 1) + SSH._numToUint32Hex(pubhex.length/2) // pubkey length + pubhex + SSH._numToUint32Hex(8 + privlen + padlen) // privkey length + '62636a7362636a73' // 64-bit dummy checksum ("bcjs", "bcjs") + hex // (only cihpered keys use real checksums) ); var pad = ''; var i; for (i = 1; i <= padlen; i += 1) { pad += '0' + i; } return PEM.packBlock({ type: "OPENSSH PRIVATE KEY" , bytes: Enc.binToBuf(bin + Enc.hexToBin(pad)) }); }; SSH._packKey = function (opts) { var jwk = opts.jwk; var els = []; var len; if ("RSA" === jwk.kty) { els.push(Enc.binToHex('ssh-rsa')); els.push(SSH._padRsa(Enc.base64ToHex(jwk.e))); els.push(SSH._padRsa(Enc.base64ToHex(jwk.n))); if (jwk.d) { els.push(SSH._padRsa(Enc.base64ToHex(jwk.d))); els.push(SSH._padRsa(Enc.base64ToHex(jwk.qi))); els.push(SSH._padRsa(Enc.base64ToHex(jwk.p))); els.push(SSH._padRsa(Enc.base64ToHex(jwk.q))); els.push(Enc.binToHex(opts.comment || '')); } return SSH._packElements(els); } if ("P-256" === jwk.crv) { els.push(Enc.binToHex('ecdsa-sha2-nistp256')); els.push(Enc.binToHex('nistp256')); len = 32; } else if ("P-384" === jwk.crv) { els.push(Enc.binToHex('ecdsa-sha2-nistp384')); els.push(Enc.binToHex('nistp384')); len = 48; } else { throw new Error("unknown key type " + (jwk.crv || jwk.kty)); } els.push('04' + SSH._padEc(Enc.base64ToHex(jwk.x), len) + SSH._padEc(Enc.base64ToHex(jwk.y), len) ); if (jwk.d) { els.push(SSH._padEc(Enc.base64ToHex(jwk.d), len)); els.push(Enc.binToHex(opts.comment || '')); } return els; }; SSH._packElements = function(els) { return els.map(function (hex) { return SSH._numToUint32Hex(hex.length/2) + hex; }).join(''); }; SSH._numToUint32Hex = function (num) { var hex = num.toString(16); while (hex.length < 8) { hex = '0' + hex; } return hex; }; SSH._padRsa = function (hex) { // BigInt is negative if the high order bit 0x80 is set, // so ASN1, SSH, and many other formats pad with '0x00' // to signifiy a positive number. var i = parseInt(hex.slice(0, 2), 16); if (0x80 & i) { return '00' + hex; } return hex; }; SSH._padEc = function (hex, len) { while (hex.length < len * 2) { hex = '00' + hex; } return hex; };