UNPKG

@oada/certs

Version:

Generate and verify JWT signatures (OAuth dynamic client registration certificates and Trellis document integrity signatures) in the Open Ag Data Alliance (OADA) and Trellis ecosystems

193 lines (139 loc) 7.14 kB
# @oada/certs [![npm](https://img.shields.io/npm/v/@oada/certs)](https://www.npmjs.com/package/@oada/certs) [![Downloads/week](https://img.shields.io/npm/dw/@oada/certs.svg)](https://npmjs.org/package/@oada/certs) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![License](https://img.shields.io/github/license/OADA/oada-certs)](LICENSE) Use this to create/sign/interact with OADA developer certificates and any other keys or signatures in the OADA and Trellis ecosystems. ## Installation ```shell # If you want command-line tool: yarn global add @oada/certs # If you just want to use the JS libs in your project: yarn add @oada/certs ``` ## Command-line: setup an OADA domain folder ```shell cd domain_folder oada-certs --create-keys oada-certs ``` NOTE: default is to look in the current folder for signing keys ## Command-line: sign a certificate ```shell # creates signed_software_statement.js in current folder oada-certs --signkey="./some_path_to_privatekey_pem" --sign=./some_path_to_unsigned_cert.js # If you are hosting signing key with a jku: oada-certs --signkey="./some_path_to_privatekey_pem" --signjku="https://some.jku.url" --signkid="someKeyIdAtThatJKU" --sign="./path_to_unsigned_cert.js" ``` ## Command-line: validate/debug a certificate ```shell # Note: caching is in-memory and therefore unused here oada-certs --validate="signed_software_statement.js" # If there are errors, they will print here. It will # also tell you if the certificate is trusted ``` ## Include library in TypeScript/JavaScript ```typescript import * as oadacerts from '@oada/certs'; // If you don't pass a jku, it puts the public jwk for the // sign key into the JWT automatically try { const signed_jwt_cert = await oadacerts.sign(payload, signkey, { jku: 'https://url.to.some.jwkset', kid: 'someKeyidInThatSet', }); } catch (error: unknown) { console.log(error, 'Error in signing certificate'); } // Returns a promise: const { trusted, payload, valid, details } = await oadacerts.validate( signed_jwt_cert ); // trusted = true if cert was signed by key on a trusted list // payload = JSON object that is the decoded client certificate // valid = true if cert was decodable with a correct signature // details = array of details about the validation process to help with debugging a cert // NOTE: if the certificate is untrusted, it cannot use a jku in the signature, // it must use a jwk to be considered valid. This avoids fetching potentially malicious URL's. // Self-explanatory utilities for working with JWK key sets (jwks): oadacerts.jwksUtils.isJWK(key); oadacerts.jwksUtils.isJWKset(set); oadacerts.jwksUtils.findJWK(kid, jwks); // jwkForSignature attempts to figure out the correct public JWK to use in // validating a given signature. Uses an intelligent cache for remote-hosted // jwk's. // What makes this function tricky is the "hint": // It was designed to make it simple to say something like: // "hint: I looked up the JKU ot JWK on the signature, and it was from a trusted source." // (i.e. hint = truthy), // and it's truthy value is then either the JWKS (object) from the trusted source, // or the jku (string) of the trusted source's jwks // or // "hint: I looked at my trusted sources and this one doesn't have a jwk or jku that matches." // (i.e. hint === false) // which means "just use the header on the signature because I have no outside reference that verifies it" // // - If boolean false, use the jku from the jose header and if no jku then use jose's jwk // - If boolean true, throw error (it should have been either an object or a string) // - If string, assume string is a jku uri, go get that URI and then check jose's jwk against it // - If object and looks like a jwks, look for jose's jwk in the set // - If object and looks like a jwk, compare that jwk with jose's jwk const jwk = oadacerts.jwksUtils.jwkForSignature(jwt, hint, { timeout: 1000 }); ``` ## API ### _async_ `sign(payload, key, options)` Generates a JWT with the given payload, signed with the given key. Key should be a JWK, but it can also be a PEM string. - `payload` _required_: The JSON payload to sign in the JWT. - `key` _required_: JWK private key to sign with. If this is a string instead of a JWK, it is assumed to be a PEM string. - `options` _optional_: If you need to specify a given `kid` (key id) and `jku` (JWK set URL) because your key is trusted, you can pass them here in `options.header`. `options.header` is passed to `jose.JWS.createSign`. ### _async_ `validate(signature, options)` - `sig` _required_: the signed JWT to validate - `options` _optional_: - `options.timeout` (default: 1000 ms): How long to wait on remote URL requests. - `options.trustedListCacheTime` (default: 3600 sec): How long to use immediately use the cached value for the trusted list before waiting on the request to finish. - `options.additionalTrustedListURIs`: Any additional URLs (array of strings) to include in the search for a trusted key. - `options.disableDefaultTrustedListURI`: Only use the additionalTrustedListURI's, not the default one. Returns `{ trusted, payload, valid, header, details }` - `trusted`: `true|false`: true if signing key was on the trusted list - `payload`: the decoded payload - `valid`: `true|false`: true if the JWT was a valid JWT and the signature matched, says nothing about whether it was trusted. - `details`: array of strings about the validation process that can be helpful for debugging. ### `validate.TRUSTED_LIST_URI` Exports the core `TRUSTED_LIST_URI` string for convenience as a property on the validate function. ### `validate.clearCache()` Mainly for testing, you can clear the internal in-memory cache for all trusted lists with this function. ### `jwksUtils.isJWKSet(set)` Return `true` if `set` looks like a JWK set (`jwks`) ### `jwksUtils.isJWK(key)` Return `true` if `key` looks like a `jwk`. ### `jwksUtils.findJWK(kid, jwks)` Search for the given `kid` (key id) in the `jwks` (JWK set) ### `jwksUtils.decodeWithoutVerify(jwt)` Given a JWT, decode the header, payload, and signature without verifying them. Returns `{ header, payload, signature }` ### _async_ `jwksUtils.jwkForSignature(jwt, hint, options)` Returns to you the JWK necessary to validate a given JWT. Described above in detail in the javascript example. ### `jwksUtils.clearJWKSCache()` Mainly for testing, clear the internal JWK key cache (i.e. previous `jku`-based keys that it has looked up). This is not the trusted list cache. ### `jwksUtils.getJWKSCache()` Returns the JWK cache to you if you want to see it. Mainly for testing. ### `jwksUtils.cachePruneOldest()` Eliminates the oldest entry from the JWK cache. Mainly for testing. ### _async_ `keys.create()` Returns `{ public, private }`. Both are JWK's. You can use `private` to sign things. ### _async_ `keys.pubFromPriv(priv)` Given a private JWK, return the corresponding public key as a JWK. ### `jose` Exports the internally-used `node-jose` module for convenience.