Documentation
¶
Overview ¶
Package hpke holds the E2E envelope crypto shared by the daemon (which seals alert plaintext to a device's per-install public key) and the relay (which carries the ciphertext verbatim). The pinned suite, AAD construction, and info string MUST stay byte-for-byte in lockstep with the iOS CryptoKit counterpart (ios/Services/HPKEEnvelope.swift); a mismatch is a silent decrypt failure. This package is wire-format only — it holds no relay state, so the daemon can import it without pulling in the relay server code.
Index ¶
- Constants
- func AAD(installationID, eventID string, sequence uint64, expiresAt int64) []byte
- func KeyPair() (pub, priv []byte, err error)
- func Open(receiverPriv, enc, ciphertext, aad []byte) (plaintext []byte, err error)
- func Seal(receiverPub, plaintext, aad []byte) (enc, ciphertext []byte, err error)
- type Envelope
Constants ¶
const InfoString = "rmote-apns-relay-v1"
Pinned HPKE suite: DHKEM(X25519, HKDF-SHA256) / HKDF-SHA256 / ChaCha20-Poly1305.
CryptoKit (iOS 17+, our deployment floor) exposes exactly ONE X25519 ciphersuite — .Curve25519_SHA256_ChachaPoly — and NO X25519+AES-GCM combo (its AES-GCM suites are AES-256-GCM on the NIST P-curves only). circl's AEAD_AES128GCM therefore has no CryptoKit counterpart and would silently fail cross-language. ChaCha20-Poly1305 is the only AEAD both sides share on X25519. This is the spike outcome (RFC 9180: DHKEM(X25519,HKDF-SHA256),HKDF-SHA256,AEAD=ChaCha20-Poly1305). Both sides MUST use InfoString unchanged — it is part of the HPKE key schedule, so a mismatch is a silent decrypt failure.
Variables ¶
This section is empty.
Functions ¶
func AAD ¶
AAD builds the per-envelope additional data both sides bind into the AEAD. It is reconstructed (not transmitted) from the sibling envelope fields, so a ciphertext sealed for one (installation, event, sequence, expiry) cannot be replayed against another. The format is fixed-width where possible:
aad = installationID || 0x1f || eventID || 0x1f || seq(8 BE) || exp(8 BE)
0x1f (unit separator) delimits the variable strings; the integers are 8-byte big-endian so Go and Swift reproduce identical bytes with no number-formatting ambiguity. Hashing is unnecessary — the AEAD authenticates these bytes.
func KeyPair ¶
KeyPair generates a fresh X25519 HPKE receiver keypair. The public key is the per-installation E2E key iOS advertises (and daemons seal to); the private key stays in the device Keychain and is never sent to the relay or any daemon.
func Open ¶
Open decrypts an HPKE envelope produced by Seal, using receiverPriv (the matching X25519 private key). A mismatch in enc, ciphertext, or aad fails the AEAD tag check — that is how tampering, replay against a different envelope, or a suite/info mismatch surfaces.
func Seal ¶
Seal encrypts plaintext to receiverPub (an X25519 HPKE public key), binding aad. It returns the KEM encapsulation (enc) and the AEAD ciphertext. enc MUST travel alongside ciphertext — the receiver needs it to derive the shared key. Seal is randomized (fresh ephemeral sender key each call), so the same inputs never yield the same output; interop is proven by open-not-by-bytes.
Types ¶
type Envelope ¶
type Envelope struct {
InstallationID string `json:"installation_id"`
KeyID string `json:"key_id"`
ProtoVersion int `json:"proto_version"`
EventID string `json:"event_id"`
Sequence uint64 `json:"sequence"`
CreatedAt int64 `json:"created_at"`
ExpiresAt int64 `json:"expires_at"`
Silent bool `json:"silent,omitempty"` // delivery hint; content remains encrypted
Enc []byte `json:"enc"` // HPKE KEM encapsulation
Ciphertext []byte `json:"ciphertext"` // ChaCha20-Poly1305 ciphertext (HPKE AEAD)
AADHash []byte `json:"aad_hash"` // sha256 of the reconstructed AAD (dedup hint)
}
Envelope is the per-device E2E payload inside POST /v1/push/alerts (contract §4). The daemon builds one per device (sealing the alert plaintext to that device's public key); the relay carries these bytes to APNs verbatim and never has the private key to decrypt them.
enc (the HPKE KEM encapsulation) replaces the retired nonce — under HPKE the AEAD nonce is derived inside the key schedule, so there is no caller-chosen nonce to carry. aad_hash is a relay/store dedup hint; the receiver reconstructs the real AAD from the sibling fields and binds it into the AEAD.