@@ -61,11 +61,13 @@ class JWT
61
61
* Decodes a JWT string into a PHP object.
62
62
*
63
63
* @param string $jwt The JWT
64
- * @param string |array|resource $key The key, or map of keys .
64
+ * @param Key |array<Key> $keyOrKeyArray The Key or array of Key objects .
65
65
* If the algorithm used is asymmetric, this is the public key
66
- * @param array $allowed_algs List of supported verification algorithms
66
+ * Each Key object contains an algorithm and matching key.
67
67
* Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
68
68
* 'HS512', 'RS256', 'RS384', and 'RS512'
69
+ * @param array $allowed_algs [DEPRECATED] List of supported verification algorithms. Only
70
+ * should be used for BC.
69
71
*
70
72
* @return object The JWT's payload as a PHP object
71
73
*
@@ -79,11 +81,11 @@ class JWT
79
81
* @uses jsonDecode
80
82
* @uses urlsafeB64Decode
81
83
*/
82
- public static function decode ($ jwt , $ key , array $ allowed_algs = array ())
84
+ public static function decode ($ jwt , $ keyOrKeyArray , array $ allowed_algs = array ())
83
85
{
84
86
$ timestamp = \is_null (static ::$ timestamp ) ? \time () : static ::$ timestamp ;
85
87
86
- if (empty ($ key )) {
88
+ if (empty ($ keyOrKeyArray )) {
87
89
throw new InvalidArgumentException ('Key may not be empty ' );
88
90
}
89
91
$ tks = \explode ('. ' , $ jwt );
@@ -106,36 +108,32 @@ public static function decode($jwt, $key, array $allowed_algs = array())
106
108
if (empty (static ::$ supported_algs [$ header ->alg ])) {
107
109
throw new UnexpectedValueException ('Algorithm not supported ' );
108
110
}
109
- if (!\in_array ($ header ->alg , $ allowed_algs )) {
110
- throw new UnexpectedValueException ('Algorithm not allowed ' );
111
+
112
+ list ($ keyMaterial , $ algorithm ) = self ::getKeyMaterialAndAlgorithm (
113
+ $ keyOrKeyArray ,
114
+ empty ($ header ->kid ) ? null : $ header ->kid
115
+ );
116
+
117
+ if (empty ($ algorithm )) {
118
+ // Use deprecated "allowed_algs" to determine if the algorithm is supported.
119
+ // This opens up the possibility of an attack in some implementations.
120
+ // @see https://github.com/firebase/php-jwt/issues/351
121
+ if (!\in_array ($ header ->alg , $ allowed_algs )) {
122
+ throw new UnexpectedValueException ('Algorithm not allowed ' );
123
+ }
124
+ } else {
125
+ // Check the algorithm
126
+ if (!self ::constantTimeEquals ($ algorithm , $ header ->alg )) {
127
+ // See issue #351
128
+ throw new UnexpectedValueException ('Incorrect key for this algorithm ' );
129
+ }
111
130
}
112
131
if ($ header ->alg === 'ES256 ' || $ header ->alg === 'ES384 ' ) {
113
132
// OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures
114
133
$ sig = self ::signatureToDER ($ sig );
115
134
}
116
135
117
- /** @var Keyring|JWTKey $key */
118
- $ key = self ::getKeyType ($ key , $ allowed_algs );
119
- if ($ key instanceof Keyring) {
120
- if (isset ($ header ->kid )) {
121
- if (!isset ($ key [$ header ->kid ])) {
122
- throw new UnexpectedValueException ('"kid" invalid, unable to lookup correct key ' );
123
- }
124
- $ key = $ key [$ header ->kid ];
125
- } else {
126
- throw new UnexpectedValueException ('"kid" empty, unable to lookup correct key ' );
127
- }
128
- }
129
- if (!($ key instanceof JWTKey)) {
130
- throw new UnexpectedValueException ('$key should be an instance of JWTKey ' );
131
- }
132
-
133
- // Check the signature
134
- if (!$ key ->isValidForAlg ($ header ->alg )) {
135
- // See issue #351
136
- throw new UnexpectedValueException ('Incorrect key for this algorithm ' );
137
- }
138
- if (!static ::verify ("$ headb64. $ bodyb64 " , $ sig , $ key ->getKeyMaterial (), $ header ->alg )) {
136
+ if (!static ::verify ("$ headb64. $ bodyb64 " , $ sig , $ keyMaterial , $ header ->alg )) {
139
137
throw new SignatureInvalidException ('Signature verification failed ' );
140
138
}
141
139
@@ -385,6 +383,47 @@ public static function urlsafeB64Encode($input)
385
383
return \str_replace ('= ' , '' , \strtr (\base64_encode ($ input ), '+/ ' , '-_ ' ));
386
384
}
387
385
386
+
387
+ /**
388
+ * Determine if an algorithm has been provided for each Key
389
+ *
390
+ * @param string|array $keyOrKeyArray
391
+ *
392
+ * @return an array containing the keyMaterial and algorithm
393
+ */
394
+ private static function getKeyMaterialAndAlgorithm ($ keyOrKeyArray , $ kid = null )
395
+ {
396
+ if (is_string ($ keyOrKeyArray )) {
397
+ return [$ keyOrKeyArray , null ];
398
+ }
399
+
400
+ if ($ keyOrKeyArray instanceof Key) {
401
+ return [$ keyOrKeyArray ->getKeyMaterial (), $ keyOrKeyArray ->getAlgorithm ()];
402
+ }
403
+
404
+ if (is_array ($ keyOrKeyArray ) || $ keyOrKeyArray instanceof ArrayAccess) {
405
+ if (!isset ($ kid )) {
406
+ throw new UnexpectedValueException ('"kid" empty, unable to lookup correct key ' );
407
+ }
408
+ if (!isset ($ keyOrKeyArray [$ kid ])) {
409
+ throw new UnexpectedValueException ('"kid" invalid, unable to lookup correct key ' );
410
+ }
411
+
412
+ $ key = $ keyOrKeyArray [$ kid ];
413
+
414
+ if ($ key instanceof Key) {
415
+ return [$ key ->getKeyMaterial (), $ key ->getAlgorithm ()];
416
+ }
417
+
418
+ return [$ key , null ];
419
+ }
420
+
421
+ throw new UnexpectedValueException (
422
+ '$keyOrKeyArray must be a string key, an array of string keys, '
423
+ . 'an instance of Firebase\JWT\Key key or an array of Firebase\JWT\Key keys '
424
+ );
425
+ }
426
+
388
427
/**
389
428
* @param string $left
390
429
* @param string $right
@@ -406,29 +445,6 @@ public static function constantTimeEquals($left, $right)
406
445
return ($ status === 0 );
407
446
}
408
447
409
- /**
410
- * @param string|array|ArrayAccess $oldType
411
- * @param string[] $algs
412
- * @return KeyInterface
413
- */
414
- public static function getKeyType ($ oldType , $ algs )
415
- {
416
- if ($ oldType instanceof KeyInterface) {
417
- return $ oldType ;
418
- }
419
- if (is_string ($ oldType )) {
420
- return new JWTKey ($ oldType , $ algs );
421
- }
422
- if (is_array ($ oldType ) || $ oldType instanceof ArrayAccess) {
423
- $ keyring = new Keyring (array ());
424
- foreach ($ oldType as $ kid => $ key ) {
425
- $ keyring [$ kid ] = new JWTKey ($ key , $ algs );
426
- }
427
- return $ keyring ;
428
- }
429
- throw new InvalidArgumentException ('Invalid type: Must be string or array ' );
430
- }
431
-
432
448
/**
433
449
* Helper method to create a JSON error.
434
450
*
0 commit comments