diff --git a/README.md b/README.md index c36a78e..fde01dc 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ available natively in browsers and [via npm][node-webcrypto] in Node.js. [jwkset]: https://tools.ietf.org/html/rfc7517#section-5 [w3c-webcrypto]: https://www.w3.org/TR/WebCryptoAPI/ [node-webcrypto]: https://www.npmjs.com/package/@trust/webcrypto +[api-docs]: https://anvilresearch.github.io/jwk ## Table of Contents @@ -71,24 +72,11 @@ $ npm run karma // Karma (browser) ## API -### JWK +Full documentation available [here][api-docs]. -#### new JWK() -#### (static) importKey() -#### sign() -#### verify() -#### encrypt() -#### decrypt() +### Examples -### JWKSet - -#### new JWKSet() -#### (static) generateKeys() -#### (static) importKeys() -#### generateKeys() -#### importKeys() -#### filter() -#### find() +TBD ## Contribute diff --git a/package.json b/package.json index e858682..c38070c 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "sinon-chai": "^2.12.0" }, "dependencies": { - "@trust/jwa": "^0.4.3", + "@trust/jwa": "^0.4.5", "@trust/webcrypto": "^0.4.0", "node-fetch": "^1.7.2", "sift": "^5.0.0" diff --git a/src/JWK.js b/src/JWK.js index 80f6838..1d61f6b 100644 --- a/src/JWK.js +++ b/src/JWK.js @@ -21,8 +21,13 @@ class JWK { /** * constructor * - * @class - * JSON Web Key + * @class JWK + * + * @description + * JSON Web Key ([IETF RFC7517](https://tools.ietf.org/html/rfc7517)) + * + * @param {Object} data + * @param {Object} [options={}] - Additional JWK metadata. */ constructor (data, options = {}) { if (!data) { @@ -38,66 +43,87 @@ class JWK { } } - // Handle object input - if (data.alg && options.alg && data.alg !== options.alg) { - throw new DataError('Conflicting algorithm option') + // Handle options input + let metadata = Object.keys(options) + .filter(key => { + let dataValue = data[key] + let optionsValue = options[key] - } else if (!data.alg && options.alg) { - data.alg = options.alg - - } else if (!data.alg && !options.alg) { - throw new DataError('Valid JWA algorithm required for JWK') - } + if (dataValue && optionsValue && dataValue !== optionsValue) { + throw new DataError(`Conflicting '${key}' option`) + } - // Handle kid transferral - if (data.kid && options.kid && data.kid !== options.kid) { - throw new DataError('Conflicting key identifier option') + return !dataValue && optionsValue + }) + .reduce((state, key) => { + state[key] = options[key] + return state + }, {}) - } else if (!data.kid && options.kid) { - data.kid = options.kid + // Assign input + Object.assign(this, data, metadata) - } else if (!data.kid && !options.kid) { - throw new DataError('Valid JWA key identifier required for JWK') + // Enforce required properties + if (!this.alg) { + throw new DataError('Valid \'alg\' required for JWK') } - Object.assign(this, data) + if (!this.kid) { + throw new DataError('Valid \'kid\' required for JWK') + } } /** * importKey * + * @description + * Import a JWK from JSON String or a JS Object. + * * @param {(String|Object)} data - * @return {Promise} + * @param {Object} [options] - Additional JWK metadata. + * @return {Promise.} A promise that resolves the JWK instance. */ - static importKey (data, options = {}) { + static importKey (data, options) { return Promise.resolve() + // Create instance .then(() => new JWK(data, options)) - .then(jwk => { - return JWA.importKey(jwk) - .then(({ cryptoKey }) => { - Object.defineProperty(jwk, 'cryptoKey', { - value: cryptoKey, - enumerable: false, - configurable: false - }) - - return jwk - }) - }) + + // Import CryptoKey and assign to JWK + .then(jwk => JWA.importKey(jwk).then(({ cryptoKey }) => { + Object.defineProperty(jwk, 'cryptoKey', { + value: cryptoKey, + enumerable: false, + configurable: false + }) + + return jwk + })) } /** * fromCryptoKey * - * @param {CryptoKey} key - * @param {Object} [options] - * @return {Promise} + * @description + * Import a JWK from a [WebCrypto CryptoKey](https://github.com/anvilresearch/webcrypto). + * + * @param {CryptoKey} key - [WebCrypto CryptoKey]{@link https://github.com/anvilresearch/webcrypto}. + * @param {Object} [options] - Additional JWK metadata. + * @return {Promise.} A promise that resolves the JWK instance. */ static fromCryptoKey (key, options) { + // Export JWK return JWA.exportKey('jwk', key) + + // Create JWK instance and assign CryptoKey .then(data => { let jwk = new JWK(data, options) - Object.defineProperty(jwk, 'cryptoKey', { value: key, enumerable: false, configurable: false }) + + Object.defineProperty(jwk, 'cryptoKey', { + value: key, + enumerable: false, + configurable: false + }) + return jwk }) } @@ -105,8 +131,22 @@ class JWK { /** * sign * - * @param {(String|Buffer)} data - * @return {Promise} + * @description + * Sign arbitrary data using the JWK. + * + * @example Signing the string "test" + * privateJwk.sign('test') + * .then(console.log) + * // + * // (line breaks for display only) + * // + * // => "MEUCIQCHwnGM8IsOJgfQsoPgs3hMd8 + * // ahfWHM9ZNvj1K6i2yhKQIgWGOuXX43 + * // lSTo-U8Pa8sURR53lv6Osjw-dtoLse + * // lftqQ" + * + * @param {(String|Buffer)} data - The data to sign. + * @return {Promise.} A promise that resolves the base64url encoded signature string. */ sign (data) { let { alg, cryptoKey } = this @@ -116,9 +156,21 @@ class JWK { /** * verify * - * @param {(String|Buffer)} data - * @param {String} signature - * @return {Promise} + * @description + * Verify a signature using the JWK. + * + * @example Verify a signature of the string "test" + * // base64url encoded signature string + * let signature = `MEUCIQCHwnGM8IsOJgfQsoPgs3hMd8ahfWHM9ZN + * vj1K6i2yhKQIgWGOuXX43lSTo-U8Pa8sURR53lv6Osjw-dtoLselftqQ` + * + * publicJwk.verify('test', signature) + * .then(console.log) + * // => true + * + * @param {(String|Buffer)} data - The data to verify. + * @param {String} signature - A base64url signature string. + * @return {Promise.} A promise that resolves the boolean result of the signature verification. */ verify (data, signature) { let { alg, cryptoKey } = this @@ -128,9 +180,19 @@ class JWK { /** * encrypt * - * @param {(String|Object)} data - * @param {(String|Buffer)} aad - integrity protected data - * @return {Promise} + * @description + * Encrypt arbitrary data using the JWK. + * + * @example Encrypt the string "data" + * secretJwk.encrypt('data') + * .then(console.log) + * // => { iv: 'u0l3ttqUFDQ8mcRboHv5Vw', + * // ciphertext: 'yq3K4w', + * // tag: 'fHlZ__uuUnHn0ac-Lnrr-A' } + * + * @param {(String|Object)} data - The data to encrypt. + * @param {(String|Buffer)} [aad] - Additional non-encrypted integrity protected data (AES-GCM). + * @return {Promise.} A promise that resolves an object containing the base64url encoded `iv`, `ciphertext` and `tag` (AES-GCM). */ encrypt (data, aad) { let { alg, cryptoKey } = this @@ -140,15 +202,28 @@ class JWK { /** * decrypt * - * @param {(String|Object)} data - * @param {(String|Buffer)} iv - * @param {(String|Buffer)} tag - * @param {(String|Buffer)} aad - * @return {Promise} + * @description + * Decrypt data using the JWK. + * + * @example Decrypt encrypted string "test" + * // base64url encoded data + * let ciphertext = 'yq3K4w' + * let iv = 'u0l3ttqUFDQ8mcRboHv5Vw' + * let tag = 'fHlZ__uuUnHn0ac-Lnrr-A' + * + * secretJwk.decrypt(ciphertext, iv, tag) + * .then(console.log) + * // => "data" + * + * @param {(String|Buffer)} ciphertext - The encrypted data to decrypt. + * @param {(String|Buffer)} iv - The initialization vector. + * @param {(String|Buffer)} [tag] - The authorization tag (AES-GCM). + * @param {(String|Buffer)} [aad] - Additional non-encrypted integrity protected data (AES-GCM). + * @return {Promise.} A promise that resolves the plaintext data. */ - decrypt (data, iv, tag, aad) { + decrypt (ciphertext, iv, tag, aad) { let { alg, cryptoKey } = this - return JWA.decrypt(alg, cryptoKey, data, iv, tag, aad) + return JWA.decrypt(alg, cryptoKey, ciphertext, iv, tag, aad) } } diff --git a/src/JWKSet.js b/src/JWKSet.js index 640917a..e95f3d4 100644 --- a/src/JWKSet.js +++ b/src/JWKSet.js @@ -19,8 +19,8 @@ const { DataError, OperationError } = require('./errors') /** * Random KID Generator + * @ignore */ -/* istanbul ignore */ function random (byteLen) { let value = crypto.getRandomValues(new Uint8Array(byteLen)) return Buffer.from(value).toString('hex') @@ -35,10 +35,12 @@ class JWKSet { /** * constructor * - * @class - * JWKSet + * @class JWKSet + * + * @description + * JSON Web Key Set ([IETF RFC7517 Section 5.](https://tools.ietf.org/html/rfc7517#section-5)) * - * @param {(Object|Array)} data + * @param {(Object|Array)} [data] */ constructor (data = {}) { if (Array.isArray(data)) { @@ -55,8 +57,62 @@ class JWKSet { /** * generateKeys * + * @description + * Instantiate a new JWKSet and generate one or many JWK keypairs and secret keys. + * + * @example Simple RSA keypair + * JWKSet.generateKeys('RS256') + * .then(console.log) + * // => { keys: [ + * // { d: '...', + * // kty: 'RSA', + * // alg: 'RS256', + * // kid: 'abcd', + * // ... }, + * // { kty: 'RSA', + * // alg: 'RS256', + * // kid: 'abcd', + * // ... } + * // ] } + * + * @example Multiple keypairs + * JWKSet.generateKeys(['RS256', 'ES256']) + * .then(console.log) + * // => { keys: [ + * // { ..., kty: 'RSA', alg: 'RS256' }, + * // { ..., kty: 'RSA', alg: 'RS256' }, + * // { ..., kty: 'EC', alg: 'ES256' }, + * // { ..., kty: 'EC', alg: 'ES256' }] } + * + * @example Object descriptor RSA keypair + * let keyDescriptor = { + * alg: 'RS256', + * kid: 'custom', + * modulusLength: 1024 + * } + * + * JWKSet.generateKeys(keyDescriptor) + * .then(console.log) + * // => { keys: [ + * // { ..., alg: 'RS256', kid: 'custom' }, + * // { ..., alg: 'RS256', kid: 'custom' }] } + * + * @example Mixed input, multiple keypairs + * let keyDescriptor = { + * alg: 'RS512', + * modulusLength: 1024 + * } + * + * JWKSet.generateKeys([keyDescriptor, 'ES256']) + * .then(console.log) + * // => { keys: [ + * // { ..., kty: 'RSA', alg: 'RS512' }, + * // { ..., kty: 'RSA', alg: 'RS512' }, + * // { ..., kty: 'EC', alg: 'ES256' }, + * // { ..., kty: 'EC', alg: 'ES256' }] } + * * @param {(String|Object|Array)} data - * @return {Promise} + * @return {Promise.} A promise that resolves a new JWKSet containing the generated key pairs. */ static generateKeys (data) { return Promise.resolve(new JWKSet()) @@ -66,8 +122,68 @@ class JWKSet { /** * importKeys * + * @description + * Instantiate a new JWKSet and import keys from JSON string, JS object, remote URL or file path. + * + * @example Import keys from JSON string + * let jsonJwkSet = '{"meta":"abcd","keys":[...]}' + * + * JWKSet.importKeys(jsonJwkSet) + * .then(console.log) + * // => { meta: 'abcd', keys: [...] } + * + * @example Import keys from object + * let jwkSet = { + * meta: 'abcd', + * keys: [...] + * } + * + * JWKSet.importKeys(jwkSet) + * .then(console.log) + * // => { meta: 'abcd', keys: [...] } + * + * @example Import keys from URL + * let jwkSetUrl = 'https://idp.example.com/jwks' + * + * JWKSet.importKeys(jwkSetUrl) + * .then(console.log) + * // + * // HTTP/1.1 200 OK + * // Content-Type: application/json + * // + * // {"meta":"abcd","keys":[...]} + * // + * // => { meta: 'abcd', + * // keys: [...] } + * + * @example Import keys from file path + * let jwkSetPath = './path/to/my/file.json' + * + * JWKSet.importKeys(jwkSetPath) + * .then(console.log) + * // + * // Contents of ./path/to/my/file.json - + * // {"meta":"abcd","keys":[...]} + * // + * // => { meta: 'abcd', + * // keys: [...] } + * + * @example Mixed input, multiple sources + * let jwkSetPath = './path/to/my/file.json' + * let jwkSet = { meta: 'abcd', keys: [...] } + * + * JWKSet.importKeys([jwkSet, jwkSetPath]) + * .then(console.log) + * // + * // Contents of ./path/to/my/file.json - + * // {"other":"efgh","keys":[...]} + * // + * // => { meta: 'abcd', + * // other: 'efgh', + * // keys: [...] } + * * @param {(String|Object|Array)} data - * @return {Promise} + * @return {Promise.} A promise that resolves a new JWKSet containing the generated key pairs. */ static importKeys (data) { return Promise.resolve(new JWKSet()) @@ -77,8 +193,55 @@ class JWKSet { /** * generateKeys * + * @description + * Generate additional keys and include them in the JWKSet. + * + * @example Simple RSA keypair + * jwks.generateKeys('RS256') + * .then(console.log) + * // => [ + * // { kty: 'RSA' }, + * // { kty: 'RSA' } + * // ] + * + * @example Multiple keypairs + * jwks.generateKeys(['RS256', 'ES256']) + * .then(console.log) + * // => [ + * // [ { kty: 'RSA' }, + * // { kty: 'RSA' } ], + * // [ { kty: 'EC' }, + * // { kty: 'EC' } ] ] + * + * @example Object descriptor RSA keypair + * let keyDescriptor = { + * alg: 'RS256', + * kid: 'custom', + * modulusLength: 1024 + * } + * + * jwks.generateKeys(keyDescriptor) + * .then(console.log) + * // => [ { kty: 'RSA', kid: 'custom' }, + * // { kty: 'RSA', kid: 'custom' } ] + * + * @example Mixed input, multiple keypairs + * let keyDescriptor = { + * alg: 'RS512', + * modulusLength: 1024 + * } + * + * jwks.generateKeys([keyDescriptor, 'ES256']) + * .then(console.log) + * // => [ + * // [ { kty: 'RSA' }, + * // { kty: 'RSA' } ], + * // [ { kty: 'EC' }, + * // { kty: 'EC' } ] + * // ] + * * @param {(String|Object|Array)} data - * @return {Promise} + * @return {Promise., Array.>>} A promise that resolves the newly generated key pairs after they are added to the JWKSet instance. */ generateKeys (data) { let cryptoKeyPromise, alg @@ -129,10 +292,74 @@ class JWKSet { /** * importKeys * + * @description + * Import additional keys and include them in the JWKSet. + * + * @example Import keys from JSON string + * let jsonJwkSet = '{"meta":"abcd","keys":[...]}' + * + * jwks.importKeys(jsonJwkSet) + * .then(console.log) + * // => [ {...}, + * // {...} ] + * + * @example Import keys from object + * let jwkSet = { + * meta: 'abcd', + * keys: [...] + * } + * + * jwks.importKeys(jwkSet) + * .then(console.log) + * // => [ {...}, + * // {...} ] + * + * @example Import keys from URL + * let jwkSetUrl = 'https://idp.example.com/jwks' + * + * jwks.importKeys(jwkSetUrl) + * .then(console.log) + * // + * // HTTP/1.1 200 OK + * // Content-Type: application/json + * // + * // {"meta":"abcd","keys":[...]} + * // + * // => [ {...}, + * // {...} ] + * + * @example Import keys from file path + * let jwkSetPath = './path/to/my/file.json' + * + * jwks.importKeys(jwkSetPath) + * .then(console.log) + * // + * // Contents of ./path/to/my/file.json - + * // {"meta":"abcd","keys":[...]} + * // + * // => [ {...}, + * // {...} ] + * + * @example Mixed input, multiple sources + * let jwkSetPath = './path/to/my/file.json' + * let jwkSet = { meta: 'abcd', keys: [...] } + * + * jwks.importKeys([jwkSet, jwkSetPath]) + * .then(console.log) + * // + * // Contents of ./path/to/my/file.json - + * // {"other":"efgh","keys":[...]} + * // + * // => [ {...}, + * // {...}, + * // {...}, + * // {...} ] + * * @param {(String|Object|Array)} data - * @return {Promise} + * @param {JWK} [kek] - Key encryption key. + * @return {Promise.>} A promise that resolves the newly imported key pairs after they are added to the JWKSet instance. * - * @todo import encrypted JWKSet + * @todo Import encrypted JWKSet */ importKeys (data) { if (!data) { @@ -192,8 +419,24 @@ class JWKSet { /** * filter * + * @description + * Execute a filter query on the JWKSet keys. + * + * @example Function predicate + * let predicate = key => key.key_ops.includes('sign') + * + * let filtered = jwks.filter(predicate) + * // => [ { ..., key_ops: ['sign'] } ] + * + * @example MongoDB-like object predicate (see [Sift]{@link https://github.com/crcn/sift.js}) + * let predicate = { key_ops: { $in: ['sign', 'verify'] } } + * + * let filtered = jwks.filter(predicate) + * // => [ { ..., key_ops: ['sign'] }, + * // { ..., key_ops: ['verify'] } ] + * * @param {(Function|Object)} predicate - Filter function or predicate object - * @return {Promise} + * @return {Array.} An array of JWKs matching the filter predicate. */ filter (predicate) { let { keys } = this @@ -215,8 +458,23 @@ class JWKSet { /** * find * + * @description + * Execute a find query on the JWKSet keys. + * + * @example Function predicate + * let predicate = key => key.key_ops.includes('sign') + * + * let filtered = jwks.find(predicate) + * // => { ..., key_ops: ['sign'] } + * + * @example MongoDB-like object predicate (see [Sift]{@link https://github.com/crcn/sift.js}) + * let predicate = { key_ops: { $in: ['sign', 'verify'] } } + * + * let filtered = jwks.find() + * // => { ..., key_ops: ['sign'] } + * * @param {(Function|Object)} predicate - Find function or predicate object - * @return {Promise} + * @return {JWK} The _first_ JWK matching the find predicate. */ find (predicate) { let { keys } = this @@ -238,6 +496,7 @@ class JWKSet { /** * rotate + * @ignore * * @param {(JWK|Array|Object|Function)} keys - jwk, array of jwks, filter predicate object or function. * @return {Promise} @@ -251,10 +510,13 @@ class JWKSet { /** * exportKeys * + * @description + * Serialize the JWKSet for storage or transmission. + * * @param {JWK} [kek] - optional encryption key - * @return {String} JSON String + * @return {String} The JSON serialized string of the JWKSet. * - * @todo encryption + * @todo Encryption */ exportKeys (kek) { return JSON.stringify(this) @@ -263,9 +525,12 @@ class JWKSet { /** * publicJwks * - * @return {String} Publishable JSON JWKSet + * @type {String} + * + * @description + * The publishable JSON serialized string of the JWKSet. Returns _only public keys_. * - * @todo memoise this + * @todo Memoization */ get publicJwks () { let keys = this.filter(key => key.cryptoKey.type === 'public') diff --git a/src/errors/DataError.js b/src/errors/DataError.js index 0bc564e..6838819 100644 --- a/src/errors/DataError.js +++ b/src/errors/DataError.js @@ -2,6 +2,7 @@ /** * DataError + * @ignore */ class DataError extends Error { diff --git a/src/errors/OperationError.js b/src/errors/OperationError.js index ef80910..ad18674 100644 --- a/src/errors/OperationError.js +++ b/src/errors/OperationError.js @@ -2,6 +2,7 @@ /** * OperationError + * @ignore */ class OperationError extends Error { diff --git a/test/JWKSpec.js b/test/JWKSpec.js index ddf38fc..55f6c43 100644 --- a/test/JWKSpec.js +++ b/test/JWKSpec.js @@ -44,7 +44,9 @@ const ECPublicJWKNoKid = Object.assign({}, ECPublicJWK) delete ECPublicJWKNoKid.kid const plainTextData = 'data' +const plainTextAad = 'meta' const encryptedData = {"iv":"RH9i_J861XN7qvgHYZ86ag","ciphertext":"qkrkiw","tag":"kqfdLgy8qopnzeKmC5JwQA"} +const encryptedDataWithAad = {"iv":"zrXJWOthT2tnFErPhWCrfw","ciphertext":"JWwKBg","tag":"txl7BQK4fxEP5cie2OQEZA","aad":"bWV0YQ"} const signedData = 'MEQCIAIqNr8-7Pozi1D-cigvEKbkP5SpKezzEEDSqM9McIV1AiBd4gioW8njOpr29Ymrvjp46q7hA7lSOjAJpdi5TjHWsg' /** @@ -69,7 +71,7 @@ describe('JWK', () => { }) it('should throw if conflicting alg options are passed in', () => { - expect(() => new JWK(ECPublicJWK, { alg: 'KS256' })).to.throw('Conflicting algorithm option') + expect(() => new JWK(ECPublicJWK, { alg: 'KS256' })).to.throw('Conflicting \'alg\' option') }) it('should not throw if alg passed in twice but not conflicting', () => { @@ -82,11 +84,11 @@ describe('JWK', () => { }) it('should throw if alg is not present on data or options', () => { - expect(new JWK(ECPublicJWKNoAlg, { alg: 'ES256' })).to.throw + expect(() => new JWK(ECPublicJWKNoAlg, {})).to.throw('Valid \'alg\' required for JWK') }) it('should throw if conflicting kid options are passed in', () => { - expect(() => new JWK(ECPublicJWK, { kid: '1' })).to.throw('Conflicting key identifier option') + expect(() => new JWK(ECPublicJWK, { kid: '1' })).to.throw('Conflicting \'kid\' option') }) it('should not throw if kid passed in twice but not conflicting', () => { @@ -99,7 +101,7 @@ describe('JWK', () => { }) it('should throw if kid is not present on data or options', () => { - expect(() => new JWK(ECPublicJWKNoKid, {})).to.throw('Valid JWA key identifier required for JWK') + expect(() => new JWK(ECPublicJWKNoKid, {})).to.throw('Valid \'kid\' required for JWK') }) it('should copy non-standard key metadata', () => { @@ -111,10 +113,43 @@ describe('JWK', () => { describe('importKey', () => { + it('should return a promise', () => { + return JWK.importKey(ECPrivateJWK).should.be.fulfilled + }) + + it('should resolve an instance of JWK', () => { + return JWK.importKey(ECPrivateJWK).should.eventually.be.an.instanceOf(JWK) + }) + + it('should have a cryptoKey property', () => { + return JWK.importKey(ECPrivateJWK).then(jwk => { + jwk.should.haveOwnProperty('cryptoKey') + }) + }) }) describe('fromCryptoKey', () => { + let cryptoKey + + before(() => { + return JWK.importKey(ECPrivateJWK).then(jwk => { + cryptoKey = jwk.cryptoKey + }) + }) + + it('should return a promise', () => { + return JWK.fromCryptoKey(cryptoKey, { alg: 'EC256', kid: 'abcd123$' }).should.eventually.be.an.instanceOf(JWK) + }) + it('should resolve an instance of JWK', () => { + return JWK.fromCryptoKey(cryptoKey, { alg: 'EC256', kid: 'abcd123$' }).should.be.fulfilled + }) + + it('should have a cryptoKey property', () => { + return JWK.fromCryptoKey(cryptoKey, { alg: 'EC256', kid: 'abcd123$' }).then(jwk => { + jwk.should.haveOwnProperty('cryptoKey') + }) + }) }) describe('sign', () => { @@ -169,6 +204,19 @@ describe('JWK', () => { data.should.haveOwnProperty('ciphertext') data.should.haveOwnProperty('iv') data.should.haveOwnProperty('tag') + data.should.not.haveOwnProperty('aad') + }) + }) + + describe('with aad', () => { + + it('should resolve encrypted data', () => { + return jwk.encrypt(plainTextData, plainTextAad).then(data => { + data.should.haveOwnProperty('ciphertext') + data.should.haveOwnProperty('iv') + data.should.haveOwnProperty('tag') + data.should.haveOwnProperty('aad') + }) }) }) }) @@ -182,12 +230,25 @@ describe('JWK', () => { it('should return a promise', () => { let { ciphertext, iv, tag } = encryptedData - return jwk.decrypt(ciphertext, iv, tag, Buffer.from('')).should.be.fulfilled + return jwk.decrypt(ciphertext, iv, tag).should.be.fulfilled }) it('should resolve plain text data', () => { let { ciphertext, iv, tag } = encryptedData - return jwk.decrypt(ciphertext, iv, tag, Buffer.from('')).should.eventually.equal(plainTextData) + return jwk.decrypt(ciphertext, iv, tag).should.eventually.equal(plainTextData) + }) + + describe('with aad', () => { + + it('should reject if aad is omitted', () => { + let { ciphertext, iv, tag } = encryptedDataWithAad + return jwk.decrypt(ciphertext, iv, tag).should.be.rejected + }) + + it('should resolve plain text data', () => { + let { ciphertext, iv, tag, aad } = encryptedDataWithAad + return jwk.decrypt(ciphertext, iv, tag, aad).should.eventually.equal(plainTextData) + }) }) }) })