# [@root/keypairs](https://git.rootprojects.org/root/keypairs.js) Lightweight JavaScript RSA and ECDSA utils that work on Windows, Mac, and Linux using modern node.js APIs (no need for C compiler). A thin wrapper around [Eckles.js (ECDSA)](https://git.coolaj86.com/coolaj86/eckles.js/) and [Rasha.js (RSA)](https://git.coolaj86.com/coolaj86/rasha.js/). # Features - [x] Generate keypairs - [x] RSA - [x] ECDSA (P-256, P-384) - [x] PEM-to-JWK (and SSH-to-JWK) - [x] JWK-to-PEM (and JWK-to-SSH) - [x] Create JWTs (and sign JWS) - [x] SHA256 JWK Thumbprints - [ ] JWK fetching. See [Keyfetch.js](https://npmjs.com/packages/keyfetch/) - [ ] OIDC - [ ] Auth0 - [ ] CLI - See [keypairs-cli](https://npmjs.com/packages/keypairs-cli/) - [x] Node - [x] Browsers (Webpack >=5) # Progress This is fully functional, but the re-usable code from ACME.js hasn't been fully teased out for the v2.0 release. (SSH conversions have not yet made it to 2.0) # Usage A brief introduction to the APIs: ```js // generate a new keypair as jwk // (defaults to EC P-256 when no options are specified) Keypairs.generate().then(function (pair) { console.log(pair.private); console.log(pair.public); }); ``` ```js // JWK to PEM // (supports various 'format' and 'encoding' options) return Keypairs.export({ jwk: pair.private, format: 'pkcs8' }).then(function ( pem ) { console.log(pem); }); ``` ```js // PEM to JWK return Keypairs.import({ pem: pem }).then(function (jwk) { console.log(jwk); }); ``` ```js // Thumbprint a JWK (SHA256) return Keypairs.thumbprint({ jwk: jwk }).then(function (thumb) { console.log(thumb); }); ``` ```js // Sign a JWT (aka compact JWS) return Keypairs.signJwt({ jwk: pair.private , iss: 'https://example.com' , exp: '1h' // optional claims , claims: { , sub: 'jon.doe@gmail.com' } }); ``` By default ECDSA keys will be used since they've had native support in node _much_ longer than RSA has, and they're smaller, and faster to generate. ## Webpack 5+ (for Browsers) This package includes native browser versions of all special functions. Since Webpack 5 now fully supports vanilla JavaScript and exclusive browser builds out-of-the-box, it's pretty easy to create a minimal config to use Keypairs in your browser projects: `webpack.config.js`: ```js 'use strict'; var path = require('path'); module.exports = { entry: './main.js', mode: 'development', devServer: { contentBase: path.join(__dirname, 'dist'), port: 3001 }, output: { publicPath: 'http://localhost:3001/' }, module: { rules: [{}] }, plugins: [] }; ``` `main.js`: ```js 'use strict'; var Keypairs = require('./keypairs.js'); Keypairs.generate().then(function (pair) { console.log(pair.private); console.log(pair.public); }); ``` `index.html`: ```html
``` ```bash npm install --save-dev webpack@5 # Or, if webpack 5 is still in beta: npm install --save-dev webpack@next npm install --save-dev webpack-cli npx webpack --mode=production ls dist/main.js ``` ## API Overview - generate (JWK) - parse (PEM) - parseOrGenerate (PEM to JWK) - import (PEM-to-JWK) - export (JWK-to-PEM, private or public) - publish (Private JWK to Public JWK) - thumbprint (JWK SHA256) - signJwt - signJws #### Keypairs.generate(options) Generates a public/private pair of JWKs as `{ private, public }` Option examples: - RSA `{ kty: 'RSA', modulusLength: 2048 }` - ECDSA `{ kty: 'ECDSA', namedCurve: 'P-256' }` When no options are supplied EC P-256 (also known as `prime256v1` and `secp256r1`) is used by default. #### Keypairs.parse(options) Parses either a JWK (encoded as JSON) or an x509 (encdode as PEM) and gives back the JWK representation. Option Examples: - JWK { key: '{ "kty":"EC", ... }' } - PEM { key: '-----BEGIN PRIVATE KEY-----\n...' } - Public Key Only { key: '-----BEGIN PRIVATE KEY-----\n...', public: true } - Must Have Private Key { key: '-----BEGIN PUBLIC KEY-----\n...', private: true } Example: ```js Keypairs.parse({ key: '...' }).catch(function (e) { // could not be parsed or was a public key console.warn(e); return Keypairs.generate(); }); ``` #### Keypairs.parseOrGenerate({ key, throw, [generate opts]... }) Parses the key. Logs a warning on failure, marches on. (a shortcut for the above, with `private: true`) Option Examples: - parse key if exist, otherwise generate `{ key: process.env["PRIVATE_KEY"] }` - generated key curve `{ key: null, namedCurve: 'P-256' }` - generated key modulus `{ key: null, modulusLength: 2048 }` Example: ```js Keypairs.parseOrGenerate({ key: process.env['PRIVATE_KEY'] }).then(function ( pair ) { console.log(pair.public); }); ``` Great for when you have a set of shared keys for development and randomly generated keys in #### Keypairs.import({ pem: '...' } Takes a PEM in pretty much any format (PKCS1, SEC1, PKCS8, SPKI) and returns a JWK. #### Keypairs.export(options) Exports a JWK as a PEM. Exports PEM in PKCS8 (private) or SPKI (public) by default. Options ```js { jwk: jwk , public: true , encoding: 'pem' // or 'der' , format: 'pkcs8' // or 'ssh', 'pkcs1', 'sec1', 'spki' } ``` #### Keypairs.publish({ jwk: jwk, exp: '3d', use: 'sig' }) Promises a public key that adheres to the OIDC and Auth0 spec (plus expiry), suitable to be published to a JWKs URL: ``` { "kty": "EC" , "crv": "P-256" , "x": "..." , "y": "..." , "kid": "..." , "use": "sig" , "exp": 1552074208 } ``` In particular this adds "use" and "exp". #### Keypairs.thumbprint({ jwk: jwk }) Promises a JWK-spec thumbprint: URL Base64-encoded sha256 #### Keypairs.signJwt({ jwk, header, claims }) Returns a JWT (otherwise known as a protected JWS in "compressed" format). ```js { jwk: jwk // required claims , iss: 'https://example.com' , exp: '15m' // all optional claims , claims: { } } ``` Exp may be human readable duration (i.e. 1h, 15m, 30s) or a datetime in seconds. Header defaults: ```js { kid: thumbprint , alg: 'xS256' , typ: 'JWT' } ``` Payload notes: - `iat: now` is added by default (set `false` to disable) - `exp` must be set (set `false` to disable) - `iss` should be the base URL for JWK lookup (i.e. via OIDC, Auth0) Notes: `header` is actually the JWS `protected` value, as all JWTs use protected headers (yay!) and `claims` are really the JWS `payload`. #### Keypairs.signJws({ jwk, header, protected, payload }) This is provided for APIs like ACME (Let's Encrypt) that use uncompressed JWS (instead of JWT, which is compressed). Options: - `header` not what you think. Leave undefined unless you need this for the spec you're following. - `protected` is the typical JWT-style header - `kid` and `alg` will be added by default (these are almost always required), set `false` explicitly to disable - `payload` can be JSON, a string, or even a buffer (which gets URL Base64 encoded) - you must set this to something, even if it's an empty string, object, or Buffer # Additional Documentation Keypairs.js provides a 1-to-1 mapping to the Rasha.js and Eckles.js APIs for the following: - generate(options) - import({ pem: '---BEGIN...' }) - export({ jwk: { kty: 'EC', ... }) - thumbprint({ jwk: jwk }) If you want to know the algorithm-specific options that are available for those you'll want to take a look at the corresponding documentation: - See ECDSA documentation at [Eckles.js](https://git.coolaj86.com/coolaj86/eckles.js/) - See RSA documentation at [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js/) # Contributions Did this project save you some time? Maybe make your day? Even save the day? Please say "thanks" via Paypal or Patreon: - Paypal: [\$5](https://paypal.me/rootprojects/5) | [\$10](https://paypal.me/rootprojects/10) | Any amount: