Decision
Content is encrypted with envelope encryption: each blob gets a random symmetric key (K_blob, XChaCha20-Poly1305). The ciphertext is content-addressed and cached normally — one hash, one copy for all clients. An app server, operated by the content provider, gates access: on each play request it wraps K_blob with a rotating epoch key and delivers the envelope over authenticated cdn/keys/v1.
CDN nodes only ever see ciphertext. Closing the epoch key stream revokes access within one epoch (~5 minutes).
Why envelope?
- One copy. Encrypting per-viewer would defeat caching — the whole network would store N copies of every blob for N viewers. Envelope encryption encrypts the key per-session, not the bytes.
- Fast rotation. Re-wrapping
K_blobunder a new epoch key is O(bytes-in-key-envelope), not O(blob-size). - Clean revocation. An access revocation closes the epoch key stream; within one epoch the client can no longer decrypt new requests.
Flow
App server responsibilities
- Stores
K_blobfor every encrypted blob (received from origin at ingest). - Authenticates client sessions (OAuth, session token — content provider’s existing stack).
- Issues envelopes on play requests:
envelope = epoch_key.wrap(K_blob). - Rotates the epoch key every 5 minutes.
- Delivers envelopes over an authenticated
cdn/keys/v1QUIC stream. - Optionally issues offline leases — client-sealed envelopes that the client persists to a local keystore for offline playback.
Why iroh QUIC for cdn/keys/v1?
Key delivery is tightly coupled to the client’s iroh identity. The QUIC handshake provides mutual authentication (client NodeId ↔ app server NodeId) and TLS 1.3 confidentiality in one step — eliminating the need for separate crypto_box_seal + X25519 key management. Clients run a single transport stack for both CDN delivery and key delivery.
0-RTT is rejected here
cdn/keys/v1 does not accept 0-RTT early data. The protocol requires EpochKeyAuth before any request stream, and 0-RTT would break that authentication sequencing.
Trust boundary and blast radius
- CDN nodes only see ciphertext. Compromise of a node does not leak plaintext.
- App server compromise exposes all content served through that app server —
K_blobkeys are long-lived there. Mitigation: epoch key rotation limits access-control blast radius, but does not provide forward secrecy for storedK_blob. Accepted trade-off. - Epoch key forward secrecy is a protocol property: epoch keys are ephemeral and rotate every 5 minutes, so a compromised epoch key cannot decrypt past envelopes retained by other clients.