File: //usr/local/CyberCP/public/snappymail/snappymail/v/2.38.2/app/libraries/snappymail/jwt.php
<?php
namespace SnappyMail;
use MailSo\Base\Utils;
abstract class JWT
{
private static function jsonEncode($data)
{
return Utils::UrlSafeBase64Encode(\json_encode($payload, JSON_THROW_ON_ERROR));
}
private static function jsonDecode($data)
{
$data = Utils::UrlSafeBase64Decode($data);
return \json_decode($data, false, 8, JSON_BIGINT_AS_STRING | JSON_THROW_ON_ERROR);
}
/**
* Decodes a JWT string into a PHP object.
*
* @param string $jwt The JWT
* @param string|array $key The key, or map of keys.
* If the algorithm used is asymmetric, this is the public key
* @param int $timestamp Allow the current timestamp to be specified. default time()
* @param int $leeway When checking nbf (Not Before) Claim, iat (Issued At) Claim or expiration times, we want to provide some extra leeway time to account for clock skew.
*/
public static function decode(string $jwt, $key, ?int $timestamp = null, int $leeway = 0) : object
{
$timestamp = $timestamp ?: \time();
if (empty($key)) {
throw new \ValueError('Key may not be empty');
}
$jwt = \explode('.', $jwt);
if (\count($jwt) != 3) {
throw new \UnexpectedValueException('Wrong number of segments');
}
try {
$header = static::jsonDecode($jwt[0]);
} catch (\Throwable $e) {
throw new \UnexpectedValueException("Invalid header encoding ({$e->getMessage()})");
}
try {
$payload = static::jsonDecode($jwt[1]);
} catch (\Throwable $e) {
throw new \UnexpectedValueException("Invalid claims encoding ({$e->getMessage()})");
}
$sig = Utils::UrlSafeBase64Decode($jwt[2]);
if (empty($header->alg)) {
throw new \UnexpectedValueException('Empty algorithm');
}
if (\is_array($key) || $key instanceof \ArrayAccess) {
if (!isset($header->kid)) {
throw new \UnexpectedValueException('"kid" empty, unable to lookup correct key');
}
if (!isset($key[$header->kid])) {
throw new \DomainException('No valid key found');
}
$key = $key[$header->kid];
}
// Check the signature
if (!static::verify("{$jwt[0]}.{$jwt[1]}", $sig, $key, $header->alg)) {
throw new \Exception('Signature verification failed');
}
// Check if the nbf if it is defined. This is the time that the
// token can actually be used. If it's not yet that time, abort.
if (isset($payload->nbf) && $payload->nbf > ($timestamp + $leeway)) {
throw new \Exception('Cannot handle token prior to ' . \date(\DateTime::ISO8601, $payload->nbf));
}
// Check that this token has been created before 'now'. This prevents
// using tokens that have been created for later use (and haven't
// correctly used the nbf claim).
if (isset($payload->iat) && $payload->iat > ($timestamp + $leeway)) {
throw new \Exception('Cannot handle token prior to ' . \date(\DateTime::ISO8601, $payload->iat));
}
// Check if this token has expired.
if (isset($payload->exp) && ($timestamp - $leeway) >= $payload->exp) {
throw new \Exception('Expired token');
}
return $payload;
}
/**
* Converts and signs a PHP object or array into a JWT string.
*
* @param object|array $payload PHP object or array
* @param string $key The private/secret key.
* @param string $alg The signing algorithm.
* Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
* @param mixed $keyId
* @param array $head An array with header elements to attach
*
* @return string A signed JWT
*/
public static function encode($payload, string $key, string $alg = 'HS256', $keyId = null, array $header = array()) : string
{
$header = \array_merge($header, array('typ' => 'JWT', 'alg' => $alg));
if ($keyId) {
$header['kid'] = $keyId;
}
$segments = array(
static::jsonEncode($header),
static::jsonEncode($payload)
);
$segments[] = Utils::UrlSafeBase64Encode(static::sign(\implode('.', $segments), $key, $alg));
return \implode('.', $segments);
}
/**
* Sign a string with a given key and algorithm.
*
* @param string $msg The message to sign
* @param string|resource $key The secret key
* @param string $alg The signing algorithm.
* Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
*
* @return string An encrypted message
*/
public static function sign(string $msg, $key, string $alg = 'HS256', string $passphrase = '') : string
{
switch ($alg)
{
case 'RS256':
case 'RS384':
case 'RS512':
$free_key = !\is_resource($key);
if ($free_key) {
$key = \openssl_pkey_get_private($key, $passphrase);
if (!$key) {
throw new \ValueError('Invalid key, reason: ' . \openssl_error_string());
}
}
try {
$details = \openssl_pkey_get_details($key);
if (!isset($details['key']) || OPENSSL_KEYTYPE_RSA !== $details['type']) {
throw new \ValueError('Key is not compatible with RSA signatures');
}
$signature = '';
if (!\openssl_sign($msg, $signature, $key, 'SHA'.\substr($alg,2))) {
throw new \DomainException('OpenSSL unable to sign data: ' . \openssl_error_string());
}
} finally {
if ($free_key) {
\openssl_pkey_free($key);
}
}
return $signature;
case 'HS256':
case 'HS512':
case 'HS384':
$algo = 'SHA' . \substr($alg, 2);
if (\in_array($algo, \hash_algos())) {
return \hash_hmac($algo, $msg, $key, true);
}
default:
throw new \ValueError("Algorithm '{$alg}' not supported");
}
}
/**
* Verify a signature with the message, key and method. Not all methods
* are symmetric, so we must have a separate verify and sign method.
*
* @param string $msg The original message (header and body)
* @param string $signature The original signature
* @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key
* @param string $alg The algorithm
*
* @return bool
*/
private static function verify(string $msg, string $signature, $key, string $alg) : bool
{
switch ($alg)
{
case 'RS256':
case 'RS384':
case 'RS512':
$free_key = !\is_resource($key);
if ($free_key) {
$key = \openssl_pkey_get_public($key);
if (!$key) {
throw new \ValueError('Invalid key, reason: ' . openssl_error_string());
}
}
try {
$details = \openssl_pkey_get_details($key);
if (!isset($details['key']) || OPENSSL_KEYTYPE_RSA !== $details['type']) {
throw new \ValueError('Key is not compatible with RSA signatures');
}
$success = \openssl_verify($msg, $signature, $key, 'SHA'.\substr($alg,2));
if (-1 == $success) {
throw new \DomainException('OpenSSL unable to verify data: ' . \openssl_error_string());
}
} finally {
if ($free_key) {
\openssl_pkey_free($key);
}
}
return $success;
case 'HS256':
case 'HS512':
case 'HS384':
$algo = 'SHA' . \substr($alg, 2);
if (\in_array($algo, \hash_algos())) {
return \hash_equals($signature, \hash_hmac($algo, $msg, $key, true));
}
// Ecdsa
// case 'ES256':
// case 'ES384':
// case 'ES512':
default:
throw new \ValueError("Algorithm '{$alg}' not supported");
}
}
}