# Proposal: Cryptography and Key Management

Capability-native abstractions for cryptographic keys and key sources.
Keys are capability objects; key material never crosses cap
boundaries. One interface serves every consumer — volume encryption,
TLS, code signing, instance identity, authenticated backups, per-service
secrets.

## Implementation Status

This proposal is partially implemented. `schema/capos.capnp` now contains the
minimal `SymmetricKey`, `PrivateKey`, and `PublicKey` ABI plus a RAM-only
`KeyVault` subset needed by the TLS/ACME precursor. `capos-tls` provides
host-tested RAM-only XChaCha20 plus HMAC-SHA256 authenticated encryption,
HMAC-SHA256 MAC/verify, and P-256 signing cores. A development-only software
`KeySource` bootstrap now mints TLS and ACME
account key handles for local proofs, labels the source as non-production, and
is rejected by production/public profiles. The implemented key surface requires
an explicit requested `KeyPurpose`, exports only public material (`spkiDer` and
P-256 JWK for ACME account JWS registration), lists non-secret vault/source
metadata, and has no raw symmetric or private-key export surface. There is
still no runtime key service, persistence,
hardware/cloud custody, symmetric-key derivation or wrapping, ACME protocol,
TLS server handshake, or production `KeySource`.

The first implementation chain is the narrow TLS/ACME precursor owned by
[`certificates-tls.md`](../backlog/certificates-tls.md):

- `crypto-privatekey-publickey-ram-signing-local-proof` -- done 2026-06-04:
  minimal
  `PrivateKey` / `PublicKey` schema and RAM signing proof for TLS server keys
  and ACME account JWS keys.
- `crypto-keyvault-ram-privatekey-custody-local-proof` -- done 2026-06-05:
  RAM-only `KeyVault` handles for those private keys, with generation,
  open/list/destroy, purpose separation, and stale-handle failure.
- `crypto-development-keysource-tls-acme-bootstrap-local-proof` --
  done 2026-06-05: development-only software `KeySource` bootstrap for local
  TLS/ACME proofs, rejected for production/public profiles.

That precursor intentionally excludes persistent storage, TPM, cloud KMS,
passphrase/passkey unlock, raw private-key import, ACME protocol, and TLS server
handshakes. It also tightens the TLS/ACME invariant: raw private-key material is
not written to manifests, boot images, logs, task records, or evidence.

The capability-infrastructure reconciliation
(`cap-infra-crypto-key-caps-phase1-reconcile-local-proof`, done 2026-06-06)
added the minimal RAM-only `SymmetricKey` ABI and local proof for XChaCha20 plus
HMAC-SHA256 authenticated encryption and HMAC-SHA256 MAC/verify. It follows the
same RAM-only rule for symmetric key bytes and adds no key export, persistence,
wrapping, or production custody.


## Problem

Nearly every forthcoming capOS subsystem wants cryptography. A
partial list:

- Volume encryption at rest
  ([volume-encryption-proposal.md](volume-encryption-proposal.md)).
- TLS termination in the web text shell gateway
  ([boot-to-shell-proposal.md](boot-to-shell-proposal.md)).
- Inter-service mTLS on a multi-host capability graph
  ([networking-proposal.md](networking-proposal.md)).
- Instance identity tokens (signed JWTs) produced from cloud
  hypervisor metadata
  ([cloud-metadata-proposal.md](cloud-metadata-proposal.md)).
- WebAuthn/passkey public-key verification for login.
- Signed audit logs
  ([system-monitoring-proposal.md](system-monitoring-proposal.md)).
- Signed boot manifests and measured boot
  ([storage-and-naming-proposal.md](storage-and-naming-proposal.md)
  Open Question #5).
- Cloud KMS integration (envelope encryption for volumes and object
  stores).
- Future: signed release artifacts, encrypted swap, session tokens.

Without a shared abstraction each of these invents its own key
interface, its own "where does the key live" story, and its own audit
trail. That is how Linux ended up with `dm-crypt`, `fscrypt`,
`keyctl`, `PKCS#11`, `ssh-agent`, `gpg-agent`, `systemd-creds`, TPM
tools, and cloud-specific SDKs as mutually-unaware silos. capOS is
young enough to avoid that.

## Design Principle: Keys Are Capabilities

In every Unix-lineage system, a key is a byte string — a secret stored
somewhere (keyring, file, memory, HSM handle), protected by a
mechanism orthogonal to the system's main abstractions (syscalls +
files + processes). Every new subsystem therefore invents a new
protection mechanism.

In capOS, a key is a capability object. Holding a `SymmetricKey` or
`PrivateKey` cap means "you may compute with this key." It does not
mean "you may see this key." Key material lives in the address space
of the service that implements the cap; callers reach it by invoking
methods.

Consequences:

- **Attenuation falls out of the capability model.** A decrypt-only
  `SymmetricKey` is a wrapper CapObject that rejects `encrypt`. A
  key bound to a single AAD domain is a wrapper that fixes the `aad`
  argument. A sign-only `PrivateKey` is a wrapper that rejects
  `decrypt`. No new kernel mechanism is needed.
- **Revocation is a cap drop.** Drop the cap, the key is gone from
  that holder's reach. Other holders are unaffected.
- **Audit is intrinsic.** Every method invocation can flow through an
  audit cap. A malicious service granted `decrypt` authority
  generates audit records for every use; it cannot exfiltrate the raw
  key material silently.
- **Hardware isolation composes cleanly.** A TPM-backed key service
  implements the same `PrivateKey` interface as an in-process
  software key service; callers cannot distinguish, and should not
  need to.

A service granted a `SymmetricKey` with both `encrypt` and `decrypt`
can still run arbitrary oracle queries against the key. That is
weaker than "the key material never leaves an HSM" and stronger than
"the key is a byte string in the process heap." When stronger
containment is required, the key service is a thin process sitting on
top of a hardware primitive (TPM, Secure Enclave, cloud KMS).

## Schemas

### Symmetric keys

```capnp
interface SymmetricKey {
    # Authenticated encryption. The Phase-1 RAM implementation supports
    # `xchacha20HmacSha256` only: XChaCha20 stream encryption with HMAC-SHA256
    # authentication. It generates a fresh nonce internally and returns
    # ciphertext plus tag separately so callers cannot choose nonce reuse.
    encrypt @0 (plaintext :Data, aad :Data, purpose :KeyPurpose)
            -> (ciphertext :Data, nonce :Data, tag :Data);

    # Authenticated decryption. `aad`, `nonce`, and `tag` must match the values
    # from `encrypt`; failures return an application error, not plaintext.
    decrypt @1 (ciphertext :Data,
                nonce :Data,
                tag :Data,
                aad :Data,
                purpose :KeyPurpose)
            -> (plaintext :Data);

    # MAC-only modes for keys with `KeyPurpose.integrity`.
    mac    @2 (message :Data, purpose :KeyPurpose) -> (tag :Data);
    verify @3 (message :Data, tag :Data, purpose :KeyPurpose) -> (ok :Bool);

    info @4 () -> (algorithm :SymmetricAlgorithm,
                   purpose :KeyPurpose,
                   identifier :Data);
}

enum SymmetricAlgorithm {
    aes256Gcm         @0;
    aes256GcmSiv      @1;
    xchacha20Poly1305 @2;
    aes256Xts         @3;  # block-device only; no authentication
    hmacSha256        @4;  # mac/verify only
    hmacSha384        @5;
    hmacSha512        @6;
    xchacha20HmacSha256 @7;  # landed local proof construction
}
```

Subkey derivation and key wrap/unwrap remain outside the landed Phase 1 ABI.
Later slices that add them must allocate new method ordinals after `info @4`
instead of reusing the Phase 1 slots.

### Asymmetric keys

```capnp
interface PublicKey {
    # Verify only for the requested purpose. A public key derived from a
    # TLS certificate key rejects an ACME account verification request, and
    # vice versa.
    verify    @0 (message :Data,
                  signature :Data,
                  scheme :SignatureScheme,
                  purpose :KeyPurpose)
              -> (ok :Bool);
    # Export raw public material (SPKI DER, JWK, OpenSSH, PGP) for
    # callers that need to distribute it. Public material is freely
    # shareable; the cap itself is an authority only to invoke
    # methods, not to "own" the public key.
    export    @1 (format :PublicKeyFormat) -> (encoded :Data);
    info      @2 () -> (algorithm :AsymmetricAlgorithm,
                        purpose :KeyPurpose,
                        identifier :Data);
}

interface PrivateKey {
    # Sign only for the requested purpose. The first implementation accepts
    # P-256 with `default` / `ecdsaSha256` and rejects other schemes.
    sign      @0 (message :Data,
                  scheme :SignatureScheme,
                  purpose :KeyPurpose)
              -> (signature :Data);
    public    @1 () -> (pk :PublicKey);
    info      @2 () -> (algorithm :AsymmetricAlgorithm,
                        purpose :KeyPurpose,
                        identifier :Data);
}

enum AsymmetricAlgorithm {
    ed25519      @0;
    x25519       @1;
    p256         @2;
    p384         @3;
    rsa2048      @4;
    rsa3072      @5;
    rsa4096      @6;
    # Post-quantum placeholders; added as capOS ships them.
    mlKem768     @7;  # ML-KEM (Kyber) for KEM
    mlDsa65      @8;  # ML-DSA (Dilithium) for signatures
}

enum SignatureScheme {
    default      @0;  # algorithm's natural default (Ed25519 pure, RSA-PSS, etc.)
    ecdsaSha256  @1;
    ecdsaSha384  @2;
    rsaPssSha256 @3;
    rsaPssSha512 @4;
    rsaPkcs1Sha256 @5;  # for compatibility only
}

enum PublicKeyFormat {
    spkiDer     @0;
    jwk         @1;
    opensshWire @2;
    pgpPacket   @3;
}
```

### Shared metadata

```capnp
enum KeyPurpose {
    generic       @0;
    blockVolume   @1;
    objectStore   @2;
    envelope      @3;   # KEK — only wraps/unwraps
    integrity     @4;   # MAC-only
    tls           @5;
    codeSigning   @6;
    instanceIdentity @7;
    authToken     @8;   # session tokens, JWTs
    webauthn      @9;
    audit         @10;
    oauthClientAssertion @11;  # RFC 7523 private_key_jwt client auth
    oidcIdToken   @12;          # IdP-side ID token signing (LocalIdentityProvider)
    dpopBinding   @13;          # RFC 9449 proof-of-possession keypairs
    acmeAccount   @14;          # RFC 8555 account JWS signing
}
```

`identifier` (bytes in `info()`) is an opaque, stable handle usable
for logging, correlating audit records, and looking up the key in a
`KeyVault`. It is not a secret. It is not a cryptographic hash of the
key (that would let an attacker confirm a guessed key); it is a
random ID chosen at key creation.

### Key sources

A `KeySource` produces keys given some unlock context. Different
implementations realize different trust models.

```capnp
interface KeySource {
    # Produce a key given an unlock context (passphrase bytes, a
    # passkey assertion, a sealed blob, an attestation report, empty
    # for sources that hold keys directly).
    unlockSymmetric @0 (context :Data, purpose :KeyPurpose)
                    -> (key :SymmetricKey);
    unlockPrivate   @1 (context :Data, purpose :KeyPurpose)
                    -> (key :PrivateKey);

    # Seal a key under this source's policy. The returned blob can be
    # stored in the clear; unlock will refuse to produce the key
    # unless its policy is satisfied.
    sealSymmetric @2 (key :SymmetricKey, policy :SealPolicy)
                  -> (blob :Data);
    sealPrivate   @3 (key :PrivateKey, policy :SealPolicy)
                  -> (blob :Data);

    # Rewrap: unseal under current policy, reseal under new policy.
    # Used for KEK rotation without touching the underlying key.
    rewrap @4 (blob :Data, newPolicy :SealPolicy) -> (newBlob :Data);

    info @5 () -> (kind :KeySourceKind, identifier :Data);
}

enum KeySourceKind {
    manifestEmbedded @0;  # dev/CI only
    passphrase       @1;
    passkeyPrf       @2;  # WebAuthn PRF extension
    tpm2             @3;
    secureEnclave    @4;
    cloudKms         @5;
    attestation      @6;  # SEV-SNP / TDX / Nitro
    network          @7;  # Tang/Clevis-style
    softwareStored   @8;  # encrypted-at-rest in a KeyVault
    oidcFederated    @9;  # OIDC AccessToken -> KMS / remote unlock, no baked creds
}

struct SealPolicy {
    union {
        none          @0 :Void;
        pcr           @1 :PcrPolicy;
        kms           @2 :KmsPolicy;
        attested      @3 :AttestationPolicy;
        composite     @4 :List(SealPolicy);  # AND of sub-policies
        tokenExchange @5 :TokenExchangePolicy;  # OIDC/OAuth2-gated unlock
    }
}

struct TokenExchangePolicy {
    # The OIDC issuer whose tokens satisfy this policy.
    issuer          @0 :Text;
    # Required token audience (the KMS / STS endpoint).
    audience        @1 :Text;
    # Required subject predicate. Union allows exact or pattern matches
    # without growing this struct; see oidc-and-oauth2-proposal for the
    # full pattern grammar.
    subjectPattern  @2 :Text;
    # Additional required claims (e.g. `groups`, tenant ID, attestation
    # fields). Values are JSON-encoded bytes.
    requiredClaims  @3 :List(NamedClaim);
    # Acceptable LoA levels mapped from `acr`/`amr`.
    minAuthStrength @4 :UInt8;
}

struct NamedClaim {
    name  @0 :Text;
    value @1 :Data;
}

struct PcrPolicy {
    pcrMask   @0 :UInt32;            # bitmap of PCR indices
    pcrDigest @1 :Data;              # expected composite digest
    bank      @2 :TpmHashBank;
}

struct KmsPolicy {
    provider    @0 :Text;            # "aws", "gcp", "azure", "vault", ...
    keyId       @1 :Text;
    grantTokens @2 :List(Text);
}

struct AttestationPolicy {
    platform        @0 :AttestationPlatform;
    measurement     @1 :Data;
    signerPublicKey @2 :Data;
    allowedVariant  @3 :List(Data);  # e.g. permitted firmware versions
}

enum AttestationPlatform {
    sevSnp @0;
    tdx    @1;
    nitro  @2;
}
```

### Key lifecycle — the `KeyVault`

A `KeyVault` is a stateful service that stores key material, issues
key handles, handles rotation, and emits audit events. It is distinct
from `KeySource`: a `KeySource` is a *factory* producing keys; a
`KeyVault` is a *registry* tracking the keys a deployment knows
about. The schema below is the landed RAM-only TLS/ACME subset. Future
symmetric-key, import, seal-policy, unlock, persistence, and rotation methods
append to this interface; they do not renumber the landed methods.

```capnp
enum KeyMaterialSource {
    ramGenerated @0;
    imported     @1;
    keySource    @2;
}

interface KeyVault {
    generatePrivate @0 (
        algorithm :AsymmetricAlgorithm,
        purpose :KeyPurpose,
        createdAtEpochSeconds :UInt64,
        auditLabel :Text
    ) -> (handle :KeyHandle, key :PrivateKey);

    openPrivate @1 (handle :KeyHandle) -> (key :PrivateKey);

    list @2 (filter :KeyFilter) -> (entries :List(KeyEntry));

    destroy @3 (handle :KeyHandle, reason :Text) -> ();
}

struct KeyHandle {
    identifier @0 :Data;
    generation @1 :UInt64;
}

struct KeyEntry {
    handle @0 :KeyHandle;
    algorithm @1 :AsymmetricAlgorithm;
    purpose @2 :KeyPurpose;
    createdAtEpochSeconds @3 :UInt64;
    lastUsedEpochSeconds @4 :UInt64;
    source @5 :KeyMaterialSource;
    auditLabel @6 :Text;
}

struct KeyFilter {
    purposes @0 :List(KeyPurpose);           # OR
    algorithms @1 :List(AsymmetricAlgorithm); # OR
}
```

## Concrete Key Sources

Not all of these ship on day one. Phases below give a sequence.

### `ManifestEmbeddedKeySource` — development and CI only

Key material baked into `SystemManifest`. Unsealable. Boot-time
validation refuses to build a production-profile image against this
source. Used for QEMU smoke tests and hermetic CI.

Do not use manifest-embedded raw private keys for the TLS/ACME precursor chain.
Those local proofs use a development-only software source that generates key
handles at boot instead, so private key material does not enter manifests,
images, logs, task records, or evidence.

### `PassphraseKeySource` — interactive unlock

Consumes a passphrase from the console login flow
([boot-to-shell-proposal.md](boot-to-shell-proposal.md)), runs
Argon2id with per-source parameters, derives a KEK, unwraps sealed
blobs. No persistent state beyond the salt and KDF parameters (which
are public).

### `PasskeyPrfKeySource` — session unlock from WebAuthn

Consumes a WebAuthn assertion whose `hmac-secret` / PRF extension
yields a per-credential symmetric secret. Derives a KEK from the PRF
output; KEK unwraps the user's sealed DEK. Key material never leaves
the authenticator; the PRF output never leaves the key service
process.

### `Tpm2KeySource` — hardware-bound, measured-boot-gated

A TPM 2.0 driver service holds the TPM; this source wraps it. Seal
policies bind keys to PCR digests; unseal succeeds only if the
running boot chain matches. Enables unattended boot while keeping the
key off the disk.

### `SecureEnclaveKeySource` — platform key stores

Analog for Apple Secure Enclave, Android StrongBox, Intel CSE. Same
interface shape as `Tpm2KeySource`; different backing primitive.

### `CloudKmsKeySource` — cloud envelope encryption

Wraps a cloud KMS (AWS KMS, GCP KMS, Azure Key Vault, HashiCorp
Vault, KMIP). Unlock calls the KMS `Decrypt` operation with a wrapped
DEK and returns the plaintext DEK as a `SymmetricKey` cap. Seal calls
KMS `Encrypt` under a named KEK.

Authentication to KMS uses the `InstanceIdentity` cap from
[cloud-metadata-proposal.md](cloud-metadata-proposal.md); no
long-lived credentials live in the capOS image.

Properties the system gets by following the envelope pattern:

- Free KEK rotation (rewrap the DEK; volume data is untouched).
- Revocation by disabling the KMS key or revoking the IAM grant.
- Cross-account / cross-region access via KMS grants.
- Every unwrap appears in the cloud provider's audit log —
  observability comes for free.

### `AttestationKeySource` — confidential computing

Consumes SEV-SNP, TDX, or Nitro attestation reports. `unlock` submits
the report to a remote verifier (often cloud KMS with attestation
policy) which returns the unwrapped DEK only if the report matches
an approved measurement. Enables "only this specific capOS image,
running on genuine attested hardware, can decrypt this volume."

### `NetworkKeySource` — Tang / Clevis-style

Unlock derives a key by interacting with one or more remote servers;
no single server sees the plaintext key (when combined with secret
sharing). Supports the "revoke access by taking the server offline"
model without physical-access requirements.

### `SoftwareStoredKeySource` — encrypted on disk, under another source

The recursive case: a source whose seal policy points at another
source. Used to compose, e.g., a file-backed key store encrypted under
a TPM-sealed master key. The outer source provides integrity (TPM
seal); the inner source provides convenience (named key lookup).

### `OidcFederatedKeySource` — token-exchange-gated unlock

Derives a key from a short-lived OIDC/OAuth2 access token. The source
holds an `OAuthClient` or `WorkloadIdentityFederation` cap (from
[oidc-and-oauth2-proposal.md](oidc-and-oauth2-proposal.md)). `unlock`
obtains a fresh token for the configured audience — either by
exchanging a local `InstanceIdentity` JWT, a Kubernetes projected
service-account token, or a user session's access token — then
presents it to a remote KMS / STS / custom key service which
returns the wrapped DEK.

Two common shapes:

1. **Cloud KMS with workload identity federation.** Audience is the
   cloud STS; after token exchange the resulting cloud credential
   calls KMS Decrypt. Replaces every baked long-lived cloud IAM
   credential in the image.
2. **Per-user volume.** Audience is a capOS-internal key service;
   the user's `AccessToken` cap proves the caller is Alice; the key
   service enforces `TokenExchangePolicy` and returns Alice's DEK.

Properties the envelope + token-exchange pattern gets the system:

- No long-lived credentials in any capOS image.
- Per-principal KMS audit (the token `sub` appears in every KMS
  decrypt log).
- Revocation by IdP account disable, token revocation, or KMS grant
  removal.
- Step-up authentication gating: a `TokenExchangePolicy` requiring
  `minAuthStrength >= loa3` means Alice must have MFA-backed
  `acr`/`amr` claims before her volume unlocks.

## Consumers

A non-exhaustive list of how this interface is meant to be used. Each
consumer either exists as a proposal or is called out as future work.

| Consumer                         | Interface  | Key source                     |
|---------------------------------|-----------|--------------------------------|
| `EncryptedBlockDevice`          | symmetric | any                            |
| `EncryptedNamespace`            | symmetric | passphrase / passkeyPrf / KMS  |
| TLS termination (web gateway)   | both      | passphrase / KMS / cloud certs |
| SSH host key signing            | private   | KeyVault / softwareStored / KMS |
| SSH public-key login            | public    | CredentialStore / authorized key store |
| mTLS between services           | both      | KeyVault with KMS seal         |
| Instance identity JWT signing   | private   | cloudKms / softwareStored      |
| Signed audit logs               | private   | KeyVault, append-only policy   |
| WebAuthn verification           | public    | CredentialStore (public keys)  |
| Signed boot manifests           | public    | public key baked into firmware |
| Encrypted swap                  | symmetric | per-boot ephemeral (in-RAM)    |
| Encrypted backups               | symmetric | dedicated KMS key              |
| Session tokens (HMAC)           | symmetric | KeyVault, rotated frequently   |

## Relationship to `CredentialStore`

The `CredentialStore` in
[boot-to-shell-proposal.md](boot-to-shell-proposal.md) stores
*verifiers* — WebAuthn public keys, password hashes, recovery codes.
Its job is *authentication*: matching a claim from a user against a
stored verifier.

The `KeyVault` proposed here stores *keys* — symmetric DEKs,
signing private keys, KEKs. Its job is *cryptography*: producing keys
for use by capOS services.

Overlap happens at passkey unlock: the `CredentialStore` verifies the
WebAuthn assertion; the resulting PRF output feeds a
`PasskeyPrfKeySource` that produces a `SymmetricKey` usable by
`EncryptedNamespace`. Two services, one flow.

Keeping these distinct matters because their audit, retention, and
exposure models differ. A `CredentialStore` can expose every stored
entry as metadata (public keys are public) without leaking secrets; a
`KeyVault` cannot. A deployment may want different replication,
backup, and recovery policies for authenticators vs. encryption keys.

## Threat Model

Separate from the consumer-specific threat models, the crypto/key
management service itself has these:

1. **Memory scraping of a live key service.** The service holds
   plaintext keys in RAM. Mitigation: small trusted-computing-base
   (one crate, audited), `mlock` the heap (no swap leakage), zeroize
   on drop, no panic-induced core dumps, cap-scoped access so only
   callers with a `Key` cap can trigger operations. Against a kernel
   exploit, no defense; that is a separate threat.
2. **Oracle abuse.** A malicious service granted a `SymmetricKey` cap
   uses it as a decryption oracle. Mitigation: granting callers
   attenuated caps (`decrypt`-only, `aad`-pinned). Audit records make
   abuse detectable.
3. **Side-channel leakage.** Timing, cache, power. Mitigation: use
   constant-time implementations (`aes` crate's hardware backend;
   `chacha20poly1305` crate is constant-time), prefer AEAD modes that
   resist nonce-reuse gracefully (GCM-SIV), avoid bespoke crypto.
4. **Downgrade attacks on algorithm selection.** A caller requests a
   weak algorithm on a key that supports stronger modes. Mitigation:
   `info()` records the canonical algorithm; `KeyPurpose` constrains
   the method set; algorithm negotiation is the caller's job, not a
   feature of the key cap.
5. **Key persistence in unintended places.** Kernel DMA buffers, swap,
   crash dumps, core files. Mitigations are deployment-level (no
   swap, or encrypted swap with a per-boot key; disable core dumps
   for the key service process; measure the boot chain so a tampered
   kernel is detectable).

## Phases

Phases align with the subsystems that need keys. Crypto primitives
come first; consumers follow their own proposals' phases.

Future asymmetric-key methods such as public-key encryption, private-key
decryption, and key agreement append after this implemented subset in later
slices.

### Phase 1 — Interfaces and RAM-only implementation

- Landed first increment: minimal `PrivateKey` / `PublicKey` interfaces plus
  `AsymmetricAlgorithm`, `SignatureScheme`, `PublicKeyFormat`, and
  `KeyPurpose` in `schema/capos.capnp`, backed by host-tested RAM-only P-256
  signing in `capos-tls`. This proves TLS-vs-ACME purpose separation and public
  export without raw private-key export.
- Landed second increment: RAM-only `KeyVault` generation/open/list/destroy,
  `KeyHandle`, source metadata, audit labels, and stale-handle fail-closed
  behavior for TLS and ACME local proofs.
- Landed third increment: development-only software `KeySource` bootstrap that
  mints TLS and ACME account keys into the RAM `KeyVault` without manifest or
  evidence private-key bytes, and rejects production/public profiles.
- Landed fourth increment: minimal RAM-only `SymmetricKey` ABI plus XChaCha20
  stream encryption with HMAC-SHA256 authentication and HMAC-SHA256 MAC/verify
  cores. The local QEMU proof covers encrypt/decrypt, tag failure, MAC
  verification, purpose failure, and operation denial without logging raw key
  material or generated metadata.
- Remaining Phase 1 surface: production/runtime `KeySource` services,
  symmetric-key derivation and wrapping, and any broader enum/struct metadata
  those services need.
- Implement a RAM-only key service using vetted Rust crates
  (`aes-gcm-siv`, `chacha20poly1305`, `ed25519-dalek`, `x25519-dalek`,
  `p256`, `rsa`, `hmac`, `hkdf`). No persistence. Pure interface exercise.
- `ManifestEmbeddedKeySource` for dev/CI.
- Host tests: AEAD round-trips, signature round-trips, key agreement,
  fuzz the decrypt/verify paths.

### Phase 2 — `KeyVault` with in-memory storage

- Landed local-proof subset: RAM-only key generation, handle-based lookup,
  metadata listing, destroy, and stale-handle refusal.
- Remaining production-oriented surface: sealed blob storage.
- `rotateSeal` implementation (metadata-only KEK rotation).
- Policy enforcement for seal/unseal.
- Audit cap integration
  ([system-monitoring-proposal.md](system-monitoring-proposal.md)).

### Phase 3 — Persistent `KeyVault` over the Store

- Sealed blobs live in a Store or Namespace.
- Access control: `KeyVault` cap is itself attenuable (read-only,
  purpose-filtered).
- Cross-reboot survival requires the Store, which requires persistent
  storage tracked in `docs/roadmap.md`.

### Phase 4 — `PassphraseKeySource` and `PasskeyPrfKeySource`

- Passphrase flow wires into console login.
- PasskeyPRF flow wires into WebAuthn assertions from the web text
  shell gateway.
- Per-user `EncryptedNamespace` becomes implementable end-to-end.

### Phase 5 — `Tpm2KeySource`

- TPM 2.0 driver as a userspace service (separate crate; talks to the
  TPM over x86 platform TIS or a virtio passthrough in cloud VMs).
- Seal policies bound to PCR digests.
- Measured-boot chain definition (firmware → bootloader → kernel →
  init → key service). PCR composition documented.

### Phase 6 — `CloudKmsKeySource`

- AWS KMS first; GCP KMS, Azure Key Vault, HashiCorp Vault, KMIP
  follow.
- Depends on `InstanceIdentity` from cloud-metadata and a functioning
  network stack.
- Cross-region / cross-account grant handling documented.

### Phase 6b — `OidcFederatedKeySource`

- Depends on `OAuthClient` and `WorkloadIdentityFederation` from
  [oidc-and-oauth2-proposal.md](oidc-and-oauth2-proposal.md).
- Workload identity federation to cloud KMS (no baked long-lived
  IAM credentials). Subject token sources: `InstanceIdentity`,
  attestation report envelope, Kubernetes projected token, GitHub
  Actions OIDC.
- Per-user volume unlock via user `AccessToken` against a
  capOS-internal key service honoring `SealPolicy.tokenExchange`.
- `TokenExchangePolicy` enforcement for seal/unseal.

### Phase 7 — `AttestationKeySource`

- SEV-SNP, TDX, or Nitro — whichever the first target cloud
  environment requires.
- Verifier can be cloud KMS with attestation policy or a standalone
  service.

### Phase 8 — Post-quantum migration

- Add ML-KEM and ML-DSA to the algorithm enums when capOS picks its
  PQ stack. Primarily a schema evolution and an added `sign` /
  `agree` path; no change to the interface shape.

## Relationship to Other Proposals

- **`volume-encryption-proposal.md`** — primary first consumer.
  `EncryptedBlockDeviceFactory.open(raw, key, format)` and
  `EncryptedNamespace` both take a `SymmetricKey` cap defined here
  (`KeyPurpose.blockVolume` / `objectStore`, typically
  `aes256GcmSiv` / `aes256Xts` / `xchacha20Poly1305`). Per-user
  session unlock invokes `PasskeyPrfKeySource.unlockSymmetric` (Phase 4)
  to mint the user DEK; system volumes unwrap a DEK through
  `Tpm2KeySource` or `CloudKmsKeySource` (Phases 5–6). `KeyVault`
  owns the sealed DEK blob and applies `SealPolicy` on every unlock;
  `rotateSeal` is how that proposal achieves KEK rotation without
  rewriting volume data.
- **`boot-to-shell-proposal.md`** — `CredentialStore` stores
  authenticator verifiers; `PasskeyPrfKeySource` here produces keys
  from assertions that pass `CredentialStore` verification.
- **`networking-proposal.md`** — TLS and mTLS need
  `PrivateKey`/`PublicKey`; instance mTLS bootstraps from a
  `CloudKmsKeySource` or `KeyVault`-issued service identity key.
- **`ssh-shell-proposal.md`** — SSH host keys are sign-only
  `PrivateKey` wrappers backed by `KeyVault`; accepted OpenSSH-format
  public keys are verifier material that map to sessions but never grant
  shell authority directly.
- **`certificates-and-tls-proposal.md`** — layers X.509, trust
  stores, CT, OCSP, pinning, ACME, and TLS config on top of the
  keys defined here. `TlsServerConfig.key()` and
  `TlsClientConfig.clientAuth()` return a `PrivateKey` cap minted by
  this proposal, typically generated by `KeyVault.generatePrivate(
  algorithm, KeyPurpose.tls, policy)`. ACME account JWS signing uses
  a purpose-separated `KeyPurpose.acmeAccount` key; ACME enrollment
  (`AcmeClient.requestCertificate(orderId, certKey, ...)`) consumes
  the TLS certificate `PrivateKey` from the same `KeyVault`. CA
  private keys live in
  `KeyVault` under a strict `SealPolicy` (typically `pcr` or
  composite KMS + attestation). Public material flows through
  `PublicKey.export(PublicKeyFormat.spkiDer)` into that proposal's
  certificate chain and trust-store structures, so this proposal's
  cap boundary is the only place TLS private material is reachable.
- **`oidc-and-oauth2-proposal.md`** — OIDC/OAuth2 client, token,
  JWKS, JWT wrapper, DPoP, and workload identity federation caps
  compose with the keys defined here. `OidcFederatedKeySource` and
  `SealPolicy.tokenExchange` (with `TokenExchangePolicy` /
  `NamedClaim` / `minAuthStrength`) live in this proposal because
  they are key-source shapes; the token protocol frame, discovery,
  JWKS handling, grant types, and verifier live there.
  `JwtSigner` and `JwtVerifier` are thin wrappers defined there
  that hold a `PrivateKey` / `PublicKey` from here and bind it to a
  fixed `(issuer, audience, claim_constraints)` tuple before
  emitting compact-serialized JWTs.
  `KeyPurpose.oauthClientAssertion` tags the key that
  `ClientAuthMethod.privateKeyJwt` and `localPrivateKeyJwt` sign
  with (RFC 7523 §2.2 client assertion against the token endpoint
  or a local STS). `KeyPurpose.oidcIdToken` tags the IdP-side
  signing key held by `LocalIdentityProvider` and published in its
  `Jwks` rotation set. `KeyPurpose.dpopBinding` tags the per-client
  DPoP keypair surfaced as `DpopKey` so `AccessToken` results stay
  `jkt`-bound (RFC 9449). Token-exchange-gated unlock flows in
  Phase 6b consume `AccessToken` and
  `WorkloadIdentityFederation` caps from that proposal and feed
  the cloud KMS or capOS-internal key service named in
  `TokenExchangePolicy.audience`.
- **`cloud-metadata-proposal.md`** — `InstanceIdentity` cap consumed
  by `CloudKmsKeySource` and `AttestationKeySource`.
- **`user-identity-and-policy-proposal.md`** — per-user keys are
  bound to session identity; the same cap chain that says "you are
  Alice" yields Alice's `SymmetricKey` via `PasskeyPrfKeySource`.
- **`cloud-deployment-proposal.md`** — hardware abstraction for
  self-encrypting drives sets up a future
  `SelfEncryptingBlockDevice` cap with hardware-held keys, a
  distinct trust model from software-crypto keys here.
- **`security-and-verification-proposal.md`** — crypto is a top
  target for tiered tooling: constant-time linting, AEAD fuzzing,
  Loom models of the unlock state machine, Kani-style proofs of
  nonce-uniqueness.
- **`system-monitoring-proposal.md`** — every `Key` method call, every
  `KeyVault` operation, and every `KeySource.unlock` should flow
  through the audit cap. Schema for audit events is defined there;
  key-management produces a specific event family.
- **`hardware-audit-persistence-proposal.md`** — the DDF audit step 1
  schema (`SegmentHeader` and durable-path `HardwareAuditRecord` fields,
  landed in `schema/capos.capnp`) can use `SymmetricKey.mac` (HMAC,
  `KeyPurpose.integrity`) and `PrivateKey.sign` (asymmetric signing) to
  seal each audit segment. `KeyPurpose.audit` is the intended tag for
  signing keys held by the audit log service. Phase 1 of this proposal
  (RAM-only key service) is the minimum prerequisite for that signing
  path to become functional.
- **`formal-mac-mic-proposal.md`** — includes GOST-style modeling.
  GOST symmetric (Kuznyechik, Magma) and asymmetric (Streebog-signed
  schemes) algorithms can be added to the enums when a deployment
  requires them.
- **`storage-and-naming-proposal.md`** — Open Question #5 (manifest
  trust, secure boot) is a prerequisite for `Tpm2KeySource` to be
  meaningful.
- **`../design-risks-register.md`** — R14 (durable identity / session
  liveness) lists this proposal among its owners: per-user
  `EncryptedNamespace` unlock, session-token HMAC keys, and
  `LocalIdentityProvider` ID-token signing keys all live behind
  `KeyVault` and `KeySource` here, so durable identity work
  cannot land before persistent `KeyVault` (Phase 3) plus
  `PassphraseKeySource` / `PasskeyPrfKeySource` (Phase 4) do.

## Open Questions

1. **Canonical algorithm set for v1.** Overshooting the enum invites
   implementation sprawl; undershooting forces schema evolution
   early. Proposed minimum: `aes256GcmSiv`, `xchacha20Poly1305`,
   `hmacSha256`, `ed25519`, `x25519`. Add `rsa*`, `p256`, post-quantum
   as real consumers arrive.
2. **Does `SymmetricKey` expose raw encrypt-without-AAD?** AEAD with
   empty AAD is trivially expressible, but some callers may want
   explicit guarantees that non-AEAD modes are unavailable. Decide
   whether the interface permits `aad == Data()` universally or
   whether `KeyPurpose` constrains it.
3. **Public key distribution.** `PublicKey` is a cap, but public
   material is *public* — should there be a "public key is
   freely-shareable bytes" escape hatch outside the cap system?
   Probably yes; `export()` exists for exactly that reason. How does
   a caller obtain a `PublicKey` cap from raw bytes? Via a
   `PublicKeyImporter` factory that verifies format, or directly in
   `KeyVault.importPublic`?
4. **Revocation of in-flight caps.** If a `SymmetricKey` cap is
   granted to 10 services and the key is compromised, can the issuer
   revoke it? capOS cap revocation is generally "drop at each
   holder"; this might warrant a `KeyVault.revoke(handle)` that
   breaks the server-side object so every `encrypt`/`decrypt` returns
   an error. Worth designing explicitly rather than leaving implicit.
5. **Audit record granularity.** Logging every `encrypt` call for
   a high-throughput volume is noisy; logging only unseal events
   misses oracle abuse. Probably: unseal and policy-violation events
   are always logged; per-operation logging is a per-`KeyVault`
   policy, off by default.
6. **Key-use quotas.** Rate-limit `decrypt` operations per cap-holder
   to contain oracle abuse? Nice to have; not clear whether it
   belongs at the `Key` interface or at a `KeyVault` policy.
7. **HSM integration.** `PKCS#11` is the de facto standard for
   HSM access. Does capOS grow a `Pkcs11KeySource`, or does each
   HSM vendor ship a capability-native driver? The cap-native path
   is cleaner but depends on vendor cooperation.
8. **Backwards compatibility with stored blobs.** `SealPolicy`,
   algorithm IDs, and seal blob formats will evolve. Define a
   versioned envelope around every sealed blob from day one, so
   rolling upgrades are possible.
9. **Side-channel guarantees per implementation.** Document the
   expectation for each `KeyAlgorithm` (e.g. "constant-time required
   for `aes*`; use the `aes` crate's hardware backend on x86_64 and
   bit-sliced implementation elsewhere"). Without this, the security
   posture varies silently across builds.
10. **GOST and other jurisdiction-mandated algorithms.** The
    `formal-mac-mic-proposal.md` carves out a GOST-style track.
    Adding Kuznyechik, Magma, and Streebog-signed schemes is an
    additive extension; what matters is that the enums stay forward-
    compatible so a GOST-capable build does not require a schema
    fork.
