1818use Jose \Component \Encryption \Algorithm \KeyEncryption \KeyWrapping ;
1919use Jose \Component \Encryption \Algorithm \KeyEncryptionAlgorithm ;
2020use Throwable ;
21+ use function count ;
2122use function is_string ;
2223use function sprintf ;
2324use function strlen ;
@@ -109,6 +110,15 @@ public function decryptUsingKeySet(
109110 return false ;
110111 }
111112
113+ /**
114+ * The header parameter names of the shared protected header, the shared unprotected header and the
115+ * per-recipient header must be disjoint (RFC 7516 section 7.2.1), as enforced by the JWEBuilder when the
116+ * token is created. Otherwise an unprotected parameter is able to redefine a protected one. The headers
117+ * are then merged in the same order as the JWEBuilder does, so that the protected header always wins.
118+ *
119+ * The shared unprotected header is never a valid source for "alg" and "enc": it is not covered by the
120+ * AAD and, unlike the per-recipient header, nothing requires those parameters to be located there.
121+ */
112122 private function decryptRecipientKey (
113123 JWE $ jwe ,
114124 JWKSet $ jwkset ,
@@ -117,15 +127,20 @@ private function decryptRecipientKey(
117127 ?JWK $ senderKey = null
118128 ): ?string {
119129 $ recipient = $ jwe ->getRecipient ($ i );
120- $ completeHeader = array_merge (
121- $ jwe ->getSharedProtectedHeader (),
122- $ jwe ->getSharedHeader (),
123- $ recipient ->getHeader ()
124- );
130+ $ sharedProtectedHeader = $ jwe ->getSharedProtectedHeader ();
131+ $ sharedHeader = $ jwe ->getSharedHeader ();
132+ $ recipientHeader = $ recipient ->getHeader ();
133+
134+ $ this ->checkDuplicatedHeaderParameters ($ sharedProtectedHeader , $ sharedHeader );
135+ $ this ->checkDuplicatedHeaderParameters ($ sharedProtectedHeader , $ recipientHeader );
136+ $ this ->checkDuplicatedHeaderParameters ($ sharedHeader , $ recipientHeader );
137+
138+ $ completeHeader = array_merge ($ sharedHeader , $ recipientHeader , $ sharedProtectedHeader );
125139 $ this ->checkCompleteHeader ($ completeHeader );
126140
127- $ key_encryption_algorithm = $ this ->getKeyEncryptionAlgorithm ($ completeHeader );
128- $ content_encryption_algorithm = $ this ->getContentEncryptionAlgorithm ($ completeHeader );
141+ $ protectedAndRecipientHeader = array_merge ($ recipientHeader , $ sharedProtectedHeader );
142+ $ key_encryption_algorithm = $ this ->getKeyEncryptionAlgorithm ($ protectedAndRecipientHeader );
143+ $ content_encryption_algorithm = $ this ->getContentEncryptionAlgorithm ($ protectedAndRecipientHeader );
129144
130145 $ this ->checkIvSize ($ jwe ->getIV (), $ content_encryption_algorithm ->getIVSize ());
131146
@@ -253,29 +268,52 @@ private function checkCompleteHeader(array $completeHeaders): void
253268 }
254269 }
255270
256- private function getKeyEncryptionAlgorithm (array $ completeHeaders ): KeyEncryptionAlgorithm
271+ private function getKeyEncryptionAlgorithm (array $ header ): KeyEncryptionAlgorithm
257272 {
258- $ key_encryption_algorithm = $ this ->keyEncryptionAlgorithmManager ->get ($ completeHeaders ['alg ' ]);
273+ $ alg = $ header ['alg ' ] ?? null ;
274+ if (! is_string ($ alg ) || $ alg === '' ) {
275+ throw new InvalidArgumentException (
276+ 'The "alg" parameter must be a non-empty string set in the protected header or in the recipient header. '
277+ );
278+ }
279+ $ key_encryption_algorithm = $ this ->keyEncryptionAlgorithmManager ->get ($ alg );
259280 if (! $ key_encryption_algorithm instanceof KeyEncryptionAlgorithm) {
260281 throw new InvalidArgumentException (sprintf (
261282 'The key encryption algorithm "%s" is not supported or does not implement KeyEncryptionAlgorithm interface. ' ,
262- $ completeHeaders [ ' alg ' ]
283+ $ alg
263284 ));
264285 }
265286
266287 return $ key_encryption_algorithm ;
267288 }
268289
269- private function getContentEncryptionAlgorithm (array $ completeHeader ): ContentEncryptionAlgorithm
290+ private function getContentEncryptionAlgorithm (array $ header ): ContentEncryptionAlgorithm
270291 {
271- $ content_encryption_algorithm = $ this ->contentEncryptionAlgorithmManager ->get ($ completeHeader ['enc ' ]);
292+ $ enc = $ header ['enc ' ] ?? null ;
293+ if (! is_string ($ enc ) || $ enc === '' ) {
294+ throw new InvalidArgumentException (
295+ 'The "enc" parameter must be a non-empty string set in the protected header or in the recipient header. '
296+ );
297+ }
298+ $ content_encryption_algorithm = $ this ->contentEncryptionAlgorithmManager ->get ($ enc );
272299 if (! $ content_encryption_algorithm instanceof ContentEncryptionAlgorithm) {
273300 throw new InvalidArgumentException (sprintf (
274- 'The key encryption algorithm "%s" is not supported or does not implement the ContentEncryption interface. ' ,
275- $ completeHeader [ ' enc ' ]
301+ 'The content encryption algorithm "%s" is not supported or does not implement the ContentEncryption interface. ' ,
302+ $ enc
276303 ));
277304 }
278305
279306 return $ content_encryption_algorithm ;
280307 }
308+
309+ private function checkDuplicatedHeaderParameters (array $ header1 , array $ header2 ): void
310+ {
311+ $ inter = array_intersect_key ($ header1 , $ header2 );
312+ if (count ($ inter ) !== 0 ) {
313+ throw new InvalidArgumentException (sprintf (
314+ 'The header contains duplicated entries: %s. ' ,
315+ implode (', ' , array_keys ($ inter ))
316+ ));
317+ }
318+ }
281319}
0 commit comments