Rev 637 | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 637 | Rev 679 | ||
---|---|---|---|
Line 1... | Line 1... | ||
1 | <?php |
1 | <?php |
2 | 2 | ||
3 | namespace Firebase\JWT; |
3 | namespace Firebase\JWT; |
4 | 4 | ||
- | 5 | use ArrayAccess; |
|
5 | use DomainException; |
6 | use DomainException; |
6 | use Exception; |
7 | use Exception; |
7 | use InvalidArgumentException; |
8 | use InvalidArgumentException; |
- | 9 | use OpenSSLAsymmetricKey; |
|
8 | use UnexpectedValueException; |
10 | use UnexpectedValueException; |
9 | use DateTime; |
11 | use DateTime; |
10 | 12 | ||
11 | /** |
13 | /** |
12 | * JSON Web Token implementation, based on this spec: |
14 | * JSON Web Token implementation, based on this spec: |
Line 56... | Line 58... | ||
56 | 58 | ||
57 | /** |
59 | /** |
58 | * Decodes a JWT string into a PHP object. |
60 | * Decodes a JWT string into a PHP object. |
59 | * |
61 | * |
60 | * @param string $jwt The JWT |
62 | * @param string $jwt The JWT |
61 | * @param string|array|resource $key The key, or map of keys. |
63 | * @param Key|array<Key>|mixed $keyOrKeyArray The Key or array of Key objects. |
62 | * If the algorithm used is asymmetric, this is the public key |
64 | * If the algorithm used is asymmetric, this is the public key |
63 | * @param array $allowed_algs List of supported verification algorithms |
65 | * Each Key object contains an algorithm and matching key. |
64 | * Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', |
66 | * Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', |
65 | * 'HS512', 'RS256', 'RS384', and 'RS512' |
67 | * 'HS512', 'RS256', 'RS384', and 'RS512' |
- | 68 | * @param array $allowed_algs [DEPRECATED] List of supported verification algorithms. Only |
|
- | 69 | * should be used for backwards compatibility. |
|
66 | * |
70 | * |
67 | * @return object The JWT's payload as a PHP object |
71 | * @return object The JWT's payload as a PHP object |
68 | * |
72 | * |
69 | * @throws InvalidArgumentException Provided JWT was empty |
73 | * @throws InvalidArgumentException Provided JWT was empty |
70 | * @throws UnexpectedValueException Provided JWT was invalid |
74 | * @throws UnexpectedValueException Provided JWT was invalid |
Line 74... | Line 78... | ||
74 | * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim |
78 | * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim |
75 | * |
79 | * |
76 | * @uses jsonDecode |
80 | * @uses jsonDecode |
77 | * @uses urlsafeB64Decode |
81 | * @uses urlsafeB64Decode |
78 | */ |
82 | */ |
79 | public static function decode($jwt, $key, array $allowed_algs = array()) |
83 | public static function decode($jwt, $keyOrKeyArray, array $allowed_algs = array()) |
80 | { |
84 | { |
81 | $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; |
85 | $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; |
82 | 86 | ||
83 | if (empty($key)) { |
87 | if (empty($keyOrKeyArray)) { |
84 | throw new InvalidArgumentException('Key may not be empty'); |
88 | throw new InvalidArgumentException('Key may not be empty'); |
85 | } |
89 | } |
86 | $tks = \explode('.', $jwt); |
90 | $tks = \explode('.', $jwt); |
87 | if (\count($tks) != 3) { |
91 | if (\count($tks) != 3) { |
88 | throw new UnexpectedValueException('Wrong number of segments'); |
92 | throw new UnexpectedValueException('Wrong number of segments'); |
Line 101... | Line 105... | ||
101 | throw new UnexpectedValueException('Empty algorithm'); |
105 | throw new UnexpectedValueException('Empty algorithm'); |
102 | } |
106 | } |
103 | if (empty(static::$supported_algs[$header->alg])) { |
107 | if (empty(static::$supported_algs[$header->alg])) { |
104 | throw new UnexpectedValueException('Algorithm not supported'); |
108 | throw new UnexpectedValueException('Algorithm not supported'); |
105 | } |
109 | } |
- | 110 | ||
- | 111 | list($keyMaterial, $algorithm) = self::getKeyMaterialAndAlgorithm( |
|
- | 112 | $keyOrKeyArray, |
|
- | 113 | empty($header->kid) ? null : $header->kid |
|
- | 114 | ); |
|
- | 115 | ||
- | 116 | if (empty($algorithm)) { |
|
- | 117 | // Use deprecated "allowed_algs" to determine if the algorithm is supported. |
|
- | 118 | // This opens up the possibility of an attack in some implementations. |
|
- | 119 | // @see https://github.com/firebase/php-jwt/issues/351 |
|
106 | if (!\in_array($header->alg, $allowed_algs)) { |
120 | if (!\in_array($header->alg, $allowed_algs)) { |
107 | throw new UnexpectedValueException('Algorithm not allowed'); |
121 | throw new UnexpectedValueException('Algorithm not allowed'); |
108 | } |
122 | } |
- | 123 | } else { |
|
- | 124 | // Check the algorithm |
|
- | 125 | if (!self::constantTimeEquals($algorithm, $header->alg)) { |
|
- | 126 | // See issue #351 |
|
- | 127 | throw new UnexpectedValueException('Incorrect key for this algorithm'); |
|
- | 128 | } |
|
- | 129 | } |
|
109 | if ($header->alg === 'ES256' || $header->alg === 'ES384') { |
130 | if ($header->alg === 'ES256' || $header->alg === 'ES384') { |
110 | // OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures |
131 | // OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures |
111 | $sig = self::signatureToDER($sig); |
132 | $sig = self::signatureToDER($sig); |
112 | } |
133 | } |
113 | 134 | ||
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)) { |
135 | if (!static::verify("$headb64.$bodyb64", $sig, $keyMaterial, $header->alg)) { |
127 | throw new SignatureInvalidException('Signature verification failed'); |
136 | throw new SignatureInvalidException('Signature verification failed'); |
128 | } |
137 | } |
129 | 138 | ||
130 | // Check the nbf if it is defined. This is the time that the |
139 | // Check the nbf if it is defined. This is the time that the |
131 | // token can actually be used. If it's not yet that time, abort. |
140 | // token can actually be used. If it's not yet that time, abort. |
Line 283... | Line 292... | ||
283 | throw new DomainException($e->getMessage(), 0, $e); |
292 | throw new DomainException($e->getMessage(), 0, $e); |
284 | } |
293 | } |
285 | case 'hash_hmac': |
294 | case 'hash_hmac': |
286 | default: |
295 | default: |
287 | $hash = \hash_hmac($algorithm, $msg, $key, true); |
296 | $hash = \hash_hmac($algorithm, $msg, $key, true); |
288 | if (\function_exists('hash_equals')) { |
- | |
289 | return \hash_equals($signature, $hash); |
297 | return self::constantTimeEquals($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); |
- | |
300 | } |
298 | } |
301 | } |
299 | } |
302 | 300 | ||
303 | /** |
301 | /** |
304 | * Decode a JSON string into a PHP object. |
302 | * Decode a JSON string into a PHP object. |
Line 382... | Line 380... | ||
382 | public static function urlsafeB64Encode($input) |
380 | public static function urlsafeB64Encode($input) |
383 | { |
381 | { |
384 | return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); |
382 | return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); |
385 | } |
383 | } |
386 | 384 | ||
- | 385 | ||
- | 386 | /** |
|
- | 387 | * Determine if an algorithm has been provided for each Key |
|
- | 388 | * |
|
- | 389 | * @param Key|array<Key>|mixed $keyOrKeyArray |
|
- | 390 | * @param string|null $kid |
|
- | 391 | * |
|
- | 392 | * @throws UnexpectedValueException |
|
- | 393 | * |
|
- | 394 | * @return array containing the keyMaterial and algorithm |
|
- | 395 | */ |
|
- | 396 | private static function getKeyMaterialAndAlgorithm($keyOrKeyArray, $kid = null) |
|
- | 397 | { |
|
- | 398 | if ( |
|
- | 399 | is_string($keyOrKeyArray) |
|
- | 400 | || is_resource($keyOrKeyArray) |
|
- | 401 | || $keyOrKeyArray instanceof OpenSSLAsymmetricKey |
|
- | 402 | ) { |
|
- | 403 | return array($keyOrKeyArray, null); |
|
- | 404 | } |
|
- | 405 | ||
- | 406 | if ($keyOrKeyArray instanceof Key) { |
|
- | 407 | return array($keyOrKeyArray->getKeyMaterial(), $keyOrKeyArray->getAlgorithm()); |
|
- | 408 | } |
|
- | 409 | ||
- | 410 | if (is_array($keyOrKeyArray) || $keyOrKeyArray instanceof ArrayAccess) { |
|
- | 411 | if (!isset($kid)) { |
|
- | 412 | throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); |
|
- | 413 | } |
|
- | 414 | if (!isset($keyOrKeyArray[$kid])) { |
|
- | 415 | throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); |
|
- | 416 | } |
|
- | 417 | ||
- | 418 | $key = $keyOrKeyArray[$kid]; |
|
- | 419 | ||
- | 420 | if ($key instanceof Key) { |
|
- | 421 | return array($key->getKeyMaterial(), $key->getAlgorithm()); |
|
- | 422 | } |
|
- | 423 | ||
- | 424 | return array($key, null); |
|
- | 425 | } |
|
- | 426 | ||
- | 427 | throw new UnexpectedValueException( |
|
- | 428 | '$keyOrKeyArray must be a string|resource key, an array of string|resource keys, ' |
|
- | 429 | . 'an instance of Firebase\JWT\Key key or an array of Firebase\JWT\Key keys' |
|
- | 430 | ); |
|
- | 431 | } |
|
- | 432 | ||
- | 433 | /** |
|
- | 434 | * @param string $left |
|
- | 435 | * @param string $right |
|
- | 436 | * @return bool |
|
- | 437 | */ |
|
- | 438 | public static function constantTimeEquals($left, $right) |
|
- | 439 | { |
|
- | 440 | if (\function_exists('hash_equals')) { |
|
- | 441 | return \hash_equals($left, $right); |
|
- | 442 | } |
|
- | 443 | $len = \min(static::safeStrlen($left), static::safeStrlen($right)); |
|
- | 444 | ||
- | 445 | $status = 0; |
|
- | 446 | for ($i = 0; $i < $len; $i++) { |
|
- | 447 | $status |= (\ord($left[$i]) ^ \ord($right[$i])); |
|
- | 448 | } |
|
- | 449 | $status |= (static::safeStrlen($left) ^ static::safeStrlen($right)); |
|
- | 450 | ||
- | 451 | return ($status === 0); |
|
- | 452 | } |
|
- | 453 | ||
387 | /** |
454 | /** |
388 | * Helper method to create a JSON error. |
455 | * Helper method to create a JSON error. |
389 | * |
456 | * |
390 | * @param int $errno An error number from json_last_error() |
457 | * @param int $errno An error number from json_last_error() |
391 | * |
458 | * |