From 6bb66d4f0b4c96f2da8ac5f14fda6bc4f53f2994 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 5 Nov 2019 20:07:45 +0100 Subject: [PATCH] feat: add JWS.verify encoding and parsing options --- docs/README.md | 4 ++++ lib/help/base64url.js | 14 +++++++------- lib/jws/verify.js | 12 ++++++++---- test/jws/encoding.test.js | 35 +++++++++++++++++++++++++++++++++++ types/index.d.ts | 14 +++++++++----- 5 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 test/jws/encoding.test.js diff --git a/docs/README.md b/docs/README.md index 39e5473eca..d9be0ad07a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1103,6 +1103,10 @@ Verifies the provided JWS in either serialization with a given `` or `< algorithms available on the keys - `complete`: `` When true returns a complete object with the parsed headers and payload instead of just the verified payload. **Default:** 'false' + - `parse`: `` When true attempts to JSON.parse the payload and falls back to returning + the payload encoded using the specified encoding. When false returns the payload as a Buffer. + **Default:** 'true' + - `encoding`: `string` The encoding to use for the payload. **Default:** 'utf8' - `crit`: `string[]` Array of Critical Header Parameter names to recognize. **Default:** '[]' - Returns: `` | `` diff --git a/lib/help/base64url.js b/lib/help/base64url.js index 07c875e517..c50759978c 100644 --- a/lib/help/base64url.js +++ b/lib/help/base64url.js @@ -26,24 +26,24 @@ const decodeToBuffer = (input) => { return Buffer.from(toBase64(input), 'base64') } -const decode = (input) => { - return decodeToBuffer(input).toString('utf8') +const decode = (input, encoding = 'utf8') => { + return decodeToBuffer(input).toString(encoding) } const b64uJSON = { encode: (input) => { return encode(JSON.stringify(input)) }, - decode: (input) => { - return JSON.parse(decode(input)) + decode: (input, encoding = 'utf8') => { + return JSON.parse(decode(input, encoding)) } } -b64uJSON.decode.try = (input) => { +b64uJSON.decode.try = (input, encoding = 'utf8') => { try { - return b64uJSON.decode(input) + return b64uJSON.decode(input, encoding) } catch (err) { - return decode(input) + return decode(input, encoding) } } diff --git a/lib/jws/verify.js b/lib/jws/verify.js index 823a2771b0..c75d556799 100644 --- a/lib/jws/verify.js +++ b/lib/jws/verify.js @@ -14,7 +14,7 @@ const SINGLE_RECIPIENT = new Set(['compact', 'flattened']) /* * @public */ -const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], complete = false, algorithms } = {}) => { +const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], complete = false, algorithms, parse = true, encoding = 'utf8' } = {}) => { if (!(key instanceof Key) && !(key instanceof KeyStore)) { throw new TypeError('key must be an instance of a key instantiated by JWK.asKey or a JWKS.KeyStore') } @@ -104,7 +104,7 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp const errs = [] for (const key of keys) { try { - return jwsVerify(true, serialization, jws, key, { crit, complete, algorithms: algorithms ? [...algorithms] : undefined }) + return jwsVerify(true, serialization, jws, key, { crit, complete, encoding, parse, algorithms: algorithms ? [...algorithms] : undefined }) } catch (err) { errs.push(err) continue @@ -127,7 +127,11 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp } if (!combinedHeader.crit || !combinedHeader.crit.includes('b64') || combinedHeader.b64) { - payload = base64url.JSON.decode.try(payload) + if (parse) { + payload = base64url.JSON.decode.try(payload, encoding) + } else { + payload = base64url.decodeToBuffer(payload) + } } if (complete) { @@ -145,7 +149,7 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp const errs = [] for (const recipient of signatures) { try { - return jwsVerify(false, 'flattened', { ...root, ...recipient }, key, { crit, complete, algorithms: algorithms ? [...algorithms] : undefined }) + return jwsVerify(false, 'flattened', { ...root, ...recipient }, key, { crit, complete, encoding, parse, algorithms: algorithms ? [...algorithms] : undefined }) } catch (err) { errs.push(err) continue diff --git a/test/jws/encoding.test.js b/test/jws/encoding.test.js new file mode 100644 index 0000000000..f0420b779d --- /dev/null +++ b/test/jws/encoding.test.js @@ -0,0 +1,35 @@ +const test = require('ava') + +const { randomBytes } = require('crypto') + +const { JWK, JWS } = require('../../lib') + +const key = JWK.generateSync('oct') + +test('JWS.verify can skip parsing the payload', t => { + let payload = 'foo' + let jws = JWS.sign(payload, key) + t.deepEqual(JWS.verify(jws, key, { parse: false }), Buffer.from(payload)) + + jws = JWS.sign.flattened(payload, key) + t.deepEqual(JWS.verify(jws, key, { parse: false }), Buffer.from(payload)) + t.deepEqual(JWS.verify(jws, key, { parse: false, complete: true }), { key, protected: { alg: 'HS256' }, payload: Buffer.from(payload) }) + + payload = randomBytes(8) + + jws = JWS.sign(payload, key) + t.deepEqual(JWS.verify(jws, key, { parse: false }), payload) + t.deepEqual(JWS.verify(jws, key, { parse: false, complete: true }), { key, protected: { alg: 'HS256' }, payload: payload }) +}) + +test('JWS.verify can parse any valid encoding', t => { + let jws = JWS.sign('foo', key) + t.deepEqual(JWS.verify(jws, key, { encoding: 'hex' }), '666f6f') + t.deepEqual(JWS.verify(jws, key, { encoding: 'base64' }), 'Zm9v') + + jws = JWS.sign.flattened('foo', key) + t.deepEqual(JWS.verify(jws, key, { encoding: 'hex' }), '666f6f') + t.deepEqual(JWS.verify(jws, key, { complete: true, encoding: 'hex' }), { key, protected: { alg: 'HS256' }, payload: '666f6f' }) + t.deepEqual(JWS.verify(jws, key, { encoding: 'base64' }), 'Zm9v') + t.deepEqual(JWS.verify(jws, key, { complete: true, encoding: 'base64' }), { key, protected: { alg: 'HS256' }, payload: 'Zm9v' }) +}) diff --git a/types/index.d.ts b/types/index.d.ts index 8f93a58617..2685b1666f 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -250,21 +250,25 @@ export namespace JWS { function general(payload: string | Buffer | object, key: JWK.Key, protected?: object, header?: object): GeneralJWS; } - interface VerifyOptions { + interface VerifyOptions { complete?: komplet; + parse?: parse; + encoding?: BufferEncoding; crit?: string[]; algorithms?: string[]; } - interface completeVerification { - payload: string | object; + interface completeVerification { + payload: T; key: JWK.Key; protected?: object; header?: object; } - function verify(jws: string | FlattenedJWS | GeneralJWS, key: JWK.Key | JWKS.KeyStore, options?: VerifyOptions): string | object; - function verify(jws: string | FlattenedJWS | GeneralJWS, key: JWK.Key | JWKS.KeyStore, options?: VerifyOptions): completeVerification; + function verify(jws: string | FlattenedJWS | GeneralJWS, key: JWK.Key | JWKS.KeyStore, options?: VerifyOptions): string | object; + function verify(jws: string | FlattenedJWS | GeneralJWS, key: JWK.Key | JWKS.KeyStore, options?: VerifyOptions): Buffer; + function verify(jws: string | FlattenedJWS | GeneralJWS, key: JWK.Key | JWKS.KeyStore, options?: VerifyOptions): completeVerification; + function verify(jws: string | FlattenedJWS | GeneralJWS, key: JWK.Key | JWKS.KeyStore, options?: VerifyOptions): completeVerification; } export namespace JWE {