UNPKG

jose-jwe-jws

Version:

Library to encrypt and decrypt data in JSON Web Encryption (JWE) format and to sign data in JSON Web Signature (JWS) format. Leverages Browser's native web crypto API.

200 lines (173 loc) 5.97 kB
/* - * Copyright 2015 Peculiar Ventures * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as Utils from './jose-utils'; /** * Handles signature verification. * * @param cryptographer an instance of WebCryptographer (or equivalent). Keep * in mind that decryption mutates the cryptographer. * @param message a JWS message * @param keyfinder (optional) a function returning a Promise<CryptoKey> given * a key id * * @author Patrizio Bruno <patrizio@desertconsulting.net> */ export class Verifier { constructor (cryptographer, message, keyfinder) { var that = this; var alg; var jwt; var aad; var header; var payload; var signatures; var protectedHeader; var jwtRx = /^([0-9a-z_-]+)\.([0-9a-z_-]+)\.([0-9a-z_-]+)$/i; that.cryptographer = cryptographer; alg = cryptographer.getContentSignAlgorithm(); if (Utils.isString(message)) { if ((jwt = jwtRx.exec(message))) { if (jwt.length !== 4) { throw new Error('wrong JWS compact serialization format'); } message = { protected: jwt[1], payload: jwt[2], signature: jwt[3] }; } else { message = JSON.parse(message); } } else if (typeof message !== 'object') { throw new Error('data format not supported'); } aad = message.protected; header = message.header; payload = message.payload; signatures = message.signatures instanceof Array ? message.signatures.slice(0) : []; signatures.forEach(function (sign) { sign.aad = sign.protected; sign.protected = JSON.parse(new Utils.Base64Url().decode(sign.protected)); }); that.aad = aad; protectedHeader = new Utils.Base64Url().decode(aad); try { protectedHeader = JSON.parse(protectedHeader); } catch (e) { } if (!protectedHeader && !header) { throw new Error('at least one header is required'); } if (!protectedHeader.alg) { throw new Error("'alg' is a mandatory header"); } if (protectedHeader.alg !== alg) { throw new Error("the alg header '" + protectedHeader.alg + "' doesn't match the requested algorithm '" + alg + "'"); } if (protectedHeader && protectedHeader.typ && protectedHeader.typ !== 'JWT') { throw new Error("typ '" + protectedHeader.typ + "' not supported"); } if (message.signature) { signatures.unshift({ aad: aad, protected: protectedHeader, header: header, signature: message.signature }); } that.signatures = []; for (var i = 0; i < signatures.length; i++) { that.signatures[i] = JSON.parse(JSON.stringify(signatures[i])); that.signatures[i].signature = Utils.arrayFromString(new Utils.Base64Url().decode(signatures[i].signature)); } that.payload = payload; that.keyPromises = {}; that.waiting_kid = 0; if (keyfinder) { that.keyfinder = keyfinder; } } /** * Add supported recipients to verify multiple signatures * * @param key public key in json format. Parameters can be base64 * encoded, strings or number (for 'e'), or CryptoKey. * @param keyId a string identifying the key. OPTIONAL * @param alg String signature algorithm. OPTIONAL * @returns Promise<string> a Promise of a key id */ addRecipient (key, keyId, alg) { var that = this; var kidPromise; var keyPromise; if (Utils.isCryptoKey(key)) { keyPromise = new Promise(function (resolve) { resolve(key); }); } else { keyPromise = Utils.importPublicKey(key, alg || that.cryptographer.getContentSignAlgorithm(), 'verify'); } if (keyId) { kidPromise = new Promise(function (resolve) { resolve(keyId); }); } else if (Utils.isCryptoKey(key)) { throw new Error('keyId is a mandatory argument when the key is a CryptoKey'); } else { console.log("it's unsafe to omit a keyId"); kidPromise = this.cryptographer.keyId(key); } that.waiting_kid++; return kidPromise.then(function (kid) { that.keyPromises[kid] = keyPromise; that.waiting_kid--; return kid; }); } /** * Verifies a JWS signature * * @returns Promise<Array> a Promise of an array of objects { kid: string, verified: bool, payload?: string } * * payload is only populated and usable if verified is true */ verify () { var that = this; var signatures = that.signatures; var keyPromises = that.keyPromises; var keyfinder = that.keyfinder; var promises = []; var check = !!keyfinder || Object.keys(that.keyPromises).length > 0; if (!check) { throw new Error('No recipients defined. At least one is required to verify the JWS.'); } if (that.waiting_kid) { throw new Error('still generating key IDs'); } signatures.forEach(function (sig) { var kid = sig.protected.kid; if (keyfinder) { keyPromises[kid] = keyfinder(kid); } promises.push(that.cryptographer.verify(sig.aad, that.payload, sig.signature, keyPromises[kid], kid) .then(function (vr) { if (vr.verified) { vr.payload = new Utils.Base64Url().decode(that.payload); } return vr; })); }); return Promise.all(promises); } }