From 2e24c43dee331a1dfe6b58e3cede04178c8113fc Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sun, 16 Dec 2018 00:27:00 -0700 Subject: [PATCH] sync version for backcompat with rsa-compat --- bin/rsa-csr.js | 6 +- lib/csr.js | 181 ++++++++++++++++++++++++++++--------------------- 2 files changed, 109 insertions(+), 78 deletions(-) diff --git a/bin/rsa-csr.js b/bin/rsa-csr.js index a923330..48be3ea 100755 --- a/bin/rsa-csr.js +++ b/bin/rsa-csr.js @@ -15,9 +15,13 @@ try { // ignore } -rsacsr({ key: key, domains: domains }).then(function (csr) { +var csr = rsacsr.sync({ key: key, domains: domains }); +console.log(csr); +/* +.then(function (csr) { // Using error so that we can redirect stdout to file //console.error("CN=" + domains[0]); //console.error("subjectAltName=" + domains.join(',')); console.log(csr); }); +*/ diff --git a/lib/csr.js b/lib/csr.js index 73654c6..dea9bf2 100644 --- a/lib/csr.js +++ b/lib/csr.js @@ -11,64 +11,81 @@ var RSA = {}; var CSR = module.exports = function rsacsr(opts) { // We're using a Promise here to be compatible with the browser version // which will probably use the webcrypto API for some of the conversions - return Promise.resolve().then(function () { - var Rasha; - opts = JSON.parse(JSON.stringify(opts)); - var pem, jwk; + opts = CSR._prepare(opts); - // We do a bit of extra error checking for user convenience - if (!opts) { throw new Error("You must pass options with key and domains to rsacsr"); } - if (!Array.isArray(opts.domains) || 0 === opts.domains.length) { - new Error("You must pass options.domains as a non-empty array"); - } - - // I need to check that 例.中国 is a valid domain name - if (!opts.domains.every(function (d) { - // allow punycode? xn-- - if ('string' === typeof d /*&& /\./.test(d) && !/--/.test(d)*/) { - return true; - } - })) { - throw new Error("You must pass options.domains as strings"); - } - - if (opts.pem) { - pem = opts.pem; - } else if (opts.jwk) { - jwk = opts.jwk; - } else { - if (!opts.key) { - throw new Error("You must pass options.key as a JSON web key"); - } else if (opts.key.kty) { - jwk = opts.key; - } else { - pem = opts.key; - } - } - - if (pem) { - try { - Rasha = require('rasha'); - } catch(e) { - throw new Error("Rasha.js is an optional dependency for PEM-to-JWK.\n" - + "Install it if you'd like to use it:\n" - + "\tnpm install --save rasha\n" - + "Otherwise supply a jwk as the private key." - ); - } - jwk = Rasha.importSync({ pem: pem }); - } - - opts.jwk = jwk; - return CSR.create(opts).then(function (bytes) { - return PEM.packBlock({ - type: "CERTIFICATE REQUEST" - , bytes: bytes /* { jwk: jwk, domains: opts.domains } */ - }); + return CSR.create(opts).then(function (bytes) { + return PEM.packBlock({ + type: "CERTIFICATE REQUEST" + , bytes: bytes /* { jwk: jwk, domains: opts.domains } */ }); }); }; +CSR._prepare = function (opts) { + var Rasha; + opts = JSON.parse(JSON.stringify(opts)); + var pem, jwk; + + // We do a bit of extra error checking for user convenience + if (!opts) { throw new Error("You must pass options with key and domains to rsacsr"); } + if (!Array.isArray(opts.domains) || 0 === opts.domains.length) { + new Error("You must pass options.domains as a non-empty array"); + } + + // I need to check that 例.中国 is a valid domain name + if (!opts.domains.every(function (d) { + // allow punycode? xn-- + if ('string' === typeof d /*&& /\./.test(d) && !/--/.test(d)*/) { + return true; + } + })) { + throw new Error("You must pass options.domains as strings"); + } + + if (opts.pem) { + pem = opts.pem; + } else if (opts.jwk) { + jwk = opts.jwk; + } else { + if (!opts.key) { + throw new Error("You must pass options.key as a JSON web key"); + } else if (opts.key.kty) { + jwk = opts.key; + } else { + pem = opts.key; + } + } + + if (pem) { + try { + Rasha = require('rasha'); + } catch(e) { + throw new Error("Rasha.js is an optional dependency for PEM-to-JWK.\n" + + "Install it if you'd like to use it:\n" + + "\tnpm install --save rasha\n" + + "Otherwise supply a jwk as the private key." + ); + } + jwk = Rasha.importSync({ pem: pem }); + } + + opts.jwk = jwk; + return opts; +}; +CSR.sync = function (opts) { + opts = CSR._prepare(opts); + var bytes = CSR.createSync(opts); + return PEM.packBlock({ + type: "CERTIFICATE REQUEST" + , bytes: bytes /* { jwk: jwk, domains: opts.domains } */ + }); +}; + +CSR.createSync = function createCsr(opts) { + var hex = CSR.request(opts.jwk, opts.domains); + var csr = CSR.signSync(opts.jwk, hex); + return Enc.hexToBuf(csr); +}; CSR.create = function createCsr(opts) { var hex = CSR.request(opts.jwk, opts.domains); return CSR.sign(opts.jwk, hex).then(function (csr) { @@ -81,42 +98,52 @@ CSR.request = function createCsrBodyEc(jwk, domains) { return X509.packCsr(asn1pub, domains); }; +CSR.signSync = function csrEcSig(jwk, request) { + var keypem = PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: X509.packPkcs1(jwk) }); + var sig = RSA.signSync(keypem, Enc.hexToBuf(request)); + return CSR.toDer({ request: request, signature: sig }); +}; CSR.sign = function csrEcSig(jwk, request) { var keypem = PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: X509.packPkcs1(jwk) }); - return RSA.sign(keypem, Enc.hexToBuf(request)).then(function (sig) { - var sty = ASN1('30' - // 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1) - , ASN1('06', '2a864886f70d01010b') - , ASN1('05') - ); - return ASN1('30' - // The Full CSR Request Body - , request - // The Signature Type - , sty - // The Signature - , ASN1.BitStr(Enc.bufToHex(sig)) - ); + return CSR.toDer({ request: request, signature: sig }); }); }; +CSR.toDer = function encode(opts) { + var sty = ASN1('30' + // 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1) + , ASN1('06', '2a864886f70d01010b') + , ASN1('05') + ); + return ASN1('30' + // The Full CSR Request Body + , opts.request + // The Signature Type + , sty + // The Signature + , ASN1.BitStr(Enc.bufToHex(opts.signature)) + ); +}; // // RSA // // Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a +RSA.signSync = function signRsaSync(keypem, ab) { + // Signer is a stream + var sign = crypto.createSign('SHA256'); + sign.write(new Uint8Array(ab)); + sign.end(); + + // The signature is ASN1 encoded, as it turns out + var sig = sign.sign(keypem); + + // Convert to a JavaScript ArrayBuffer just because + return new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength)); +}; RSA.sign = function signRsa(keypem, ab) { return Promise.resolve().then(function () { - // Signer is a stream - var sign = crypto.createSign('SHA256'); - sign.write(new Uint8Array(ab)); - sign.end(); - - // The signature is ASN1 encoded, as it turns out - var sig = sign.sign(keypem); - - // Convert to a JavaScript ArrayBuffer just because - return new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength)); + return RSA.signSync(keypem, ab); }); };