├── .github └── workflows │ └── dependency-review.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Base64Url.php ├── Exceptions │ ├── AlgorithmMismatchException.php │ ├── EmptyTokenException.php │ ├── InsecureTokenException.php │ ├── IntegrityViolationException.php │ ├── InvalidClaimTypeException.php │ ├── InvalidStructureException.php │ ├── SigningFailedException.php │ ├── TokenExpiredException.php │ ├── TokenInactiveException.php │ ├── UndefinedAlgorithmException.php │ ├── UnsupportedAlgorithmException.php │ └── UnsupportedTokenTypeException.php ├── JWT.php ├── TokenDecoded.php ├── TokenEncoded.php └── Validation.php └── tests ├── TokenBaseTest.php ├── TokenEncodedTest.php └── keys ├── rs256 ├── private.key ├── public.pub └── public_invalid.pub ├── rs384 ├── private.key ├── public.pub └── public_invalid.pub └── rs512 ├── private.key ├── public.pub └── public_invalid.pub /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v3 19 | - name: 'Dependency Review' 20 | uses: actions/dependency-review-action@v2 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | .phpunit.result.cache 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Nowakowski Radosław 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # There are no updates, because there are no issues. This package is stable and maintained when needed. 2 | 3 | # JSON Web Tokens (JWT) implementation for PHP 7 4 | 5 | ## JWT 6 | 7 | Read more about JWT here: 8 | 9 | * [RFC 7519](https://tools.ietf.org/html/rfc7519) 10 | * [jwt.io](https://jwt.io/introduction/) 11 | * [JWT Handbook](https://auth0.com/resources/ebooks/jwt-handbook) 12 | 13 | ## License 14 | 15 | Please check [BSD-3 Clause](http://opensource.org/licenses/BSD-3-Clause) terms before use. 16 | 17 | ## Supported algorithms 18 | 19 | * HS256 20 | * HS384 21 | * HS512 22 | * RS256 23 | * RS384 24 | * RS512 25 | 26 | ## Installation 27 | 28 | You can add this package to your project by running composer command: 29 | ``` 30 | composer require nowakowskir/php-jwt 31 | ``` 32 | 33 | Make sure your vendor auto load file is loaded correctly and the following classes are used. 34 | 35 | ``` 36 | use Nowakowskir\JWT\JWT; 37 | use Nowakowskir\JWT\TokenDecoded; 38 | use Nowakowskir\JWT\TokenEncoded; 39 | ``` 40 | 41 | ## Elements 42 | 43 | When using this package, you will be mostly using two classes: ```TokenEncoded``` and ```TokenDecoded```. 44 | 45 | You can transform objects of those class like below: 46 | 47 | ``` 48 | TokenEncoded => TokenDecoded 49 | TokenDecoded => TokenEncoded 50 | ``` 51 | 52 | 53 | ### TokenDecoded 54 | 55 | This class is a representation of a decoded token. It consists of a header and payload. Both elements are arrays. 56 | 57 | Token represented by an object of ```TokenDecoded``` class lets you access and modify any of its parts. 58 | 59 | ### TokenEncoded 60 | 61 | This class is a representation of an encoded token. 62 | 63 | ## Usage 64 | 65 | ### Building the new JWT 66 | 67 | There are two arguments you can optionally pass to ```TokenDecode``` constructor. These are payload and header. 68 | 69 | ``` 70 | $tokenDecoded = new TokenDecoded(['payload_key' => 'value'], ['header_key' => 'value']); 71 | $tokenEncoded = $tokenDecoded->encode($privateKey, JWT::ALGORITHM_RS256); 72 | 73 | echo 'Your token is: ' . $tokenEncoded->toString(); 74 | ``` 75 | 76 | > Please check *Security best practices* section to understand why providing algorithm is mandatory when encoding a token! 77 | 78 | ### Instantiating existing token 79 | 80 | ``` 81 | $tokenEncoded = new TokenEncoded('Existing JSON Web Token'); 82 | ``` 83 | 84 | ### Getting token's header 85 | 86 | ``` 87 | $tokenEncoded = new TokenEncoded('Existing JSON Web Token'); 88 | $header = $tokenEncoded->decode()->getHeader(); 89 | ``` 90 | 91 | ### Getting token's payload 92 | 93 | ``` 94 | $tokenEncoded = new TokenEncoded('Existing JSON Web Token'); 95 | $payload = $tokenEncoded->decode()->getPayload(); 96 | ``` 97 | 98 | > Please note that providing a key is not required to decode a token, as its header and payload are public. You should put special attention to not pass any confidential information within the token's header and payload. JWT only allows you to verify if the token containing the given payload was issued by a trusted party. It does not protect your data passed in a payload! Be aware anybody can access your token's payload! 99 | 100 | ### Validating token 101 | 102 | In order to use a decoded payload make sure your token goes through validate process first. Otherwise, payload can't be assumed as trusted! 103 | 104 | ``` 105 | try { 106 | $tokenEncoded->validate($publicKey, JWT::ALGORITHM_RS256); 107 | } catch(Exception $e) { 108 | // Token validation failed. 109 | } 110 | ``` 111 | 112 | > Please check the the *Security best practices* section to understand why providing an algorithm is mandatory when validating a token! 113 | 114 | If you need more detailed information about why your validation process has failed, there are several exception classes you can catch: 115 | 116 | Exception Class | Description 117 | ------------ | ------------- 118 | ``Nowakowskir\JWT\Exceptions\IntegrityViolationException`` | Token is not trusted. Either an invalid key was provided or a token was tampered. 119 | ``Nowakowskir\JWT\Exceptions\AlgorithmMismatchException`` | If the algorithm you decided to use to validate the token is different from the algorithm specified in the token's header. 120 | ``Nowakowskir\JWT\Exceptions\TokenExpiredException`` | Token has expired (if ```exp``` was set by issuer). 121 | ``Nowakowskir\JWT\Exceptions\TokenInactiveException`` | Token is not yet active (if ```nbf``` was set by issuer). 122 | 123 | 124 | ### Building the new JWT with expiration date (exp) 125 | 126 | If you want your token to expire at some date, you can use ```exp``` flag. 127 | 128 | ``` 129 | $tokenDecoded = new TokenDecoded(['exp' => time() + 1000]); 130 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_RS256); 131 | ``` 132 | 133 | ### Building the new JWT with not before date (nbf) 134 | 135 | If you want your token to be not active until reach some date, you can use ```nbf``` flag. 136 | 137 | ``` 138 | $tokenDecoded = new TokenDecoded(['nbf' => time() + 1000]); 139 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_RS256); 140 | ``` 141 | 142 | ### Solving clock difference issue between servers (exp, nbf) 143 | 144 | Because the clock may vary across the servers, you can use so-called ``leeway`` to solve this issue. It's some kind of time margin which will be taken into account when validating token (exp, nbf). 145 | 146 | ``` 147 | $leeway = 500; 148 | $tokenEncoded = new TokenEncoded('Existing JSON Web Token'); 149 | $tokenEncoded->validate($key, JWT::ALGORITHM_RS256, $leeway); 150 | ``` 151 | 152 | ## Security best practices 153 | 154 | ### Don't pass confidential data in token's payload 155 | 156 | Please note that providing a key is not required to decode a token, as its header and payload are public. You should put special attention to not pass any confidential information within the token's header and payload. JWT only allows you to verify if the token containing the given payload was issued by a trusted party. It does not protect your data passed in a payload! Be aware anybody can access your token's payload! 157 | 158 | ### Don't trust your payload until you validate a token 159 | 160 | The only way to ensure the token is valid is to use ```TokenEncoded::validate()``` method. Please keep in mind that ```TokenDecoded::decode()``` method decodes a token only. It gives you access to its payload without any validation! 161 | 162 | The reason why it allows you to get the token's payload without any validation is that: 163 | 164 | * it's a nature of JWT that token's payload is not encrypted and is not protected by keys, so you should not even have illusion it is protected, 165 | * you may need to use some parts of your token's payload before token validation. 166 | 167 | ### Enforce algorithm when encoding and validating token 168 | 169 | As in some circumstances, the algorithm defined in token's header may be modified by an attacker, it's highly recommended to not rely on the algorithm contained in token's header. 170 | 171 | Due to security reasons you should choose one algorithm whenever possible and stick to it in both issuer and verifier applications. 172 | 173 | To increase your tokens' security, this package requires an algorithm to be provided when encoding and validating tokens. 174 | 175 | Below you can find correct way of encoding and decoding tokens: 176 | 177 | ``` 178 | // Issuer 179 | $tokenDecoded = new TokenDecoded(); 180 | $tokenEncoded = $tokenDecoded->encode($privateKey, JWT::ALGORITHM_RS256); 181 | ``` 182 | 183 | ``` 184 | // Consumer 185 | $tokenEncoded->validate($publicKey, JWT::ALGORITHM_RS256); 186 | ``` 187 | 188 | As you can see, both use the same algorithm. 189 | 190 | This package throws ```Nowakowskir\JWT\Exceptions\AlgorithmMismatchException``` if the algorithm you decided to use to validate the token is different from the algorithm specified in the token's header. 191 | 192 | This protects your token against successful validation in case the token has been tampered. 193 | 194 | You may be tempted to do some workaround and use the algorithm contained in the token's header for validation purposes, although it's highly not recommended! 195 | 196 | ``` 197 | // Don't use algorithm defined in token's header like here! 198 | $header = $tokenEncoded->decode()->getHeader(); 199 | $tokenEncoded->validate($publicKey, $header['alg']); 200 | ``` 201 | 202 | ### Using insecure tokens 203 | 204 | Creating insecure tokens is not possible due to security reasons. 205 | 206 | This package does not let you create a token with ```none``` algorithm or empty signature. 207 | 208 | Trying to do so will result in ```Nowakowskir\JWT\Exceptions\InsecureTokenException``` exception. 209 | 210 | ``` 211 | try { 212 | $tokenEncoded = new TokenEncoded('Existing JSON Web Token with none algorithm or missing signature'); 213 | } catch (InsecureTokenException $e) { 214 | // Insecure token 215 | } 216 | ``` 217 | 218 | ``` 219 | try { 220 | $tokenDecoded = new TokenDecoded(); 221 | 222 | $tokenDeoded->encode($privateKey, 'none'); 223 | } catch (InsecureTokenException $e) { 224 | // Insecure token 225 | } 226 | ``` 227 | 228 | It's also not possible to parse token without an algorithm defined. 229 | 230 | ``` 231 | try { 232 | $tokenEncoded = new TokenEncoded('Existing JSON Web Token without an algorithm'); 233 | } catch (UndefinedAlgorithmException $e) { 234 | // Algorithm not provided 235 | } 236 | ``` 237 | 238 | 239 | ### Generate a strong private key 240 | 241 | First, you need to generate a private key. 242 | 243 | ``` 244 | ssh-keygen -t rsa -b 4096 -m PEM -f private.key 245 | chmod 600 private.key 246 | ``` 247 | 248 | Next, you need to generate a public key based on the private key. 249 | 250 | ``` 251 | openssl rsa -in private.key -pubout -outform PEM -out public.pub 252 | ``` 253 | 254 | ### Rotate your public/private key pair regularly 255 | 256 | To minimize the risk of gaining your public/private key by an unauthorized entity, rotate it regularly. 257 | 258 | ### Protect your private key 259 | 260 | Make sure your private key is secured and not accessible by any unauthorized entities. Special care should be taken to file permissions. In most cases, you should set ```600``` permissions on your private key file, which means it's accessible only by the file's owner. 261 | 262 | ### Protect your public key 263 | 264 | Even if it's called public, try to share this key only when it's really required. Also, file permissions should be as restrictive as possible. Do not pass public keys between requests or expose them to the public audience. 265 | 266 | ### Don't pass tokens in URL 267 | 268 | They will be stored in server logs, browser history, etc. 269 | 270 | ### Use token's expiration date 271 | 272 | Whenever possible, use the token's expiration date, so the token is valid as short as necessary. 273 | 274 | ### Check for updates 275 | 276 | Regularly check for updates of this package. 277 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nowakowskir/php-jwt", 3 | "description": "JSON Web Token implementation for PHP.", 4 | "type": "library", 5 | "homepage": "https://github.com/nowakowskir/php-jwt", 6 | "authors": [ 7 | { 8 | "name": "Radosław Nowakowski", 9 | "email": "nowakowski.r@gmail.com" 10 | } 11 | ], 12 | "keywords": [ 13 | "JWT", 14 | "JSON", 15 | "Token" 16 | ], 17 | "license": "BSD-3-Clause", 18 | "require": { 19 | "php": ">=7.2.1" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "Nowakowskir\\JWT\\": "src/" 24 | } 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "Tests\\": "tests/" 29 | } 30 | }, 31 | "require-dev": { 32 | "phpunit/phpunit": "^7.5|^8.0" 33 | } 34 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests/TokenEncodedTest.php 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Base64Url.php: -------------------------------------------------------------------------------- 1 | 9 | * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD 10 | * @link https://github.com/nowakowskir/php-jwt 11 | */ 12 | class Base64Url 13 | { 14 | 15 | public static function encode($data) : string 16 | { 17 | return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 18 | } 19 | 20 | public static function decode($data) : string 21 | { 22 | return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Exceptions/AlgorithmMismatchException.php: -------------------------------------------------------------------------------- 1 | 15 | * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD 16 | * @link https://github.com/nowakowskir/php-jwt 17 | */ 18 | class JWT 19 | { 20 | 21 | /** 22 | * List of available algorithm keys. 23 | */ 24 | const ALGORITHM_HS256 = 'HS256'; 25 | const ALGORITHM_HS384 = 'HS384'; 26 | const ALGORITHM_HS512 = 'HS512'; 27 | const ALGORITHM_RS256 = 'RS256'; 28 | const ALGORITHM_RS384 = 'RS384'; 29 | const ALGORITHM_RS512 = 'RS512'; 30 | 31 | /** 32 | * Mapping of available algorithm keys with their types and target algorithms. 33 | */ 34 | const ALGORITHMS = [ 35 | self::ALGORITHM_HS256 => ['hash_hmac', 'SHA256'], 36 | self::ALGORITHM_HS384 => ['hash_hmac', 'SHA384'], 37 | self::ALGORITHM_HS512 => ['hash_hmac', 'SHA512'], 38 | self::ALGORITHM_RS256 => ['openssl', 'SHA256'], 39 | self::ALGORITHM_RS384 => ['openssl', 'SHA384'], 40 | self::ALGORITHM_RS512 => ['openssl', 'SHA512'], 41 | ]; 42 | 43 | /** 44 | * Decodes encoded token. 45 | * 46 | * @param TokenEncoded $tokenEncoded Encoded token 47 | * 48 | * @return TokenDecoded 49 | */ 50 | public static function decode(TokenEncoded $tokenEncoded): TokenDecoded 51 | { 52 | return new TokenDecoded( 53 | json_decode(Base64Url::decode($tokenEncoded->getPayload()), true), 54 | json_decode(Base64Url::decode($tokenEncoded->getHeader()), true) 55 | ); 56 | } 57 | 58 | /** 59 | * Encodes decoded token. 60 | * 61 | * @param TokenDecoded $tokenDecoded Decoded token 62 | * @param string $key Key used to sign the token 63 | * @param string $algorithm Algorithm 64 | * 65 | * @return TokenEncoded 66 | */ 67 | public static function encode(TokenDecoded $tokenDecoded, string $key, string $algorithm): TokenEncoded 68 | { 69 | $header = $tokenDecoded->getHeader(); 70 | 71 | if (array_key_exists('alg', $header) && $algorithm !== $header['alg']) { 72 | throw new AlgorithmMismatchException('Algorithm provided in token\'s header doesn\'t match encoding algorithm.'); 73 | } 74 | 75 | $header = array_merge($tokenDecoded->getHeader(), [ 76 | 'alg' => $algorithm 77 | ]); 78 | 79 | $elements = []; 80 | $elements[] = Base64Url::encode(json_encode($header)); 81 | $elements[] = Base64Url::encode(json_encode($tokenDecoded->getPayload())); 82 | 83 | $signature = self::sign(implode('.', $elements), $key, $header['alg']); 84 | $elements[] = Base64Url::encode($signature); 85 | 86 | return new TokenEncoded(implode('.', $elements)); 87 | } 88 | 89 | /** 90 | * Generates signature for given message. 91 | * 92 | * @param string $message Message to sign, which is base64 encoded values of header and payload separated by dot 93 | * @param string $key Key used to sign the token 94 | * @param string $algorithm Algorithm to use for signing the token 95 | * 96 | * @return string 97 | * 98 | * @throws SigningFailedException 99 | * @throws SigningFailedException 100 | */ 101 | protected static function sign(string $message, string $key, string $algorithm): string 102 | { 103 | list($function, $type) = self::getAlgorithmData($algorithm); 104 | 105 | switch ($function) { 106 | case 'hash_hmac': 107 | try { 108 | $signature = hash_hmac($type, $message, $key, true); 109 | } catch (Exception $e) { 110 | throw new SigningFailedException(sprintf('Signing failed: %s', $e->getMessage())); 111 | } 112 | if ($signature === false) { 113 | throw new SigningFailedException('Signing failed'); 114 | } 115 | return $signature; 116 | break; 117 | case 'openssl': 118 | $signature = ''; 119 | 120 | try { 121 | $sign = openssl_sign($message, $signature, $key, $type); 122 | } catch (Exception $e) { 123 | throw new SigningFailedException(sprintf('Signing failed: %s', $e->getMessage())); 124 | } 125 | 126 | if (!$sign) { 127 | throw new SigningFailedException('Signing failed'); 128 | } 129 | 130 | return $signature; 131 | break; 132 | default: 133 | throw new UnsupportedAlgorithmException('Invalid function'); 134 | break; 135 | } 136 | } 137 | 138 | /** 139 | * Validates token's using provided key. 140 | * 141 | * This method should be used to check if given token is valid. 142 | * 143 | * Following things should be verified: 144 | * - if token contains algorithm defined in its header 145 | * - if token integrity is met using provided key 146 | * - if token contains expiration date (exp) in its payload - current time against this date 147 | * - if token contains not before date (nbf) in its payload - current time against this date 148 | * - if token contains issued at date (iat) in its payload - current time against this date 149 | * 150 | * @param TokenEncoded $tokenEncoded Encoded token 151 | * @param string $key Key used to signature verification 152 | * @param string $algorithm Force algorithm to signature verification (recommended) 153 | * @param int|null $leeway Some optional period to avoid clock synchronization issues 154 | * @param array|null $claimsExclusions Claims to be excluded from validation 155 | * 156 | * @return bool 157 | * 158 | * @throws IntegrityViolationException 159 | * @throws UnsupportedAlgorithmException 160 | */ 161 | public static function validate(TokenEncoded $tokenEncoded, string $key, string $algorithm, ?int $leeway = null, ?array $claimsExclusions = null): bool 162 | { 163 | $tokenDecoded = self::decode($tokenEncoded); 164 | 165 | $signature = Base64Url::decode($tokenEncoded->getSignature()); 166 | $payload = $tokenDecoded->getPayload(); 167 | 168 | list($function, $type) = self::getAlgorithmData($algorithm); 169 | 170 | switch ($function) { 171 | case 'hash_hmac': 172 | if (hash_equals(hash_hmac($type, $tokenEncoded->getMessage(), $key, true), $signature) !== true) { 173 | throw new IntegrityViolationException('Invalid signature'); 174 | } 175 | break; 176 | case 'openssl': 177 | if (openssl_verify($tokenEncoded->getMessage(), $signature, $key, $type) !== 1) { 178 | throw new IntegrityViolationException('Invalid signature'); 179 | } 180 | break; 181 | default: 182 | throw new UnsupportedAlgorithmException('Unsupported algorithm type'); 183 | break; 184 | } 185 | 186 | if (array_key_exists('exp', $payload)) { 187 | Validation::checkExpirationDate($payload['exp'], $leeway); 188 | } 189 | 190 | if (array_key_exists('nbf', $payload)) { 191 | Validation::checkNotBeforeDate($payload['nbf'], $leeway); 192 | } 193 | 194 | return true; 195 | } 196 | 197 | /** 198 | * Transforms algorithm key into array containing its type and target algorithm. 199 | * 200 | * @param string $algorithm Algorithm key 201 | * 202 | * @return array 203 | */ 204 | public static function getAlgorithmData(string $algorithm): array 205 | { 206 | Validation::checkAlgorithmSupported($algorithm); 207 | 208 | return self::ALGORITHMS[$algorithm]; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/TokenDecoded.php: -------------------------------------------------------------------------------- 1 | 9 | * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD 10 | * @link https://github.com/nowakowskir/php-jwt 11 | */ 12 | class TokenDecoded 13 | { 14 | 15 | /** 16 | * Array containing token's header elements. 17 | */ 18 | protected $header; 19 | 20 | /** 21 | * Array containing token's payload elements. 22 | */ 23 | protected $payload; 24 | 25 | /** 26 | * @param array|null $payload 27 | * @param array|null $header 28 | */ 29 | public function __construct(?array $payload = null, ?array $header = null) 30 | { 31 | $this->payload = $payload ?? []; 32 | $this->header = $header ?? []; 33 | } 34 | 35 | /** 36 | * Gets array with token's payload. 37 | * 38 | * @return array 39 | */ 40 | public function getPayload(): array 41 | { 42 | return $this->payload; 43 | } 44 | 45 | /** 46 | * Sets array with token's payload. 47 | * 48 | * @param array $payload 49 | * 50 | * @return void 51 | */ 52 | public function setPayload(array $payload): void 53 | { 54 | $this->payload = $payload; 55 | } 56 | 57 | /** 58 | * Gets array with token's header. 59 | * 60 | * @return array 61 | * 62 | * @return void 63 | */ 64 | public function getHeader(): array 65 | { 66 | return $this->header; 67 | } 68 | 69 | /** 70 | * Sets array with token's header. 71 | * 72 | * @param array $header 73 | * 74 | * @return void 75 | */ 76 | public function setHeader(array $header): void 77 | { 78 | $this->header = $header; 79 | } 80 | 81 | /** 82 | * Performs auto encoding. 83 | * 84 | * @param string $key Key used to sign the token. 85 | * @param string $algorithm Algorithm to be used. 86 | * 87 | * @return TokenEncoded 88 | */ 89 | public function encode(string $key, string $algorithm): TokenEncoded 90 | { 91 | return JWT::encode($this, $key, $algorithm); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/TokenEncoded.php: -------------------------------------------------------------------------------- 1 | 9 | * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD 10 | * @link https://github.com/nowakowskir/php-jwt 11 | */ 12 | class TokenEncoded 13 | { 14 | /** 15 | * String representation of the encoded token. 16 | */ 17 | protected $token; 18 | 19 | /** 20 | * Base64 url encoded representation of JSON encoded token's header. 21 | */ 22 | protected $header; 23 | 24 | /** 25 | * Base64 url encoded representation of JSON encoded token's payload. 26 | */ 27 | protected $payload; 28 | 29 | /** 30 | * Base64 url encoded representation of token's signature. 31 | */ 32 | protected $signature; 33 | 34 | /** 35 | * @param string $token 36 | * 37 | * @throws EmptyTokenException 38 | */ 39 | public function __construct(string $token) 40 | { 41 | Validation::checkTokenStructure($token); 42 | 43 | $elements = explode('.', $token); 44 | list($header, $payload, $signature) = $elements; 45 | 46 | $headerArray = json_decode(Base64Url::decode($header), true); 47 | $payloadArray = json_decode(Base64Url::decode($payload), true); 48 | 49 | Validation::checkAlgorithmDefined($headerArray); 50 | Validation::checkAlgorithmSupported($headerArray['alg']); 51 | Validation::checkSignatureMissing($signature); 52 | 53 | Validation::checkClaimType('nbf', 'integer', $payloadArray); 54 | Validation::checkClaimType('exp', 'integer', $payloadArray); 55 | Validation::checkClaimType('iat', 'integer', $payloadArray); 56 | 57 | Validation::checkClaimType('iss', 'string', $payloadArray); 58 | Validation::checkClaimType('sub', 'string', $payloadArray); 59 | Validation::checkClaimType('aud', 'string', $payloadArray); 60 | Validation::checkClaimType('jti', 'string', $payloadArray); 61 | 62 | $this->token = $token; 63 | $this->payload = $payload; 64 | $this->header = $header; 65 | $this->signature = $signature; 66 | } 67 | 68 | /** 69 | * Gets message part of the token. 70 | * 71 | * @return string 72 | */ 73 | public function getMessage(): string 74 | { 75 | return sprintf('%s.%s', $this->getHeader(), $this->getPayload()); 76 | } 77 | 78 | /** 79 | * Gets payload part of the token. 80 | * 81 | * @return string 82 | */ 83 | public function getPayload(): string 84 | { 85 | return $this->payload; 86 | } 87 | 88 | /** 89 | * Gets header part of the token. 90 | * 91 | * @return string 92 | */ 93 | public function getHeader(): string 94 | { 95 | return $this->header; 96 | } 97 | 98 | /** 99 | * Get signature part of the token. 100 | * 101 | * @return string 102 | */ 103 | public function getSignature(): string 104 | { 105 | return $this->signature; 106 | } 107 | 108 | /** 109 | * Performs auto decoding. 110 | * 111 | * @return TokenDecoded 112 | */ 113 | public function decode(): TokenDecoded 114 | { 115 | return JWT::decode($this); 116 | } 117 | 118 | /** 119 | * Performs auto validation using given key. 120 | * 121 | * @param string $key Key 122 | * @param string|null $algorithm Force algorithm to signature verification (recommended) 123 | * @param int|null $leeway Optional leeway 124 | * 125 | * @return bool 126 | */ 127 | public function validate(string $key, string $algorithm, ?int $leeway = null): bool 128 | { 129 | return JWT::validate($this, $key, $algorithm, $leeway); 130 | } 131 | 132 | /** 133 | * Returns string representation of token. 134 | * 135 | * @return string 136 | */ 137 | public function toString(): string 138 | { 139 | return $this->token; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Validation.php: -------------------------------------------------------------------------------- 1 | 19 | * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD 20 | * @link https://github.com/nowakowskir/php-jwt 21 | */ 22 | class Validation 23 | { 24 | 25 | /** 26 | * Checks if expiration date has been reached. 27 | * 28 | * @param int $exp Timestamp of expiration date 29 | * @param int|null $leeway Some optional period to avoid clock synchronization issues 30 | * 31 | * @throws TokenExpiredException 32 | */ 33 | public static function checkExpirationDate(int $exp, ?int $leeway = null): void 34 | { 35 | $time = time() - ($leeway ? $leeway : 0); 36 | 37 | if ($time >= $exp) { 38 | throw new TokenExpiredException('Token is not valid since: ' . date(DateTime::ISO8601, $exp)); 39 | } 40 | } 41 | 42 | /** 43 | * Checks if not before date has been reached. 44 | * 45 | * @param int $nbf Timestamp of activation (not before) date 46 | * @param int|null $leeway Some optional period to avoid clock synchronization issues 47 | * 48 | * @throws TokenInactiveException 49 | */ 50 | public static function checkNotBeforeDate(int $nbf, ?int $leeway = null): void 51 | { 52 | $time = time() + ($leeway ?? 0); 53 | 54 | if ($time < $nbf) { 55 | throw new TokenInactiveException('Token is not valid before: ' . date(DateTime::ISO8601, $nbf)); 56 | } 57 | } 58 | 59 | /** 60 | * Checks token structure. 61 | * 62 | * @param string $token Token 63 | * 64 | * @throws InvalidStructureException 65 | */ 66 | public static function checkTokenStructure(string $token): void 67 | { 68 | $elements = explode('.', $token); 69 | 70 | if (count($elements) !== 3) { 71 | throw new InvalidStructureException('Wrong number of segments'); 72 | } 73 | 74 | list($header, $payload, $signature) = $elements; 75 | 76 | if (null === json_decode(Base64Url::decode($header))) { 77 | throw new InvalidStructureException('Invalid header'); 78 | } 79 | if (null === json_decode(Base64Url::decode($payload))) { 80 | throw new InvalidStructureException('Invalid payload'); 81 | } 82 | if (false === Base64Url::decode($signature)) { 83 | throw new InvalidStructureException('Invalid signature'); 84 | } 85 | } 86 | 87 | public static function checkAlgorithmDefined(array $header) 88 | { 89 | if (! array_key_exists('alg', $header)) { 90 | throw new UndefinedAlgorithmException('Missing algorithm in token header'); 91 | } 92 | } 93 | 94 | /** 95 | * Checks if algorithm has been provided and is supported. 96 | * 97 | * @param string $algorithm 98 | * 99 | * @throws InsecureTokenException 100 | * @throws UnsupportedAlgorithmException 101 | */ 102 | public static function checkAlgorithmSupported(string $algorithm) 103 | { 104 | if (strtolower($algorithm) === 'none') { 105 | throw new InsecureTokenException('Unsecure token are not supported: none algorithm provided'); 106 | } 107 | 108 | if (! array_key_exists($algorithm, JWT::ALGORITHMS)) { 109 | throw new UnsupportedAlgorithmException('Invalid algorithm'); 110 | } 111 | } 112 | 113 | /** 114 | * 115 | * @param string $token 116 | * @return void 117 | * @throws InsecureTokenException 118 | */ 119 | public static function checkSignatureMissing(string $signature): void 120 | { 121 | if (strlen($signature) === 0) { 122 | throw new InsecureTokenException('Unsecure token are not supported: signature is missing'); 123 | } 124 | } 125 | 126 | /** 127 | * Checks if given key exists in the payload and if so, checks if it's of integer type. 128 | * 129 | * @param string $claim Claim name 130 | * @param array $payload Payload array 131 | * 132 | * @throws InvalidClaimTypeException 133 | */ 134 | public static function checkClaimType(string $claim, string $type, array $payload): void 135 | { 136 | switch ($type) { 137 | case 'integer': 138 | if (array_key_exists($claim, $payload) && ! is_int($payload[$claim])) { 139 | throw new InvalidClaimTypeException(sprintf('Invalid %s claim - %s value required', $claim, $type)); 140 | } 141 | break; 142 | case 'string': 143 | default: 144 | if (array_key_exists($claim, $payload) && ! is_string($payload[$claim])) { 145 | throw new InvalidClaimTypeException(sprintf('Invalid %s claim - %s value required', $claim, $type)); 146 | } 147 | break; 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /tests/TokenBaseTest.php: -------------------------------------------------------------------------------- 1 | 15 | * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD 16 | * @link https://github.com/nowakowskir/php-jwt 17 | */ 18 | abstract class TokenBaseTest extends TestCase 19 | { 20 | 21 | protected function check_token_integrity($algorithm, $privateKey, $publicKey = null) : void 22 | { 23 | $tokenDecoded = new TokenDecoded(['alg' => $algorithm], ['success' => 1]); 24 | 25 | $publicKey = $publicKey ?? $privateKey; 26 | 27 | $exception = false; 28 | 29 | try { 30 | $token = $tokenDecoded->encode($privateKey, $algorithm)->toString(); 31 | 32 | $tokenEncoded = new TokenEncoded($token); 33 | $tokenEncoded->validate($publicKey, $algorithm); 34 | } catch (Exception $e) { 35 | $exception = true; 36 | } 37 | 38 | $this->assertFalse($exception); 39 | } 40 | 41 | protected function check_token_integrity_violation($algorithm, $privateKey, $publicKey = null) : void 42 | { 43 | $this->expectException(IntegrityViolationException::class); 44 | $tokenDecoded = new TokenDecoded(['alg' => $algorithm], ['success' => 1]); 45 | 46 | $publicKey = $publicKey ?? $privateKey; 47 | 48 | $token = $tokenDecoded->encode($privateKey, $algorithm)->toString(); 49 | 50 | $tokenEncoded = new TokenEncoded($token); 51 | $tokenEncoded->validate($publicKey, $algorithm); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/TokenEncodedTest.php: -------------------------------------------------------------------------------- 1 | 25 | * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD 26 | * @link https://github.com/nowakowskir/php-jwt 27 | */ 28 | class TokenEncodedTest extends TokenBaseTest 29 | { 30 | public function test_providing_different_algorithm_in_token_header_throws_an_exception() 31 | { 32 | $tokenDecoded = new TokenDecoded(null, ['alg' => JWT::ALGORITHM_RS512]); 33 | 34 | $key = file_get_contents('./tests/keys/rs512/private.key'); 35 | 36 | $exception = false; 37 | 38 | try { 39 | $tokenDecoded->encode($key, JWT::ALGORITHM_RS256); 40 | } catch (AlgorithmMismatchException $e) { 41 | $exception = true; 42 | } 43 | 44 | $this->assertTrue($exception); 45 | } 46 | 47 | /** 48 | * Checks if tampering the token and successful validation is possible 49 | * with known public key and algorithm being not forced during validation. 50 | * 51 | * If the token is encoded using the RSA algorithm and the attacker possess a public key 52 | * and validation doesn't enforce a specific algorithm to be used, 53 | * it is possible to tamper the token by switching its algorithm to HMAC, signing it with a public key 54 | * allowing the token to be successfully validated. 55 | */ 56 | public function test_bypassing_would_be_possible_without_algorithm_forcing(): void 57 | { 58 | // Issuer part 59 | $issuerTokenDecoded = new TokenDecoded(); 60 | $issuerPrivateKey = file_get_contents('./tests/keys/rs256/private.key'); 61 | $issuerTokenEncoded = $issuerTokenDecoded->encode($issuerPrivateKey, JWT::ALGORITHM_RS256); 62 | $issuerTokenString = $issuerTokenEncoded->toString(); 63 | 64 | // Attacker part 65 | $capturedPublicKey = file_get_contents('./tests/keys/rs256/public.pub'); 66 | $attackerTokenEncoded = new TokenEncoded($issuerTokenString); 67 | $attackerTokenDecoded = $attackerTokenEncoded->decode(); 68 | $header = $attackerTokenDecoded->getHeader(); 69 | $header['alg'] = JWT::ALGORITHM_HS256; 70 | $attackerTokenDecoded->setHeader($header); 71 | $craftedTokenEncoded = $attackerTokenDecoded->encode($capturedPublicKey, JWT::ALGORITHM_HS256); 72 | $craftedTokenString = $craftedTokenEncoded->toString(); 73 | 74 | // Verifier part 75 | $verifierPublicKey = file_get_contents('./tests/keys/rs256/public.pub'); 76 | $verifierTokenEncoded = new TokenEncoded($craftedTokenString); 77 | $verifierHeader = $verifierTokenEncoded->decode()->getHeader(); 78 | $exception = false; 79 | 80 | try { 81 | $verifierTokenEncoded->validate($verifierPublicKey, $verifierHeader['alg']); 82 | } catch (IntegrityViolationException $e) { 83 | $exception = true; 84 | } 85 | 86 | $this->assertFalse($exception); 87 | } 88 | 89 | /** 90 | * Checks if tampering token and successful validation is not possible 91 | * when the algorithm is forced during validation. 92 | * 93 | * This should result in unsuccessful validation as opposite to 94 | * test_bypassing_possible_with_no_algorithm_forcing test. 95 | */ 96 | public function test_bypassing_not_possible_with_algorithm_forcing(): void 97 | { 98 | // Issuer part 99 | $issuerTokenDecoded = new TokenDecoded(); 100 | $issuerPrivateKey = file_get_contents('./tests/keys/rs256/private.key'); 101 | $issuerTokenEncoded = $issuerTokenDecoded->encode($issuerPrivateKey, JWT::ALGORITHM_RS256); 102 | $issuerTokenString = $issuerTokenEncoded->toString(); 103 | 104 | // Attacker part 105 | $capturedPublicKey = file_get_contents('./tests/keys/rs256/public.pub'); 106 | $attackerTokenEncoded = new TokenEncoded($issuerTokenString); 107 | $attackerTokenDecoded = $attackerTokenEncoded->decode(); 108 | $header = $attackerTokenDecoded->getHeader(); 109 | $header['alg'] = JWT::ALGORITHM_HS256; 110 | $attackerTokenDecoded->setHeader($header); 111 | $craftedTokenEncoded = $attackerTokenDecoded->encode($capturedPublicKey, JWT::ALGORITHM_HS256); 112 | $craftedTokenString = $craftedTokenEncoded->toString(); 113 | 114 | // Verifier part 115 | $verifierPublicKey = file_get_contents('./tests/keys/rs256/public.pub'); 116 | $verifierTokenEncoded = new TokenEncoded($craftedTokenString); 117 | 118 | $exception = false; 119 | try { 120 | $verifierTokenEncoded->validate($verifierPublicKey, JWT::ALGORITHM_RS256); 121 | } catch (IntegrityViolationException $e) { 122 | $exception = true; 123 | } 124 | 125 | $this->assertTrue($exception); 126 | } 127 | 128 | /** 129 | * Checks if it's not possible to create an encoded token with string with an invalid structure. 130 | * 131 | * This should result with InvalidStructureException. 132 | */ 133 | public function test_building_encoded_token_with_invalid_structure(): void 134 | { 135 | $this->expectException(InvalidStructureException::class); 136 | new TokenEncoded('aaa.bbb.ccc'); 137 | } 138 | 139 | /** 140 | * Checks if it's not possible to create an encoded token which has none algorithm defined in its header. 141 | * 142 | * This should result with InsecureTokenException. 143 | */ 144 | public function test_building_encoded_token_with_none_algorithm(): void 145 | { 146 | $header = Base64Url::encode(json_encode(['typ' => 'JWT', 'alg' => 'none'])); 147 | $payload = Base64Url::encode(json_encode([])); 148 | $signature = Base64Url::encode('signature'); 149 | 150 | $token = sprintf('%s.%s.%s', $header, $payload, $signature); 151 | 152 | $this->expectException(InsecureTokenException::class); 153 | new TokenEncoded($token); 154 | } 155 | 156 | /** 157 | * Checks if it's not possible to create an encoded token which has no algorithm defined in its header. 158 | * 159 | * This should result with UndefinedAlgorithmException. 160 | */ 161 | public function test_building_encoded_token_with_missing_algorithm(): void 162 | { 163 | $header = Base64Url::encode(json_encode(['typ' => 'JWT'])); 164 | $payload = Base64Url::encode(json_encode([])); 165 | $signature = Base64Url::encode('signature'); 166 | 167 | $token = sprintf('%s.%s.%s', $header, $payload, $signature); 168 | 169 | $this->expectException(UndefinedAlgorithmException::class); 170 | new TokenEncoded($token); 171 | } 172 | 173 | /** 174 | * Checks if it's not possible to create an encoded token which has an empty signature. 175 | * 176 | * This should result with InsecureTokenException. 177 | */ 178 | public function test_building_encoded_token_with_empty_signature(): void 179 | { 180 | $header = Base64Url::encode(json_encode(['typ' => 'JWT', 'alg' => JWT::ALGORITHM_HS256])); 181 | $payload = Base64Url::encode(json_encode([])); 182 | 183 | $token = sprintf('%s.%s.', $header, $payload); 184 | 185 | $this->expectException(InsecureTokenException::class); 186 | new TokenEncoded($token); 187 | } 188 | 189 | /** 190 | * Checks if it's not possible to create an encoded token with an invalid exp value. 191 | * 192 | * exp must be integer number and other values should not be accepted. 193 | * 194 | * This should result with InvalidClaimTypeException. 195 | */ 196 | public function test_building_encoded_token_with_invalid_exp_claim_type(): void 197 | { 198 | $header = Base64Url::encode(json_encode(['typ' => 'JWT', 'alg' => JWT::ALGORITHM_HS256])); 199 | $payload = Base64Url::encode(json_encode(['exp' => 'string'])); 200 | $signature = Base64Url::encode('signature'); 201 | 202 | $token = sprintf('%s.%s.%s', $header, $payload, $signature); 203 | 204 | $this->expectException(InvalidClaimTypeException::class); 205 | new TokenEncoded($token); 206 | } 207 | 208 | /** 209 | * Checks if it's not possible to create an encoded token with an invalid nbf value. 210 | * 211 | * nbf must be integer number and other values should not be accepted. 212 | * 213 | * This should result with InvalidClaimTypeException. 214 | */ 215 | public function test_building_encoded_token_with_invalid_nbf_claim_type(): void 216 | { 217 | $header = Base64Url::encode(json_encode(['typ' => 'JWT', 'alg' => JWT::ALGORITHM_HS256])); 218 | $payload = Base64Url::encode(json_encode(['nbf' => 'string'])); 219 | $signature = Base64Url::encode('signature'); 220 | 221 | $token = sprintf('%s.%s.%s', $header, $payload, $signature); 222 | 223 | $this->expectException(InvalidClaimTypeException::class); 224 | new TokenEncoded($token); 225 | } 226 | 227 | /** 228 | * Checks if it's not possible to create an encoded token with an invalid iat value. 229 | * 230 | * iat must be integer number and other values should not be accepted. 231 | * 232 | * This should result with InvalidClaimTypeException. 233 | */ 234 | public function test_building_encoded_token_with_invalid_iat_claim_type(): void 235 | { 236 | $header = Base64Url::encode(json_encode(['typ' => 'JWT', 'alg' => JWT::ALGORITHM_HS256])); 237 | $payload = Base64Url::encode(json_encode(['iat' => 'string'])); 238 | $signature = Base64Url::encode('signature'); 239 | 240 | $token = sprintf('%s.%s.%s', $header, $payload, $signature); 241 | 242 | $this->expectException(InvalidClaimTypeException::class); 243 | new TokenEncoded($token); 244 | } 245 | 246 | /** 247 | * Checks if it's not possible to create an encoded token with an invalid iss value. 248 | * 249 | * iss must be string and other values should not be accepted. 250 | * 251 | * This should result with InvalidClaimTypeException. 252 | */ 253 | public function test_building_encoded_token_with_invalid_iss_claim_type(): void 254 | { 255 | $header = Base64Url::encode(json_encode(['typ' => 'JWT', 'alg' => JWT::ALGORITHM_HS256])); 256 | $payload = Base64Url::encode(json_encode(['iss' => 1])); 257 | $signature = Base64Url::encode('signature'); 258 | 259 | $token = sprintf('%s.%s.%s', $header, $payload, $signature); 260 | 261 | $this->expectException(InvalidClaimTypeException::class); 262 | new TokenEncoded($token); 263 | } 264 | 265 | /** 266 | * Checks if it's not possible to create an encoded token with an invalid aud value. 267 | * 268 | * aud must be string and other values should not be accepted. 269 | * 270 | * This should result with InvalidClaimTypeException. 271 | */ 272 | public function test_building_encoded_token_with_invalid_aud_claim_type(): void 273 | { 274 | $header = Base64Url::encode(json_encode(['typ' => 'JWT', 'alg' => JWT::ALGORITHM_HS256])); 275 | $payload = Base64Url::encode(json_encode(['aud' => 1])); 276 | $signature = Base64Url::encode('signature'); 277 | 278 | $token = sprintf('%s.%s.%s', $header, $payload, $signature); 279 | 280 | $this->expectException(InvalidClaimTypeException::class); 281 | new TokenEncoded($token); 282 | } 283 | 284 | /** 285 | * Checks if it's not possible to create an encoded token with an invalid jti value. 286 | * 287 | * jti must be string and other values should not be accepted. 288 | * 289 | * This should result with InvalidClaimTypeException. 290 | */ 291 | public function test_building_encoded_token_with_invalid_jti_claim_type(): void 292 | { 293 | $header = Base64Url::encode(json_encode(['typ' => 'JWT', 'alg' => JWT::ALGORITHM_HS256])); 294 | $payload = Base64Url::encode(json_encode(['jti' => 1])); 295 | $signature = Base64Url::encode('signature'); 296 | 297 | $token = sprintf('%s.%s.%s', $header, $payload, $signature); 298 | 299 | $this->expectException(InvalidClaimTypeException::class); 300 | new TokenEncoded($token); 301 | } 302 | 303 | /** 304 | * Checks if it's not possible to create an encoded token with an invalid sub value. 305 | * 306 | * sub must be string and other values should not be accepted. 307 | * 308 | * This should result with InvalidClaimTypeException. 309 | */ 310 | public function test_building_encoded_token_with_invalid_sub_claim_type(): void 311 | { 312 | $header = Base64Url::encode(json_encode(['typ' => 'JWT', 'alg' => JWT::ALGORITHM_HS256])); 313 | $payload = Base64Url::encode(json_encode(['sub' => 1])); 314 | $signature = Base64Url::encode('signature'); 315 | 316 | $token = sprintf('%s.%s.%s', $header, $payload, $signature); 317 | 318 | $this->expectException(InvalidClaimTypeException::class); 319 | new TokenEncoded($token); 320 | } 321 | 322 | /** 323 | * Checks, if it's not possible to encode a token when provided key, doesn't comply 324 | * with selected algorithm's standards. 325 | * 326 | * This should result with SigningFailedException. 327 | */ 328 | public function test_encoding_with_incorrect_key_format_for_given_algorithm(): void 329 | { 330 | $this->expectException(SigningFailedException::class); 331 | 332 | $key = ']V@IaC1%fU,DrVI'; 333 | 334 | $tokenDecoded = new TokenDecoded(); 335 | $tokenDecoded->encode($key, JWT::ALGORITHM_RS256); 336 | } 337 | 338 | /** 339 | * Checks, if it's not possible to encode a token with no algorithm defined in its header. 340 | * 341 | * This should result with InsecureTokenException. 342 | */ 343 | public function test_encoding_with_none_algorithm(): void 344 | { 345 | $this->expectException(InsecureTokenException::class); 346 | 347 | $key = ']V@IaC1%fU,DrVI'; 348 | 349 | $tokenDecoded = new TokenDecoded(['alg' => 'none']); 350 | $tokenDecoded->encode($key, 'none'); 351 | } 352 | 353 | /** 354 | * Checks, if it's not possible to encode a token with an unsupported algorithm. 355 | * 356 | * This should result with UnsupportedAlgorithmException. 357 | */ 358 | public function test_encoding_with_unsupported_algorithm(): void 359 | { 360 | $this->expectException(UnsupportedAlgorithmException::class); 361 | 362 | $key = ']V@IaC1%fU,DrVI'; 363 | 364 | $tokenDecoded = new TokenDecoded(['alg' => 'XYZ']); 365 | $tokenDecoded->encode($key, 'XYZ'); 366 | } 367 | 368 | /** 369 | * Checks, if it's not possible to encode a token with an invalid exp value. 370 | * 371 | * exp must be integer and other values should not be accepted. 372 | * 373 | * This should result with InvalidClaimTypeException. 374 | */ 375 | public function test_encoding_with_wrong_exp_claim_type(): void 376 | { 377 | $this->expectException(InvalidClaimTypeException::class); 378 | 379 | $key = ']V@IaC1%fU,DrVI'; 380 | 381 | $tokenDecoded = new TokenDecoded(['exp' => 'string']); 382 | $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 383 | } 384 | 385 | /** 386 | * Checks, if it's not possible to encode decoded token with an invalid nbf value. 387 | * 388 | * nbf must be integer and other values should not be accepted. 389 | * 390 | * This should result with InvalidClaimTypeException. 391 | */ 392 | public function test_encoding_with_wrong_nbf_claim_type(): void 393 | { 394 | $this->expectException(InvalidClaimTypeException::class); 395 | 396 | $key = ']V@IaC1%fU,DrVI'; 397 | 398 | $tokenDecoded = new TokenDecoded(['nbf' => 'string']); 399 | $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 400 | } 401 | 402 | /** 403 | * Checks, if it's not possible to encode decoded token with an invalid iat value. 404 | * 405 | * iat must be integer and other values should not be accepted. 406 | * 407 | * This should result with InvalidClaimTypeException. 408 | */ 409 | public function test_encoding_with_wrong_iat_claim_type(): void 410 | { 411 | $this->expectException(InvalidClaimTypeException::class); 412 | 413 | $key = ']V@IaC1%fU,DrVI'; 414 | 415 | $tokenDecoded = new TokenDecoded(['iat' => 'string']); 416 | $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 417 | } 418 | 419 | /** 420 | * Checks, if it's not possible to encode decoded token with an invalid iss value. 421 | * 422 | * iss must be string and other values should not be accepted. 423 | * 424 | * This should result with InvalidClaimTypeException. 425 | */ 426 | public function test_encoding_with_wrong_iss_claim_type(): void 427 | { 428 | $this->expectException(InvalidClaimTypeException::class); 429 | 430 | $key = ']V@IaC1%fU,DrVI'; 431 | 432 | $tokenDecoded = new TokenDecoded(['iss' => 1]); 433 | $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 434 | } 435 | 436 | /** 437 | * Checks, if it's not possible to encode decoded token with an invalid sub value. 438 | * 439 | * sub must be string and other values should not be accepted. 440 | * 441 | * This should result with InvalidClaimTypeException. 442 | */ 443 | public function test_encoding_with_wrong_sub_claim_type(): void 444 | { 445 | $this->expectException(InvalidClaimTypeException::class); 446 | 447 | $key = ']V@IaC1%fU,DrVI'; 448 | 449 | $tokenDecoded = new TokenDecoded(['sub' => 1]); 450 | $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 451 | } 452 | 453 | /** 454 | * Checks, if it's not possible to encode decoded token with an invalid aud value. 455 | * 456 | * aud must be string and other values should not be accepted. 457 | * 458 | * This should result with InvalidClaimTypeException. 459 | */ 460 | public function test_encoding_with_wrong_aud_claim_type(): void 461 | { 462 | $this->expectException(InvalidClaimTypeException::class); 463 | 464 | $key = ']V@IaC1%fU,DrVI'; 465 | 466 | $tokenDecoded = new TokenDecoded(['aud' => 1]); 467 | $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 468 | } 469 | 470 | /** 471 | * Checks, if it's not possible to encode decoded token with an invalid jti value. 472 | * 473 | * jti must be string and other values should not be accepted. 474 | * 475 | * This should result with InvalidClaimTypeException. 476 | */ 477 | public function test_encoding_with_wrong_jti_claim_type(): void 478 | { 479 | $this->expectException(InvalidClaimTypeException::class); 480 | 481 | $key = ']V@IaC1%fU,DrVI'; 482 | 483 | $tokenDecoded = new TokenDecoded(['jti' => 1]); 484 | $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 485 | } 486 | 487 | /** 488 | * Checks, if it's possible to encode the token when header was set 489 | * through setter method instead of constructor. 490 | */ 491 | public function test_encoding_decoding_with_indirect_header(): void 492 | { 493 | $key = ']V@IaC1%fU,DrVI'; 494 | 495 | $timestamp = time(); 496 | 497 | $tokenDecoded = new TokenDecoded(); 498 | $tokenDecoded->setHeader(['xyz' => $timestamp]); 499 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 500 | 501 | $header = $tokenEncoded->decode()->getHeader(); 502 | $this->assertTrue(array_key_exists('xyz', $header)); 503 | $this->assertEquals($timestamp, $header['xyz']); 504 | } 505 | 506 | /** 507 | * Checks, if it's possible to encode the token when payload was set through 508 | * setter method instead of constructor. 509 | */ 510 | public function test_encoding_decoding_with_indirect_payload(): void 511 | { 512 | $key = ']V@IaC1%fU,DrVI'; 513 | 514 | $tokenDecoded = new TokenDecoded(); 515 | $tokenDecoded->setPayload(['success' => 1]); 516 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 517 | 518 | $payload = $tokenEncoded->decode()->getPayload(); 519 | $this->assertTrue(array_key_exists('success', $payload)); 520 | $this->assertEquals(1, $payload['success']); 521 | } 522 | 523 | /** 524 | * Checks, if it's possible to encode the token when alg was not defined in token's header. 525 | * 526 | * Default algorithm should be set automatically. 527 | */ 528 | public function test_encoding_decoding_with_auto_appending_header_alg(): void 529 | { 530 | $key = ']V@IaC1%fU,DrVI'; 531 | 532 | $tokenDecoded = new TokenDecoded(); 533 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 534 | 535 | $header = $tokenEncoded->decode()->getHeader(); 536 | $this->assertTrue(array_key_exists('alg', $header)); 537 | $this->assertEquals(JWT::ALGORITHM_HS256, $header['alg']); 538 | } 539 | 540 | /** 541 | * Checks basic decoding payload functionality. 542 | */ 543 | public function test_decoding_payload(): void 544 | { 545 | $key = ']V@IaC1%fU,DrVI'; 546 | 547 | $tokenDecoded = new TokenDecoded(['success' => 1]); 548 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 549 | 550 | $payload = $tokenEncoded->decode()->getPayload(); 551 | $this->assertTrue(array_key_exists('success', $payload)); 552 | $this->assertEquals(1, $payload['success']); 553 | } 554 | 555 | /** 556 | * Checks successful encoding, decoding and validating flow for HS256. 557 | */ 558 | public function test_validation_integrity_hs256(): void 559 | { 560 | $this->check_token_integrity(JWT::ALGORITHM_HS256, ']V@IaC1%fU,DrVI'); 561 | } 562 | 563 | /** 564 | * Checks unsuccessful encoding, decoding and validating flow for HS256. 565 | */ 566 | public function test_validation_integrity_violation_hs256(): void 567 | { 568 | $this->check_token_integrity_violation(JWT::ALGORITHM_HS256, ']V@IaC1%fU,DrVI', 'ErC0gfQ0qlkf6WQ'); 569 | } 570 | 571 | /** 572 | * Checks successful encoding, decoding and validating flow for HS384. 573 | */ 574 | public function test_validation_integrity_hs384(): void 575 | { 576 | $this->check_token_integrity(JWT::ALGORITHM_HS384, ']V@IaC1%fU,DrVI'); 577 | } 578 | 579 | /** 580 | * Checks unsuccessful encoding, decoding and validating flow for HS384. 581 | */ 582 | public function test_validation_integrity_violation_hs384(): void 583 | { 584 | $this->check_token_integrity_violation(JWT::ALGORITHM_HS384, ']V@IaC1%fU,DrVI', 'ErC0gfQ0qlkf6WQ'); 585 | } 586 | 587 | /** 588 | * Checks successful encoding, decoding and validating flow for HS512. 589 | */ 590 | public function test_validation_integrity_hs512(): void 591 | { 592 | $this->check_token_integrity(JWT::ALGORITHM_HS512, ']V@IaC1%fU,DrVI'); 593 | } 594 | 595 | /** 596 | * Checks unsuccessful encoding, decoding and validating flow for HS512. 597 | */ 598 | public function test_validation_integrity_violation_hs512(): void 599 | { 600 | $this->check_token_integrity_violation(JWT::ALGORITHM_HS512, ']V@IaC1%fU,DrVI', 'ErC0gfQ0qlkf6WQ'); 601 | } 602 | 603 | /** 604 | * Checks successful encoding, decoding and validating flow for RS256. 605 | */ 606 | public function test_validation_integrity_rs256(): void 607 | { 608 | $this->check_token_integrity(JWT::ALGORITHM_RS256, file_get_contents('./tests/keys/rs256/private.key'), file_get_contents('./tests/keys/rs256/public.pub')); 609 | } 610 | 611 | /** 612 | * Checks unsuccessful encoding, decoding and validating flow for RS256. 613 | */ 614 | public function test_validation_integrity_violation_rs256(): void 615 | { 616 | $this->check_token_integrity_violation(JWT::ALGORITHM_RS256, file_get_contents('./tests/keys/rs256/private.key'), file_get_contents('./tests/keys/rs256/public_invalid.pub')); 617 | } 618 | 619 | /** 620 | * Checks successful encoding, decoding and validating flow for RS384. 621 | */ 622 | public function test_validation_integrity_rs384(): void 623 | { 624 | $this->check_token_integrity(JWT::ALGORITHM_RS384, file_get_contents('./tests/keys/rs384/private.key'), file_get_contents('./tests/keys/rs384/public.pub')); 625 | } 626 | 627 | /** 628 | * Checks unsuccessful encoding, decoding and validating flow for RS384. 629 | */ 630 | public function test_validation_integrity_violation_rs384(): void 631 | { 632 | $this->check_token_integrity_violation(JWT::ALGORITHM_RS384, file_get_contents('./tests/keys/rs384/private.key'), file_get_contents('./tests/keys/rs384/public_invalid.pub')); 633 | } 634 | 635 | /** 636 | * Checks successful encoding, decoding and validating flow for RS512. 637 | */ 638 | public function test_validation_integrity_rs512(): void 639 | { 640 | $this->check_token_integrity(JWT::ALGORITHM_RS512, file_get_contents('./tests/keys/rs512/private.key'), file_get_contents('./tests/keys/rs512/public.pub')); 641 | } 642 | 643 | /** 644 | * Checks unsuccessful encoding, decoding and validating flow for RS512. 645 | */ 646 | public function test_validation_integrity_violation_rs512(): void 647 | { 648 | $this->check_token_integrity_violation(JWT::ALGORITHM_RS512, file_get_contents('./tests/keys/rs512/private.key'), file_get_contents('./tests/keys/rs512/public_invalid.pub')); 649 | } 650 | 651 | /** 652 | * Checks if validation fails for token with unsupported algorithm. 653 | * 654 | * This should result with UnsupportedAlgorithmException. 655 | */ 656 | public function test_validating_with_unsupported_algorithm(): void 657 | { 658 | $header = Base64Url::encode(json_encode(['typ' => 'JWT', 'alg' => 'XYZ'])); 659 | $payload = Base64Url::encode(json_encode([])); 660 | $signature = Base64Url::encode('signature'); 661 | 662 | $token = sprintf('%s.%s.%s', $header, $payload, $signature); 663 | $key = ']V@IaC1%fU,DrVI'; 664 | 665 | $this->expectException(UnsupportedAlgorithmException::class); 666 | $tokenEncoded = new TokenEncoded($token); 667 | $tokenEncoded->validate($key); 668 | } 669 | 670 | /** 671 | * Checks if validation succeeds for token with valid exp. 672 | */ 673 | public function test_validation_expiration_date_valid(): void 674 | { 675 | $key = ']V@IaC1%fU,DrVI'; 676 | 677 | $timestamp = time() + 100; 678 | 679 | $exception = false; 680 | 681 | try { 682 | $tokenDecoded = new TokenDecoded(['exp' => $timestamp]); 683 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 684 | 685 | $tokenEncoded->validate($key, JWT::ALGORITHM_HS256); 686 | } catch (Exception $e) { 687 | $exception = true; 688 | } 689 | 690 | $this->assertFalse($exception); 691 | } 692 | 693 | /** 694 | * Checks if validation fails for token with an invalid exp. 695 | * 696 | * This should result with TokenExpiredException. 697 | */ 698 | public function test_validation_with_expiration_date_invalid(): void 699 | { 700 | $this->expectException(TokenExpiredException::class); 701 | 702 | $key = ']V@IaC1%fU,DrVI'; 703 | 704 | $timestamp = time() - 100; 705 | 706 | $tokenDecoded = new TokenDecoded(['exp' => $timestamp]); 707 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 708 | 709 | $tokenEncoded->validate($key, JWT::ALGORITHM_HS256); 710 | } 711 | 712 | /** 713 | * Checks if validation succeeds for token with valid exp. 714 | */ 715 | public function test_validation_with_expiration_date_invalid_leeway_valid(): void 716 | { 717 | $key = ']V@IaC1%fU,DrVI'; 718 | 719 | $timestamp = time() - 100; 720 | 721 | $exception = false; 722 | 723 | try { 724 | $tokenDecoded = new TokenDecoded(['exp' => $timestamp]); 725 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 726 | 727 | $tokenEncoded->validate($key, JWT::ALGORITHM_HS256, 101); 728 | } catch (Exception $e) { 729 | $exception = true; 730 | } 731 | 732 | $this->assertFalse($exception); 733 | } 734 | 735 | /** 736 | * Checks if validation fails for token with an invalid exp and not compensated by leeway. 737 | * 738 | * This should result with TokenExpiredException. 739 | */ 740 | public function test_validation_with_expiration_date_invalid_leeway_invalid(): void 741 | { 742 | $this->expectException(TokenExpiredException::class); 743 | 744 | $key = ']V@IaC1%fU,DrVI'; 745 | 746 | $timestamp = time() - 100; 747 | 748 | $tokenDecoded = new TokenDecoded(['exp' => $timestamp]); 749 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 750 | 751 | $tokenEncoded->validate($key, JWT::ALGORITHM_HS256, 100); 752 | } 753 | 754 | /** 755 | * Checks if validation succeeds for token with valid nbf. 756 | */ 757 | public function test_validation_with_not_before_date_valid(): void 758 | { 759 | $key = ']V@IaC1%fU,DrVI'; 760 | 761 | $timestamp = time() - 100; 762 | 763 | $exception = false; 764 | 765 | try { 766 | $tokenDecoded = new TokenDecoded(['nbf' => $timestamp]); 767 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 768 | 769 | $tokenEncoded->validate($key, JWT::ALGORITHM_HS256); 770 | } catch (Exception $e) { 771 | $exception = true; 772 | } 773 | 774 | $this->assertFalse($exception); 775 | } 776 | 777 | /** 778 | * Checks if validation fails for token with an invalid nbf. 779 | * 780 | * This should result with TokenInactiveException. 781 | */ 782 | public function test_validation_with_not_before_date_invalid(): void 783 | { 784 | $this->expectException(TokenInactiveException::class); 785 | 786 | $key = ']V@IaC1%fU,DrVI'; 787 | 788 | $timestamp = time() + 100; 789 | 790 | $tokenDecoded = new TokenDecoded(['nbf' => $timestamp]); 791 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 792 | 793 | $tokenEncoded->validate($key, JWT::ALGORITHM_HS256); 794 | } 795 | 796 | /** 797 | * Checks if validation succeeds for token with an invalid nbf but compensated by leeway. 798 | */ 799 | public function test_validation_with_not_before_date_invalid_leeway_valid(): void 800 | { 801 | $key = ']V@IaC1%fU,DrVI'; 802 | 803 | $timestamp = time() + 100; 804 | 805 | $exception = false; 806 | 807 | try { 808 | $tokenDecoded = new TokenDecoded(['nbf' => $timestamp]); 809 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 810 | 811 | $tokenEncoded->validate($key, JWT::ALGORITHM_HS256, 100); 812 | } catch (Exception $e) { 813 | $exception = true; 814 | } 815 | 816 | $this->assertFalse($exception); 817 | } 818 | 819 | /** 820 | * Checks if validation fails for token with an invalid nbf and not compensated by leeway. 821 | * 822 | * This should result with TokenInactiveException. 823 | */ 824 | public function test_validation_with_not_before_date_invalid_leeway_invalid(): void 825 | { 826 | $this->expectException(TokenInactiveException::class); 827 | 828 | $key = ']V@IaC1%fU,DrVI'; 829 | 830 | $timestamp = time() + 100; 831 | 832 | $exception = false; 833 | 834 | $tokenDecoded = new TokenDecoded(['nbf' => $timestamp]); 835 | $tokenEncoded = $tokenDecoded->encode($key, JWT::ALGORITHM_HS256); 836 | 837 | $tokenEncoded->validate($key, JWT::ALGORITHM_HS256, 99); 838 | } 839 | } 840 | -------------------------------------------------------------------------------- /tests/keys/rs256/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /tests/keys/rs256/public.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsf+zPeRVrqDIiQofDz+u 3 | gXnFqOfxaBrVkhbJR8PGM9C/xbg8eeCGQzxvdLvrG+4xbW9XqwXuzfTZJ2zgvG7H 4 | mOBpBaYeJsFY7X4rV/80oAg4qtwH2KZLRSA+PJOp6HGqHkg0z9aJ3No0wL3w+9ZU 5 | dcDjmhTpHMw4QVwfApOJCNK1FEpE4P3ScMVihnnkvi6IXfu4a8X+rPDkjpatzuPm 6 | Xf5JIHDGyRbIKzcW070GB8B2X+XQ1Fa+E6XB1316HKK8K5ZxOu4vPvTUmpsozhpq 7 | AaI4DC7GbKwD6VYPVNlQBgLUti4/oQqYAaeQAmgertGCayzQoFibWI5D31XiwmmW 8 | Bm0w5PUnYxvHlMrVQiv3TUVY0zghX++zYuwBTaF2H3gxQnL4uQ0RiKzt1wiiDajB 9 | vDB3PCFDvy68rzblk7zz9wm9EPts58zBwN5aQZUmdv1BSjlWBj7sOW7niwf7kFrE 10 | l1HV0rc7SqRRIJKGZrOFWOLO/mrWnvKZ0P9jgpLiAihXeT903VyvIBu0FSmS5jxG 11 | ziycIE+hAdExQdsxvX/UCW62l5rykLfS16gvJAiST45uBeZRdSXl4XRKAopkvaPj 12 | cvc4TCsdxSxkKrUSWbGTTjUQolt/4778qM9QT2fTSlxPueLs5KfeGPdE4IvG8XdP 13 | pJ/zYjGQ/Zqf9MuDnoci4NkCAwEAAQ== 14 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /tests/keys/rs256/public_invalid.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsf+zPeRVrqDIiQofDz+u 3 | gXnFqOfxaBrVkhbJR8PGM9C/xbg8eeCGQzxvdLvrG+4xbW9XqwXuzfTZJ2zgvG7H 4 | mOBpBaYeJsFY7X4rV/80oAg4qtwH2KZLRSA+PJOp6HGqHkg0z9aJ3No0wL3w+9ZU 5 | dcDjmhTpHMw4QVwfApOJCNK1FEpE4P3ScMVihnnkvi6IXfu4a8X+rPDkjpatzuPm 6 | Xf5JIHDGyRbIKzcW070GB8B2X+XQ1Fa+E6XB1316HKK8K5ZxOu4vPvTUmpsozhpq 7 | AaI4DC7GbKwD6VYPVNlQBgLUti4/oQqYAaeQAmgertGCayzQoFibWI5D31XiwmmW 8 | Bm0w5PUnYxvHlMrVQiv3TUVY0zghX++zYuwBTaF2H3gxQnL4uQ0RiKzt1wiiDajB 9 | vDB3PCFDvy68rzblk7zz9wm9EPts58zBwN5aQZUmdv1BSjlWBj7sOW7niwf7kFrE 10 | l1HV0rc7SqRRIJKGZrOFWOLO/mrWnvKZ0P9jgpLiAihXeT903VyvIBu0FSmS5jxG 11 | ziycIE+hAdExQdsxvX/UCW62l5rykLfS16gvJAiST45uBeZRdSXl4XRKAopkvaPj 12 | cvc4TCsdxSxkKrUSWbGTTjUQolt/4778qM9QT2fTSlxPueLs5KfeGPdE4IvG8XdP 13 | pJ/zYjGQ/Zqf9MuDnoci4NkCAwEAAB== 14 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /tests/keys/rs384/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /tests/keys/rs384/public.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxSnigwzJzojKUtizneZQ 3 | k2lXaaCO65jDiyEHlrcpxk/zduh+cWrvjSD2DWgzLJay3c67gw/Aru4eQWHWtsPk 4 | P0bHi0v6WlpFbeoFDLldKsDI/e0+LWJOIUFligjTYluANwLedERBefMWVIX2qewu 5 | W6ZsBLZUVR+NirA1MQocqvIFwt8Gb7IroOm/P86X78QUHnHYHlm0TsV9plt6Dv68 6 | 2ZIST9QM2wPPeD3RqO1OV78nXsgngnpNgtU6Wwt/ZtxcEdjf7fLMkD3zvvwkwKeZ 7 | bTEIhmyamH3aZPOx+hk8/NBq+pX+FCCK6go/btoG3NKKII9fMpSiHwiyCji4294E 8 | D4vHhS4Z+zMH3xk4+CHb5Kvh2CwmxiwJsgfLxCxisQaX6ex8j5gaE8noRqRU/DVD 9 | lkjdvkm+sDYK5shIKozyZo8tXdT9qR342RYe4IV4iyjNcw7w5VPGWX61+SR52wvI 10 | 4HtTdLFlfG1WsssA0mB6qIYEKu0UUtmptIYbZWxatglB9NybAH7Fe+JCeDyW2E9H 11 | HuKJ2fziwk7Wl0Aj8ccJS+7qGQ/aNRqgdm9xs2Eb6zWHXlmlRaoGBECeJi+YFtpg 12 | DkxSMycNZ2P6qI1p22E5fWvtYvUIgnyQZA50pDZ1eQt7v9ojX9uve3l2sTRy8yvj 13 | v351S9loFyNoknv6hyEsAuUCAwEAAQ== 14 | -----END PUBLIC KEY----- 15 | -------------------------------------------------------------------------------- /tests/keys/rs384/public_invalid.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxSnigwzJzojKUtizneZQ 3 | k2lXaaCO65jDiyEHlrcpxk/zduh+cWrvjSD2DWgzLJay3c67gw/Aru4eQWHWtsPk 4 | P0bHi0v6WlpFbeoFDLldKsDI/e0+LWJOIUFligjTYluANwLedERBefMWVIX2qewu 5 | W6ZsBLZUVR+NirA1MQocqvIFwt8Gb7IroOm/P86X78QUHnHYHlm0TsV9plt6Dv68 6 | 2ZIST9QM2wPPeD3RqO1OV78nXsgngnpNgtU6Wwt/ZtxcEdjf7fLMkD3zvvwkwKeZ 7 | bTEIhmyamH3aZPOx+hk8/NBq+pX+FCCK6go/btoG3NKKII9fMpSiHwiyCji4294E 8 | D4vHhS4Z+zMH3xk4+CHb5Kvh2CwmxiwJsgfLxCxisQaX6ex8j5gaE8noRqRU/DVD 9 | lkjdvkm+sDYK5shIKozyZo8tXdT9qR342RYe4IV4iyjNcw7w5VPGWX61+SR52wvI 10 | HHtTdLFlfG1WsssA0mB6qIYEKu0UUtmptIYbZWxatglB9NybAH7Fe+JCeDyW2E9H 11 | H1KJ2fziwk7Wl0Aj8ccJS+7qGQ/aNRqgdm9xs2Eb6zWHXlmlRaoGBECeJi+YFtpg 12 | DkxSMycNZ2P6qI1p22E5fWvtYvUIgnyQZA50pDZ1eQt7v9ojX9uve3l2sTRy8yvj 13 | v351S9loFyNoknv6hyEsAuUCAwEAAQ== 14 | -----END PUBLIC KEY----- 15 | -------------------------------------------------------------------------------- /tests/keys/rs512/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /tests/keys/rs512/public.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2/ztEQpW9+JPdASK4oA3 3 | rtOZAC99AczdD3R+PdrPM5DNKGRxmjcb1cxv4dmeft+2cEdvTAPwNZfDtX/DtrAG 4 | aKfVsRwvkY6e/6pgRDG4p1KsLtSjcrveMCy800CFnmxIjLRCtOrXCYirHovw/LaX 5 | rLVjNbGe3vyvEE7sFy3SuBLsnBX/DaB0+3tYUXd6MS+4tF5LZOE1WXjLtdX8P/Rh 6 | ztRVixtOphhXcGXk1ww/gwXHl+XsIXigwY3d4R36NMlQ2dnqB6g9zcBbtBripppT 7 | A9NQXfjvTVcCqm22VHPj0fSJA+7wsSiOS4QkNs9jR97QPPxFE21m9IDBiGNbEtm0 8 | Cj/0+/wc0ilY78Yfx/26wnpXdRJ670o9HT7S1zbkysOMcgQN3elcGEvgydD8ZGJe 9 | dDSGqq0bMPitRvo3oVio6XapsgnJxV0ZaW7IZ+l81D4uj0UcmjlbL9FiX892Qivy 10 | kVyhSGNsu9+u1thGV7Lt7nA+CLbHRhpML7UYAXsWLE7NtiPml0ecwfeC1TT7uU4B 11 | 3LApxumHUVn4TLoNb1RLVmcIdh4PvVwIS/W3NKOSBOLiLdmKADiqokQnnnes9CL7 12 | m6KT9Ub81mVy4uF43hznThiUCZlHYxmz1yDoVNXb50TpOYhTaODQs//7ndIfgY9b 13 | ag1XLEZK8ANRLrozZ97SgYUCAwEAAQ== 14 | -----END PUBLIC KEY----- 15 | -------------------------------------------------------------------------------- /tests/keys/rs512/public_invalid.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2/ztEQpW9+JPdASK4oA3 3 | rtOZAC99AczdD3R+PdrPM5DNKGRxmjcb1cxv4dmeft+2cEdvTAPwNZfDtX/DtrAG 4 | aKfVsRwvkY6e/6pgRDG4p1KsLtSjcrveMCy800CFnmxIjLRCtOrXCYirHovw/LaX 5 | rLVjNbGe3vyvEE7sFy3SuBLsnBX/DaB0+3tYUXd6MS+4tF5LZOE1WXjLtdX8P/Rh 6 | ztRVixtOphhXcGXk1ww/gwXHl+XsIXigwY3d4R36NMlQ2dnqB6g9zcBbtBripppT 7 | A9NQXfjvTVcCqm22VHPj0fSJA+7wsSiOS4QkNs9jR97QPPxFE21m9IDBiGNbEtm0 8 | Cj/0+/wc0ilY78Yfx/26wnpXdRJ670o9HT7S1zbkysOMcgQN3elcGEvgydD8ZGJe 9 | dDRGqq0bMPitRvo3oVio6XapsgnJxV0ZaW7IZ+l81D4uj0UcmjlbL9FiX892Qivy 10 | yVyhSGNsu9+u1thGV7Lt7nA+CLbHRhpML7UYAXsWLE7NtiPml0ecwfeC1TT7uU4B 11 | 3LApxumHUVn4TLoNb1RLVmcIdh4PvVwIS/W3NKOSBOLiLdmKADiqokQnnnes9CL7 12 | m6KT9Ub81mVy4uF43hznThiUCZlHYxmz1yDoVNXb50TpOYhTaODQs//7ndIfgY9b 13 | ag1XLEZK8ANRLrozZ97SgYUCAwEAAQ== 14 | -----END PUBLIC KEY----- 15 | --------------------------------------------------------------------------------