# ADR-005: Single-Use KeyPackages **Status:** Accepted --- ## Context MLS (RFC 9420) specifies that KeyPackages must be used at most once. A KeyPackage contains the client's HPKE init key, which is used during the `add_members()` operation to encrypt the Welcome message. If the same KeyPackage is used twice, the same HPKE shared secret is derived for both group additions, which destroys the forward secrecy of the initial key exchange. The Authentication Service (AS) stores KeyPackages uploaded by clients and serves them to peers who want to add the client to a group. The design question is: **how should the AS enforce single-use semantics?** ### Alternatives considered 1. **Mark-as-used.** The AS could keep a "used" flag on each KeyPackage and reject subsequent fetch requests for packages already marked as used. This preserves the package on the server (for auditing or retry) but requires additional state tracking and introduces a race condition: if two peers fetch the same package concurrently, one of them will receive a "used" package unless the flag is set atomically with the first fetch. 2. **Reference counting.** The AS could allow a KeyPackage to be fetched a configurable number of times. This would support use cases like "allow the same package to be used in N group additions." However, MLS requires strict single-use, making this approach non-compliant. 3. **Atomic removal on fetch.** The AS removes the KeyPackage from storage in the same operation that returns it. The first fetch succeeds and returns the package; subsequent fetches for the same package find nothing. This is the simplest approach and provides the strongest guarantee. --- ## Decision The Authentication Service **atomically removes** a KeyPackage when it is fetched. The `fetchKeyPackage` method is destructive: it returns the package and deletes it in a single operation. If no packages are stored for the requested identity, an empty response is returned. ### Implementation The server stores KeyPackages in a per-identity queue (currently backed by `DashMap` with `Vec>` values). The `fetchKeyPackage` operation: 1. Locks the entry for the requested identity key. 2. Pops the first KeyPackage from the queue (FIFO order). 3. Returns the popped package. 4. The lock is released. If the queue is empty (or no entry exists for the identity key), the method returns empty `Data`. ```text Before fetch: identity_key_0x1234 -> [KP_1, KP_2, KP_3] After fetchKeyPackage(identity_key=0x1234): Returns: KP_1 identity_key_0x1234 -> [KP_2, KP_3] ``` ### Client responsibilities Because the AS consumes KeyPackages on fetch, clients must manage their KeyPackage supply: 1. **Pre-upload multiple KeyPackages.** After generating their identity, a client should upload several KeyPackages (e.g., 10-100) so that multiple peers can add them to groups concurrently. 2. **Monitor supply.** Clients should periodically check (via a future monitoring endpoint or heuristic) whether their KeyPackage supply on the server is running low, and replenish by uploading more. 3. **Handle empty responses.** A client trying to add a peer whose KeyPackage supply is exhausted will receive an empty response from `fetchKeyPackage`. The client should handle this gracefully -- e.g., by notifying the user that the peer needs to upload more KeyPackages. ### Fingerprint for tamper detection The `uploadKeyPackage` method returns a SHA-256 fingerprint of the uploaded package. This fingerprint serves as a tamper-detection mechanism: 1. The uploading client records the fingerprint. 2. When a peer fetches the KeyPackage, they can compute the SHA-256 hash of the received package. 3. If the fetched package's hash does not match the expected fingerprint (communicated out-of-band), the server may have tampered with the package. This is a defense-in-depth measure. In practice, MLS's own signature verification on KeyPackages also detects tampering, since the KeyPackage includes a signature over its contents using the uploader's Ed25519 identity key. --- ## Consequences ### Benefits - **Forward secrecy of initial key exchange.** Each `add_members()` operation uses a fresh HPKE init key, so the shared secret derived from the Welcome message is unique. Compromising one group addition does not compromise others. - **Simplicity.** Atomic removal is the simplest possible implementation of single-use semantics. There is no "used" flag, no reference count, no expiration timer. The package is either in the store (available) or not (consumed). - **No race conditions.** Because removal is atomic with fetch, two concurrent fetches for the same identity key will each receive a different KeyPackage (or one will receive an empty response if only one package remains). There is no window where two fetchers could receive the same package. - **Compliance with RFC 9420.** The single-use semantics are a direct implementation of MLS's requirement that each KeyPackage's HPKE init key be used at most once. ### Costs and trade-offs - **Client must manage supply.** Unlike a reusable credential, single-use KeyPackages are a consumable resource. Clients must proactively upload packages and monitor their supply. A client that goes offline for an extended period may exhaust its supply, becoming unreachable for new group additions. - **No retry after fetch.** If a client fetches a KeyPackage and then fails to complete the `add_members()` operation (e.g., due to a crash or network error), the KeyPackage is consumed and wasted. The client must fetch a new one and retry. - **Storage scaling.** If each client uploads N KeyPackages and there are M clients, the AS must store up to M * N packages. For reasonable values (e.g., 1000 clients, 100 packages each), this is 100,000 packages -- well within the capacity of an in-memory store. For larger deployments, persistent storage would be needed. ### Residual risks - **KeyPackage exhaustion attack.** A malicious client could repeatedly fetch a target's KeyPackages without using them, draining the target's supply and preventing legitimate peers from adding the target to groups. Mitigation: rate limiting on `fetchKeyPackage` calls (planned for a future milestone) and the `Auth` struct for identifying and blocking abusive clients. - **Server-side compromise.** If the AS is compromised, the attacker could read stored KeyPackages and use the HPKE init keys to decrypt future Welcome messages. Mitigation: this is inherent to any prekey distribution service (Signal has the same risk with X3DH prekey bundles). MLS's post-compromise security means that even if the initial key exchange is compromised, subsequent epoch updates restore security. --- ## Code references | File | Relevance | |---|---| | `schemas/auth.capnp` | `AuthenticationService` interface: `uploadKeyPackage`, `fetchKeyPackage` | | `schemas/node.capnp` | `NodeService` interface: same methods with `Auth` parameter | | `crates/quicprochat-server/src/storage.rs` | Server-side KeyPackage storage (DashMap-backed queue) | | `crates/quicprochat-server/src/main.rs` | RPC handler: `fetchKeyPackage` implementation with atomic removal | --- ## Further reading - [Design Decisions Overview](overview.md) -- index of all ADRs - [Auth Schema](../wire-format/auth-schema.md) -- the RPC interface for KeyPackage operations - [NodeService Schema](../wire-format/node-service-schema.md) -- the unified interface including auth methods - [ADR-004: MLS-Unaware Delivery Service](adr-004-mls-unaware-ds.md) -- related design decision for the DS - [Architecture Overview](../architecture/overview.md) -- system-level view showing the AS in context