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
|
||||
==========
|
||||
|
||||
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)
|
||||
* [Eckles.js](https://git.coolaj86.com/coolaj86/eckles.js)
|
||||
* Generate a Certificate Signing Requests (CSR), and sign it!
|
||||
|
||||
I'm mostly done with [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js)
|
||||
and I already have a working prototype to generate CSRs.
|
||||
Need JWK-to-PEM? Try [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js)
|
||||
|
||||
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)
|
||||
|
|
|
@ -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",
|
||||
"version": "0.0.3",
|
||||
"description": "",
|
||||
"version": "1.0.1",
|
||||
"description": "💯 A focused, zero-dependency library to generate a Certificate Signing Request (CSR) and sign it!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"bin": {
|
||||
"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/)",
|
||||
"license": "MPL-2.0"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue