Compare commits
	
		
			8 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8211d9b6d8 | |||
| 361b0bf994 | |||
| c392b72120 | |||
| f2a9fc083a | |||
| f68f5f8d0f | |||
| bd8056ff87 | |||
| e138491328 | |||
| 579f31ebbe | 
							
								
								
									
										217
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										217
									
								
								README.md
									
									
									
									
									
								
							| @ -1,16 +1,215 @@ | ||||
| RSA-CSR.js | ||||
| [RSA-CSR.js](https://git.coolaj86.com/coolaj86/rsa-csr.js) | ||||
| ========== | ||||
| 
 | ||||
| This is a work in progress. | ||||
| A [Root](https://therootcompany.com) Project. | ||||
| 
 | ||||
| I recently finished the EC variants: | ||||
| Built for [ACME.js](https://git.coolaj86.com/coolaj86/acme.js) | ||||
| and [Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock-express.js) | ||||
| 
 | ||||
| * [ECDSA-CSR.js](https://git.coolaj86.com/coolaj86/ecdsa-csr.js) | ||||
| * [Eckles.js](https://git.coolaj86.com/coolaj86/eckles.js) | ||||
| A focused, **zero-dependency** library that can do exactly one thing really, really well: | ||||
|   * 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. | ||||
| | < 300 lines of code | 1.7k gzipped | 4.7k minified | 8.5k with comments | | ||||
| 
 | ||||
| It'll all wrap up soon - expect it within a week. | ||||
| Features | ||||
| ======== | ||||
| 
 | ||||
| In the meantime, I'm squatting the module name. | ||||
| * [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 | ||||
| * [ ] 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 | ||||
| ----- | ||||
| 
 | ||||
| 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({ jwk: key, domains: domains }).then(function (csr) { | ||||
|   console.log('CSR PEM:'); | ||||
|   console.log(csr); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| The output will look something like this (but much longer): | ||||
| 
 | ||||
| ``` | ||||
| -----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/new) 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 | ||||
| ----- | ||||
| 
 | ||||
| [RSA-CSR.js](https://git.coolaj86.com/coolaj86/rsa-csr.js) | | ||||
| MPL-2.0 | | ||||
| [Terms of Use](https://therootcompany.com/legal/#terms) | | ||||
| [Privacy Policy](https://therootcompany.com/legal/#privacy) | ||||
|  | ||||
| @ -15,7 +15,7 @@ try { | ||||
|   // ignore
 | ||||
| } | ||||
| 
 | ||||
| rsacsr({ key: key, domains: domains }).then(function (csr) { | ||||
| rsacsr({ jwk: key, domains: domains }).then(function (csr) { | ||||
|   // Using error so that we can redirect stdout to file
 | ||||
|   //console.error("CN=" + domains[0]);
 | ||||
|   //console.error("subjectAltName=" + domains.join(','));
 | ||||
|  | ||||
| @ -110,13 +110,13 @@ RSA.sign = function signRsa(keypem, ab) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     // Signer is a stream
 | ||||
|     var sign = crypto.createSign('SHA256'); | ||||
|     sign.write(new Uint8Array(ab)); | ||||
|     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 new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength)); | ||||
|     return sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var Enc = require('./encoding.js') | ||||
| var Enc = require('./encoding.js'); | ||||
| var PEM = module.exports; | ||||
| 
 | ||||
| PEM.packBlock = function (opts) { | ||||
|  | ||||
							
								
								
									
										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' });
 | ||||
| } | ||||
							
								
								
									
										31
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								package.json
									
									
									
									
									
								
							| @ -1,11 +1,34 @@ | ||||
| { | ||||
|   "name": "rsa-csr", | ||||
|   "version": "0.0.3", | ||||
|   "description": "", | ||||
|   "version": "1.0.6", | ||||
|   "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", | ||||
|   "scripts": { | ||||
|     "test": "echo \"Error: no test specified\" && exit 1" | ||||
|   "bin": { | ||||
|     "rsa-csr": "bin/rsa-csr.js" | ||||
|   }, | ||||
|   "files": [ | ||||
|     "bin", | ||||
|     "fixtures", | ||||
|     "lib" | ||||
|   ], | ||||
|   "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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user