Nuns Playing Monopoly

    @root/keypairs

    0.10.3 • Public • Published

    @root/keypairs

    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) and Rasha.js (RSA).

    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
      • [ ] OIDC
      • [ ] Auth0
    • [ ] 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:

    // 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);
    });
    // JWK to PEM
    // (supports various 'format' and 'encoding' options)
    return Keypairs.export({ jwk: pair.private, format: 'pkcs8' }).then(function (
    	pem
    ) {
    	console.log(pem);
    });
    // PEM to JWK
    return Keypairs.import({ pem: pem }).then(function (jwk) {
    	console.log(jwk);
    });
    // Thumbprint a JWK (SHA256)
    return Keypairs.thumbprint({ jwk: jwk }).then(function (thumb) {
    	console.log(thumb);
    });
    // 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:

    '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:

    'use strict';
    
    var Keypairs = require('./keypairs.js');
    
    Keypairs.generate().then(function (pair) {
    	console.log(pair.private);
    	console.log(pair.public);
    });

    index.html:

    <!DOCTYPE html>
    <html>
    	<body>
    		<script src="./dist/main.js"></script>
    	</body>
    </html>
    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:

    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:

    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

    { 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).

    { 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:

    { 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:

    Contributions

    Did this project save you some time? Maybe make your day? Even save the day?

    Please say "thanks" via Paypal or Patreon:

    Where does your contribution go?

    Root is a collection of experts who trust each other and enjoy working together on deep-tech, Indie Web projects.

    Our goal is to operate as a sustainable community.

    Your contributions - both in code and especially monetarily - help to not just this project, but also our broader work of projects that fuel the Indie Web.

    Also, we chat on Keybase in #rootprojects

    Commercial Support

    Do you need...

    • more features?
    • bugfixes, on your timeline?
    • custom code, built by experts?
    • commercial support and licensing?

    Contact aj@therootcompany.com for support options.

    Legal

    Copyright AJ ONeal, Root 2018-2019

    MPL-2.0 | Terms of Use | Privacy Policy

    Install

    npm i @root/keypairs

    DownloadsWeekly Downloads

    29,407

    Version

    0.10.3

    License

    MPL-2.0

    Unpacked Size

    69.6 kB

    Total Files

    20

    Last publish

    Collaborators

    • coolaj86