[Web] Update composer libraries

This commit is contained in:
FreddleSpl0it committed 2026-10-28 11:56:29 +01:00
1 parent 9cc8ac2908
commit bd8c784118
1297 files changed
+71585 -24460

No files matched your search

@@ -2,6 +2,17 @@
namespace Firebase\JWT;
class BeforeValidException extends \UnexpectedValueException
class BeforeValidException extends \UnexpectedValueException implements JWTExceptionWithPayloadInterface
{
private object $payload;
public function setPayload(object $payload): void
{
$this->payload = $payload;
}
public function getPayload(): object
{
return $this->payload;
}
}
@@ -80,9 +80,9 @@ class CachedKeySet implements ArrayAccess
ClientInterface $httpClient,
RequestFactoryInterface $httpFactory,
CacheItemPoolInterface $cache,
int $expiresAfter = null,
?int $expiresAfter = null,
bool $rateLimit = false,
string $defaultAlg = null
?string $defaultAlg = null
) {
$this->jwksUri = $jwksUri;
$this->httpClient = $httpClient;
@@ -178,6 +178,17 @@ class CachedKeySet implements ArrayAccess
}
$request = $this->httpFactory->createRequest('GET', $this->jwksUri);
$jwksResponse = $this->httpClient->sendRequest($request);
if ($jwksResponse->getStatusCode() !== 200) {
throw new UnexpectedValueException(
\sprintf(
'HTTP Error: %d %s for URI "%s"',
$jwksResponse->getStatusCode(),
$jwksResponse->getReasonPhrase(),
$this->jwksUri,
),
$jwksResponse->getStatusCode()
);
}
$this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody());
if (!isset($this->keySet[$keyId])) {
@@ -202,15 +213,21 @@ class CachedKeySet implements ArrayAccess
}
$cacheItem = $this->cache->getItem($this->rateLimitCacheKey);
if (!$cacheItem->isHit()) {
$cacheItem->expiresAfter(1); // # of calls are cached each minute
$cacheItemData = [];
if ($cacheItem->isHit() && \is_array($data = $cacheItem->get())) {
$cacheItemData = $data;
}
$callsPerMinute = (int) $cacheItem->get();
$callsPerMinute = $cacheItemData['callsPerMinute'] ?? 0;
$expiry = $cacheItemData['expiry'] ?? new \DateTime('+60 seconds', new \DateTimeZone('UTC'));
if (++$callsPerMinute > $this->maxCallsPerMinute) {
return true;
}
$cacheItem->set($callsPerMinute);
$cacheItem->set(['expiry' => $expiry, 'callsPerMinute' => $callsPerMinute]);
$cacheItem->expiresAt($expiry);
$this->cache->save($cacheItem);
return false;
}
@@ -2,6 +2,29 @@
namespace Firebase\JWT;
class ExpiredException extends \UnexpectedValueException
class ExpiredException extends \UnexpectedValueException implements JWTExceptionWithPayloadInterface
{
private object $payload;
private ?int $timestamp = null;
public function setPayload(object $payload): void
{
$this->payload = $payload;
}
public function getPayload(): object
{
return $this->payload;
}
public function setTimestamp(int $timestamp): void
{
$this->timestamp = $timestamp;
}
public function getTimestamp(): ?int
{
return $this->timestamp;
}
}
+61 -8
View File
@@ -27,10 +27,25 @@ class JWK
private const EC_CURVES = [
'P-256' => '1.2.840.10045.3.1.7', // Len: 64
'secp256k1' => '1.3.132.0.10', // Len: 64
// 'P-384' => '1.3.132.0.34', // Len: 96 (not yet supported)
'P-384' => '1.3.132.0.34', // Len: 96
// 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
];
// Known standard curves from the IANA JOSE registry which are not supported
private const KNOWN_UNSUPPORTED_EC_CURVES = [
'P-521', // RFC 7518
'Ed25519', // RFC 8037
'Ed448', // RFC 8037
'X25519', // RFC 8037
'X448' // RFC 8037
];
// For keys with "kty" equal to "OKP" (Octet Key Pair), the "crv" parameter must contain the key subtype.
// This library supports the following subtypes:
private const OKP_SUBTYPES = [
'Ed25519' => true, // RFC 8037
];
/**
* Parse a set of JWK keys
*
@@ -46,7 +61,7 @@ class JWK
*
* @uses parseKey
*/
public static function parseKeySet(array $jwks, string $defaultAlg = null): array
public static function parseKeySet(#[\SensitiveParameter] array $jwks, ?string $defaultAlg = null): array
{
$keys = [];
@@ -87,7 +102,7 @@ class JWK
*
* @uses createPemFromModulusAndExponent
*/
public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
public static function parseKey(#[\SensitiveParameter] array $jwk, ?string $defaultAlg = null): ?Key
{
if (empty($jwk)) {
throw new InvalidArgumentException('JWK must not be empty');
@@ -136,7 +151,10 @@ class JWK
}
if (!isset(self::EC_CURVES[$jwk['crv']])) {
throw new DomainException('Unrecognised or unsupported EC curve');
if (!\in_array($jwk['crv'], self::KNOWN_UNSUPPORTED_EC_CURVES)) {
throw new DomainException('Unrecognised EC curve');
}
return null;
}
if (empty($jwk['x']) || empty($jwk['y'])) {
@@ -145,8 +163,34 @@ class JWK
$publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']);
return new Key($publicKey, $jwk['alg']);
case 'OKP':
if (isset($jwk['d'])) {
// The key is actually a private key
throw new UnexpectedValueException('Key data must be for a public key');
}
if (!isset($jwk['crv'])) {
throw new UnexpectedValueException('crv not set');
}
if (empty(self::OKP_SUBTYPES[$jwk['crv']])) {
throw new DomainException('Unrecognised or unsupported OKP key subtype');
}
if (empty($jwk['x'])) {
throw new UnexpectedValueException('x not set');
}
// This library works internally with EdDSA keys (Ed25519) encoded in standard base64.
$publicKey = JWT::convertBase64urlToBase64($jwk['x']);
return new Key($publicKey, $jwk['alg']);
case 'oct':
if (!isset($jwk['k'])) {
throw new UnexpectedValueException('k not set');
}
return new Key(JWT::urlsafeB64Decode($jwk['k']), $jwk['alg']);
default:
// Currently only RSA is supported
break;
}
@@ -156,7 +200,7 @@ class JWK
/**
* Converts the EC JWK values to pem format.
*
* @param string $crv The EC curve (only P-256 is supported)
* @param string $crv The EC curve (only P-256 & P-384 is supported)
* @param string $x The EC x-coordinate
* @param string $y The EC y-coordinate
*
@@ -186,7 +230,7 @@ class JWK
)
);
return sprintf(
return \sprintf(
"-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
wordwrap(base64_encode($pem), 64, "\n", true)
);
@@ -208,6 +252,14 @@ class JWK
): string {
$mod = JWT::urlsafeB64Decode($n);
$exp = JWT::urlsafeB64Decode($e);
// Correct encoding for ASN1, as ints are represented as unsigned in jwk
// but signed in ASN1. Prepending null byte makes it unsigned.
if (\strlen($mod) > 0 && \ord($mod[0]) >= 128) {
$mod = \chr(0) . $mod;
}
if (\strlen($exp) > 0 && \ord($exp[0]) >= 128) {
$exp = \chr(0) . $exp;
}
$modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod);
$publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp);
@@ -241,7 +293,8 @@ class JWK
* DER-encode the length
*
* DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
* {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
* {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3}
* for more information.
*
* @param int $length
* @return string
+254 -61
View File
@@ -31,6 +31,8 @@ class JWT
private const ASN1_SEQUENCE = 0x10;
private const ASN1_BIT_STRING = 0x03;
private const RSA_KEY_MIN_LENGTH = 2048;
/**
* When checking nbf, iat or expiration times,
* we want to provide some extra leeway time to
@@ -62,7 +64,8 @@ class JWT
'RS256' => ['openssl', 'SHA256'],
'RS384' => ['openssl', 'SHA384'],
'RS512' => ['openssl', 'SHA512'],
'EdDSA' => ['sodium_crypto', 'EdDSA'],
'PS256' => ['openssl', 'SHA256'],
'EdDSA' => ['sodium_crypto', 'EdDSA']
];
/**
@@ -78,6 +81,7 @@ class JWT
* Supported algorithms are 'ES384','ES256',
* 'HS256', 'HS384', 'HS512', 'RS256', 'RS384'
* and 'RS512'.
* @param stdClass $headers Optional. Populates stdClass with headers.
*
* @return stdClass The JWT's payload as a PHP object
*
@@ -86,7 +90,8 @@ class JWT
* @throws UnexpectedValueException Provided JWT was invalid
* @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
* @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
* @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'
* @throws BeforeValidException Provided JWT is trying to be used before it's
* been created as defined by 'iat'
* @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim
*
* @uses jsonDecode
@@ -94,7 +99,8 @@ class JWT
*/
public static function decode(
string $jwt,
$keyOrKeyArray
#[\SensitiveParameter] $keyOrKeyArray,
?stdClass &$headers = null
): stdClass {
// Validate JWT
$timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp;
@@ -102,7 +108,7 @@ class JWT
if (empty($keyOrKeyArray)) {
throw new InvalidArgumentException('Key may not be empty');
}
$tks = \explode('.', $jwt);
$tks = \explode('.', $jwt, 4);
if (\count($tks) !== 3) {
throw new UnexpectedValueException('Wrong number of segments');
}
@@ -111,6 +117,9 @@ class JWT
if (null === ($header = static::jsonDecode($headerRaw))) {
throw new UnexpectedValueException('Invalid header encoding');
}
if ($headers !== null) {
$headers = $header;
}
$payloadRaw = static::urlsafeB64Decode($bodyb64);
if (null === ($payload = static::jsonDecode($payloadRaw))) {
throw new UnexpectedValueException('Invalid claims encoding');
@@ -122,6 +131,16 @@ class JWT
if (!$payload instanceof stdClass) {
throw new UnexpectedValueException('Payload must be a JSON object');
}
if (isset($payload->iat) && !\is_numeric($payload->iat)) {
throw new UnexpectedValueException('Payload iat must be a number');
}
if (isset($payload->nbf) && !\is_numeric($payload->nbf)) {
throw new UnexpectedValueException('Payload nbf must be a number');
}
if (isset($payload->exp) && !\is_numeric($payload->exp)) {
throw new UnexpectedValueException('Payload exp must be a number');
}
$sig = static::urlsafeB64Decode($cryptob64);
if (empty($header->alg)) {
throw new UnexpectedValueException('Empty algorithm');
@@ -147,24 +166,31 @@ class JWT
// Check 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 + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf)
if (isset($payload->nbf) && floor($payload->nbf) > ($timestamp + static::$leeway)) {
$ex = new BeforeValidException(
'Cannot handle token with nbf prior to ' . \date(DateTime::ATOM, (int) floor($payload->nbf))
);
$ex->setPayload($payload);
throw $ex;
}
// 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 + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat)
if (!isset($payload->nbf) && isset($payload->iat) && floor($payload->iat) > ($timestamp + static::$leeway)) {
$ex = new BeforeValidException(
'Cannot handle token with iat prior to ' . \date(DateTime::ATOM, (int) floor($payload->iat))
);
$ex->setPayload($payload);
throw $ex;
}
// Check if this token has expired.
if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
throw new ExpiredException('Expired token');
$ex = new ExpiredException('Expired token');
$ex->setPayload($payload);
$ex->setTimestamp($timestamp);
throw $ex;
}
return $payload;
@@ -174,11 +200,11 @@ class JWT
* Converts and signs a PHP array into a JWT string.
*
* @param array<mixed> $payload PHP array
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
* @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
* 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
* @param string $keyId
* @param array<string, string> $head An array with header elements to attach
* @param array<string, string|string[]> $head An array with header elements to attach
*
* @return string A signed JWT
*
@@ -187,17 +213,27 @@ class JWT
*/
public static function encode(
array $payload,
$key,
#[\SensitiveParameter] $key,
string $alg,
string $keyId = null,
array $head = null
?string $keyId = null,
?array $head = null
): string {
$header = ['typ' => 'JWT', 'alg' => $alg];
$header = ['typ' => 'JWT'];
if (isset($head)) {
$header = \array_merge($header, $head);
}
$header['alg'] = $alg;
if ($keyId !== null) {
$header['kid'] = $keyId;
}
if (isset($head) && \is_array($head)) {
$header = \array_merge($head, $header);
if (isset($payload['nbf']) && !\is_numeric($payload['nbf'])) {
throw new UnexpectedValueException('Payload nbf must be a number');
}
if (isset($payload['iat']) && !\is_numeric($payload['iat'])) {
throw new UnexpectedValueException('Payload iat must be a number');
}
if (isset($payload['exp']) && !\is_numeric($payload['exp'])) {
throw new UnexpectedValueException('Payload exp must be a number');
}
$segments = [];
$segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header));
@@ -214,9 +250,9 @@ class JWT
* Sign a string with a given key and algorithm.
*
* @param string $msg The message to sign
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
* @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
* 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
* @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'ES256K', 'HS256',
* 'HS384', 'HS512', 'RS256', 'RS384', 'PS256' and 'RS512'
*
* @return string An encrypted message
*
@@ -224,7 +260,7 @@ class JWT
*/
public static function sign(
string $msg,
$key,
#[\SensitiveParameter] $key,
string $alg
): string {
if (empty(static::$supported_algs[$alg])) {
@@ -236,10 +272,22 @@ class JWT
if (!\is_string($key)) {
throw new InvalidArgumentException('key must be a string when using hmac');
}
self::validateHmacKeyLength($key, $algorithm);
return \hash_hmac($algorithm, $msg, $key, true);
case 'openssl':
if ($alg === 'PS256') {
return self::signPS256($key, $msg);
}
$signature = '';
$success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line
if (!$key = openssl_pkey_get_private($key)) {
throw new DomainException('OpenSSL unable to validate key');
}
if (str_starts_with($alg, 'RS')) {
self::validateRsaKeyLength($key);
} elseif (str_starts_with($alg, 'ES')) {
self::validateEcKeyLength($key, $alg);
}
$success = \openssl_sign($msg, $signature, $key, $algorithm);
if (!$success) {
throw new DomainException('OpenSSL unable to sign data');
}
@@ -250,20 +298,8 @@ class JWT
}
return $signature;
case 'sodium_crypto':
if (!\function_exists('sodium_crypto_sign_detached')) {
throw new DomainException('libsodium is not available');
}
if (!\is_string($key)) {
throw new InvalidArgumentException('key must be a string when using EdDSA');
}
try {
// The last non-empty line is used as the key.
$lines = array_filter(explode("\n", $key));
$key = base64_decode((string) end($lines));
if (\strlen($key) === 0) {
throw new DomainException('Key cannot be empty string');
}
return sodium_crypto_sign_detached($msg, $key);
return sodium_crypto_sign_detached($msg, self::validateEdDSAKey($key));
} catch (Exception $e) {
throw new DomainException($e->getMessage(), 0, $e);
}
@@ -278,7 +314,8 @@ class JWT
*
* @param string $msg The original message (header and body)
* @param string $signature The original signature
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works.
* for RS*, must be an instance of OpenSSLAsymmetricKey
* @param string $alg The algorithm
*
* @return bool
@@ -288,7 +325,7 @@ class JWT
private static function verify(
string $msg,
string $signature,
$keyMaterial,
#[\SensitiveParameter] $keyMaterial,
string $alg
): bool {
if (empty(static::$supported_algs[$alg])) {
@@ -298,7 +335,18 @@ class JWT
list($function, $algorithm) = static::$supported_algs[$alg];
switch ($function) {
case 'openssl':
$success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line
if ($alg === 'PS256') {
return self::verifyPS256($keyMaterial, $msg, $signature);
}
if (!$key = openssl_pkey_get_public($keyMaterial)) {
throw new DomainException('OpenSSL unable to validate key');
}
if (str_starts_with($alg, 'RS')) {
self::validateRsaKeyLength($key);
} elseif (str_starts_with($alg, 'ES')) {
self::validateEcKeyLength($key, $alg);
}
$success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm);
if ($success === 1) {
return true;
}
@@ -310,19 +358,8 @@ class JWT
'OpenSSL error: ' . \openssl_error_string()
);
case 'sodium_crypto':
if (!\function_exists('sodium_crypto_sign_verify_detached')) {
throw new DomainException('libsodium is not available');
}
if (!\is_string($keyMaterial)) {
throw new InvalidArgumentException('key must be a string when using EdDSA');
}
try {
// The last non-empty line is used as the key.
$lines = array_filter(explode("\n", $keyMaterial));
$key = base64_decode((string) end($lines));
if (\strlen($key) === 0) {
throw new DomainException('Key cannot be empty string');
}
$key = self::validateEdDSAKey($keyMaterial);
if (\strlen($signature) === 0) {
throw new DomainException('Signature cannot be empty string');
}
@@ -335,6 +372,7 @@ class JWT
if (!\is_string($keyMaterial)) {
throw new InvalidArgumentException('key must be a string when using hmac');
}
self::validateHmacKeyLength($keyMaterial, $algorithm);
$hash = \hash_hmac($algorithm, $msg, $keyMaterial, true);
return self::constantTimeEquals($hash, $signature);
}
@@ -372,12 +410,7 @@ class JWT
*/
public static function jsonEncode(array $input): string
{
if (PHP_VERSION_ID >= 50400) {
$json = \json_encode($input, \JSON_UNESCAPED_SLASHES);
} else {
// PHP 5.3 only
$json = \json_encode($input);
}
$json = \json_encode($input, \JSON_UNESCAPED_SLASHES);
if ($errno = \json_last_error()) {
self::handleJsonError($errno);
} elseif ($json === 'null') {
@@ -399,13 +432,28 @@ class JWT
* @throws InvalidArgumentException invalid base64 characters
*/
public static function urlsafeB64Decode(string $input): string
{
return \base64_decode(self::convertBase64UrlToBase64($input));
}
/**
* Convert a string in the base64url (URL-safe Base64) encoding to standard base64.
*
* @param string $input A Base64 encoded string with URL-safe characters (-_ and no padding)
*
* @return string A Base64 encoded string with standard characters (+/) and padding (=), when
* needed.
*
* @see https://www.rfc-editor.org/rfc/rfc4648
*/
public static function convertBase64UrlToBase64(string $input): string
{
$remainder = \strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= \str_repeat('=', $padlen);
}
return \base64_decode(\strtr($input, '-_', '+/'));
return \strtr($input, '-_', '+/');
}
/**
@@ -420,7 +468,6 @@ class JWT
return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
}
/**
* Determine if an algorithm has been provided for each Key
*
@@ -432,7 +479,7 @@ class JWT
* @return Key
*/
private static function getKey(
$keyOrKeyArray,
#[\SensitiveParameter] $keyOrKeyArray,
?string $kid
): Key {
if ($keyOrKeyArray instanceof Key) {
@@ -639,4 +686,150 @@ class JWT
return [$pos, $data];
}
/**
* Validate HMAC key length
*
* @param string $key HMAC key material
* @param string $algorithm The algorithm
*
* @throws DomainException Provided key is too short
*/
private static function validateHmacKeyLength(string $key, string $algorithm): void
{
$keyLength = \strlen($key) * 8;
$minKeyLength = (int) \str_replace('SHA', '', $algorithm);
if ($keyLength < $minKeyLength) {
throw new DomainException('Provided key is too short');
}
}
/**
* Validate RSA key length
*
* @param OpenSSLAsymmetricKey $key RSA key material
* @throws DomainException Provided key is too short
*/
private static function validateRsaKeyLength(#[\SensitiveParameter] OpenSSLAsymmetricKey $key): void
{
if (!$keyDetails = openssl_pkey_get_details($key)) {
throw new DomainException('Unable to validate key');
}
if ($keyDetails['bits'] < self::RSA_KEY_MIN_LENGTH) {
throw new DomainException('Provided key is too short');
}
}
/**
* Validate RSA key length
*
* @param OpenSSLAsymmetricKey $key RSA key material
* @param string $algorithm The algorithm
* @throws DomainException Provided key is too short
*/
private static function validateEcKeyLength(
#[\SensitiveParameter] OpenSSLAsymmetricKey $key,
string $algorithm
): void {
if (!$keyDetails = openssl_pkey_get_details($key)) {
throw new DomainException('Unable to validate key');
}
$minKeyLength = (int) \str_replace('ES', '', $algorithm);
if ($keyDetails['bits'] < $minKeyLength) {
throw new DomainException('Provided key is too short');
}
}
/**
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial
* @return non-empty-string
*/
private static function validateEdDSAKey(#[\SensitiveParameter] $keyMaterial): string
{
if (!\function_exists('sodium_crypto_sign_verify_detached')) {
throw new DomainException('libsodium is not available');
}
if (!\is_string($keyMaterial)) {
throw new InvalidArgumentException('key must be a string when using EdDSA');
}
// The last non-empty line is used as the key.
$lines = array_filter(explode("\n", $keyMaterial));
$key = self::urlsafeB64Decode((string) end($lines));
if (\strlen($key) === 0) {
throw new DomainException('Key cannot be empty string');
}
return $key;
}
/**
* Signs a message with a PS256 algorithm
*
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key
* @param string $message
* @throws DomainException Provided key is invalid
*/
private static function signPS256(
#[\SensitiveParameter] string|OpenSSLAsymmetricKey|OpenSSLCertificate $key,
string $message
): string {
if (!class_exists('\phpseclib3\Crypt\RSA')) {
throw new DomainException('phpseclib/phpseclib is required for PS256 support');
}
if ($key instanceof OpenSSLCertificate) {
throw new DomainException('Cannot sign with an X.509 certificate. A private key is required.');
}
if ($key instanceof OpenSSLAsymmetricKey) {
if (!openssl_pkey_export($key, $pem)) {
throw new DomainException('OpenSSL unable to export the AsymmetricKey');
}
$key = $pem;
}
/** @var \phpseclib3\Crypt\RSA\PrivateKey $rsa */
$rsa = \phpseclib3\Crypt\PublicKeyLoader::load($key);
return $rsa->withPadding(\phpseclib3\Crypt\RSA::SIGNATURE_PSS)
->withHash('sha256')
->sign($message);
}
/**
* Validates a PS256 algorithm signature.
*
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key
* @param string $message
* @param string $signature
* @throws DomainException Provided key is invalid
*/
private static function verifyPS256(
#[\SensitiveParameter] string|OpenSSLAsymmetricKey|OpenSSLCertificate $key,
string $message,
string $signature
): bool {
if (!class_exists('\phpseclib3\Crypt\RSA')) {
throw new DomainException('phpseclib/phpseclib is required for PS256 support');
}
if ($key instanceof OpenSSLAsymmetricKey) {
$details = openssl_pkey_get_details($key);
if (!$details || !isset($details['key'])) {
throw new DomainException('OpenSSL unable to extract public key');
}
$key = $details['key'];
} elseif ($key instanceof OpenSSLCertificate) {
if (!openssl_x509_export($key, $pem)) {
throw new DomainException('OpenSSL unable to export certificate');
}
$key = $pem;
}
/** @var \phpseclib3\Crypt\RSA\PublicKey $rsa */
$rsa = \phpseclib3\Crypt\PublicKeyLoader::load($key);
return $rsa->withPadding(\phpseclib3\Crypt\RSA::SIGNATURE_PSS)
->withHash('sha256')
->verify($message, $signature);
}
}
@@ -0,0 +1,20 @@
<?php
namespace Firebase\JWT;
interface JWTExceptionWithPayloadInterface
{
/**
* Get the payload that caused this exception.
*
* @return object
*/
public function getPayload(): object;
/**
* Get the payload that caused this exception.
*
* @param object $payload
* @return void
*/
public function setPayload(object $payload): void;
}
+6 -17
View File
@@ -9,26 +9,19 @@ use TypeError;
class Key
{
/** @var string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate */
private $keyMaterial;
/** @var string */
private $algorithm;
/**
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial
* @param string $algorithm
*/
public function __construct(
$keyMaterial,
string $algorithm
#[\SensitiveParameter] private $keyMaterial,
private string $algorithm
) {
if (
!\is_string($keyMaterial)
if (!\is_string($keyMaterial)
&& !$keyMaterial instanceof OpenSSLAsymmetricKey
&& !$keyMaterial instanceof OpenSSLCertificate
&& !\is_resource($keyMaterial)
) {
throw new TypeError('Key material must be a string, resource, or OpenSSLAsymmetricKey');
throw new TypeError('Key material must be a string, OpenSSLCertificate, or OpenSSLAsymmetricKey');
}
if (empty($keyMaterial)) {
@@ -38,10 +31,6 @@ class Key
if (empty($algorithm)) {
throw new InvalidArgumentException('Algorithm must not be empty');
}
// TODO: Remove in PHP 8.0 in favor of class constructor property promotion
$this->keyMaterial = $keyMaterial;
$this->algorithm = $algorithm;
}
/**
@@ -55,7 +44,7 @@ class Key
}
/**
* @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate
* @return string|OpenSSLAsymmetricKey|OpenSSLCertificate
*/
public function getKeyMaterial()
{