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)
77 lines
2.8 KiB
Markdown
77 lines
2.8 KiB
Markdown
# 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 (v1–v5) 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).
|