# Key Lifecycle and Zeroization

quicproquo uses multiple key types with different lifetimes, creation
patterns, and destruction guarantees. This page provides a comprehensive
lifecycle diagram for every key type in the system, from generation through
zeroization.

## Lifecycle Overview

```text
Key Type              Creation            Distribution         Use                    Destruction
--------------------------------------------------------------------------------------------------------------
Ed25519 Identity      Once per client     AS registration      MLS signing,           Zeroizing<[u8;32]>
                      (OsRng)             + MLS credential     credential binding     on struct drop

HPKE Init Key         Per KeyPackage      Uploaded to AS       Decrypt Welcome        Consumed by openmls;
                      (openmls backend)   in KeyPackage        (join_group)           deleted from keystore

MLS Epoch Keys        Per Commit          Internal (ratchet    Encrypt/decrypt        Old epoch keys deleted
                      (openmls ratchet)   tree derivation)     application messages   after processing Commit

Hybrid KEM Keys       Per peer (future)   Public portion       X25519+ML-KEM-768      Ephemeral per encrypt;
                      (OsRng)             sent to peers        hybrid encryption      static part on drop
```

## Ed25519 Identity Key

**Source:** `crates/quicproquo-core/src/identity.rs`

The Ed25519 identity key is the most long-lived secret in the system. It
represents the client's cryptographic identity across all sessions and groups.

### Lifecycle

```text
                    +-----------------+
                    |    OsRng        |
                    |  (getrandom)    |
                    +--------+--------+
                             |
                    generate()
                             |
                    +--------v--------+
                    | IdentityKeypair |
                    | seed: Zeroizing |  <-- 32-byte Ed25519 seed
                    | verifying: Vk   |  <-- 32-byte public key
                    +--------+--------+
                             |
              +--------------+--------------+
              |              |              |
         Persist to     Register with   Embed in MLS
         state file     Auth Service    BasicCredential
         (seed_bytes)   (public_key)    (CredentialWithKey)
              |              |              |
              |              |         Sign KeyPackages,
              |              |         Commits, Proposals
              |              |              |
         Load on next   Server stores   Used for lifetime
         client start   public key      of client
         (from_seed)    as queue index        |
              |              |              |
              +------+-------+--------------+
                     |
            struct dropped
                     |
            +--------v--------+
            |  Zeroizing<T>   |
            |  overwrites     |
            |  seed with 0x00 |
            +-----------------+
```

### Key Properties

- **Generation:** `SigningKey::generate(&mut OsRng)` produces 32 bytes of
  entropy from the OS CSPRNG. The seed is immediately wrapped in `Zeroizing`.
- **Persistence:** The `seed_bytes()` method returns a plain `[u8; 32]` for
  serialization to the client state file. The caller must handle this copy
  securely.
- **Reconstruction:** `from_seed(seed)` re-derives the `VerifyingKey` from the
  seed deterministically. The seed is wrapped in `Zeroizing` upon construction.
- **Destruction:** When the `IdentityKeypair` struct is dropped, the
  `Zeroizing<[u8; 32]>` wrapper overwrites the 32 seed bytes with zeros.
- **No Clone/Copy:** The struct does not implement `Clone` or `Copy`, preventing
  accidental duplication of the secret seed.

### Fingerprint

The fingerprint (`SHA-256(public_key_bytes)`) is derived from the public key and
is used as a compact identifier in logs. It is not secret and does not require
zeroization.

## HPKE Init Keys

**Source:** `crates/quicproquo-core/src/keystore.rs` and
`crates/quicproquo-core/src/group.rs`

HPKE init keys are generated by the openmls backend as part of MLS KeyPackage
creation. They are single-use: each init key is consumed exactly once when
processing a Welcome message.

### Lifecycle

