From 0eee161630707e6f6b219bdee9ef87375d825dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Thu, 14 Aug 2025 18:46:10 +0200 Subject: [PATCH] Use new webauthn library for 2FA --- composer.json | 4 +- composer.lock | 217 +++++------------- config/packages/security.yaml | 2 +- migrations/Version20250813214628.php | 75 ++++++ src/Entity/UserSystem/WebauthnKey.php | 23 +- .../WebauthnKeyLastUseTwoFactorProvider.php | 9 +- 6 files changed, 158 insertions(+), 172 deletions(-) create mode 100644 migrations/Version20250813214628.php diff --git a/composer.json b/composer.json index 37edc870..27a8aaa9 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "florianv/swap-bundle": "dev-master", "gregwar/captcha-bundle": "^2.1.0", "hshn/base64-encoded-file": "^5.0", - "jbtronics/2fa-webauthn": "^v2.2.0", + "jbtronics/2fa-webauthn": "^3.0.0", "jbtronics/dompdf-font-loader-bundle": "^1.0.0", "jbtronics/settings-bundle": "^v2.6.0", "jfcherng/php-diff": "^6.14", @@ -95,7 +95,7 @@ "twig/intl-extra": "^3.8", "twig/markdown-extra": "^3.8", "twig/string-extra": "^3.8", - "web-auth/webauthn-symfony-bundle": "^4.0.0" + "web-auth/webauthn-symfony-bundle": "^5.0.0" }, "require-dev": { "dama/doctrine-test-bundle": "^v8.0.0", diff --git a/composer.lock b/composer.lock index f04fbece..57927786 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": "6d897d6a314105a9654fb223b93bff2e", + "content-hash": "dbbcf3d9100362f05816908abb4d95dc", "packages": [ { "name": "amphp/amp", @@ -3499,16 +3499,16 @@ }, { "name": "doctrine/migrations", - "version": "3.9.2", + "version": "3.9.3", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "fa94c6f06b1bc6d4759481ec20b8b81d13e861be" + "reference": "cd12028853c418b454602e3fda89e519e9af947b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/fa94c6f06b1bc6d4759481ec20b8b81d13e861be", - "reference": "fa94c6f06b1bc6d4759481ec20b8b81d13e861be", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/cd12028853c418b454602e3fda89e519e9af947b", + "reference": "cd12028853c418b454602e3fda89e519e9af947b", "shasum": "" }, "require": { @@ -3582,7 +3582,7 @@ ], "support": { "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/3.9.2" + "source": "https://github.com/doctrine/migrations/tree/3.9.3" }, "funding": [ { @@ -3598,7 +3598,7 @@ "type": "tidelift" } ], - "time": "2025-07-29T11:36:14+00:00" + "time": "2025-08-13T22:04:47+00:00" }, { "name": "doctrine/orm", @@ -4955,28 +4955,28 @@ }, { "name": "jbtronics/2fa-webauthn", - "version": "v2.2.3", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/jbtronics/2fa-webauthn.git", - "reference": "fda6f39e70784cbf1f93cf758bf798563219d451" + "reference": "542424bcc51f06932cbecfd7b75afbb5e107c9ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jbtronics/2fa-webauthn/zipball/fda6f39e70784cbf1f93cf758bf798563219d451", - "reference": "fda6f39e70784cbf1f93cf758bf798563219d451", + "url": "https://api.github.com/repos/jbtronics/2fa-webauthn/zipball/542424bcc51f06932cbecfd7b75afbb5e107c9ce", + "reference": "542424bcc51f06932cbecfd7b75afbb5e107c9ce", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7": "^1.5", - "php": "^8.1", + "php": "^8.2", "psr/log": "^3.0.0|^2.0.0", "scheb/2fa-bundle": "^6.0.0|^7.0.0", "symfony/framework-bundle": "^6.0|^7.0", "symfony/psr-http-message-bridge": "^2.1|^6.1|^7.0", "symfony/uid": "^6.0|^7.0", - "web-auth/webauthn-lib": "^4.7" + "web-auth/webauthn-lib": "^5.2" }, "require-dev": { "phpunit/phpunit": "^9.5", @@ -4998,7 +4998,7 @@ "email": "mail@jan-boehmer.de" } ], - "description": "Webauthn Two-Factor-Authentictication Plugin for scheb/2fa", + "description": "Webauthn Two-Factor-Authentication Plugin for scheb/2fa", "keywords": [ "2fa", "scheb-2fa", @@ -5009,9 +5009,9 @@ ], "support": { "issues": "https://github.com/jbtronics/2fa-webauthn/issues", - "source": "https://github.com/jbtronics/2fa-webauthn/tree/v2.2.3" + "source": "https://github.com/jbtronics/2fa-webauthn/tree/v3.0.0" }, - "time": "2025-03-27T19:23:40+00:00" + "time": "2025-08-14T15:12:48+00:00" }, { "name": "jbtronics/dompdf-font-loader-bundle", @@ -16384,44 +16384,40 @@ }, { "name": "web-auth/webauthn-lib", - "version": "4.9.2", + "version": "5.2.2", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "008b25171c27cf4813420d0de31cc059bcc71f1a" + "reference": "8937c397c8ae91b5af422ca8aa915c756062da74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/008b25171c27cf4813420d0de31cc059bcc71f1a", - "reference": "008b25171c27cf4813420d0de31cc059bcc71f1a", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/8937c397c8ae91b5af422ca8aa915c756062da74", + "reference": "8937c397c8ae91b5af422ca8aa915c756062da74", "shasum": "" }, "require": { "ext-json": "*", - "ext-mbstring": "*", "ext-openssl": "*", - "lcobucci/clock": "^2.2|^3.0", "paragonie/constant_time_encoding": "^2.6|^3.0", - "php": ">=8.1", + "php": ">=8.2", + "phpdocumentor/reflection-docblock": "^5.3", "psr/clock": "^1.0", "psr/event-dispatcher": "^1.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", "psr/log": "^1.0|^2.0|^3.0", "spomky-labs/cbor-php": "^3.0", "spomky-labs/pki-framework": "^1.0", + "symfony/clock": "^6.4|^7.0", "symfony/deprecation-contracts": "^3.2", - "symfony/uid": "^6.1|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", "web-auth/cose-lib": "^4.2.3" }, "suggest": { - "phpdocumentor/reflection-docblock": "As of 4.5.x, the phpdocumentor/reflection-docblock component will become mandatory for converting objects such as the Metadata Statement", - "psr/clock-implementation": "As of 4.5.x, the PSR Clock implementation will replace lcobucci/clock", "psr/log-implementation": "Recommended to receive logs from the library", "symfony/event-dispatcher": "Recommended to use dispatched events", - "symfony/property-access": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", - "symfony/property-info": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", - "symfony/serializer": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", "web-token/jwt-library": "Mandatory for fetching Metadata Statement from distant sources" }, "type": "library", @@ -16458,7 +16454,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/4.9.2" + "source": "https://github.com/web-auth/webauthn-lib/tree/5.2.2" }, "funding": [ { @@ -16470,41 +16466,38 @@ "type": "patreon" } ], - "time": "2025-01-04T09:47:58+00:00" + "time": "2025-03-16T14:38:43+00:00" }, { "name": "web-auth/webauthn-symfony-bundle", - "version": "4.9.2", + "version": "5.2.2", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-symfony-bundle.git", - "reference": "80aa16fa6f16ab8f017a4108ffcd2ecc12264c07" + "reference": "aebb0315b43728a92973cc3d4d471cbe414baa54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/80aa16fa6f16ab8f017a4108ffcd2ecc12264c07", - "reference": "80aa16fa6f16ab8f017a4108ffcd2ecc12264c07", + "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/aebb0315b43728a92973cc3d4d471cbe414baa54", + "reference": "aebb0315b43728a92973cc3d4d471cbe414baa54", "shasum": "" }, "require": { - "nyholm/psr7": "^1.5", - "php": ">=8.1", - "phpdocumentor/reflection-docblock": "^5.3", + "php": ">=8.2", "psr/event-dispatcher": "^1.0", - "symfony/config": "^6.1|^7.0", - "symfony/dependency-injection": "^6.1|^7.0", - "symfony/framework-bundle": "^6.1|^7.0", - "symfony/http-client": "^6.1|^7.0", - "symfony/property-access": "^6.1|^7.0", - "symfony/property-info": "^6.1|^7.0", - "symfony/psr-http-message-bridge": "^2.1|^6.1|^7.0", - "symfony/security-bundle": "^6.1|^7.0", - "symfony/security-core": "^6.1|^7.0", - "symfony/security-http": "^6.1|^7.0", - "symfony/serializer": "^6.1|^7.0", - "symfony/validator": "^6.1|^7.0", - "web-auth/webauthn-lib": "self.version", - "web-token/jwt-library": "^3.3|^4.0" + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "web-auth/webauthn-lib": "self.version" + }, + "suggest": { + "symfony/security-bundle": "Symfony firewall using a JSON API (perfect for script applications)" }, "type": "symfony-bundle", "extra": { @@ -16543,7 +16536,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.9.2" + "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/5.2.2" }, "funding": [ { @@ -16555,96 +16548,7 @@ "type": "patreon" } ], - "time": "2025-01-04T09:38:56+00:00" - }, - { - "name": "web-token/jwt-library", - "version": "4.0.5", - "source": { - "type": "git", - "url": "https://github.com/web-token/jwt-library.git", - "reference": "9b43aaef764cb134390e7daff045abedb9a1e81b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-library/zipball/9b43aaef764cb134390e7daff045abedb9a1e81b", - "reference": "9b43aaef764cb134390e7daff045abedb9a1e81b", - "shasum": "" - }, - "require": { - "brick/math": "^0.12|^0.13", - "php": ">=8.2", - "psr/clock": "^1.0", - "spomky-labs/pki-framework": "^1.2.1" - }, - "conflict": { - "spomky-labs/jose": "*" - }, - "suggest": { - "ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance", - "ext-gmp": "GMP or BCMath is highly recommended to improve the library performance", - "ext-openssl": "For key management (creation, optimization, etc.) and some algorithms (AES, RSA, ECDSA, etc.)", - "ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", - "paragonie/sodium_compat": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", - "spomky-labs/aes-key-wrap": "For all Key Wrapping algorithms (AxxxKW, AxxxGCMKW, PBES2-HSxxx+AyyyKW...)", - "symfony/console": "Needed to use console commands", - "symfony/http-client": "To enable JKU/X5U support." - }, - "type": "library", - "autoload": { - "psr-4": { - "Jose\\Component\\": "" - } - }, - "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": "JWT library", - "homepage": "https://github.com/web-token", - "keywords": [ - "JOSE", - "JWE", - "JWK", - "JWKSet", - "JWS", - "Jot", - "RFC7515", - "RFC7516", - "RFC7517", - "RFC7518", - "RFC7519", - "RFC7520", - "bundle", - "jwa", - "jwt", - "symfony" - ], - "support": { - "issues": "https://github.com/web-token/jwt-library/issues", - "source": "https://github.com/web-token/jwt-library/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/Spomky", - "type": "github" - }, - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2025-08-05T14:17:28+00:00" + "time": "2025-03-24T12:00:00+00:00" }, { "name": "webmozart/assert", @@ -17115,16 +17019,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { @@ -17143,7 +17047,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -17167,9 +17071,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2025-07-27T20:03:57+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "phar-io/manifest", @@ -18096,12 +18000,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "0479005335290f5c772337cf359599dad65bb63f" + "reference": "3aed1e89d8a5e2b6d350f0fdd21db0e7f49ab9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0479005335290f5c772337cf359599dad65bb63f", - "reference": "0479005335290f5c772337cf359599dad65bb63f", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/3aed1e89d8a5e2b6d350f0fdd21db0e7f49ab9dc", + "reference": "3aed1e89d8a5e2b6d350f0fdd21db0e7f49ab9dc", "shasum": "" }, "conflict": { @@ -18776,6 +18680,7 @@ "snipe/snipe-it": "<8.1", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", + "soosyze/soosyze": "<=2", "spatie/browsershot": "<5.0.5", "spatie/image-optimizer": "<1.7.3", "spencer14420/sp-php-email-handler": "<1", @@ -19050,7 +18955,7 @@ "type": "tidelift" } ], - "time": "2025-08-12T21:05:30+00:00" + "time": "2025-08-14T00:22:26+00:00" }, { "name": "sebastian/cli-parser", diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 95f5c6b1..e7a44e0c 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -13,7 +13,7 @@ security: firewalls: dev: - pattern: ^/(_(profiler|wdt)|css|images|js)/ + pattern: ^/(_(profiler|wdt)|css|images|js|\.well-known)/ security: false main: provider: app_user_provider diff --git a/migrations/Version20250813214628.php b/migrations/Version20250813214628.php new file mode 100644 index 00000000..995ff0ae --- /dev/null +++ b/migrations/Version20250813214628.php @@ -0,0 +1,75 @@ +connection; + $rows = $connection->fetchAllAssociative('SELECT id, transports, other_ui FROM webauthn_keys'); + + foreach ($rows as $row) { + $id = $row['id']; + $new_transports = json_encode(unserialize($row['transports'], ['allowed_classes' => false]), + JSON_THROW_ON_ERROR); + $new_other_ui = json_encode(unserialize($row['other_ui'], ['allowed_classes' => false]), + JSON_THROW_ON_ERROR); + + $connection->executeStatement( + 'UPDATE webauthn_keys SET transports = :transports, other_ui = :other_ui WHERE id = :id', + [ + 'transports' => $new_transports, + 'other_ui' => $new_other_ui, + 'id' => $id, + ] + ); + } + } + + public function mySQLUp(Schema $schema): void + { + $this->convertArrayToJson(); + $this->addSql('ALTER TABLE webauthn_keys CHANGE transports transports JSON NOT NULL, CHANGE other_ui other_ui JSON DEFAULT NULL'); + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE webauthn_keys CHANGE transports transports LONGTEXT NOT NULL, CHANGE other_ui other_ui LONGTEXT DEFAULT NULL'); + } + + public function sqLiteUp(Schema $schema): void + { + //As there is no JSON type in SQLite, we only need to convert the data. + $this->convertArrayToJson(); + } + + public function sqLiteDown(Schema $schema): void + { + //Nothing to do here, as SQLite does not support JSON type and we are not changing the column type. + } + + public function postgreSQLUp(Schema $schema): void + { + $this->convertArrayToJson(); + $this->addSql('ALTER TABLE webauthn_keys ALTER transports TYPE JSON'); + $this->addSql('ALTER TABLE webauthn_keys ALTER other_ui TYPE JSON'); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE webauthn_keys ALTER transports TYPE TEXT'); + $this->addSql('ALTER TABLE webauthn_keys ALTER other_ui TYPE TEXT'); + } +} diff --git a/src/Entity/UserSystem/WebauthnKey.php b/src/Entity/UserSystem/WebauthnKey.php index b2716e07..7d3cb7b3 100644 --- a/src/Entity/UserSystem/WebauthnKey.php +++ b/src/Entity/UserSystem/WebauthnKey.php @@ -100,16 +100,19 @@ class WebauthnKey extends BasePublicKeyCredentialSource implements TimeStampable public static function fromRegistration(BasePublicKeyCredentialSource $registration): self { return new self( - $registration->getPublicKeyCredentialId(), - $registration->getType(), - $registration->getTransports(), - $registration->getAttestationType(), - $registration->getTrustPath(), - $registration->getAaguid(), - $registration->getCredentialPublicKey(), - $registration->getUserHandle(), - $registration->getCounter(), - $registration->getOtherUI() + publicKeyCredentialId: $registration->publicKeyCredentialId, + type: $registration->type, + transports: $registration->transports, + attestationType: $registration->attestationType, + trustPath: $registration->trustPath, + aaguid: $registration->aaguid, + credentialPublicKey: $registration->credentialPublicKey, + userHandle: $registration->userHandle, + counter: $registration->counter, + otherUI: $registration->otherUI, + backupEligible: $registration->backupEligible, + backupStatus: $registration->backupStatus, + uvInitialized: $registration->uvInitialized, ); } } diff --git a/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php b/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php index 9bfa691d..4d1269d6 100644 --- a/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php +++ b/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php @@ -33,6 +33,7 @@ use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderInterface use Symfony\Component\DependencyInjection\Attribute\AsDecorator; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; +use Webauthn\PublicKeyCredential; /** * This class decorates the Webauthn TwoFactorProvider and adds additional logic which allows us to set a last used date @@ -88,10 +89,12 @@ class WebauthnKeyLastUseTwoFactorProvider implements TwoFactorProviderInterface private function getWebauthnKeyFromCode(string $authenticationCode): ?WebauthnKey { - $publicKeyCredentialLoader = $this->webauthnProvider->getPublicKeyCredentialLoader(); + $serializer = $this->webauthnProvider->getWebauthnSerializer(); //Try to load the public key credential from the code - $publicKeyCredential = $publicKeyCredentialLoader->load($authenticationCode); + $publicKeyCredential = $serializer->deserialize($authenticationCode, PublicKeyCredential::class, 'json', [ + 'json_decode_options' => JSON_THROW_ON_ERROR + ]); //Find the credential source for the given credential id $publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($publicKeyCredential->rawId); @@ -103,4 +106,4 @@ class WebauthnKeyLastUseTwoFactorProvider implements TwoFactorProviderInterface return $publicKeyCredentialSource; } -} \ No newline at end of file +}