Help us shape the future of Go! Take the 2026 Go Developer Survey

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