From ad312edfa68899b52d3e35d3953008ada6599954 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 5 Mar 2019 12:23:50 -0700 Subject: [PATCH] v1.2.5: fix multiple bugs with conversion --- bin/keypairs.js | 110 ++++++++++++++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/bin/keypairs.js b/bin/keypairs.js index ac93fe2..8116a3e 100755 --- a/bin/keypairs.js +++ b/bin/keypairs.js @@ -13,7 +13,7 @@ var Keypairs = require('../'); var pkg = require('../package.json'); var args = process.argv.slice(2); -var opts = { jwks: [], jwts: [], jwss: [], payloads: [], names: [], filenames: [], files: [], pems: [] }; +var opts = { keys: [], jwts: [], jwss: [], payloads: [], names: [], filenames: [], files: [] }; var conflicts = { 'namedCurve': 'modulusLength' , 'public': 'private' @@ -187,13 +187,7 @@ args.forEach(function (arg) { } // Possibly the output file - if (!opts.extra1) { - opts.extra1 = arg; - opts.names.push({ taken: false, name: arg }); - return; - } - if (!opts.extra2) { - opts.extra2 = arg; + if (opts.names.length < 3) { opts.names.push({ taken: false, name: arg }); return; } @@ -220,7 +214,7 @@ function guess(txt, filename) { try { var json = JSON.parse(txt); if (-1 !== [ 'RSA', 'EC' ].indexOf(json.kty)) { - opts.jwks.push({ jwk: json, filename: filename }); + opts.keys.push({ raw: txt, jwk: json, filename: filename }); return true; } else if (json.signature && json.payload && (json.header || json.protected)) { opts.jwss.push(json); @@ -231,15 +225,15 @@ function guess(txt, filename) { } } catch(e) { try { - var pem = Eckles.importSync({ pem: txt }); + var jwk = Eckles.importSync({ pem: txt }); // pem._string = txt; - opts.pems.push(pem); + opts.keys.push({ jwk: jwk, pem: true, raw: txt }); return true; } catch(e) { try { - var pem = Rasha.importSync({ pem: txt }); + var jwk = Rasha.importSync({ pem: txt }); // pem._string = txt; - opts.pems.push(pem); + opts.keys.push({ jwk: jwk, pem: true, raw: txt }); return true; } catch(e) { // ignore @@ -348,8 +342,8 @@ if ('sign' === opts.action) { function readKeypair() { // note that the jwk may be a string - var jwkopts = opts.jwks.shift(); - var jwk = jwkopts && jwkopts.jwk; + var keyopts = opts.keys.shift(); + var jwk = keyopts && keyopts.jwk; if (!jwk) { console.error("no keys could be parsed from the given arguments"); console.error(opts.names.map(function (t) { return t.name; })); @@ -358,13 +352,13 @@ function readKeypair() { } // omit the primary private key from the list of actual (or soon-to-be) files - if (jwkopts.filename) { + if (keyopts.filename) { opts.names = opts.names.filter(function (name) { - return name.name !== jwkopts.filename; + return name.name !== keyopts.filename; }); } - var pair = { private: null, public: null }; + var pair = { private: null, public: null, pem: keyopts.pem, raw: keyopts.raw }; if (jwk.d) { pair.private = jwk; } @@ -372,69 +366,91 @@ function readKeypair() { return pair; } +// Note: some of the conditions can be factored out +// this was all built in high-speed iterative during the 3ams+ function convertKeypair(pair) { //var pair = readKeypair(); var ps = []; - if (pair.private && !opts.public) { - if ((!opts.privEncoding || 'json' === opts.privEncoding) && (!opts.privFormat || 'jwk' === opts.privFormat)) { + // if it's private only, or if it's not public-only, produce the private key + if (pair.private || !opts.public) { + // if it came from pem (or is explicitly json), it should go to jwk + // otherwise, if it came from jwk, it should go to pem + if (((!opts.privEncoding && pair.pem) || 'json' === opts.privEncoding) + && ((!opts.privFormat && pair.pem) || 'jwk' === opts.privFormat)) { ps.push(Promise.resolve(pair.private)); } else { ps.push(Keypairs.export({ jwk: pair.private, format: opts.privFormat, encoding: opts.privEncoding })); } } + + // if it's not private key only, we want to produce the public key if (!opts.private) { if (opts.public) { + // if it's public-only the ambigious options will fall to the private key + // so we need to fix that if (!opts.pubFormat) { opts.pubFormat = opts.privFormat; } if (!opts.pubEncoding) { opts.pubEncoding = opts.privEncoding; } } - if ((!opts.pubEncoding || 'json' === opts.pubEncoding) && (!opts.pubFormat || 'jwk' === opts.pubFormat)) { + // same as above - swap formats by default + if (((!opts.pubEncoding && pair.pem) || 'json' === opts.pubEncoding) + && ((!opts.pubFormat && pair.pem) || 'jwk' === opts.pubFormat)) { ps.push(Promise.resolve(pair.public)); } else { ps.push(Keypairs.export({ jwk: pair.public, format: opts.pubFormat, encoding: opts.pubEncoding, public: true })); } } - return Promise.all(ps).then(function (arr) { - // only use the first key - var key = convert(0, opts.public); + + return Promise.all(ps).then(function (exported) { + // start with the first key, annotating if it should be public + var index = 0; + var key = stringifyIfJson(index, opts.public); + + // re: opts.names + // if we're only doing the public key we can end early + // (if the source key was from a file and was in opts.names, + // we're safe here because we already removed it earlier) + if (opts.public) { - // end early if (opts.names.length) { - writeFile(opts.names[0].name, key, !opts.public); // todo make pub/priv param consistent, not flip-flop + writeFile(opts.names[index].name, key, !opts.public); } else { - console.warn(key + "\n"); + // output public keys to stderr + printPublic(key); } + // end <-- we're not outputting other keys return; } // private key stuff - if (opts.names.length) { - writeFile(opts.names[0].name, key, true); + if (opts.names.length >= 1) { + writeFile(opts.names[index].name, key, true); } else { - console.info(key + "\n"); + printPrivate(key); } // pub key stuff - if (!opts.private) { - if (opts.names.length >= 2) { - writeFile(opts.names[1].name, key, false); - } else { - console.warn(key + "\n"); - } + // we have to output the private key, + // but the public key can be derived at any time + // so we don't need to put the same noise to the screen + if (!opts.private && opts.names.length >= 2) { + index = 1; + key = stringifyIfJson(index, false); + writeFile(opts.names[index].name, key, false); } return pair; - function convert(i, pub) { - if (arr[i].kty) { + function stringifyIfJson(i, pub) { + if (exported[i].kty) { if (pub) { - if (opts.expiresAt) { arr[i].exp = opts.expiresAt; } - arr[i].use = "sig"; + if (opts.expiresAt) { exported[i].exp = opts.expiresAt; } + exported[i].use = "sig"; } - arr[i] = JSON.stringify(arr[i]); + exported[i] = JSON.stringify(exported[i]); } - return arr[i]; + return exported[i]; } }); } @@ -445,6 +461,7 @@ function genKeypair() { , modulusLength: opts.modulusLength , namedCurve: opts.namedCurve }).then(function (pair) { + // always generate as jwk by default var ps = []; if ((!opts.privEncoding || 'json' === opts.privEncoding) && (!opts.privFormat || 'jwk' === opts.privFormat)) { ps.push(Promise.resolve(pair.private)); @@ -488,8 +505,10 @@ function writeFile(name, key, priv) { overwrite = opts.overwrite; if (!opts.overwrite) { if (priv) { + // output private keys to stdout console.info(key + "\n"); } else { + // output public keys to stderr console.warn(key + "\n"); } console.error("'" + name + "' exists! force overwrite with 'overwrite'"); @@ -583,3 +602,10 @@ function decodeJwt(jwt) { , signature: parts[2] //Buffer.from(parts[2], 'base64') }; } + +function printPrivate(key) { + console.info(key + "\n"); +} +function printPublic(key) { + console.warn(key + "\n"); +} diff --git a/package.json b/package.json index 0f6e4c4..0f82710 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "keypairs", - "version": "1.2.2", + "version": "1.2.5", "description": "Lightweight RSA/ECDSA keypair generation and JWK <-> PEM", "main": "keypairs.js", "files": [