Compare commits

..

2 Commits
sync ... master

Author SHA1 Message Date
8211d9b6d8 node v6 bugfix: don't convert NodeBuffer to Uint8Array 2019-06-03 03:25:19 -06:00
361b0bf994 doc updates 2018-12-16 00:34:53 -07:00
4 changed files with 81 additions and 114 deletions

View File

@ -1,8 +1,9 @@
[RSA-CSR.js](https://git.coolaj86.com/coolaj86/rsa-csr.js) [RSA-CSR.js](https://git.coolaj86.com/coolaj86/rsa-csr.js)
========== ==========
Sponsored by [Root](https://therootcompany.com), A [Root](https://therootcompany.com) Project.
built for [ACME.js](https://git.coolaj86.com/coolaj86/acme.js)
Built for [ACME.js](https://git.coolaj86.com/coolaj86/acme.js)
and [Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock-express.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: A focused, **zero-dependency** library that can do exactly one thing really, really well:
@ -10,10 +11,6 @@ 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 | | < 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 Features
======== ========
@ -32,6 +29,10 @@ Features
* [x] Vanilla Node.js * [x] Vanilla Node.js
* no school like the old school * no school like the old school
* easy to read and understand * 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 Usage
----- -----
@ -56,7 +57,7 @@ var key = {
}; };
var domains = [ 'example.com', 'www.example.com' ]; var domains = [ 'example.com', 'www.example.com' ];
return rsacsr({ key: key, domains: domains }).then(function (csr) { return rsacsr({ jwk: key, domains: domains }).then(function (csr) {
console.log('CSR PEM:'); console.log('CSR PEM:');
console.log(csr); console.log(csr);
}); });

View File

@ -15,13 +15,9 @@ try {
// ignore // ignore
} }
var csr = rsacsr.sync({ key: key, domains: domains }); rsacsr({ jwk: key, domains: domains }).then(function (csr) {
console.log(csr);
/*
.then(function (csr) {
// Using error so that we can redirect stdout to file // Using error so that we can redirect stdout to file
//console.error("CN=" + domains[0]); //console.error("CN=" + domains[0]);
//console.error("subjectAltName=" + domains.join(',')); //console.error("subjectAltName=" + domains.join(','));
console.log(csr); console.log(csr);
}); });
*/

View File

@ -11,84 +11,64 @@ var RSA = {};
var CSR = module.exports = function rsacsr(opts) { var CSR = module.exports = function rsacsr(opts) {
// We're using a Promise here to be compatible with the browser version // 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 // which will probably use the webcrypto API for some of the conversions
opts = CSR._prepare(opts); return Promise.resolve().then(function () {
var Rasha;
opts = JSON.parse(JSON.stringify(opts));
var pem, jwk;
return CSR.create(opts).then(function (bytes) { // We do a bit of extra error checking for user convenience
return CSR._encode(opts, bytes); 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");
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) { // I need to check that 例.中国 is a valid domain name
pem = opts.pem; if (!opts.domains.every(function (d) {
} else if (opts.jwk) { // allow punycode? xn--
jwk = opts.jwk; if ('string' === typeof d /*&& /\./.test(d) && !/--/.test(d)*/) {
} else { return true;
if (!opts.key) { }
throw new Error("You must pass options.key as a JSON web key"); })) {
} else if (opts.key.kty) { throw new Error("You must pass options.domains as strings");
jwk = opts.key; }
if (opts.pem) {
pem = opts.pem;
} else if (opts.jwk) {
jwk = opts.jwk;
} else { } else {
pem = opts.key; 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) { if (pem) {
try { try {
Rasha = require('rasha'); Rasha = require('rasha');
} catch(e) { } catch(e) {
throw new Error("Rasha.js is an optional dependency for PEM-to-JWK.\n" throw new Error("Rasha.js is an optional dependency for PEM-to-JWK.\n"
+ "Install it if you'd like to use it:\n" + "Install it if you'd like to use it:\n"
+ "\tnpm install --save rasha\n" + "\tnpm install --save rasha\n"
+ "Otherwise supply a jwk as the private key." + "Otherwise supply a jwk as the private key."
); );
}
jwk = Rasha.importSync({ pem: pem });
} }
jwk = Rasha.importSync({ pem: pem });
}
opts.jwk = jwk; opts.jwk = jwk;
return opts; return CSR.create(opts).then(function (bytes) {
}; return PEM.packBlock({
CSR.sync = function (opts) { type: "CERTIFICATE REQUEST"
opts = CSR._prepare(opts); , bytes: bytes /* { jwk: jwk, domains: opts.domains } */
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) { CSR.create = function createCsr(opts) {
var hex = CSR.request(opts.jwk, opts.domains); var hex = CSR.request(opts.jwk, opts.domains);
return CSR.sign(opts.jwk, hex).then(function (csr) { return CSR.sign(opts.jwk, hex).then(function (csr) {
@ -101,52 +81,42 @@ CSR.request = function createCsrBodyEc(jwk, domains) {
return X509.packCsr(asn1pub, 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) { CSR.sign = function csrEcSig(jwk, request) {
var keypem = PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: X509.packPkcs1(jwk) }); var keypem = PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: X509.packPkcs1(jwk) });
return RSA.sign(keypem, Enc.hexToBuf(request)).then(function (sig) { return RSA.sign(keypem, Enc.hexToBuf(request)).then(function (sig) {
return CSR.toDer({ request: request, signature: 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))
);
}); });
}; };
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 // RSA
// //
// Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a // 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) { RSA.sign = function signRsa(keypem, ab) {
return Promise.resolve().then(function () { return Promise.resolve().then(function () {
return RSA.signSync(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);
}); });
}; };

View File

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