diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 20a58b8..7490f99 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: php: [ '8.2', '8.3', '8.4', '8.5' ] ext_base: [ 'none, dom, tokenizer, xml, xmlwriter,' ] ext_lib: [ 'curl, mbstring, openssl,' ] - ext_optional: [ '', 'bcmath', 'gmp' ] + ext_optional: [ '', 'bcmath', 'gmp', 'sodium' ] name: PHP ${{ matrix.php }} (${{ matrix.ext_optional }}) diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 5a1b6fa..a2954d6 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -9,6 +9,7 @@ 'concat_space' => ['spacing' => 'none'], // Custom library style. 'declare_strict_types' => true, // Enforce strict code. 'global_namespace_import' => ['import_classes' => false, 'import_constants' => false, 'import_functions' => false], + 'ordered_imports' => ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'], 'php_unit_attributes' => true, 'php_unit_construct' => true, 'php_unit_method_casing' => true, diff --git a/composer.json b/composer.json index f12f10b..e895b50 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,6 @@ "ext-mbstring": "*", "ext-openssl": "*", "guzzlehttp/guzzle": "^7.9.2", - "spomky-labs/base64url": "^2.0.4", "web-token/jwt-library": "^3.4.9|^4.0.6" }, "suggest": { @@ -42,9 +41,10 @@ "ext-gmp": "Optional for performance." }, "require-dev": { - "phpunit/phpunit": "^11.5.46|^12.5.2", + "friendsofphp/php-cs-fixer": "^v3.92.3", "phpstan/phpstan": "^2.1.33", - "friendsofphp/php-cs-fixer": "^v3.92.2", + "phpunit/phpunit": "^11.5.46|^12.5.2", + "spomky-labs/base64url": "^2.0.4", "symfony/polyfill-iconv": "^1.33", "phpstan/phpstan-strict-rules": "^2.0" }, diff --git a/src/Encryption.php b/src/Encryption.php index 33de5bb..b0670e7 100644 --- a/src/Encryption.php +++ b/src/Encryption.php @@ -10,8 +10,8 @@ namespace Minishlink\WebPush; -use Base64Url\Base64Url; use Jose\Component\Core\JWK; +use Jose\Component\Core\Util\Base64UrlSafe; use Jose\Component\Core\Util\Ecc\PrivateKey; use Jose\Component\Core\Util\ECKey; @@ -76,8 +76,8 @@ public static function deterministicEncrypt( array $localKeyObject, string $salt ): array { - $userPublicKey = Base64Url::decode($userPublicKey); - $userAuthToken = Base64Url::decode($userAuthToken); + $userPublicKey = Base64UrlSafe::decode($userPublicKey); + $userAuthToken = Base64UrlSafe::decode($userAuthToken); // get local key pair if (count($localKeyObject) === 1) { @@ -91,9 +91,9 @@ public static function deterministicEncrypt( $localJwk = new JWK([ 'kty' => 'EC', 'crv' => 'P-256', - 'd' => Base64Url::encode($localPrivateKeyObject->getSecret()->toBytes(false)), - 'x' => Base64Url::encode($localPublicKeyObject[0]), - 'y' => Base64Url::encode($localPublicKeyObject[1]), + 'd' => Base64UrlSafe::encodeUnpadded($localPrivateKeyObject->getSecret()->toBytes(false)), + 'x' => Base64UrlSafe::encodeUnpadded($localPublicKeyObject[0]), + 'y' => Base64UrlSafe::encodeUnpadded($localPublicKeyObject[1]), ]); } if (!$localPublicKey) { @@ -105,8 +105,8 @@ public static function deterministicEncrypt( $userJwk = new JWK([ 'kty' => 'EC', 'crv' => 'P-256', - 'x' => Base64Url::encode($userPublicKeyObjectX), - 'y' => Base64Url::encode($userPublicKeyObjectY), + 'x' => Base64UrlSafe::encodeUnpadded($userPublicKeyObjectX), + 'y' => Base64UrlSafe::encodeUnpadded($userPublicKeyObjectY), ]); // get shared secret from user public key and local private key @@ -267,9 +267,9 @@ private static function createLocalKeyObject(): array new JWK([ 'kty' => 'EC', 'crv' => 'P-256', - 'x' => Base64Url::encode(self::addNullPadding($details['ec']['x'])), - 'y' => Base64Url::encode(self::addNullPadding($details['ec']['y'])), - 'd' => Base64Url::encode(self::addNullPadding($details['ec']['d'])), + 'x' => Base64UrlSafe::encodeUnpadded(self::addNullPadding($details['ec']['x'])), + 'y' => Base64UrlSafe::encodeUnpadded(self::addNullPadding($details['ec']['y'])), + 'd' => Base64UrlSafe::encodeUnpadded(self::addNullPadding($details['ec']['d'])), ]), ]; } diff --git a/src/Utils.php b/src/Utils.php index 195f585..c9ad94d 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -10,8 +10,8 @@ namespace Minishlink\WebPush; -use Base64Url\Base64Url; use Jose\Component\Core\JWK; +use Jose\Component\Core\Util\Base64UrlSafe; use Jose\Component\Core\Util\Ecc\PublicKey; class Utils @@ -34,8 +34,8 @@ public static function serializePublicKey(PublicKey $publicKey): string public static function serializePublicKeyFromJWK(JWK $jwk): string { $hexString = '04'; - $hexString .= str_pad(bin2hex(Base64Url::decode($jwk->get('x'))), 64, '0', STR_PAD_LEFT); - $hexString .= str_pad(bin2hex(Base64Url::decode($jwk->get('y'))), 64, '0', STR_PAD_LEFT); + $hexString .= str_pad(bin2hex(Base64UrlSafe::decode($jwk->get('x'))), 64, '0', STR_PAD_LEFT); + $hexString .= str_pad(bin2hex(Base64UrlSafe::decode($jwk->get('y'))), 64, '0', STR_PAD_LEFT); return $hexString; } diff --git a/src/VAPID.php b/src/VAPID.php index f44bdf0..01e3908 100644 --- a/src/VAPID.php +++ b/src/VAPID.php @@ -10,9 +10,9 @@ namespace Minishlink\WebPush; -use Base64Url\Base64Url; use Jose\Component\Core\AlgorithmManager; use Jose\Component\Core\JWK; +use Jose\Component\Core\Util\Base64UrlSafe; use Jose\Component\KeyManagement\JWKFactory; use Jose\Component\Signature\Algorithm\ES256; use Jose\Component\Signature\JWSBuilder; @@ -51,14 +51,14 @@ public static function validate(array $vapid): array throw new \ErrorException('Failed to convert VAPID public key from hexadecimal to binary'); } $vapid['publicKey'] = base64_encode($binaryPublicKey); - $vapid['privateKey'] = base64_encode(str_pad(Base64Url::decode($jwk->get('d')), self::PRIVATE_KEY_LENGTH, '0', STR_PAD_LEFT)); + $vapid['privateKey'] = base64_encode(str_pad(Base64UrlSafe::decode($jwk->get('d')), self::PRIVATE_KEY_LENGTH, '0', STR_PAD_LEFT)); } if (!isset($vapid['publicKey'])) { throw new \ErrorException('[VAPID] You must provide a public key.'); } - $publicKey = Base64Url::decode($vapid['publicKey']); + $publicKey = Base64UrlSafe::decode($vapid['publicKey']); if (Utils::safeStrlen($publicKey) !== self::PUBLIC_KEY_LENGTH) { throw new \ErrorException('[VAPID] Public key should be 65 bytes long when decoded.'); @@ -68,7 +68,7 @@ public static function validate(array $vapid): array throw new \ErrorException('[VAPID] You must provide a private key.'); } - $privateKey = Base64Url::decode($vapid['privateKey']); + $privateKey = Base64UrlSafe::decode($vapid['privateKey']); if (Utils::safeStrlen($privateKey) !== self::PRIVATE_KEY_LENGTH) { throw new \ErrorException('[VAPID] Private key should be 32 bytes long when decoded.'); @@ -126,9 +126,9 @@ public static function getVapidHeaders( $jwk = new JWK([ 'kty' => 'EC', 'crv' => 'P-256', - 'x' => Base64Url::encode($x), - 'y' => Base64Url::encode($y), - 'd' => Base64Url::encode($privateKey), + 'x' => Base64UrlSafe::encodeUnpadded($x), + 'y' => Base64UrlSafe::encodeUnpadded($y), + 'd' => Base64UrlSafe::encodeUnpadded($privateKey), ]); $jwsCompactSerializer = new CompactSerializer(); @@ -140,7 +140,7 @@ public static function getVapidHeaders( ->build(); $jwt = $jwsCompactSerializer->serialize($jws, 0); - $encodedPublicKey = Base64Url::encode($publicKey); + $encodedPublicKey = Base64UrlSafe::encodeUnpadded($publicKey); if ($contentEncoding === ContentEncoding::aesgcm) { return [ @@ -174,14 +174,14 @@ public static function createVapidKeys(): array throw new \ErrorException('Failed to convert VAPID public key from hexadecimal to binary'); } - $binaryPrivateKey = hex2bin(str_pad(bin2hex(Base64Url::decode($jwk->get('d'))), 2 * self::PRIVATE_KEY_LENGTH, '0', STR_PAD_LEFT)); + $binaryPrivateKey = hex2bin(str_pad(bin2hex(Base64UrlSafe::decode($jwk->get('d'))), 2 * self::PRIVATE_KEY_LENGTH, '0', STR_PAD_LEFT)); if (!$binaryPrivateKey) { throw new \ErrorException('Failed to convert VAPID private key from hexadecimal to binary'); } return [ - 'publicKey' => Base64Url::encode($binaryPublicKey), - 'privateKey' => Base64Url::encode($binaryPrivateKey), + 'publicKey' => Base64UrlSafe::encode($binaryPublicKey), + 'privateKey' => Base64UrlSafe::encode($binaryPrivateKey), ]; } } diff --git a/src/WebPush.php b/src/WebPush.php index 2f338e4..1c772c7 100644 --- a/src/WebPush.php +++ b/src/WebPush.php @@ -10,12 +10,12 @@ namespace Minishlink\WebPush; -use Base64Url\Base64Url; use GuzzleHttp\Client; -use GuzzleHttp\Pool; use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Pool; use GuzzleHttp\Psr7\Request; +use Jose\Component\Core\Util\Base64UrlSafe; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -260,8 +260,8 @@ protected function prepare(array $notifications): array ]; if ($contentEncoding === ContentEncoding::aesgcm->value) { - $headers['Encryption'] = 'salt='.Base64Url::encode($salt); - $headers['Crypto-Key'] = 'dh='.Base64Url::encode($localPublicKey); + $headers['Encryption'] = 'salt='.Base64UrlSafe::encode($salt); + $headers['Crypto-Key'] = 'dh='.Base64UrlSafe::encode($localPublicKey); } $encryptionContentCodingHeader = Encryption::getContentCodingHeader($salt, $localPublicKey, ContentEncoding::from($contentEncoding));