Skip to content

fix(encryption): read alg/enc exclusively from protected header in JWEDecrypter#658

Open
rossaddison wants to merge 1 commit into
web-token:4.2.xfrom
rossaddison:fix/algorithm-confusion-jws-jwe
Open

fix(encryption): read alg/enc exclusively from protected header in JWEDecrypter#658
rossaddison wants to merge 1 commit into
web-token:4.2.xfrom
rossaddison:fix/algorithm-confusion-jws-jwe

Conversation

@rossaddison

Copy link
Copy Markdown

Summary

JWEDecrypter::decryptRecipientKey() builds $completeHeader by merging the shared protected header, the shared unprotected header, and the per-recipient unprotected header via array_merge(). Because array_merge() exhibits last-wins behaviour for duplicate string keys, an attacker can override the integrity-protected alg or enc parameters by placing different values in an unprotected header field.

This creates a TOCTOU split:

  • HeaderCheckerManager validates alg/enc from the protected header
  • getKeyEncryptionAlgorithm() / getContentEncryptionAlgorithm() previously resolved the algorithm from the merged header where unprotected values win

Related advisory: #114

What this PR does NOT change

JWSVerifier::getAlgorithm() already reads alg exclusively from getProtectedHeader() and is not affected.

Fix

Pass $jwe->getSharedProtectedHeader() — the integrity-protected portion — to both getKeyEncryptionAlgorithm() and getContentEncryptionAlgorithm() instead of the merged $completeHeader.

The merged $completeHeader is still forwarded to decryptCEK() unchanged, because ECDH-ES parameters (epk, apu, apv) may legitimately reside in per-recipient unprotected headers per RFC 7516 §4.6.

is_string() guards are added in both getter methods so a malformed non-string header value cannot reach the AlgorithmManager.

RFC references

  • RFC 7516 §4.1.1 — alg MUST be integrity-protected
  • RFC 7516 §4.1.2 — enc MUST be integrity-protected

Testing

Please run the existing JWE test suite. A targeted regression test demonstrating the override scenario would be a welcome addition from the maintainers.

…EDecrypter

RFC 7516 §4.1.1 (alg) and §4.1.2 (enc) require both parameters to be
integrity-protected. The previous implementation passed the merged
$completeHeader (sharedProtectedHeader + sharedHeader + recipientHeader)
to getKeyEncryptionAlgorithm() and getContentEncryptionAlgorithm(). Because
array_merge() exhibits last-wins behaviour, an attacker could override alg
or enc by placing a different value in an unprotected header field, creating
a TOCTOU split between HeaderCheckerManager validation and actual decryption.

Fix: extract alg/enc exclusively from getSharedProtectedHeader(). The merged
$completeHeader is still forwarded to decryptCEK() because ECDH parameters
(epk, apu, apv) may legitimately reside in per-recipient unprotected headers.

Also adds is_string() guards in both getter methods so a malformed non-string
header value cannot reach the AlgorithmManager.

Note: JWSVerifier::getAlgorithm() already reads alg from getProtectedHeader()
only and is not affected.

Fixes web-token#114
@Spomky Spomky self-assigned this Jun 20, 2026
@Spomky Spomky added this to the 4.2.0 milestone Jun 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants