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

2.8 KiB
Raw Permalink Blame History

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

// 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).