From c7cde2383299870b6c88577a767aafe8efde2cd7 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 25 Aug 2026 17:09:48 -0700 Subject: [PATCH 1/3] Add mapJWSAlgToCOSEAlg() helper --- .../server/src/helpers/mapJWSAlgToCOSEAlg.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 packages/server/src/helpers/mapJWSAlgToCOSEAlg.ts diff --git a/packages/server/src/helpers/mapJWSAlgToCOSEAlg.ts b/packages/server/src/helpers/mapJWSAlgToCOSEAlg.ts new file mode 100644 index 00000000..6ef58c9d --- /dev/null +++ b/packages/server/src/helpers/mapJWSAlgToCOSEAlg.ts @@ -0,0 +1,28 @@ +import { COSEALG } from './cose.ts'; + +/** + * Map JWS algorithms to COSE algorithm IDs + * + * See https://www.rfc-editor.org/rfc/rfc7518.html#section-3.1 for possible values + */ +export function mapJWSAlgToCOSEAlg(alg: string): COSEALG { + let algCOSE: COSEALG; + + if (alg === 'ES256') { + algCOSE = COSEALG.ES256; + } else if (alg === 'ES384') { + algCOSE = COSEALG.ES384; + } else if (alg === 'ES512') { + algCOSE = COSEALG.ES512; + } else if (alg === 'RS256') { + algCOSE = COSEALG.RS256; + } else if (alg === 'RS384') { + algCOSE = COSEALG.RS384; + } else if (alg === 'RS512') { + algCOSE = COSEALG.RS512; + } else { + throw new Error(`Unable to map JWS algorithm "${alg}" to a COSE algorithm`); + } + + return algCOSE; +} From bd1c8a88121d6daac1dafd9fe3da754cde095f5d Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 25 Aug 2026 17:09:56 -0700 Subject: [PATCH 2/3] Use JWT's header.alg to determine hash algorithm --- packages/server/src/metadata/verifyJWT.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/server/src/metadata/verifyJWT.ts b/packages/server/src/metadata/verifyJWT.ts index 10762c3c..4759b7bf 100644 --- a/packages/server/src/metadata/verifyJWT.ts +++ b/packages/server/src/metadata/verifyJWT.ts @@ -1,6 +1,7 @@ import { convertX509PublicKeyToCOSE } from '../helpers/convertX509PublicKeyToCOSE.ts'; import { isoBase64URL, isoUint8Array } from '../helpers/iso/index.ts'; import { COSEALG, COSEKEYS, isCOSEPublicKeyEC2, isCOSEPublicKeyRSA } from '../helpers/cose.ts'; +import { mapJWSAlgToCOSEAlg } from '../helpers/mapJWSAlgToCOSEAlg.ts'; import { verifyEC2 } from '../helpers/iso/isoCrypto/verifyEC2.ts'; import { verifyRSA } from '../helpers/iso/isoCrypto/verifyRSA.ts'; import type { Uint8Array_ } from '../types/index.ts'; @@ -21,18 +22,24 @@ export function verifyJWT(jwt: string, leafCert: Uint8Array_): Promise const data = isoUint8Array.fromUTF8String(`${header}.${payload}`); const signatureBytes = isoBase64URL.toBuffer(signature); + // We just need the `alg` from the header, so only partially define the shape of it + const headerJSON: { alg: string } = JSON.parse(isoBase64URL.toUTF8String(header)); + + const jwtHeaderHashAlgCOSE = mapJWSAlgToCOSEAlg(headerJSON.alg); + if (isCOSEPublicKeyEC2(certCOSE)) { return verifyEC2({ data, signature: signatureBytes, cosePublicKey: certCOSE, - shaHashOverride: COSEALG.ES256, + shaHashOverride: jwtHeaderHashAlgCOSE, }); } else if (isCOSEPublicKeyRSA(certCOSE)) { return verifyRSA({ data, signature: signatureBytes, cosePublicKey: certCOSE, + shaHashOverride: jwtHeaderHashAlgCOSE, }); } From c6187b591574445826e7d45b5a0cde8b5673b1fa Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 25 Aug 2026 17:18:15 -0700 Subject: [PATCH 3/3] Add tests for mapJWSAlgToCOSEAlg() --- .../src/helpers/mapJWSAlgToCOSEAlg.test.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 packages/server/src/helpers/mapJWSAlgToCOSEAlg.test.ts diff --git a/packages/server/src/helpers/mapJWSAlgToCOSEAlg.test.ts b/packages/server/src/helpers/mapJWSAlgToCOSEAlg.test.ts new file mode 100644 index 00000000..953a58e6 --- /dev/null +++ b/packages/server/src/helpers/mapJWSAlgToCOSEAlg.test.ts @@ -0,0 +1,33 @@ +import { assertEquals, assertThrows } from '@std/assert'; +import { mapJWSAlgToCOSEAlg } from './mapJWSAlgToCOSEAlg.ts'; +import { COSEALG } from './cose.ts'; + +Deno.test('should map "ES256" to -7', () => { + assertEquals(mapJWSAlgToCOSEAlg('ES256'), COSEALG.ES256); +}); + +Deno.test('should map "ES384" to -35', () => { + assertEquals(mapJWSAlgToCOSEAlg('ES384'), COSEALG.ES384); +}); + +Deno.test('should map "ES512" to -36', () => { + assertEquals(mapJWSAlgToCOSEAlg('ES512'), COSEALG.ES512); +}); + +Deno.test('should map "RS256" to -257', () => { + assertEquals(mapJWSAlgToCOSEAlg('RS256'), COSEALG.RS256); +}); + +Deno.test('should map "RS384" to -7', () => { + assertEquals(mapJWSAlgToCOSEAlg('RS384'), COSEALG.RS384); +}); + +Deno.test('should map "RS512" to -7', () => { + assertEquals(mapJWSAlgToCOSEAlg('RS512'), COSEALG.RS512); +}); + +Deno.test('should raise on unsupported JWS alg', () => { + assertThrows(() => { + mapJWSAlgToCOSEAlg('FOOALG'); + }); +});