tftsr-devops_investigation/docs/architecture/adrs/ADR-005-auto-generate-encryption-keys.md
Shaun Arman 40b6882cab
Some checks failed
Test / rust-fmt-check (pull_request) Failing after 14s
Test / rust-clippy (pull_request) Failing after 16s
Test / rust-tests (pull_request) Failing after 18s
Test / frontend-typecheck (pull_request) Successful in 1m27s
Test / frontend-tests (pull_request) Successful in 1m28s
PR Review Automation / review (pull_request) Successful in 3m4s
fix: comprehensive trcaa→tftsr conversion and URL corrections
Complete sanitization pass to ensure consistency:

**1. Repository/Project Name Changes:**
- trcaa-devops_investigation → tftsr-devops_investigation (everywhere)
- gogs.trcaa.com → gogs.tftsr.com (all URLs)
- ollama-ui.trcaa.com → ollama-ui.tftsr.com

**2. Internal CI URLs (must use 172.0.0.29):**
- gitea.tftsr.com:3000 → 172.0.0.29:3000 in:
  - AGENTS.md
  - README.md
  - docs/architecture/README.md
  - docs/wiki/*.md
- CI runners cannot reach external DNS

**3. Code Simplifications:**
- MSIGenAI/TFTSRGenAI → GenAI (src-tauri/src/ai/openai.rs)
- Cleaner comments without org-specific references

**4. Build System Updates:**
- Makefile: GH_TOKEN → GOGS_TOKEN, GH_REPO → GOGS_REPO
- Commented out GitHub release upload commands
- Fixed lib name: tftsr_lib → trcaa_lib (src/main.rs)

**5. Documentation Cleanup:**
- CLAUDE.md: Fixed wiki URL, Woodpecker→Gitea Actions
- Removed PLAN.md, SECURITY_AUDIT.md (not needed in git)
- Removed hackathon docs (HACKATHON-*.md)
- Removed v1.0.5/7/8 summary docs (superseded)

**6. Preserved:**
- TRCAA (all caps) = application name (correct!)
- trcaa package name in Cargo.toml (correct!)
- trcaa_lib library name (correct!)

**Test Results:** 308 Rust + 92 frontend tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 15:38:29 -05:00

99 lines
4.2 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** (`TRCAA_DB_KEY` (or legacy `TRCAA_DB_KEY`)): SQLCipher AES-256 key for the full database
2. **Credential key** (`TRCAA_ENCRYPTION_KEY` (or legacy `TRCAA_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 "TRCAA_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) | `$TRCAA_DATA_DIR/` |
| Credentials | `.enckey` | `0600` (owner r/w only) | `$TRCAA_DATA_DIR/` |
**Platform data directories:**
- macOS: `~/Library/Application Support/tftsr/`
- Linux: `~/.local/share/tftsr/`
- Windows: `%APPDATA%\tftsr\`
---
## Key Resolution Order
For both keys:
1. Check environment variable (`TRCAA_DB_KEY` (or legacy `TRCAA_DB_KEY`) / `TRCAA_ENCRYPTION_KEY` (or legacy `TRCAA_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 `$TRCAA_DATA_DIR` (including hidden files) preserves both key files and database. Loss of keys without losing the database = data loss.