Files
jose/test/jws/flattened.verify.test.ts
Filip Skokan 4d970fe995 refactor: validate each token once rather than once per layer
Route compact, flattened, general, and JWT operations through shared JWS
and JWE cores so each token is validated, parsed, and encoded once.
Consolidate critical-header and algorithm-option validation.

Signed-off-by: Filip Skokan <[email protected]>
2026-07-29 18:50:54 +02:00

177 lines
5.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'ava'
import * as crypto from 'crypto'
import { FlattenedSign, base64url, flattenedVerify } from '../../src/index.js'
test.before(async (t) => {
const encode = TextEncoder.prototype.encode.bind(new TextEncoder())
t.context.plaintext = encode('It’s a dangerous business, Frodo, going out your door.')
t.context.secret = crypto.randomFillSync(new Uint8Array(32))
})
test('JWS format validation', async (t) => {
const fullJws = await new FlattenedSign(t.context.plaintext)
.setProtectedHeader({ bar: 'baz' })
.setUnprotectedHeader({ alg: 'HS256' })
.sign(t.context.secret)
{
await t.throwsAsync(flattenedVerify(null, t.context.secret), {
message: 'Flattened JWS must be an object',
code: 'ERR_JWS_INVALID',
})
}
{
const jws = { ...fullJws }
jws.protected = undefined
jws.header = undefined
await t.throwsAsync(flattenedVerify(jws, t.context.secret), {
message: 'Flattened JWS must have either of the "protected" or "header" members',
code: 'ERR_JWS_INVALID',
})
}
{
await t.notThrowsAsync(async () => {
await flattenedVerify(
await new FlattenedSign(new Uint8Array())
.setProtectedHeader({ alg: 'HS256' })
.sign(t.context.secret),
t.context.secret,
)
})
}
{
const jws = { ...fullJws }
jws.signature = undefined
const assertion = {
message: 'JWS Signature missing or incorrect type',
code: 'ERR_JWS_INVALID',
}
await t.throwsAsync(flattenedVerify(jws, t.context.secret), assertion)
jws.signature = null
await t.throwsAsync(flattenedVerify(jws, t.context.secret), assertion)
}
{
const jws = { ...fullJws }
const assertion = {
message: 'JWS Protected Header incorrect type',
code: 'ERR_JWS_INVALID',
}
jws.protected = null
await t.throwsAsync(flattenedVerify(jws, t.context.secret), assertion)
}
{
const jws = { ...fullJws }
const assertion = {
message: 'JWS Unprotected Header incorrect type',
code: 'ERR_JWS_INVALID',
}
jws.header = null
await t.throwsAsync(flattenedVerify(jws, t.context.secret), assertion)
}
{
const jws = { ...fullJws }
const assertion = {
message: 'JWS Protected Header is invalid',
code: 'ERR_JWS_INVALID',
}
jws.protected = `1${jws.protected}`
await t.throwsAsync(flattenedVerify(jws, t.context.secret), assertion)
// RFC 7515 Section 4 - the Protected Header must be a JSON object. Without this a JSON
// string, number, null or array was accepted and returned as the protectedHeader.
for (const json of ['"foo"', '123', 'null', '[]']) {
await t.throwsAsync(
flattenedVerify({ ...jws, protected: base64url.encode(json) }, t.context.secret),
assertion,
)
}
}
{
const jws = { ...fullJws }
const assertion = {
message: 'JWS Payload missing',
code: 'ERR_JWS_INVALID',
}
jws.payload = undefined
await t.throwsAsync(flattenedVerify(jws, t.context.secret), assertion)
}
{
const jws = { ...fullJws }
jws.header = { alg: 'HS256', bar: 'bar' }
const assertion = {
message: 'JWS Protected and JWS Unprotected Header Parameter names must be disjoint',
code: 'ERR_JWS_INVALID',
}
await t.throwsAsync(flattenedVerify(jws, t.context.secret), assertion)
}
{
const jws = { ...fullJws }
jws.header = undefined
const assertion = {
message: 'JWS "alg" (Algorithm) Header Parameter missing or invalid',
code: 'ERR_JWS_INVALID',
}
await t.throwsAsync(flattenedVerify(jws, t.context.secret), assertion)
}
{
const jws = { ...fullJws }
jws.payload = null
const assertion = {
message: 'JWS Payload must be a string',
code: 'ERR_JWS_INVALID',
}
await t.throwsAsync(flattenedVerify(jws, t.context.secret), assertion)
}
{
const jws = { ...fullJws }
const assertion = {
message: 'signature verification failed',
code: 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED',
}
await t.throwsAsync(flattenedVerify(jws, crypto.randomFillSync(new Uint8Array(32))), assertion)
}
})
test('sign empty data', async (t) => {
const jws = await new FlattenedSign(new Uint8Array(0))
.setProtectedHeader({ alg: 'HS256' })
.sign(new Uint8Array(32))
t.is(jws.payload, '')
const { payload } = await flattenedVerify(jws, new Uint8Array(32))
t.is(payload.byteLength, 0)
})
test('a non-ASCII payload is a JWSInvalid', async (t) => {
// The payload segment is used to rebuild the signing input before it is base64url decoded.
const jws = await new FlattenedSign(new TextEncoder().encode('foo'))
.setProtectedHeader({ alg: 'HS256' })
.sign(new Uint8Array(32))
await t.throwsAsync(flattenedVerify({ ...jws, payload: '€' }, new Uint8Array(32)), {
code: 'ERR_JWS_INVALID',
message: 'The payload is not a valid base64url string',
})
// A well-formed but wrong payload still fails as a signature verification failure.
await t.throwsAsync(flattenedVerify({ ...jws, payload: 'YmFy' }, new Uint8Array(32)), {
code: 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED',
})
})