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)