hpke

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

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

View Source
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

func AAD(installationID, eventID string, sequence uint64, expiresAt int64) []byte

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

func KeyPair() (pub, priv []byte, err error)

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

func Open(receiverPriv, enc, ciphertext, aad []byte) (plaintext []byte, err error)

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

func Seal(receiverPub, plaintext, aad []byte) (enc, ciphertext []byte, err error)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL