From 84c1e5d4d151f2a3bb4da0371c9720638e357ee8 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 11 Oct 2022 12:45:32 +0200 Subject: [PATCH 1/9] Tighten `string` to `non-empty-string` where possible --- src/Decoder.php | 4 ++++ src/Encoder.php | 4 ++++ src/Encoding/JoseEncoder.php | 7 ++++++- src/JwtFacade.php | 1 + src/Parser.php | 2 ++ src/Signer.php | 6 ++++++ src/Signer/Blake2b.php | 6 +++++- src/Signer/Ecdsa.php | 5 +++++ src/Signer/Ecdsa/MultibyteStringConverter.php | 2 ++ src/Signer/Ecdsa/SignatureConverter.php | 6 ++++++ src/Signer/Eddsa.php | 7 ++++++- src/Signer/Hmac.php | 6 +++++- src/Signer/OpenSSL.php | 2 ++ src/SodiumBase64Polyfill.php | 14 ++++++++++++-- src/Token.php | 2 ++ src/Token/Parser.php | 9 +++++++++ src/UnencryptedToken.php | 2 ++ tests/JwtFacadeTest.php | 1 + tests/MaliciousTamperingPreventionTest.php | 1 + tests/RFC6978VectorTest.php | 2 ++ .../Signer/Ecdsa/MultibyteStringConverterTest.php | 14 +++++++++----- tests/Signer/EddsaTest.php | 4 +--- tests/Token/ParserTest.php | 2 +- tests/UnsupportedParser.php | 2 +- 24 files changed, 95 insertions(+), 16 deletions(-) diff --git a/src/Decoder.php b/src/Decoder.php index 804c78e5..6b24b926 100644 --- a/src/Decoder.php +++ b/src/Decoder.php @@ -10,6 +10,8 @@ interface Decoder /** * Decodes from JSON, validating the errors * + * @param non-empty-string $json + * * @throws CannotDecodeContent When something goes wrong while decoding. */ public function jsonDecode(string $json): mixed; @@ -19,6 +21,8 @@ public function jsonDecode(string $json): mixed; * * @link http://tools.ietf.org/html/rfc4648#section-5 * + * @return ($data is non-empty-string ? non-empty-string : string) + * * @throws CannotDecodeContent When something goes wrong while decoding. */ public function base64UrlDecode(string $data): string; diff --git a/src/Encoder.php b/src/Encoder.php index 69437631..3a4cbb59 100644 --- a/src/Encoder.php +++ b/src/Encoder.php @@ -10,6 +10,8 @@ interface Encoder /** * Encodes to JSON, validating the errors * + * @return non-empty-string + * * @throws CannotEncodeContent When something goes wrong while encoding. */ public function jsonEncode(mixed $data): string; @@ -18,6 +20,8 @@ public function jsonEncode(mixed $data): string; * Encodes to base64url * * @link http://tools.ietf.org/html/rfc4648#section-5 + * + * @return ($data is non-empty-string ? non-empty-string : string) */ public function base64UrlEncode(string $data): string; } diff --git a/src/Encoding/JoseEncoder.php b/src/Encoding/JoseEncoder.php index 0d904442..6079f51f 100644 --- a/src/Encoding/JoseEncoder.php +++ b/src/Encoding/JoseEncoder.php @@ -8,6 +8,7 @@ use Lcobucci\JWT\Encoder; use Lcobucci\JWT\SodiumBase64Polyfill; +use function assert; use function json_decode; use function json_encode; @@ -23,10 +24,14 @@ final class JoseEncoder implements Encoder, Decoder public function jsonEncode(mixed $data): string { try { - return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); + $jsonEncoded = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); } catch (JsonException $exception) { throw CannotEncodeContent::jsonIssues($exception); } + + assert($jsonEncoded !== ''); + + return $jsonEncoded; } public function jsonDecode(string $json): mixed diff --git a/src/JwtFacade.php b/src/JwtFacade.php index cd495f4e..8b388fc0 100644 --- a/src/JwtFacade.php +++ b/src/JwtFacade.php @@ -45,6 +45,7 @@ public function issue( return $customiseBuilder($builder, $now)->getToken($signer, $signingKey); } + /** @param non-empty-string $jwt */ public function parse( string $jwt, SignedWith $signedWith, diff --git a/src/Parser.php b/src/Parser.php index b0a0e80d..fa77f04e 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -12,6 +12,8 @@ interface Parser /** * Parses the JWT and returns a token * + * @param non-empty-string $jwt + * * @throws CannotDecodeContent When something goes wrong while decoding. * @throws InvalidTokenStructure When token string structure is invalid. * @throws UnsupportedHeaderFound When parsed token has an unsupported header. diff --git a/src/Signer.php b/src/Signer.php index 71e9296b..7e787d2e 100644 --- a/src/Signer.php +++ b/src/Signer.php @@ -12,12 +12,16 @@ interface Signer { /** * Returns the algorithm id + * + * @return non-empty-string */ public function algorithmId(): string; /** * Creates a hash for the given payload * + * @param non-empty-string $payload + * * @throws CannotSignPayload When payload signing fails. * @throws InvalidKeyProvided When issue key is invalid/incompatible. * @throws ConversionFailed When signature could not be converted. @@ -27,6 +31,8 @@ public function sign(string $payload, Key $key): string; /** * Returns if the expected hash matches with the data and key * + * @param non-empty-string $payload + * * @throws InvalidKeyProvided When issue key is invalid/incompatible. * @throws ConversionFailed When signature could not be converted. */ diff --git a/src/Signer/Blake2b.php b/src/Signer/Blake2b.php index 71f2d9dd..9625a09a 100644 --- a/src/Signer/Blake2b.php +++ b/src/Signer/Blake2b.php @@ -5,6 +5,7 @@ use Lcobucci\JWT\Signer; +use function assert; use function hash_equals; use function sodium_crypto_generichash; use function strlen; @@ -26,7 +27,10 @@ public function sign(string $payload, Key $key): string throw InvalidKeyProvided::tooShort(self::MINIMUM_KEY_LENGTH_IN_BITS, $actualKeyLength); } - return sodium_crypto_generichash($payload, $key->contents()); + $hash = sodium_crypto_generichash($payload, $key->contents()); + assert($hash !== ''); + + return $hash; } public function verify(string $expected, string $payload, Key $key): bool diff --git a/src/Signer/Ecdsa.php b/src/Signer/Ecdsa.php index e22dfaf7..57cea388 100644 --- a/src/Signer/Ecdsa.php +++ b/src/Signer/Ecdsa.php @@ -6,6 +6,8 @@ use Lcobucci\JWT\Signer\Ecdsa\MultibyteStringConverter; use Lcobucci\JWT\Signer\Ecdsa\SignatureConverter; +use function assert; + use const OPENSSL_KEYTYPE_EC; abstract class Ecdsa extends OpenSSL @@ -15,6 +17,7 @@ public function __construct( ) { } + /** @return non-empty-string */ final public function sign(string $payload, Key $key): string { return $this->converter->fromAsn1( @@ -25,6 +28,8 @@ final public function sign(string $payload, Key $key): string final public function verify(string $expected, string $payload, Key $key): bool { + assert($expected !== ''); + return $this->verifySignature( $this->converter->toAsn1($expected, $this->pointLength()), $payload, diff --git a/src/Signer/Ecdsa/MultibyteStringConverter.php b/src/Signer/Ecdsa/MultibyteStringConverter.php index a72dd434..0e02a0f2 100644 --- a/src/Signer/Ecdsa/MultibyteStringConverter.php +++ b/src/Signer/Ecdsa/MultibyteStringConverter.php @@ -65,6 +65,7 @@ public function toAsn1(string $points, int $length): string . self::ASN1_INTEGER . dechex($lengthS) . $pointS, ); assert(is_string($asn1)); + assert($asn1 !== ''); return $asn1; } @@ -109,6 +110,7 @@ public function fromAsn1(string $signature, int $length): string $points = hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT) . str_pad($pointS, $length, '0', STR_PAD_LEFT)); assert(is_string($points)); + assert($points !== ''); return $points; } diff --git a/src/Signer/Ecdsa/SignatureConverter.php b/src/Signer/Ecdsa/SignatureConverter.php index d854614e..1124e3af 100644 --- a/src/Signer/Ecdsa/SignatureConverter.php +++ b/src/Signer/Ecdsa/SignatureConverter.php @@ -21,6 +21,8 @@ interface SignatureConverter /** * Converts the signature generated by OpenSSL into what JWA defines * + * @return non-empty-string + * * @throws ConversionFailed When there was an issue during the format conversion. */ public function fromAsn1(string $signature, int $length): string; @@ -28,6 +30,10 @@ public function fromAsn1(string $signature, int $length): string; /** * Converts the JWA signature into something OpenSSL understands * + * @param non-empty-string $points + * + * @return non-empty-string + * * @throws ConversionFailed When there was an issue during the format conversion. */ public function toAsn1(string $points, int $length): string; diff --git a/src/Signer/Eddsa.php b/src/Signer/Eddsa.php index 03ad88bd..c8dc4bf0 100644 --- a/src/Signer/Eddsa.php +++ b/src/Signer/Eddsa.php @@ -6,6 +6,7 @@ use Lcobucci\JWT\Signer; use SodiumException; +use function assert; use function sodium_crypto_sign_detached; use function sodium_crypto_sign_verify_detached; @@ -19,10 +20,14 @@ public function algorithmId(): string public function sign(string $payload, Key $key): string { try { - return sodium_crypto_sign_detached($payload, $key->contents()); + $signature = sodium_crypto_sign_detached($payload, $key->contents()); } catch (SodiumException $sodiumException) { throw new InvalidKeyProvided($sodiumException->getMessage(), 0, $sodiumException); } + + assert($signature !== ''); + + return $signature; } public function verify(string $expected, string $payload, Key $key): bool diff --git a/src/Signer/Hmac.php b/src/Signer/Hmac.php index 152d4d50..bc595da4 100644 --- a/src/Signer/Hmac.php +++ b/src/Signer/Hmac.php @@ -28,7 +28,11 @@ final public function verify(string $expected, string $payload, Key $key): bool return hash_equals($expected, $this->sign($payload, $key)); } - /** @internal */ + /** + * @internal + * + * @return non-empty-string + */ abstract public function algorithm(): string; /** diff --git a/src/Signer/OpenSSL.php b/src/Signer/OpenSSL.php index 6a615fb3..bcc7065c 100644 --- a/src/Signer/OpenSSL.php +++ b/src/Signer/OpenSSL.php @@ -34,6 +34,8 @@ abstract class OpenSSL implements Signer ]; /** + * @return non-empty-string + * * @throws CannotSignPayload * @throws InvalidKeyProvided */ diff --git a/src/SodiumBase64Polyfill.php b/src/SodiumBase64Polyfill.php index 5bead0c5..65879753 100644 --- a/src/SodiumBase64Polyfill.php +++ b/src/SodiumBase64Polyfill.php @@ -23,6 +23,7 @@ final class SodiumBase64Polyfill public const SODIUM_BASE64_VARIANT_URLSAFE = 5; public const SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING = 7; + /** @return ($decoded is non-empty-string ? non-empty-string : string) */ public static function bin2base64(string $decoded, int $variant): string { if (! function_exists('sodium_bin2base64')) { @@ -32,6 +33,7 @@ public static function bin2base64(string $decoded, int $variant): string return sodium_bin2base64($decoded, $variant); } + /** @return ($decoded is non-empty-string ? non-empty-string : string) */ public static function bin2base64Fallback(string $decoded, int $variant): string { $encoded = base64_encode($decoded); @@ -53,7 +55,11 @@ public static function bin2base64Fallback(string $decoded, int $variant): string return $encoded; } - /** @throws CannotDecodeContent */ + /** + * @return ($encoded is non-empty-string ? non-empty-string : string) + * + * @throws CannotDecodeContent + */ public static function base642bin(string $encoded, int $variant): string { if (! function_exists('sodium_base642bin')) { @@ -67,7 +73,11 @@ public static function base642bin(string $encoded, int $variant): string } } - /** @throws CannotDecodeContent */ + /** + * @return ($encoded is non-empty-string ? non-empty-string : string) + * + * @throws CannotDecodeContent + */ public static function base642binFallback(string $encoded, int $variant): string { if ( diff --git a/src/Token.php b/src/Token.php index 37808502..8f85790f 100644 --- a/src/Token.php +++ b/src/Token.php @@ -50,6 +50,8 @@ public function isExpired(DateTimeInterface $now): bool; /** * Returns an encoded representation of the token + * + * @return non-empty-string */ public function toString(): string; } diff --git a/src/Token/Parser.php b/src/Token/Parser.php index 2391b72e..8e21ba9a 100644 --- a/src/Token/Parser.php +++ b/src/Token/Parser.php @@ -9,6 +9,7 @@ use Lcobucci\JWT\Token as TokenInterface; use function array_key_exists; +use function assert; use function count; use function explode; use function is_array; @@ -26,6 +27,8 @@ public function __construct(private readonly Decoder $decoder) public function parse(string $jwt): TokenInterface { [$encodedHeaders, $encodedClaims, $encodedSignature] = $this->splitJwt($jwt); + assert($encodedHeaders !== ''); + assert($encodedClaims !== ''); $header = $this->parseHeader($encodedHeaders); @@ -39,6 +42,8 @@ public function parse(string $jwt): TokenInterface /** * Splits the JWT string into an array * + * @param non-empty-string $jwt + * * @return string[] * * @throws InvalidTokenStructure When JWT doesn't have all parts. @@ -57,6 +62,8 @@ private function splitJwt(string $jwt): array /** * Parses the header from a string * + * @param non-empty-string $data + * * @return mixed[] * * @throws UnsupportedHeaderFound When an invalid header is informed. @@ -84,6 +91,8 @@ private function parseHeader(string $data): array /** * Parses the claim set from a string * + * @param non-empty-string $data + * * @return mixed[] * * @throws InvalidTokenStructure When parsed content isn't an array or contains non-parseable dates. diff --git a/src/UnencryptedToken.php b/src/UnencryptedToken.php index d177059a..8692f0d8 100644 --- a/src/UnencryptedToken.php +++ b/src/UnencryptedToken.php @@ -20,6 +20,8 @@ public function signature(): Signature; /** * Returns the token payload + * + * @return non-empty-string */ public function payload(): string; } diff --git a/tests/JwtFacadeTest.php b/tests/JwtFacadeTest.php index 32c72fce..591443d3 100644 --- a/tests/JwtFacadeTest.php +++ b/tests/JwtFacadeTest.php @@ -58,6 +58,7 @@ protected function setUp(): void $this->issuer = 'bar'; } + /** @return non-empty-string */ private function createToken(): string { return (new JwtFacade(clock: $this->clock))->issue( diff --git a/tests/MaliciousTamperingPreventionTest.php b/tests/MaliciousTamperingPreventionTest.php index e8698f89..30746e8a 100644 --- a/tests/MaliciousTamperingPreventionTest.php +++ b/tests/MaliciousTamperingPreventionTest.php @@ -105,6 +105,7 @@ public function preventRegressionsThatAllowsMaliciousTampering(): void ); } + /** @return non-empty-string */ private function createMaliciousToken(string $token): string { $dec = new JoseEncoder(); diff --git a/tests/RFC6978VectorTest.php b/tests/RFC6978VectorTest.php index c3d71bc8..4b2e3009 100644 --- a/tests/RFC6978VectorTest.php +++ b/tests/RFC6978VectorTest.php @@ -34,6 +34,8 @@ final class RFC6978VectorTest extends TestCase * @covers \Lcobucci\JWT\Signer\Ecdsa\Sha384 * @covers \Lcobucci\JWT\Signer\Ecdsa\Sha512 * @covers \Lcobucci\JWT\Signer\OpenSSL + * + * @param non-empty-string $payload */ public function theVectorsFromRFC6978CanBeVerified( Ecdsa $signer, diff --git a/tests/Signer/Ecdsa/MultibyteStringConverterTest.php b/tests/Signer/Ecdsa/MultibyteStringConverterTest.php index 18bb56f5..cd3ca8c4 100644 --- a/tests/Signer/Ecdsa/MultibyteStringConverterTest.php +++ b/tests/Signer/Ecdsa/MultibyteStringConverterTest.php @@ -7,10 +7,8 @@ use Lcobucci\JWT\Signer\Ecdsa\MultibyteStringConverter; use PHPUnit\Framework\TestCase; -use function assert; use function bin2hex; use function hex2bin; -use function is_string; use function strlen; /** @coversDefaultClass \Lcobucci\JWT\Signer\Ecdsa\MultibyteStringConverter */ @@ -23,6 +21,10 @@ final class MultibyteStringConverterTest extends TestCase * @covers ::toAsn1 * @covers ::octetLength * @covers ::preparePositiveInteger + * + * @param non-empty-string $r + * @param non-empty-string $s + * @param non-empty-string $asn1 */ public function toAsn1ShouldReturnThePointsInAnAsn1SequenceFormat( string $r, @@ -31,7 +33,8 @@ public function toAsn1ShouldReturnThePointsInAnAsn1SequenceFormat( ): void { $converter = new MultibyteStringConverter(); $message = hex2bin($r . $s); - assert(is_string($message)); + self::assertIsString($message); + self::assertNotSame('', $message); self::assertSame($asn1, bin2hex($converter->toAsn1($message, strlen($r)))); } @@ -65,7 +68,8 @@ public function fromAsn1ShouldReturnTheConcatenatedPoints(string $r, string $s, { $converter = new MultibyteStringConverter(); $message = hex2bin($asn1); - assert(is_string($message)); + self::assertIsString($message); + self::assertNotSame('', $message); self::assertSame($r . $s, bin2hex($converter->fromAsn1($message, strlen($r)))); } @@ -113,7 +117,7 @@ public function fromAsn1ShouldRaiseExceptionOnInvalidMessage(string $message, st { $converter = new MultibyteStringConverter(); $message = hex2bin($message); - assert(is_string($message)); + self::assertIsString($message); $this->expectException(ConversionFailed::class); $this->expectExceptionMessage($expectedMessage); diff --git a/tests/Signer/EddsaTest.php b/tests/Signer/EddsaTest.php index c01be9a5..a0bb846e 100644 --- a/tests/Signer/EddsaTest.php +++ b/tests/Signer/EddsaTest.php @@ -117,8 +117,7 @@ public function signatureOfRfcExample(): void $signer = new Eddsa(); $encoder = new JoseEncoder(); - $decoded = $encoder->base64UrlDecode('nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A'); - self::assertNotSame('', $decoded); + $decoded = $encoder->base64UrlDecode('nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A'); $key = InMemory::plainText( $decoded . $encoder->base64UrlDecode('11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo'), @@ -152,7 +151,6 @@ public function verificationOfRfcExample(): void $encoder = new JoseEncoder(); $decoded = $encoder->base64UrlDecode('11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo'); - self::assertNotSame('', $decoded); $key = InMemory::plainText($decoded); $payload = 'eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc'; diff --git a/tests/Token/ParserTest.php b/tests/Token/ParserTest.php index af7a72d8..63e3d284 100644 --- a/tests/Token/ParserTest.php +++ b/tests/Token/ParserTest.php @@ -49,7 +49,7 @@ public function parseMustRaiseExceptionWhenTokenDoesNotHaveThreeParts(): void $this->expectException(InvalidTokenStructure::class); $this->expectExceptionMessage('The JWT string must have two dots'); - $parser->parse(''); + $parser->parse('.'); } /** @test */ diff --git a/tests/UnsupportedParser.php b/tests/UnsupportedParser.php index 4a9f8925..85f8cb26 100644 --- a/tests/UnsupportedParser.php +++ b/tests/UnsupportedParser.php @@ -55,7 +55,7 @@ public function isExpired(DateTimeInterface $now): bool public function toString(): string { - return ''; + return 'unsupported-parser'; } }; } From 86b29bd9e1bbeccc4b8c130670aca655578da615 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Wed, 12 Oct 2022 14:41:54 +0200 Subject: [PATCH 2/9] Splitted JWT must be checked runtime for string emptiness --- Makefile | 2 +- src/Token/InvalidTokenStructure.php | 10 ++++++++++ src/Token/Parser.php | 11 ++++++++--- tests/Token/ParserTest.php | 22 ++++++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index bc4c201f..7a166cac 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ vendor/composer/installed.json: composer.json composer.lock .PHONY: phpunit phpunit: - @php -d assert.exception=1 -d zend.assertions=1 vendor/bin/phpunit + @php -d assert.exception=1 -d zend.assertions=1 vendor/bin/phpunit $(PHPUNIT_FLAGS) .PHONY: infection infection: diff --git a/src/Token/InvalidTokenStructure.php b/src/Token/InvalidTokenStructure.php index 49c28409..7abb6c3a 100644 --- a/src/Token/InvalidTokenStructure.php +++ b/src/Token/InvalidTokenStructure.php @@ -13,6 +13,16 @@ public static function missingOrNotEnoughSeparators(): self return new self('The JWT string must have two dots'); } + public static function missingHeaderPart(): self + { + return new self('The JWT string is missing the Header part'); + } + + public static function missingClaimsPart(): self + { + return new self('The JWT string is missing the Claim part'); + } + public static function arrayExpected(string $part): self { return new self($part . ' must be an array'); diff --git a/src/Token/Parser.php b/src/Token/Parser.php index 8e21ba9a..c82733af 100644 --- a/src/Token/Parser.php +++ b/src/Token/Parser.php @@ -9,7 +9,6 @@ use Lcobucci\JWT\Token as TokenInterface; use function array_key_exists; -use function assert; use function count; use function explode; use function is_array; @@ -27,8 +26,14 @@ public function __construct(private readonly Decoder $decoder) public function parse(string $jwt): TokenInterface { [$encodedHeaders, $encodedClaims, $encodedSignature] = $this->splitJwt($jwt); - assert($encodedHeaders !== ''); - assert($encodedClaims !== ''); + + if ($encodedHeaders === '') { + throw InvalidTokenStructure::missingHeaderPart(); + } + + if ($encodedClaims === '') { + throw InvalidTokenStructure::missingClaimsPart(); + } $header = $this->parseHeader($encodedHeaders); diff --git a/tests/Token/ParserTest.php b/tests/Token/ParserTest.php index 63e3d284..12d290d4 100644 --- a/tests/Token/ParserTest.php +++ b/tests/Token/ParserTest.php @@ -52,6 +52,28 @@ public function parseMustRaiseExceptionWhenTokenDoesNotHaveThreeParts(): void $parser->parse('.'); } + /** @test */ + public function parseMustRaiseExceptionWhenTokenDoesNotHaveHeaders(): void + { + $parser = $this->createParser(); + + $this->expectException(InvalidTokenStructure::class); + $this->expectExceptionMessage('The JWT string is missing the Header part'); + + $parser->parse('.b.c'); + } + + /** @test */ + public function parseMustRaiseExceptionWhenTokenDoesNotHaveClaims(): void + { + $parser = $this->createParser(); + + $this->expectException(InvalidTokenStructure::class); + $this->expectExceptionMessage('The JWT string is missing the Claim part'); + + $parser->parse('a..c'); + } + /** @test */ public function parseMustRaiseExceptionWhenHeaderCannotBeDecoded(): void { From f50d4cc1ac91bb746a866bb521afe9b5d1fd441d Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Wed, 12 Oct 2022 11:17:30 +0200 Subject: [PATCH 3/9] Remove empty Signer, empty Key and empty Signature --- src/Configuration.php | 18 --- src/Signer/Key/InMemory.php | 6 - src/Signer/None.php | 27 ----- src/Token/Parser.php | 10 +- src/Token/Signature.php | 6 - tests/Benchmark/NoneBench.php | 29 ----- tests/ConfigurationTest.php | 98 +++++++++------- tests/KeyDumpSigner.php | 26 +++++ tests/Signer/Key/InMemoryTest.php | 17 --- tests/Signer/NoneTest.php | 64 ----------- tests/TimeFractionPrecisionTest.php | 13 ++- tests/Token/ParserTest.php | 108 +++++++++--------- tests/Token/PlainTest.php | 4 +- tests/Token/SignatureTest.php | 13 --- tests/UnsignedTokenTest.php | 10 +- .../Constraint/ConstraintTestCase.php | 2 +- 16 files changed, 163 insertions(+), 288 deletions(-) delete mode 100644 src/Signer/None.php delete mode 100644 tests/Benchmark/NoneBench.php create mode 100644 tests/KeyDumpSigner.php delete mode 100644 tests/Signer/NoneTest.php diff --git a/src/Configuration.php b/src/Configuration.php index 64a121b0..488ea3e2 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -7,8 +7,6 @@ use Lcobucci\JWT\Encoding\ChainedFormatter; use Lcobucci\JWT\Encoding\JoseEncoder; use Lcobucci\JWT\Signer\Key; -use Lcobucci\JWT\Signer\Key\InMemory; -use Lcobucci\JWT\Signer\None; use Lcobucci\JWT\Validation\Constraint; /** @@ -74,22 +72,6 @@ public static function forSymmetricSigner( ); } - /** @deprecated Deprecated since v4.3 */ - public static function forUnsecuredSigner( - Encoder $encoder = new JoseEncoder(), - Decoder $decoder = new JoseEncoder(), - ): self { - $key = InMemory::empty(); - - return new self( - new None(), - $key, - $key, - $encoder, - $decoder, - ); - } - /** @param callable(ClaimsFormatter): Builder $builderFactory */ public function setBuilderFactory(callable $builderFactory): void { diff --git a/src/Signer/Key/InMemory.php b/src/Signer/Key/InMemory.php index 14fca4d6..5b2cce2d 100644 --- a/src/Signer/Key/InMemory.php +++ b/src/Signer/Key/InMemory.php @@ -18,12 +18,6 @@ private function __construct(public readonly string $contents, public readonly s { } - /** @deprecated Deprecated since v4.3 */ - public static function empty(): self - { - return new self('', ''); - } - /** @param non-empty-string $contents */ public static function plainText(string $contents, string $passphrase = ''): self { diff --git a/src/Signer/None.php b/src/Signer/None.php deleted file mode 100644 index 29d0ed79..00000000 --- a/src/Signer/None.php +++ /dev/null @@ -1,27 +0,0 @@ -parseClaims($encodedClaims), $encodedClaims), - $this->parseSignature($header, $encodedSignature), + $this->parseSignature($encodedSignature), ); } @@ -146,14 +146,10 @@ private function convertDate(int|float|string $timestamp): DateTimeImmutable /** * Returns the signature from given data * - * @param mixed[] $header + * @param non-empty-string $data */ - private function parseSignature(array $header, string $data): Signature + private function parseSignature(string $data): Signature { - if ($data === '' || ! array_key_exists('alg', $header) || $header['alg'] === 'none') { - return Signature::fromEmptyData(); - } - $hash = $this->decoder->base64UrlDecode($data); return new Signature($hash, $data); diff --git a/src/Token/Signature.php b/src/Token/Signature.php index 4fff9888..ec491d29 100644 --- a/src/Token/Signature.php +++ b/src/Token/Signature.php @@ -9,12 +9,6 @@ public function __construct(private readonly string $hash, private readonly stri { } - /** @deprecated Deprecated since v4.3 */ - public static function fromEmptyData(): self - { - return new self('', ''); - } - public function hash(): string { return $this->hash; diff --git a/tests/Benchmark/NoneBench.php b/tests/Benchmark/NoneBench.php deleted file mode 100644 index e6460b5b..00000000 --- a/tests/Benchmark/NoneBench.php +++ /dev/null @@ -1,29 +0,0 @@ -verificationKey()); } - /** - * @test - * - * @covers ::forUnsecuredSigner - * @covers ::signer - * @covers ::signingKey - * @covers ::verificationKey - */ - public function forUnsecuredSignerShouldConfigureSignerAndBothKeys(): void - { - $key = InMemory::empty(); - $config = Configuration::forUnsecuredSigner(); - - self::assertInstanceOf(None::class, $config->signer()); - self::assertEquals($key, $config->signingKey()); - self::assertEquals($key, $config->verificationKey()); - } - /** * @test * * @covers ::builder * - * @uses \Lcobucci\JWT\Configuration::forUnsecuredSigner + * @uses \Lcobucci\JWT\Configuration::forSymmetricSigner */ public function builderShouldCreateABuilderWithDefaultEncoderAndClaimFactory(): void { - $config = Configuration::forUnsecuredSigner(); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); $builder = $config->builder(); self::assertInstanceOf(BuilderImpl::class, $builder); @@ -132,11 +116,15 @@ public function builderShouldCreateABuilderWithDefaultEncoderAndClaimFactory(): * * @covers ::builder * - * @uses \Lcobucci\JWT\Configuration::forUnsecuredSigner + * @uses \Lcobucci\JWT\Configuration::forSymmetricSigner */ public function builderShouldCreateABuilderWithCustomizedEncoderAndClaimFactory(): void { - $config = Configuration::forUnsecuredSigner($this->encoder); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + $this->encoder, + ); $builder = $config->builder(); self::assertInstanceOf(BuilderImpl::class, $builder); @@ -149,13 +137,16 @@ public function builderShouldCreateABuilderWithCustomizedEncoderAndClaimFactory( * @covers ::builder * @covers ::setBuilderFactory * - * @uses \Lcobucci\JWT\Configuration::forUnsecuredSigner + * @uses \Lcobucci\JWT\Configuration::forSymmetricSigner */ public function builderShouldUseBuilderFactoryWhenThatIsConfigured(): void { $builder = $this->createMock(Builder::class); - $config = Configuration::forUnsecuredSigner(); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); $config->setBuilderFactory( static function () use ($builder): Builder { return $builder; @@ -169,11 +160,14 @@ static function () use ($builder): Builder { * * @covers ::parser * - * @uses \Lcobucci\JWT\Configuration::forUnsecuredSigner + * @uses \Lcobucci\JWT\Configuration::forSymmetricSigner */ public function parserShouldReturnAParserWithDefaultDecoder(): void { - $config = Configuration::forUnsecuredSigner(); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); $parser = $config->parser(); self::assertNotEquals(new ParserImpl($this->decoder), $parser); @@ -184,11 +178,15 @@ public function parserShouldReturnAParserWithDefaultDecoder(): void * * @covers ::parser * - * @uses \Lcobucci\JWT\Configuration::forUnsecuredSigner + * @uses \Lcobucci\JWT\Configuration::forSymmetricSigner */ public function parserShouldReturnAParserWithCustomizedDecoder(): void { - $config = Configuration::forUnsecuredSigner(decoder: $this->decoder); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + decoder: $this->decoder, + ); $parser = $config->parser(); self::assertEquals(new ParserImpl($this->decoder), $parser); @@ -200,11 +198,14 @@ public function parserShouldReturnAParserWithCustomizedDecoder(): void * @covers ::parser * @covers ::setParser * - * @uses \Lcobucci\JWT\Configuration::forUnsecuredSigner + * @uses \Lcobucci\JWT\Configuration::forSymmetricSigner */ public function parserShouldNotCreateAnInstanceIfItWasConfigured(): void { - $config = Configuration::forUnsecuredSigner(); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); $config->setParser($this->parser); self::assertSame($this->parser, $config->parser()); @@ -215,11 +216,14 @@ public function parserShouldNotCreateAnInstanceIfItWasConfigured(): void * * @covers ::validator * - * @uses \Lcobucci\JWT\Configuration::forUnsecuredSigner + * @uses \Lcobucci\JWT\Configuration::forSymmetricSigner */ public function validatorShouldReturnTheDefaultWhenItWasNotConfigured(): void { - $config = Configuration::forUnsecuredSigner(); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); $validator = $config->validator(); self::assertNotSame($this->validator, $validator); @@ -231,11 +235,14 @@ public function validatorShouldReturnTheDefaultWhenItWasNotConfigured(): void * @covers ::validator * @covers ::setValidator * - * @uses \Lcobucci\JWT\Configuration::forUnsecuredSigner + * @uses \Lcobucci\JWT\Configuration::forSymmetricSigner */ public function validatorShouldReturnTheConfiguredValidator(): void { - $config = Configuration::forUnsecuredSigner(); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); $config->setValidator($this->validator); self::assertSame($this->validator, $config->validator()); @@ -246,11 +253,14 @@ public function validatorShouldReturnTheConfiguredValidator(): void * * @covers ::validationConstraints * - * @uses \Lcobucci\JWT\Configuration::forUnsecuredSigner + * @uses \Lcobucci\JWT\Configuration::forSymmetricSigner */ public function validationConstraintsShouldReturnAnEmptyArrayWhenItWasNotConfigured(): void { - $config = Configuration::forUnsecuredSigner(); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); self::assertSame([], $config->validationConstraints()); } @@ -261,11 +271,14 @@ public function validationConstraintsShouldReturnAnEmptyArrayWhenItWasNotConfigu * @covers ::validationConstraints * @covers ::setValidationConstraints * - * @uses \Lcobucci\JWT\Configuration::forUnsecuredSigner + * @uses \Lcobucci\JWT\Configuration::forSymmetricSigner */ public function validationConstraintsShouldReturnTheConfiguredValidator(): void { - $config = Configuration::forUnsecuredSigner(); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); $config->setValidationConstraints($this->validationConstraints); self::assertSame([$this->validationConstraints], $config->validationConstraints()); @@ -276,12 +289,15 @@ public function validationConstraintsShouldReturnTheConfiguredValidator(): void * * @covers ::builder * - * @uses \Lcobucci\JWT\Configuration::forUnsecuredSigner + * @uses \Lcobucci\JWT\Configuration::forSymmetricSigner */ public function customClaimFormatterCanBeUsed(): void { $formatter = $this->createMock(ClaimsFormatter::class); - $config = Configuration::forUnsecuredSigner(); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); self::assertEquals(new BuilderImpl(new JoseEncoder(), $formatter), $config->builder($formatter)); } diff --git a/tests/KeyDumpSigner.php b/tests/KeyDumpSigner.php new file mode 100644 index 00000000..d5677804 --- /dev/null +++ b/tests/KeyDumpSigner.php @@ -0,0 +1,26 @@ +contents(); + } + + // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter + public function verify(string $expected, string $payload, Key $key): bool + { + return $expected === $key->contents(); + } +} diff --git a/tests/Signer/Key/InMemoryTest.php b/tests/Signer/Key/InMemoryTest.php index 4254efd3..cef109b0 100644 --- a/tests/Signer/Key/InMemoryTest.php +++ b/tests/Signer/Key/InMemoryTest.php @@ -47,23 +47,6 @@ public function base64EncodedShouldDecodeKeyContents(): void self::assertSame('testing', $key->contents()); } - /** - * @test - * - * @covers ::empty - * @covers ::guardAgainstEmptyKey - * @covers ::__construct - * @covers ::contents - * @covers ::passphrase - */ - public function emptyShouldCreateAKeyWithEmptyContentsAndPassphrase(): void - { - $key = InMemory::empty(); - - self::assertSame('', $key->contents()); - self::assertSame('', $key->passphrase()); - } - /** * @test * diff --git a/tests/Signer/NoneTest.php b/tests/Signer/NoneTest.php deleted file mode 100644 index 767f1b58..00000000 --- a/tests/Signer/NoneTest.php +++ /dev/null @@ -1,64 +0,0 @@ -algorithmId()); - } - - /** - * @test - * - * @covers ::sign - */ - public function signShouldReturnAnEmptyString(): void - { - $signer = new None(); - - self::assertSame('', $signer->sign('test', InMemory::plainText('test'))); - } - - /** - * @test - * - * @covers ::verify - */ - public function verifyShouldReturnTrueWhenSignatureHashIsEmpty(): void - { - $signer = new None(); - - self::assertTrue($signer->verify('', 'test', InMemory::plainText('test'))); - } - - /** - * @test - * - * @covers ::verify - */ - public function verifyShouldReturnFalseWhenSignatureHashIsEmpty(): void - { - $signer = new None(); - - self::assertFalse($signer->verify('testing', 'test', InMemory::plainText('test'))); - } -} diff --git a/tests/TimeFractionPrecisionTest.php b/tests/TimeFractionPrecisionTest.php index b3d326f8..01884d47 100644 --- a/tests/TimeFractionPrecisionTest.php +++ b/tests/TimeFractionPrecisionTest.php @@ -6,6 +6,8 @@ use DateTimeImmutable; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Encoding\JoseEncoder; +use Lcobucci\JWT\KeyDumpSigner; +use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Token\Plain; use PHPUnit\Framework\TestCase; @@ -21,7 +23,6 @@ * @covers \Lcobucci\JWT\Token\DataSet * @covers \Lcobucci\JWT\Token\Signature * @covers \Lcobucci\JWT\Signer\Key\InMemory - * @covers \Lcobucci\JWT\Signer\None * @covers \Lcobucci\JWT\Validation\Validator * @covers \Lcobucci\JWT\Validation\ConstraintViolation * @covers \Lcobucci\JWT\Validation\RequiredConstraintsViolated @@ -36,7 +37,10 @@ final class TimeFractionPrecisionTest extends TestCase */ public function timeFractionsPrecisionsAreRespected(string $timeFraction): void { - $config = Configuration::forUnsecuredSigner(); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); $issuedAt = DateTimeImmutable::createFromFormat('U.u', $timeFraction); @@ -72,7 +76,10 @@ public function typeConversionDoesNotCauseParsingErrors(float|int|string $issued $headers = $encoder->base64UrlEncode($encoder->jsonEncode(['typ' => 'JWT', 'alg' => 'none'])); $claims = $encoder->base64UrlEncode($encoder->jsonEncode(['iat' => $issuedAt])); - $config = Configuration::forUnsecuredSigner(); + $config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); $parsedToken = $config->parser()->parse($headers . '.' . $claims . '.'); self::assertInstanceOf(Plain::class, $parsedToken); diff --git a/tests/Token/ParserTest.php b/tests/Token/ParserTest.php index 12d290d4..04f2e78c 100644 --- a/tests/Token/ParserTest.php +++ b/tests/Token/ParserTest.php @@ -138,10 +138,10 @@ public function parseMustRaiseExceptionWhenDealingWithInvalidClaims(): void /** @test */ public function parseMustReturnAnUnsecuredTokenWhenSignatureIsNotInformed(): void { - $this->decoder->expects(self::exactly(2)) + $this->decoder->expects(self::exactly(3)) ->method('base64UrlDecode') - ->withConsecutive(['a'], ['b']) - ->willReturnOnConsecutiveCalls('a_dec', 'b_dec'); + ->withConsecutive(['a'], ['b'], ['c']) + ->willReturnOnConsecutiveCalls('a_dec', 'b_dec', 'c_dec'); $this->decoder->expects(self::exactly(2)) ->method('jsonDecode') @@ -152,25 +152,26 @@ public function parseMustReturnAnUnsecuredTokenWhenSignatureIsNotInformed(): voi ); $parser = $this->createParser(); - $token = $parser->parse('a.b.'); + $token = $parser->parse('a.b.c'); self::assertInstanceOf(Plain::class, $token); - $headers = new DataSet(['typ' => 'JWT', 'alg' => 'none'], 'a'); - $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $headers = new DataSet(['typ' => 'JWT', 'alg' => 'none'], 'a'); + $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $signature = new Signature('c_dec', 'c'); self::assertEquals($headers, $token->headers()); self::assertEquals($claims, $token->claims()); - self::assertEquals(Signature::fromEmptyData(), $token->signature()); + self::assertEquals($signature, $token->signature()); } /** @test */ public function parseMustConfigureTypeToJWTWhenItIsMissing(): void { - $this->decoder->expects(self::exactly(2)) - ->method('base64UrlDecode') - ->withConsecutive(['a'], ['b']) - ->willReturnOnConsecutiveCalls('a_dec', 'b_dec'); + $this->decoder->expects(self::exactly(3)) + ->method('base64UrlDecode') + ->withConsecutive(['a'], ['b'], ['c']) + ->willReturnOnConsecutiveCalls('a_dec', 'b_dec', 'c_dec'); $this->decoder->expects(self::exactly(2)) ->method('jsonDecode') @@ -181,25 +182,26 @@ public function parseMustConfigureTypeToJWTWhenItIsMissing(): void ); $parser = $this->createParser(); - $token = $parser->parse('a.b.'); + $token = $parser->parse('a.b.c'); self::assertInstanceOf(Plain::class, $token); - $headers = new DataSet(['typ' => 'JWT', 'alg' => 'none'], 'a'); - $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $headers = new DataSet(['typ' => 'JWT', 'alg' => 'none'], 'a'); + $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $signature = new Signature('c_dec', 'c'); self::assertEquals($headers, $token->headers()); self::assertEquals($claims, $token->claims()); - self::assertEquals(Signature::fromEmptyData(), $token->signature()); + self::assertEquals($signature, $token->signature()); } /** @test */ public function parseMustNotChangeTypeWhenItIsConfigured(): void { - $this->decoder->expects(self::exactly(2)) - ->method('base64UrlDecode') - ->withConsecutive(['a'], ['b']) - ->willReturnOnConsecutiveCalls('a_dec', 'b_dec'); + $this->decoder->expects(self::exactly(3)) + ->method('base64UrlDecode') + ->withConsecutive(['a'], ['b'], ['c']) + ->willReturnOnConsecutiveCalls('a_dec', 'b_dec', 'c_dec'); $this->decoder->expects(self::exactly(2)) ->method('jsonDecode') @@ -210,25 +212,26 @@ public function parseMustNotChangeTypeWhenItIsConfigured(): void ); $parser = $this->createParser(); - $token = $parser->parse('a.b.'); + $token = $parser->parse('a.b.c'); self::assertInstanceOf(Plain::class, $token); - $headers = new DataSet(['typ' => 'JWS', 'alg' => 'none'], 'a'); - $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $headers = new DataSet(['typ' => 'JWS', 'alg' => 'none'], 'a'); + $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $signature = new Signature('c_dec', 'c'); self::assertEquals($headers, $token->headers()); self::assertEquals($claims, $token->claims()); - self::assertEquals(Signature::fromEmptyData(), $token->signature()); + self::assertEquals($signature, $token->signature()); } /** @test */ public function parseShouldReplicateClaimValueOnHeaderWhenNeeded(): void { - $this->decoder->expects(self::exactly(2)) + $this->decoder->expects(self::exactly(3)) ->method('base64UrlDecode') - ->withConsecutive(['a'], ['b']) - ->willReturnOnConsecutiveCalls('a_dec', 'b_dec'); + ->withConsecutive(['a'], ['b'], ['c']) + ->willReturnOnConsecutiveCalls('a_dec', 'b_dec', 'c_dec'); $this->decoder->expects(self::exactly(2)) ->method('jsonDecode') @@ -239,25 +242,26 @@ public function parseShouldReplicateClaimValueOnHeaderWhenNeeded(): void ); $parser = $this->createParser(); - $token = $parser->parse('a.b.'); + $token = $parser->parse('a.b.c'); self::assertInstanceOf(Plain::class, $token); - $headers = new DataSet(['typ' => 'JWT', 'alg' => 'none', RegisteredClaims::AUDIENCE => 'test'], 'a'); - $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $headers = new DataSet(['typ' => 'JWT', 'alg' => 'none', RegisteredClaims::AUDIENCE => 'test'], 'a'); + $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $signature = new Signature('c_dec', 'c'); self::assertEquals($headers, $token->headers()); self::assertEquals($claims, $token->claims()); - self::assertEquals(Signature::fromEmptyData(), $token->signature()); + self::assertEquals($signature, $token->signature()); } /** @test */ public function parseMustReturnANonSignedTokenWhenSignatureAlgorithmIsMissing(): void { - $this->decoder->expects(self::exactly(2)) + $this->decoder->expects(self::exactly(3)) ->method('base64UrlDecode') - ->withConsecutive(['a'], ['b']) - ->willReturnOnConsecutiveCalls('a_dec', 'b_dec'); + ->withConsecutive(['a'], ['b'], ['c']) + ->willReturnOnConsecutiveCalls('a_dec', 'b_dec', 'c_dec'); $this->decoder->expects(self::exactly(2)) ->method('jsonDecode') @@ -272,21 +276,22 @@ public function parseMustReturnANonSignedTokenWhenSignatureAlgorithmIsMissing(): self::assertInstanceOf(Plain::class, $token); - $headers = new DataSet(['typ' => 'JWT'], 'a'); - $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $headers = new DataSet(['typ' => 'JWT'], 'a'); + $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $signature = new Signature('c_dec', 'c'); self::assertEquals($headers, $token->headers()); self::assertEquals($claims, $token->claims()); - self::assertEquals(Signature::fromEmptyData(), $token->signature()); + self::assertEquals($signature, $token->signature()); } /** @test */ public function parseMustReturnANonSignedTokenWhenSignatureAlgorithmIsNone(): void { - $this->decoder->expects(self::exactly(2)) + $this->decoder->expects(self::exactly(3)) ->method('base64UrlDecode') - ->withConsecutive(['a'], ['b']) - ->willReturnOnConsecutiveCalls('a_dec', 'b_dec'); + ->withConsecutive(['a'], ['b'], ['c']) + ->willReturnOnConsecutiveCalls('a_dec', 'b_dec', 'c_dec'); $this->decoder->expects(self::exactly(2)) ->method('jsonDecode') @@ -301,12 +306,13 @@ public function parseMustReturnANonSignedTokenWhenSignatureAlgorithmIsNone(): vo self::assertInstanceOf(Plain::class, $token); - $headers = new DataSet(['typ' => 'JWT', 'alg' => 'none'], 'a'); - $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $headers = new DataSet(['typ' => 'JWT', 'alg' => 'none'], 'a'); + $claims = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b'); + $signature = new Signature('c_dec', 'c'); self::assertEquals($headers, $token->headers()); self::assertEquals($claims, $token->claims()); - self::assertEquals(Signature::fromEmptyData(), $token->signature()); + self::assertEquals($signature, $token->signature()); } /** @test */ @@ -347,10 +353,10 @@ public function parseMustConvertDateClaimsToObjects(): void RegisteredClaims::EXPIRATION_TIME => 1486930757.023055, ]; - $this->decoder->expects(self::exactly(2)) + $this->decoder->expects(self::exactly(3)) ->method('base64UrlDecode') - ->withConsecutive(['a'], ['b']) - ->willReturnOnConsecutiveCalls('a_dec', 'b_dec'); + ->withConsecutive(['a'], ['b'], ['c']) + ->willReturnOnConsecutiveCalls('a_dec', 'b_dec', 'c_dec'); $this->decoder->expects(self::exactly(2)) ->method('jsonDecode') @@ -360,7 +366,7 @@ public function parseMustConvertDateClaimsToObjects(): void $data, ); - $token = $this->createParser()->parse('a.b.'); + $token = $this->createParser()->parse('a.b.c'); self::assertInstanceOf(Plain::class, $token); $claims = $token->claims(); @@ -381,10 +387,10 @@ public function parseMustConvertStringDates(): void { $data = [RegisteredClaims::NOT_BEFORE => '1486930757.000000']; - $this->decoder->expects(self::exactly(2)) + $this->decoder->expects(self::exactly(3)) ->method('base64UrlDecode') - ->withConsecutive(['a'], ['b']) - ->willReturnOnConsecutiveCalls('a_dec', 'b_dec'); + ->withConsecutive(['a'], ['b'], ['c']) + ->willReturnOnConsecutiveCalls('a_dec', 'b_dec', 'c_dec'); $this->decoder->expects(self::exactly(2)) ->method('jsonDecode') @@ -394,7 +400,7 @@ public function parseMustConvertStringDates(): void $data, ); - $token = $this->createParser()->parse('a.b.'); + $token = $this->createParser()->parse('a.b.c'); self::assertInstanceOf(Plain::class, $token); $claims = $token->claims(); @@ -425,7 +431,7 @@ public function parseShouldRaiseExceptionOnInvalidDate(): void $this->expectException(InvalidTokenStructure::class); $this->expectExceptionMessage('Value is not in the allowed date format: 14/10/2018 10:50:10.10 UTC'); - $this->createParser()->parse('a.b.'); + $this->createParser()->parse('a.b.c'); } /** @test */ diff --git a/tests/Token/PlainTest.php b/tests/Token/PlainTest.php index 5621b248..3554cb44 100644 --- a/tests/Token/PlainTest.php +++ b/tests/Token/PlainTest.php @@ -461,9 +461,9 @@ public function isExpiredShouldReturnTrueAfterTokenExpires(): void */ public function toStringMustReturnEncodedDataWithEmptySignature(): void { - $token = $this->createToken(null, null, Signature::fromEmptyData()); + $token = $this->createToken(null, null, new Signature('123', '456')); - self::assertSame('headers.claims.', $token->toString()); + self::assertSame('headers.claims.456', $token->toString()); } /** diff --git a/tests/Token/SignatureTest.php b/tests/Token/SignatureTest.php index d79ebcbf..4781a844 100644 --- a/tests/Token/SignatureTest.php +++ b/tests/Token/SignatureTest.php @@ -14,19 +14,6 @@ */ final class SignatureTest extends TestCase { - /** - * @test - * - * @covers ::fromEmptyData - */ - public function fromEmptyDataShouldReturnAnEmptySignature(): void - { - $signature = Signature::fromEmptyData(); - - self::assertSame('', $signature->hash()); - self::assertSame('', $signature->toString()); - } - /** @test */ public function hashShouldReturnTheHash(): void { diff --git a/tests/UnsignedTokenTest.php b/tests/UnsignedTokenTest.php index 1cf8e6c6..8f9c9146 100644 --- a/tests/UnsignedTokenTest.php +++ b/tests/UnsignedTokenTest.php @@ -6,6 +6,8 @@ use DateTimeImmutable; use Lcobucci\Clock\FrozenClock; use Lcobucci\JWT\Configuration; +use Lcobucci\JWT\KeyDumpSigner; +use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Token; use Lcobucci\JWT\Validation\Constraint; use Lcobucci\JWT\Validation\Constraint\IdentifiedBy; @@ -29,7 +31,6 @@ * @covers \Lcobucci\JWT\Token\Plain * @covers \Lcobucci\JWT\Token\DataSet * @covers \Lcobucci\JWT\Token\Signature - * @covers \Lcobucci\JWT\Signer\None * @covers \Lcobucci\JWT\Signer\Key\InMemory * @covers \Lcobucci\JWT\SodiumBase64Polyfill * @covers \Lcobucci\JWT\Validation\ConstraintViolation @@ -49,7 +50,10 @@ class UnsignedTokenTest extends TestCase /** @before */ public function createConfiguration(): void { - $this->config = Configuration::forUnsecuredSigner(); + $this->config = Configuration::forSymmetricSigner( + new KeyDumpSigner(), + InMemory::plainText('private'), + ); } /** @test */ @@ -67,7 +71,7 @@ public function builderCanGenerateAToken(): Token ->withClaim('user', $user) ->getToken($this->config->signer(), $this->config->signingKey()); - self::assertEquals(new Token\Signature('', ''), $token->signature()); + self::assertEquals(new Token\Signature('private', 'cHJpdmF0ZQ'), $token->signature()); self::assertEquals(['https://client.abc.com'], $token->claims()->get(Token\RegisteredClaims::AUDIENCE)); self::assertSame('https://api.abc.com', $token->claims()->get(Token\RegisteredClaims::ISSUER)); self::assertEquals($expiration, $token->claims()->get(Token\RegisteredClaims::EXPIRATION_TIME)); diff --git a/tests/Validation/Constraint/ConstraintTestCase.php b/tests/Validation/Constraint/ConstraintTestCase.php index c2f3dbfa..f7f27fc6 100644 --- a/tests/Validation/Constraint/ConstraintTestCase.php +++ b/tests/Validation/Constraint/ConstraintTestCase.php @@ -22,7 +22,7 @@ protected function buildToken( return new Plain( new DataSet($headers, ''), new DataSet($claims, ''), - $signature ?? Signature::fromEmptyData(), + $signature ?? new Signature('sig+hash', 'sig+encoded'), ); } } From 675f0470bd5a6206921291e4a9e37cb9d033645a Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Wed, 12 Oct 2022 11:21:08 +0200 Subject: [PATCH 4/9] Require signature to be non-empty-string, with assert() --- src/Token/Parser.php | 3 +++ tests/TimeFractionPrecisionTest.php | 2 +- tests/Token/ParserTest.php | 10 +++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Token/Parser.php b/src/Token/Parser.php index e4d268f4..1565a833 100644 --- a/src/Token/Parser.php +++ b/src/Token/Parser.php @@ -9,6 +9,7 @@ use Lcobucci\JWT\Token as TokenInterface; use function array_key_exists; +use function assert; use function count; use function explode; use function is_array; @@ -35,6 +36,8 @@ public function parse(string $jwt): TokenInterface throw InvalidTokenStructure::missingClaimsPart(); } + assert($encodedSignature !== ''); + $header = $this->parseHeader($encodedHeaders); return new Plain( diff --git a/tests/TimeFractionPrecisionTest.php b/tests/TimeFractionPrecisionTest.php index 01884d47..027a1b99 100644 --- a/tests/TimeFractionPrecisionTest.php +++ b/tests/TimeFractionPrecisionTest.php @@ -80,7 +80,7 @@ public function typeConversionDoesNotCauseParsingErrors(float|int|string $issued new KeyDumpSigner(), InMemory::plainText('private'), ); - $parsedToken = $config->parser()->parse($headers . '.' . $claims . '.'); + $parsedToken = $config->parser()->parse($headers . '.' . $claims . '.cHJpdmF0ZQ'); self::assertInstanceOf(Plain::class, $parsedToken); self::assertSame($timeFraction, $parsedToken->claims()->get('iat')->format('U.u')); diff --git a/tests/Token/ParserTest.php b/tests/Token/ParserTest.php index 04f2e78c..33215a6e 100644 --- a/tests/Token/ParserTest.php +++ b/tests/Token/ParserTest.php @@ -90,7 +90,7 @@ public function parseMustRaiseExceptionWhenHeaderCannotBeDecoded(): void $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Nope'); - $parser->parse('a.b.'); + $parser->parse('a.b.c'); } /** @test */ @@ -104,7 +104,7 @@ public function parseMustRaiseExceptionWhenDealingWithInvalidHeaders(): void $this->expectException(InvalidTokenStructure::class); $this->expectExceptionMessage('headers must be an array'); - $parser->parse('a.a.'); + $parser->parse('a.a.a'); } /** @test */ @@ -118,7 +118,7 @@ public function parseMustRaiseExceptionWhenHeaderIsFromAnEncryptedToken(): void $this->expectException(UnsupportedHeaderFound::class); $this->expectExceptionMessage('Encryption is not supported yet'); - $parser->parse('a.a.'); + $parser->parse('a.a.a'); } /** @test */ @@ -132,7 +132,7 @@ public function parseMustRaiseExceptionWhenDealingWithInvalidClaims(): void $this->expectException(InvalidTokenStructure::class); $this->expectExceptionMessage('claims must be an array'); - $parser->parse('a.a.'); + $parser->parse('a.a.a'); } /** @test */ @@ -453,6 +453,6 @@ public function parseShouldRaiseExceptionOnTimestampBeyondDateTimeImmutableRange ); $this->expectException(InvalidTokenStructure::class); - $this->createParser()->parse('a.b.'); + $this->createParser()->parse('a.b.c'); } } From 0c79509707c83038df60f49d7ea3524e31b57f91 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Wed, 12 Oct 2022 14:35:32 +0200 Subject: [PATCH 5/9] Expand `non-empty-string` tightening --- src/Signer.php | 3 +++ src/Signer/Ecdsa.php | 4 ---- src/Signer/Key.php | 1 + src/Signer/Key/InMemory.php | 2 ++ src/Token/InvalidTokenStructure.php | 5 +++++ src/Token/Parser.php | 5 +++-- src/Token/Signature.php | 7 +++++++ tests/Benchmark/SignerBench.php | 1 + tests/RFC6978VectorTest.php | 5 ++--- tests/Signer/Blake2bTest.php | 1 + tests/Signer/EddsaTest.php | 1 + tests/Token/ParserTest.php | 11 +++++++++++ 12 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/Signer.php b/src/Signer.php index 7e787d2e..a8efe296 100644 --- a/src/Signer.php +++ b/src/Signer.php @@ -22,6 +22,8 @@ public function algorithmId(): string; * * @param non-empty-string $payload * + * @return non-empty-string + * * @throws CannotSignPayload When payload signing fails. * @throws InvalidKeyProvided When issue key is invalid/incompatible. * @throws ConversionFailed When signature could not be converted. @@ -31,6 +33,7 @@ public function sign(string $payload, Key $key): string; /** * Returns if the expected hash matches with the data and key * + * @param non-empty-string $expected * @param non-empty-string $payload * * @throws InvalidKeyProvided When issue key is invalid/incompatible. diff --git a/src/Signer/Ecdsa.php b/src/Signer/Ecdsa.php index 57cea388..d0db425d 100644 --- a/src/Signer/Ecdsa.php +++ b/src/Signer/Ecdsa.php @@ -6,8 +6,6 @@ use Lcobucci\JWT\Signer\Ecdsa\MultibyteStringConverter; use Lcobucci\JWT\Signer\Ecdsa\SignatureConverter; -use function assert; - use const OPENSSL_KEYTYPE_EC; abstract class Ecdsa extends OpenSSL @@ -28,8 +26,6 @@ final public function sign(string $payload, Key $key): string final public function verify(string $expected, string $payload, Key $key): bool { - assert($expected !== ''); - return $this->verifySignature( $this->converter->toAsn1($expected, $this->pointLength()), $payload, diff --git a/src/Signer/Key.php b/src/Signer/Key.php index ea6b725a..c19a0ec8 100644 --- a/src/Signer/Key.php +++ b/src/Signer/Key.php @@ -5,6 +5,7 @@ interface Key { + /** @return non-empty-string */ public function contents(): string; public function passphrase(): string; diff --git a/src/Signer/Key/InMemory.php b/src/Signer/Key/InMemory.php index 5b2cce2d..c84a7678 100644 --- a/src/Signer/Key/InMemory.php +++ b/src/Signer/Key/InMemory.php @@ -14,6 +14,7 @@ final class InMemory implements Key { + /** @param non-empty-string $contents */ private function __construct(public readonly string $contents, public readonly string $passphrase) { } @@ -53,6 +54,7 @@ public static function file(string $path, string $passphrase = ''): self assert(is_string($contents)); self::guardAgainstEmptyKey($contents); + assert($contents !== ''); return new self($contents, $passphrase); } diff --git a/src/Token/InvalidTokenStructure.php b/src/Token/InvalidTokenStructure.php index 7abb6c3a..c62c6e01 100644 --- a/src/Token/InvalidTokenStructure.php +++ b/src/Token/InvalidTokenStructure.php @@ -23,6 +23,11 @@ public static function missingClaimsPart(): self return new self('The JWT string is missing the Claim part'); } + public static function missingSignaturePart(): self + { + return new self('The JWT string is missing the Signature part'); + } + public static function arrayExpected(string $part): self { return new self($part . ' must be an array'); diff --git a/src/Token/Parser.php b/src/Token/Parser.php index 1565a833..b2bab5a3 100644 --- a/src/Token/Parser.php +++ b/src/Token/Parser.php @@ -9,7 +9,6 @@ use Lcobucci\JWT\Token as TokenInterface; use function array_key_exists; -use function assert; use function count; use function explode; use function is_array; @@ -36,7 +35,9 @@ public function parse(string $jwt): TokenInterface throw InvalidTokenStructure::missingClaimsPart(); } - assert($encodedSignature !== ''); + if ($encodedSignature === '') { + throw InvalidTokenStructure::missingSignaturePart(); + } $header = $this->parseHeader($encodedHeaders); diff --git a/src/Token/Signature.php b/src/Token/Signature.php index ec491d29..5deca086 100644 --- a/src/Token/Signature.php +++ b/src/Token/Signature.php @@ -5,10 +5,15 @@ final class Signature { + /** + * @param non-empty-string $hash + * @param non-empty-string $encoded + */ public function __construct(private readonly string $hash, private readonly string $encoded) { } + /** @return non-empty-string */ public function hash(): string { return $this->hash; @@ -16,6 +21,8 @@ public function hash(): string /** * Returns the encoded version of the signature + * + * @return non-empty-string */ public function toString(): string { diff --git a/tests/Benchmark/SignerBench.php b/tests/Benchmark/SignerBench.php index 315e7301..a7e644bb 100644 --- a/tests/Benchmark/SignerBench.php +++ b/tests/Benchmark/SignerBench.php @@ -23,6 +23,7 @@ abstract class SignerBench private Signer $signer; private Key $signingKey; private Key $verificationKey; + /** @var non-empty-string */ private string $signature; final public function init(): void diff --git a/tests/RFC6978VectorTest.php b/tests/RFC6978VectorTest.php index 4b2e3009..c9063fe4 100644 --- a/tests/RFC6978VectorTest.php +++ b/tests/RFC6978VectorTest.php @@ -11,9 +11,7 @@ use Lcobucci\JWT\Signer\Key\InMemory; use PHPUnit\Framework\TestCase; -use function assert; use function hex2bin; -use function is_string; use const PHP_EOL; @@ -45,7 +43,8 @@ public function theVectorsFromRFC6978CanBeVerified( string $expectedS, ): void { $signature = hex2bin($expectedR . $expectedS); - assert(is_string($signature)); + self::assertIsString($signature); + self::assertNotSame('', $signature); static::assertTrue($signer->verify($signature, $payload, $key)); } diff --git a/tests/Signer/Blake2bTest.php b/tests/Signer/Blake2bTest.php index 81758923..9ba8da9a 100644 --- a/tests/Signer/Blake2bTest.php +++ b/tests/Signer/Blake2bTest.php @@ -28,6 +28,7 @@ final class Blake2bTest extends TestCase private InMemory $keyOne; private InMemory $keyTwo; + /** @var non-empty-string */ private string $expectedHashWithKeyOne; /** @before */ diff --git a/tests/Signer/EddsaTest.php b/tests/Signer/EddsaTest.php index a0bb846e..f17acf01 100644 --- a/tests/Signer/EddsaTest.php +++ b/tests/Signer/EddsaTest.php @@ -76,6 +76,7 @@ public function verifyShouldReturnTrueWhenSignatureIsValid(): void { $payload = 'testing'; $signature = sodium_crypto_sign_detached($payload, self::$eddsaKeys['private']->contents()); + self::assertNotSame('', $signature); $signer = new Eddsa(); diff --git a/tests/Token/ParserTest.php b/tests/Token/ParserTest.php index 33215a6e..6adc2676 100644 --- a/tests/Token/ParserTest.php +++ b/tests/Token/ParserTest.php @@ -74,6 +74,17 @@ public function parseMustRaiseExceptionWhenTokenDoesNotHaveClaims(): void $parser->parse('a..c'); } + /** @test */ + public function parseMustRaiseExceptionWhenTokenDoesNotHaveSignature(): void + { + $parser = $this->createParser(); + + $this->expectException(InvalidTokenStructure::class); + $this->expectExceptionMessage('The JWT string is missing the Signature part'); + + $parser->parse('a.b.'); + } + /** @test */ public function parseMustRaiseExceptionWhenHeaderCannotBeDecoded(): void { From 0775845d0821da8a16f706ed4f52249af9e854f9 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Mon, 7 Nov 2022 11:09:59 +0100 Subject: [PATCH 6/9] Move KeyDumpSigner to Tests\ namespace --- phpstan.neon.dist | 18 ------------------ src/Signer/Hmac.php | 4 ++-- tests/ConfigurationTest.php | 1 - tests/KeyDumpSigner.php | 3 ++- tests/TimeFractionPrecisionTest.php | 1 - tests/UnsignedTokenTest.php | 1 - 6 files changed, 4 insertions(+), 24 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index a3ed187b..278343b7 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -3,21 +3,3 @@ parameters: paths: - src - tests - - ignoreErrors: - - """ - #^Call to deprecated method fromEmptyData\\(\\) of class Lcobucci\\\\JWT\\\\Token\\\\Signature: - Deprecated since v4\\.3$# - """ - - """ - #^Call to deprecated method forUnsecuredSigner\\(\\) of class Lcobucci\\\\JWT\\\\Configuration: - Deprecated since v4\\.3$# - """ - - """ - #^Call to deprecated method empty\\(\\) of class Lcobucci\\\\JWT\\\\Signer\\\\Key\\\\InMemory: - Deprecated since v4\\.3$# - """ - - """ - #^.+ of deprecated class Lcobucci\\\\JWT\\\\Signer\\\\None: - Deprecated since v4\\.3$# - """ diff --git a/src/Signer/Hmac.php b/src/Signer/Hmac.php index bc595da4..815f84c3 100644 --- a/src/Signer/Hmac.php +++ b/src/Signer/Hmac.php @@ -30,8 +30,8 @@ final public function verify(string $expected, string $payload, Key $key): bool /** * @internal - * - * @return non-empty-string + * + * @return non-empty-string */ abstract public function algorithm(): string; diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index ba228fd2..8bf9f940 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -28,7 +28,6 @@ * @uses \Lcobucci\JWT\Encoding\MicrosecondBasedDateConversion * @uses \Lcobucci\JWT\Encoding\UnifyAudience * @uses \Lcobucci\JWT\Signer\Key\InMemory - * @uses \Lcobucci\JWT\Signer\None * @uses \Lcobucci\JWT\Token\Builder * @uses \Lcobucci\JWT\Token\Parser * @uses \Lcobucci\JWT\Validation\Validator diff --git a/tests/KeyDumpSigner.php b/tests/KeyDumpSigner.php index d5677804..7f94fda0 100644 --- a/tests/KeyDumpSigner.php +++ b/tests/KeyDumpSigner.php @@ -1,8 +1,9 @@ Date: Mon, 7 Nov 2022 12:19:09 +0100 Subject: [PATCH 7/9] Complete `non-empty-string` tightening --- src/Builder.php | 12 +++++++ src/ClaimsFormatter.php | 4 +-- src/Signer/Ecdsa.php | 9 ++++-- src/Signer/InvalidKeyProvided.php | 5 +++ src/Signer/Key/FileCouldNotBeRead.php | 1 + src/Signer/Key/InMemory.php | 8 +++-- src/Token.php | 8 +++++ src/Token/Builder.php | 7 ++-- src/Token/DataSet.php | 6 ++-- src/Token/InvalidTokenStructure.php | 3 +- src/Token/Parser.php | 23 +++++++++++-- src/Token/RegisteredClaimGiven.php | 1 + .../CannotValidateARegisteredClaim.php | 1 + .../Constraint/HasClaimWithValue.php | 1 + src/Validation/Constraint/IdentifiedBy.php | 1 + src/Validation/Constraint/IssuedBy.php | 3 +- src/Validation/Constraint/PermittedFor.php | 1 + src/Validation/Constraint/RelatedTo.php | 1 + src/Validation/ConstraintViolation.php | 1 + tests/HmacTokenTest.php | 4 +-- tests/JwtFacadeTest.php | 1 + tests/Token/ParserTest.php | 32 +++++++++++++++++-- .../Constraint/ConstraintTestCase.php | 4 +-- .../Constraint/HasClaimWithValueTest.php | 4 ++- 24 files changed, 119 insertions(+), 22 deletions(-) diff --git a/src/Builder.php b/src/Builder.php index 4650c0db..63cac11b 100644 --- a/src/Builder.php +++ b/src/Builder.php @@ -15,6 +15,8 @@ interface Builder { /** * Appends new items to audience + * + * @param non-empty-string ...$audiences */ public function permittedFor(string ...$audiences): Builder; @@ -25,6 +27,8 @@ public function expiresAt(DateTimeImmutable $expiration): Builder; /** * Configures the token id + * + * @param non-empty-string $id */ public function identifiedBy(string $id): Builder; @@ -35,6 +39,8 @@ public function issuedAt(DateTimeImmutable $issuedAt): Builder; /** * Configures the issuer + * + * @param non-empty-string $issuer */ public function issuedBy(string $issuer): Builder; @@ -45,17 +51,23 @@ public function canOnlyBeUsedAfter(DateTimeImmutable $notBefore): Builder; /** * Configures the subject + * + * @param non-empty-string $subject */ public function relatedTo(string $subject): Builder; /** * Configures a header item + * + * @param non-empty-string $name */ public function withHeader(string $name, mixed $value): Builder; /** * Configures a claim item * + * @param non-empty-string $name + * * @throws RegisteredClaimGiven When trying to set a registered claim. */ public function withClaim(string $name, mixed $value): Builder; diff --git a/src/ClaimsFormatter.php b/src/ClaimsFormatter.php index 75f2693d..e448ff25 100644 --- a/src/ClaimsFormatter.php +++ b/src/ClaimsFormatter.php @@ -6,9 +6,9 @@ interface ClaimsFormatter { /** - * @param array $claims + * @param array $claims * - * @return array + * @return array */ public function formatClaims(array $claims): array; } diff --git a/src/Signer/Ecdsa.php b/src/Signer/Ecdsa.php index d0db425d..3eda9342 100644 --- a/src/Signer/Ecdsa.php +++ b/src/Signer/Ecdsa.php @@ -15,7 +15,6 @@ public function __construct( ) { } - /** @return non-empty-string */ final public function sign(string $payload, Key $key): string { return $this->converter->fromAsn1( @@ -50,13 +49,19 @@ final protected function guardAgainstIncompatibleKey(int $type, int $lengthInBit } } - /** @internal */ + /** + * @internal + * + * @return positive-int + */ abstract public function expectedKeyLength(): int; /** * Returns the length of each point in the signature, so that we can calculate and verify R and S points properly * * @internal + * + * @return positive-int */ abstract public function pointLength(): int; } diff --git a/src/Signer/InvalidKeyProvided.php b/src/Signer/InvalidKeyProvided.php index efb219dd..97d02ccc 100644 --- a/src/Signer/InvalidKeyProvided.php +++ b/src/Signer/InvalidKeyProvided.php @@ -13,6 +13,10 @@ public static function cannotBeParsed(string $details): self return new self('It was not possible to parse your key, reason:' . $details); } + /** + * @param non-empty-string $expectedType + * @param non-empty-string $actualType + */ public static function incompatibleKeyType(string $expectedType, string $actualType): self { return new self( @@ -21,6 +25,7 @@ public static function incompatibleKeyType(string $expectedType, string $actualT ); } + /** @param positive-int $expectedLength */ public static function incompatibleKeyLength(int $expectedLength, int $actualLength): self { return new self( diff --git a/src/Signer/Key/FileCouldNotBeRead.php b/src/Signer/Key/FileCouldNotBeRead.php index aed80215..09dcf2e8 100644 --- a/src/Signer/Key/FileCouldNotBeRead.php +++ b/src/Signer/Key/FileCouldNotBeRead.php @@ -9,6 +9,7 @@ final class FileCouldNotBeRead extends InvalidArgumentException implements Exception { + /** @param non-empty-string $path */ public static function onPath(string $path, ?Throwable $cause = null): self { return new self( diff --git a/src/Signer/Key/InMemory.php b/src/Signer/Key/InMemory.php index c84a7678..f8fdedf2 100644 --- a/src/Signer/Key/InMemory.php +++ b/src/Signer/Key/InMemory.php @@ -40,7 +40,11 @@ public static function base64Encoded(string $contents, string $passphrase = ''): return new self($decoded, $passphrase); } - /** @throws FileCouldNotBeRead */ + /** + * @param non-empty-string $path + * + * @throws FileCouldNotBeRead + */ public static function file(string $path, string $passphrase = ''): self { try { @@ -54,11 +58,11 @@ public static function file(string $path, string $passphrase = ''): self assert(is_string($contents)); self::guardAgainstEmptyKey($contents); - assert($contents !== ''); return new self($contents, $passphrase); } + /** @phpstan-assert non-empty-string $contents */ private static function guardAgainstEmptyKey(string $contents): void { if ($contents === '') { diff --git a/src/Token.php b/src/Token.php index 8f85790f..340f0a3c 100644 --- a/src/Token.php +++ b/src/Token.php @@ -15,21 +15,29 @@ public function headers(): DataSet; /** * Returns if the token is allowed to be used by the audience + * + * @param non-empty-string $audience */ public function isPermittedFor(string $audience): bool; /** * Returns if the token has the given id + * + * @param non-empty-string $id */ public function isIdentifiedBy(string $id): bool; /** * Returns if the token has the given subject + * + * @param non-empty-string $subject */ public function isRelatedTo(string $subject): bool; /** * Returns if the token was issued by any of given issuers + * + * @param non-empty-string ...$issuers */ public function hasBeenIssuedBy(string ...$issuers): bool; diff --git a/src/Token/Builder.php b/src/Token/Builder.php index 0830791b..a83760d0 100644 --- a/src/Token/Builder.php +++ b/src/Token/Builder.php @@ -18,10 +18,10 @@ final class Builder implements BuilderInterface { - /** @var array */ + /** @var array */ private array $headers = ['typ' => 'JWT', 'alg' => null]; - /** @var array */ + /** @var array */ private array $claims = []; public function __construct(private readonly Encoder $encoder, private readonly ClaimsFormatter $claimFormatter) @@ -82,6 +82,7 @@ public function withClaim(string $name, mixed $value): BuilderInterface return $this->setClaim($name, $value); } + /** @param non-empty-string $name */ private function setClaim(string $name, mixed $value): BuilderInterface { $this->claims[$name] = $value; @@ -90,7 +91,7 @@ private function setClaim(string $name, mixed $value): BuilderInterface } /** - * @param array $items + * @param array $items * * @throws CannotEncodeContent When data cannot be converted to JSON. */ diff --git a/src/Token/DataSet.php b/src/Token/DataSet.php index 34424721..6c0b98ab 100644 --- a/src/Token/DataSet.php +++ b/src/Token/DataSet.php @@ -7,22 +7,24 @@ final class DataSet { - /** @param array $data */ + /** @param array $data */ public function __construct(private readonly array $data, private readonly string $encoded) { } + /** @param non-empty-string $name */ public function get(string $name, mixed $default = null): mixed { return $this->data[$name] ?? $default; } + /** @param non-empty-string $name */ public function has(string $name): bool { return array_key_exists($name, $this->data); } - /** @return array */ + /** @return array */ public function all(): array { return $this->data; diff --git a/src/Token/InvalidTokenStructure.php b/src/Token/InvalidTokenStructure.php index c62c6e01..abba344b 100644 --- a/src/Token/InvalidTokenStructure.php +++ b/src/Token/InvalidTokenStructure.php @@ -28,9 +28,10 @@ public static function missingSignaturePart(): self return new self('The JWT string is missing the Signature part'); } + /** @param non-empty-string $part */ public static function arrayExpected(string $part): self { - return new self($part . ' must be an array'); + return new self($part . ' must be an array with non-empty-string keys'); } public static function dateIsNotParseable(string $value): self diff --git a/src/Token/Parser.php b/src/Token/Parser.php index b2bab5a3..27db5404 100644 --- a/src/Token/Parser.php +++ b/src/Token/Parser.php @@ -73,7 +73,7 @@ private function splitJwt(string $jwt): array * * @param non-empty-string $data * - * @return mixed[] + * @return array * * @throws UnsupportedHeaderFound When an invalid header is informed. * @throws InvalidTokenStructure When parsed content isn't an array. @@ -86,6 +86,8 @@ private function parseHeader(string $data): array throw InvalidTokenStructure::arrayExpected('headers'); } + $this->guardAgainstEmptyStringKeys($header, 'headers'); + if (array_key_exists('enc', $header)) { throw UnsupportedHeaderFound::encryption(); } @@ -102,7 +104,7 @@ private function parseHeader(string $data): array * * @param non-empty-string $data * - * @return mixed[] + * @return array * * @throws InvalidTokenStructure When parsed content isn't an array or contains non-parseable dates. */ @@ -114,6 +116,8 @@ private function parseClaims(string $data): array throw InvalidTokenStructure::arrayExpected('claims'); } + $this->guardAgainstEmptyStringKeys($claims, 'claims'); + if (array_key_exists(RegisteredClaims::AUDIENCE, $claims)) { $claims[RegisteredClaims::AUDIENCE] = (array) $claims[RegisteredClaims::AUDIENCE]; } @@ -129,6 +133,21 @@ private function parseClaims(string $data): array return $claims; } + /** + * @param array $array + * @param non-empty-string $part + * + * @phpstan-assert array $array + */ + private function guardAgainstEmptyStringKeys(array $array, string $part): void + { + foreach ($array as $key => $value) { + if ($key === '') { + throw InvalidTokenStructure::arrayExpected($part); + } + } + } + /** @throws InvalidTokenStructure */ private function convertDate(int|float|string $timestamp): DateTimeImmutable { diff --git a/src/Token/RegisteredClaimGiven.php b/src/Token/RegisteredClaimGiven.php index e70996e1..ce40a6ab 100644 --- a/src/Token/RegisteredClaimGiven.php +++ b/src/Token/RegisteredClaimGiven.php @@ -13,6 +13,7 @@ final class RegisteredClaimGiven extends InvalidArgumentException implements Exc private const DEFAULT_MESSAGE = 'Builder#withClaim() is meant to be used for non-registered claims, ' . 'check the documentation on how to set claim "%s"'; + /** @param non-empty-string $name */ public static function forClaim(string $name): self { return new self(sprintf(self::DEFAULT_MESSAGE, $name)); diff --git a/src/Validation/Constraint/CannotValidateARegisteredClaim.php b/src/Validation/Constraint/CannotValidateARegisteredClaim.php index 0f7e028a..c60b03e4 100644 --- a/src/Validation/Constraint/CannotValidateARegisteredClaim.php +++ b/src/Validation/Constraint/CannotValidateARegisteredClaim.php @@ -8,6 +8,7 @@ final class CannotValidateARegisteredClaim extends InvalidArgumentException implements Exception { + /** @param non-empty-string $claim */ public static function create(string $claim): self { return new self( diff --git a/src/Validation/Constraint/HasClaimWithValue.php b/src/Validation/Constraint/HasClaimWithValue.php index 14f7e884..d3ba1d6e 100644 --- a/src/Validation/Constraint/HasClaimWithValue.php +++ b/src/Validation/Constraint/HasClaimWithValue.php @@ -12,6 +12,7 @@ final class HasClaimWithValue implements Constraint { + /** @param non-empty-string $claim */ public function __construct(private readonly string $claim, private readonly mixed $expectedValue) { if (in_array($claim, Token\RegisteredClaims::ALL, true)) { diff --git a/src/Validation/Constraint/IdentifiedBy.php b/src/Validation/Constraint/IdentifiedBy.php index c168098e..44541a75 100644 --- a/src/Validation/Constraint/IdentifiedBy.php +++ b/src/Validation/Constraint/IdentifiedBy.php @@ -9,6 +9,7 @@ final class IdentifiedBy implements Constraint { + /** @param non-empty-string $id */ public function __construct(private readonly string $id) { } diff --git a/src/Validation/Constraint/IssuedBy.php b/src/Validation/Constraint/IssuedBy.php index e2833f74..8ba3890d 100644 --- a/src/Validation/Constraint/IssuedBy.php +++ b/src/Validation/Constraint/IssuedBy.php @@ -9,9 +9,10 @@ final class IssuedBy implements Constraint { - /** @var string[] */ + /** @var non-empty-string[] */ private readonly array $issuers; + /** @param non-empty-string ...$issuers */ public function __construct(string ...$issuers) { $this->issuers = $issuers; diff --git a/src/Validation/Constraint/PermittedFor.php b/src/Validation/Constraint/PermittedFor.php index b72dc409..48544c9a 100644 --- a/src/Validation/Constraint/PermittedFor.php +++ b/src/Validation/Constraint/PermittedFor.php @@ -9,6 +9,7 @@ final class PermittedFor implements Constraint { + /** @param non-empty-string $audience */ public function __construct(private readonly string $audience) { } diff --git a/src/Validation/Constraint/RelatedTo.php b/src/Validation/Constraint/RelatedTo.php index ca07fadd..16493623 100644 --- a/src/Validation/Constraint/RelatedTo.php +++ b/src/Validation/Constraint/RelatedTo.php @@ -9,6 +9,7 @@ final class RelatedTo implements Constraint { + /** @param non-empty-string $subject */ public function __construct(private readonly string $subject) { } diff --git a/src/Validation/ConstraintViolation.php b/src/Validation/ConstraintViolation.php index f108fba2..17c75468 100644 --- a/src/Validation/ConstraintViolation.php +++ b/src/Validation/ConstraintViolation.php @@ -16,6 +16,7 @@ public function __construct( parent::__construct($message); } + /** @param non-empty-string $message */ public static function error(string $message, Constraint $constraint): self { return new self(message: $message, constraint: $constraint::class); diff --git a/tests/HmacTokenTest.php b/tests/HmacTokenTest.php index a029a6c5..e3503d77 100644 --- a/tests/HmacTokenTest.php +++ b/tests/HmacTokenTest.php @@ -15,7 +15,6 @@ use function assert; use function file_put_contents; -use function is_string; use function sys_get_temp_dir; use function tempnam; @@ -154,7 +153,8 @@ public function everythingShouldWorkWhenUsingATokenGeneratedByOtherLibs(): void public function signatureValidationWithLocalFileKeyReferenceWillOperateWithKeyContents(): void { $key = tempnam(sys_get_temp_dir(), 'a-very-long-prefix-to-create-a-longer-key'); - assert(is_string($key)); + self::assertIsString($key); + self::assertNotSame('', $key); file_put_contents( $key, diff --git a/tests/JwtFacadeTest.php b/tests/JwtFacadeTest.php index 591443d3..3255b25e 100644 --- a/tests/JwtFacadeTest.php +++ b/tests/JwtFacadeTest.php @@ -48,6 +48,7 @@ final class JwtFacadeTest extends TestCase private FrozenClock $clock; private Sha256 $signer; private InMemory $key; + /** @var non-empty-string */ private string $issuer; protected function setUp(): void diff --git a/tests/Token/ParserTest.php b/tests/Token/ParserTest.php index 6adc2676..93ea0376 100644 --- a/tests/Token/ParserTest.php +++ b/tests/Token/ParserTest.php @@ -105,7 +105,7 @@ public function parseMustRaiseExceptionWhenHeaderCannotBeDecoded(): void } /** @test */ - public function parseMustRaiseExceptionWhenDealingWithInvalidHeaders(): void + public function parseMustRaiseExceptionWhenDealingWithNonArrayHeaders(): void { $this->decoder->method('jsonDecode') ->willReturn('A very invalid header'); @@ -118,6 +118,20 @@ public function parseMustRaiseExceptionWhenDealingWithInvalidHeaders(): void $parser->parse('a.a.a'); } + /** @test */ + public function parseMustRaiseExceptionWhenDealingWithHeadersThatHaveEmptyStringKeys(): void + { + $this->decoder->method('jsonDecode') + ->willReturn(['' => 'foo']); + + $parser = $this->createParser(); + + $this->expectException(InvalidTokenStructure::class); + $this->expectExceptionMessage('headers must be an array'); + + $parser->parse('a.a.a'); + } + /** @test */ public function parseMustRaiseExceptionWhenHeaderIsFromAnEncryptedToken(): void { @@ -133,7 +147,7 @@ public function parseMustRaiseExceptionWhenHeaderIsFromAnEncryptedToken(): void } /** @test */ - public function parseMustRaiseExceptionWhenDealingWithInvalidClaims(): void + public function parseMustRaiseExceptionWhenDealingWithNonArrayClaims(): void { $this->decoder->method('jsonDecode') ->willReturnOnConsecutiveCalls(['typ' => 'JWT'], 'A very invalid claim set'); @@ -146,6 +160,20 @@ public function parseMustRaiseExceptionWhenDealingWithInvalidClaims(): void $parser->parse('a.a.a'); } + /** @test */ + public function parseMustRaiseExceptionWhenDealingWithClaimsThatHaveEmptyStringKeys(): void + { + $this->decoder->method('jsonDecode') + ->willReturnOnConsecutiveCalls(['typ' => 'JWT'], ['' => 'foo']); + + $parser = $this->createParser(); + + $this->expectException(InvalidTokenStructure::class); + $this->expectExceptionMessage('claims must be an array'); + + $parser->parse('a.a.a'); + } + /** @test */ public function parseMustReturnAnUnsecuredTokenWhenSignatureIsNotInformed(): void { diff --git a/tests/Validation/Constraint/ConstraintTestCase.php b/tests/Validation/Constraint/ConstraintTestCase.php index f7f27fc6..07860dda 100644 --- a/tests/Validation/Constraint/ConstraintTestCase.php +++ b/tests/Validation/Constraint/ConstraintTestCase.php @@ -11,8 +11,8 @@ abstract class ConstraintTestCase extends TestCase { /** - * @param mixed[] $claims - * @param mixed[] $headers + * @param array $claims + * @param array $headers */ protected function buildToken( array $claims = [], diff --git a/tests/Validation/Constraint/HasClaimWithValueTest.php b/tests/Validation/Constraint/HasClaimWithValueTest.php index 7def728b..3c083316 100644 --- a/tests/Validation/Constraint/HasClaimWithValueTest.php +++ b/tests/Validation/Constraint/HasClaimWithValueTest.php @@ -23,6 +23,8 @@ final class HasClaimWithValueTest extends ConstraintTestCase * @dataProvider registeredClaims * * @covers \Lcobucci\JWT\Validation\Constraint\CannotValidateARegisteredClaim + * + * @param non-empty-string $claim */ public function registeredClaimsCannotBeValidatedUsingThisConstraint(string $claim): void { @@ -34,7 +36,7 @@ public function registeredClaimsCannotBeValidatedUsingThisConstraint(string $cla new HasClaimWithValue($claim, 'testing'); } - /** @return iterable */ + /** @return iterable */ public function registeredClaims(): iterable { foreach (Token\RegisteredClaims::ALL as $claim) { From dfb0e6bed5aaa3dfc123a3773d202e402d9b739e Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Mon, 7 Nov 2022 12:19:57 +0100 Subject: [PATCH 8/9] Stick to phpunit/php-code-coverage:9.2.17 until coverage bugs are fixed --- composer.json | 1 + composer.lock | 60 +++++++++++++++++++++++++-------------------------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/composer.json b/composer.json index e1a96128..754ecfb5 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.2", "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/php-code-coverage": "9.2.17", "phpunit/phpunit": "^9.5" }, "autoload": { diff --git a/composer.lock b/composer.lock index 741d781a..faef11db 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c62d8e77ab2d18ae47c3916909cf1fd6", + "content-hash": "d6c4b37d715f1faf15addcff56eb32fa", "packages": [ { "name": "lcobucci/clock", @@ -209,16 +209,16 @@ }, { "name": "composer/pcre", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd" + "reference": "4482b6409ca6bfc2af043a5711cd21ac3e7a8dfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd", - "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "url": "https://api.github.com/repos/composer/pcre/zipball/4482b6409ca6bfc2af043a5711cd21ac3e7a8dfb", + "reference": "4482b6409ca6bfc2af043a5711cd21ac3e7a8dfb", "shasum": "" }, "require": { @@ -260,7 +260,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.0.0" + "source": "https://github.com/composer/pcre/tree/3.0.2" }, "funding": [ { @@ -276,7 +276,7 @@ "type": "tidelift" } ], - "time": "2022-02-25T20:21:48+00:00" + "time": "2022-11-03T20:24:16+00:00" }, { "name": "composer/xdebug-handler", @@ -1702,16 +1702,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.8.11", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "46e223dd68a620da18855c23046ddb00940b4014" + "reference": "a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014", - "reference": "46e223dd68a620da18855c23046ddb00940b4014", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f", + "reference": "a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f", "shasum": "" }, "require": { @@ -1741,7 +1741,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.11" + "source": "https://github.com/phpstan/phpstan/tree/1.9.1" }, "funding": [ { @@ -1757,7 +1757,7 @@ "type": "tidelift" } ], - "time": "2022-10-24T15:45:13+00:00" + "time": "2022-11-04T13:35:59+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2229,16 +2229,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.25", + "version": "9.5.26", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" + "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2", "shasum": "" }, "require": { @@ -2311,7 +2311,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26" }, "funding": [ { @@ -2327,7 +2327,7 @@ "type": "tidelift" } ], - "time": "2022-09-25T03:44:45+00:00" + "time": "2022-10-28T06:00:21+00:00" }, { "name": "psr/cache", @@ -3755,16 +3755,16 @@ }, { "name": "symfony/console", - "version": "v6.1.6", + "version": "v6.1.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "7fa3b9cf17363468795e539231a5c91b02b608fc" + "reference": "a1282bd0c096e0bdb8800b104177e2ce404d8815" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/7fa3b9cf17363468795e539231a5c91b02b608fc", - "reference": "7fa3b9cf17363468795e539231a5c91b02b608fc", + "url": "https://api.github.com/repos/symfony/console/zipball/a1282bd0c096e0bdb8800b104177e2ce404d8815", + "reference": "a1282bd0c096e0bdb8800b104177e2ce404d8815", "shasum": "" }, "require": { @@ -3831,7 +3831,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.1.6" + "source": "https://github.com/symfony/console/tree/v6.1.7" }, "funding": [ { @@ -3847,7 +3847,7 @@ "type": "tidelift" } ], - "time": "2022-10-07T08:04:03+00:00" + "time": "2022-10-26T21:42:49+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4588,16 +4588,16 @@ }, { "name": "symfony/string", - "version": "v6.1.6", + "version": "v6.1.7", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864" + "reference": "823f143370880efcbdfa2dbca946b3358c4707e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/7e7e0ff180d4c5a6636eaad57b65092014b61864", - "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864", + "url": "https://api.github.com/repos/symfony/string/zipball/823f143370880efcbdfa2dbca946b3358c4707e5", + "reference": "823f143370880efcbdfa2dbca946b3358c4707e5", "shasum": "" }, "require": { @@ -4653,7 +4653,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.6" + "source": "https://github.com/symfony/string/tree/v6.1.7" }, "funding": [ { From aaccee1718ab72f09bc686065079f8d939339869 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Mon, 7 Nov 2022 12:41:58 +0100 Subject: [PATCH 9/9] Document why `none` is not supported --- docs/supported-algorithms.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/supported-algorithms.md b/docs/supported-algorithms.md index 7143b498..0115e644 100644 --- a/docs/supported-algorithms.md +++ b/docs/supported-algorithms.md @@ -36,4 +36,10 @@ They're usually recommended for scenarios where creation is handled by a compone | `RS512` | RSASSA-PKCS1-v1_5 using SHA-512 | `\Lcobucci\JWT\Signer\Rsa\Sha512` | `>= 2048 bits` | | `EdDSA` | EdDSA signature algorithms | `\Lcobucci\JWT\Signer\Eddsa` | `>= 256 bits` | +## `none` algorithm + +The `none` algorithm as described by [JWT standard] is intentionally not implemented and not supported. +The risk of misusing it is too high, and even where other means guarantee the token validity a symmetric algorithm +shouldn't represent a computational bottleneck with modern hardware. + [JWT standard]: https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms