diff options
author | Anton Luka Šijanec <anton@sijanec.eu> | 2022-01-11 12:35:47 +0100 |
---|---|---|
committer | Anton Luka Šijanec <anton@sijanec.eu> | 2022-01-11 12:35:47 +0100 |
commit | 19985dbb8c0aa66dc4bf7905abc1148de909097d (patch) | |
tree | 2cd5a5d20d7e80fc2a51adf60d838d8a2c40999e /vendor/minishlink/web-push/src/VAPID.php | |
download | 1ka-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.php | 197 |
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) + ]; + } +} |