service/WebPushPayloadCodec.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QByteArray> | ||
| 4 | #include <QString> | ||
| 5 | #include <optional> | ||
| 6 | |||
| 7 | // Sprint 96 S96.2.2: Web Push message encryption (RFC 8291 on RFC 8188 | ||
| 8 | // "aes128gcm") for DAV-Push receivers. Pure functions; no DAV, provider or | ||
| 9 | // UI state. Cryptographic primitives come from the system OpenSSL | ||
| 10 | // (OpenSslRuntime, ADR-96-3); HKDF/HMAC-SHA-256 from Qt. | ||
| 11 | // | ||
| 12 | // Invariants: | ||
| 13 | // - A payload is accepted only after a successful AES-GCM tag check; | ||
| 14 | // encrypted bytes alone never count as a valid push message. | ||
| 15 | // - Bounded: at most kMaxEncryptedBytes in, kMaxPlaintextBytes out, one | ||
| 16 | // record (RFC 8291 §4), a 65-byte uncompressed P-256 sender key. | ||
| 17 | namespace WebPushPayloadCodec { | ||
| 18 | |||
| 19 | constexpr int kMaxEncryptedBytes = 16 * 1024 + 256; | ||
| 20 | constexpr int kMaxPlaintextBytes = 16 * 1024; | ||
| 21 | constexpr int kPublicKeyBytes = 65; // uncompressed P-256 point | ||
| 22 | constexpr int kAuthSecretBytes = 16; | ||
| 23 | |||
| 24 | // The receiver's key material of one subscription. privateKeyDer is the | ||
| 25 | // OpenSSL DER encoding (secret, keyring only); publicKey the uncompressed | ||
| 26 | // point sent as subscription-public-key; authSecret the RFC 8291 secret. | ||
| 27 | struct Keys { | ||
| 28 | QByteArray privateKeyDer; | ||
| 29 | QByteArray publicKey; | ||
| 30 | QByteArray authSecret; | ||
| 31 | 87 | bool isValid() const { | |
| 32 |
4/6✓ Branch 3 → 4 taken 86 times.
✓ Branch 3 → 9 taken 1 time.
✓ Branch 5 → 6 taken 86 times.
✗ Branch 5 → 9 not taken.
✓ Branch 7 → 8 taken 86 times.
✗ Branch 7 → 9 not taken.
|
173 | return !privateKeyDer.isEmpty() && publicKey.size() == kPublicKeyBytes && |
| 33 | 173 | authSecret.size() == kAuthSecretBytes; | |
| 34 | } | ||
| 35 | }; | ||
| 36 | |||
| 37 | // True when the OpenSSL primitives are loadable; `error` names the reason. | ||
| 38 | bool available(QString *error = nullptr); | ||
| 39 | |||
| 40 | // A fresh P-256 key pair plus a 16-byte auth secret from the system CSPRNG. | ||
| 41 | std::optional<Keys> generateKeys(QString *error = nullptr); | ||
| 42 | |||
| 43 | // Decrypts one aes128gcm Web Push body for `keys`. nullopt with a typed | ||
| 44 | // reason in `error` for truncation, wrong key/tag, invalid sender point, | ||
| 45 | // unsupported record layout or padding errors. | ||
| 46 | std::optional<QByteArray> decrypt(const QByteArray &body, const Keys &keys, QString *error = nullptr); | ||
| 47 | |||
| 48 | // The push-server side (tests, fake providers): encrypts `plaintext` for a | ||
| 49 | // receiver. `salt` (16 bytes) and `senderPrivateKeyDer` may be given for | ||
| 50 | // deterministic vectors; otherwise they are random / generated. | ||
| 51 | std::optional<QByteArray> encrypt(const QByteArray &plaintext, const QByteArray &receiverPublicKey, | ||
| 52 | const QByteArray &authSecret, QString *error = nullptr, | ||
| 53 | const QByteArray &salt = {}, const QByteArray &senderPrivateKeyDer = {}); | ||
| 54 | |||
| 55 | // Strict unpadded base64url (RFC 4648 §5) as used by Web Push; padding and | ||
| 56 | // characters outside the URL alphabet are rejected. | ||
| 57 | std::optional<QByteArray> decodeBase64Url(const QString &text); | ||
| 58 | QString encodeBase64Url(const QByteArray &bytes); | ||
| 59 | |||
| 60 | // A P-256 private key (DER) from its raw 32-byte scalar and public point — | ||
| 61 | // for published test vectors only. | ||
| 62 | std::optional<QByteArray> privateKeyFromRaw(const QByteArray &scalar, const QByteArray &publicKey); | ||
| 63 | |||
| 64 | } // namespace WebPushPayloadCodec | ||
| 65 |