/** * Decodes a serialised JWT. * * @param string $token the serialised JWT * @param SimpleJWT\Keys\KeySet $keys the key set containing the key to verify the * JWT's signature * @param string $expected_alg the expected value of the `alg` parameter, which * should be agreed between the parties out-of-band * @param string $kid the ID of the key to use to verify the signature. If null, this * is automatically retrieved * @param bool|array $skip_validation an array of headers or claims that * should be ignored as part of the validation process (e.g. if expired tokens * are to be accepted), or false if all validation * is to be performed. * @param string $format the JWT serialisation format * @return JWT the decoded JWT * @throws InvalidTokenException if the token is invalid for any reason */ public static function decode($token, $keys, $expected_alg, $kid = null, $skip_validation = array(), $format = self::COMPACT_FORMAT) { if ($skip_validation === false) { $skip_validation = array(); } switch ($format) { case self::COMPACT_FORMAT: $parts = explode('.', $token, 3); if (count($parts) != 3) { throw new InvalidTokenException('Cannot decode compact serialisation', InvalidTokenException::TOKEN_PARSE_ERROR); } list($protected, $payload, $signature) = $parts; break; case self::JSON_FORMAT: $obj = json_decode($token, true); if ($obj == null) { throw new InvalidTokenException('Cannot decode JSON', InvalidTokenException::TOKEN_PARSE_ERROR); } $payload = $obj['payload']; if (isset($obj['signatures'])) { foreach ($obj['signatures'] as $signature_obj) { if (isset($signature_obj['header']['kid'])) { $target_kid = $signature_obj['header']['kid']; if ($target_kid == $kid || $keys->getById($target_kid) != null) { $unprotected = $signature_obj['header']; $protected = $signature_obj['protected']; $signature = $signature_obj['signature']; break; } } throw new InvalidTokenException('Cannot find verifiable signature', InvalidTokenException::TOKEN_PARSE_ERROR); } } else { $unprotected = $obj['header']; $protected = $obj['protected']; $signature = $obj['signature']; } break; default: throw new \InvalidArgumentException('Incorrect format'); } $headers = json_decode(Util::base64url_decode($protected), true); if ($headers == null) { throw new InvalidTokenException('Cannot decode header', InvalidTokenException::TOKEN_PARSE_ERROR); } // Process crit if (isset($headers['crit'])) { foreach ($headers['crit'] as $critical) { if (!in_array($critical, array('nbf', 'exp', 'alg', 'kid'))) { throw new InvalidTokenException('Critical header not supported: ' . $critical, InvalidTokenException::UNSUPPORTED_ERROR); } } } if (isset($unprotected)) { $headers = array_merge($headers, $unprotected); } $claims = json_decode(Util::base64url_decode($payload), true); if ($claims == null) { throw new InvalidTokenException('Cannot decode claims', InvalidTokenException::TOKEN_PARSE_ERROR); } // Check signatures if ($headers['alg'] != $expected_alg) { throw new InvalidTokenException('Unexpected algorithm', InvalidTokenException::SIGNATURE_VERIFICATION_ERROR); } $signer = AlgorithmFactory::create($expected_alg); $signing_input = $protected . '.' . $payload; try { if (isset($headers['kid'])) { $kid = $headers['kid']; } $result = $signer->verify($signature, $signing_input, $keys, $kid); } catch (KeyException $e) { throw new InvalidTokenException($e->getMessage(), InvalidTokenException::SIGNATURE_VERIFICATION_ERROR, $e); } catch (CryptException $e) { throw new InvalidTokenException($e->getMessage(), InvalidTokenException::SIGNATURE_VERIFICATION_ERROR, $e); } if (!$result) { throw new InvalidTokenException('Incorrect signature', InvalidTokenException::SIGNATURE_VERIFICATION_ERROR); } // Check time, etc $time = time(); if (isset($claims['nbf']) && !in_array('nbf', $skip_validation)) { if ($time < $claims['nbf'] - self::$TIME_ALLOWANCE) { throw new InvalidTokenException('Too early due to nbf claim', InvalidTokenException::TOO_EARLY_ERROR, null, $claims['nbf']); } } if (isset($claims['exp']) && !in_array('exp', $skip_validation)) { if ($time > $claims['exp'] + self::$TIME_ALLOWANCE) { throw new InvalidTokenException('Too late due to exp claim', InvalidTokenException::TOO_LATE_ERROR, null, $claims['exp']); } } return new JWT($headers, $claims); }
/** * Decrypts a JWE. * * @param string $token the serialised JWE * @param SimpleJWT\Keys\KeySet $keys the key set containing the key to verify the * JWT's signature * @param string $expected_alg the expected value of the `alg` parameter, which * should be agreed between the parties out-of-band * @param string $format the JWE serialisation format * @return JWE the decrypted JWE * @throws InvalidTokenException if the token is invalid for any reason */ public static function decrypt($token, $keys, $expected_alg, $format = self::COMPACT_FORMAT) { switch ($format) { case self::COMPACT_FORMAT: $parts = explode('.', $token, 5); if (count($parts) != 5) { throw new InvalidTokenException('Cannot decode compact serialisation', InvalidTokenException::TOKEN_PARSE_ERROR); } list($protected, $encrypted_key, $iv, $ciphertext, $tag) = $parts; break; case self::JSON_FORMAT: $obj = json_decode($token, true); if ($obj == null) { throw new InvalidTokenException('Cannot decode JSON', InvalidTokenException::TOKEN_PARSE_ERROR); } $protected = $obj['protected']; $unprotected = $obj['unprotected']; $iv = $obj['iv']; $ciphertext = $obj['ciphertext']; $tag = $obj['tag']; if (isset($obj['recipients'])) { foreach ($obj['recipients'] as $recipient) { if (isset($recipient_obj['header']['kid'])) { $target_kid = $recipient_obj['header']['kid']; if ($keys->getById($target_kid) != null) { $unprotected = isset($unprotected) ? array_merge($unprotected, $recipient_obj['header']) : $recipient_obj['header']; $encrypted_key = $recipient_obj['encrypted_key']; break; } } throw new InvalidTokenException('Cannot find verifiable signature', InvalidTokenException::TOKEN_PARSE_ERROR); } } else { $unprotected = isset($unprotected) ? array_merge($unprotected, $obj['header']) : $obj['header']; $encrypted_key = $obj['encrypted_key']; } break; default: throw new \InvalidArgumentException('Incorrect format'); } $headers = json_decode(Util::base64url_decode($protected), true); if ($headers == null) { throw new InvalidTokenException('Cannot decode header', InvalidTokenException::TOKEN_PARSE_ERROR); } if (isset($unprotected)) { $headers = array_merge($headers, $unprotected); } // Process crit if (isset($headers['crit'])) { foreach ($headers['crit'] as $critical) { if (!in_array($critical, array('alg', 'enc', 'kid', 'zip'))) { throw new InvalidTokenException('Critical header not supported: ' . $critical, InvalidTokenException::UNSUPPORTED_ERROR); } } } if (!isset($headers['alg'])) { throw new InvalidTokenException('alg parameter missing', InvalidTokenException::TOKEN_PARSE_ERROR); } if (!isset($headers['enc'])) { throw new InvalidTokenException('enc parameter missing', InvalidTokenException::TOKEN_PARSE_ERROR); } if ($headers['alg'] != $expected_alg) { throw new InvalidTokenException('Unexpected algorithm', InvalidTokenException::DECRYPTION_ERROR); } $key_enc = AlgorithmFactory::create($headers['alg']); $content_enc = AlgorithmFactory::create($headers['enc']); if ($key_enc instanceof KeyDerivationAlgorithm) { try { $agreed_key = $key_enc->deriveKey($keys, $headers, $kid); } catch (KeyException $e) { throw new InvalidTokenException($e->getMessage(), InvalidTokenException::DECRYPTION_ERROR, $e); } catch (CryptException $e) { throw new InvalidTokenException($e->getMessage(), InvalidTokenException::DECRYPTION_ERROR, $e); } if ($key_enc instanceof KeyEncryptionAlgorithm) { // Key agreement with wrapping $keys->add(new SymmetricKey(array('kty' => SymmetricKey::KTY, 'alg' => $this->headers['alg'], 'k' => Util::base64url_encode($agreed_key), 'kid' => '#key-agreement-with-wrapping'), 'php')); $kid = '#key-agreement-with-wrapping'; } else { // Direct key agreement or direct encryption $cek = $agreed_key; if ($encrypted_key != '') { throw new InvalidTokenException('encrypted key should be empty', InvalidTokenException::TOKEN_PARSE_ERROR); } } } if (!isset($cek) && $key_enc instanceof KeyEncryptionAlgorithm) { try { if (!isset($kid)) { $kid = isset($headers['kid']) ? $headers['kid'] : null; } $cek = $key_enc->decryptKey($encrypted_key, $keys, $headers, $kid); } catch (KeyException $e) { throw new InvalidTokenException($e->getMessage(), InvalidTokenException::DECRYPTION_ERROR, $e); } catch (CryptException $e) { throw new InvalidTokenException($e->getMessage(), InvalidTokenException::DECRYPTION_ERROR, $e); } } if (!$cek) { throw new InvalidTokenException('alg parameter incorrect', InvalidTokenException::TOKEN_PARSE_ERROR); } try { $plaintext = $content_enc->decryptAndVerify($ciphertext, $tag, $cek, $protected, $iv); if (isset($headers['zip'])) { switch ($headers['zip']) { case 'DEF': $plaintext = gzinflate($plaintext); break; default: throw new InvalidTokenException('Unsupported zip header:' . $headers['zip'], InvalidTokenException::UNSUPPORTED_ERROR); } } } catch (CryptException $e) { throw new InvalidTokenException($e->getMessage(), InvalidTokenException::DECRYPTION_ERROR, $e); } return new JWE($headers, $plaintext); }