Der Sync-Schlüssel verlässt nie den Server im Klartext - verliert das einzige Gerät sync.key, erzeugte AppBootstrapper bisher stillschweigend einen neuen, unabhängigen Schlüssel, ohne dass der Nutzer etwas davon merkt. Bereits synchronisierte Server-Daten werden dadurch dauerhaft unlesbar. AppBootstrapper erkennt jetzt, ob ein fehlender Schlüssel bei einem bereits eingeloggten Gerät auftritt (SyncKeyStatus) - SettingsViewModel zeigt dafür ein Warnbanner im Synchronisation-Tab mit der Empfehlung, im Zweifel über ein noch intaktes zweites Gerät neu zu koppeln. Zusätzlich ein komplett offline laufender, selbstverwalteter Wiederherstellungscode (kein neuer dauerhafter Server-Speicher für ein sicherheitsrelevantes Geheimnis): SyncCrypto bekommt einen neuen, vom Pairing-Code kryptografisch unabhängigen Recovery-Code (120 Bit, Base32), mit dem der Sync-Schlüssel lokal verschlüsselt und als Datei durch den Nutzer selbst gesichert werden kann. Neuer SyncKeyRecoveryService kapselt Erzeugen/Einlösen; Redeem schreibt den wiederhergestellten Schlüssel und setzt vorsorglich Pull-Cursor/Versionsverfolgung zurück. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
33 lines
1.6 KiB
C#
33 lines
1.6 KiB
C#
using System.Text.Json;
|
|
using LehrerApp.Sync.Crypto;
|
|
|
|
namespace LehrerApp.Sync;
|
|
|
|
/// <summary>
|
|
/// Selbstverwalteter Wiederherstellungsweg für den lokalen Sync-Schlüssel (TODO 10.3.2). Anders
|
|
/// als das Geräte-Pairing (<see cref="SnapshotService"/>) läuft das komplett offline, ohne
|
|
/// Server-Beteiligung: der Nutzer sichert die verschlüsselte Schlüsseldatei selbst (z.B.
|
|
/// USB-Stick, eigene Cloud) und merkt sich den Code getrennt davon — Datei und Code zusammen
|
|
/// ergeben erst den Schlüssel.
|
|
/// </summary>
|
|
public class SyncKeyRecoveryService(byte[] syncKey, string keyPath)
|
|
{
|
|
public string CreateRecoveryFile(string code) =>
|
|
JsonSerializer.Serialize(new SyncKeyRecoveryFile(
|
|
SyncCrypto.EncryptKeyWithRecoveryCode(syncKey, code), DateTime.UtcNow));
|
|
|
|
/// <exception cref="System.Security.Cryptography.CryptographicException">
|
|
/// Falscher Code oder beschädigte/manipulierte Datei.</exception>
|
|
/// <exception cref="InvalidOperationException">Datei ist kein gültiges Wiederherstellungsformat.</exception>
|
|
public void RestoreFromFile(string fileContent, string code)
|
|
{
|
|
var file = JsonSerializer.Deserialize<SyncKeyRecoveryFile>(fileContent);
|
|
if (file is null || string.IsNullOrWhiteSpace(file.WrappedKey))
|
|
throw new InvalidOperationException("Ungültige Wiederherstellungsdatei.");
|
|
var restoredKey = SyncCrypto.DecryptKeyWithRecoveryCode(file.WrappedKey, code);
|
|
SyncCrypto.SaveKey(restoredKey, keyPath);
|
|
}
|
|
}
|
|
|
|
public record SyncKeyRecoveryFile(string WrappedKey, DateTime CreatedAt);
|