diff --git a/packages/w3c/websub/src/index.test.ts b/packages/w3c/websub/src/index.test.ts index b5134e7f..2acb7103 100644 --- a/packages/w3c/websub/src/index.test.ts +++ b/packages/w3c/websub/src/index.test.ts @@ -178,5 +178,17 @@ describe('WebSub helpers', () => { secret, signatureHeader: `sha256=${'0'.repeat(signature.length)}`, })).toBe(false); + + for (const malformed of [ + `sha256=${signature}zz`, + `sha256=${signature}f`, + `sha256=${signature}=ignored`, + ]) { + expect(verifyDistributionSignature({ + body, + secret, + signatureHeader: malformed, + })).toBe(false); + } }); }); diff --git a/packages/w3c/websub/src/index.ts b/packages/w3c/websub/src/index.ts index e3f143b9..7cd297a9 100644 --- a/packages/w3c/websub/src/index.ts +++ b/packages/w3c/websub/src/index.ts @@ -201,10 +201,13 @@ export function verifyDistributionSignature(input: { signatureHeader?: string | null; }): boolean { if (!input.signatureHeader) return false; - const [algorithm, signature] = input.signatureHeader.split('=', 2); + const match = /^([a-z0-9]+)=([0-9a-fA-F]+)$/.exec(input.signatureHeader); + const algorithm = match?.[1]; + const signature = match?.[2]; if (!algorithm || !signature || !SUPPORTED_SIGNATURE_ALGORITHMS.has(algorithm)) return false; const expected = createHmac(algorithm, input.secret).update(input.body).digest('hex'); + if (signature.length !== expected.length) return false; const expectedBytes = Buffer.from(expected, 'hex'); const actualBytes = Buffer.from(signature, 'hex'); return expectedBytes.length === actualBytes.length && timingSafeEqual(expectedBytes, actualBytes);