diff --git a/composer.json b/composer.json index 57340401c11f7104fedf80d9bc01288825df4da6..37359240fb28051b3f1f347b4d92923bfbe6aa10 100644 --- a/composer.json +++ b/composer.json @@ -5,16 +5,16 @@ "license": "AGPL-3.0-or-later", "require": { "php": ">=7.3", - "ext-json": "*", - "ext-gmp": "*", "ext-curl": "*", + "ext-gmp": "*", + "ext-json": "*", "dbp/relay-core-bundle": "^0.1.10", "guzzlehttp/guzzle": "^7.0", "kevinrob/guzzle-cache-middleware": "^3.3 | ^4.0", "symfony/framework-bundle": "^5.2", "symfony/security-core": "^5.2", "symfony/yaml": "^5.2", - "web-token/jwt-easy": "^2.1", + "web-token/jwt-checker": "^2.0", "web-token/jwt-signature-algorithm-rsa": "^2.1" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 5121947131418444c6fb8b421967ca98aac21533..260681ecef3ff6742fe91cd35976235be1a63e59 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": "bfcc47e656a04623e54ce0068072dce7", + "content-hash": "a19d06896f68569849e63859ca44f78f", "packages": [ { "name": "api-platform/core", @@ -6446,170 +6446,6 @@ ], "time": "2021-03-17T14:55:52+00:00" }, - { - "name": "web-token/jwt-easy", - "version": "v2.2.11", - "source": { - "type": "git", - "url": "https://github.com/web-token/jwt-easy.git", - "reference": "01db23252bb53d4fd36975b55dd58466bab1bb30" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-easy/zipball/01db23252bb53d4fd36975b55dd58466bab1bb30", - "reference": "01db23252bb53d4fd36975b55dd58466bab1bb30", - "shasum": "" - }, - "require": { - "web-token/jwt-checker": "^2.1", - "web-token/jwt-encryption": "^2.1", - "web-token/jwt-signature": "^2.1" - }, - "suggest": { - "web-token/jwt-encryption-algorithm-aescbc": "Adds AES-CBC based encryption algorithms", - "web-token/jwt-encryption-algorithm-aesgcm": "Adds AES-GCM based encryption algorithms", - "web-token/jwt-encryption-algorithm-aesgcmkw": "Adds AES-GCM Key Wrapping based encryption algorithms", - "web-token/jwt-encryption-algorithm-aeskw": "Adds AES Key Wrapping based encryption algorithms", - "web-token/jwt-encryption-algorithm-dir": "Adds Direct encryption algorithm", - "web-token/jwt-encryption-algorithm-ecdh-es": "Adds ECDH-ES based encryption algorithms", - "web-token/jwt-encryption-algorithm-pbes2": "Adds PBES2 based encryption algorithms", - "web-token/jwt-encryption-algorithm-rsa": "Adds RSA based encryption algorithms", - "web-token/jwt-signature-algorithm-ecdsa": "Adds ECDSA based signature algorithms", - "web-token/jwt-signature-algorithm-eddsa": "Adds EdDSA based signature algorithms", - "web-token/jwt-signature-algorithm-hmac": "Adds HMAC based signature algorithms", - "web-token/jwt-signature-algorithm-none": "Adds none signature algorithms", - "web-token/jwt-signature-algorithm-rsa": "Adds RSA based signature algorithms" - }, - "type": "library", - "autoload": { - "psr-4": { - "Jose\\Easy\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky" - }, - { - "name": "All contributors", - "homepage": "https://github.com/web-token/jwt-framework/contributors" - } - ], - "description": "Easy toolset to use the JWT Framework.", - "homepage": "https://github.com/web-token", - "keywords": [ - "JOSE", - "JWE", - "JWK", - "JWKSet", - "JWS", - "Jot", - "RFC7515", - "RFC7516", - "RFC7517", - "RFC7518", - "RFC7519", - "RFC7520", - "bundle", - "jwa", - "jwt", - "symfony" - ], - "support": { - "source": "https://github.com/web-token/jwt-easy/tree/v2.2.11" - }, - "funding": [ - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2021-03-17T14:55:52+00:00" - }, - { - "name": "web-token/jwt-encryption", - "version": "v2.2.11", - "source": { - "type": "git", - "url": "https://github.com/web-token/jwt-encryption.git", - "reference": "3b8d67d7c5c013750703e7c27f1001544407bbb2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-encryption/zipball/3b8d67d7c5c013750703e7c27f1001544407bbb2", - "reference": "3b8d67d7c5c013750703e7c27f1001544407bbb2", - "shasum": "" - }, - "require": { - "web-token/jwt-core": "^2.1" - }, - "suggest": { - "web-token/jwt-encryption-algorithm-aescbc": "AES CBC Based Content Encryption Algorithms", - "web-token/jwt-encryption-algorithm-aesgcm": "AES GCM Based Content Encryption Algorithms", - "web-token/jwt-encryption-algorithm-aesgcmkw": "AES GCM Key Wrapping Based Key Encryption Algorithms", - "web-token/jwt-encryption-algorithm-aeskw": "AES Key Wrapping Based Key Encryption Algorithms", - "web-token/jwt-encryption-algorithm-dir": "Direct Key Encryption Algorithms", - "web-token/jwt-encryption-algorithm-ecdh-es": "ECDH-ES Based Key Encryption Algorithms", - "web-token/jwt-encryption-algorithm-experimental": "Experimental Key and Signature Algorithms", - "web-token/jwt-encryption-algorithm-pbes2": "PBES2 Based Key Encryption Algorithms", - "web-token/jwt-encryption-algorithm-rsa": "RSA Based Key Encryption Algorithms" - }, - "type": "library", - "autoload": { - "psr-4": { - "Jose\\Component\\Encryption\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky" - }, - { - "name": "All contributors", - "homepage": "https://github.com/web-token/jwt-encryption/contributors" - } - ], - "description": "Encryption component of the JWT Framework.", - "homepage": "https://github.com/web-token", - "keywords": [ - "JOSE", - "JWE", - "JWK", - "JWKSet", - "JWS", - "Jot", - "RFC7515", - "RFC7516", - "RFC7517", - "RFC7518", - "RFC7519", - "RFC7520", - "bundle", - "jwa", - "jwt", - "symfony" - ], - "support": { - "source": "https://github.com/web-token/jwt-encryption/tree/v2.2.11" - }, - "funding": [ - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2021-03-17T14:55:52+00:00" - }, { "name": "web-token/jwt-signature", "version": "v2.2.11", @@ -10553,9 +10389,9 @@ "prefer-lowest": false, "platform": { "php": ">=7.3", - "ext-json": "*", + "ext-curl": "*", "ext-gmp": "*", - "ext-curl": "*" + "ext-json": "*" }, "platform-dev": [], "platform-overrides": { diff --git a/src/Authenticator/LocalTokenValidator.php b/src/Authenticator/LocalTokenValidator.php index d6625b52e422fb33e0aea97726540c6ac83c3e84..9cacb6659e5738a0bc2b4c109336bc4cd79f64d8 100644 --- a/src/Authenticator/LocalTokenValidator.php +++ b/src/Authenticator/LocalTokenValidator.php @@ -6,9 +6,18 @@ namespace Dbp\Relay\AuthBundle\Authenticator; use Dbp\Relay\AuthBundle\OIDC\OIDError; use Dbp\Relay\AuthBundle\OIDC\OIDProvider; +use Jose\Component\Checker; +use Jose\Component\Checker\AlgorithmChecker; +use Jose\Component\Checker\ClaimCheckerManager; +use Jose\Component\Checker\HeaderCheckerManager; +use Jose\Component\Core\AlgorithmManager; use Jose\Component\Core\JWKSet; -use Jose\Easy\Load; -use Jose\Easy\Validate; +use Jose\Component\Signature\Algorithm; +use Jose\Component\Signature\JWSLoader; +use Jose\Component\Signature\JWSTokenSupport; +use Jose\Component\Signature\JWSVerifier; +use Jose\Component\Signature\Serializer\CompactSerializer; +use Jose\Component\Signature\Serializer\JWSSerializerManager; class LocalTokenValidator extends TokenValidatorBase { @@ -46,28 +55,55 @@ class LocalTokenValidator extends TokenValidatorBase // The spec doesn't allow this, but just to be sure assert(!in_array('none', $algs, true)); - // Checks not needed/used here: - // * sub(): This is the keycloak user ID by default, nothing we know beforehand - // * jti(): Nothing we know beforehand - // * aud(): The audience needs to be checked afterwards with checkAudience() + $keySet = JWKSet::createFromKeyData($jwks); + + $serializerManager = new JWSSerializerManager([ + new CompactSerializer(), + ]); + + $algorithmManager = new AlgorithmManager([ + new Algorithm\RS256(), + new Algorithm\RS384(), + new Algorithm\RS512(), + new Algorithm\PS256(), + new Algorithm\PS384(), + new Algorithm\PS512(), + ]); + + $jwsVerifier = new JWSVerifier( + $algorithmManager + ); + + $headerCheckerManager = new HeaderCheckerManager( + [new AlgorithmChecker($algs, true)], + [new JWSTokenSupport()], + ); + + $jwsLoader = new JWSLoader( + $serializerManager, + $jwsVerifier, + $headerCheckerManager + ); + try { - $keySet = JWKSet::createFromKeyData($jwks); - $validate = Load::jws($accessToken); - $validate = $validate - ->algs($algs) - ->keyset($keySet) - ->exp($this->leewaySeconds) - ->iat($this->leewaySeconds) - ->nbf($this->leewaySeconds) - ->iss($issuer); - assert($validate instanceof Validate); - $jwtResult = $validate->run(); + $jws = $jwsLoader->loadAndVerifyWithKeySet($accessToken, $keySet, $signature); + $jwt = json_decode($jws->getPayload(), true, 512, JSON_THROW_ON_ERROR); + + // Checks not needed/used here: + // * sub: This is the keycloak user ID by default, nothing we know beforehand + // * jti: Nothing we know beforehand + // * aud: The audience needs to be checked afterwards with checkAudience() + $claimCheckerManager = new ClaimCheckerManager([ + new Checker\IssuedAtChecker($this->leewaySeconds), + new Checker\NotBeforeChecker($this->leewaySeconds), + new Checker\ExpirationTimeChecker($this->leewaySeconds), + new Checker\IssuerChecker([$issuer]), + ]); + $claimCheckerManager->check($jwt); } catch (\Exception $e) { throw new TokenValidationException('Token validation failed: '.$e->getMessage()); } - $jwt = $jwtResult->claims->all(); - // XXX: Keycloak will add extra data to the token returned by introspection, mirror this behaviour here // to avoid breakage when switching between local/remote validation. // https://github.com/keycloak/keycloak/blob/8225157a1cecef30034530aa/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java#L59 diff --git a/tests/Authenticator/LocalTokenValidatorTest.php b/tests/Authenticator/LocalTokenValidatorTest.php index 980e4623fbd369e2d20715d30cd638f9a0c57cb5..e28a5812880456364bb8050c6867d10baaacf887 100644 --- a/tests/Authenticator/LocalTokenValidatorTest.php +++ b/tests/Authenticator/LocalTokenValidatorTest.php @@ -152,7 +152,7 @@ class LocalTokenValidatorTest extends TestCase $jwt = $this->getJWT(); $payload = explode('.', $jwt)[1]; $noneToken = base64_encode('{"alg":"none","typ":"JWT"}').'.'.$payload.'.'; - $this->expectExceptionMessageMatches('/Unsupported algorithm/'); + $this->expectExceptionMessageMatches('/Unable to load and verify the token/'); $this->tokenValidator->validate($noneToken); } @@ -190,7 +190,7 @@ class LocalTokenValidatorTest extends TestCase $parts = explode('.', $jwt); $parts[1] = 'REVBREJFRUY='; - $this->expectExceptionMessageMatches('/Invalid signature/'); + $this->expectExceptionMessageMatches('/Unable to load and verify the token/'); $this->tokenValidator->validate(implode('.', $parts)); }