*/ /** Allow to manage the JSON Web Tokens * Based on https://tools.ietf.org/html/rfc7519 */ class jwt { // PROPERTIES // {{{ /** List the allowed algorithms to sign the token */ private $supportedAlgs = array ( 'HS256' => array('hash_hmac', 'SHA256'), 'HS512' => array('hash_hmac', 'SHA512'), 'HS384' => array('hash_hmac', 'SHA384'), ); // }}} /** Create the token based on payload, key * @param array $payload The payload to store * @param string $key The key to be used to sign the token * @param string|null $alg The algorithm to use to sign the token (default * is HS256) * Allowed algorithms : HS256, HS512, HS384 * @return string The Token */ public function encode ($payload, $key, $alg = "HS256") // {{{ { if (! key_exists ($alg, $this->supportedAlgs)) throw new \Exception (dgettext ("domframework", "Invalid encode algorithm requested : not allowed"), 500); $header = array ("typ" => "JWT", "alg" => $alg); $segments = array (); $segments[] = $this->urlsafeB64Encode ($this->jsonEncode ($header)); $segments[] = $this->urlsafeB64Encode ($this->jsonEncode ($payload)); $toBeSigned = implode ('.', $segments); $signature = $this->sign ($toBeSigned, $key, $alg); $segments[] = $this->urlsafeB64Encode ($signature); return implode ('.', $segments); } // }}} /** Decode the provide JWT and return an array of the payload * @param string $jwt The token to examine * @param string $key The key used to sign the message * @param array|null $allowedAlg List of allowed algorithms. If null, all the * algorithms defined in $this->supportedAlgs are allowed * @return array the decoded payload * @throw Exception if the key is not able to verify the token with the * provided password */ public function decode ($jwt, $key, $allowedAlg = null) // {{{ { if ($allowedAlg === null) $allowedAlg = array_keys ($this->supportedAlgs); if (empty ($key)) throw new \Exception (dgettext ("domframework", "Key may not be empty"), 500); $tks = explode(".", $jwt); if (count ($tks) != 3) throw new \Exception (dgettext ("domframework", "Malformed JWT Token"), 403); list ($headerb64, $payloadb64, $signb64) = $tks; $header = $this->jsonDecode ($this->urlsafeB64Decode ($headerb64)); $payload = $this->jsonDecode ($this->urlsafeB64Decode ($payloadb64)); $signature = $this->urlsafeB64Decode ($signb64); if ($header === null) throw new \Exception (dgettext ("domframework", "JWT Header not readable"), 403); if ($payload === null) throw new \Exception (dgettext ("domframework", "JWT Payload not readable"), 403); if ($signature === false) throw new \Exception (dgettext ("domframework", "JWT Signature not readable"), 403); if (empty ($header->alg)) throw new \Exception (dgettext ("domframework", "JWT with Empty algorithm"), 403); if (! in_array ($header->alg, $allowedAlg)) throw new \Exception (dgettext ("domframework", "JWT with Invalid algorithm"), 403); if (empty ($header->typ)) throw new \Exception (dgettext ("domframework", "JWT with Empty type set"), 403); if ($header->typ !== "JWT") throw new \Exception (dgettext ("domframework", "JWT with Invalid type"), 403); if (! $this->verify("$headerb64.$payloadb64", $signature, $key, $header->alg)) throw new \Exception (dgettext ("domframework", "JWT Signature verification failed"), 403); return $payload; } // }}} /** Verify the provided token with the key and generate an return true if it * can be verify * @param string $input The text in Base64 to check * @param string $sign The user provided signature in binary * @param string $key The key to use to sign the input * @param string $alg The algorithm to use to sign the input * @return boolean Return true if the input signed is valid */ private function verify ($input, $sign, $key, $alg) // {{{ { $signature = $this->sign ($input, $key, $alg); if (function_exists ("hash_equals") && (! key_exists ("hash_equals", $GLOBALS) || $GLOBALS["hash_equals"] === true)) return hash_equals ($signature, $sign); if (strlen ($signature) !== strlen ($sign)) return false; $status = 0; for ($i = 0; $i < strlen ($signature); $i++) $status |= ord ($signature[$i]) ^ ord ($sign[$i]); return $status === 0; } // }}} /** Create a signing key * @return string the signing key proposed */ public function createKey () // {{{ { return sha1 (microtime (true)); } // }}} /** Sign the requested string with the provided key and based on the algorithm * @param string $input The string to sign * @param string $key The key to use * @param string $alg The algorithm to use to sign * @return string The signed string in binary */ private function sign ($input, $key, $alg) // {{{ { if (! key_exists ($alg, $this->supportedAlgs)) throw new \Exception (dgettext ("domframework", "Invalid encode algorithm requested : not allowed"), 500); list ($method, $algorithm) = $this->supportedAlgs[$alg]; switch($method) { case 'hash_hmac': return hash_hmac ($algorithm, $input, $key, true); default: throw new \Exception (dgettext ("domframework", "Invalid method to sign the JWT"), 500); } } // }}} /** Return the provided string in base64 without equal at the end * To be URL compliant, the slash and plus are converted to underscore and * dash * @param string $str The string the convert in base64 * @return string The string converted in base64 */ private function urlsafeB64Encode ($str) // {{{ { return rtrim (strtr (base64_encode ($str), '+/', '-_'), "="); } // }}} /** Return the provided base64 to string * @param string $str The string the convert from base64 * @return string The string converted from base64 */ private function urlsafeB64Decode ($str) // {{{ { $str = strtr ($str, '-_', '+/'); $remainder = strlen ($str) % 4; switch (strlen ($str) % 4) { case 0: break; case 2: $str .= "=="; break; case 3: $str .= "="; break; default: return false; } return base64_decode ($str); } // }}} /** Return the provided array to JSON string * @param object $input The object to convert in JSON * @return string The JSON string */ private function jsonEncode ($input) // {{{ { $json = json_encode ($input); if ($json === "null" && $input !== null) throw new \Exception (dgettext ("domframework", "JSON Encode : Null result with non-null input"), 500); return $json; } // }}} /** Decode the provided JSON string and return the result * If null, there is a decode problem * @param string $input The string to decode * @return mixed The decoded string in object */ private function jsonDecode ($input) // {{{ { return json_decode ($input, false, 512, JSON_BIGINT_AS_STRING); } // }}} }