237 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			237 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # rsa-compat.js
 | ||
| 
 | ||
| | Sponsored by [ppl](https://ppl.family). Created at [Daplie](https://dapliefounder.com).
 | ||
| 
 | ||
| JavaScript RSA utils that work on Windows, Mac, and Linux with or without C compiler
 | ||
| 
 | ||
| In order to provide a module that "just works" everywhere, we mix and match methods
 | ||
| from `node.js` core, `ursa`, `forge`, and others.
 | ||
| 
 | ||
| This is useful for **certbot** and **letsencrypt**.
 | ||
| 
 | ||
| (in the future we'd like to provide the same API to the browser)
 | ||
| 
 | ||
| Install
 | ||
| =======
 | ||
| 
 | ||
| node.js
 | ||
| 
 | ||
| ```bash
 | ||
| npm install --save rsa-compat
 | ||
| ```
 | ||
| 
 | ||
| For **more efficient** RSA key generation:
 | ||
| <small>(I dropped `ursa` as an "optional dependency" because the non-fatal error messages on unsupported platforms and node versions were confusing people, but I still recommend installing it)</small>
 | ||
| 
 | ||
| ```bash
 | ||
| npm install --save ursa
 | ||
| ```
 | ||
| 
 | ||
| **Node < v6** support:
 | ||
| 
 | ||
| ```bash
 | ||
| npm install --save buffer-v6-polyfill
 | ||
| ```
 | ||
| 
 | ||
| ### CLI
 | ||
| 
 | ||
| ```bash
 | ||
| npm install --global rsa-compat
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| Usage
 | ||
| =====
 | ||
| 
 | ||
| CLI
 | ||
| ---
 | ||
| 
 | ||
| You can generate keypairs on Windows, Mac, and Linux using rsa-keygen-js:
 | ||
| 
 | ||
| ```bash
 | ||
| # generates a new keypair in the current directory
 | ||
| rsa-keypiar-js
 | ||
| ```
 | ||
| 
 | ||
| Examples
 | ||
| --------
 | ||
| 
 | ||
| Generate an RSA Keypair:
 | ||
| 
 | ||
| ```javascript
 | ||
| var RSA = require('rsa-compat').RSA;
 | ||
| 
 | ||
| var bitlen = 1024;
 | ||
| var exp = 65537;
 | ||
| var options = { public: true, pem: true, internal: true };
 | ||
| 
 | ||
| RSA.generateKeypair(bitlen, exp, options, function (err, keypair) {
 | ||
|   console.log(keypair);
 | ||
| });
 | ||
| ```
 | ||
| 
 | ||
| Here's what the object might look like:
 | ||
| 
 | ||
| `console.log(keypair)`:
 | ||
| ```javascript
 | ||
| 
 | ||
| { publicKeyPem: '-----BEGIN RSA PUBLIC KEY-----\n/*base64 pem-encoded string*/'
 | ||
| , privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\n/*base64 pem-encoded string*/'
 | ||
| , privateKeyJwk: {
 | ||
|     kty: "RSA"
 | ||
|   , n: '/*base64 modulus n = pq*/'
 | ||
|   , e: '/*base64 exponent (usually 65537)*/'
 | ||
|   , d: '/*base64 private exponent (d = e^−1 (mod ϕ(n))/'
 | ||
|   , p: '/*base64 first prime*/'
 | ||
|   , q: '/*base64 second prime*/'
 | ||
|   , dp: '/*base64 first exponent for Chinese remainder theorem (dP = d (mod p−1))*/'
 | ||
|   , dq: '/*base64 Second exponent, used for CRT (dQ = d (mod q−1))/'
 | ||
|   , qi: '/*base64 Coefficient, used for CRT (qinv = q^−1 (mod p))*/'
 | ||
|   }
 | ||
| , publicKeyJwk: {
 | ||
|     kty: "RSA"
 | ||
|   , n: '/*base64 modulus n = pq*/'
 | ||
|   , e: '/*base64 exponent (usually 65537)*/'
 | ||
|   }
 | ||
| 
 | ||
| , _ursa: '/*undefined or intermediate ursa object*/'
 | ||
| , _ursaPublic: '/*undefined or intermediate ursa object*/'
 | ||
| , _forge: '/*undefined or intermediate forge object*/'
 | ||
| , _forgePublic: '/*undefined or intermediate forge object*/'
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| NOTE: this object is JSON safe as _ursa and _forge will be ignored
 | ||
| 
 | ||
| See http://crypto.stackexchange.com/questions/6593/what-data-is-saved-in-rsa-private-key to learn a little more about the meaning of the specific fields in the JWK.
 | ||
| 
 | ||
| API
 | ||
| ---
 | ||
| 
 | ||
| * `RSA.generateKeypair(bitlen, exp, options, cb)`
 | ||
| * `RSA.import(keypair, options)`
 | ||
| * `RSA.exportPrivatePem(keypair)`
 | ||
| * `RSA.exportPublicPem(keypair)`
 | ||
| * `RSA.exportPrivateJwk(keypair)`
 | ||
| * `RSA.exportPublicJwk(keypair)`
 | ||
| * `RSA.signJws(keypair, header, protect, payload)`
 | ||
|   * (deprecated `RSA.signJws(keypair, payload, nonce)`)
 | ||
| * `RSA.generateCsrPem(keypair, names)`
 | ||
| * `RSA.generateCsrDerWeb64(keypair, names)`
 | ||
| 
 | ||
| `keypair` can be any object with any of these keys `publicKeyPem, privateKeyPem, publicKeyJwk, privateKeyJwk`
 | ||
| 
 | ||
| ### RSA.generateKeypair(bitlen, exp, options, cb)
 | ||
| 
 | ||
| Create a private keypair and export it as PEM, JWK, and/or internal formats
 | ||
| 
 | ||
| ```javascript
 | ||
| RSA.generateKeypair(null, null, null, function (keypair) { /*...*/ });
 | ||
| 
 | ||
| RSA.generateKeypair(1024, 65537, { pem: false, public: false, internal: false }, function (keypair) { /*...*/ });
 | ||
| ```
 | ||
| 
 | ||
| `bitlen`: *1024* (default), 2048, or 4096
 | ||
| 
 | ||
| `exp`: *65537* (default)
 | ||
| 
 | ||
| `options`:
 | ||
| ```javascript
 | ||
| { public: false       // export public keys
 | ||
| , pem: false          // export pems
 | ||
| , jwk: true           // export jwks
 | ||
| , internal: false     // preserve internal intermediate formats (_ursa, _forge)
 | ||
| , thumbprint: false   // JWK sha256 thumbprint
 | ||
| , fingerprint: false  // NOT IMPLEMENTED (RSA key fingerprint)
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ### RSA.import(keypair, options)
 | ||
| 
 | ||
| Imports keypair as JWKs and internal values `_ursa` and `_forge`.
 | ||
| 
 | ||
| ```javascript
 | ||
| var keypair = RSA.import({ privateKeyPem: '...'});
 | ||
| 
 | ||
| console.log(keypair);
 | ||
| ```
 | ||
| 
 | ||
| ```javascript
 | ||
| { privateKeyPem: ..., privateKeyJwk: ..., _ursa: ..., _forge: ... }
 | ||
| ```
 | ||
| 
 | ||
| ### RSA.export*(keypair)
 | ||
| 
 | ||
| You put in an object like `{ privateKeyPem: '...' }` or `{ publicKeyJwk: {} }`
 | ||
| and you get back the keys in the format you requested.
 | ||
| 
 | ||
| Note:
 | ||
| 
 | ||
| * Private keys **can** be used to export both private and public keys
 | ||
| * Public keys can **NOT** be used to generate private keys
 | ||
| 
 | ||
| Example:
 | ||
| 
 | ||
| ```javascript
 | ||
| var keypair = { privateKeyPem: '...' };
 | ||
| 
 | ||
| keypair.publicKeyJwk = RSA.exportPublicJwk(keypair);
 | ||
| 
 | ||
| console.log(keypair);
 | ||
| ```
 | ||
| 
 | ||
| ### RSA.signJws(keypair, payload, nonce)
 | ||
| 
 | ||
| Generates a signature in JWS format (necessary for **certbot**/**letsencrypt**).
 | ||
| 
 | ||
| ```javascript
 | ||
| var message = "Hello, World!"
 | ||
| var nonce = crypto.randomBytes(16).toString('hex');
 | ||
| var jws = RSA.signJws(keypair, message, nonce);
 | ||
| 
 | ||
| console.log(jws);
 | ||
| ```
 | ||
| 
 | ||
| The result looks like this:
 | ||
| 
 | ||
| ```javascript
 | ||
| { "header": {
 | ||
|     "alg": "RS256",
 | ||
|     "jwk": {
 | ||
|       "kty": "RSA",
 | ||
|       "n": "AMJubTfOtAarnJytLE8fhNsEI8wnpjRvBXGK/Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocDUgukpfkY+2UPUS/GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdkom2FMhGxkLaEUGDSfsNznTTZNBBg9",
 | ||
|       "e": "AQAB"
 | ||
|     }
 | ||
|   },
 | ||
|   "protected": "eyJub25jZSI6IjhlZjU2MjRmNWVjOWQzZWYifQ",
 | ||
|   "payload": "JLzF1NBNCV3kfbJ5sFaFyX94fJuL2H-IzaoBN-ciiHk",
 | ||
|   "signature": "Wb2al5SDyh5gjmkV79MK9m3sfNBBPjntSKor-34BBoGwr6n8qEnBmqB1Y4zbo-5rmvsoPmJsnRlP_hRiUY86zSAQyfbisTGrGBl0IQ7ditpkfYVm0rBWJ8WnYNqYNp8K3qcD7NW72tsy-XoWEjNlz4lWJeRdEG2Nt4CJgnREH4Y"
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ### RSA.generateCsr*(keypair, names)
 | ||
| 
 | ||
| You can generate the CSR in human-readable or binary / base64 formats:
 | ||
| 
 | ||
| `RSA.generateCsrPem(keypair, names)`:
 | ||
| ```javascript
 | ||
| var pem = RSA.generateCsrPem(keypair, [ 'example.com', 'www.example.com' ]);
 | ||
| 
 | ||
| console.log(pem);
 | ||
| ```
 | ||
| 
 | ||
| web-safe base64 for **certbot**/**letsencrypt**:
 | ||
| 
 | ||
| `RSA.generateCsrDerWeb64(keypair, names)`:
 | ||
| ```javascript
 | ||
| var web64 = RSA.generateCsrDerWeb64(keypair, [ 'example.com', 'www.example.com' ]);
 | ||
| 
 | ||
| console.log(web64);
 | ||
| ```
 | ||
| 
 | ||
| ChangeLog:
 | ||
| 
 | ||
| * v1.4.0
 | ||
|   * remove ursa as dependency (just causes confusion), but note in docs
 | ||
|   * drop node < v6 support
 |