Surfaced on the v1.0.0-rc.7 Windows verification round as 'Bridge
Error connection failed: storage crypto decrypt aead error'.
Root cause: IdentityFileStore::ensure_dek treated the platform
keyring as the authoritative store and deleted identity.dek after
successfully promoting it. Subsequent launches whose process
context could not reach the keyring (Windows SSH session hits
ERROR_NO_SUCH_LOGON_SESSION; macOS LaunchAgent contexts hit
errSecMissingEntitlement) saw dek_path.exists() = false and
generated a fresh DEK, even though the keyring still held the
DEK that originally encrypted identity.tskey. Next ChaCha20-
Poly1305 AEAD decrypt of the identity blob then failed because
the in-process DEK was 32 fresh random bytes, not the bytes that
encrypted the stored ciphertext. The bookmark store (which
shares the DEK via crypto()) also broke for the same reason.
Manual reproduction on the rc.7 build at 100.84.219.45:
* Launch via SSH (keyring unreachable) -> file DEK_v1 created,
identity.tskey eventually encrypted under DEK_v1.
* Launch via RDP (keyring reachable) -> file DEK_v1 promoted
to keyring, identity.dek deleted.
* Launch via SSH again (keyring unreachable, file gone) -> a
fresh DEK_v2 is written to file. identity.tskey still
encrypted under DEK_v1.
* Next decrypt: DEK_v2 vs identity.tskey ciphertext -> AEAD
tag mismatch -> StorageError::Crypto('decrypt: \u2026') ->
bubble up as 'storage crypto decrypt aead error'.
Fix invariants:
* identity.dek (file) is the durable source of truth and is
never deleted by ensure_dek.
* keyring is opportunistic: we copy the DEK into it for the
UX-level convenience of platform-managed secret storage,
but its presence/absence does not affect correctness.
* ensure_dek on first install writes the DEK to BOTH places.
* ensure_dek on subsequent launches: keep using the file DEK;
re-copy into the keyring if not present (idempotent).
* load_dek prefers the file; only consults the keyring as a
legacy-migration fallback for installs that lost their file
mirror before this commit landed.
No SDD / SAD / SRS contract changes - the file fallback at
identity.dek and the in-keyring entry at app.chanora.identity::
identity-dek::<canonical-dir> were both already documented
behaviours; this commit corrects which one is authoritative.
The file lives in app-private storage where the platform
sandbox is the access-control authority (this was already
called out in the existing open_private comment on non-Unix
targets), so retaining the file mirror does not weaken the
security posture in any meaningful way relative to the prior
keyring-only durable-state design.
Verified on Linux: cargo test --workspace 59/0/3 (no regressions
from feceacf + 5c413ba).