22
33namespace  Firebase \JWT ;
44
5+ use  ArrayAccess ;
56use  DomainException ;
67use  Exception ;
78use  InvalidArgumentException ;
@@ -58,11 +59,13 @@ class JWT
5859     * Decodes a JWT string into a PHP object. 
5960     * 
6061     * @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 . 
6263     *                                                  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.  
6465     *                                                  Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', 
6566     *                                                  'HS512', 'RS256', 'RS384', and 'RS512' 
67+      * @param array                     $allowed_algs   [DEPRECATED] List of supported verification algorithms. Only 
68+      *                                                  should be used for backwards  compatibility. 
6669     * 
6770     * @return object The JWT's payload as a PHP object 
6871     * 
@@ -76,11 +79,11 @@ class JWT
7679     * @uses jsonDecode 
7780     * @uses urlsafeB64Decode 
7881     */ 
79-     public  static  function  decode ($ jwt , $ key  , array  $ allowed_algs  = array ())
82+     public  static  function  decode ($ jwt , $ keyOrKeyArray  , array  $ allowed_algs  = array ())
8083    {
8184        $ timestamp  = \is_null (static ::$ timestamp ) ? \time () : static ::$ timestamp ;
8285
83-         if  (empty ($ key  )) {
86+         if  (empty ($ keyOrKeyArray  )) {
8487            throw  new  InvalidArgumentException ('Key may not be empty ' );
8588        }
8689        $ tks  = \explode ('. ' , $ jwt );
@@ -103,27 +106,32 @@ public static function decode($jwt, $key, array $allowed_algs = array())
103106        if  (empty (static ::$ supported_algs [$ header ->alg ])) {
104107            throw  new  UnexpectedValueException ('Algorithm not supported ' );
105108        }
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+             }
108128        }
109129        if  ($ header ->alg  === 'ES256 '  || $ header ->alg  === 'ES384 ' ) {
110130            // OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures 
111131            $ sig  = self ::signatureToDER ($ sig );
112132        }
113133
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 )) {
127135            throw  new  SignatureInvalidException ('Signature verification failed ' );
128136        }
129137
@@ -285,18 +293,7 @@ private static function verify($msg, $signature, $key, $alg)
285293            case  'hash_hmac ' :
286294            default :
287295                $ 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 );
300297        }
301298    }
302299
@@ -384,6 +381,69 @@ public static function urlsafeB64Encode($input)
384381        return  \str_replace ('= ' , '' , \strtr (\base64_encode ($ input ), '+/ ' , '-_ ' ));
385382    }
386383
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+ 
387447    /** 
388448     * Helper method to create a JSON error. 
389449     * 
0 commit comments