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
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'ssecret-session.cbyte-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,
plainfallback only if the daemon returnsNotSupported— 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 talkorg.freedesktop.secretscan read items once the collection is open. RedFlag's answer to "who is asking" is the capability token'sAgentID+Operationfields, verified independently by the executor. secrets has no equivalent: there is no per-caller capability onGetSecrets, noSO_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.
Unlockis 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 (thesouveraine-stepupPAM 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.