# Shade V3.2 — At-Rest Storage Encryption

**Status:** Implementert (0.4.0) — `@shade/storage-encrypted`, `@shade/keychain`,
`shade migrate-storage`, `shade rotate-storage-key`
**Effort:** L (4–8 uker)
**Forrige:** V3.1
**Adresserer:** V2.1 §2

---

## Mål

Opt-in beskyttelse av sensitiv state — identity-nøkler, session-state, valgfri
stream-resume-secret — med nøkler som **ikke** ligger i klartekst i databasen.
Trusselmodellen sier i dag eksplisitt at en stjålet DB eksponerer private
nøkler; dette løser det for deploys som velger å aktivere det.

---

## Scope

### Inn

- Ny `EncryptedStorageProvider`-wrapper som dekorerer `SQLiteStorage` /
  `PostgresStorage`.
- Per-rad AES-256-GCM på sensitive felter (`identity_*`, `session_*`,
  valgfritt `stream_state.streamSecret`).
- KDF-pluggin (default `scrypt` fra `@noble/hashes`) for passphrase-basert
  master-nøkkel.
- Tre nøkkelkilder ut av boksen:
  1. **Passphrase + KDF** — utvikler oppgir secret ved oppstart.
  2. **OS keychain** — macOS Keychain, Linux libsecret, Windows Credential
     Vault (Node-only).
  3. **App-injected key** — appens egen kode forsyner 32-byte nøkkel (mest
     fleksibel).
- Migrasjons-CLI: `shade migrate-storage --encrypt --key-source=...`.
- Trusselmodell-oppdatering: "når enabled, hva er fortsatt udekket" — memory
  compromise, swap, runtime-tap.

### Ut

- Browser/IndexedDB at-rest (egen pakke, vurderes etter V3.8).
- HSM/Secure Enclave (separate driver senere).
- "Always-on by default" — vi flyger opt-in for å ikke bryte eksisterende
  deploys.

---

## Design

### Krypteringsenhet

- Per-rad AEAD: `nonce(12) || ciphertext || tag(16)`.
- `nonce = HKDF(rowKey, "shade-row-nonce-v1" || tableName || pk)[..12]` —
  deterministisk per (tabell, pk) for å unngå nonce-reuse uten å lagre nonce
  separat. Endring av (tabell, pk) → re-encryption.
- AAD binder `tableName || columnName || pk` så feltombytting blokkeres.

### Nøkkelhierarki

```text
masterKey (fra kilde — passphrase / keychain / app-injected)
   │
   ├─ HKDF("shade-storage-v1") → storageKey (32 bytes)
   │     │
   │     └─ HKDF(storageKey, table || column) → fieldKey
   │
   └─ HKDF("shade-storage-version-v1") → versjonsnøkkel (rotasjon)
```

### Migrasjon

1. CLI leser ukryptert DB.
2. Skriver rad-for-rad-kryptering til ny `_v2`-tabell.
3. Atomisk rename + drop gammel.
4. Backup `.bak`-fil etterlatt i samme dir.

### Rotasjon

- `shade rotate-storage-key --new-source=...` re-krypterer med ny masterKey.
- Online ratchet (les med gammel, skriv med ny) for store DB.

---

## Leveranser

### Pakker

- Ny modul: `@shade/storage-encrypted` (re-export over SQLite/PG).
- Utvidelse i `@shade/cli`: `migrate-storage`, `rotate-storage-key`.
- Hjelpe-pakke: `@shade/keychain` (Node-only, valgfri peer-dep) for OS-keychain.

### Tester

- Unit: KDF-derivasjon, nonce-determinisme, AAD-binding.
- Integration: full lifecycle på SQLite + PG; start/stopp; krasj under
  migrasjon.
- Tamper: bit-flip i ciphertext / AAD / nonce → dekrypterings-feil.
- Vector-fil: kryss-sjekk masterKey → fieldKey-derivasjon mot
  `test-vectors/storage-encryption.json`.

### Dokumentasjon

- `docs/storage-encryption.md` — full guide.
- `THREAT-MODEL.md` — ny kolonne "with at-rest enabled".
- Migrasjonsnotat i `MIGRATION.md`.

---

## Akseptansekriterier

- [ ] Eksisterende ukryptert deploy fortsetter uten endringer (opt-in).
- [ ] `shade migrate-storage --encrypt` migrerer en levende SQLite uten
      datatap, verifisert med dump-diff.
- [ ] Rotasjon kan gjøres uten downtime > 5 s for små DB.
- [ ] Wrong passphrase / wrong key → klar feilmelding, ikke krasj.
- [ ] Test-vectors deles med Android-implementasjonen (V3.5 forplikter at
      vector-filen kjøres der).

---

## Avhengigheter

- V3.1 — `THREAT-MODEL.md` skal være lenket til testene først, så vi kan
  utvide tabellen.

---

## Risiko

**Datatap.** En migrasjon som krasjer halvveis kan etterlate korrupt DB.
Mitigeres ved:

- Atomic-rename + `.bak`-fil.
- Dry-run-modus (`--dry-run` validerer all dekryptering før skriving).
- Refuser å starte hvis WAL har uncommitted writes.

**Nøkkeltap = totaltap.** Hvis bruker mister passphrase = ingen tilgang.
Dokumenter klart, og pek på V3.10 (Social Recovery) som langtidsløsning.

---

## Migrasjon

0.3.x deploys er ukrypterte → fortsatt ukrypterte. Aktivering er én
CLI-kommando. Backwards-kompatibel.
