refactor: parse a Protected Header in one place

Add parseJoseHeader and use it to decode and validate protected headers
for JWS, JWE, and decodeProtectedHeader.

Signed-off-by: Filip Skokan <[email protected]>
This commit is contained in:
Filip Skokan
2026-07-29 18:52:03 +02:00
parent 474c6d39be
commit 3f50c6dadc
4 changed files with 34 additions and 36 deletions
+20 -1
View File
@@ -1,5 +1,6 @@
import { decode } from '../util/base64url.js'
import { encode } from './buffer_utils.js'
import { encode, strictDecoder } from './buffer_utils.js'
import { isObject } from './type_checks.js'
export const unprotected: unique symbol = Symbol()
@@ -44,3 +45,21 @@ export async function digest(
const subtleDigest = `SHA-${algorithm.slice(-3)}`
return new Uint8Array(await crypto.subtle.digest(subtleDigest, data as Uint8Array<ArrayBuffer>))
}
/** Base64url decode, UTF-8 decode, JSON parse, and require a JSON object - in one place. */
export function parseJoseHeader<T>(
b64: string,
ErrorClass: new (message: string) => Error,
message: string,
): T {
let parsed: unknown
try {
parsed = JSON.parse(strictDecoder.decode(decode(b64)))
} catch {
throw new ErrorClass(message)
}
if (!isObject(parsed)) {
throw new ErrorClass(message)
}
return parsed as T
}
+3 -10
View File
@@ -1,11 +1,10 @@
import type * as types from '../types.d.ts'
import { decode as b64u } from '../util/base64url.js'
import { decrypt, generateCek } from './content_encryption.js'
import { decodeBase64url, encodeBase64url } from './helpers.js'
import { decodeBase64url, encodeBase64url, parseJoseHeader } from './helpers.js'
import { JOSEAlgNotAllowed, JOSENotSupported, JWEInvalid } from '../util/errors.js'
import { isDisjoint, isObject } from './type_checks.js'
import { decryptKeyManagement } from './key_management.js'
import { concat, decoder, encode, strictDecoder } from './buffer_utils.js'
import { concat, decoder, encode } from './buffer_utils.js'
import { validateCrit, validateAlgorithms, JWE_RECOGNIZED } from './options.js'
import { normalizeKey } from './normalize_key.js'
import { jweAlgorithm, jweEncryption } from './jwe_algorithms.js'
@@ -89,13 +88,7 @@ export function checkRecipient(jwe: types.FlattenedJWE): void {
export function shareJWE(jwe: types.FlattenedJWE): SharedJWE {
let parsedProt: types.JWEHeaderParameters | undefined
if (jwe.protected) {
try {
const protectedHeader = b64u(jwe.protected)
parsedProt = JSON.parse(strictDecoder.decode(protectedHeader))
if (!isObject(parsedProt)) throw new Error()
} catch {
throw new JWEInvalid('JWE Protected Header is invalid')
}
parsedProt = parseJoseHeader(jwe.protected, JWEInvalid, 'JWE Protected Header is invalid')
}
const protectedHeader: Uint8Array =
+4 -11
View File
@@ -1,11 +1,10 @@
import type * as types from '../types.d.ts'
import { decode as b64u } from '../util/base64url.js'
import { verify } from './signing.js'
import { jwsAlgorithm } from './jws_algorithms.js'
import { JOSEAlgNotAllowed, JWSInvalid, JWSSignatureVerificationFailed } from '../util/errors.js'
import { concat, decoder, encoder, encode, strictDecoder } from './buffer_utils.js'
import { decodeBase64url, encodeBase64url } from './helpers.js'
import { isDisjoint, isObject } from './type_checks.js'
import { concat, decoder, encoder, encode } from './buffer_utils.js'
import { decodeBase64url, encodeBase64url, parseJoseHeader } from './helpers.js'
import { isDisjoint } from './type_checks.js'
import { checkKeyType } from './check_key_type.js'
import { validateCrit, validateAlgorithms, JWS_RECOGNIZED } from './options.js'
import { normalizeKey } from './normalize_key.js'
@@ -71,13 +70,7 @@ export async function verifySignature(
): Promise<VerifiedSignature> {
let parsedProt: types.JWSHeaderParameters = {}
if (jws.protected) {
try {
const protectedHeader = b64u(jws.protected)
parsedProt = JSON.parse(strictDecoder.decode(protectedHeader))
if (!isObject(parsedProt)) throw new Error()
} catch {
throw new JWSInvalid('JWS Protected Header is invalid')
}
parsedProt = parseJoseHeader(jws.protected, JWSInvalid, 'JWS Protected Header is invalid')
}
let joseHeader: types.JWSHeaderParameters
+7 -14
View File
@@ -4,9 +4,7 @@
* @module
*/
import { decode as b64u } from './base64url.js'
import { strictDecoder } from '../lib/buffer_utils.js'
import { isObject } from '../lib/type_checks.js'
import { parseJoseHeader } from '../lib/helpers.js'
import type * as types from '../types.d.ts'
/** JWE and JWS Header Parameters */
@@ -43,16 +41,11 @@ export function decodeProtectedHeader(token: string | object): ProtectedHeaderPa
}
}
try {
if (typeof protectedB64u !== 'string' || !protectedB64u) {
throw new Error()
}
const result = JSON.parse(strictDecoder.decode(b64u(protectedB64u!)))
if (!isObject(result)) {
throw new Error()
}
return result as ProtectedHeaderParameters
} catch {
throw new TypeError('Invalid Token or Protected Header formatting')
const invalid = 'Invalid Token or Protected Header formatting'
if (typeof protectedB64u !== 'string' || !protectedB64u) {
throw new TypeError(invalid)
}
return parseJoseHeader<ProtectedHeaderParameters>(protectedB64u, TypeError, invalid)
}