PSR12
This commit is contained in:
457
src/Jwt.php
457
src/Jwt.php
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/** DomFramework
|
||||
* @package domframework
|
||||
* @author Dominique Fournier <dominique@fournier38.fr>
|
||||
@@ -15,216 +16,278 @@ namespace Domframework;
|
||||
*/
|
||||
class Jwt
|
||||
{
|
||||
// PROPERTIES
|
||||
/** List the allowed algorithms to sign the token
|
||||
*/
|
||||
private $supportedAlgs = array (
|
||||
// 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, sign it with key, and optionally
|
||||
* encrypt it with ckey
|
||||
* Do not put confidential data in payload without encrypt it, as the result
|
||||
* is only a Base64 format of JSON...
|
||||
* @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
|
||||
* @param string|null $ckey The cipher key to encrypt the payload
|
||||
* @param string|null $cipherMethod The method to cipher the payload
|
||||
* des-ede3-cbc by default
|
||||
* @return string The Token
|
||||
*/
|
||||
public function encode ($payload, $key, $alg = "HS256", $ckey = null,
|
||||
$cipherMethod = "des-ede3-cbc")
|
||||
{
|
||||
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));
|
||||
$payload = $this->jsonEncode ($payload);
|
||||
if ($ckey)
|
||||
{
|
||||
$encrypt = new Encrypt ();
|
||||
$payload = $encrypt->encrypt ($payload, $ckey, $cipherMethod);
|
||||
/** Create the token based on payload, sign it with key, and optionally
|
||||
* encrypt it with ckey
|
||||
* Do not put confidential data in payload without encrypt it, as the result
|
||||
* is only a Base64 format of JSON...
|
||||
* @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
|
||||
* @param string|null $ckey The cipher key to encrypt the payload
|
||||
* @param string|null $cipherMethod The method to cipher the payload
|
||||
* des-ede3-cbc by default
|
||||
* @return string The Token
|
||||
*/
|
||||
public function encode(
|
||||
$payload,
|
||||
$key,
|
||||
$alg = "HS256",
|
||||
$ckey = null,
|
||||
$cipherMethod = "des-ede3-cbc"
|
||||
) {
|
||||
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));
|
||||
$payload = $this->jsonEncode($payload);
|
||||
if ($ckey) {
|
||||
$encrypt = new Encrypt();
|
||||
$payload = $encrypt->encrypt($payload, $ckey, $cipherMethod);
|
||||
}
|
||||
$segments[] = $this->urlsafeB64Encode($payload);
|
||||
$toBeSigned = implode('.', $segments);
|
||||
$signature = $this->sign($toBeSigned, $key, $alg);
|
||||
$segments[] = $this->urlsafeB64Encode($signature);
|
||||
return implode('.', $segments);
|
||||
}
|
||||
$segments[] = $this->urlsafeB64Encode ($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
|
||||
* @param string|null $ckey The cipher key to decrypt the payload
|
||||
* @param string|null $cipherMethod The method to cipher the payload
|
||||
* des-ede3-cbc by default
|
||||
* @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, $ckey = null,
|
||||
$cipherMethod = "des-ede3-cbc")
|
||||
{
|
||||
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 = (object)$this->jsonDecode ($this->urlsafeB64Decode ($headerb64));
|
||||
$payload = $this->urlsafeB64Decode ($payloadb64);
|
||||
if ($ckey)
|
||||
{
|
||||
$encrypt = new Encrypt ();
|
||||
$payload = $encrypt->decrypt ($payload, $ckey);
|
||||
/** 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
|
||||
* @param string|null $ckey The cipher key to decrypt the payload
|
||||
* @param string|null $cipherMethod The method to cipher the payload
|
||||
* des-ede3-cbc by default
|
||||
* @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,
|
||||
$ckey = null,
|
||||
$cipherMethod = "des-ede3-cbc"
|
||||
) {
|
||||
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 = (object)$this->jsonDecode($this->urlsafeB64Decode($headerb64));
|
||||
$payload = $this->urlsafeB64Decode($payloadb64);
|
||||
if ($ckey) {
|
||||
$encrypt = new Encrypt();
|
||||
$payload = $encrypt->decrypt($payload, $ckey);
|
||||
}
|
||||
$payload = $this->jsonDecode($payload);
|
||||
$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;
|
||||
}
|
||||
$payload = $this->jsonDecode ($payload);
|
||||
$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)
|
||||
/** 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)
|
||||
{
|
||||
case 'hash_hmac':
|
||||
return hash_hmac ($algorithm, $input, $key, true);
|
||||
default:
|
||||
throw new \Exception (dgettext ("domframework",
|
||||
"Invalid method to sign the JWT"), 500);
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
/** 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)
|
||||
/** Create a signing key
|
||||
* @return string the signing key proposed
|
||||
*/
|
||||
public function createKey()
|
||||
{
|
||||
case 0: break;
|
||||
case 2: $str .= "=="; break;
|
||||
case 3: $str .= "="; break;
|
||||
default:
|
||||
return false;
|
||||
return sha1(microtime(true));
|
||||
}
|
||||
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;
|
||||
}
|
||||
/** 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);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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);
|
||||
}
|
||||
/** 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user