Skip to content

Commit 2c24f4c

Browse files
authored
Replace spomky-labs/base64url with paragonie/constant_time_encoding (#397)
* Replace `spomky-labs/base64url` with `paragonie/constant_time_encoding` * Directly using the `Base64UrlSafe` class * Add composer package `paragonie/constant_time_encoding`
1 parent 410b1fa commit 2c24f4c

File tree

6 files changed

+42
-42
lines changed

6 files changed

+42
-42
lines changed

composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"ext-openssl": "*",
3636
"guzzlehttp/guzzle": "^7.4.5",
3737
"web-token/jwt-library": "^3.3.0",
38-
"spomky-labs/base64url": "^2.0.4"
38+
"paragonie/constant_time_encoding": "^2.6"
3939
},
4040
"suggest": {
4141
"ext-bcmath": "Optional for performance.",
@@ -51,4 +51,4 @@
5151
"Minishlink\\WebPush\\": "src"
5252
}
5353
}
54-
}
54+
}

src/Encryption.php

+11-11
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313

1414
namespace Minishlink\WebPush;
1515

16-
use Base64Url\Base64Url;
1716
use Jose\Component\Core\JWK;
1817
use Jose\Component\Core\Util\Ecc\PrivateKey;
1918
use Jose\Component\Core\Util\ECKey;
19+
use ParagonIE\ConstantTime\Base64UrlSafe;
2020

2121
class Encryption
2222
{
@@ -66,8 +66,8 @@ public static function encrypt(string $payload, string $userPublicKey, string $u
6666
*/
6767
public static function deterministicEncrypt(string $payload, string $userPublicKey, string $userAuthToken, string $contentEncoding, array $localKeyObject, string $salt): array
6868
{
69-
$userPublicKey = Base64Url::decode($userPublicKey);
70-
$userAuthToken = Base64Url::decode($userAuthToken);
69+
$userPublicKey = Base64UrlSafe::decodeNoPadding($userPublicKey);
70+
$userAuthToken = Base64UrlSafe::decodeNoPadding($userAuthToken);
7171

7272
// get local key pair
7373
if (count($localKeyObject) === 1) {
@@ -81,9 +81,9 @@ public static function deterministicEncrypt(string $payload, string $userPublicK
8181
$localJwk = new JWK([
8282
'kty' => 'EC',
8383
'crv' => 'P-256',
84-
'd' => Base64Url::encode($localPrivateKeyObject->getSecret()->toBytes(false)),
85-
'x' => Base64Url::encode($localPublicKeyObject[0]),
86-
'y' => Base64Url::encode($localPublicKeyObject[1]),
84+
'd' => Base64UrlSafe::encodeUnpadded($localPrivateKeyObject->getSecret()->toBytes(false)),
85+
'x' => Base64UrlSafe::encodeUnpadded($localPublicKeyObject[0]),
86+
'y' => Base64UrlSafe::encodeUnpadded($localPublicKeyObject[1]),
8787
]);
8888
}
8989
if (!$localPublicKey) {
@@ -95,8 +95,8 @@ public static function deterministicEncrypt(string $payload, string $userPublicK
9595
$userJwk = new JWK([
9696
'kty' => 'EC',
9797
'crv' => 'P-256',
98-
'x' => Base64Url::encode($userPublicKeyObjectX),
99-
'y' => Base64Url::encode($userPublicKeyObjectY),
98+
'x' => Base64UrlSafe::encodeUnpadded($userPublicKeyObjectX),
99+
'y' => Base64UrlSafe::encodeUnpadded($userPublicKeyObjectY),
100100
]);
101101

102102
// get shared secret from user public key and local private key
@@ -252,9 +252,9 @@ private static function createLocalKeyObject(): array
252252
new JWK([
253253
'kty' => 'EC',
254254
'crv' => 'P-256',
255-
'x' => Base64Url::encode(self::addNullPadding($details['ec']['x'])),
256-
'y' => Base64Url::encode(self::addNullPadding($details['ec']['y'])),
257-
'd' => Base64Url::encode(self::addNullPadding($details['ec']['d'])),
255+
'x' => Base64UrlSafe::encodeUnpadded(self::addNullPadding($details['ec']['x'])),
256+
'y' => Base64UrlSafe::encodeUnpadded(self::addNullPadding($details['ec']['y'])),
257+
'd' => Base64UrlSafe::encodeUnpadded(self::addNullPadding($details['ec']['d'])),
258258
]),
259259
];
260260
}

src/Utils.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313

1414
namespace Minishlink\WebPush;
1515

16-
use Base64Url\Base64Url;
1716
use Jose\Component\Core\JWK;
1817
use Jose\Component\Core\Util\Ecc\PublicKey;
18+
use ParagonIE\ConstantTime\Base64UrlSafe;
1919

2020
class Utils
2121
{
@@ -37,8 +37,8 @@ public static function serializePublicKey(PublicKey $publicKey): string
3737
public static function serializePublicKeyFromJWK(JWK $jwk): string
3838
{
3939
$hexString = '04';
40-
$hexString .= str_pad(bin2hex(Base64Url::decode($jwk->get('x'))), 64, '0', STR_PAD_LEFT);
41-
$hexString .= str_pad(bin2hex(Base64Url::decode($jwk->get('y'))), 64, '0', STR_PAD_LEFT);
40+
$hexString .= str_pad(bin2hex(Base64UrlSafe::decodeNoPadding($jwk->get('x'))), 64, '0', STR_PAD_LEFT);
41+
$hexString .= str_pad(bin2hex(Base64UrlSafe::decodeNoPadding($jwk->get('y'))), 64, '0', STR_PAD_LEFT);
4242

4343
return $hexString;
4444
}

src/VAPID.php

+11-11
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313

1414
namespace Minishlink\WebPush;
1515

16-
use Base64Url\Base64Url;
1716
use Jose\Component\Core\AlgorithmManager;
1817
use Jose\Component\Core\JWK;
1918
use Jose\Component\KeyManagement\JWKFactory;
2019
use Jose\Component\Signature\Algorithm\ES256;
2120
use Jose\Component\Signature\JWSBuilder;
2221
use Jose\Component\Signature\Serializer\CompactSerializer;
22+
use ParagonIE\ConstantTime\Base64UrlSafe;
2323

2424
class VAPID
2525
{
@@ -54,14 +54,14 @@ public static function validate(array $vapid): array
5454
throw new \ErrorException('Failed to convert VAPID public key from hexadecimal to binary');
5555
}
5656
$vapid['publicKey'] = base64_encode($binaryPublicKey);
57-
$vapid['privateKey'] = base64_encode(str_pad(Base64Url::decode($jwk->get('d')), 2 * self::PRIVATE_KEY_LENGTH, '0', STR_PAD_LEFT));
57+
$vapid['privateKey'] = base64_encode(str_pad(Base64UrlSafe::decodeNoPadding($jwk->get('d')), 2 * self::PRIVATE_KEY_LENGTH, '0', STR_PAD_LEFT));
5858
}
5959

6060
if (!isset($vapid['publicKey'])) {
6161
throw new \ErrorException('[VAPID] You must provide a public key.');
6262
}
6363

64-
$publicKey = Base64Url::decode($vapid['publicKey']);
64+
$publicKey = Base64UrlSafe::decodeNoPadding($vapid['publicKey']);
6565

6666
if (Utils::safeStrlen($publicKey) !== self::PUBLIC_KEY_LENGTH) {
6767
throw new \ErrorException('[VAPID] Public key should be 65 bytes long when decoded.');
@@ -71,7 +71,7 @@ public static function validate(array $vapid): array
7171
throw new \ErrorException('[VAPID] You must provide a private key.');
7272
}
7373

74-
$privateKey = Base64Url::decode($vapid['privateKey']);
74+
$privateKey = Base64UrlSafe::decodeNoPadding($vapid['privateKey']);
7575

7676
if (Utils::safeStrlen($privateKey) !== self::PRIVATE_KEY_LENGTH) {
7777
throw new \ErrorException('[VAPID] Private key should be 32 bytes long when decoded.');
@@ -122,9 +122,9 @@ public static function getVapidHeaders(string $audience, string $subject, string
122122
$jwk = new JWK([
123123
'kty' => 'EC',
124124
'crv' => 'P-256',
125-
'x' => Base64Url::encode($x),
126-
'y' => Base64Url::encode($y),
127-
'd' => Base64Url::encode($privateKey),
125+
'x' => Base64UrlSafe::encodeUnpadded($x),
126+
'y' => Base64UrlSafe::encodeUnpadded($y),
127+
'd' => Base64UrlSafe::encodeUnpadded($privateKey),
128128
]);
129129

130130
$jwsCompactSerializer = new CompactSerializer();
@@ -136,7 +136,7 @@ public static function getVapidHeaders(string $audience, string $subject, string
136136
->build();
137137

138138
$jwt = $jwsCompactSerializer->serialize($jws, 0);
139-
$encodedPublicKey = Base64Url::encode($publicKey);
139+
$encodedPublicKey = Base64UrlSafe::encodeUnpadded($publicKey);
140140

141141
if ($contentEncoding === "aesgcm") {
142142
return [
@@ -169,14 +169,14 @@ public static function createVapidKeys(): array
169169
throw new \ErrorException('Failed to convert VAPID public key from hexadecimal to binary');
170170
}
171171

172-
$binaryPrivateKey = hex2bin(str_pad(bin2hex(Base64Url::decode($jwk->get('d'))), 2 * self::PRIVATE_KEY_LENGTH, '0', STR_PAD_LEFT));
172+
$binaryPrivateKey = hex2bin(str_pad(bin2hex(Base64UrlSafe::decodeNoPadding($jwk->get('d'))), 2 * self::PRIVATE_KEY_LENGTH, '0', STR_PAD_LEFT));
173173
if (!$binaryPrivateKey) {
174174
throw new \ErrorException('Failed to convert VAPID private key from hexadecimal to binary');
175175
}
176176

177177
return [
178-
'publicKey' => Base64Url::encode($binaryPublicKey),
179-
'privateKey' => Base64Url::encode($binaryPrivateKey),
178+
'publicKey' => Base64UrlSafe::encodeUnpadded($binaryPublicKey),
179+
'privateKey' => Base64UrlSafe::encodeUnpadded($binaryPrivateKey),
180180
];
181181
}
182182
}

src/WebPush.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313

1414
namespace Minishlink\WebPush;
1515

16-
use Base64Url\Base64Url;
1716
use GuzzleHttp\Client;
1817
use GuzzleHttp\Exception\RequestException;
1918
use GuzzleHttp\Psr7\Request;
19+
use ParagonIE\ConstantTime\Base64UrlSafe;
2020
use Psr\Http\Message\ResponseInterface;
2121

2222
class WebPush
@@ -208,8 +208,8 @@ protected function prepare(array $notifications): array
208208
];
209209

210210
if ($contentEncoding === "aesgcm") {
211-
$headers['Encryption'] = 'salt='.Base64Url::encode($salt);
212-
$headers['Crypto-Key'] = 'dh='.Base64Url::encode($localPublicKey);
211+
$headers['Encryption'] = 'salt='.Base64UrlSafe::encodeUnpadded($salt);
212+
$headers['Crypto-Key'] = 'dh='.Base64UrlSafe::encodeUnpadded($localPublicKey);
213213
}
214214

215215
$encryptionContentCodingHeader = Encryption::getContentCodingHeader($salt, $localPublicKey, $contentEncoding);

tests/EncryptionTest.php

+12-12
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
* file that was distributed with this source code.
99
*/
1010

11-
use Base64Url\Base64Url;
1211
use Jose\Component\Core\JWK;
1312
use Minishlink\WebPush\Encryption;
1413
use Minishlink\WebPush\Utils;
14+
use ParagonIE\ConstantTime\Base64UrlSafe;
1515
use PHPUnit\Framework\Attributes\DataProvider;
1616

1717
/**
@@ -23,30 +23,30 @@ public function testDeterministicEncrypt(): void
2323
{
2424
$contentEncoding = "aes128gcm";
2525
$plaintext = 'When I grow up, I want to be a watermelon';
26-
$this->assertEquals('V2hlbiBJIGdyb3cgdXAsIEkgd2FudCB0byBiZSBhIHdhdGVybWVsb24', Base64Url::encode($plaintext));
26+
$this->assertEquals('V2hlbiBJIGdyb3cgdXAsIEkgd2FudCB0byBiZSBhIHdhdGVybWVsb24', Base64UrlSafe::encodeUnpadded($plaintext));
2727

2828
$payload = Encryption::padPayload($plaintext, 0, $contentEncoding);
29-
$this->assertEquals('V2hlbiBJIGdyb3cgdXAsIEkgd2FudCB0byBiZSBhIHdhdGVybWVsb24C', Base64Url::encode($payload));
29+
$this->assertEquals('V2hlbiBJIGdyb3cgdXAsIEkgd2FudCB0byBiZSBhIHdhdGVybWVsb24C', Base64UrlSafe::encodeUnpadded($payload));
3030

3131
$userPublicKey = 'BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4';
3232
$userAuthToken = 'BTBZMqHH6r4Tts7J_aSIgg';
3333

34-
$localPublicKey = Base64Url::decode('BP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A8');
35-
$salt = Base64Url::decode('DGv6ra1nlYgDCS1FRnbzlw');
34+
$localPublicKey = Base64UrlSafe::decodeNoPadding('BP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A8');
35+
$salt = Base64UrlSafe::decodeNoPadding('DGv6ra1nlYgDCS1FRnbzlw');
3636

3737
[$localPublicKeyObjectX, $localPublicKeyObjectY] = Utils::unserializePublicKey($localPublicKey);
3838
$localJwk = new JWK([
3939
'kty' => 'EC',
4040
'crv' => 'P-256',
4141
'd' => 'yfWPiYE-n46HLnH0KqZOF1fJJU3MYrct3AELtAQ-oRw',
42-
'x' => Base64Url::encode($localPublicKeyObjectX),
43-
'y' => Base64Url::encode($localPublicKeyObjectY),
42+
'x' => Base64UrlSafe::encodeUnpadded($localPublicKeyObjectX),
43+
'y' => Base64UrlSafe::encodeUnpadded($localPublicKeyObjectY),
4444
]);
4545

4646
$expected = [
4747
'localPublicKey' => $localPublicKey,
4848
'salt' => $salt,
49-
'cipherText' => Base64Url::decode('8pfeW0KbunFT06SuDKoJH9Ql87S1QUrd irN6GcG7sFz1y1sqLgVi1VhjVkHsUoEsbI_0LpXMuGvnzQ'),
49+
'cipherText' => Base64UrlSafe::decodeNoPadding('8pfeW0KbunFT06SuDKoJH9Ql87S1QUrdirN6GcG7sFz1y1sqLgVi1VhjVkHsUoEsbI_0LpXMuGvnzQ'),
5050
];
5151

5252
$result = Encryption::deterministicEncrypt(
@@ -59,17 +59,17 @@ public function testDeterministicEncrypt(): void
5959
);
6060

6161
$this->assertEquals(Utils::safeStrlen($expected['cipherText']), Utils::safeStrlen($result['cipherText']));
62-
$this->assertEquals(Base64Url::encode($expected['cipherText']), Base64Url::encode($result['cipherText']));
62+
$this->assertEquals(Base64UrlSafe::encodeUnpadded($expected['cipherText']), Base64UrlSafe::encodeUnpadded($result['cipherText']));
6363
$this->assertEquals($expected, $result);
6464
}
6565

6666
public function testGetContentCodingHeader(): void
6767
{
68-
$localPublicKey = Base64Url::decode('BP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A8');
69-
$salt = Base64Url::decode('DGv6ra1nlYgDCS1FRnbzlw');
68+
$localPublicKey = Base64UrlSafe::decodeNoPadding('BP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A8');
69+
$salt = Base64UrlSafe::decodeNoPadding('DGv6ra1nlYgDCS1FRnbzlw');
7070

7171
$result = Encryption::getContentCodingHeader($salt, $localPublicKey, "aes128gcm");
72-
$expected = Base64Url::decode('DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A8');
72+
$expected = Base64UrlSafe::decodeNoPadding('DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A8');
7373

7474
$this->assertEquals(Utils::safeStrlen($expected), Utils::safeStrlen($result));
7575
$this->assertEquals($expected, $result);

0 commit comments

Comments
 (0)