Skip to content

Commit

Permalink
feat: add JWS.verify encoding and parsing options
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Nov 5, 2019
1 parent dfc91d8 commit 6bb66d4
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 16 deletions.
4 changes: 4 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,10 @@ Verifies the provided JWS in either serialization with a given `<JWK.Key>` or `<
algorithms available on the keys
- `complete`: `<boolean>` When true returns a complete object with the parsed headers and payload
instead of just the verified payload. **Default:** 'false'
- `parse`: `<boolean>` 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: `<string>` &vert; `<Object>`

Expand Down
14 changes: 7 additions & 7 deletions lib/help/base64url.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
12 changes: 8 additions & 4 deletions lib/jws/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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
Expand Down
35 changes: 35 additions & 0 deletions test/jws/encoding.test.js
Original file line number Diff line number Diff line change
@@ -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' })
})
14 changes: 9 additions & 5 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,21 +250,25 @@ export namespace JWS {
function general(payload: string | Buffer | object, key: JWK.Key, protected?: object, header?: object): GeneralJWS;
}

interface VerifyOptions<komplet> {
interface VerifyOptions<komplet = false, parse = true> {
complete?: komplet;
parse?: parse;
encoding?: BufferEncoding;
crit?: string[];
algorithms?: string[];
}

interface completeVerification {
payload: string | object;
interface completeVerification<T> {
payload: T;
key: JWK.Key;
protected?: object;
header?: object;
}

function verify(jws: string | FlattenedJWS | GeneralJWS, key: JWK.Key | JWKS.KeyStore, options?: VerifyOptions<false>): string | object;
function verify(jws: string | FlattenedJWS | GeneralJWS, key: JWK.Key | JWKS.KeyStore, options?: VerifyOptions<true>): 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<false, false>): Buffer;
function verify(jws: string | FlattenedJWS | GeneralJWS, key: JWK.Key | JWKS.KeyStore, options?: VerifyOptions<true>): completeVerification<string | object>;
function verify(jws: string | FlattenedJWS | GeneralJWS, key: JWK.Key | JWKS.KeyStore, options?: VerifyOptions<true, false>): completeVerification<Buffer>;
}

export namespace JWE {
Expand Down

0 comments on commit 6bb66d4

Please sign in to comment.