mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-10-29 20:53:30 +00:00
[Web] Update composer libraries
This commit is contained in:
1297 files changed
+71585
-24460
No files matched your search
@@ -1,5 +1,160 @@
|
||||
# Changelog
|
||||
|
||||
## [7.1.1](https://github.com/googleapis/php-jwt/compare/v7.1.0...v7.1.1) (2026-09-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Limit the number of segments a JWT can be exploded into ([#639](https://github.com/googleapis/php-jwt/issues/639)) ([93d248c](https://github.com/googleapis/php-jwt/commit/93d248c650aea615d74a5ffa20c91c7a818e9b8d))
|
||||
|
||||
## [7.1.0](https://github.com/googleapis/php-jwt/compare/v7.0.5...v7.1.0) (2026-06-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add support for PS256 ([#637](https://github.com/googleapis/php-jwt/issues/637)) ([aa0b447](https://github.com/googleapis/php-jwt/commit/aa0b4472c137255545b177a1840891304eac55f1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Skip parsing valid unsupported curves ([#638](https://github.com/googleapis/php-jwt/issues/638)) ([8a6c2a7](https://github.com/googleapis/php-jwt/commit/8a6c2a75d7f45e781c93d618939f8a045fe69f53))
|
||||
* Update homepage URL in composer.json ([#635](https://github.com/googleapis/php-jwt/issues/635)) ([f7ae571](https://github.com/googleapis/php-jwt/commit/f7ae571efe255aed89c8f8d0f2cb7f71c5835664))
|
||||
* Validate numeric type of iat, nbf and exp claims in encode ([#634](https://github.com/googleapis/php-jwt/issues/634)) ([958e422](https://github.com/googleapis/php-jwt/commit/958e422961e874843a7fcc339e55a0101a66201d))
|
||||
|
||||
## [7.0.5](https://github.com/firebase/php-jwt/compare/v7.0.4...v7.0.5) (2026-03-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* RSA from JWK sometimes returns empty Instance ([#628](https://github.com/firebase/php-jwt/issues/628)) ([b4c78aa](https://github.com/firebase/php-jwt/commit/b4c78aa731664122198ad36c0033aa29e807397a))
|
||||
|
||||
## [7.0.4](https://github.com/firebase/php-jwt/compare/v7.0.3...v7.0.4) (2026-03-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* readme examples, add tests for all examples ([#626](https://github.com/firebase/php-jwt/issues/626)) ([510a00c](https://github.com/firebase/php-jwt/commit/510a00c0e6353bc7d68412fab67e57a13954cb46))
|
||||
* use urlsafeB64Decode everywhere ([#627](https://github.com/firebase/php-jwt/issues/627)) ([b889495](https://github.com/firebase/php-jwt/commit/b889495c83ddc3f3885ca3f0b65b41b1cb37a3b1))
|
||||
|
||||
## [7.0.3](https://github.com/firebase/php-jwt/compare/v7.0.2...v7.0.3) (2026-02-18)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* add environment for Release Please job ([#619](https://github.com/firebase/php-jwt/issues/619)) ([300fd02](https://github.com/firebase/php-jwt/commit/300fd02c883f096c9067df652dbd23f62cb5e2a7))
|
||||
|
||||
## [7.0.2](https://github.com/firebase/php-jwt/compare/v7.0.1...v7.0.2) (2025-12-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add key length validation for ec keys ([#615](https://github.com/firebase/php-jwt/issues/615)) ([7044f9a](https://github.com/firebase/php-jwt/commit/7044f9ae7e7d175d28cca71714feb236f1c0e252))
|
||||
|
||||
## [7.0.0](https://github.com/firebase/php-jwt/compare/v6.11.1...v7.0.0) (2025-12-15)
|
||||
|
||||
|
||||
### ⚠️ ⚠️ ⚠️ Security Fixes ⚠️ ⚠️ ⚠️
|
||||
* add key size validation ([#613](https://github.com/firebase/php-jwt/issues/613)) ([6b80341](https://github.com/firebase/php-jwt/commit/6b80341bf57838ea2d011487917337901cd71576))
|
||||
**NOTE**: This fix will cause keys with a size below the minimally allowed size to break.
|
||||
|
||||
### Features
|
||||
|
||||
* add SensitiveParameter attribute to security-critical parameters ([#603](https://github.com/firebase/php-jwt/issues/603)) ([4dbfac0](https://github.com/firebase/php-jwt/commit/4dbfac0260eeb0e9e643063c99998e3219cc539b))
|
||||
* store timestamp in `ExpiredException` ([#604](https://github.com/firebase/php-jwt/issues/604)) ([f174826](https://github.com/firebase/php-jwt/commit/f1748260d218a856b6a0c23715ac7fae1d7ca95b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* validate iat and nbf on payload ([#568](https://github.com/firebase/php-jwt/issues/568)) ([953b2c8](https://github.com/firebase/php-jwt/commit/953b2c88bb445b7e3bb82a5141928f13d7343afd))
|
||||
|
||||
## [6.11.1](https://github.com/firebase/php-jwt/compare/v6.11.0...v6.11.1) (2025-04-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update error text for consistency ([#528](https://github.com/firebase/php-jwt/issues/528)) ([c11113a](https://github.com/firebase/php-jwt/commit/c11113afa13265e016a669e75494b9203b8a7775))
|
||||
|
||||
## [6.11.0](https://github.com/firebase/php-jwt/compare/v6.10.2...v6.11.0) (2025-01-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support octet typed JWK ([#587](https://github.com/firebase/php-jwt/issues/587)) ([7cb8a26](https://github.com/firebase/php-jwt/commit/7cb8a265fa81edf2fa6ef8098f5bc5ae573c33ad))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* refactor constructor Key to use PHP 8.0 syntax ([#577](https://github.com/firebase/php-jwt/issues/577)) ([29fa2ce](https://github.com/firebase/php-jwt/commit/29fa2ce9e0582cd397711eec1e80c05ce20fabca))
|
||||
|
||||
## [6.10.2](https://github.com/firebase/php-jwt/compare/v6.10.1...v6.10.2) (2024-11-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Mitigate PHP8.4 deprecation warnings ([#570](https://github.com/firebase/php-jwt/issues/570)) ([76808fa](https://github.com/firebase/php-jwt/commit/76808fa227f3811aa5cdb3bf81233714b799a5b5))
|
||||
* support php 8.4 ([#583](https://github.com/firebase/php-jwt/issues/583)) ([e3d68b0](https://github.com/firebase/php-jwt/commit/e3d68b044421339443c74199edd020e03fb1887e))
|
||||
|
||||
## [6.10.1](https://github.com/firebase/php-jwt/compare/v6.10.0...v6.10.1) (2024-05-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ensure ratelimit expiry is set every time ([#556](https://github.com/firebase/php-jwt/issues/556)) ([09cb208](https://github.com/firebase/php-jwt/commit/09cb2081c2c3bc0f61e2f2a5fbea5741f7498648))
|
||||
* ratelimit cache expiration ([#550](https://github.com/firebase/php-jwt/issues/550)) ([dda7250](https://github.com/firebase/php-jwt/commit/dda725033585ece30ff8cae8937320d7e9f18bae))
|
||||
|
||||
## [6.10.0](https://github.com/firebase/php-jwt/compare/v6.9.0...v6.10.0) (2023-11-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow typ header override ([#546](https://github.com/firebase/php-jwt/issues/546)) ([79cb30b](https://github.com/firebase/php-jwt/commit/79cb30b729a22931b2fbd6b53f20629a83031ba9))
|
||||
|
||||
## [6.9.0](https://github.com/firebase/php-jwt/compare/v6.8.1...v6.9.0) (2023-10-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add payload to jwt exception ([#521](https://github.com/firebase/php-jwt/issues/521)) ([175edf9](https://github.com/firebase/php-jwt/commit/175edf958bb61922ec135b2333acf5622f2238a2))
|
||||
|
||||
## [6.8.1](https://github.com/firebase/php-jwt/compare/v6.8.0...v6.8.1) (2023-07-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* accept float claims but round down to ignore them ([#492](https://github.com/firebase/php-jwt/issues/492)) ([3936842](https://github.com/firebase/php-jwt/commit/39368423beeaacb3002afa7dcb75baebf204fe7e))
|
||||
* different BeforeValidException messages for nbf and iat ([#526](https://github.com/firebase/php-jwt/issues/526)) ([0a53cf2](https://github.com/firebase/php-jwt/commit/0a53cf2986e45c2bcbf1a269f313ebf56a154ee4))
|
||||
|
||||
## [6.8.0](https://github.com/firebase/php-jwt/compare/v6.7.0...v6.8.0) (2023-06-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for P-384 curve ([#515](https://github.com/firebase/php-jwt/issues/515)) ([5de4323](https://github.com/firebase/php-jwt/commit/5de4323f4baf4d70bca8663bd87682a69c656c3d))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle invalid http responses ([#508](https://github.com/firebase/php-jwt/issues/508)) ([91c39c7](https://github.com/firebase/php-jwt/commit/91c39c72b22fc3e1191e574089552c1f2041c718))
|
||||
|
||||
## [6.7.0](https://github.com/firebase/php-jwt/compare/v6.6.0...v6.7.0) (2023-06-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ed25519 support to JWK (public keys) ([#452](https://github.com/firebase/php-jwt/issues/452)) ([e53979a](https://github.com/firebase/php-jwt/commit/e53979abae927de916a75b9d239cfda8ce32be2a))
|
||||
|
||||
## [6.6.0](https://github.com/firebase/php-jwt/compare/v6.5.0...v6.6.0) (2023-06-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow get headers when decoding token ([#442](https://github.com/firebase/php-jwt/issues/442)) ([fb85f47](https://github.com/firebase/php-jwt/commit/fb85f47cfaeffdd94faf8defdf07164abcdad6c3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* only check iat if nbf is not used ([#493](https://github.com/firebase/php-jwt/issues/493)) ([398ccd2](https://github.com/firebase/php-jwt/commit/398ccd25ea12fa84b9e4f1085d5ff448c21ec797))
|
||||
|
||||
## [6.5.0](https://github.com/firebase/php-jwt/compare/v6.4.0...v6.5.0) (2023-05-12)
|
||||
|
||||
|
||||
|
||||
+81
-43
@@ -17,22 +17,22 @@ composer require firebase/php-jwt
|
||||
```
|
||||
|
||||
Optionally, install the `paragonie/sodium_compat` package from composer if your
|
||||
php is < 7.2 or does not have libsodium installed:
|
||||
php env does not have libsodium installed:
|
||||
|
||||
```bash
|
||||
composer require paragonie/sodium_compat
|
||||
```
|
||||
|
||||
Example
|
||||
-------
|
||||
## Example
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
$key = 'example_key';
|
||||
$key = 'example_key_of_sufficient_length';
|
||||
$payload = [
|
||||
'iss' => 'http://example.org',
|
||||
'aud' => 'http://example.com',
|
||||
'iss' => 'example.org',
|
||||
'aud' => 'example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
@@ -45,9 +45,13 @@ $payload = [
|
||||
*/
|
||||
$jwt = JWT::encode($payload, $key, 'HS256');
|
||||
$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
|
||||
|
||||
print_r($decoded);
|
||||
|
||||
// Pass a stdClass in as the third parameter to get the decoded header values
|
||||
$headers = new stdClass();
|
||||
$decoded = JWT::decode($jwt, new Key($key, 'HS256'), $headers);
|
||||
print_r($headers);
|
||||
|
||||
/*
|
||||
NOTE: This will now be an object instead of an associative array. To get
|
||||
an associative array, you will need to cast it as such:
|
||||
@@ -65,21 +69,22 @@ $decoded_array = (array) $decoded;
|
||||
JWT::$leeway = 60; // $leeway in seconds
|
||||
$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
|
||||
```
|
||||
Example encode/decode headers
|
||||
-------
|
||||
|
||||
## Example encode/decode headers
|
||||
|
||||
Decoding the JWT headers without verifying the JWT first is NOT recommended, and is not supported by
|
||||
this library. This is because without verifying the JWT, the header values could have been tampered with.
|
||||
Any value pulled from an unverified header should be treated as if it could be any string sent in from an
|
||||
attacker. If this is something you still want to do in your application for whatever reason, it's possible to
|
||||
decode the header values manually simply by calling `json_decode` and `base64_decode` on the JWT
|
||||
attacker. If this is something you still want to do in your application for whatever reason, it's possible to
|
||||
decode the header values manually simply by calling `json_decode` and `base64_decode` on the JWT
|
||||
header part:
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
|
||||
$key = 'example_key';
|
||||
$key = 'example_key_of_sufficient_length';
|
||||
$payload = [
|
||||
'iss' => 'http://example.org',
|
||||
'aud' => 'http://example.com',
|
||||
'iss' => 'example.org',
|
||||
'aud' => 'example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
@@ -99,8 +104,9 @@ $decoded = json_decode(base64_decode($headersB64), true);
|
||||
|
||||
print_r($decoded);
|
||||
```
|
||||
Example with RS256 (openssl)
|
||||
----------------------------
|
||||
|
||||
## Example with RS256 (openssl)
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
@@ -168,8 +174,7 @@ $decoded_array = (array) $decoded;
|
||||
echo "Decode:\n" . print_r($decoded_array, true) . "\n";
|
||||
```
|
||||
|
||||
Example with a passphrase
|
||||
-------------------------
|
||||
## Example with a passphrase
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
@@ -182,7 +187,7 @@ $passphrase = '[YOUR_PASSPHRASE]';
|
||||
// Can be generated with "ssh-keygen -t rsa -m pem"
|
||||
$privateKeyFile = '/path/to/key-with-passphrase.pem';
|
||||
|
||||
// Create a private key of type "resource"
|
||||
/** @var OpenSSLAsymmetricKey $privateKey */
|
||||
$privateKey = openssl_pkey_get_private(
|
||||
file_get_contents($privateKeyFile),
|
||||
$passphrase
|
||||
@@ -205,8 +210,8 @@ $decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
|
||||
echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
|
||||
```
|
||||
|
||||
Example with EdDSA (libsodium and Ed25519 signature)
|
||||
----------------------------
|
||||
## Example with EdDSA (libsodium and Ed25519 signature)
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
@@ -234,21 +239,21 @@ echo "Encode:\n" . print_r($jwt, true) . "\n";
|
||||
|
||||
$decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA'));
|
||||
echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
|
||||
````
|
||||
```
|
||||
|
||||
## Example with multiple keys
|
||||
|
||||
Example with multiple keys
|
||||
--------------------------
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
// Example RSA keys from previous example
|
||||
// $privateKey1 = '...';
|
||||
// $publicKey1 = '...';
|
||||
// $privateRsKey = '...';
|
||||
// $publicRsKey = '...';
|
||||
|
||||
// Example EdDSA keys from previous example
|
||||
// $privateKey2 = '...';
|
||||
// $publicKey2 = '...';
|
||||
// $privateEcKey = '...';
|
||||
// $publicEcKey = '...';
|
||||
|
||||
$payload = [
|
||||
'iss' => 'example.org',
|
||||
@@ -257,14 +262,14 @@ $payload = [
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
$jwt1 = JWT::encode($payload, $privateKey1, 'RS256', 'kid1');
|
||||
$jwt2 = JWT::encode($payload, $privateKey2, 'EdDSA', 'kid2');
|
||||
$jwt1 = JWT::encode($payload, $privateRsKey, 'RS256', 'kid1');
|
||||
$jwt2 = JWT::encode($payload, $privateEcKey, 'EdDSA', 'kid2');
|
||||
echo "Encode 1:\n" . print_r($jwt1, true) . "\n";
|
||||
echo "Encode 2:\n" . print_r($jwt2, true) . "\n";
|
||||
|
||||
$keys = [
|
||||
'kid1' => new Key($publicKey1, 'RS256'),
|
||||
'kid2' => new Key($publicKey2, 'EdDSA'),
|
||||
'kid1' => new Key($publicRsKey, 'RS256'),
|
||||
'kid2' => new Key($publicEcKey, 'EdDSA'),
|
||||
];
|
||||
|
||||
$decoded1 = JWT::decode($jwt1, $keys);
|
||||
@@ -274,8 +279,39 @@ echo "Decode 1:\n" . print_r((array) $decoded1, true) . "\n";
|
||||
echo "Decode 2:\n" . print_r((array) $decoded2, true) . "\n";
|
||||
```
|
||||
|
||||
Using JWKs
|
||||
----------
|
||||
## Example with PS256
|
||||
|
||||
### Note
|
||||
PHP's OpenSSL extension does not support RSASSA-PSS signatures (PS256) by default, so we provide support via a soft dependency on the [phpseclib/phpseclib](https://github.com/phpseclib/phpseclib) library. It is necessary to install this library in your project if you plan to use PS256.
|
||||
```bash
|
||||
composer install phpseclib/phpseclib:^3.0
|
||||
```
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
$privateRsKey = '-----BEGIN RSA PRIVATE KEY----- ...';
|
||||
$publicKey = '-----BEGIN PUBLIC KEY----- ...';
|
||||
|
||||
$payload = [
|
||||
'iss' => 'example.org',
|
||||
'aud' => 'example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
/**
|
||||
* PS256 support requires phpseclib/phpseclib
|
||||
*/
|
||||
$jwt = JWT::encode($payload, $privateRsKey, 'PS256', 'keyid');
|
||||
echo "Encode:\n" . print_r($jwt, true) . "\n";
|
||||
|
||||
$decoded = JWT::decode($jwt, new Key($publicKey, 'PS256'));
|
||||
echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
|
||||
```
|
||||
|
||||
## Using JWKs
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWK;
|
||||
@@ -287,11 +323,11 @@ $jwks = ['keys' => []];
|
||||
|
||||
// JWK::parseKeySet($jwks) returns an associative array of **kid** to Firebase\JWT\Key
|
||||
// objects. Pass this as the second parameter to JWT::decode.
|
||||
JWT::decode($payload, JWK::parseKeySet($jwks));
|
||||
$decoded = JWT::decode($jwt, JWK::parseKeySet($jwks));
|
||||
print_r($decoded);
|
||||
```
|
||||
|
||||
Using Cached Key Sets
|
||||
---------------------
|
||||
## Using Cached Key Sets
|
||||
|
||||
The `CachedKeySet` class can be used to fetch and cache JWKS (JSON Web Key Sets) from a public URI.
|
||||
This has the following advantages:
|
||||
@@ -311,7 +347,7 @@ $jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk';
|
||||
$httpClient = new GuzzleHttp\Client();
|
||||
|
||||
// Create an HTTP request factory (can be any PSR-17 compatible HTTP request factory)
|
||||
$httpFactory = new GuzzleHttp\Psr\HttpFactory();
|
||||
$httpFactory = new GuzzleHttp\Psr7\HttpFactory();
|
||||
|
||||
// Create a cache item pool (can be any PSR-6 compatible cache item pool)
|
||||
$cacheItemPool = Phpfastcache\CacheManager::getInstance('files');
|
||||
@@ -346,7 +382,7 @@ use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
try {
|
||||
$decoded = JWT::decode($payload, $keys);
|
||||
$decoded = JWT::decode($jwt, $keys);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
// provided key/key-array is empty or malformed.
|
||||
} catch (DomainException $e) {
|
||||
@@ -373,8 +409,10 @@ All exceptions in the `Firebase\JWT` namespace extend `UnexpectedValueException`
|
||||
like this:
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use UnexpectedValueException;
|
||||
try {
|
||||
$decoded = JWT::decode($payload, $keys);
|
||||
$decoded = JWT::decode($jwt, $keys);
|
||||
} catch (LogicException $e) {
|
||||
// errors having to do with environmental setup or malformed JWT Keys
|
||||
} catch (UnexpectedValueException $e) {
|
||||
@@ -389,7 +427,7 @@ instead, you can do the following:
|
||||
|
||||
```php
|
||||
// return type is stdClass
|
||||
$decoded = JWT::decode($payload, $keys);
|
||||
$decoded = JWT::decode($jwt, $keys);
|
||||
|
||||
// cast to array
|
||||
$decoded = json_decode(json_encode($decoded), true);
|
||||
@@ -400,8 +438,8 @@ Tests
|
||||
Run the tests using phpunit:
|
||||
|
||||
```bash
|
||||
$ pear install PHPUnit
|
||||
$ phpunit --configuration phpunit.xml.dist
|
||||
$ composer update
|
||||
$ vendor/bin/phpunit -c phpunit.xml.dist
|
||||
PHPUnit 3.7.10 by Sebastian Bergmann.
|
||||
.....
|
||||
Time: 0 seconds, Memory: 2.50Mb
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
7.2.1
|
||||
+18
-7
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "firebase/php-jwt",
|
||||
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
||||
"homepage": "https://github.com/firebase/php-jwt",
|
||||
"homepage": "https://github.com/googleapis/php-jwt",
|
||||
"keywords": [
|
||||
"php",
|
||||
"jwt"
|
||||
@@ -20,11 +20,12 @@
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"require": {
|
||||
"php": "^7.4||^8.0"
|
||||
"php": "^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present",
|
||||
"ext-sodium": "Support EdDSA (Ed25519) signatures"
|
||||
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
||||
"phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -32,11 +33,21 @@
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"guzzlehttp/guzzle": "^6.5||^7.4",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"guzzlehttp/guzzle": "^7.4||^8.0",
|
||||
"phpspec/prophecy-phpunit": "^2.2",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"psr/cache": "^1.0||^2.0",
|
||||
"psr/cache": "^2.0||^3.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0"
|
||||
"psr/http-factory": "^1.0",
|
||||
"phpfastcache/phpfastcache": "^9.2",
|
||||
"phpseclib/phpseclib": "~3.0"
|
||||
},
|
||||
"extra": {
|
||||
"component": {
|
||||
"id": "jwt",
|
||||
"target": "googleapis/php-jwt.git",
|
||||
"path": "Jwt",
|
||||
"entry": "README.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
+20
@@ -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
@@ -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()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user