$user = UserProfileRuntimeCache::getInstance()->getObject($serviceWorker->userID);
$style = new Style($user->styleID);
if (!$style->styleID) {
- $style = StyleHandler::getInstance()->getDefaultStyle();
+ $style = StyleHandler::getInstance()->getStyle();
}
/** @see NotificationEmailDeliveryBackgroundJob::perform() */
ServiceWorkerHandler::getInstance()->sendToServiceWorker($serviceWorker, JSON::encode($content));
} catch (ClientExceptionInterface $e) {
+ \wcfDebug($e->getCode(), $e);
if ($e->getCode() === 413) {
// Payload too large, we can't do anything here other than discard the message.
\wcf\functions\exception\logThrowable($e);
use Jose\Component\Core\JWK;
use Jose\Component\Core\Util\ECKey;
use Jose\Component\KeyManagement\JWKFactory;
+use Laminas\Diactoros\Stream;
use ParagonIE\ConstantTime\Base64;
use ParagonIE\ConstantTime\Base64UrlSafe;
+use Psr\Http\Message\RequestInterface;
use wcf\data\service\worker\ServiceWorker;
/**
public const HASH_ALGORITHM = 'sha256';
/**
- * Return the encrypted payload and necessary headers to send a push message to the push-service.
+ * Return a request with the encrypted payload and add necessary headers.
* {@link https://www.rfc-editor.org/rfc/rfc8291}
*
* @param ServiceWorker $serviceWorker
* @param string $payload
- * @param array $headers to this array the encryption headers will be added
- * @return string the encrypted payload
+ * @param RequestInterface $request
+ * @return RequestInterface the new request with the encrypted payload
*/
public static function encrypt(
ServiceWorker $serviceWorker,
#[\SensitiveParameter]
string $payload,
- array &$headers
- ): string {
+ RequestInterface $request,
+ ): RequestInterface {
// Section 3.1
// user
$userPublicKey = Base64Url::decode($serviceWorker->publicKey);
}
$record = $encryptedText . $tag;
- return Encryption::getEncryptionContentCodingHeader(
+ $body = new Stream('php://temp', 'wb+');
+ $body->write(Encryption::getEncryptionContentCodingHeader(
$serviceWorker->contentEncoding,
\mb_strlen($record, '8bit'),
$salt,
$newPublicKey
- ) . $record;
+ ) . $record);
+ $body->rewind();
+
+ return $request->withBody($body)->withHeader('content-length', \mb_strlen($body, '8bit'));
}
private static function getSharedSecret(JWK $publicKey, #[\SensitiveParameter] JWK $privateKey): string
} elseif ($contentEncoding === ServiceWorker::CONTENT_ENCODING_AESGCM) {
return \pack('n*', $paddingLength) . \str_pad($payload, $paddingLength + $length, "\x00", STR_PAD_LEFT);
} else {
- throw new \RuntimeException('Unknown content encoding "' . $contentEncoding . '"');
+ throw new \RuntimeException("Unknown content encoding '{$contentEncoding}'");
}
}
} elseif ($contentEncoding === ServiceWorker::CONTENT_ENCODING_AES128GCM) {
return "Content-Encoding: {$type}\x00";
} else {
- throw new \RuntimeException('Unknown content encoding "' . $contentEncoding . '"');
+ throw new \RuntimeException("Unknown content encoding '{$contentEncoding}'");
}
}
} elseif ($contentEncoding === ServiceWorker::CONTENT_ENCODING_AESGCM) {
return "";
} else {
- throw new \RuntimeException('Unknown content encoding "' . $contentEncoding . '"');
+ throw new \RuntimeException("Unknown content encoding '{$contentEncoding}'");
}
}
} elseif ($serviceWorker->contentEncoding === ServiceWorker::CONTENT_ENCODING_AES128GCM) {
$info = "WebPush: info\x00" . $userPublicKey . $newPublicKey;
} else {
- throw new \RuntimeException('Unknown content encoding "' . $serviceWorker->contentEncoding . '"');
+ throw new \RuntimeException("Unknown content encoding '{$serviceWorker->contentEncoding}'");
}
return \hash_hkdf(
);
}
- $headers = [
+ $request = new Request('POST', $serviceWorker->endpoint, [
'content-type' => 'application/octet-stream',
'content-encoding' => $serviceWorker->contentEncoding,
'ttl' => ServiceWorkerHandler::TTL,
- ];
- $content = Encryption::encrypt(
+ ]);
+
+ $request = Encryption::encrypt(
$serviceWorker,
$payload,
- $headers
+ $request
);
- $headers['content-length'] = \mb_strlen($content, '8bit');
- VAPID::addHeader($serviceWorker, $headers);
+ $request = VAPID::addHeader($serviceWorker, $request);
- $this->getClient()->send(
- new Request(
- 'POST',
- $serviceWorker->endpoint,
- $headers,
- $content
- )
- );
+ $this->getClient()->send($request);
}
private function getClient(): ClientInterface
use Base64Url\Base64Url;
use Jose\Component\Core\JWK;
use ParagonIE\ConstantTime\Binary;
+use Psr\Http\Message\RequestInterface;
/**
* @author Olaf Braun
*
* @return JWK
*/
- public static function createJWK(?string $x = null, string $y = null, #[\SensitiveParameter] ?string $d = null): JWK
- {
+ public static function createJWK(
+ ?string $x = null,
+ ?string $y = null,
+ #[\SensitiveParameter] ?string $d = null
+ ): JWK {
+ \assert(
+ ($x === null && $y === null) || ($x !== null && $y !== null),
+ "Both x and y must be set or both must be null."
+ );
+
$values = [
'kty' => 'EC',
'crv' => Encryption::CURVE_ALGORITHM,
return new JWK($values);
}
+
+ /**
+ * Return a request with an updated crypto-key header.
+ * This header needs a `;` to separate multiple keys.
+ *
+ * @param RequestInterface $request
+ * @param string $name
+ * @param string $value
+ *
+ * @return RequestInterface
+ */
+ public static function updateCryptoKeyHeader(
+ RequestInterface $request,
+ string $name,
+ string $value
+ ): RequestInterface {
+ return $request->withHeader('crypto-key', \implode(';', [
+ ...$request->getHeader('crypto-key'),
+ \sprintf('%s=%s', $name, $value)
+ ]));
+ }
}
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\Signature\Serializer\CompactSerializer;
use ParagonIE\ConstantTime\Base64UrlSafe;
+use Psr\Http\Message\RequestInterface;
use wcf\data\service\worker\ServiceWorker;
use wcf\util\JSON;
public const PRIVATE_KEY_LENGTH = 32;
/**
- * Add the VAPID header to the request headers.
+ * Return a request with the VAPID header.
* {@link https://www.rfc-editor.org/rfc/rfc8282}
*
* @param ServiceWorker $serviceWorker
- * @param array $headers
+ * @param RequestInterface $request
+ *
+ * @return RequestInterface
*/
- public static function addHeader(ServiceWorker $serviceWorker, array &$headers): void
+ public static function addHeader(ServiceWorker $serviceWorker, RequestInterface $request): RequestInterface
{
$rawPublicKey = Base64Url::decode(SERVICE_WORKER_PUBLIC_KEY);
// Validate the length of the public key
$jwt = $compactSerializer->serialize($jws, 0);
if ($serviceWorker->contentEncoding === ServiceWorker::CONTENT_ENCODING_AESGCM) {
- $headers['Authorization'] = 'WebPush ' . $jwt;
- $headers['Crypto-Key'] .= ';p256ecdsa=' . SERVICE_WORKER_PUBLIC_KEY;
+ $request = $request->withHeader('authorization', "WebPush {$jwt}");
+ return Util::updateCryptoKeyHeader($request, 'p256ecdsa', SERVICE_WORKER_PUBLIC_KEY);
} elseif ($serviceWorker->contentEncoding === ServiceWorker::CONTENT_ENCODING_AES128GCM) {
- $headers['Authorization'] = 'vapid t=' . $jwt . ', k=' . SERVICE_WORKER_PUBLIC_KEY;
+ return $request->withHeader('authorization', \sprintf("vapid t=%s, k=%s", $jwt, SERVICE_WORKER_PUBLIC_KEY));
} else {
throw new \InvalidArgumentException('Invalid content encoding: "' . $serviceWorker->contentEncoding . '"');
}