node-jose
A JavaScript implementation of the JSON Object Signing and Encryption (JOSE) for current web browsers and node.js-based servers. This library implements (wherever possible) all algorithms, formats, and options in JWS, JWE, JWK, and JWA and uses native cryptographic support (WebCrypto API or node.js' "crypto" module) where feasible.
Installing
To install the latest from NPM:
npm install node-jose
Or to install a specific release:
npm install node-jose@0.3.0
Alternatively, the latest unpublished code can be installed directly from the repository:
npm install git+https://github.com/cisco/node-jose.git
Basics
Require the library as normal:
var jose = require('node-jose');
This library uses Promises for nearly every operation.
This library supports Browserify and Webpack. To use in a web browser, require('node-jose')
and bundle with the rest of your app.
The content to be signed/encrypted -- or returned from being verified/decrypted -- are Buffer objects.
Keys and Key Stores
The jose.JWK
namespace deals with JWK and JWK-sets.
jose.JWK.Key
is a logical representation of a JWK, and is the "raw" entry point for various cryptographic operations (e.g., sign, verify, encrypt, decrypt).jose.JWK.KeyStore
represents a collection of Keys.
Creating a JWE or JWS ultimately require one or more explicit Key objects.
Processing a JWE or JWS relies on a KeyStore.
Obtaining a KeyStore
To create an empty keystore:
keystore = jose.JWK.createKeyStore();
To import a JWK-set as a keystore:
jose.JWK.asKeyStore(input).
then(function(result) {
keystore = result;
});
Exporting a KeyStore
To export the public keys of a keystore as a JWK-set:
output = keystore.toJSON();
To export all the keys of a keystore:
output = keystore.toJSON(true);
Retrieving Keys
To retrieve a key from a keystore:
key = keystore.get(kid);
This retrieves the first key that matches the given {kid}. If multiple keys have the same {kid}, you can further narrow what to retrieve:
key = keystore.get(kid, { kty: 'RSA' });
key = keystore.get(kid, { use: 'enc' });
key = keystore.get(kid, { use: 'RSA-OAEP' });
key = keystore.get(kid, { kty: 'RSA', use: 'enc' });
key = keystore.get({ kid: kid, kty: 'RSA', use: 'enc' });
Searching for Keys
To retrieve all the keys from a keystore:
everything = keystore.all();
all()
can be filtered much like get()
:
everything = keystore.all({ kid: kid });
everything = keystore.all({ kty: 'RSA' });
everything = keystore.all({ use: 'enc' });
everything = keystore.all({ alg: 'RSA-OAEP' });
everything = keystore.all({ kid: kid, kty: 'RSA', alg: 'RSA-OAEP' });
Managing Keys
To import an existing Key (as a JSON object or Key instance):
keystore.add(input).
then(function(result) {
key = result;
});
To import and existing Key from a PEM or DER:
keystore.add(input, form).
then(function(result) {
});
To generate a new Key:
keystore.generate("oct", 256).
then(function(result) {
key = result;
});
var props = {
kid: 'gBdaS-G8RLax2qgObTD94w',
alg: 'A256GCM',
use: 'enc'
};
keystore.generate("oct", 256, props).
then(function(result) {
key = result;
});
To remove a Key from its Keystore:
keystore.remove(key);
Importing and Exporting a Single Key
To import a single Key:
jose.JWK.asKey(input).
then(function(result) {
});
jose.JWK.asKey(input, form).
then(function(result) {
});
To export the public portion of a Key as a JWK:
var output = key.toJSON();
To export the public and private portions of a Key:
var output = key.toJSON(true);
Obtaining a Key's Thumbprint
To get or calculate a RFC 7638 thumbprint for a key:
key.thumbprint(hash).
then(function(print) {
});
When importing or generating a key that does not have a "kid" defined, a
"SHA-256" thumbprint is calculated and used as the "kid".
Signatures
Keys Used for Signing and Verifying
When signing content, the key is expected to meet one of the following:
- A secret key (e.g,
"kty":"oct"
) - The private key from a PKI (
"kty":"EC"
or "kty":"RSA"
) key pair
When verifying content, the key is expected to meet one of the following:
- A secret key (e.g,
"kty":"oct"
) - The public key from a PKI (
"kty":"EC"
or "kty":"RSA"
) key pair
Signing Content
At its simplest, to create a JWS:
jose.JWS.createSign(key).
update(input).
final().
then(function(result) {
});
The JWS is signed using the preferred algorithm appropriate for the given Key. The preferred algorithm is the first item returned by key.algorithms("sign")
.
To create a JWS using another serialization format:
jose.JWS.createSign({ format: 'flattened' }, key).
update(input).
final().
then(function(result) {
});
jose.JWS.createSign({ format: 'compact' }, key).
update(input).
final().
then(function(result) {
});
To create a JWS using a specific algorithm:
jose.JWS.createSign({ alg: 'PS256' }, key).
update(input).
final().
then(function(result) {
});
To create a JWS for a specified content type:
jose.JWS.createSign({ fields: { cty: 'jwk+json' } }, key).
update(input).
final().
then(function(result) {
});
To create a JWS from String content:
jose.JWS.createSign(key).
update(input, "utf8").
final().
then(function(result) {
});
To create a JWS with multiple signatures:
jose.JWS.createSign(keys).
update(input).
final().
then(function(result) {
});
Verifying a JWS
To verify a JWS, and retrieve the payload:
jose.JWS.createVerify(keystore).
verify(input).
then(function(result) {
});
To verify using an implied Key:
jose.JWS.createVerify(key).
verify(input).
then(function(result) {
});
To verify using a key embedded in the JWS:
jose.JWS.createVerify().
verify(input, { allowEmbeddedKey: true }).
then(function(result) {
});
Alternatively, a cached createVerify()
can be configured to allow an embedded key:
var verifier = jose.JWS.createVerify({ allowEmbeddedKey: true });
verifier.verify(input).
then(function(result) {
});
The key can be embedded using either 'jwk' or 'x5c', and can be located in either the JWS Unprotected Header or JWS Protected Header.
NOTE: verify()
will use the embedded key (if found and permitted) instead of any other key.
Allowing (or Disallowing) Signature Algorithms
To restrict what signature algorithms are allowed when verifying, add the allowAlgs
member to the options
Object. The allowAlgs
member is either a string or an array of strings, where the string value(s) can be one of the following:
"*"
: accept all supported algorithms<alg name>
(e.g., "PS256"
): accept the specific algorithm (can have a single '*' to match a range of algorithms)!<alg name>
(e.g., "!RS256"
): do not accept the specific algorithm (can have a single '*' to match a range of algorithms)
The negation is intended to be used with the wildcard accept string, and disallow takes precedence over allowed.
To only accept RSA-PSS sigatures:
var opts = {
algorithms: ["PS*"]
};
jose.JWS.createVerify(key, opts).
verify(input).
then(function(result) {
});
To accept any algorithm, but disallow HMAC-based signatures:
var opts = {
algorithms: ["*", "!HS*"]
};
jose.JWS.createVerify(key, opts).
verify(input).
then(input).
then(function(result) {
});
Handling crit
Header Members
To accept 'crit' field members, add the handlers
member to the options Object. The handlers
member is itself an Object, where its member names are the crit
header member, and the value is one of:
Boolean
: accepts (if true
) -- or rejects (if false
) -- the JWS if the member is present.Function
: takes the JWE decrypt output (just prior to decrypting) and returns a Promise for the processing of the member.Object
: An object with the following Function
members:
- "prepare" -- takes the JWE decrypt output (just prior to decrypting) and returns a Promise for the processing of the member.
- "complete" -- takes the JWE decrypt output (immediately after decrypting) and returns a Promise for the processing of the member.
NOTE If the handler function returns a promise, the fulfilled value is ignored. It is expected these handler functions will modify the provided value directly.
To simply accept a crit
header member:
var opts = {
handlers: {
"exp": true
}
};
jose.JWS.createVerify(key, opts).
verify(input).
then(function(result) {
});
To perform additional (pre-verify) processing on a crit
header member:
var opts = {
handlers: {
"exp": function(jws) {
jws.header.exp = new Date(jws.header.exp);
}
}
};
jose.JWS.createVerify(key, opts).
verify(input).
then(function(result) {
});
To perform additional (post-verify) processing on a crit
header member:
var opts = {
handlers: {
"exp": {
complete: function(jws) {
jws.header.exp = new Date(jws.header.exp);
}
}
}
};
jose.JWS.createVerify(key, opts).
verify(input).
then(function(result) {
});
Encryption
Keys Used for Encrypting and Decrypting
When encrypting content, the key is expected to meet one of the following:
- A secret key (e.g,
"kty":"oct"
) - The public key from a PKI (
"kty":"EC"
or "kty":"RSA"
) key pair
When decrypting content, the key is expected to meet one of the following:
- A secret key (e.g,
"kty":"oct"
) - The private key from a PKI (
"kty":"EC"
or "kty":"RSA"
) key pair
Encrypting Content
At its simplest, to create a JWE:
jose.JWE.createEncrypt(key).
update(input).
final().
then(function(result) {
});
How the JWE content is encrypted depends on the provided Key.
- If the Key only supports content encryption algorithms, then the preferred algorithm is used to encrypt the content and the key encryption algorithm (i.e., the "alg" member) is set to "dir". The preferred algorithm is the first item returned by
key.algorithms("encrypt")
. - If the Key supports key management algorithms, then the JWE content is encrypted using "A128CBC-HS256" by default, and the Content Encryption Key is encrypted using the preferred algorithms for the given Key. The preferred algorithm is the first item returned by
key.algorithms("wrap")
.
To create a JWE using a different serialization format:
jose.JWE.createEncrypt({ format: 'compact' }, key).
update(input).
final().
then(function(result) {
});
jose.JWE.createEncrypt({ format: 'flattened' }, key).
update(input).
final().
then(function(result) {
});
To create a JWE and compressing the content before encrypting:
jose.JWE.createEncrypt({ zip: true }, key).
update(input).
final().
then(function(result) {
});
To create a JWE for a specific content type:
jose.JWE.createEncrypt({ fields: { cty : 'jwk+json' } }, key).
update(input).
final().
then(function(result) {
});
To create a JWE with multiple recipients:
jose.JWE.createEncrypt(keys).
update(input).
final().
then(function(result) {
});
Decrypting a JWE
To decrypt a JWE, and retrieve the plaintext:
jose.JWE.createDecrypt(keystore).
decrypt(input).
then(function(result) {
});
To decrypt a JWE using an implied key:
jose.JWE.createDecrypt(key).
decrypt(input).
then(function(result) {
});
Allowing (or Disallowing) Encryption Algorithms
To restrict what encryption algorithms are allowed when verifying, add the allowAlgs
member to the options
Object. The allowAlgs
member is either a string or an array of strings, where the string value(s) can be one of the following:
"*"
: accept all supported algorithms<alg name>
(e.g., "A128KW"
): accept the specific algorithm (can have a single '*' to match a range of similar algorithms)!<alg name>
(e.g., "!RSA1_5"
): do not accept the specific algorithm (can have a single '*' to match a range of similar algorithms)
The negation is intended to be used with the wildcard accept string, and disallow takes precedence over allowed.
To only accept "dir" and AES-GCM encryption:
var opts = {
algorithms: ["dir", "A*GCM"]
};
jose.JWE.createDecrypt(key, opts).
decrypt(input).
then(function(result) {
});
To accept any algorithm, but disallow RSA-based encryption:
var opts = {
algorithms: ["*", "!RSA*"]
};
jose.JWS.createVerify(key, opts).
verify(input).
then(input).
then(function(result) {
});
Handling crit
Header Members
To accept 'crit' field members, add the handlers
member to the options Object. The handlers
member is itself an Object, where its member names are the crit
header member, and the value is one of:
Boolean
: accepts (if true
) -- or rejects (if false
) -- the JWE if the member is present.Function
: takes the JWE decrypt output (just prior to decrypting) and returns a Promise for the processing of the member.Object
: An object with the following Function
members:
- "prepare" -- takes the JWE decrypt output (just prior to decrypting) and returns a Promise for the processing of the member.
- "complete" -- takes the JWE decrypt output (immediately after decrypting) and returns a Promise for the processing of the member.
NOTE If the handler function returns a promise, the fulfilled value is ignored. It is expected these handler functions will modify the provided value directly.
To simply accept a crit
header member:
var opts = {
handlers: {
"exp": true
}
};
jose.JWE.createDecrypt(key, opts).
decrypt(input).
then(function(result) {
});
To perform additional (pre-decrypt) processing on a crit
header member:
var opts = {
handlers: {
"exp": function(jwe) {
jwe.header.exp = new Date(jwe.header.exp);
}
}
};
jose.JWE.createDecrypt(key, opts).
decrypt(input).
then(function(result) {
});
To perform additional (post-decrypt) processing on a crit
header member:
var opts = {
handlers: {
"exp": {
complete: function(jwe) {
jwe.header.exp = new Date(jwe.header.exp);
}
}
}
};
jose.JWE.createDecrypt(key, opts).
decrypt(input).
then(function(result) {
});
Useful Utilities
Converting to Buffer
To convert a Typed Array, ArrayBuffer, or Array of Numbers to a Buffer:
buff = jose.util.asBuffer(input);
URI-Safe Base64
This exposes urlsafe-base64's encode
and decode
methods as encode
and decode
(respectively).
To convert from a Buffer to a base64uri-encoded String:
var output = jose.util.base64url.encode(input);
To convert a String to a base64uri-encoded String:
output = jose.util.base64url.encode(input, "utf8");
output = jose.util.base64url.encode(input);
To convert a base64uri-encoded String to a Buffer:
var output = jose.util.base64url.decode(input);
Random Bytes
To generate a Buffer of octets, regardless of platform:
var rnd = jose.util.randomBytes(32);
This function uses:
crypto.randomBytes()
on node.jscrypto.getRandomValues()
on modern browsers- A PRNG based on AES and SHA-1 for older platforms