2,9 → 2,11 |
|
namespace Firebase\JWT; |
|
use ArrayAccess; |
use DomainException; |
use Exception; |
use InvalidArgumentException; |
use OpenSSLAsymmetricKey; |
use UnexpectedValueException; |
use DateTime; |
|
58,11 → 60,13 |
* Decodes a JWT string into a PHP object. |
* |
* @param string $jwt The JWT |
* @param string|array|resource $key The key, or map of keys. |
* @param Key|array<Key>|mixed $keyOrKeyArray The Key or array of Key objects. |
* If the algorithm used is asymmetric, this is the public key |
* @param array $allowed_algs List of supported verification algorithms |
* Each Key object contains an algorithm and matching key. |
* Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', |
* 'HS512', 'RS256', 'RS384', and 'RS512' |
* @param array $allowed_algs [DEPRECATED] List of supported verification algorithms. Only |
* should be used for backwards compatibility. |
* |
* @return object The JWT's payload as a PHP object |
* |
76,11 → 80,11 |
* @uses jsonDecode |
* @uses urlsafeB64Decode |
*/ |
public static function decode($jwt, $key, array $allowed_algs = array()) |
public static function decode($jwt, $keyOrKeyArray, array $allowed_algs = array()) |
{ |
$timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; |
|
if (empty($key)) { |
if (empty($keyOrKeyArray)) { |
throw new InvalidArgumentException('Key may not be empty'); |
} |
$tks = \explode('.', $jwt); |
103,27 → 107,32 |
if (empty(static::$supported_algs[$header->alg])) { |
throw new UnexpectedValueException('Algorithm not supported'); |
} |
|
list($keyMaterial, $algorithm) = self::getKeyMaterialAndAlgorithm( |
$keyOrKeyArray, |
empty($header->kid) ? null : $header->kid |
); |
|
if (empty($algorithm)) { |
// Use deprecated "allowed_algs" to determine if the algorithm is supported. |
// This opens up the possibility of an attack in some implementations. |
// @see https://github.com/firebase/php-jwt/issues/351 |
if (!\in_array($header->alg, $allowed_algs)) { |
throw new UnexpectedValueException('Algorithm not allowed'); |
} |
} else { |
// Check the algorithm |
if (!self::constantTimeEquals($algorithm, $header->alg)) { |
// See issue #351 |
throw new UnexpectedValueException('Incorrect key for this algorithm'); |
} |
} |
if ($header->alg === 'ES256' || $header->alg === 'ES384') { |
// OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures |
$sig = self::signatureToDER($sig); |
} |
|
if (\is_array($key) || $key instanceof \ArrayAccess) { |
if (isset($header->kid)) { |
if (!isset($key[$header->kid])) { |
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); |
} |
$key = $key[$header->kid]; |
} else { |
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); |
} |
} |
|
// Check the signature |
if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) { |
if (!static::verify("$headb64.$bodyb64", $sig, $keyMaterial, $header->alg)) { |
throw new SignatureInvalidException('Signature verification failed'); |
} |
|
285,21 → 294,10 |
case 'hash_hmac': |
default: |
$hash = \hash_hmac($algorithm, $msg, $key, true); |
if (\function_exists('hash_equals')) { |
return \hash_equals($signature, $hash); |
return self::constantTimeEquals($signature, $hash); |
} |
$len = \min(static::safeStrlen($signature), static::safeStrlen($hash)); |
|
$status = 0; |
for ($i = 0; $i < $len; $i++) { |
$status |= (\ord($signature[$i]) ^ \ord($hash[$i])); |
} |
$status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash)); |
|
return ($status === 0); |
} |
} |
|
/** |
* Decode a JSON string into a PHP object. |
* |
384,7 → 382,76 |
return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); |
} |
|
|
/** |
* Determine if an algorithm has been provided for each Key |
* |
* @param Key|array<Key>|mixed $keyOrKeyArray |
* @param string|null $kid |
* |
* @throws UnexpectedValueException |
* |
* @return array containing the keyMaterial and algorithm |
*/ |
private static function getKeyMaterialAndAlgorithm($keyOrKeyArray, $kid = null) |
{ |
if ( |
is_string($keyOrKeyArray) |
|| is_resource($keyOrKeyArray) |
|| $keyOrKeyArray instanceof OpenSSLAsymmetricKey |
) { |
return array($keyOrKeyArray, null); |
} |
|
if ($keyOrKeyArray instanceof Key) { |
return array($keyOrKeyArray->getKeyMaterial(), $keyOrKeyArray->getAlgorithm()); |
} |
|
if (is_array($keyOrKeyArray) || $keyOrKeyArray instanceof ArrayAccess) { |
if (!isset($kid)) { |
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); |
} |
if (!isset($keyOrKeyArray[$kid])) { |
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); |
} |
|
$key = $keyOrKeyArray[$kid]; |
|
if ($key instanceof Key) { |
return array($key->getKeyMaterial(), $key->getAlgorithm()); |
} |
|
return array($key, null); |
} |
|
throw new UnexpectedValueException( |
'$keyOrKeyArray must be a string|resource key, an array of string|resource keys, ' |
. 'an instance of Firebase\JWT\Key key or an array of Firebase\JWT\Key keys' |
); |
} |
|
/** |
* @param string $left |
* @param string $right |
* @return bool |
*/ |
public static function constantTimeEquals($left, $right) |
{ |
if (\function_exists('hash_equals')) { |
return \hash_equals($left, $right); |
} |
$len = \min(static::safeStrlen($left), static::safeStrlen($right)); |
|
$status = 0; |
for ($i = 0; $i < $len; $i++) { |
$status |= (\ord($left[$i]) ^ \ord($right[$i])); |
} |
$status |= (static::safeStrlen($left) ^ static::safeStrlen($right)); |
|
return ($status === 0); |
} |
|
/** |
* Helper method to create a JSON error. |
* |
* @param int $errno An error number from json_last_error() |