| <?php |
| |
| namespace OAuth2\Encryption; |
| |
| use Exception; |
| use InvalidArgumentException; |
| |
| /** |
| * @link https://github.com/F21/jwt |
| * @author F21 |
| */ |
| class Jwt implements EncryptionInterface |
| { |
| /** |
| * @param $payload |
| * @param $key |
| * @param string $algo |
| * @return string |
| */ |
| public function encode($payload, $key, $algo = 'HS256') |
| { |
| $header = $this->generateJwtHeader($payload, $algo); |
| |
| $segments = array( |
| $this->urlSafeB64Encode(json_encode($header)), |
| $this->urlSafeB64Encode(json_encode($payload)) |
| ); |
| |
| $signing_input = implode('.', $segments); |
| |
| $signature = $this->sign($signing_input, $key, $algo); |
| $segments[] = $this->urlsafeB64Encode($signature); |
| |
| return implode('.', $segments); |
| } |
| |
| /** |
| * @param string $jwt |
| * @param null $key |
| * @param array|bool $allowedAlgorithms |
| * @return bool|mixed |
| */ |
| public function decode($jwt, $key = null, $allowedAlgorithms = true) |
| { |
| if (!strpos($jwt, '.')) { |
| return false; |
| } |
| |
| $tks = explode('.', $jwt); |
| |
| if (count($tks) != 3) { |
| return false; |
| } |
| |
| list($headb64, $payloadb64, $cryptob64) = $tks; |
| |
| if (null === ($header = json_decode($this->urlSafeB64Decode($headb64), true))) { |
| return false; |
| } |
| |
| if (null === $payload = json_decode($this->urlSafeB64Decode($payloadb64), true)) { |
| return false; |
| } |
| |
| $sig = $this->urlSafeB64Decode($cryptob64); |
| |
| if ((bool) $allowedAlgorithms) { |
| if (!isset($header['alg'])) { |
| return false; |
| } |
| |
| // check if bool arg supplied here to maintain BC |
| if (is_array($allowedAlgorithms) && !in_array($header['alg'], $allowedAlgorithms)) { |
| return false; |
| } |
| |
| if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header['alg'])) { |
| return false; |
| } |
| } |
| |
| return $payload; |
| } |
| |
| /** |
| * @param $signature |
| * @param $input |
| * @param $key |
| * @param string $algo |
| * @return bool |
| * @throws InvalidArgumentException |
| */ |
| private function verifySignature($signature, $input, $key, $algo = 'HS256') |
| { |
| // use constants when possible, for HipHop support |
| switch ($algo) { |
| case'HS256': |
| case'HS384': |
| case'HS512': |
| return $this->hash_equals( |
| $this->sign($input, $key, $algo), |
| $signature |
| ); |
| |
| case 'RS256': |
| return openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256') === 1; |
| |
| case 'RS384': |
| return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384') === 1; |
| |
| case 'RS512': |
| return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512') === 1; |
| |
| default: |
| throw new InvalidArgumentException("Unsupported or invalid signing algorithm."); |
| } |
| } |
| |
| /** |
| * @param $input |
| * @param $key |
| * @param string $algo |
| * @return string |
| * @throws Exception |
| */ |
| private function sign($input, $key, $algo = 'HS256') |
| { |
| switch ($algo) { |
| case 'HS256': |
| return hash_hmac('sha256', $input, $key, true); |
| |
| case 'HS384': |
| return hash_hmac('sha384', $input, $key, true); |
| |
| case 'HS512': |
| return hash_hmac('sha512', $input, $key, true); |
| |
| case 'RS256': |
| return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256'); |
| |
| case 'RS384': |
| return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384'); |
| |
| case 'RS512': |
| return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512'); |
| |
| default: |
| throw new Exception("Unsupported or invalid signing algorithm."); |
| } |
| } |
| |
| /** |
| * @param $input |
| * @param $key |
| * @param string $algo |
| * @return mixed |
| * @throws Exception |
| */ |
| private function generateRSASignature($input, $key, $algo) |
| { |
| if (!openssl_sign($input, $signature, $key, $algo)) { |
| throw new Exception("Unable to sign data."); |
| } |
| |
| return $signature; |
| } |
| |
| /** |
| * @param string $data |
| * @return string |
| */ |
| public function urlSafeB64Encode($data) |
| { |
| $b64 = base64_encode($data); |
| $b64 = str_replace(array('+', '/', "\r", "\n", '='), |
| array('-', '_'), |
| $b64); |
| |
| return $b64; |
| } |
| |
| /** |
| * @param string $b64 |
| * @return mixed|string |
| */ |
| public function urlSafeB64Decode($b64) |
| { |
| $b64 = str_replace(array('-', '_'), |
| array('+', '/'), |
| $b64); |
| |
| return base64_decode($b64); |
| } |
| |
| /** |
| * Override to create a custom header |
| */ |
| protected function generateJwtHeader($payload, $algorithm) |
| { |
| return array( |
| 'typ' => 'JWT', |
| 'alg' => $algorithm, |
| ); |
| } |
| |
| /** |
| * @param string $a |
| * @param string $b |
| * @return bool |
| */ |
| protected function hash_equals($a, $b) |
| { |
| if (function_exists('hash_equals')) { |
| return hash_equals($a, $b); |
| } |
| $diff = strlen($a) ^ strlen($b); |
| for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) { |
| $diff |= ord($a[$i]) ^ ord($b[$i]); |
| } |
| |
| return $diff === 0; |
| } |
| } |