From 75160b12821f7f4299cce7f0b69c83c1502ae071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Luka=20=C5=A0ijanec?= Date: Mon, 27 May 2024 13:08:29 +0200 Subject: 2024-02-19 upstream --- vendor/minishlink/web-push/src/VAPID.php | 394 +++++++++++++++---------------- 1 file changed, 197 insertions(+), 197 deletions(-) (limited to 'vendor/minishlink/web-push/src/VAPID.php') diff --git a/vendor/minishlink/web-push/src/VAPID.php b/vendor/minishlink/web-push/src/VAPID.php index c741ec9..e1f555f 100644 --- a/vendor/minishlink/web-push/src/VAPID.php +++ b/vendor/minishlink/web-push/src/VAPID.php @@ -1,197 +1,197 @@ - - * - * 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) - ]; - } -} + + * + * 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) + ]; + } +} -- cgit v1.2.3