diff --git a/infection.json.dist b/infection.json.dist index 0f6ca701..318f231c 100644 --- a/infection.json.dist +++ b/infection.json.dist @@ -32,6 +32,11 @@ "Lcobucci\\JWT\\Signer\\Ecdsa\\MultibyteStringConverter::preparePositiveInteger" ] }, + "LogicalNot": { + "ignoreSourceCodeByRegex": [ + "if \\(!function_exists\\('sodium_\\w+'\\)\\) \\{" + ] + }, "MBString": { "ignore": [ "Lcobucci\\JWT\\Signer\\Ecdsa\\MultibyteStringConverter" diff --git a/src/Encoding/CannotDecodeContent.php b/src/Encoding/CannotDecodeContent.php index 33607b11..8363061a 100644 --- a/src/Encoding/CannotDecodeContent.php +++ b/src/Encoding/CannotDecodeContent.php @@ -6,7 +6,6 @@ use JsonException; use Lcobucci\JWT\Exception; use RuntimeException; -use SodiumException; final class CannotDecodeContent extends RuntimeException implements Exception { @@ -15,8 +14,8 @@ public static function jsonIssues(JsonException $previous): self return new self('Error while decoding from JSON', 0, $previous); } - public static function invalidBase64String(SodiumException $sodiumException): self + public static function invalidBase64String(): self { - return new self('Error while decoding from Base64Url, invalid base64 characters detected', 0, $sodiumException); + return new self('Error while decoding from Base64Url, invalid base64 characters detected'); } } diff --git a/src/Encoding/JoseEncoder.php b/src/Encoding/JoseEncoder.php index 4d8fc241..597d15f9 100644 --- a/src/Encoding/JoseEncoder.php +++ b/src/Encoding/JoseEncoder.php @@ -6,17 +6,14 @@ use JsonException; use Lcobucci\JWT\Decoder; use Lcobucci\JWT\Encoder; -use SodiumException; +use Lcobucci\JWT\SodiumBase64Polyfill; use function json_decode; use function json_encode; -use function sodium_base642bin; -use function sodium_bin2base64; use const JSON_THROW_ON_ERROR; use const JSON_UNESCAPED_SLASHES; use const JSON_UNESCAPED_UNICODE; -use const SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING; /** * A utilitarian class that encodes and decodes data according with JOSE specifications @@ -47,15 +44,17 @@ public function jsonDecode(string $json) public function base64UrlEncode(string $data): string { - return sodium_bin2base64($data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING); + return SodiumBase64Polyfill::bin2base64( + $data, + SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING + ); } public function base64UrlDecode(string $data): string { - try { - return sodium_base642bin($data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, ''); - } catch (SodiumException $sodiumException) { - throw CannotDecodeContent::invalidBase64String($sodiumException); - } + return SodiumBase64Polyfill::base642bin( + $data, + SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING + ); } } diff --git a/src/Signer/Key/InMemory.php b/src/Signer/Key/InMemory.php index 2b62edae..1a4df0f5 100644 --- a/src/Signer/Key/InMemory.php +++ b/src/Signer/Key/InMemory.php @@ -3,17 +3,13 @@ namespace Lcobucci\JWT\Signer\Key; -use Lcobucci\JWT\Encoding\CannotDecodeContent; use Lcobucci\JWT\Signer\Key; -use SodiumException; +use Lcobucci\JWT\SodiumBase64Polyfill; use SplFileObject; use Throwable; use function assert; use function is_string; -use function sodium_base642bin; - -use const SODIUM_BASE64_VARIANT_ORIGINAL; final class InMemory implements Key { @@ -38,11 +34,10 @@ public static function plainText(string $contents, string $passphrase = ''): sel public static function base64Encoded(string $contents, string $passphrase = ''): self { - try { - $decoded = sodium_base642bin($contents, SODIUM_BASE64_VARIANT_ORIGINAL, ''); - } catch (SodiumException $sodiumException) { - throw CannotDecodeContent::invalidBase64String($sodiumException); - } + $decoded = SodiumBase64Polyfill::base642bin( + $contents, + SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_ORIGINAL + ); return new self($decoded, $passphrase); } diff --git a/src/SodiumBase64Polyfill.php b/src/SodiumBase64Polyfill.php new file mode 100644 index 00000000..6a9c7ce5 --- /dev/null +++ b/src/SodiumBase64Polyfill.php @@ -0,0 +1,88 @@ +testString = sodium_base642bin('I+o2tVq8ynY=', SODIUM_BASE64_VARIANT_ORIGINAL, ''); + } + + /** + * @test + * + * @coversNothing + */ + public function constantsMatchExtensionOnes(): void + { + self::assertSame( + SODIUM_BASE64_VARIANT_ORIGINAL, + SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_ORIGINAL + ); + self::assertSame( + SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING, + SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING + ); + self::assertSame( + SODIUM_BASE64_VARIANT_URLSAFE, + SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE + ); + self::assertSame( + SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, + SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING + ); + } + + /** + * @test + * @dataProvider provideVariants + * + * @covers ::bin2base64 + * @covers ::bin2base64Fallback + */ + public function bin2base64(int $variant): void + { + $expected = sodium_bin2base64($this->testString, $variant); + + self::assertSame( + $expected, + SodiumBase64Polyfill::bin2base64($this->testString, $variant) + ); + + self::assertSame( + $expected, + SodiumBase64Polyfill::bin2base64Fallback($this->testString, $variant) + ); + } + + /** + * @test + * @dataProvider provideVariants + * + * @covers ::base642bin + * @covers ::base642binFallback + */ + public function base642binFallback(int $variant): void + { + self::assertSame( + $this->testString, + SodiumBase64Polyfill::base642bin( + sodium_bin2base64($this->testString, $variant), + $variant + ) + ); + + self::assertSame( + $this->testString, + SodiumBase64Polyfill::base642binFallback( + sodium_bin2base64($this->testString, $variant), + $variant + ) + ); + } + + /** @return int[][] */ + public function provideVariants(): array + { + return [ + [SODIUM_BASE64_VARIANT_ORIGINAL], + [SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING], + [SODIUM_BASE64_VARIANT_URLSAFE], + [SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING], + ]; + } + + /** + * @test + * + * @covers ::base642bin + * + * @uses \Lcobucci\JWT\Encoding\CannotDecodeContent::invalidBase64String() + */ + public function sodiumBase642BinRaisesExceptionOnInvalidBase64(): void + { + $this->expectException(CannotDecodeContent::class); + + SodiumBase64Polyfill::base642bin('ááá', SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING); + } + + /** + * @test + * + * @covers ::base642binFallback + * + * @uses \Lcobucci\JWT\Encoding\CannotDecodeContent::invalidBase64String() + */ + public function fallbackBase642BinRaisesExceptionOnInvalidBase64(): void + { + $this->expectException(CannotDecodeContent::class); + + SodiumBase64Polyfill::base642binFallback('ááá', SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING); + } +}