diff --git a/src/JWT.php b/src/JWT.php index e9d75639..2a3f6aaa 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -109,14 +109,22 @@ public static function decode( throw new UnexpectedValueException('Wrong number of segments'); } list($headb64, $bodyb64, $cryptob64) = $tks; - $headerRaw = static::urlsafeB64Decode($headb64); + try { + $headerRaw = static::urlsafeB64Decode($headb64); + } catch (InvalidArgumentException $e) { + throw new UnexpectedValueException('Unable to decode header'); + } if (null === ($header = static::jsonDecode($headerRaw))) { throw new UnexpectedValueException('Invalid header encoding'); } if ($headers !== null) { $headers = $header; } - $payloadRaw = static::urlsafeB64Decode($bodyb64); + try { + $payloadRaw = static::urlsafeB64Decode($bodyb64); + } catch (InvalidArgumentException $e) { + throw new UnexpectedValueException('Unable to decode payload'); + } if (null === ($payload = static::jsonDecode($payloadRaw))) { throw new UnexpectedValueException('Invalid claims encoding'); } @@ -127,7 +135,11 @@ public static function decode( if (!$payload instanceof stdClass) { throw new UnexpectedValueException('Payload must be a JSON object'); } - $sig = static::urlsafeB64Decode($cryptob64); + try { + $sig = static::urlsafeB64Decode($cryptob64); + } catch (InvalidArgumentException $e) { + throw new UnexpectedValueException('Unable to decode signature'); + } if (empty($header->alg)) { throw new UnexpectedValueException('Empty algorithm'); } @@ -411,11 +423,18 @@ public static function jsonEncode(array $input): string * * @return string A decoded string * - * @throws InvalidArgumentException invalid base64 characters + * @throws InvalidArgumentException invalid base64URL characters */ public static function urlsafeB64Decode(string $input): string { - return \base64_decode(self::convertBase64UrlToBase64($input)); + if (strpbrk($input, '+/=') !== false) { + throw new InvalidArgumentException('Input is not valid Base64URL'); + } + $result = \base64_decode(self::convertBase64UrlToBase64($input), true); + if ($result === false) { + throw new InvalidArgumentException('Input is not valid Base64URL'); + } + return $result; } /** diff --git a/tests/JWTTest.php b/tests/JWTTest.php index d09d43e3..ac1e1ff5 100644 --- a/tests/JWTTest.php +++ b/tests/JWTTest.php @@ -38,6 +38,25 @@ public function testMalformedJsonThrowsException() JWT::jsonDecode('this is not valid JSON string'); } + public function testBase64UrlDecode() + { + $decoded = JWT::urlsafeB64Decode('VGVzdCB3aXRoIGEgbWludXM-'); + $expected = 'Test with a minus>'; + $this->assertSame($expected, $decoded); + } + + public function testMalformedBase64Url() + { + $this->expectException(InvalidArgumentException::class); + JWT::urlsafeB64Decode('VGVzdCB3aXR&oIGEgbWludXM-'); + } + + public function testMalformedBase64UrlButValidBase64() + { + $this->expectException(InvalidArgumentException::class); + JWT::urlsafeB64Decode('VGVzdCB3aXRoIGEgc2xhc2g/'); + } + public function testExpiredToken() { $this->expectException(ExpiredException::class);