summaryrefslogtreecommitdiffstats
path: root/vendor/minishlink/web-push/src/VAPID.php
diff options
context:
space:
mode:
authorAnton Luka Šijanec <anton@sijanec.eu>2022-01-11 12:35:47 +0100
committerAnton Luka Šijanec <anton@sijanec.eu>2022-01-11 12:35:47 +0100
commit19985dbb8c0aa66dc4bf7905abc1148de909097d (patch)
tree2cd5a5d20d7e80fc2a51adf60d838d8a2c40999e /vendor/minishlink/web-push/src/VAPID.php
download1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar
1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar.gz
1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar.bz2
1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar.lz
1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar.xz
1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.tar.zst
1ka-19985dbb8c0aa66dc4bf7905abc1148de909097d.zip
Diffstat (limited to '')
-rw-r--r--vendor/minishlink/web-push/src/VAPID.php197
1 files changed, 197 insertions, 0 deletions
diff --git a/vendor/minishlink/web-push/src/VAPID.php b/vendor/minishlink/web-push/src/VAPID.php
new file mode 100644
index 0000000..c741ec9
--- /dev/null
+++ b/vendor/minishlink/web-push/src/VAPID.php
@@ -0,0 +1,197 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the WebPush library.
+ *
+ * (c) Louis Lagrange <lagrange.louis@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Minishlink\WebPush;
+
+use Base64Url\Base64Url;
+use Jose\Component\Core\AlgorithmManager;
+use Jose\Component\Core\Converter\StandardConverter;
+use Jose\Component\Core\JWK;
+use Jose\Component\Core\Util\Ecc\NistCurve;
+use Jose\Component\Core\Util\Ecc\Point;
+use Jose\Component\Core\Util\Ecc\PublicKey;
+use Jose\Component\KeyManagement\JWKFactory;
+use Jose\Component\Signature\Algorithm\ES256;
+use Jose\Component\Signature\JWSBuilder;
+use Jose\Component\Signature\Serializer\CompactSerializer;
+
+class VAPID
+{
+ private const PUBLIC_KEY_LENGTH = 65;
+ private const PRIVATE_KEY_LENGTH = 32;
+
+ /**
+ * @param array $vapid
+ *
+ * @return array
+ *
+ * @throws \ErrorException
+ */
+ public static function validate(array $vapid): array
+ {
+ if (!isset($vapid['subject'])) {
+ throw new \ErrorException('[VAPID] You must provide a subject that is either a mailto: or a URL.');
+ }
+
+ if (isset($vapid['pemFile'])) {
+ $vapid['pem'] = file_get_contents($vapid['pemFile']);
+
+ if (!$vapid['pem']) {
+ throw new \ErrorException('Error loading PEM file.');
+ }
+ }
+
+ if (isset($vapid['pem'])) {
+ $jwk = JWKFactory::createFromKey($vapid['pem']);
+ if ($jwk->get('kty') !== 'EC' || !$jwk->has('d') || !$jwk->has('x') || !$jwk->has('y')) {
+ throw new \ErrorException('Invalid PEM data.');
+ }
+ $publicKey = PublicKey::create(Point::create(
+ gmp_init(bin2hex(Base64Url::decode($jwk->get('x'))), 16),
+ gmp_init(bin2hex(Base64Url::decode($jwk->get('y'))), 16)
+ ));
+
+ $binaryPublicKey = hex2bin(Utils::serializePublicKey($publicKey));
+ if (!$binaryPublicKey) {
+ 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')), 2 * 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']);
+
+ if (Utils::safeStrlen($publicKey) !== self::PUBLIC_KEY_LENGTH) {
+ throw new \ErrorException('[VAPID] Public key should be 65 bytes long when decoded.');
+ }
+
+ if (!isset($vapid['privateKey'])) {
+ throw new \ErrorException('[VAPID] You must provide a private key.');
+ }
+
+ $privateKey = Base64Url::decode($vapid['privateKey']);
+
+ if (Utils::safeStrlen($privateKey) !== self::PRIVATE_KEY_LENGTH) {
+ throw new \ErrorException('[VAPID] Private key should be 32 bytes long when decoded.');
+ }
+
+ return [
+ 'subject' => $vapid['subject'],
+ 'publicKey' => $publicKey,
+ 'privateKey' => $privateKey,
+ ];
+ }
+
+ /**
+ * This method takes the required VAPID parameters and returns the required
+ * header to be added to a Web Push Protocol Request.
+ *
+ * @param string $audience This must be the origin of the push service
+ * @param string $subject This should be a URL or a 'mailto:' email address
+ * @param string $publicKey The decoded VAPID public key
+ * @param string $privateKey The decoded VAPID private key
+ * @param string $contentEncoding
+ * @param null|int $expiration The expiration of the VAPID JWT. (UNIX timestamp)
+ *
+ * @return array Returns an array with the 'Authorization' and 'Crypto-Key' values to be used as headers
+ * @throws \ErrorException
+ */
+ public static function getVapidHeaders(string $audience, string $subject, string $publicKey, string $privateKey, string $contentEncoding, ?int $expiration = null)
+ {
+ $expirationLimit = time() + 43200; // equal margin of error between 0 and 24h
+ if (null === $expiration || $expiration > $expirationLimit) {
+ $expiration = $expirationLimit;
+ }
+
+ $header = [
+ 'typ' => 'JWT',
+ 'alg' => 'ES256',
+ ];
+
+ $jwtPayload = json_encode([
+ 'aud' => $audience,
+ 'exp' => $expiration,
+ 'sub' => $subject,
+ ], JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
+ if (!$jwtPayload) {
+ throw new \ErrorException('Failed to encode JWT payload in JSON');
+ }
+
+ list($x, $y) = Utils::unserializePublicKey($publicKey);
+ $jwk = JWK::create([
+ 'kty' => 'EC',
+ 'crv' => 'P-256',
+ 'x' => Base64Url::encode($x),
+ 'y' => Base64Url::encode($y),
+ 'd' => Base64Url::encode($privateKey),
+ ]);
+
+ $jsonConverter = new StandardConverter();
+ $jwsCompactSerializer = new CompactSerializer($jsonConverter);
+ $jwsBuilder = new JWSBuilder($jsonConverter, AlgorithmManager::create([new ES256()]));
+ $jws = $jwsBuilder
+ ->create()
+ ->withPayload($jwtPayload)
+ ->addSignature($jwk, $header)
+ ->build();
+
+ $jwt = $jwsCompactSerializer->serialize($jws, 0);
+ $encodedPublicKey = Base64Url::encode($publicKey);
+
+ if ($contentEncoding === "aesgcm") {
+ return [
+ 'Authorization' => 'WebPush '.$jwt,
+ 'Crypto-Key' => 'p256ecdsa='.$encodedPublicKey,
+ ];
+ } elseif ($contentEncoding === 'aes128gcm') {
+ return [
+ 'Authorization' => 'vapid t='.$jwt.', k='.$encodedPublicKey,
+ ];
+ }
+
+ throw new \ErrorException('This content encoding is not supported');
+ }
+
+ /**
+ * This method creates VAPID keys in case you would not be able to have a Linux bash.
+ * DO NOT create keys at each initialization! Save those keys and reuse them.
+ *
+ * @return array
+ * @throws \ErrorException
+ */
+ public static function createVapidKeys(): array
+ {
+ $curve = NistCurve::curve256();
+ $privateKey = $curve->createPrivateKey();
+ $publicKey = $curve->createPublicKey($privateKey);
+
+ $binaryPublicKey = hex2bin(Utils::serializePublicKey($publicKey));
+ if (!$binaryPublicKey) {
+ throw new \ErrorException('Failed to convert VAPID public key from hexadecimal to binary');
+ }
+
+ $binaryPrivateKey = hex2bin(str_pad(gmp_strval($privateKey->getSecret(), 16), 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)
+ ];
+ }
+}