From 05d74f3e3ae7d328cc51cc806ac212d3659a2cbe Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Wed, 31 Aug 2022 18:36:35 +0200 Subject: [PATCH] Upgrade dependencies and remove custom method for B64URLSafe encoding (#259) * Upgrade dependencies * Direct call to ParagonIE\ConstantTime\Base64::decodeNoPadding(). --- composer.json | 78 +++++++++---------- src/metadata-service/composer.json | 8 +- src/symfony/composer.json | 24 +++--- src/webauthn/composer.json | 12 +-- .../TPMAttestationStatementSupport.php | 4 +- src/webauthn/src/CollectedClientData.php | 6 +- .../src/PublicKeyCredentialDescriptor.php | 3 +- .../src/PublicKeyCredentialLoader.php | 5 +- .../src/PublicKeyCredentialSource.php | 7 +- .../src/TokenBinding/TokenBinding.php | 4 +- src/webauthn/src/Util/Base64.php | 8 -- tests/library/Functional/AttestationTest.php | 6 +- 12 files changed, 79 insertions(+), 86 deletions(-) diff --git a/composer.json b/composer.json index 450f705f..a5ccbc46 100644 --- a/composer.json +++ b/composer.json @@ -37,32 +37,32 @@ "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "beberlei/assert": "^3.2", - "fgrosse/phpasn1": "^2.1", + "beberlei/assert": "^3.3", + "fgrosse/phpasn1": "^2.4", "lcobucci/clock": "^2.2", - "nyholm/psr7": "^1.1", - "paragonie/constant_time_encoding": "^2.4", + "nyholm/psr7": "^1.5", + "paragonie/constant_time_encoding": "^2.6", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.0", - "psr/log": "^2.0|^3.0", + "psr/log": "^3.0", "spomky-labs/cbor-bundle": "^3.0", "spomky-labs/cbor-php": "^3.0", "spomky-labs/pki-framework": "^1.0", - "symfony/config": "^6.0", - "symfony/dependency-injection": "^6.0", - "symfony/framework-bundle": "^6.0", - "symfony/http-client": "^6.0", - "symfony/psr-http-message-bridge": "^2.0", - "symfony/security-bundle": "^6.0", - "symfony/security-core": "^6.0", - "symfony/security-http": "^6.0", - "symfony/serializer": "^6.0", - "symfony/validator": "^6.0", - "symfony/uid": "^6.0", - "thecodingmachine/safe": "^2.0", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.1", + "symfony/framework-bundle": "^6.1", + "symfony/http-client": "^6.1", + "symfony/psr-http-message-bridge": "^2.1", + "symfony/security-bundle": "^6.1", + "symfony/security-core": "^6.1", + "symfony/security-http": "^6.1", + "symfony/serializer": "^6.1", + "symfony/validator": "^6.1", + "symfony/uid": "^6.1", + "thecodingmachine/safe": "^2.2", "web-auth/cose-lib": "^4.0.6", - "web-token/jwt-signature": "^3.0" + "web-token/jwt-signature": "^3.1" }, "replace": { "web-auth/webauthn-lib": "self.version", @@ -86,36 +86,36 @@ "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support" }, "require-dev": { - "doctrine/annotations": "^1.7", - "doctrine/dbal": "^3.0", - "doctrine/doctrine-bundle": "^2.0", - "doctrine/orm": "^2.6", + "doctrine/annotations": "^1.13", + "doctrine/dbal": "^3.4", + "doctrine/doctrine-bundle": "^2.7", + "doctrine/orm": "^2.13", "ekino/phpstan-banned-code": "^1.0", "infection/infection": "^0.26", "matthiasnoback/symfony-dependency-injection-test": "^4.3", - "php-http/curl-client": "^2.0", - "php-http/mock-client": "^1.3", + "php-http/curl-client": "^2.2", + "php-http/mock-client": "^1.5", "php-parallel-lint/php-parallel-lint": "^1.3", - "phpstan/phpstan": "^1.0", + "phpstan/phpstan": "^1.8", "phpstan/phpstan-beberlei-assert": "^1.0", "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^9.5.24", "qossmic/deptrac-shim": "^0.24", "rector/rector": "^0.14", "roave/security-advisories": "dev-latest", - "symfony/browser-kit": "^6.0", - "symfony/finder": "^6.0", - "symfony/monolog-bundle": "^3.5", - "symfony/phpunit-bridge": "^6.0", - "symfony/var-dumper": "^6.0", - "symfony/yaml": "^6.0", - "symplify/easy-coding-standard": "^11.0", + "symfony/browser-kit": "^6.1", + "symfony/finder": "^6.1", + "symfony/monolog-bundle": "^3.8", + "symfony/phpunit-bridge": "^6.1", + "symfony/var-dumper": "^6.1", + "symfony/yaml": "^6.1", + "symplify/easy-coding-standard": "^11.1", "thecodingmachine/phpstan-safe-rule": "^1.2", - "web-token/jwt-key-mgmt": "^3.0", - "web-token/jwt-signature-algorithm-ecdsa": "^3.0", - "web-token/jwt-signature-algorithm-eddsa": "^3.0", - "web-token/jwt-signature-algorithm-rsa": "^3.0" + "web-token/jwt-key-mgmt": "^3.1", + "web-token/jwt-signature-algorithm-ecdsa": "^3.1", + "web-token/jwt-signature-algorithm-eddsa": "^3.1", + "web-token/jwt-signature-algorithm-rsa": "^3.1" } } diff --git a/src/metadata-service/composer.json b/src/metadata-service/composer.json index 78b1d549..9ae0bcf8 100644 --- a/src/metadata-service/composer.json +++ b/src/metadata-service/composer.json @@ -22,14 +22,14 @@ "require": { "php": ">=8.1", "ext-json": "*", - "beberlei/assert": "^3.2", + "beberlei/assert": "^3.3", "lcobucci/clock": "^2.2", - "paragonie/constant_time_encoding": "^2.4", + "paragonie/constant_time_encoding": "^2.6", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "psr/log": "^2.0|^3.0", + "psr/log": "^3.0", "spomky-labs/pki-framework": "^1.0", - "thecodingmachine/safe": "^2.0" + "thecodingmachine/safe": "^2.2" }, "autoload": { "psr-4": { diff --git a/src/symfony/composer.json b/src/symfony/composer.json index 1749a4ab..6051d93f 100644 --- a/src/symfony/composer.json +++ b/src/symfony/composer.json @@ -21,20 +21,20 @@ ], "require": { "php": ">=8.1", - "nyholm/psr7": "^1.1", + "nyholm/psr7": "^1.5", "spomky-labs/cbor-bundle": "^3.0", - "symfony/config": "^6.0", - "symfony/dependency-injection": "^6.0", - "symfony/framework-bundle": "^6.0", - "symfony/http-client": "^6.0", - "symfony/psr-http-message-bridge": "^2.0", - "symfony/security-bundle": "^6.0", - "symfony/security-core": "^6.0", - "symfony/security-http": "^6.0", - "symfony/serializer": "^6.0", - "symfony/validator": "^6.0", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.1", + "symfony/framework-bundle": "^6.1", + "symfony/http-client": "^6.1", + "symfony/psr-http-message-bridge": "^2.1", + "symfony/security-bundle": "^6.1", + "symfony/security-core": "^6.1", + "symfony/security-http": "^6.1", + "symfony/serializer": "^6.1", + "symfony/validator": "^6.1", "web-auth/webauthn-lib": "self.version", - "web-token/jwt-signature": "^3.0" + "web-token/jwt-signature": "^3.1" }, "autoload": { "psr-4": { diff --git a/src/webauthn/composer.json b/src/webauthn/composer.json index 5a69f919..d8b32fc8 100644 --- a/src/webauthn/composer.json +++ b/src/webauthn/composer.json @@ -24,16 +24,16 @@ "ext-json": "*", "ext-openssl": "*", "ext-mbstring": "*", - "beberlei/assert": "^3.2", - "fgrosse/phpasn1": "^2.1", - "paragonie/constant_time_encoding": "^2.4", + "beberlei/assert": "^3.3", + "fgrosse/phpasn1": "^2.4", + "paragonie/constant_time_encoding": "^2.6", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.0", - "psr/log": "^2.0|^3.0", + "psr/log": "^3.0", "spomky-labs/cbor-php": "^3.0", - "symfony/uid": "^6.0", - "thecodingmachine/safe": "^2.0", + "symfony/uid": "^6.1", + "thecodingmachine/safe": "^2.2", "web-auth/cose-lib": "^4.0.6", "web-auth/metadata-service": "self.version" }, diff --git a/src/webauthn/src/AttestationStatement/TPMAttestationStatementSupport.php b/src/webauthn/src/AttestationStatement/TPMAttestationStatementSupport.php index b689389f..4f4b22fd 100644 --- a/src/webauthn/src/AttestationStatement/TPMAttestationStatementSupport.php +++ b/src/webauthn/src/AttestationStatement/TPMAttestationStatementSupport.php @@ -19,6 +19,7 @@ use function is_array; use Lcobucci\Clock\Clock; use Lcobucci\Clock\SystemClock; +use ParagonIE\ConstantTime\Base64UrlSafe; use RuntimeException; use Safe\DateTimeImmutable; use function Safe\openssl_verify; @@ -28,7 +29,6 @@ use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; use Webauthn\TrustPath\EcdaaKeyIdTrustPath; -use Webauthn\Util\Base64; final class TPMAttestationStatementSupport implements AttestationStatementSupport { @@ -268,7 +268,7 @@ private function getUnique(string $type, StringStream $stream): string private function getExponent(string $exponent): string { - return bin2hex($exponent) === '00000000' ? Base64::decodeUrlSafe('AQAB') : $exponent; + return bin2hex($exponent) === '00000000' ? Base64UrlSafe::decodeNoPadding('AQAB') : $exponent; } private function getTPMHash(string $nameAlg): string diff --git a/src/webauthn/src/CollectedClientData.php b/src/webauthn/src/CollectedClientData.php index 9bcbc499..92873791 100644 --- a/src/webauthn/src/CollectedClientData.php +++ b/src/webauthn/src/CollectedClientData.php @@ -8,8 +8,8 @@ use Assert\Assertion; use InvalidArgumentException; use const JSON_THROW_ON_ERROR; +use ParagonIE\ConstantTime\Base64UrlSafe; use Webauthn\TokenBinding\TokenBinding; -use Webauthn\Util\Base64; class CollectedClientData { @@ -43,7 +43,7 @@ public function __construct( $challenge = $data['challenge'] ?? ''; Assertion::string($challenge, 'Invalid parameter "challenge". Shall be a string.'); - $challenge = Base64::decodeUrlSafe($challenge); + $challenge = Base64UrlSafe::decodeNoPadding($challenge); $this->challenge = $challenge; Assertion::notEmpty($challenge, 'Invalid parameter "challenge". Shall not be empty.'); @@ -61,7 +61,7 @@ public function __construct( public static function createFormJson(string $data): self { - $rawData = Base64::decodeUrlSafe($data); + $rawData = Base64UrlSafe::decodeNoPadding($data); $json = json_decode($rawData, true, 512, JSON_THROW_ON_ERROR); Assertion::isArray($json, 'Invalid collected client data'); diff --git a/src/webauthn/src/PublicKeyCredentialDescriptor.php b/src/webauthn/src/PublicKeyCredentialDescriptor.php index 18bd6a64..7b1deb83 100644 --- a/src/webauthn/src/PublicKeyCredentialDescriptor.php +++ b/src/webauthn/src/PublicKeyCredentialDescriptor.php @@ -9,7 +9,6 @@ use const JSON_THROW_ON_ERROR; use JsonSerializable; use ParagonIE\ConstantTime\Base64UrlSafe; -use Webauthn\Util\Base64; class PublicKeyCredentialDescriptor implements JsonSerializable { @@ -77,7 +76,7 @@ public static function createFromArray(array $json): self Assertion::keyExists($json, 'type', 'Invalid input. "type" is missing.'); Assertion::keyExists($json, 'id', 'Invalid input. "id" is missing.'); - $id = Base64::decodeUrlSafe($json['id']); + $id = Base64UrlSafe::decodeNoPadding($json['id']); return new self($json['type'], $id, $json['transports'] ?? []); } diff --git a/src/webauthn/src/PublicKeyCredentialLoader.php b/src/webauthn/src/PublicKeyCredentialLoader.php index 37203b6d..ed88ca2e 100644 --- a/src/webauthn/src/PublicKeyCredentialLoader.php +++ b/src/webauthn/src/PublicKeyCredentialLoader.php @@ -11,6 +11,7 @@ use InvalidArgumentException; use const JSON_THROW_ON_ERROR; use function ord; +use ParagonIE\ConstantTime\Base64UrlSafe; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use function Safe\unpack; @@ -66,7 +67,7 @@ public function loadArray(array $json): PublicKeyCredential Assertion::isArray($json['response'], 'The parameter "response" shall be an array'); Assertion::eq($json['type'], 'public-key', sprintf('Unsupported type "%s"', $json['type'])); - $id = Base64::decodeUrlSafe($json['id']); + $id = Base64UrlSafe::decodeNoPadding($json['id']); $rawId = Base64::decode($json['rawId']); Assertion::true(hash_equals($id, $rawId)); @@ -128,7 +129,7 @@ private function createResponse(array $response): AuthenticatorResponse $response['clientDataJSON'] ), $attestationObject); case array_key_exists('authenticatorData', $response) && array_key_exists('signature', $response): - $authData = Base64::decodeUrlSafe($response['authenticatorData']); + $authData = Base64UrlSafe::decodeNoPadding($response['authenticatorData']); $authDataStream = new StringStream($authData); $rp_id_hash = $authDataStream->read(32); diff --git a/src/webauthn/src/PublicKeyCredentialSource.php b/src/webauthn/src/PublicKeyCredentialSource.php index 02c76f4c..0109f7d5 100644 --- a/src/webauthn/src/PublicKeyCredentialSource.php +++ b/src/webauthn/src/PublicKeyCredentialSource.php @@ -13,7 +13,6 @@ use Throwable; use Webauthn\TrustPath\TrustPath; use Webauthn\TrustPath\TrustPathLoader; -use Webauthn\Util\Base64; /** * @see https://www.w3.org/TR/webauthn/#iface-pkcredential @@ -166,14 +165,14 @@ public static function createFromArray(array $data): self try { return new self( - Base64::decodeUrlSafe($data['publicKeyCredentialId']), + Base64UrlSafe::decodeNoPadding($data['publicKeyCredentialId']), $data['type'], $data['transports'], $data['attestationType'], TrustPathLoader::loadTrustPath($data['trustPath']), $uuid, - Base64::decodeUrlSafe($data['credentialPublicKey']), - Base64::decodeUrlSafe($data['userHandle']), + Base64UrlSafe::decodeNoPadding($data['credentialPublicKey']), + Base64UrlSafe::decodeNoPadding($data['userHandle']), $data['counter'], $data['otherUI'] ?? null ); diff --git a/src/webauthn/src/TokenBinding/TokenBinding.php b/src/webauthn/src/TokenBinding/TokenBinding.php index c40296e0..5fbd106c 100644 --- a/src/webauthn/src/TokenBinding/TokenBinding.php +++ b/src/webauthn/src/TokenBinding/TokenBinding.php @@ -6,7 +6,7 @@ use function array_key_exists; use Assert\Assertion; -use Webauthn\Util\Base64; +use ParagonIE\ConstantTime\Base64UrlSafe; class TokenBinding { @@ -45,7 +45,7 @@ public static function createFormArray(array $json): self implode(', ', self::getSupportedStatus()) ) ); - $id = array_key_exists('id', $json) ? Base64::decodeUrlSafe($json['id']) : null; + $id = array_key_exists('id', $json) ? Base64UrlSafe::decodeNoPadding($json['id']) : null; return new self($status, $id); } diff --git a/src/webauthn/src/Util/Base64.php b/src/webauthn/src/Util/Base64.php index 7a7b2141..5475ef95 100644 --- a/src/webauthn/src/Util/Base64.php +++ b/src/webauthn/src/Util/Base64.php @@ -4,20 +4,12 @@ namespace Webauthn\Util; -use Assert\Assertion; use InvalidArgumentException; use ParagonIE\ConstantTime\Base64UrlSafe; use Throwable; abstract class Base64 { - public static function decodeUrlSafe(string $data): string - { - Assertion::regex($data, '/^[A-Za-z0-9\-_]*$/', 'Invalid Base 64 Url Safe character.'); - - return Base64UrlSafe::decode($data); - } - public static function decode(string $data): string { try { diff --git a/tests/library/Functional/AttestationTest.php b/tests/library/Functional/AttestationTest.php index c0f18f65..04da2b4d 100644 --- a/tests/library/Functional/AttestationTest.php +++ b/tests/library/Functional/AttestationTest.php @@ -5,6 +5,7 @@ namespace Webauthn\Tests\Functional; use ParagonIE\ConstantTime\Base64UrlSafe; +use RangeException; use Webauthn\AttestedCredentialData; use Webauthn\AuthenticatorAttestationResponse; use Webauthn\AuthenticatorData; @@ -20,9 +21,10 @@ final class AttestationTest extends AbstractTestCase /** * @test */ - public function aResponseCannotBeLoaded() + public function aResponseCannotBeLoaded(): void { - static::expectExceptionMessage('Invalid Base 64 Url Safe character.'); + static::expectException(RangeException::class); + static::expectExceptionMessage('Incorrect padding'); $response = '{"id":"wHU13DaUWRqIQq94SAfCG8jqUZGdW0N95hnchI3rG7s===","rawId":"wHU13DaUWRqIQq94SAfCG8jqUZGdW0N95hnchI3rG7s","response":{"authenticatorData":"lgTqgoJOmKStoUtEYtDXOo7EaRMNqRsZMHRZIp90o1kBAAAAag","signature":"MEYCIQD4faYQG08_xpmAxFwp33OObSPavG7iUCJimHhH2QwyVAIhAMVRovz5DR_itNGYzTpKgO2urLgx5F2mZf3U4INTRR74","userHandle":"MDFHN0VEWUMxQ1QxSjBUUVBIWEY3QVlGNUs","clientDataJSON":"eyJvcmlnaW4iOiJodHRwczovL3dlYmF1dGhuLnNwb21reS1sYWJzLmNvbSIsImNoYWxsZW5nZSI6IkhaaktrWURKTEgtVnF6bFgtaXpCcUc3Q1pvN0FVRmtobG12TnRHM1VKSjQiLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0"},"getClientExtensionResults":{},"type":"public-key"}'; $this->getPublicKeyCredentialLoader()