```text
              +----------------------------+
              | generate_key_package()     |
              | (GroupMember method)        |
              +-------------+--------------+
                            |
              openmls internally generates
              HPKE init keypair (X25519)
                            |
              +-------------v--------------+
              | DiskKeyStore / StoreCrypto |
              | stores init private key    |
              | keyed by init key ref      |
              +-------------+--------------+
                            |
              KeyPackage (containing init
              public key) is TLS-encoded
              and uploaded to Auth Service
                            |
              +-------------v--------------+
              | Peer fetches KeyPackage    |
              | from AS; includes it in    |
              | Welcome message            |
              +-------------+--------------+
                            |
              +-------------v--------------+
              | join_group(welcome)        |
              | openmls calls              |
              | new_from_welcome()         |
              | reads init private key     |
              | from DiskKeyStore          |
              | decrypts Welcome's HPKE    |
              | ciphertext                 |
              +-------------+--------------+
                            |
              Init key is consumed
              (never reused per MLS spec)
                            |
              +-------------v--------------+
              | Key deleted from store     |
              | (openmls manages cleanup)  |
              +----------------------------+
```

### Key Properties

- **Generation:** Handled internally by the openmls backend (`StoreCrypto`).
  The `generate_key_package()` method on `GroupMember` triggers this.
- **Storage:** The `DiskKeyStore` is either ephemeral (in-memory `HashMap`) or
  persistent (bincode-serialized to disk). The init private key is stored as a
  JSON-serialized `MlsEntity` keyed by the init key reference bytes.
- **Single use:** Per RFC 9420, each HPKE init key is used exactly once. This
  prevents replay attacks and ensures forward secrecy of the initial key exchange.
- **Critical constraint:** The same `GroupMember` instance (and therefore the same
  `StoreCrypto` backend) that called `generate_key_package()` must later call
  `join_group()`. If a different backend is used, the init private key will not be
  found and `new_from_welcome()` will fail.
- **Persistence mode:** `DiskKeyStore::persistent(path)` writes the entire
  key-value map to disk on every store/delete operation, ensuring HPKE init keys
  survive process restarts.

## MLS Epoch Keys

**Managed by:** `openmls` (internal to the `MlsGroup` state machine)

MLS epoch keys are derived internally by the openmls ratchet tree. They are not
directly accessible in quicproquo code but are critical to understanding the
system's security properties.

### Lifecycle

```text
              +----------------------------+
              | create_group(group_id)     |
              | or join_group(welcome)     |
              +-------------+--------------+
                            |
              Epoch 0: initial key material
              derived from ratchet tree
                            |
              +-------------v--------------+
              | send_message(plaintext)    |
              | encrypts with current      |
              | epoch's application key    |
              +-------------+--------------+
                            |
              +-------------v--------------+
              | add_member() / Commit      |
              | merge_pending_commit()     |
              | or merge_staged_commit()   |
              +-------------+--------------+
                            |
              Epoch advances: new key
              material derived from
              updated ratchet tree
                            |
              +-------------v--------------+
              | Old epoch keys deleted     |
              | by openmls internally      |
              | (forward secrecy)          |
              +----------------------------+
```

### Key Properties

- **Derivation:** Each epoch's key material is derived from the ratchet tree, a
  binary tree where each leaf represents a group member. Internal nodes hold
  derived key material. See
  [Post-Compromise Security](post-compromise-security.md) for details.
- **Advancement:** Epochs advance when a Commit is processed --
  `merge_pending_commit()` (for the sender) or `merge_staged_commit()` (for
  receivers).
- **Deletion:** Old epoch keys are deleted after the Commit is processed. This
  deletion is what provides [forward secrecy](forward-secrecy.md) at the MLS
  layer.
- **No direct access:** quicproquo code interacts with epoch keys only
  indirectly through `send_message()` and `receive_message()`.

## Hybrid KEM Keys (Future -- M5+)

**Source:** `crates/quicproquo-core/src/hybrid_kem.rs`

The hybrid KEM keypair combines X25519 (classical) with ML-KEM-768
(post-quantum) for content encryption that resists both classical and quantum
attacks.

### Lifecycle

