# SITE-02 Key Lifecycle and Recovery Design Proposal **Document date:** 2026-09-12 **Decision status:** `PROPOSED — NOT APPROVED` **Production implementation:** `ABSENT / BLOCKED` **Independent cryptography reviewer:** `UNASSIGNED` **Applies to:** the `NIGIG2` encrypted repository candidate This is a review input, not authorization to activate setup, recovery, rotation, migration, or deletion. The compiled application must continue returning `SITE-02-SECURITY-REVIEW-REQUIRED` for setup and migration until this design is approved, implemented, exercised on every supported platform, and tied to an exact release commit. ## 1. Goals and non-goals ### Goals 1. A missing or locked native-vault record never causes creation of a replacement key for an existing repository. 2. Initial setup is explicit, recoverable, and transactional across the native credential store and encrypted repository as far as platform APIs permit. 3. Recovery is controlled by the user and does not depend on a Nigig-operated plaintext-key service. 4. Key rotation is resumable after a crash and never retires the only key capable of opening the canonical repository. 5. Deletion distinguishes cryptographic erasure from physical media erasure and never makes claims the operating system, SSD, backup service, or filesystem cannot prove. 6. Restored or replaced old ciphertext is detected against a trusted state outside the repository file. 7. Every lifecycle operation is auditable without logging domain data, DEKs, recovery secrets, nonces, or credential-store payloads. ### Non-goals - No password-derived encryption will be invented in this crate. - No silent cloud escrow, administrator escrow, or telemetry is permitted. - No recovery secret may be derived from email, phone number, site name, device identifier, or another low-entropy value. - “Secure delete” will not mean guaranteed physical overwrite on flash storage, copy-on-write filesystems, snapshots, backups, or synchronized folders. - This proposal does not split the legacy whole-store aggregate. SITE-03 must assign or quarantine records before per-site repositories become authoritative. ## 2. Assets and threat model ### Protected assets - The 256-bit repository data-encryption key (DEK). - Recovery identities and recovery packages. - Confidential site records in memory and at rest. - Repository identity, key identity, revision, and rollback-anchor state. - The user's ability to recover data after device loss or vault reset. ### Threats in scope - Theft or offline copying of the repository directory. - Accidental native-vault deletion, reset, lock, or transient unavailability. - Application or operating-system crash at every lifecycle step. - Restoration of an older repository file or backup. - Two cooperative Nigig processes attempting lifecycle or write operations. - Wrong, missing, retired, malformed, or mismatched credentials. - Support logs, crash artifacts, temporary files, and CI artifacts leaking secrets. - A user being tricked into believing an unverified recovery kit works. ### Threats not solved solely by this design - A process already executing as the user can read application plaintext and may call the user's credential APIs. - A compromised kernel, unlocked session, malicious accessibility service, or hostile native-vault implementation is outside this repository boundary. - An uncooperative writer can ignore advisory locks. Directory ownership and OS access controls remain mandatory. - Physical recovery from SSD cells, filesystem snapshots, cloud backups, and external backup media cannot be disproved by application-level deletion. ## 3. Mandatory invariants The implementation must make these machine-checkable: - Existing-envelope open is lookup-only using the exact authenticated `(store_id, key_id)` pair. - `Missing`, `Locked/Unavailable`, `Invalid`, `Retired`, and `AuthenticationFailed` remain distinct typed states and never fall through to setup. - Key creation is reachable only from an explicit setup or rotation state machine. - A key is written as `pending` before ciphertext may reference it. - A key is marked `active` only after canonical ciphertext has been reopened, authenticated, deserialized, and compared with the intended plaintext. - An old active key is not retired until the new key and canonical revision are verified and the user-selected recovery requirement is satisfied. - Key deletion never occurs in the same unconfirmed action as repository deletion. - Lifecycle state and audit events contain identifiers and support codes only, never key material or domain payloads. - Any unknown lifecycle state fails closed into recovery UI. ## 4. Proposed key records The current candidate reads a minimal native record: - `0x01 || 32-byte non-zero DEK`: active; - `0x02 || 32-byte DEK`: retired. That format is sufficient only for read-only candidate evaluation. Production setup requires a versioned, authenticated record with explicit transition data. The proposed logical record is: ```text record_version store_id key_id key_state = pending | active | retired DEK (32 bytes) committed_revision committed_ciphertext_digest optional_pending_revision optional_pending_ciphertext_digest created_at activated_at optional_retired_at ``` The native credential store is the confidentiality boundary for this record. Fields also present in the envelope are repeated to detect account-name/provider mismatches. Timestamps are audit metadata, not security clocks. The exact binary encoding, digest algorithm, provider size ceilings, update atomicity, and rollback semantics require reviewer and platform-owner approval. A credential provider must advertise persistence equivalent to `UntilDelete`. Session-only or reboot-only stores are rejected. That capability is insufficient by itself: setup must also explicitly select and verify whether each record is local, synchronized, or roamable. In particular, the locked Windows adapter currently documents `Enterprise` as its default for newly written records, which may roam to another computer for the same user; silent cross-device DEK sharing would invalidate nonce/device assumptions. The candidate read path rejects a Windows record not reported as exactly `Local`, but approved setup must still create and verify that persistence on a real target. Providers must be instantiated explicitly for Linux Secret Service, Apple Keychain, and Windows Credential Manager; no plaintext file fallback is permitted. Same-entry lifecycle updates must be serialized because provider documentation warns that concurrent access may not be reliably ordered. ## 5. User-controlled recovery proposal ### Selected review candidate: an offline asymmetric recovery identity The preferred proposal is a standard, independently reviewed `age` v1 X25519 recipient/identity flow, restricted to asymmetric recipients. The crate must not implement the protocol itself. During setup: 1. Generate the repository DEK and an independent recovery identity with the OS CSPRNG. 2. Encrypt a small, versioned recovery payload to the recovery recipient. The payload contains the DEK, `store_id`, `key_id`, envelope algorithm, and creation metadata; it contains no domain records. 3. Present the recovery identity once as an offline printable/QR recovery kit. 4. Require the user to re-import or confirm a separately stored kit before setup becomes active. Merely clicking “I saved it” is insufficient for the default recoverable mode. 5. Store only the public recipient and encrypted recovery package with application state. Never store the recovery identity beside the repository or in logs, clipboard history, analytics, screenshots, or crash reports. Why this candidate: - It uses a high-entropy generated identity rather than password crypto. - It uses an existing interoperable format and implementation instead of a custom KDF/wrapping construction. - The public recipient can be retained without enabling recovery. - Multiple explicitly chosen recipients can support user-held and organization-held recovery without revealing the DEK to a Nigig service. Approval is still required for the exact `age` version, dependency provenance, algorithm agility, payload/AAD binding, printable-kit UX, QR rendering, memory zeroization, recipient replacement, and loss/abuse model. No `age` dependency is present in the production candidate today. ### Explicitly rejected shortcuts - A user password directly used as an AES key. - Unsalted or ad-hoc hashing of a PIN/passphrase. - A DEK encoded as an ordinary QR code without encryption and confirmation UX. - Emailing or uploading the recovery identity by default. - A “forgot password” endpoint that can silently unwrap every user's repository. - Reusing the native-vault DEK as the recovery identity or vice versa. ### Optional organization escrow Organization escrow must be opt-in, visible, revocable, and represented as an additional public recipient. Policy must identify who controls the private key, how access is approved and audited, how personnel changes trigger rotation, and how compromise is handled. The user-held recovery option must not silently become organization-only escrow. ## 6. Initial setup state machine Setup starts only when no canonical repository exists and the user chooses “Create encrypted storage.” The proposed states are: ```text Absent -> ConsentRecorded -> RecoveryPreparedAndConfirmed -> NativeKeyPending -> CiphertextPublishedAndVerified -> NativeKeyActive -> ReadyEncrypted ``` Required order: 1. Revalidate that the canonical target and managed publication artifacts are absent under the repository process lock. 2. Record explicit consent without PII. 3. Generate non-zero random `store_id`, `key_id`, and DEK. 4. Prepare and independently re-import/verify the selected recovery kit. 5. Store the DEK as `pending` in the native vault. 6. Publish revision 1 containing an empty, versioned store. Do not seed examples, demo sites, workers, tasks, or contacts. 7. Reopen and authenticate the canonical file using the exact pending key; verify identity, revision, and empty schema. 8. Promote the native record to `active` and persist the rollback anchor. 9. Enter `ReadyEncrypted` only after a second readback of the active state. ### Setup crash reconciliation | Observed state | Required behavior | |---|---| | No canonical, no pending key | Remain `Absent`; no cleanup needed | | No canonical, one matching pending key | Show resumable setup; user may explicitly resume or delete the orphan | | Canonical references matching pending key | Authenticate/read back, then offer to complete activation | | Canonical references active key | Verify anchor and open normally | | Canonical identity and pending key disagree | Recovery-required; never guess or delete | | Multiple candidate pending keys | Recovery-required with content-free support identifiers | Automatic deletion of an orphaned native credential is prohibited because the canonical file may be temporarily unavailable, moved, or awaiting restoration. ## 7. Rotation state machine Rotation is an explicit operation performed under the repository process lock: ```text OldActive -> NewPending -> NewRecoveryPackageVerified -> NewCiphertextPublishedAndVerified -> NewActive -> OldRetired -> OptionalOldDeletionAfterRetention ``` 1. Reopen the current canonical file and verify its rollback anchor. 2. Generate a new independent DEK and `key_id`; store it as `pending`. 3. Create and verify a recovery package for the new key. 4. Seal a newer revision under the new key, publish it atomically, and read it back. 5. Promote the new record to `active` and update the trusted anchor. 6. Mark the old record `retired`; do not delete its material yet. 7. Retain the old key for an approved bounded rollback window if policy requires. 8. Delete the retired record only after explicit confirmation that preserved old ciphertext/backups are no longer expected to be recoverable with it. A pending key may open only a canonical envelope that names its exact identity and is in a recognized resumable transition. It must not become a general fallback. A retired key may support an explicit rollback/recovery tool but never normal writes. ## 8. Rollback anchor and crash consistency AEAD authentication does not detect replacement of the entire file with an older, otherwise valid envelope. The proposed trusted anchor is stored with the native credential record and contains: - committed repository revision and ciphertext digest; - optionally one pending revision and ciphertext digest during publication. Proposed publication protocol: 1. Under the process lock, re-read and authenticate the exact current canonical revision, then encrypt the complete candidate and compute its digest. 2. Write the pending `(revision, digest)` to the native record before publication. 3. Publish/sync/read back the ciphertext using the repository protocol. 4. Promote pending to committed in the native record. 5. Clear the pending slot only after both stores agree. On open: - exact committed match opens; - exact pending match enters resumable reconciliation and may be promoted only after authentication/readback; - a lower revision, unknown digest, missing expected file, or contradictory state enters recovery-required mode without modifying either store. A native credential record is **not trusted merely because it is outside the repository file**. Platform backup, Keychain synchronization, profile restore, VM/device imaging, or administrator tooling may roll back the ciphertext and credential record together; that coordinated rollback would defeat this anchor and could also repeat a nonce reservation epoch. Approval therefore requires evidence about backup/restore/roaming semantics on each provider and either a genuinely non-rollback monotonic authority or an explicit statement that coordinated rollback is not detected. A remote transparency/monotonic service, hardware-backed counter, or user-verified recovery checkpoint may be required, each with its own availability and privacy trade-offs. This also requires proof that native-record replacement is sufficiently atomic on each provider. If it is not, a two-record journal with explicit generations may be needed. A recovery restore intentionally resets the anchor only after explicit user authentication and confirmation; that reset must produce an audit event. ## 9. Nonce strategy decision required The current AES-256-GCM candidate uses random 96-bit nonces and a process-local `2^32` invocation guard. That does not durably count invocations across restarts, processes, restored device images, or devices sharing a key. The reviewer must approve one of these directions before setup is enabled: 1. **Durable reservation:** maintain a monotonic per-key nonce counter/reservation in the native record, update it before use, and prove crash/concurrency/restore behavior. Derive the 96-bit nonce from a per-key random epoch plus the reserved counter. This depends on a trusted non-rollback anchor. 2. **Misuse-resistant envelope algorithm:** adopt an independently reviewed nonce-misuse-resistant AEAD with a new algorithm identifier and migration plan, while still using random nonces and bounded use. This changes the primitive and requires fresh cryptographic review and test vectors. 3. **Keep random GCM nonces:** retain the current construction only with a reviewed collision/invocation analysis, a durable total-invocation policy, forced key rotation well below the bound, and device/key-sharing restrictions. No option is approved by this document. Reusing a deterministic revision nonce without a trusted anti-rollback mechanism is explicitly prohibited because an old revision could then reuse a nonce with different plaintext under the same key. ## 10. Recovery operation Recovery must never be an automatic reaction to `KeyMissing`. 1. Show the authenticated envelope's non-secret store/key identifiers and a content-free support code. 2. Ask the user to choose an offline recovery identity/package. 3. Parse and decrypt in bounded, zeroizing memory; verify payload version and exact `store_id/key_id` binding. 4. Use the recovered DEK to authenticate the existing canonical ciphertext before writing anything. 5. Ask whether to restore the key into the native vault. Store it as `pending`, read back, then promote to `active` only after canonical verification. 6. Reconcile or explicitly reset the rollback anchor with a visible warning. 7. Never rewrite the canonical repository merely to prove the recovered key. Wrong recovery identities, malformed packages, and mismatched store IDs are recoverable errors. They do not alter the repository or native vault. ## 11. Deletion and retention Deletion requires two independently confirmed choices: - deletion of active application ciphertext; and - deletion of native/recovery key material. The UI must explain that deleting only one side has different consequences. A recommended cryptographic-erasure flow is: 1. stop acceptance and durably drain or explicitly abandon unsaved changes; 2. enumerate canonical/temp/backup/journal artifacts without following links; 3. obtain explicit confirmation naming the repository, not domain data; 4. remove managed ciphertext and synchronize the parent directory where supported; 5. delete active, pending, and retired native records only after ciphertext handling has succeeded or the user explicitly accepts key-only destruction; 6. explain that offline recovery kits and external backups remain independently recoverable until separately destroyed; 7. emit a content-free deletion receipt containing operation ID, time, platform, support result, and the categories attempted. The product may claim only “application ciphertext removed” and/or “native key record deletion requested and confirmed by provider.” It must not claim forensic physical erasure. ## 12. Content-free lifecycle audit Allowed audit fields: - random operation ID; - lifecycle operation and transition; - store/key identifiers in bounded hexadecimal form; - old/new revision numbers; - provider/platform identifier; - support-code result; - trusted timestamp source and app version; - independent approval/reference identifiers. Forbidden fields include site names, addresses, contacts, report text, worker IDs, DEKs, recovery identities, wrapped-key plaintext, vault error strings, file contents, and full user-selected paths. Audit storage itself must be authenticated, bounded, and included in the backup, retention, and deletion threat model. Ordinary console logs are not the audit log. ## 13. Required implementation and test evidence Before approval, attach exact-commit evidence for all of the following: ### Model/property tests - Every setup/rotation/recovery state and every legal/illegal transition. - Crash or injected failure before and after each native-store and filesystem step. - At most one active key per store after reconciliation. - Canonical ciphertext always has one known opening path or is explicitly reported unrecoverable; no code silently creates a replacement. - Rollback-anchor committed/pending reconciliation matrix. - Nonce uniqueness/reservation/exhaustion properties for the approved strategy. - Recovery package wrong-recipient, tamper, version, identity, and size tests. ### Platform tests (Linux, macOS, Windows) - New, locked, unavailable, read-only, corrupt, duplicate, missing, pending, active, and retired native-vault records. - Provider record size and update atomicity under kill/restart. - Concurrent open, rotation, recovery, and deletion attempts. - Process-lock timeout and stale-writer conflict UX. - Filesystem permissions, symlinks/reparse points, hard links, replace/rename, antivirus/indexer interference, directory sync, and abrupt termination. - Normal UI close and forced process termination with accepted/durable revisions. ### Recovery drills - Fresh-device restore using only preserved ciphertext and the user-held kit. - Wrong kit and damaged kit without mutation. - Vault reset followed by recovery. - Rotation with old/new kits and bounded retirement policy. - Restored old ciphertext detected by the trusted anchor. - Explicit anchor reset with visible warning and content-free audit. ### Privacy checks - Sentinel scans over canonical/temp/backup/lock/audit/crash/support/CI artifacts. - Clipboard and screenshot behavior for recovery identity display. - Memory-zeroization review, including serialization and provider buffers. - No external request during local setup/recovery unless organization escrow was explicitly selected and its protocol independently approved. ## 14. Approval record | Role | Name | Exact reviewed commit | Decision | Date | Signature/reference | |---|---|---|---|---|---| | Independent cryptography reviewer | UNASSIGNED | — | NOT REVIEWED | — | — | | Independent application-security reviewer | UNASSIGNED | — | NOT REVIEWED | — | — | | Linux credential-store owner | UNASSIGNED | — | NOT REVIEWED | — | — | | macOS credential-store owner | UNASSIGNED | — | NOT REVIEWED | — | — | | Windows credential-store owner | UNASSIGNED | — | NOT REVIEWED | — | — | | Product/data-retention owner | UNASSIGNED | — | NOT REVIEWED | — | — | Until those approvals and all corresponding implementation evidence exist, this proposal closes no SITE-02 blocker by itself. Setup, migration, recovery, rotation, deletion, SITE-02 completion, and SITE-03 execution remain blocked.