AJ ONeal 11965ca603 | ||
---|---|---|
bin | ||
fixtures | ||
lib | ||
tests | ||
.gitignore | ||
.prettierrc | ||
LICENSE | ||
README.md | ||
ecdsa.js | ||
keypairs.js | ||
package-lock.json | ||
package.json | ||
rsa.js |
README.md
@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
- 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
- See keypairs-cli
- Node
- 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
ls dist/
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 (setfalse
to disable)exp
must be set (setfalse
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 headerkid
andalg
will be added by default (these are almost always required), setfalse
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:
- Paypal: $5 | $10 | Any amount: paypal@therootcompany.com
- Patreon: https://patreon.com/rootprojects
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