From 45cb0f4874bbef8deb395e9f37ad24dfed080c54 Mon Sep 17 00:00:00 2001 From: nicumicle <20170987+nicumicleI@users.noreply.github.com> Date: Wed, 21 Sep 2022 07:41:07 +0200 Subject: [PATCH 01/13] Add error codes to all exceptions --- src/BeforeValidException.php | 2 + src/CachedKeySet.php | 20 ++++- src/ExceptionCodes.php | 60 +++++++++++++ src/ExpiredException.php | 1 + src/JWK.php | 64 +++++++++++--- src/JWT.php | 141 +++++++++++++++++++++++------- src/Key.php | 15 +++- src/SignatureInvalidException.php | 1 + 8 files changed, 254 insertions(+), 50 deletions(-) create mode 100644 src/ExceptionCodes.php diff --git a/src/BeforeValidException.php b/src/BeforeValidException.php index c147852b..70a22065 100644 --- a/src/BeforeValidException.php +++ b/src/BeforeValidException.php @@ -4,4 +4,6 @@ class BeforeValidException extends \UnexpectedValueException { + public const NBF_PRIOR_TO_DATE = 1; + const IAT_PRIOR_TO_DATE = 2; } diff --git a/src/CachedKeySet.php b/src/CachedKeySet.php index e2215b30..d0c4ea22 100644 --- a/src/CachedKeySet.php +++ b/src/CachedKeySet.php @@ -99,7 +99,10 @@ public function __construct( public function offsetGet($keyId): Key { if (!$this->keyIdExists($keyId)) { - throw new OutOfBoundsException('Key ID not found'); + throw new OutOfBoundsException( + 'Key ID not found', + ExceptionCodes::KEY_ID_NOT_FOUND + ); } return $this->keySet[$keyId]; } @@ -119,7 +122,10 @@ public function offsetExists($keyId): bool */ public function offsetSet($offset, $value): void { - throw new LogicException('Method not implemented'); + throw new LogicException( + 'Method not implemented', + ExceptionCodes::OFFSET_SET_METHOD_NOT_IMPLEMENTED + ); } /** @@ -127,7 +133,10 @@ public function offsetSet($offset, $value): void */ public function offsetUnset($offset): void { - throw new LogicException('Method not implemented'); + throw new LogicException( + 'Method not implemented', + ExceptionCodes::OFFSET_UNSET_METHOD_NOT_IMPLEMENTED + ); } private function keyIdExists(string $keyId): bool @@ -198,7 +207,10 @@ private function getCacheItem(): CacheItemInterface private function setCacheKeys(): void { if (empty($this->jwksUri)) { - throw new RuntimeException('JWKS URI is empty'); + throw new RuntimeException( + 'JWKS URI is empty', + ExceptionCodes::JWKS_URI_IS_EMPTY + ); } // ensure we do not have illegal characters diff --git a/src/ExceptionCodes.php b/src/ExceptionCodes.php new file mode 100644 index 00000000..40891fa0 --- /dev/null +++ b/src/ExceptionCodes.php @@ -0,0 +1,60 @@ + $v) { @@ -65,7 +71,11 @@ public static function parseKeySet(array $jwks, string $defaultAlg = null): arra } if (0 === \count($keys)) { - throw new UnexpectedValueException('No supported algorithms found in JWK Set'); + throw new UnexpectedValueException( + 'No supported algorithms found in JWK Set', + ExceptionCodes::JWT_ALGORITHM_NOT_SUPPORTED + + ); } return $keys; @@ -89,11 +99,17 @@ public static function parseKeySet(array $jwks, string $defaultAlg = null): arra public static function parseKey(array $jwk, string $defaultAlg = null): ?Key { if (empty($jwk)) { - throw new InvalidArgumentException('JWK must not be empty'); + throw new InvalidArgumentException( + 'JWK must not be empty', + ExceptionCodes::JWK_IS_EMPTY + ); } if (!isset($jwk['kty'])) { - throw new UnexpectedValueException('JWK must contain a "kty" parameter'); + throw new UnexpectedValueException( + 'JWK must contain a "kty" parameter', + ExceptionCodes::JWT_MISSING_KTY_PARAMETER + ); } if (!isset($jwk['alg'])) { @@ -102,7 +118,10 @@ public static function parseKey(array $jwk, string $defaultAlg = null): ?Key // for parsing in this library. Use the $defaultAlg parameter when parsing the // key set in order to prevent this error. // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 - throw new UnexpectedValueException('JWK must contain an "alg" parameter'); + throw new UnexpectedValueException( + 'JWK must contain an "alg" parameter', + ExceptionCodes::JWT_MISSING_ALG_PARAMETER + ); } $jwk['alg'] = $defaultAlg; } @@ -110,36 +129,55 @@ public static function parseKey(array $jwk, string $defaultAlg = null): ?Key switch ($jwk['kty']) { case 'RSA': if (!empty($jwk['d'])) { - throw new UnexpectedValueException('RSA private keys are not supported'); + throw new UnexpectedValueException( + 'RSA private keys are not supported', + ExceptionCodes::JWT_RSA_KEYS_NOT_SUPPORTED + ); } if (!isset($jwk['n']) || !isset($jwk['e'])) { - throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"'); + throw new UnexpectedValueException( + 'RSA keys must contain values for both "n" and "e"', + ExceptionCodes::JWT_RSA_KEYS_MISSING_N_AND_E + ); } $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']); $publicKey = \openssl_pkey_get_public($pem); if (false === $publicKey) { throw new DomainException( - 'OpenSSL error: ' . \openssl_error_string() + 'OpenSSL error: ' . \openssl_error_string(), + ExceptionCodes::JWT_OPEN_SSL_ERROR ); } return new Key($publicKey, $jwk['alg']); case 'EC': if (isset($jwk['d'])) { // The key is actually a private key - throw new UnexpectedValueException('Key data must be for a public key'); + throw new UnexpectedValueException( + 'Key data must be for a public key', + ExceptionCodes::JWK_EC_D_IS_NOT_SET + ); } if (empty($jwk['crv'])) { - throw new UnexpectedValueException('crv not set'); + throw new UnexpectedValueException( + 'crv not set', + ExceptionCodes::JWT_EC_CRV_IS_EMPTY + ); } if (!isset(self::EC_CURVES[$jwk['crv']])) { - throw new DomainException('Unrecognised or unsupported EC curve'); + throw new DomainException( + 'Unrecognised or unsupported EC curve', + ExceptionCodes::JWK_UNSUPPORTED_EC_CURVE + ); } if (empty($jwk['x']) || empty($jwk['y'])) { - throw new UnexpectedValueException('x and y not set'); + throw new UnexpectedValueException( + 'x and y not set', + ExceptionCodes::JWT_X_AND_Y_ARE_EMPTY + ); } $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']); diff --git a/src/JWT.php b/src/JWT.php index 9964073d..8373998d 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -95,34 +95,55 @@ public static function decode( $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; if (empty($keyOrKeyArray)) { - throw new InvalidArgumentException('Key may not be empty'); + throw new InvalidArgumentException( + 'Key may not be empty', + ExceptionCodes::KEY_NOT_EMPTY + ); } $tks = \explode('.', $jwt); if (\count($tks) !== 3) { - throw new UnexpectedValueException('Wrong number of segments'); + throw new UnexpectedValueException( + 'Wrong number of segments', + ExceptionCodes::WRONG_NUMBER_OF_SEGMENTS + ); } list($headb64, $bodyb64, $cryptob64) = $tks; $headerRaw = static::urlsafeB64Decode($headb64); if (null === ($header = static::jsonDecode($headerRaw))) { - throw new UnexpectedValueException('Invalid header encoding'); + throw new UnexpectedValueException( + 'Invalid header encoding', + ExceptionCodes::INVALID_HEADER_ENCODING + ); } $payloadRaw = static::urlsafeB64Decode($bodyb64); if (null === ($payload = static::jsonDecode($payloadRaw))) { - throw new UnexpectedValueException('Invalid claims encoding'); + throw new UnexpectedValueException( + 'Invalid claims encoding', + ExceptionCodes::INVALID_CLAIMS_ENCODING + ); } if (\is_array($payload)) { // prevent PHP Fatal Error in edge-cases when payload is empty array $payload = (object) $payload; } if (!$payload instanceof stdClass) { - throw new UnexpectedValueException('Payload must be a JSON object'); + throw new UnexpectedValueException( + 'Payload must be a JSON object', + ExceptionCodes::PAYLOAD_NOT_JSON + ); } $sig = static::urlsafeB64Decode($cryptob64); if (empty($header->alg)) { - throw new UnexpectedValueException('Empty algorithm'); + throw new UnexpectedValueException( + 'Empty algorithm', + ExceptionCodes::EMPTY_ALGORITHM + ); } if (empty(static::$supported_algs[$header->alg])) { - throw new UnexpectedValueException('Algorithm not supported'); + throw new UnexpectedValueException( + 'Algorithm not supported', + ExceptionCodes::DECODE_ALGORITHM_NOT_SUPPORTED + ); } $key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null); @@ -130,21 +151,28 @@ public static function decode( // Check the algorithm if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) { // See issue #351 - throw new UnexpectedValueException('Incorrect key for this algorithm'); + throw new UnexpectedValueException( + 'Incorrect key for this algorithm', + ExceptionCodes::INCORRECT_KEY_FOR_ALGORITHM + ); } if ($header->alg === 'ES256' || $header->alg === 'ES384') { // OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures $sig = self::signatureToDER($sig); } if (!self::verify("${headb64}.${bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) { - throw new SignatureInvalidException('Signature verification failed'); + throw new SignatureInvalidException( + 'Signature verification failed', + SignatureInvalidException::SIGNATURE_VERIFICATION_FAILED + ); } // 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) + 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf), + BeforeValidException::NBF_PRIOR_TO_DATE ); } @@ -153,13 +181,14 @@ public static function decode( // 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) + 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat), + BeforeValidException::IAT_PRIOR_TO_DATE ); } // Check if this token has expired. if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { - throw new ExpiredException('Expired token'); + throw new ExpiredException('Expired token', ExpiredException::TOKEN_EXPIRED); } return $payload; @@ -223,20 +252,29 @@ public static function sign( string $alg ): string { if (empty(static::$supported_algs[$alg])) { - throw new DomainException('Algorithm not supported'); + throw new DomainException( + 'Algorithm not supported', + ExceptionCodes::SIGN_ALGORITHM_NOT_SUPPORTED + ); } list($function, $algorithm) = static::$supported_algs[$alg]; switch ($function) { case 'hash_hmac': if (!\is_string($key)) { - throw new InvalidArgumentException('key must be a string when using hmac'); + throw new InvalidArgumentException( + 'key must be a string when using hmac', + ExceptionCodes::KEY_IS_NOT_STRING + ); } return \hash_hmac($algorithm, $msg, $key, true); case 'openssl': $signature = ''; $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line if (!$success) { - throw new DomainException('OpenSSL unable to sign data'); + throw new DomainException( + 'OpenSSL unable to sign data', + ExceptionCodes::OPENSSL_CAN_NOT_SIGN_DATA + ); } if ($alg === 'ES256') { $signature = self::signatureFromDER($signature, 256); @@ -249,7 +287,10 @@ public static function sign( throw new DomainException('libsodium is not available'); } if (!\is_string($key)) { - throw new InvalidArgumentException('key must be a string when using EdDSA'); + throw new InvalidArgumentException( + 'key must be a string when using EdDSA', + ExceptionCodes::SODIUM_KEY_IS_NOT_STRING + ); } try { // The last non-empty line is used as the key. @@ -257,11 +298,18 @@ public static function sign( $key = base64_decode((string) end($lines)); return sodium_crypto_sign_detached($msg, $key); } catch (Exception $e) { - throw new DomainException($e->getMessage(), 0, $e); + throw new DomainException( + $e->getMessage(), + ExceptionCodes::SODIUM_EXCEPTION, + $e + ); } } - throw new DomainException('Algorithm not supported'); + throw new DomainException( + 'Algorithm not supported', + ExceptionCodes::SIGN_GENERAL_EXCEPTION + ); } /** @@ -284,7 +332,10 @@ private static function verify( string $alg ): bool { if (empty(static::$supported_algs[$alg])) { - throw new DomainException('Algorithm not supported'); + throw new DomainException( + 'Algorithm not supported', + ExceptionCodes::VERIFY_ALGORITHM_NOT_SUPPORTED + ); } list($function, $algorithm) = static::$supported_algs[$alg]; @@ -299,14 +350,21 @@ private static function verify( } // returns 1 on success, 0 on failure, -1 on error. throw new DomainException( - 'OpenSSL error: ' . \openssl_error_string() + 'OpenSSL error: ' . \openssl_error_string(), + ExceptionCodes::VERIFY_OPEN_SSL_ERROR ); case 'sodium_crypto': if (!\function_exists('sodium_crypto_sign_verify_detached')) { - throw new DomainException('libsodium is not available'); + throw new DomainException( + 'libsodium is not available', + ExceptionCodes::VERIFY_SODIUM_NOT_AVAILABLE + ); } if (!\is_string($keyMaterial)) { - throw new InvalidArgumentException('key must be a string when using EdDSA'); + throw new InvalidArgumentException( + 'key must be a string when using EdDSA', + ExceptionCodes::VERIFY_KEY_MATERIAL_IS_NOT_STRING + ); } try { // The last non-empty line is used as the key. @@ -314,12 +372,19 @@ private static function verify( $key = base64_decode((string) end($lines)); return sodium_crypto_sign_verify_detached($signature, $msg, $key); } catch (Exception $e) { - throw new DomainException($e->getMessage(), 0, $e); + throw new DomainException( + $e->getMessage(), + ExceptionCodes::VERIFY_SODIUM_EXCEPTION, + $e + ); } case 'hash_hmac': default: if (!\is_string($keyMaterial)) { - throw new InvalidArgumentException('key must be a string when using hmac'); + throw new InvalidArgumentException( + 'key must be a string when using hmac', + ExceptionCodes::VERIFY_KEY_IS_NOT_STRING + ); } $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true); return self::constantTimeEquals($hash, $signature); @@ -342,7 +407,10 @@ public static function jsonDecode(string $input) if ($errno = \json_last_error()) { self::handleJsonError($errno); } elseif ($obj === null && $input !== 'null') { - throw new DomainException('Null result with non-null input'); + throw new DomainException( + 'Null result with non-null input', + ExceptionCodes::DECODED_JSON_IS_NULL + ); } return $obj; } @@ -367,10 +435,16 @@ public static function jsonEncode(array $input): string if ($errno = \json_last_error()) { self::handleJsonError($errno); } elseif ($json === 'null' && $input !== null) { - throw new DomainException('Null result with non-null input'); + throw new DomainException( + 'Null result with non-null input', + ExceptionCodes::ENCODED_JSON_IS_NULL + ); } if ($json === false) { - throw new DomainException('Provided object could not be encoded to valid JSON'); + throw new DomainException( + 'Provided object could not be encoded to valid JSON', + ExceptionCodes::INVALID_JSON + ); } return $json; } @@ -431,10 +505,16 @@ private static function getKey( } if (empty($kid)) { - throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); + throw new UnexpectedValueException( + '"kid" empty, unable to lookup correct key', + ExceptionCodes::KID_IS_EMPTY + ); } if (!isset($keyOrKeyArray[$kid])) { - throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); + throw new UnexpectedValueException( + '"kid" invalid, unable to lookup correct key', + ExceptionCodes::KID_IS_INVALID + ); } return $keyOrKeyArray[$kid]; @@ -482,7 +562,8 @@ private static function handleJsonError(int $errno): void throw new DomainException( isset($messages[$errno]) ? $messages[$errno] - : 'Unknown JSON error: ' . $errno + : 'Unknown JSON error: ' . $errno, + ExceptionCodes::JSON_ERROR ); } diff --git a/src/Key.php b/src/Key.php index 00cf7f2e..56811446 100644 --- a/src/Key.php +++ b/src/Key.php @@ -28,15 +28,24 @@ public function __construct( && !$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, resource, or OpenSSLAsymmetricKey', + ExceptionCodes::KEY_MATERIAL_IS_INVALID + ); } if (empty($keyMaterial)) { - throw new InvalidArgumentException('Key material must not be empty'); + throw new InvalidArgumentException( + 'Key material must not be empty', + ExceptionCodes::KEY_MATERIAL_IS_EMPTY + ); } if (empty($algorithm)) { - throw new InvalidArgumentException('Algorithm must not be empty'); + throw new InvalidArgumentException( + 'Algorithm must not be empty', + ExceptionCodes::KEY_ALGORITHM_IS_EMPTY + ); } // TODO: Remove in PHP 8.0 in favor of class constructor property promotion diff --git a/src/SignatureInvalidException.php b/src/SignatureInvalidException.php index d35dee9f..26d0f512 100644 --- a/src/SignatureInvalidException.php +++ b/src/SignatureInvalidException.php @@ -4,4 +4,5 @@ class SignatureInvalidException extends \UnexpectedValueException { + public const SIGNATURE_VERIFICATION_FAILED = 1; } From da07b361550e1e11789955ea40a4f01abb1f9a13 Mon Sep 17 00:00:00 2001 From: nicumicle <20170987+nicumicleI@users.noreply.github.com> Date: Wed, 21 Sep 2022 07:46:39 +0200 Subject: [PATCH 02/13] Refactor Exception codes --- src/ExceptionCodes.php | 83 ++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/src/ExceptionCodes.php b/src/ExceptionCodes.php index 40891fa0..53ae6c5d 100644 --- a/src/ExceptionCodes.php +++ b/src/ExceptionCodes.php @@ -1,8 +1,5 @@ Date: Wed, 21 Sep 2022 07:59:05 +0200 Subject: [PATCH 03/13] Add newline at end of file --- src/ExceptionCodes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ExceptionCodes.php b/src/ExceptionCodes.php index 53ae6c5d..a10ed827 100644 --- a/src/ExceptionCodes.php +++ b/src/ExceptionCodes.php @@ -54,4 +54,4 @@ class ExceptionCodes public const KEY_MATERIAL_IS_INVALID = 43; public const KEY_MATERIAL_IS_EMPTY = 44; public const KEY_ALGORITHM_IS_EMPTY = 45; -} \ No newline at end of file +} From a2d3bb73de63d4745d62574e560920c64eb07c5e Mon Sep 17 00:00:00 2001 From: nicumicle <20170987+nicumicleI@users.noreply.github.com> Date: Wed, 21 Sep 2022 08:01:34 +0200 Subject: [PATCH 04/13] Add public to const in BeforeValidException --- src/BeforeValidException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BeforeValidException.php b/src/BeforeValidException.php index 70a22065..e99e6d1a 100644 --- a/src/BeforeValidException.php +++ b/src/BeforeValidException.php @@ -5,5 +5,5 @@ class BeforeValidException extends \UnexpectedValueException { public const NBF_PRIOR_TO_DATE = 1; - const IAT_PRIOR_TO_DATE = 2; + public const IAT_PRIOR_TO_DATE = 2; } From bf7d6db53af222fc77e17c66bb84c5aa245d29cd Mon Sep 17 00:00:00 2001 From: nicumicle <20170987+nicumicleI@users.noreply.github.com> Date: Wed, 21 Sep 2022 10:34:45 +0200 Subject: [PATCH 05/13] Fix exception codes --- src/ExceptionCodes.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ExceptionCodes.php b/src/ExceptionCodes.php index a10ed827..de3555e7 100644 --- a/src/ExceptionCodes.php +++ b/src/ExceptionCodes.php @@ -49,9 +49,9 @@ class ExceptionCodes public const JWK_EC_D_IS_NOT_SET = 40; public const JWT_EC_CRV_IS_EMPTY = 41; public const JWK_UNSUPPORTED_EC_CURVE = 42; - public const JWT_X_AND_Y_ARE_EMPTY = 42; + public const JWT_X_AND_Y_ARE_EMPTY = 43; - public const KEY_MATERIAL_IS_INVALID = 43; - public const KEY_MATERIAL_IS_EMPTY = 44; - public const KEY_ALGORITHM_IS_EMPTY = 45; + public const KEY_MATERIAL_IS_INVALID = 44; + public const KEY_MATERIAL_IS_EMPTY = 45; + public const KEY_ALGORITHM_IS_EMPTY = 46; } From abf93d1bab467d7446808bcdb4e1a0ecec1bb228 Mon Sep 17 00:00:00 2001 From: nicumicle <20170987+nicumicleI@users.noreply.github.com> Date: Thu, 6 Oct 2022 05:25:03 +0200 Subject: [PATCH 06/13] Move all exception codes to the ExceptionCodes class --- src/BeforeValidException.php | 2 - src/ExceptionCodes.php | 80 ++++++++++++++++--------------- src/ExpiredException.php | 1 - src/JWT.php | 8 ++-- src/SignatureInvalidException.php | 1 - 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/BeforeValidException.php b/src/BeforeValidException.php index e99e6d1a..c147852b 100644 --- a/src/BeforeValidException.php +++ b/src/BeforeValidException.php @@ -4,6 +4,4 @@ class BeforeValidException extends \UnexpectedValueException { - public const NBF_PRIOR_TO_DATE = 1; - public const IAT_PRIOR_TO_DATE = 2; } diff --git a/src/ExceptionCodes.php b/src/ExceptionCodes.php index de3555e7..001720f5 100644 --- a/src/ExceptionCodes.php +++ b/src/ExceptionCodes.php @@ -12,46 +12,50 @@ class ExceptionCodes public const EMPTY_ALGORITHM = 6; public const DECODE_ALGORITHM_NOT_SUPPORTED = 7; public const INCORRECT_KEY_FOR_ALGORITHM = 8; - public const SIGN_ALGORITHM_NOT_SUPPORTED = 9; - public const KEY_IS_NOT_STRING = 10; - public const OPENSSL_CAN_NOT_SIGN_DATA = 11; - public const SODIUM_KEY_IS_NOT_STRING = 12; - public const SODIUM_EXCEPTION = 13; - public const SIGN_GENERAL_EXCEPTION = 14; - public const VERIFY_ALGORITHM_NOT_SUPPORTED = 15; - public const VERIFY_OPEN_SSL_ERROR = 16; - public const VERIFY_SODIUM_NOT_AVAILABLE = 17; - public const VERIFY_KEY_MATERIAL_IS_NOT_STRING = 18; - public const VERIFY_SODIUM_EXCEPTION = 19; - public const VERIFY_KEY_IS_NOT_STRING = 20; - public const DECODED_JSON_IS_NULL = 21; - public const ENCODED_JSON_IS_NULL = 22; - public const INVALID_JSON = 23; - public const KID_IS_EMPTY = 24; - public const KID_IS_INVALID = 25; - public const JSON_ERROR = 26; + public const SIGNATURE_VERIFICATION_FAILED = 9; + public const NBF_PRIOR_TO_DATE = 10; + public const IAT_PRIOR_TO_DATE = 11; + public const TOKEN_EXPIRED = 12; + public const SIGN_ALGORITHM_NOT_SUPPORTED = 13; + public const KEY_IS_NOT_STRING = 14; + public const OPENSSL_CAN_NOT_SIGN_DATA = 15; + public const SODIUM_KEY_IS_NOT_STRING = 16; + public const SODIUM_EXCEPTION = 17; + public const SIGN_GENERAL_EXCEPTION = 18; + public const VERIFY_ALGORITHM_NOT_SUPPORTED = 19; + public const VERIFY_OPEN_SSL_ERROR = 20; + public const VERIFY_SODIUM_NOT_AVAILABLE = 21; + public const VERIFY_KEY_MATERIAL_IS_NOT_STRING = 22; + public const VERIFY_SODIUM_EXCEPTION = 23; + public const VERIFY_KEY_IS_NOT_STRING = 24; + public const DECODED_JSON_IS_NULL = 25; + public const ENCODED_JSON_IS_NULL = 26; + public const INVALID_JSON = 27; + public const KID_IS_EMPTY = 28; + public const KID_IS_INVALID = 29; + public const JSON_ERROR = 30; - public const KEY_ID_NOT_FOUND = 27; - public const OFFSET_SET_METHOD_NOT_IMPLEMENTED = 28; - public const OFFSET_UNSET_METHOD_NOT_IMPLEMENTED = 29; + public const KEY_ID_NOT_FOUND = 31; + public const OFFSET_SET_METHOD_NOT_IMPLEMENTED = 32; + public const OFFSET_UNSET_METHOD_NOT_IMPLEMENTED = 33; - public const JWKS_URI_IS_EMPTY = 30; + public const JWKS_URI_IS_EMPTY = 34; - public const JWK_MISSING_KEYS = 31; - public const JWT_KEYS_IS_EMPTY = 32; - public const JWT_ALGORITHM_NOT_SUPPORTED = 33; - public const JWK_IS_EMPTY = 34; - public const JWT_MISSING_KTY_PARAMETER = 35; - public const JWT_MISSING_ALG_PARAMETER = 36; - public const JWT_RSA_KEYS_NOT_SUPPORTED = 37; - public const JWT_RSA_KEYS_MISSING_N_AND_E = 38; - public const JWT_OPEN_SSL_ERROR = 39; - public const JWK_EC_D_IS_NOT_SET = 40; - public const JWT_EC_CRV_IS_EMPTY = 41; - public const JWK_UNSUPPORTED_EC_CURVE = 42; - public const JWT_X_AND_Y_ARE_EMPTY = 43; + public const JWK_MISSING_KEYS = 35; + public const JWT_KEYS_IS_EMPTY = 36; + public const JWT_ALGORITHM_NOT_SUPPORTED = 37; + public const JWK_IS_EMPTY = 38; + public const JWT_MISSING_KTY_PARAMETER = 39; + public const JWT_MISSING_ALG_PARAMETER = 40; + public const JWT_RSA_KEYS_NOT_SUPPORTED = 41; + public const JWT_RSA_KEYS_MISSING_N_AND_E = 42; + public const JWT_OPEN_SSL_ERROR = 43; + public const JWK_EC_D_IS_NOT_SET = 44; + public const JWT_EC_CRV_IS_EMPTY = 45; + public const JWK_UNSUPPORTED_EC_CURVE = 46; + public const JWT_X_AND_Y_ARE_EMPTY = 47; - public const KEY_MATERIAL_IS_INVALID = 44; - public const KEY_MATERIAL_IS_EMPTY = 45; - public const KEY_ALGORITHM_IS_EMPTY = 46; + public const KEY_MATERIAL_IS_INVALID = 48; + public const KEY_MATERIAL_IS_EMPTY = 49; + public const KEY_ALGORITHM_IS_EMPTY = 50; } diff --git a/src/ExpiredException.php b/src/ExpiredException.php index 5b5300f1..81ba52d4 100644 --- a/src/ExpiredException.php +++ b/src/ExpiredException.php @@ -4,5 +4,4 @@ class ExpiredException extends \UnexpectedValueException { - public const TOKEN_EXPIRED = 1; } diff --git a/src/JWT.php b/src/JWT.php index 8373998d..9467d73d 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -163,7 +163,7 @@ public static function decode( if (!self::verify("${headb64}.${bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) { throw new SignatureInvalidException( 'Signature verification failed', - SignatureInvalidException::SIGNATURE_VERIFICATION_FAILED + ExceptionCodes::SIGNATURE_VERIFICATION_FAILED ); } @@ -172,7 +172,7 @@ public static function decode( if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) { throw new BeforeValidException( 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf), - BeforeValidException::NBF_PRIOR_TO_DATE + ExceptionCodes::NBF_PRIOR_TO_DATE ); } @@ -182,13 +182,13 @@ public static function decode( if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) { throw new BeforeValidException( 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat), - BeforeValidException::IAT_PRIOR_TO_DATE + ExceptionCodes::IAT_PRIOR_TO_DATE ); } // Check if this token has expired. if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { - throw new ExpiredException('Expired token', ExpiredException::TOKEN_EXPIRED); + throw new ExpiredException('Expired token', ExceptionCodes::TOKEN_EXPIRED); } return $payload; diff --git a/src/SignatureInvalidException.php b/src/SignatureInvalidException.php index 26d0f512..d35dee9f 100644 --- a/src/SignatureInvalidException.php +++ b/src/SignatureInvalidException.php @@ -4,5 +4,4 @@ class SignatureInvalidException extends \UnexpectedValueException { - public const SIGNATURE_VERIFICATION_FAILED = 1; } From 1b9d33a597b5f2b23b31a91f8390d7ec12e8982c Mon Sep 17 00:00:00 2001 From: nicumicle <20170987+nicumicleI@users.noreply.github.com> Date: Wed, 21 Sep 2022 07:41:07 +0200 Subject: [PATCH 07/13] Add error codes to all exceptions --- src/BeforeValidException.php | 2 + src/CachedKeySet.php | 20 ++++- src/ExceptionCodes.php | 60 +++++++++++++ src/ExpiredException.php | 1 + src/JWK.php | 64 +++++++++++--- src/JWT.php | 141 +++++++++++++++++++++++------- src/Key.php | 15 +++- src/SignatureInvalidException.php | 1 + 8 files changed, 254 insertions(+), 50 deletions(-) create mode 100644 src/ExceptionCodes.php diff --git a/src/BeforeValidException.php b/src/BeforeValidException.php index c147852b..70a22065 100644 --- a/src/BeforeValidException.php +++ b/src/BeforeValidException.php @@ -4,4 +4,6 @@ class BeforeValidException extends \UnexpectedValueException { + public const NBF_PRIOR_TO_DATE = 1; + const IAT_PRIOR_TO_DATE = 2; } diff --git a/src/CachedKeySet.php b/src/CachedKeySet.php index 87f470d7..e3703101 100644 --- a/src/CachedKeySet.php +++ b/src/CachedKeySet.php @@ -99,7 +99,10 @@ public function __construct( public function offsetGet($keyId): Key { if (!$this->keyIdExists($keyId)) { - throw new OutOfBoundsException('Key ID not found'); + throw new OutOfBoundsException( + 'Key ID not found', + ExceptionCodes::KEY_ID_NOT_FOUND + ); } return $this->keySet[$keyId]; } @@ -119,7 +122,10 @@ public function offsetExists($keyId): bool */ public function offsetSet($offset, $value): void { - throw new LogicException('Method not implemented'); + throw new LogicException( + 'Method not implemented', + ExceptionCodes::OFFSET_SET_METHOD_NOT_IMPLEMENTED + ); } /** @@ -127,7 +133,10 @@ public function offsetSet($offset, $value): void */ public function offsetUnset($offset): void { - throw new LogicException('Method not implemented'); + throw new LogicException( + 'Method not implemented', + ExceptionCodes::OFFSET_UNSET_METHOD_NOT_IMPLEMENTED + ); } private function keyIdExists(string $keyId): bool @@ -198,7 +207,10 @@ private function getCacheItem(): CacheItemInterface private function setCacheKeys(): void { if (empty($this->jwksUri)) { - throw new RuntimeException('JWKS URI is empty'); + throw new RuntimeException( + 'JWKS URI is empty', + ExceptionCodes::JWKS_URI_IS_EMPTY + ); } // ensure we do not have illegal characters diff --git a/src/ExceptionCodes.php b/src/ExceptionCodes.php new file mode 100644 index 00000000..40891fa0 --- /dev/null +++ b/src/ExceptionCodes.php @@ -0,0 +1,60 @@ + $v) { @@ -65,7 +71,11 @@ public static function parseKeySet(array $jwks, string $defaultAlg = null): arra } if (0 === \count($keys)) { - throw new UnexpectedValueException('No supported algorithms found in JWK Set'); + throw new UnexpectedValueException( + 'No supported algorithms found in JWK Set', + ExceptionCodes::JWT_ALGORITHM_NOT_SUPPORTED + + ); } return $keys; @@ -89,11 +99,17 @@ public static function parseKeySet(array $jwks, string $defaultAlg = null): arra public static function parseKey(array $jwk, string $defaultAlg = null): ?Key { if (empty($jwk)) { - throw new InvalidArgumentException('JWK must not be empty'); + throw new InvalidArgumentException( + 'JWK must not be empty', + ExceptionCodes::JWK_IS_EMPTY + ); } if (!isset($jwk['kty'])) { - throw new UnexpectedValueException('JWK must contain a "kty" parameter'); + throw new UnexpectedValueException( + 'JWK must contain a "kty" parameter', + ExceptionCodes::JWT_MISSING_KTY_PARAMETER + ); } if (!isset($jwk['alg'])) { @@ -102,7 +118,10 @@ public static function parseKey(array $jwk, string $defaultAlg = null): ?Key // for parsing in this library. Use the $defaultAlg parameter when parsing the // key set in order to prevent this error. // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 - throw new UnexpectedValueException('JWK must contain an "alg" parameter'); + throw new UnexpectedValueException( + 'JWK must contain an "alg" parameter', + ExceptionCodes::JWT_MISSING_ALG_PARAMETER + ); } $jwk['alg'] = $defaultAlg; } @@ -110,36 +129,55 @@ public static function parseKey(array $jwk, string $defaultAlg = null): ?Key switch ($jwk['kty']) { case 'RSA': if (!empty($jwk['d'])) { - throw new UnexpectedValueException('RSA private keys are not supported'); + throw new UnexpectedValueException( + 'RSA private keys are not supported', + ExceptionCodes::JWT_RSA_KEYS_NOT_SUPPORTED + ); } if (!isset($jwk['n']) || !isset($jwk['e'])) { - throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"'); + throw new UnexpectedValueException( + 'RSA keys must contain values for both "n" and "e"', + ExceptionCodes::JWT_RSA_KEYS_MISSING_N_AND_E + ); } $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']); $publicKey = \openssl_pkey_get_public($pem); if (false === $publicKey) { throw new DomainException( - 'OpenSSL error: ' . \openssl_error_string() + 'OpenSSL error: ' . \openssl_error_string(), + ExceptionCodes::JWT_OPEN_SSL_ERROR ); } return new Key($publicKey, $jwk['alg']); case 'EC': if (isset($jwk['d'])) { // The key is actually a private key - throw new UnexpectedValueException('Key data must be for a public key'); + throw new UnexpectedValueException( + 'Key data must be for a public key', + ExceptionCodes::JWK_EC_D_IS_NOT_SET + ); } if (empty($jwk['crv'])) { - throw new UnexpectedValueException('crv not set'); + throw new UnexpectedValueException( + 'crv not set', + ExceptionCodes::JWT_EC_CRV_IS_EMPTY + ); } if (!isset(self::EC_CURVES[$jwk['crv']])) { - throw new DomainException('Unrecognised or unsupported EC curve'); + throw new DomainException( + 'Unrecognised or unsupported EC curve', + ExceptionCodes::JWK_UNSUPPORTED_EC_CURVE + ); } if (empty($jwk['x']) || empty($jwk['y'])) { - throw new UnexpectedValueException('x and y not set'); + throw new UnexpectedValueException( + 'x and y not set', + ExceptionCodes::JWT_X_AND_Y_ARE_EMPTY + ); } $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']); diff --git a/src/JWT.php b/src/JWT.php index 977d7fbd..77db4af2 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -95,34 +95,55 @@ public static function decode( $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; if (empty($keyOrKeyArray)) { - throw new InvalidArgumentException('Key may not be empty'); + throw new InvalidArgumentException( + 'Key may not be empty', + ExceptionCodes::KEY_NOT_EMPTY + ); } $tks = \explode('.', $jwt); if (\count($tks) !== 3) { - throw new UnexpectedValueException('Wrong number of segments'); + throw new UnexpectedValueException( + 'Wrong number of segments', + ExceptionCodes::WRONG_NUMBER_OF_SEGMENTS + ); } list($headb64, $bodyb64, $cryptob64) = $tks; $headerRaw = static::urlsafeB64Decode($headb64); if (null === ($header = static::jsonDecode($headerRaw))) { - throw new UnexpectedValueException('Invalid header encoding'); + throw new UnexpectedValueException( + 'Invalid header encoding', + ExceptionCodes::INVALID_HEADER_ENCODING + ); } $payloadRaw = static::urlsafeB64Decode($bodyb64); if (null === ($payload = static::jsonDecode($payloadRaw))) { - throw new UnexpectedValueException('Invalid claims encoding'); + throw new UnexpectedValueException( + 'Invalid claims encoding', + ExceptionCodes::INVALID_CLAIMS_ENCODING + ); } if (\is_array($payload)) { // prevent PHP Fatal Error in edge-cases when payload is empty array $payload = (object) $payload; } if (!$payload instanceof stdClass) { - throw new UnexpectedValueException('Payload must be a JSON object'); + throw new UnexpectedValueException( + 'Payload must be a JSON object', + ExceptionCodes::PAYLOAD_NOT_JSON + ); } $sig = static::urlsafeB64Decode($cryptob64); if (empty($header->alg)) { - throw new UnexpectedValueException('Empty algorithm'); + throw new UnexpectedValueException( + 'Empty algorithm', + ExceptionCodes::EMPTY_ALGORITHM + ); } if (empty(static::$supported_algs[$header->alg])) { - throw new UnexpectedValueException('Algorithm not supported'); + throw new UnexpectedValueException( + 'Algorithm not supported', + ExceptionCodes::DECODE_ALGORITHM_NOT_SUPPORTED + ); } $key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null); @@ -130,21 +151,28 @@ public static function decode( // Check the algorithm if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) { // See issue #351 - throw new UnexpectedValueException('Incorrect key for this algorithm'); + throw new UnexpectedValueException( + 'Incorrect key for this algorithm', + ExceptionCodes::INCORRECT_KEY_FOR_ALGORITHM + ); } if ($header->alg === 'ES256' || $header->alg === 'ES384') { // OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures $sig = self::signatureToDER($sig); } if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) { - throw new SignatureInvalidException('Signature verification failed'); + throw new SignatureInvalidException( + 'Signature verification failed', + SignatureInvalidException::SIGNATURE_VERIFICATION_FAILED + ); } // 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) + 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf), + BeforeValidException::NBF_PRIOR_TO_DATE ); } @@ -153,13 +181,14 @@ public static function decode( // 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) + 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat), + BeforeValidException::IAT_PRIOR_TO_DATE ); } // Check if this token has expired. if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { - throw new ExpiredException('Expired token'); + throw new ExpiredException('Expired token', ExpiredException::TOKEN_EXPIRED); } return $payload; @@ -223,20 +252,29 @@ public static function sign( string $alg ): string { if (empty(static::$supported_algs[$alg])) { - throw new DomainException('Algorithm not supported'); + throw new DomainException( + 'Algorithm not supported', + ExceptionCodes::SIGN_ALGORITHM_NOT_SUPPORTED + ); } list($function, $algorithm) = static::$supported_algs[$alg]; switch ($function) { case 'hash_hmac': if (!\is_string($key)) { - throw new InvalidArgumentException('key must be a string when using hmac'); + throw new InvalidArgumentException( + 'key must be a string when using hmac', + ExceptionCodes::KEY_IS_NOT_STRING + ); } return \hash_hmac($algorithm, $msg, $key, true); case 'openssl': $signature = ''; $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line if (!$success) { - throw new DomainException('OpenSSL unable to sign data'); + throw new DomainException( + 'OpenSSL unable to sign data', + ExceptionCodes::OPENSSL_CAN_NOT_SIGN_DATA + ); } if ($alg === 'ES256') { $signature = self::signatureFromDER($signature, 256); @@ -249,7 +287,10 @@ public static function sign( throw new DomainException('libsodium is not available'); } if (!\is_string($key)) { - throw new InvalidArgumentException('key must be a string when using EdDSA'); + throw new InvalidArgumentException( + 'key must be a string when using EdDSA', + ExceptionCodes::SODIUM_KEY_IS_NOT_STRING + ); } try { // The last non-empty line is used as the key. @@ -257,11 +298,18 @@ public static function sign( $key = base64_decode((string) end($lines)); return sodium_crypto_sign_detached($msg, $key); } catch (Exception $e) { - throw new DomainException($e->getMessage(), 0, $e); + throw new DomainException( + $e->getMessage(), + ExceptionCodes::SODIUM_EXCEPTION, + $e + ); } } - throw new DomainException('Algorithm not supported'); + throw new DomainException( + 'Algorithm not supported', + ExceptionCodes::SIGN_GENERAL_EXCEPTION + ); } /** @@ -284,7 +332,10 @@ private static function verify( string $alg ): bool { if (empty(static::$supported_algs[$alg])) { - throw new DomainException('Algorithm not supported'); + throw new DomainException( + 'Algorithm not supported', + ExceptionCodes::VERIFY_ALGORITHM_NOT_SUPPORTED + ); } list($function, $algorithm) = static::$supported_algs[$alg]; @@ -299,14 +350,21 @@ private static function verify( } // returns 1 on success, 0 on failure, -1 on error. throw new DomainException( - 'OpenSSL error: ' . \openssl_error_string() + 'OpenSSL error: ' . \openssl_error_string(), + ExceptionCodes::VERIFY_OPEN_SSL_ERROR ); case 'sodium_crypto': if (!\function_exists('sodium_crypto_sign_verify_detached')) { - throw new DomainException('libsodium is not available'); + throw new DomainException( + 'libsodium is not available', + ExceptionCodes::VERIFY_SODIUM_NOT_AVAILABLE + ); } if (!\is_string($keyMaterial)) { - throw new InvalidArgumentException('key must be a string when using EdDSA'); + throw new InvalidArgumentException( + 'key must be a string when using EdDSA', + ExceptionCodes::VERIFY_KEY_MATERIAL_IS_NOT_STRING + ); } try { // The last non-empty line is used as the key. @@ -314,12 +372,19 @@ private static function verify( $key = base64_decode((string) end($lines)); return sodium_crypto_sign_verify_detached($signature, $msg, $key); } catch (Exception $e) { - throw new DomainException($e->getMessage(), 0, $e); + throw new DomainException( + $e->getMessage(), + ExceptionCodes::VERIFY_SODIUM_EXCEPTION, + $e + ); } case 'hash_hmac': default: if (!\is_string($keyMaterial)) { - throw new InvalidArgumentException('key must be a string when using hmac'); + throw new InvalidArgumentException( + 'key must be a string when using hmac', + ExceptionCodes::VERIFY_KEY_IS_NOT_STRING + ); } $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true); return self::constantTimeEquals($hash, $signature); @@ -342,7 +407,10 @@ public static function jsonDecode(string $input) if ($errno = \json_last_error()) { self::handleJsonError($errno); } elseif ($obj === null && $input !== 'null') { - throw new DomainException('Null result with non-null input'); + throw new DomainException( + 'Null result with non-null input', + ExceptionCodes::DECODED_JSON_IS_NULL + ); } return $obj; } @@ -367,10 +435,16 @@ public static function jsonEncode(array $input): string if ($errno = \json_last_error()) { self::handleJsonError($errno); } elseif ($json === 'null' && $input !== null) { - throw new DomainException('Null result with non-null input'); + throw new DomainException( + 'Null result with non-null input', + ExceptionCodes::ENCODED_JSON_IS_NULL + ); } if ($json === false) { - throw new DomainException('Provided object could not be encoded to valid JSON'); + throw new DomainException( + 'Provided object could not be encoded to valid JSON', + ExceptionCodes::INVALID_JSON + ); } return $json; } @@ -431,10 +505,16 @@ private static function getKey( } if (empty($kid)) { - throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); + throw new UnexpectedValueException( + '"kid" empty, unable to lookup correct key', + ExceptionCodes::KID_IS_EMPTY + ); } if (!isset($keyOrKeyArray[$kid])) { - throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); + throw new UnexpectedValueException( + '"kid" invalid, unable to lookup correct key', + ExceptionCodes::KID_IS_INVALID + ); } return $keyOrKeyArray[$kid]; @@ -482,7 +562,8 @@ private static function handleJsonError(int $errno): void throw new DomainException( isset($messages[$errno]) ? $messages[$errno] - : 'Unknown JSON error: ' . $errno + : 'Unknown JSON error: ' . $errno, + ExceptionCodes::JSON_ERROR ); } diff --git a/src/Key.php b/src/Key.php index 00cf7f2e..56811446 100644 --- a/src/Key.php +++ b/src/Key.php @@ -28,15 +28,24 @@ public function __construct( && !$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, resource, or OpenSSLAsymmetricKey', + ExceptionCodes::KEY_MATERIAL_IS_INVALID + ); } if (empty($keyMaterial)) { - throw new InvalidArgumentException('Key material must not be empty'); + throw new InvalidArgumentException( + 'Key material must not be empty', + ExceptionCodes::KEY_MATERIAL_IS_EMPTY + ); } if (empty($algorithm)) { - throw new InvalidArgumentException('Algorithm must not be empty'); + throw new InvalidArgumentException( + 'Algorithm must not be empty', + ExceptionCodes::KEY_ALGORITHM_IS_EMPTY + ); } // TODO: Remove in PHP 8.0 in favor of class constructor property promotion diff --git a/src/SignatureInvalidException.php b/src/SignatureInvalidException.php index d35dee9f..26d0f512 100644 --- a/src/SignatureInvalidException.php +++ b/src/SignatureInvalidException.php @@ -4,4 +4,5 @@ class SignatureInvalidException extends \UnexpectedValueException { + public const SIGNATURE_VERIFICATION_FAILED = 1; } From 6451f8557234b451beedba37075008febf89e153 Mon Sep 17 00:00:00 2001 From: nicumicle <20170987+nicumicleI@users.noreply.github.com> Date: Wed, 21 Sep 2022 07:46:39 +0200 Subject: [PATCH 08/13] Refactor Exception codes --- src/ExceptionCodes.php | 83 ++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/src/ExceptionCodes.php b/src/ExceptionCodes.php index 40891fa0..53ae6c5d 100644 --- a/src/ExceptionCodes.php +++ b/src/ExceptionCodes.php @@ -1,8 +1,5 @@ Date: Wed, 21 Sep 2022 07:59:05 +0200 Subject: [PATCH 09/13] Add newline at end of file --- src/ExceptionCodes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ExceptionCodes.php b/src/ExceptionCodes.php index 53ae6c5d..a10ed827 100644 --- a/src/ExceptionCodes.php +++ b/src/ExceptionCodes.php @@ -54,4 +54,4 @@ class ExceptionCodes public const KEY_MATERIAL_IS_INVALID = 43; public const KEY_MATERIAL_IS_EMPTY = 44; public const KEY_ALGORITHM_IS_EMPTY = 45; -} \ No newline at end of file +} From 2f467b37fbb82f5531bc902cbaf481425e84439b Mon Sep 17 00:00:00 2001 From: nicumicle <20170987+nicumicleI@users.noreply.github.com> Date: Wed, 21 Sep 2022 08:01:34 +0200 Subject: [PATCH 10/13] Add public to const in BeforeValidException --- src/BeforeValidException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BeforeValidException.php b/src/BeforeValidException.php index 70a22065..e99e6d1a 100644 --- a/src/BeforeValidException.php +++ b/src/BeforeValidException.php @@ -5,5 +5,5 @@ class BeforeValidException extends \UnexpectedValueException { public const NBF_PRIOR_TO_DATE = 1; - const IAT_PRIOR_TO_DATE = 2; + public const IAT_PRIOR_TO_DATE = 2; } From 38b5fe6d5de90052287dcc963e1c561ea0c592bf Mon Sep 17 00:00:00 2001 From: nicumicle <20170987+nicumicleI@users.noreply.github.com> Date: Wed, 21 Sep 2022 10:34:45 +0200 Subject: [PATCH 11/13] Fix exception codes --- src/ExceptionCodes.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ExceptionCodes.php b/src/ExceptionCodes.php index a10ed827..de3555e7 100644 --- a/src/ExceptionCodes.php +++ b/src/ExceptionCodes.php @@ -49,9 +49,9 @@ class ExceptionCodes public const JWK_EC_D_IS_NOT_SET = 40; public const JWT_EC_CRV_IS_EMPTY = 41; public const JWK_UNSUPPORTED_EC_CURVE = 42; - public const JWT_X_AND_Y_ARE_EMPTY = 42; + public const JWT_X_AND_Y_ARE_EMPTY = 43; - public const KEY_MATERIAL_IS_INVALID = 43; - public const KEY_MATERIAL_IS_EMPTY = 44; - public const KEY_ALGORITHM_IS_EMPTY = 45; + public const KEY_MATERIAL_IS_INVALID = 44; + public const KEY_MATERIAL_IS_EMPTY = 45; + public const KEY_ALGORITHM_IS_EMPTY = 46; } From 11271dc3d34b08d53d94c2f5851baff94f5c5113 Mon Sep 17 00:00:00 2001 From: nicumicle <20170987+nicumicleI@users.noreply.github.com> Date: Thu, 6 Oct 2022 05:25:03 +0200 Subject: [PATCH 12/13] Move all exception codes to the ExceptionCodes class --- src/BeforeValidException.php | 2 - src/ExceptionCodes.php | 80 ++++++++++++++++--------------- src/ExpiredException.php | 1 - src/JWT.php | 8 ++-- src/SignatureInvalidException.php | 1 - 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/BeforeValidException.php b/src/BeforeValidException.php index e99e6d1a..c147852b 100644 --- a/src/BeforeValidException.php +++ b/src/BeforeValidException.php @@ -4,6 +4,4 @@ class BeforeValidException extends \UnexpectedValueException { - public const NBF_PRIOR_TO_DATE = 1; - public const IAT_PRIOR_TO_DATE = 2; } diff --git a/src/ExceptionCodes.php b/src/ExceptionCodes.php index de3555e7..001720f5 100644 --- a/src/ExceptionCodes.php +++ b/src/ExceptionCodes.php @@ -12,46 +12,50 @@ class ExceptionCodes public const EMPTY_ALGORITHM = 6; public const DECODE_ALGORITHM_NOT_SUPPORTED = 7; public const INCORRECT_KEY_FOR_ALGORITHM = 8; - public const SIGN_ALGORITHM_NOT_SUPPORTED = 9; - public const KEY_IS_NOT_STRING = 10; - public const OPENSSL_CAN_NOT_SIGN_DATA = 11; - public const SODIUM_KEY_IS_NOT_STRING = 12; - public const SODIUM_EXCEPTION = 13; - public const SIGN_GENERAL_EXCEPTION = 14; - public const VERIFY_ALGORITHM_NOT_SUPPORTED = 15; - public const VERIFY_OPEN_SSL_ERROR = 16; - public const VERIFY_SODIUM_NOT_AVAILABLE = 17; - public const VERIFY_KEY_MATERIAL_IS_NOT_STRING = 18; - public const VERIFY_SODIUM_EXCEPTION = 19; - public const VERIFY_KEY_IS_NOT_STRING = 20; - public const DECODED_JSON_IS_NULL = 21; - public const ENCODED_JSON_IS_NULL = 22; - public const INVALID_JSON = 23; - public const KID_IS_EMPTY = 24; - public const KID_IS_INVALID = 25; - public const JSON_ERROR = 26; + public const SIGNATURE_VERIFICATION_FAILED = 9; + public const NBF_PRIOR_TO_DATE = 10; + public const IAT_PRIOR_TO_DATE = 11; + public const TOKEN_EXPIRED = 12; + public const SIGN_ALGORITHM_NOT_SUPPORTED = 13; + public const KEY_IS_NOT_STRING = 14; + public const OPENSSL_CAN_NOT_SIGN_DATA = 15; + public const SODIUM_KEY_IS_NOT_STRING = 16; + public const SODIUM_EXCEPTION = 17; + public const SIGN_GENERAL_EXCEPTION = 18; + public const VERIFY_ALGORITHM_NOT_SUPPORTED = 19; + public const VERIFY_OPEN_SSL_ERROR = 20; + public const VERIFY_SODIUM_NOT_AVAILABLE = 21; + public const VERIFY_KEY_MATERIAL_IS_NOT_STRING = 22; + public const VERIFY_SODIUM_EXCEPTION = 23; + public const VERIFY_KEY_IS_NOT_STRING = 24; + public const DECODED_JSON_IS_NULL = 25; + public const ENCODED_JSON_IS_NULL = 26; + public const INVALID_JSON = 27; + public const KID_IS_EMPTY = 28; + public const KID_IS_INVALID = 29; + public const JSON_ERROR = 30; - public const KEY_ID_NOT_FOUND = 27; - public const OFFSET_SET_METHOD_NOT_IMPLEMENTED = 28; - public const OFFSET_UNSET_METHOD_NOT_IMPLEMENTED = 29; + public const KEY_ID_NOT_FOUND = 31; + public const OFFSET_SET_METHOD_NOT_IMPLEMENTED = 32; + public const OFFSET_UNSET_METHOD_NOT_IMPLEMENTED = 33; - public const JWKS_URI_IS_EMPTY = 30; + public const JWKS_URI_IS_EMPTY = 34; - public const JWK_MISSING_KEYS = 31; - public const JWT_KEYS_IS_EMPTY = 32; - public const JWT_ALGORITHM_NOT_SUPPORTED = 33; - public const JWK_IS_EMPTY = 34; - public const JWT_MISSING_KTY_PARAMETER = 35; - public const JWT_MISSING_ALG_PARAMETER = 36; - public const JWT_RSA_KEYS_NOT_SUPPORTED = 37; - public const JWT_RSA_KEYS_MISSING_N_AND_E = 38; - public const JWT_OPEN_SSL_ERROR = 39; - public const JWK_EC_D_IS_NOT_SET = 40; - public const JWT_EC_CRV_IS_EMPTY = 41; - public const JWK_UNSUPPORTED_EC_CURVE = 42; - public const JWT_X_AND_Y_ARE_EMPTY = 43; + public const JWK_MISSING_KEYS = 35; + public const JWT_KEYS_IS_EMPTY = 36; + public const JWT_ALGORITHM_NOT_SUPPORTED = 37; + public const JWK_IS_EMPTY = 38; + public const JWT_MISSING_KTY_PARAMETER = 39; + public const JWT_MISSING_ALG_PARAMETER = 40; + public const JWT_RSA_KEYS_NOT_SUPPORTED = 41; + public const JWT_RSA_KEYS_MISSING_N_AND_E = 42; + public const JWT_OPEN_SSL_ERROR = 43; + public const JWK_EC_D_IS_NOT_SET = 44; + public const JWT_EC_CRV_IS_EMPTY = 45; + public const JWK_UNSUPPORTED_EC_CURVE = 46; + public const JWT_X_AND_Y_ARE_EMPTY = 47; - public const KEY_MATERIAL_IS_INVALID = 44; - public const KEY_MATERIAL_IS_EMPTY = 45; - public const KEY_ALGORITHM_IS_EMPTY = 46; + public const KEY_MATERIAL_IS_INVALID = 48; + public const KEY_MATERIAL_IS_EMPTY = 49; + public const KEY_ALGORITHM_IS_EMPTY = 50; } diff --git a/src/ExpiredException.php b/src/ExpiredException.php index 5b5300f1..81ba52d4 100644 --- a/src/ExpiredException.php +++ b/src/ExpiredException.php @@ -4,5 +4,4 @@ class ExpiredException extends \UnexpectedValueException { - public const TOKEN_EXPIRED = 1; } diff --git a/src/JWT.php b/src/JWT.php index 77db4af2..bb6fd2e4 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -163,7 +163,7 @@ public static function decode( if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) { throw new SignatureInvalidException( 'Signature verification failed', - SignatureInvalidException::SIGNATURE_VERIFICATION_FAILED + ExceptionCodes::SIGNATURE_VERIFICATION_FAILED ); } @@ -172,7 +172,7 @@ public static function decode( if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) { throw new BeforeValidException( 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf), - BeforeValidException::NBF_PRIOR_TO_DATE + ExceptionCodes::NBF_PRIOR_TO_DATE ); } @@ -182,13 +182,13 @@ public static function decode( if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) { throw new BeforeValidException( 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat), - BeforeValidException::IAT_PRIOR_TO_DATE + ExceptionCodes::IAT_PRIOR_TO_DATE ); } // Check if this token has expired. if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { - throw new ExpiredException('Expired token', ExpiredException::TOKEN_EXPIRED); + throw new ExpiredException('Expired token', ExceptionCodes::TOKEN_EXPIRED); } return $payload; diff --git a/src/SignatureInvalidException.php b/src/SignatureInvalidException.php index 26d0f512..d35dee9f 100644 --- a/src/SignatureInvalidException.php +++ b/src/SignatureInvalidException.php @@ -4,5 +4,4 @@ class SignatureInvalidException extends \UnexpectedValueException { - public const SIGNATURE_VERIFICATION_FAILED = 1; } From ff1f0540621749899b257d78fa0d9811f013bafa Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Mon, 22 May 2023 12:48:13 -0700 Subject: [PATCH 13/13] Update src/JWT.php --- src/JWT.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JWT.php b/src/JWT.php index cff8feb3..511962ec 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -448,7 +448,7 @@ public static function jsonEncode(array $input): string } if ($errno = \json_last_error()) { self::handleJsonError($errno); - } elseif ($json === 'null' && $input !== null) { + } elseif ($json === 'null') { throw new DomainException( 'Null result with non-null input', ExceptionCodes::ENCODED_JSON_IS_NULL