fix(encryption): read alg/enc exclusively from protected header in JWEDecrypter#658
fix(encryption): read alg/enc exclusively from protected header in JWEDecrypter#658rossaddison wants to merge 1 commit into
Conversation
…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
|
Thanks for the report and the detailed write-up. The underlying concern is legitimate: an unprotected header field must never be able to override an integrity-protected The blocking issue Reading This is exactly the RFC 7520 §5.13 vector we already test. Applying this PR makes With the The §4.1.1 "MUST be integrity-protected" requirement you cite holds for compact / single-recipient, but it does not apply to per-recipient Suggested direction The real flaw is the merge precedence in array_merge($sharedProtected, $sharedUnprotected, $recipientHeader) // unprotected wins, that's the bugA correct fix would:
Worth keeping from this PR
Could you rework it along those lines and add a regression test that (a) keeps RFC 7520 §5.13 green and (b) asserts that an unprotected |
| $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($completeHeader); | ||
| // RFC 7516 §4.1.1 (alg) and §4.1.2 (enc) require both parameters to be | ||
| // integrity-protected. Reading them from the merged $completeHeader allows | ||
| // an attacker to override either value via an unprotected header field. |
There was a problem hiding this comment.
Please remove these comments in the method.
Summary
JWEDecrypter::decryptRecipientKey()builds$completeHeaderby merging the shared protected header, the shared unprotected header, and the per-recipient unprotected header viaarray_merge(). Becausearray_merge()exhibits last-wins behaviour for duplicate string keys, an attacker can override the integrity-protectedalgorencparameters by placing different values in an unprotected header field.This creates a TOCTOU split:
HeaderCheckerManagervalidatesalg/encfrom the protected headergetKeyEncryptionAlgorithm()/getContentEncryptionAlgorithm()previously resolved the algorithm from the merged header where unprotected values winRelated advisory: #114
What this PR does NOT change
JWSVerifier::getAlgorithm()already readsalgexclusively fromgetProtectedHeader()and is not affected.Fix
Pass
$jwe->getSharedProtectedHeader()— the integrity-protected portion — to bothgetKeyEncryptionAlgorithm()andgetContentEncryptionAlgorithm()instead of the merged$completeHeader.The merged
$completeHeaderis still forwarded todecryptCEK()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 theAlgorithmManager.RFC references
algMUST be integrity-protectedencMUST be integrity-protectedTesting
Please run the existing JWE test suite. A targeted regression test demonstrating the override scenario would be a welcome addition from the maintainers.