v1.0.1: 💯 working, tested, documented. W00T!
This commit is contained in:
parent
6b1860dfb7
commit
579f31ebbe
212
README.md
212
README.md
@ -1,16 +1,212 @@
|
|||||||
RSA-CSR.js
|
RSA-CSR.js
|
||||||
==========
|
==========
|
||||||
|
|
||||||
This is a work in progress.
|
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)
|
||||||
|
|
||||||
I recently finished the EC variants:
|
A focused, **zero-dependency** library that can do exactly one thing really, really well:
|
||||||
|
|
||||||
* [ECDSA-CSR.js](https://git.coolaj86.com/coolaj86/ecdsa-csr.js)
|
* Generate a Certificate Signing Requests (CSR), and sign it!
|
||||||
* [Eckles.js](https://git.coolaj86.com/coolaj86/eckles.js)
|
|
||||||
|
|
||||||
I'm mostly done with [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js)
|
Need JWK-to-PEM? Try [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js)
|
||||||
and I already have a working prototype to generate CSRs.
|
|
||||||
|
|
||||||
It'll all wrap up soon - expect it within a week.
|
Need to generate an EC CSR? Try [ECSDA-CSR.js](https://git.coolaj86.com/coolaj86/ecdsa-csr.js)
|
||||||
|
|
||||||
In the meantime, I'm squatting the module name.
|
Features
|
||||||
|
========
|
||||||
|
|
||||||
|
* [x] Universal CSR support (RSA signing) that Just Works™
|
||||||
|
* Common Name (CN) Subject
|
||||||
|
* Subject Alternative Names (SANs / altnames)
|
||||||
|
* 2048, 3072, and 4096 bit JWK RSA
|
||||||
|
* RSASSA PKCS1 v1.5
|
||||||
|
* [x] Zero Dependencies
|
||||||
|
* (no ASN1.js, PKI.js, forge, jrsasign - not even elliptic.js!)
|
||||||
|
* [x] Quality
|
||||||
|
* Focused
|
||||||
|
* Lightweight
|
||||||
|
* Well-Commented, Well-Documented
|
||||||
|
* Secure
|
||||||
|
* [x] Vanilla Node.js
|
||||||
|
* no school like the old school
|
||||||
|
* easy to read and understand
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
Given an array of domains it uses the first for the Common Name (CN),
|
||||||
|
also known as Subject, and all of them as the Subject Alternative Names (SANs or altnames).
|
||||||
|
|
||||||
|
```js
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var rsacsr = require('rsa-csr');
|
||||||
|
var key = {
|
||||||
|
"kty": "RSA",
|
||||||
|
"n": "m2tt...-CNw",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "Cpfo...HMQQ",
|
||||||
|
"p": "ynG-...sTCE",
|
||||||
|
"q": "xIkA...1Q1c",
|
||||||
|
"dp": "tzDG...B1QE",
|
||||||
|
"dq": "kh5d...aL48",
|
||||||
|
"qi": "AlHW...HhFU"
|
||||||
|
};
|
||||||
|
var domains = [ 'example.com', 'www.example.com' ];
|
||||||
|
|
||||||
|
return rsacsr({ key: key, domains: domains }).then(function (csr) {
|
||||||
|
console.log('CSR PEM:');
|
||||||
|
console.log(csr);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The output will look something like this (but much longer):
|
||||||
|
|
||||||
|
```js
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIClTCCAX0CAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3
|
||||||
|
DQEBAQUAA4IBDwAwggEKAoIBAQCba21UHE+VbDTpmYYFZUOV+OQ8AngOCdjROsPC
|
||||||
|
0KiEfMvEaEM3NQl58u6QL7G7QsEr.....3pIpUUkx5WbwJY6xDrCyFKG8ktpnee6
|
||||||
|
WjpTOBnpgHUI1/5ydnf0v29L9N+ALIJGKQxhub3iqB6EhCl93iiQtf4e7M/lzX7l
|
||||||
|
c1xqsSwVZ3RQVY9bRP9NdGuW4hVvscy5ypqRtXPXQpxMnYwfi9qW5Uo=
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PEM-to-JWK
|
||||||
|
|
||||||
|
If you need to convert a PEM to JWK first, do so:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var Rasha = require('rasha');
|
||||||
|
|
||||||
|
Rasha.import({ pem: '-----BEGIN RSA PRIVATE KEY-----\nMIIEpAI..." }).then(function (jwk) {
|
||||||
|
console.log(jwk);
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CLI
|
||||||
|
|
||||||
|
You're probably better off using OpenSSL for most commandline tasks,
|
||||||
|
but the `rsa-csr` and `rasha` CLIs are useful for testing and debugging.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g rsa-csr
|
||||||
|
npm install -g rasha
|
||||||
|
|
||||||
|
rasha ./privkey.pem > ./privkey.jwk.json
|
||||||
|
rsa-csr ./privkey.jwk.json example.com,www.example.com > csr.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
* `key` should be a JWK
|
||||||
|
* Need PEM support? Use [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js).
|
||||||
|
* (supports PEM, DER, PKCS#1 and PKCS#8)
|
||||||
|
* `domains` must be a list of strings representing domain names
|
||||||
|
* correctly handles utf-8
|
||||||
|
* you may also use punycoded, if needed
|
||||||
|
* `subject` will be `domains[0]` by default
|
||||||
|
* you shouldn't use this unless you need to
|
||||||
|
* you may need to if you need utf-8 for domains, but punycode for the subject
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
You can double check that the CSR you get out is actually valid:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate a key, if needed
|
||||||
|
openssl genrsa -out ./privkey-rsa.pkcs1.pem $keysize
|
||||||
|
|
||||||
|
# Convert to JWK
|
||||||
|
rasha ./privkey-rsa.pkcs1.pem > ./privkey-rsa.jwk.json
|
||||||
|
|
||||||
|
# Create a CSR with your domains
|
||||||
|
npx rsa-csr ./privkey-rsa.jwk.json example.com,www.example.com > csr.pem
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
openssl req -text -noout -verify -in csr.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
New to Crypto?
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Just a heads up in case you have no idea what you're doing:
|
||||||
|
|
||||||
|
First of all, [don't panic](https://coolaj86.com/articles/dont-panic.html).
|
||||||
|
|
||||||
|
Next:
|
||||||
|
|
||||||
|
* RSA stands for... well, that doesn't matter, actually.
|
||||||
|
* DSA stands for _Digital Signing Algorithm_.
|
||||||
|
* RSA a separate standard from EC/ECDSA, but both are *asymmetric*
|
||||||
|
* Private keys are actually keypairs (they contain the public key)
|
||||||
|
|
||||||
|
In many cases the terms get used (and misused) interchangably,
|
||||||
|
which can be confusing. You'll survive, I promise.
|
||||||
|
|
||||||
|
* PEM is just a Base64-encoded DER (think JSON as hex or base64)
|
||||||
|
* DER is an binary _object notation_ for ASN.1 (think actual stringified JSON or XML)
|
||||||
|
* ASN.1 is _object notation_ standard (think JSON, the standard)
|
||||||
|
* X.509 is a suite of schemas (think XLST or json-schema.org)
|
||||||
|
* PKCS#8, PKIK, SPKI are all X.509 schemas (think defining `firstName` vs `first_name` vs `firstname`)
|
||||||
|
|
||||||
|
Now forget about all that and just know this:
|
||||||
|
|
||||||
|
**This library solves your problem if** you need RSA _something-or-other_ and CSR _something-or-other_
|
||||||
|
in order to deal with SSL certificates in an internal organization.
|
||||||
|
|
||||||
|
If that's not what you're doing, you may want HTTPS and SSL through
|
||||||
|
[Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock-express.js),
|
||||||
|
or you may be looking for something else entirely.
|
||||||
|
|
||||||
|
Goals vs Non-Goals
|
||||||
|
-----
|
||||||
|
|
||||||
|
This was built for use by [ACME.js](https://git.coolaj86.com/coolaj86/acme.js)
|
||||||
|
and [Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock-express.js).
|
||||||
|
|
||||||
|
Rather than trying to make a generic implementation that works with everything under the sun,
|
||||||
|
this library is intentionally focused on around the use case of generating certificates for
|
||||||
|
ACME services (such as Let's Encrypt).
|
||||||
|
|
||||||
|
That said, [please tell me](https://git.coolaj86.com/coolaj86/rsa-csr.js/issues) if it doesn't
|
||||||
|
do what you need, it may make sense to add it (or otherwise, perhaps to help you create a fork).
|
||||||
|
|
||||||
|
The primary goal of this project is for this code to do exactly (and all of)
|
||||||
|
what it needs to do - No more, no less.
|
||||||
|
|
||||||
|
* Support RSA JWKs
|
||||||
|
* 2048-bit
|
||||||
|
* 3072-bit
|
||||||
|
* 4096-bit
|
||||||
|
* Support PEM and DER via Rasha.js
|
||||||
|
* PKCS#1 (traditional)
|
||||||
|
* PKCS#8
|
||||||
|
* RSASSA-PKCS1-v1_5
|
||||||
|
* Vanilla node.js (ECMAScript 5.1)
|
||||||
|
* No babel
|
||||||
|
* No dependencies
|
||||||
|
|
||||||
|
However, there are a few areas where I'd be willing to stretch:
|
||||||
|
|
||||||
|
* Type definition files for altscript languages
|
||||||
|
|
||||||
|
It is not a goal of this project to support any RSA profiles
|
||||||
|
except those that are universally supported by browsers and
|
||||||
|
are sufficiently secure (overkill is overkill).
|
||||||
|
|
||||||
|
> A little copying is better than a little dependency. - [Go Proverbs](https://go-proverbs.github.io) by Rob Pike
|
||||||
|
|
||||||
|
This code is considered small and focused enough that,
|
||||||
|
rather than making it a dependency in other small projects,
|
||||||
|
I personally just copy over the code.
|
||||||
|
|
||||||
|
Hence, all of these projects are MPL-2.0 licensed.
|
||||||
|
|
||||||
|
Legal
|
||||||
|
-----
|
||||||
|
|
||||||
|
MPL-2.0 |
|
||||||
|
[Terms of Use](https://therootcompany.com/legal/#terms) |
|
||||||
|
[Privacy Policy](https://therootcompany.com/legal/#privacy)
|
||||||
|
111
lib/telemetry.js
Normal file
111
lib/telemetry.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// We believe in a proactive approach to sustainable open source.
|
||||||
|
// As part of that we make it easy for you to opt-in to following our progress
|
||||||
|
// and we also stay up-to-date on telemetry such as operating system and node
|
||||||
|
// version so that we can focus our efforts where they'll have the greatest impact.
|
||||||
|
//
|
||||||
|
// Want to learn more about our Terms, Privacy Policy, and Mission?
|
||||||
|
// Check out https://therootcompany.com/legal/
|
||||||
|
|
||||||
|
var os = require('os');
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var https = require('https');
|
||||||
|
var pkg = require('../package.json');
|
||||||
|
|
||||||
|
// to help focus our efforts in the right places
|
||||||
|
var data = {
|
||||||
|
package: pkg.name
|
||||||
|
, version: pkg.version
|
||||||
|
, node: process.version
|
||||||
|
, arch: process.arch || os.arch()
|
||||||
|
, platform: process.platform || os.platform()
|
||||||
|
, release: os.release()
|
||||||
|
};
|
||||||
|
|
||||||
|
function addCommunityMember(opts) {
|
||||||
|
setTimeout(function () {
|
||||||
|
var req = https.request({
|
||||||
|
hostname: 'api.therootcompany.com'
|
||||||
|
, port: 443
|
||||||
|
, path: '/api/therootcompany.com/public/community'
|
||||||
|
, method: 'POST'
|
||||||
|
, headers: { 'Content-Type': 'application/json' }
|
||||||
|
}, function (resp) {
|
||||||
|
// let the data flow, so we can ignore it
|
||||||
|
resp.on('data', function () {});
|
||||||
|
//resp.on('data', function (chunk) { console.log(chunk.toString()); });
|
||||||
|
resp.on('error', function () { /*ignore*/ });
|
||||||
|
//resp.on('error', function (err) { console.error(err); });
|
||||||
|
});
|
||||||
|
var obj = JSON.parse(JSON.stringify(data));
|
||||||
|
obj.action = 'updates';
|
||||||
|
try {
|
||||||
|
obj.ppid = ppid(obj.action);
|
||||||
|
} catch(e) {
|
||||||
|
// ignore
|
||||||
|
//console.error(e);
|
||||||
|
}
|
||||||
|
obj.name = opts.name || undefined;
|
||||||
|
obj.address = opts.email;
|
||||||
|
obj.community = 'node.js@therootcompany.com';
|
||||||
|
|
||||||
|
req.write(JSON.stringify(obj, 2, null));
|
||||||
|
req.end();
|
||||||
|
req.on('error', function () { /*ignore*/ });
|
||||||
|
//req.on('error', function (err) { console.error(err); });
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ping(action) {
|
||||||
|
setTimeout(function () {
|
||||||
|
var req = https.request({
|
||||||
|
hostname: 'api.therootcompany.com'
|
||||||
|
, port: 443
|
||||||
|
, path: '/api/therootcompany.com/public/ping'
|
||||||
|
, method: 'POST'
|
||||||
|
, headers: { 'Content-Type': 'application/json' }
|
||||||
|
}, function (resp) {
|
||||||
|
// let the data flow, so we can ignore it
|
||||||
|
resp.on('data', function () { });
|
||||||
|
//resp.on('data', function (chunk) { console.log(chunk.toString()); });
|
||||||
|
resp.on('error', function () { /*ignore*/ });
|
||||||
|
//resp.on('error', function (err) { console.error(err); });
|
||||||
|
});
|
||||||
|
var obj = JSON.parse(JSON.stringify(data));
|
||||||
|
obj.action = action;
|
||||||
|
try {
|
||||||
|
obj.ppid = ppid(obj.action);
|
||||||
|
} catch(e) {
|
||||||
|
// ignore
|
||||||
|
//console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.write(JSON.stringify(obj, 2, null));
|
||||||
|
req.end();
|
||||||
|
req.on('error', function (/*e*/) { /*console.error('req.error', e);*/ });
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// to help identify unique installs without getting
|
||||||
|
// the personally identifiable info that we don't want
|
||||||
|
function ppid(action) {
|
||||||
|
var parts = [ action, data.package, data.version, data.node, data.arch, data.platform, data.release ];
|
||||||
|
var ifaces = os.networkInterfaces();
|
||||||
|
Object.keys(ifaces).forEach(function (ifname) {
|
||||||
|
if (/^en/.test(ifname) || /^eth/.test(ifname) || /^wl/.test(ifname)) {
|
||||||
|
if (ifaces[ifname] && ifaces[ifname].length) {
|
||||||
|
parts.push(ifaces[ifname][0].mac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return crypto.createHash('sha1').update(parts.join(',')).digest('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.ping = ping;
|
||||||
|
module.exports.joinCommunity = addCommunityMember;
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
ping('install');
|
||||||
|
//addCommunityMember({ name: "AJ ONeal", email: 'coolaj86@gmail.com' });
|
||||||
|
}
|
25
package.json
25
package.json
@ -1,11 +1,28 @@
|
|||||||
{
|
{
|
||||||
"name": "rsa-csr",
|
"name": "rsa-csr",
|
||||||
"version": "0.0.3",
|
"version": "1.0.1",
|
||||||
"description": "",
|
"description": "💯 A focused, zero-dependency library to generate a Certificate Signing Request (CSR) and sign it!",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"bin": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"rsa-csr": "bin/rsa-csr.js"
|
||||||
},
|
},
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "node lib/telemetry.js event:install",
|
||||||
|
"test": "bash test.sh"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.coolaj86.com/coolaj86/rsa-csr.js"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"zero-dependency",
|
||||||
|
"CSR",
|
||||||
|
"RSA",
|
||||||
|
"x509"
|
||||||
|
],
|
||||||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
||||||
"license": "MPL-2.0"
|
"license": "MPL-2.0"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user