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

4.8 KiB

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 for priority.