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)
2.8 KiB
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
- DB-Version hochzählen:
versioninBollwerkDatabaseanpassen - @AutoMigration ergänzen:
autoMigrations = [AutoMigration(from = X, to = Y)]in der@Database-Annotation - Falls AutoMigration nicht reicht (z.B. Column-Rename, Table-Rebuild):
@RenameColumn/@DeleteColumnAutoMigrationSpec schreiben- Oder manuelle
Migration(X, Y)inMigrations.kt+DatabaseModule.addMigrations()
- Schema-Export prüfen: JSON in
app/schemas/wird automatisch generiert
Beispiel: Einfache Spalte hinzufügen
// BollwerkDatabase.kt
@Database(
entities = [...],
version = 7,
autoMigrations = [AutoMigration(from = 6, to = 7)],
exportSchema = true
)
Fertig – Room erkennt die neue Spalte automatisch.
Beispiel: Spalte umbenennen
@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:
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).