Interchangeably use RSA & ECDSA with PEM and JWK for Signing, Verifying, CSR generation and JOSE. Ugh... that was a mouthful. :)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AJ ONeal a5236f6c2f v1.2.4: remove stray logs 6 months ago
bin v1.2.4: remove stray logs 6 months ago
.gitignore v1.0.0: just wrap Rasha and Eckles for now 6 months ago
README.md v1.2.0: add CLI stuff 6 months ago
example.js v1.0.1: export and update docs 6 months ago
keypairs.js v1.2.0: add CLI stuff 6 months ago
package-lock.json v1.2.0: add CLI stuff 6 months ago
package.json v1.2.3: bugfix double convert 6 months ago
test.js v1.1.0: Add tests, more convenience methods, more docs 6 months ago

README.md

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

Features

  • Generate keypairs
    • RSA
    • ECDSA (P-256, P-384)
  • PEM-to-JWK (and SSH-to-JWK)
  • JWK-to-PEM (and JWK-to-SSH)
  • Create JWTs (and sign JWS)
  • SHA256 JWK Thumbprints
  • JWK fetching. See Keyfetch.js
    • OIDC
    • Auth0
  • CLI

Usage

A brief (albeit somewhat nonsensical) introduction to the APIs:

Keypairs.generate().then(function (pair) {
  return Keypairs.export({ jwk: pair.private }).then(function (pem) {
    return Keypairs.import({ pem: pem }).then(function (jwk) {
      return Keypairs.thumbprint({ jwk: jwk }).then(function (thumb) {
        console.log(thumb);
        return Keypairs.signJwt({
          jwk: keypair.private
        , claims: {
            iss: 'https://example.com'
          , sub: 'jon.doe@gmail.com'
          , exp: Math.round(Date.now()/1000) + (3 * 24 * 60 * 60)
          }
        });
      });
    });
  });
});

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.

API Overview

  • generate
  • parse
  • parseOrGenerate
  • 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 })

Synchronously strips a key of its private parts and returns the public version.

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
, claims: {
  }
}

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: