Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/souveraine-components/secrets.md
Fimeg 19475878dd deprecate chatty/libcmatrix/qtpim, clean stale refs
Chatty superseded by souveraine's Matrix sensorium. libcmatrix unused.
qtpim contacts work stalled. All repos archived on Gitea.

- STATE.md: remove component sections, update secrets consumers, decisions
- Mark deprecated docs with headers
- Update INVENTORY.md Tier 3 entries as deprecated
- Update secrets.md consumer list
2026-07-21 13:51:14 -04:00

91 lines
4.8 KiB
Markdown

# souveraine-secrets
User-tier Secret Service daemon. Implements `org.freedesktop.secrets`
(`org.freedesktop.Secret.Service`) on the session D-Bus so libsecret clients
— souveraine-player, culver, and our own `keyring-core` credential store —
get a secrets backend without gnome-keyring or KWallet.
Source: `src/bin/souveraine-secrets.rs`, `src/secrets/{service,collection,item,store,session,session_object,transport,dh,types,error}.rs`.
## The load-bearing choice
The store is keyed from the machine's existing Ed25519 `SeedId`, **not** a
passphrase-unlocked keyring. Concretely (`src/secrets/store.rs`): the seed
never leaves the process and is never used directly as the storage key.
Instead the daemon signs a fixed domain-separation string and runs the
(deterministic) Ed25519 signature through HKDF-SHA256 to derive a dedicated
AES-256 key. That keeps "sign federation events" (machined's domain) and
"encrypt local secrets" as **separate derived keys from one root**, rather
than reusing the same key material for two purposes.
The seed is a **precondition, never created by the daemon**. No seed at
`~/.souveraine/seed-id` → loud startup failure. Generation lives in the
guarded CLI verb `souveraine identity generate`.
## Why everything is "permanently unlocked"
Because the seed identity is resident in the process for its whole lifetime,
there is no separate "unlock the keyring" step. `OpenSession` always
succeeds immediately; `Unlock` is a no-op (`src/secrets/service.rs`). The
protection is not a passphrase gate at the D-Bus layer — it is that the
encrypted blob on disk is useless without the seed, and the seed lives in
machined's domain.
## What crosses the boundary
The Secret Service protocol's own crypto is implemented faithfully so
libsecret clients interoperate:
- **Key exchange** (`dh.rs`): `dh-ietf1024-sha256-aes128-cbc-pkcs7`
classic (non-EC) DH over RFC 2409 Second Oakley Group (MODP 1024-bit),
shared secret through HKDF-SHA256 → 16-byte AES-128 session key. Mirrors
libsecret's `secret-session.c` byte-for-byte.
- **Transport** (`transport.rs`): AES-128-CBC + PKCS7, IV = 16 fresh random
bytes per secret, wire shape `(session_path, iv, ciphertext, content_type)`.
- **Algorithms**: AES negotiated first, `plain` fallback only if the daemon
returns `NotSupported` — covering every real libsecret client (`session.rs`).
## Audit gaps (toward the RedFlag bar)
secrets is the furthest of the three from the RedFlag posture, because its
threat model is currently "the user's session is the trust boundary" rather
than "every caller is attested." Read against RedFlag's primitives:
- **No caller attestation on `GetSecret`.** Any process on the session bus
that can talk `org.freedesktop.secrets` can read items once the collection
is open. RedFlag's answer to "who is asking" is the capability token's
`AgentID` + `Operation` fields, verified independently by the executor.
secrets has no equivalent: there is no per-caller capability on
`GetSecrets`, no `SO_PEERCRED`-backed caller identity, no "this token
authorizes reading *this* item." The D-Bus policy is the only gate, and
D-Bus policy is installation-based, not identity-based (exactly what P7
rejects).
- **No closure/scoping.** RedFlag's token binds to a closure hash — the
secret is only released for the exact artifact set authorized. A secrets
equivalent would bind a token to a specific item or collection path, so a
token minted for one app's Matrix credential cannot read the email
credential. Nothing like this exists today.
- **`Unlock` is a no-op.** This is intentional given the seed-rooted design,
but it means there is no step-up / freshness at the secrets layer. Step-up
lives in sessiond (the `souveraine-stepup` PAM service); secrets should
eventually honor a step-up token before releasing personal-tier
credentials, not rely on "the session is already unlocked."
- **KeyID/fingerprint not pinned.** As with machined, the seed identity has
no advertised fingerprint (`hex(sha256(pubkey)[:16])`) that a caller or
auditor pins. A seed rotation silently changes what "the keyring" is.
- **Binary integrity.** Same as machined and RedFlag SEC-022: no
self/peer hash verification. DAC (`root`/owner-owned, non-overwritable)
is the standing guarantee.
## Federation note
Because the root is the machine seed, and seeds federate (FEDERATION.md),
there is a deliberate design tension to resolve in the review: a secret
encrypted under machine A's derived key is not readable on machine B even
though both share the federated *agent* identity. The seed-per-machine vs
seed-per-agent question (machined owns the *machine* seed; agents have
their own seed lineage) is exactly where secrets' trust model is decided.
Recorded here, not answered — it belongs in the services-infrastructure
review.
See [audit-status.md](audit-status.md) for priority.