Files
apophis-fastify/src/test/cli/protocol-conformance-p2.test.ts
T

264 lines
8.5 KiB
TypeScript

/**
* P2 Protocol Conformance Tests
*
* Additional test vectors for JWT (RS256, ES256), HTTP Signature edge cases,
* and X.509/SPIFFE strictness beyond the base protocol-extensions.test.ts.
*/
import { test } from 'node:test'
import assert from 'node:assert'
import { createSign, generateKeyPairSync } from 'node:crypto'
import { jwtExtension } from '../../extensions/jwt.js'
import { httpSignatureExtension } from '../../extensions/http-signature.js'
import type { PredicateContext } from '../../extension/types.js'
const makeCtx = (overrides: Partial<PredicateContext['evalContext']> = {}): PredicateContext['evalContext'] => ({
request: {
body: undefined,
headers: {},
query: {},
params: {},
},
response: {
body: undefined,
headers: {},
statusCode: 200,
},
...overrides,
} as PredicateContext['evalContext'])
const makeRoute = () => ({
path: '/test',
method: 'GET' as const,
category: 'observer' as const,
requires: [],
ensures: [],
invariants: [],
regexPatterns: {},
validateRuntime: true,
})
// ============================================================================
// JWT: RS256 and ES256 verification vectors
// ============================================================================
test('jwt: validates RS256 signature with RSA public key', () => {
const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })
const payload = { sub: 'user-123', iat: Math.floor(Date.now() / 1000) }
const header = { alg: 'RS256', typ: 'JWT' }
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url')
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url')
const signingInput = `${encodedHeader}.${encodedPayload}`
const signer = createSign('RSA-SHA256')
signer.update(signingInput)
signer.end()
const signature = signer.sign(privateKey).toString('base64url')
const token = `${signingInput}.${signature}`
const ext = jwtExtension({
keys: { default: publicKey.export({ type: 'spki', format: 'pem' }).toString() },
verify: true,
})
const state = ext.onSuiteStart!({}) as Record<string, unknown>
const ctx: PredicateContext = {
route: makeRoute(),
evalContext: makeCtx({
request: {
body: undefined,
headers: { authorization: `Bearer ${token}` },
query: {},
params: {},
},
}),
accessor: [],
extensionState: state,
}
const result = ext.predicates!.jwt_valid!(ctx)
assert.ok(result.success)
assert.strictEqual(result.value, true)
})
test('jwt: rejects RS256 token with wrong public key', () => {
const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })
const { publicKey: wrongPublicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })
const payload = { sub: 'user-123', iat: Math.floor(Date.now() / 1000) }
const header = { alg: 'RS256', typ: 'JWT' }
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url')
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url')
const signingInput = `${encodedHeader}.${encodedPayload}`
const signer = createSign('RSA-SHA256')
signer.update(signingInput)
signer.end()
const signature = signer.sign(privateKey).toString('base64url')
const token = `${signingInput}.${signature}`
const ext = jwtExtension({
keys: { default: wrongPublicKey.export({ type: 'spki', format: 'pem' }).toString() },
verify: true,
})
const state = ext.onSuiteStart!({}) as Record<string, unknown>
const ctx: PredicateContext = {
route: makeRoute(),
evalContext: makeCtx({
request: {
body: undefined,
headers: { authorization: `Bearer ${token}` },
query: {},
params: {},
},
}),
accessor: [],
extensionState: state,
}
const result = ext.predicates!.jwt_valid!(ctx)
assert.ok(result.success)
assert.strictEqual(result.value, false)
})
test('jwt: validates ES256 signature with EC public key', () => {
const { privateKey, publicKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' })
const payload = { sub: 'user-123', iat: Math.floor(Date.now() / 1000) }
const header = { alg: 'ES256', typ: 'JWT' }
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url')
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url')
const signingInput = `${encodedHeader}.${encodedPayload}`
const signer = createSign('SHA256')
signer.update(signingInput)
signer.end()
const signature = signer.sign(privateKey).toString('base64url')
const token = `${signingInput}.${signature}`
const ext = jwtExtension({
keys: { default: publicKey.export({ type: 'spki', format: 'pem' }).toString() },
verify: true,
})
const state = ext.onSuiteStart!({}) as Record<string, unknown>
const ctx: PredicateContext = {
route: makeRoute(),
evalContext: makeCtx({
request: {
body: undefined,
headers: { authorization: `Bearer ${token}` },
query: {},
params: {},
},
}),
accessor: [],
extensionState: state,
}
const result = ext.predicates!.jwt_valid!(ctx)
assert.ok(result.success)
assert.strictEqual(result.value, true)
})
test('jwt: rejects ES256 token with wrong public key', () => {
const { privateKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' })
const { publicKey: wrongPublicKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' })
const payload = { sub: 'user-123', iat: Math.floor(Date.now() / 1000) }
const header = { alg: 'ES256', typ: 'JWT' }
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url')
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url')
const signingInput = `${encodedHeader}.${encodedPayload}`
const signer = createSign('SHA256')
signer.update(signingInput)
signer.end()
const signature = signer.sign(privateKey).toString('base64url')
const token = `${signingInput}.${signature}`
const ext = jwtExtension({
keys: { default: wrongPublicKey.export({ type: 'spki', format: 'pem' }).toString() },
verify: true,
})
const state = ext.onSuiteStart!({}) as Record<string, unknown>
const ctx: PredicateContext = {
route: makeRoute(),
evalContext: makeCtx({
request: {
body: undefined,
headers: { authorization: `Bearer ${token}` },
query: {},
params: {},
},
}),
accessor: [],
extensionState: state,
}
const result = ext.predicates!.jwt_valid!(ctx)
assert.ok(result.success)
assert.strictEqual(result.value, false)
})
// ============================================================================
// HTTP Signature: negative corpus and edge cases
// ============================================================================
test('httpSignature: rejects unsupported signature algorithm', () => {
const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })
const signatureInput = 'sig1=("@method")'
const signer = createSign('SHA512')
signer.update('dummy')
signer.end()
const signature = signer.sign(privateKey).toString('base64')
const ext = httpSignatureExtension()
const ctx: PredicateContext = {
route: makeRoute(),
evalContext: makeCtx({
request: {
body: undefined,
headers: {
signature: `sig1=:${signature}:`,
'signature-input': signatureInput,
},
query: {},
params: {},
},
}),
accessor: [],
extensionState: {},
}
const result = ext.predicates!.signature_valid!(ctx)
assert.ok(result.success)
assert.strictEqual(result.value, false)
})
test('httpSignature: rejects signature with mismatched label', () => {
const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })
const ext = httpSignatureExtension({ publicKey: publicKey.export({ type: 'spki', format: 'pem' }).toString() })
const signatureInput = 'sig1=("@method")'
const signer = createSign('SHA256')
signer.update('dummy')
signer.end()
const signature = signer.sign(privateKey).toString('base64')
const ctx: PredicateContext = {
route: makeRoute(),
evalContext: makeCtx({
request: {
body: undefined,
headers: {
signature: `sig2=:${signature}:`,
'signature-input': signatureInput,
},
query: {},
params: {},
},
}),
accessor: [],
extensionState: {},
}
const result = ext.predicates!.signature_valid!(ctx)
assert.ok(result.success)
assert.strictEqual(result.value, false)
})