99 lines
4.0 KiB
Markdown
99 lines
4.0 KiB
Markdown
|
|
# ADR-005: Auto-generate Encryption Keys at Runtime
|
||
|
|
|
||
|
|
**Status**: Accepted
|
||
|
|
**Date**: 2026-04
|
||
|
|
**Deciders**: sarman
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Context
|
||
|
|
|
||
|
|
The application uses two encryption keys:
|
||
|
|
1. **Database key** (`TFTSR_DB_KEY`): SQLCipher AES-256 key for the full database
|
||
|
|
2. **Credential key** (`TFTSR_ENCRYPTION_KEY`): AES-256-GCM key for token/API key encryption
|
||
|
|
|
||
|
|
The original design required both to be set as environment variables in release builds. This caused:
|
||
|
|
- **Critical failure on Mac**: Fresh installs would crash at startup with "file is not a database" error
|
||
|
|
- **Silent failure on save**: Saving AI providers would fail with "TFTSR_ENCRYPTION_KEY must be set in release builds"
|
||
|
|
- **Developer friction**: Switching from `cargo tauri dev` (debug, plain SQLite) to a release build would crash because the existing plain database couldn't be opened as encrypted
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Decision
|
||
|
|
|
||
|
|
Auto-generate cryptographically secure 256-bit keys at first launch and persist them to the app data directory with restricted file permissions.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Key Storage
|
||
|
|
|
||
|
|
| Key | File | Permissions | Location |
|
||
|
|
|-----|------|-------------|----------|
|
||
|
|
| Database | `.dbkey` | `0600` (owner r/w only) | `$TFTSR_DATA_DIR/` |
|
||
|
|
| Credentials | `.enckey` | `0600` (owner r/w only) | `$TFTSR_DATA_DIR/` |
|
||
|
|
|
||
|
|
**Platform data directories:**
|
||
|
|
- macOS: `~/Library/Application Support/trcaa/`
|
||
|
|
- Linux: `~/.local/share/trcaa/`
|
||
|
|
- Windows: `%APPDATA%\trcaa\`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Key Resolution Order
|
||
|
|
|
||
|
|
For both keys:
|
||
|
|
1. Check environment variable (`TFTSR_DB_KEY` / `TFTSR_ENCRYPTION_KEY`) — use if set and non-empty
|
||
|
|
2. If debug build — use hardcoded dev key (never touches filesystem)
|
||
|
|
3. If `.dbkey` / `.enckey` exists and is non-empty — load from file
|
||
|
|
4. Otherwise — generate 32 random bytes via `OsRng`, hex-encode to 64-char string, write to file with `mode 0600`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Plain-to-Encrypted Migration
|
||
|
|
|
||
|
|
When a release build encounters an existing plain SQLite database (written by a debug build), rather than crashing:
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Detect plain SQLite via 16-byte header check ("SQLite format 3\0")
|
||
|
|
2. Copy database to .db.plain-backup
|
||
|
|
3. Open plain database
|
||
|
|
4. ATTACH encrypted database at temp path with new key
|
||
|
|
5. SELECT sqlcipher_export('encrypted') -- copies all tables, indexes, triggers
|
||
|
|
6. DETACH encrypted
|
||
|
|
7. rename(temp_encrypted, original_path)
|
||
|
|
8. Open encrypted database with key
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Alternatives Considered
|
||
|
|
|
||
|
|
| Option | Pros | Cons |
|
||
|
|
|--------|------|------|
|
||
|
|
| **Auto-generate keys** (chosen) | Works out-of-the-box, no user config | Key file loss = data loss (acceptable: key + DB on same machine) |
|
||
|
|
| Require env vars (original) | Explicit — users know their key | Crashes on fresh install, poor UX |
|
||
|
|
| Derive from machine ID | No file to lose | Machine ID changes break DB on hardware changes |
|
||
|
|
| OS keychain | Most secure | Complex cross-platform implementation; adds dependency |
|
||
|
|
| Prompt user for password | User controls key | Poor UX for a tool; password complexity issues |
|
||
|
|
|
||
|
|
**Why not OS keychain:**
|
||
|
|
The `tauri-plugin-stronghold` already provides a keychain-like abstraction for credentials, but integrating SQLCipher key retrieval into Stronghold would create a chicken-and-egg problem: Stronghold itself needs to be initialized before the database that stores Stronghold's key material.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Consequences
|
||
|
|
|
||
|
|
**Positive:**
|
||
|
|
- Zero-configuration installation — app works on first launch
|
||
|
|
- Developers can freely switch between debug and release builds
|
||
|
|
- Environment variable override still available for automated/enterprise deployments
|
||
|
|
- Key files are protected by Unix file permissions (`0600`)
|
||
|
|
|
||
|
|
**Negative:**
|
||
|
|
- If `.dbkey` or `.enckey` are deleted, the database and all stored credentials become permanently inaccessible
|
||
|
|
- Key files are not themselves encrypted — OS-level protection depends on filesystem permissions
|
||
|
|
- Not suitable for multi-user scenarios where different users need isolated key material (single-user desktop app — acceptable)
|
||
|
|
|
||
|
|
**Mitigation for key loss:**
|
||
|
|
Document clearly that backing up `$TFTSR_DATA_DIR` (including hidden files) preserves both key files and database. Loss of keys without losing the database = data loss.
|