bollwerk/docs/migration-guide.md
Jens Reinemann 8c0db56223 feat: E2EE Messaging mit Tink HPKE (X25519 + ChaCha20-Poly1305)
Closes #96

## App

- E2EEKeyManager: Tink HPKE Schlüsselpaar generieren, privaten Key
  via EncryptedSharedPreferences sichern, Nachrichten verschlüsseln
  und entschlüsseln (X25519 + ChaCha20-Poly1305)
- EnsureKeyPairUseCase: Keypair-Initialisierung beim App-Start;
  Public Key via HTTP PUT an Server übermitteln
- MainActivity: EnsureKeyPairUseCase.execute() in onCreate
- SettingsKey: E2EEPrivateKeyset + E2EEPublicKeyBase64 als SENSITIVE_KEYS
- MessageRepositoryImpl: sendMessage verschlüsselt Body mit Empfänger-
  Public-Key; eingehende Nachrichten werden lokal entschlüsselt und
  als Klartext in Room gespeichert; Public-Key-Cache (in-memory) +
  key_updated Handler
- WebSocketClient: KeyUpdated Event hinzugefügt
- WebSocketClientImpl: key_updated Frame parsen; Exception-Logging
- Tink 1.15.0 als neue Dependency

## Server

- V4 Flyway Migration: ALTER TABLE users ADD COLUMN public_key TEXT
- Tables.kt: publicKey Feld in Users-Objekt
- UserRepository: getPublicKey() / setPublicKey()
- UserRoutes: PUT /api/users/{id}/public-key (Auth + Owner-Check +
  Längenvalidierung ≤ 10.000 Zeichen) und
  GET /api/users/{id}/public-key
- WebSocketManager: notifyKeyUpdated() Broadcast an alle anderen
  verbundenen Clients
- MessageRepository: EncryptionService für message body bypassed –
  Server speichert E2EE-Ciphertext direkt (Zero-Knowledge)

## Tests

- E2EEKeyManagerTest: 5 Tests (Roundtrip, Nonce-Uniqueness,
  Wrong-Key, hasKeyPair)
- EnsureKeyPairUseCaseTest: 4 Tests (generate+upload, skip wenn
  vorhanden, kein Upload ohne UserId, kein Crash bei Server-Fehler)
- MessageRepositoryImplTest: 5 neue E2EE-Tests

## Docs

- docs/migration-guide.md: E2EE-Einschränkungen dokumentiert
  (Pending-Message Klartext in SQLite)

## Follow-up

- #105: E2EE Private Key – AndroidKeysetManager statt
  CleartextKeysetHandle (Security Hardening)
2026-05-18 00:22:28 +02:00

77 lines
2.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Room-Migrationen Entwickler-Leitfaden
## Grundsatz
Ab Version 6 nutzt die App **Room @AutoMigration** für Schema-Änderungen.
Room generiert die Migrationen automatisch User müssen nichts tun.
`fallbackToDestructiveMigration()` ist als Fallback aktiv: Kann Room keine
automatische Migration ableiten, wird die DB zurückgesetzt und neu erstellt.
## Aktuelle DB-Version
**Version 6** (Stand: Mai 2026)
Historische manuelle Migrationen (v1v5) wurden entfernt.
Keine Rückwärtskompatibilität zu älteren DB-Versionen.
## Checkliste für neue Schema-Änderungen
1. **DB-Version hochzählen**: `version` in `BollwerkDatabase` anpassen
2. **@AutoMigration ergänzen**: `autoMigrations = [AutoMigration(from = X, to = Y)]` in der `@Database`-Annotation
3. **Falls AutoMigration nicht reicht** (z.B. Column-Rename, Table-Rebuild):
- `@RenameColumn` / `@DeleteColumn` AutoMigrationSpec schreiben
- Oder manuelle `Migration(X, Y)` in `Migrations.kt` + `DatabaseModule.addMigrations()`
4. **Schema-Export prüfen**: JSON in `app/schemas/` wird automatisch generiert
## Beispiel: Einfache Spalte hinzufügen
```kotlin
// BollwerkDatabase.kt
@Database(
entities = [...],
version = 7,
autoMigrations = [AutoMigration(from = 6, to = 7)],
exportSchema = true
)
```
Fertig Room erkennt die neue Spalte automatisch.
## Beispiel: Spalte umbenennen
```kotlin
@Database(
entities = [...],
version = 7,
autoMigrations = [AutoMigration(from = 6, to = 7, spec = V6ToV7::class)],
exportSchema = true
)
abstract class BollwerkDatabase : RoomDatabase() {
@RenameColumn(tableName = "items", fromColumnName = "old_name", toColumnName = "new_name")
class V6ToV7 : AutoMigrationSpec
}
```
## Schema-Export
Room exportiert Schemas automatisch nach `app/schemas/`. Diese JSON-Dateien werden
versioniert und können für `MigrationTestHelper` verwendet werden.
Konfiguration in `app/build.gradle.kts`:
```kotlin
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}
```
## E2EE Bekannte Einschränkungen
### Pending Messages im lokalen SQLite-Speicher
Nachrichten, die noch nicht erfolgreich an den Server gesendet wurden (Offline-Zustand), werden temporär als **Klartext** in der lokalen Room-Datenbank (`messages`-Tabelle, `is_pending = true`) gespeichert. Dies entspricht dem Standardverhalten von Offline-First E2EE-Messaging-Apps (z. B. Signal).
**Sicherheitsbeurteilung:** Die Room-Datenbank liegt im App-privaten Verzeichnis (`/data/data/de.bollwerk.app/databases/`). Auf normalen (nicht gerooteten) Geräten ist sie für andere Apps nicht zugänglich. Ein Angreifer mit physischem Zugriff auf ein gerootetes Gerät oder ADB-Zugriff (Developer Mode) könnte die Pending-Message-Klartexte theoretisch auslesen.
**Geplante Maßnahme:** Verschlüsselung der lokalen Message-DB mit SQLCipher (zukünftiges Feature).