2
2
3
3
namespace Firebase \JWT ;
4
4
5
+ use ArrayAccess ;
5
6
use DomainException ;
6
7
use Exception ;
7
8
use InvalidArgumentException ;
@@ -58,11 +59,13 @@ class JWT
58
59
* Decodes a JWT string into a PHP object.
59
60
*
60
61
* @param string $jwt The JWT
61
- * @param string |array|resource $key The key, or map of keys .
62
+ * @param Key |array<Key> $keyOrKeyArray The Key or array of Key objects .
62
63
* If the algorithm used is asymmetric, this is the public key
63
- * @param array $allowed_algs List of supported verification algorithms
64
+ * Each Key object contains an algorithm and matching key.
64
65
* Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
65
66
* 'HS512', 'RS256', 'RS384', and 'RS512'
67
+ * @param array $allowed_algs [DEPRECATED] List of supported verification algorithms. Only
68
+ * should be used for backwards compatibility.
66
69
*
67
70
* @return object The JWT's payload as a PHP object
68
71
*
@@ -76,11 +79,11 @@ class JWT
76
79
* @uses jsonDecode
77
80
* @uses urlsafeB64Decode
78
81
*/
79
- public static function decode ($ jwt , $ key , array $ allowed_algs = array ())
82
+ public static function decode ($ jwt , $ keyOrKeyArray , array $ allowed_algs = array ())
80
83
{
81
84
$ timestamp = \is_null (static ::$ timestamp ) ? \time () : static ::$ timestamp ;
82
85
83
- if (empty ($ key )) {
86
+ if (empty ($ keyOrKeyArray )) {
84
87
throw new InvalidArgumentException ('Key may not be empty ' );
85
88
}
86
89
$ tks = \explode ('. ' , $ jwt );
@@ -103,27 +106,32 @@ public static function decode($jwt, $key, array $allowed_algs = array())
103
106
if (empty (static ::$ supported_algs [$ header ->alg ])) {
104
107
throw new UnexpectedValueException ('Algorithm not supported ' );
105
108
}
106
- if (!\in_array ($ header ->alg , $ allowed_algs )) {
107
- throw new UnexpectedValueException ('Algorithm not allowed ' );
109
+
110
+ list ($ keyMaterial , $ algorithm ) = self ::getKeyMaterialAndAlgorithm (
111
+ $ keyOrKeyArray ,
112
+ empty ($ header ->kid ) ? null : $ header ->kid
113
+ );
114
+
115
+ if (empty ($ algorithm )) {
116
+ // Use deprecated "allowed_algs" to determine if the algorithm is supported.
117
+ // This opens up the possibility of an attack in some implementations.
118
+ // @see https://github.com/firebase/php-jwt/issues/351
119
+ if (!\in_array ($ header ->alg , $ allowed_algs )) {
120
+ throw new UnexpectedValueException ('Algorithm not allowed ' );
121
+ }
122
+ } else {
123
+ // Check the algorithm
124
+ if (!self ::constantTimeEquals ($ algorithm , $ header ->alg )) {
125
+ // See issue #351
126
+ throw new UnexpectedValueException ('Incorrect key for this algorithm ' );
127
+ }
108
128
}
109
129
if ($ header ->alg === 'ES256 ' || $ header ->alg === 'ES384 ' ) {
110
130
// OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures
111
131
$ sig = self ::signatureToDER ($ sig );
112
132
}
113
133
114
- if (\is_array ($ key ) || $ key instanceof \ArrayAccess) {
115
- if (isset ($ header ->kid )) {
116
- if (!isset ($ key [$ header ->kid ])) {
117
- throw new UnexpectedValueException ('"kid" invalid, unable to lookup correct key ' );
118
- }
119
- $ key = $ key [$ header ->kid ];
120
- } else {
121
- throw new UnexpectedValueException ('"kid" empty, unable to lookup correct key ' );
122
- }
123
- }
124
-
125
- // Check the signature
126
- if (!static ::verify ("$ headb64. $ bodyb64 " , $ sig , $ key , $ header ->alg )) {
134
+ if (!static ::verify ("$ headb64. $ bodyb64 " , $ sig , $ keyMaterial , $ header ->alg )) {
127
135
throw new SignatureInvalidException ('Signature verification failed ' );
128
136
}
129
137
@@ -285,18 +293,7 @@ private static function verify($msg, $signature, $key, $alg)
285
293
case 'hash_hmac ' :
286
294
default :
287
295
$ hash = \hash_hmac ($ algorithm , $ msg , $ key , true );
288
- if (\function_exists ('hash_equals ' )) {
289
- return \hash_equals ($ signature , $ hash );
290
- }
291
- $ len = \min (static ::safeStrlen ($ signature ), static ::safeStrlen ($ hash ));
292
-
293
- $ status = 0 ;
294
- for ($ i = 0 ; $ i < $ len ; $ i ++) {
295
- $ status |= (\ord ($ signature [$ i ]) ^ \ord ($ hash [$ i ]));
296
- }
297
- $ status |= (static ::safeStrlen ($ signature ) ^ static ::safeStrlen ($ hash ));
298
-
299
- return ($ status === 0 );
296
+ return self ::constantTimeEquals ($ signature , $ hash );
300
297
}
301
298
}
302
299
@@ -384,6 +381,69 @@ public static function urlsafeB64Encode($input)
384
381
return \str_replace ('= ' , '' , \strtr (\base64_encode ($ input ), '+/ ' , '-_ ' ));
385
382
}
386
383
384
+
385
+ /**
386
+ * Determine if an algorithm has been provided for each Key
387
+ *
388
+ * @param string|array $keyOrKeyArray
389
+ * @param string|null $kid
390
+ *
391
+ * @return an array containing the keyMaterial and algorithm
392
+ */
393
+ private static function getKeyMaterialAndAlgorithm ($ keyOrKeyArray , $ kid = null )
394
+ {
395
+ if (is_string ($ keyOrKeyArray )) {
396
+ return array ($ keyOrKeyArray , null );
397
+ }
398
+
399
+ if ($ keyOrKeyArray instanceof Key) {
400
+ return array ($ keyOrKeyArray ->getKeyMaterial (), $ keyOrKeyArray ->getAlgorithm ());
401
+ }
402
+
403
+ if (is_array ($ keyOrKeyArray ) || $ keyOrKeyArray instanceof ArrayAccess) {
404
+ if (!isset ($ kid )) {
405
+ throw new UnexpectedValueException ('"kid" empty, unable to lookup correct key ' );
406
+ }
407
+ if (!isset ($ keyOrKeyArray [$ kid ])) {
408
+ throw new UnexpectedValueException ('"kid" invalid, unable to lookup correct key ' );
409
+ }
410
+
411
+ $ key = $ keyOrKeyArray [$ kid ];
412
+
413
+ if ($ key instanceof Key) {
414
+ return array ($ key ->getKeyMaterial (), $ key ->getAlgorithm ());
415
+ }
416
+
417
+ return array ($ key , null );
418
+ }
419
+
420
+ throw new UnexpectedValueException (
421
+ '$keyOrKeyArray must be a string key, an array of string keys, '
422
+ . 'an instance of Firebase\JWT\Key key or an array of Firebase\JWT\Key keys '
423
+ );
424
+ }
425
+
426
+ /**
427
+ * @param string $left
428
+ * @param string $right
429
+ * @return bool
430
+ */
431
+ public static function constantTimeEquals ($ left , $ right )
432
+ {
433
+ if (\function_exists ('hash_equals ' )) {
434
+ return \hash_equals ($ left , $ right );
435
+ }
436
+ $ len = \min (static ::safeStrlen ($ left ), static ::safeStrlen ($ right ));
437
+
438
+ $ status = 0 ;
439
+ for ($ i = 0 ; $ i < $ len ; $ i ++) {
440
+ $ status |= (\ord ($ left [$ i ]) ^ \ord ($ right [$ i ]));
441
+ }
442
+ $ status |= (static ::safeStrlen ($ left ) ^ static ::safeStrlen ($ right ));
443
+
444
+ return ($ status === 0 );
445
+ }
446
+
387
447
/**
388
448
* Helper method to create a JSON error.
389
449
*
0 commit comments