jose/test/jws/sanity.test.js
2019-07-21 18:05:57 +02:00

290 lines
10 KiB
JavaScript

const test = require('ava')
const base64url = require('../../lib/help/base64url')
const { JWKS, JWK: { generateSync }, JWS, errors } = require('../..')
test('algorithms option be an array of strings', t => {
;[{}, new Object(), false, null, Infinity, 0, '', Buffer.from('foo')].forEach((val) => { // eslint-disable-line no-new-object
t.throws(() => {
JWS.verify({
header: { alg: 'HS256' },
payload: 'foo',
signature: 'bar'
}, generateSync('oct'), { algorithms: val })
}, { instanceOf: TypeError, message: '"algorithms" option must be an array of non-empty strings' })
t.throws(() => {
JWS.verify({
header: { alg: 'HS256' },
payload: 'foo',
signature: 'bar'
}, generateSync('oct'), { algorithms: [val] })
}, { instanceOf: TypeError, message: '"algorithms" option must be an array of non-empty strings' })
})
})
test('compact parts length check', t => {
t.throws(() => {
JWS.verify('', generateSync('oct'))
}, { instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS malformed or invalid serialization' })
t.throws(() => {
JWS.verify('.', generateSync('oct'))
}, { instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS malformed or invalid serialization' })
t.throws(() => {
JWS.verify('...', generateSync('oct'))
}, { instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS malformed or invalid serialization' })
})
test('verify key or store argument', t => {
;[{}, new Object(), false, null, Infinity, 0, Buffer.from('foo')].forEach((val) => { // eslint-disable-line no-new-object
t.throws(() => {
JWS.verify('..', val)
}, { instanceOf: TypeError, message: 'key must be an instance of a key instantiated by JWK.asKey or a JWKS.KeyStore' })
})
})
test('JWS sign accepts buffer', t => {
const k = generateSync('oct')
JWS.sign(Buffer.from('foo'), k)
t.pass()
})
test('JWS sign accepts string', t => {
const k = generateSync('oct')
JWS.sign('foo', k)
t.pass()
})
test('JWS sign accepts object', t => {
const k = generateSync('oct')
JWS.sign({}, k)
t.pass()
})
test('JWS sign rejects other', t => {
const k = generateSync('oct')
;[[], false, true, undefined, null, Infinity, 0].forEach((val) => {
t.throws(() => {
JWS.sign(val, k)
}, { instanceOf: TypeError, message: 'payload argument must be a Buffer, string or an object' })
})
})
test('JWS sign rejects non keys', t => {
;[[], false, true, undefined, null, Infinity, 0].forEach((val) => {
t.throws(() => {
JWS.sign('foo', val)
}, { instanceOf: TypeError, message: 'key must be an instance of a key instantiated by JWK.asKey' })
})
})
test('JWS sign protectedHeader rejects non objects or undefined', t => {
const k = generateSync('oct')
;[[], false, true, null, Infinity, 0, Buffer.from('foo')].forEach((val) => {
t.throws(() => {
JWS.sign('foo', k, val)
}, { instanceOf: TypeError, message: 'protectedHeader argument must be a plain object when provided' })
})
})
test('JWS sign unprotectedHeader rejects non objects or undefined', t => {
const k = generateSync('oct')
const sign = new JWS.Sign('foo')
;[[], false, true, null, Infinity, 0, Buffer.from('foo')].forEach((val) => {
t.throws(() => {
sign.recipient(k, undefined, val)
}, { instanceOf: TypeError, message: 'unprotectedHeader argument must be a plain object when provided' })
})
})
test('JWS prot and unprot headers must be disjoint', t => {
const k = generateSync('oct')
const sign = new JWS.Sign('foo')
t.throws(() => {
sign.recipient(k, { foo: 1 }, { foo: 0 })
}, { instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS Protected and JWS Unprotected Header Parameter names must be disjoint' })
})
test('JWS must have recipients', t => {
const sign = new JWS.Sign('foo')
t.throws(() => {
sign.sign('compact')
}, { instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'missing recipients' })
})
test('JWS valid serialization must be provided', t => {
;[[], false, true, null, Infinity, 0, 'foo', ''].forEach((val) => {
const sign = new JWS.Sign('foo')
t.throws(() => {
sign.sign(val)
}, { instanceOf: TypeError, message: 'serialization must be one of "compact", "flattened", "general"' })
})
})
test('JWS compact does not support multiple recipients', t => {
const k = generateSync('oct')
const k2 = generateSync('EC')
const sign = new JWS.Sign('foo')
sign.recipient(k)
sign.recipient(k2)
t.throws(() => {
sign.sign('compact')
}, { instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS Compact Serialization doesn\'t support multiple recipients or JWS unprotected headers' })
})
test('JWS compact does not support unprotected header', t => {
const k = generateSync('oct')
const sign = new JWS.Sign('foo')
sign.recipient(k, undefined, { foo: 1 })
t.throws(() => {
sign.sign('compact')
}, { instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS Compact Serialization doesn\'t support multiple recipients or JWS unprotected headers' })
})
test('JWS flattened does not support multiple recipients', t => {
const k = generateSync('oct')
const k2 = generateSync('EC')
const sign = new JWS.Sign('foo')
sign.recipient(k)
sign.recipient(k2)
t.throws(() => {
sign.sign('flattened')
}, { instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'Flattened JWS JSON Serialization doesn\'t support multiple recipients' })
})
test('JWS no alg specified but cannot resolve', t => {
const k1 = generateSync('RSA', undefined, { alg: 'foo' })
t.throws(() => {
JWS.sign({}, k1)
}, { instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'could not resolve a usable "alg" for a recipient' })
})
test('JWS verify must be able to parse the protected header', t => {
const k = generateSync('oct')
const sign = new JWS.Sign('foo').recipient(k)
const jws = sign.sign('flattened')
jws.protected = jws.protected.substr(1)
t.throws(() => {
JWS.verify(jws, k)
}, { instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'could not parse JWS protected header' })
})
test('JWS verify must have disjoint header members', t => {
const k = generateSync('oct')
const sign = new JWS.Sign('foo').recipient(k)
const jws = sign.sign('flattened')
jws.header = { alg: 'HS256' }
t.throws(() => {
JWS.verify(jws, k)
}, { instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS Protected and JWS Unprotected Header Parameter names must be disjoint' })
})
test('JWS no alg specified (multi recipient)', t => {
const sign = new JWS.Sign({})
sign.recipient(generateSync('RSA'))
sign.recipient(generateSync('EC'))
sign.recipient(generateSync('oct', 256))
const jws = sign.sign('general')
t.deepEqual(base64url.JSON.decode(jws.signatures[0].protected), { alg: 'PS256' })
t.deepEqual(base64url.JSON.decode(jws.signatures[1].protected), { alg: 'ES256' })
t.deepEqual(base64url.JSON.decode(jws.signatures[2].protected), { alg: 'HS256' })
})
test('JWS no alg specified (multi recipient) with per-recipient protected headers', t => {
const sign = new JWS.Sign({})
const k1 = generateSync('RSA', undefined, { kid: 'kid_1' })
sign.recipient(k1, { kid: k1.kid })
const k2 = generateSync('EC', undefined, { kid: 'kid_2' })
sign.recipient(k2, { kid: k2.kid })
const k3 = generateSync('oct', undefined, { kid: 'kid_3' })
sign.recipient(k3, { kid: k3.kid })
const jws = sign.sign('general')
t.deepEqual(base64url.JSON.decode(jws.signatures[0].protected), { alg: 'PS256', kid: 'kid_1' })
t.deepEqual(base64url.JSON.decode(jws.signatures[1].protected), { alg: 'ES256', kid: 'kid_2' })
t.deepEqual(base64url.JSON.decode(jws.signatures[2].protected), { alg: 'HS256', kid: 'kid_3' })
})
test('JWS keystore match multi but fails with verification error', t => {
const k = generateSync('oct')
const ks = new JWKS.KeyStore(generateSync('oct'), generateSync('oct'))
const jws = JWS.sign({}, k)
t.throws(() => {
JWS.verify(jws, ks)
}, { instanceOf: errors.JWSVerificationFailed, code: 'ERR_JWS_VERIFICATION_FAILED' })
})
test('JWS general fails with decryption error', t => {
const k = generateSync('oct')
const k2 = generateSync('oct')
const k3 = generateSync('oct')
const sign = new JWS.Sign('foo')
sign.recipient(k)
sign.recipient(k2)
const jws = sign.sign('general')
t.throws(() => {
JWS.verify(jws, k3)
}, { instanceOf: errors.JWSVerificationFailed, code: 'ERR_JWS_VERIFICATION_FAILED' })
})
test('JWS verify algorithms whitelist', t => {
const k = generateSync('oct')
const jws = JWS.sign({}, k, { alg: 'HS512' })
JWS.verify(jws, k, { algorithms: ['HS256', 'HS512'] })
t.throws(() => {
JWS.verify(jws, k, { algorithms: ['RS256'] })
}, { instanceOf: errors.JOSEAlgNotWhitelisted, code: 'ERR_JOSE_ALG_NOT_WHITELISTED', message: 'alg not whitelisted' })
})
test('JWS verify algorithms whitelist (with keystore)', t => {
const k = generateSync('oct')
const k2 = generateSync('oct')
const ks = new JWKS.KeyStore(k, k2)
const jws = JWS.sign({}, k2, { alg: 'HS512' })
JWS.verify(jws, ks, { algorithms: ['HS256', 'HS512'] })
t.throws(() => {
JWS.verify(jws, ks, { algorithms: ['RS256'] })
}, { instanceOf: errors.JOSEAlgNotWhitelisted, code: 'ERR_JOSE_ALG_NOT_WHITELISTED', message: 'alg not whitelisted' })
})
test('JWS verify algorithms whitelist (multi-recipient)', t => {
const k = generateSync('oct')
const k2 = generateSync('RSA')
const sign = new JWS.Sign({})
sign.recipient(k)
sign.recipient(k2)
const jws = sign.sign('general')
JWS.verify(jws, k, { algorithms: ['HS256', 'PS256'] })
JWS.verify(jws, k2, { algorithms: ['HS256', 'PS256'] })
let err
err = t.throws(() => {
JWS.verify(jws, k, { algorithms: ['RS256'] })
}, { instanceOf: errors.JOSEMultiError, code: 'ERR_JOSE_MULTIPLE_ERRORS' })
;[...err].forEach((e, i) => {
if (i === 0) {
t.is(e.constructor, errors.JOSEAlgNotWhitelisted)
} else {
t.is(e.constructor, errors.JOSEAlgNotWhitelisted)
}
})
err = t.throws(() => {
JWS.verify(jws, k2, { algorithms: ['HS256'] })
}, { instanceOf: errors.JOSEMultiError, code: 'ERR_JOSE_MULTIPLE_ERRORS' })
;[...err].forEach((e, i) => {
if (i === 0) {
t.is(e.constructor, errors.JWKKeySupport)
} else {
t.is(e.constructor, errors.JOSEAlgNotWhitelisted)
}
})
})