diff --git a/README.md b/README.md index 8f17095..87734e7 100644 --- a/README.md +++ b/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) diff --git a/lib/telemetry.js b/lib/telemetry.js new file mode 100644 index 0000000..9623b77 --- /dev/null +++ b/lib/telemetry.js @@ -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' }); +} diff --git a/package.json b/package.json index 3a41bb8..c4a49b4 100644 --- a/package.json +++ b/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 (https://coolaj86.com/)", "license": "MPL-2.0" }