Compare commits

..

4 Commits
master ... sync

4 changed files with 121 additions and 88 deletions

View File

@ -1,9 +1,8 @@
[RSA-CSR.js](https://git.coolaj86.com/coolaj86/rsa-csr.js)
==========
A [Root](https://therootcompany.com) Project.
Built for [ACME.js](https://git.coolaj86.com/coolaj86/acme.js)
Sponsored by [Root](https://therootcompany.com),
built for [ACME.js](https://git.coolaj86.com/coolaj86/acme.js)
and [Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock-express.js)
A focused, **zero-dependency** library that can do exactly one thing really, really well:
@ -11,6 +10,10 @@ A focused, **zero-dependency** library that can do exactly one thing really, rea
| < 300 lines of code | 1.7k gzipped | 4.7k minified | 8.5k with comments |
Need JWK-to-PEM? Try [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js)
Need to generate an EC CSR? Try [ECSDA-CSR.js](https://git.coolaj86.com/coolaj86/ecdsa-csr.js)
Features
========
@ -29,10 +32,6 @@ Features
* [x] Vanilla Node.js
* no school like the old school
* easy to read and understand
* [ ] JWK-to-PEM
* See [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js)
* [ ] EC CSR
* See [ECSDA-CSR.js](https://git.coolaj86.com/coolaj86/ecdsa-csr.js)
Usage
-----
@ -57,7 +56,7 @@ var key = {
};
var domains = [ 'example.com', 'www.example.com' ];
return rsacsr({ jwk: key, domains: domains }).then(function (csr) {
return rsacsr({ key: key, domains: domains }).then(function (csr) {
console.log('CSR PEM:');
console.log(csr);
});

View File

@ -15,9 +15,13 @@ try {
// ignore
}
rsacsr({ jwk: 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);
});
*/

View File

@ -11,64 +11,84 @@ 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 CSR._encode(opts, bytes);
});
};
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 CSR._encode(opts, bytes);
};
CSR._encode = function (opts, bytes) {
if ('der' === (opts.encoding||'').toLowerCase()) {
return bytes;
}
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 +101,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(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 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(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 sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength);
return RSA.signSync(keypem, ab);
});
};

View File

@ -1,6 +1,6 @@
{
"name": "rsa-csr",
"version": "1.0.6",
"version": "1.0.7",
"description": "💯 A focused, zero-dependency library to generate a Certificate Signing Request (CSR) and sign it!",
"homepage": "https://git.coolaj86.com/coolaj86/rsa-csr.js",
"main": "index.js",