@oada/oada-certs

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

Usage no npm install needed!

<script type="module">
  import oadaOadaCerts from 'https://cdn.skypack.dev/@oada/oada-certs';
</script>

README

oada-certs

Use this to create/sign/interact with OADA developer certificates and any other keys or signatures in the OADA and Trellis ecosystems.

Installation

# If you want command-line tool:
npm install -g @oada/oada-certs

# If you just want to use the JS libs in your project:
npm install @oada/oada-certs

Command-line: setup an OADA domain folder

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

# 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

# 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 javascript:

const oadacerts = require('@oada/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(err) {
  console.log('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 certicate
// 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 URL's (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.