# chanora_storage Two strictly separated storage concerns per SAD-067: 1. **`BookmarkRepository`** — non-secret bookmark state via SQLite with optional encrypted password fields (`rusqlite` bundled, DEC-013.1). 2. **`IdentityFileStore`** — Beta fallback storage for identity material while platform secure-storage backends mature. ## Architecture ### IdentityFileStore - Persists a single TS3 identity to `/identity.tskey` encrypted with ChaCha20-Poly1305. - The Data Encryption Key (DEK) is 32 random bytes stored in the platform keyring (Linux Secret Service, macOS Keychain, Windows Credential Manager, iOS Keychain) when available, with a best-effort file fallback at `identity.dek` (mode 0600 on Unix). - Legacy plaintext files from pre-Beta are still readable; the next `save()` upgrades them to encrypted form. - Audio metadata (`transmit_mode`, `release_tail_ms`, PTT binding) is persisted alongside as `audio_meta.json` (plaintext, non-secret). ### BookmarkRepository - SQLite-backed store at `/chanora.db`. - Schema v1: basic bookmark columns. Schema v2: adds `password_blob` for encrypted passwords. - When constructed via `with_crypto()`, the `password` column is replaced by a ChaCha20-Poly1305 envelope under the same per-install DEK. - Legacy plaintext passwords are transparently read and upgraded on the next `update()`. ### Crypto abstraction - `Crypto` trait: `encrypt(plaintext)` / `decrypt(blob)` — callers see only the encrypt/decrypt pair. - `DekCrypto` — concrete implementation sharing the same per-install DEK with `IdentityFileStore`. ## Public API Summary ### Types | Type | Role | |---|---| | `IdentityFileStore` | Encrypted identity file store | | `BookmarkRepository` | SQLite bookmark store with optional password encryption | | `Bookmark` | Bookmark DTO: id, display_name, host, nickname, password | | `PttBindingMeta` | Persisted PTT binding metadata | | `StorageError` | NotFound, Migration, Sqlite, SecureStore, Io, Crypto | | `Crypto` trait | Encrypt/decrypt abstraction | ### IdentityFileStore methods - `new(dir)` — create or open store, ensure DEK exists - `load()` → `Option` — read identity (handles legacy plaintext) - `save(identity)` — persist encrypted (ChaCha20-Poly1305, atomic write) - `clear()` — remove identity file - `crypto()` — obtain a `Crypto` handle sharing the DEK - `set_transmit_mode(mode)` / `get_transmit_mode()` — audio settings persistence - `set_release_tail_ms(ms)` / `get_release_tail_ms()` — release-tail persistence - `set_ptt_binding(...)` / `get_ptt_binding()` — PTT binding persistence ### BookmarkRepository methods - `new(dir)` / `with_crypto(dir, crypto)` — open (plain or encrypted) - `add(bookmark)` → `i64` — insert, return id - `update(bookmark)` — replace by id - `delete(id)` — remove by id - `list()` → `Vec` — all bookmarks ordered by id - `upsert_or_add(bookmark)` — insert or update by host, preserves user's display name - `encrypts_passwords()` — whether password encryption is active ## Platform notes - Unix: files written with mode 0600. - Keyring access can be disabled via `CHANORA_DISABLE_KEYRING=1` for tests/headless environments. - Android: file in app-private storage (not encrypted at rest — documented Beta gap). - iOS/Windows/macOS: caller provides the storage directory; platform sandbox handles access control.