```text
              +----------------------------+
              | HybridKeypair::generate()  |
              | OsRng for both components  |
              +-------------+--------------+
                            |
              +-------------v--------------+
              | HybridKeypair              |
              | x25519_sk: StaticSecret    |  32B (ZeroizeOnDrop)
              | x25519_pk: PublicKey       |  32B
              | mlkem_dk: DecapsulationKey |  2400B
              | mlkem_ek: EncapsulationKey |  1184B
              +-------------+--------------+
                            |
              +--------------+--------------+
              |                             |
         public_key()                  to_bytes()
         -> HybridPublicKey            -> HybridKeypairBytes
         (32B + 1184B)                 (for persistence)
              |                             |
         Distributed to peers          Stored securely
         for encryption                (serde Serialize)
              |
         +----v----+
         | Sender  |
         | hybrid_encrypt(pk, pt)      |
         | 1. Ephemeral X25519 DH      |
         | 2. ML-KEM-768 encapsulate   |
         | 3. HKDF(x25519_ss||mlkem_ss)|
         | 4. ChaCha20-Poly1305 AEAD   |
         +----+----+
              |
         Envelope: ver(1) | eph_pk(32) | mlkem_ct(1088) | nonce(12) | ct(var)
              |
         +----v----+
         | Recipient                   |
         | hybrid_decrypt(kp, env)     |
         | 1. X25519 DH with eph_pk    |
         | 2. ML-KEM-768 decapsulate   |
         | 3. HKDF derive same key     |
         | 4. ChaCha20-Poly1305 decrypt|
         +---------+
```

### Key Properties

- **Generation:** Both X25519 and ML-KEM-768 components use `OsRng`.
- **Key sizes:** X25519 secret: 32B, ML-KEM-768 decapsulation key: 2400B,
  encapsulation key: 1184B.
- **Serialization:** `HybridKeypairBytes` (serde) stores all components for
  persistence. `HybridPublicKey` stores only the public portions.
- **Zeroization of IKM:** The combined shared secret (`x25519_ss || mlkem_ss`)
  is wrapped in `Zeroizing<Vec<u8>>` and cleared after HKDF derivation.
- **Ephemeral per encryption:** Each call to `hybrid_encrypt` generates a fresh
  `EphemeralSecret` for X25519, ensuring that even if the static keypair is
  compromised, past encryptions remain protected.
- **Integration timeline:** M5 will integrate this into the MLS crypto provider.
  See [Post-Quantum Readiness](post-quantum-readiness.md).

## Zeroization Summary

| Key Type | Zeroization Mechanism | When |
|----------|----------------------|------|
| Ed25519 seed | `Zeroizing<[u8; 32]>` | `IdentityKeypair` drop |
| Ed25519 seed (accessor) | Plain `[u8; 32]` copy | Caller responsibility |
| HPKE init private | Managed by openmls/`DiskKeyStore` | After Welcome processing |
| MLS epoch keys | Managed by openmls internally | After Commit processing |
| Hybrid IKM | `Zeroizing<Vec<u8>>` | After HKDF derivation |
| Hybrid X25519 static | `ZeroizeOnDrop` (x25519-dalek) | `HybridKeypair` drop |
| Hybrid ephemeral | `EphemeralSecret` (x25519-dalek) | After DH computation |

## Security Considerations

1. **Memory residue:** Zeroization prevents key material from lingering in freed
   memory, but it does not protect against an attacker with live memory access
   (e.g., a debugger or cold-boot attack). Full protection would require
   hardware-backed key storage (e.g., HSM, TPM, or OS keychain), which is not
   yet implemented.

2. **Swap and core dumps:** If the process's memory is swapped to disk or
   written to a core dump, key material may persist on non-volatile storage.
   Mitigations include `mlock()` (not yet implemented) and disabling core dumps.

3. **Compiler optimizations:** The `zeroize` crate uses compiler barriers
   (`core::sync::atomic::compiler_fence`) to prevent the optimizer from eliding
   the zeroing write as a dead store.

4. **Copies via `seed_bytes()`:** The `IdentityKeypair::seed_bytes()` method
   returns a plain `[u8; 32]`. The caller (typically the persistence layer) is
   responsible for zeroizing this copy after writing it to disk.

## Related Pages

- [Cryptography Overview](overview.md) -- algorithm inventory
- [Ed25519 Identity Keys](identity-keys.md) -- identity key details
- [Forward Secrecy](forward-secrecy.md) -- how key deletion enables FS
- [Post-Compromise Security](post-compromise-security.md) -- epoch advancement
- [Post-Quantum Readiness](post-quantum-readiness.md) -- hybrid KEM integration
