Schluesselbund: ein Hauptschluessel, mehrfach verpackt (#190)

SecretKeyStore.Wrap band den Hauptschluessel unter Windows per DPAPI ans
Benutzerkonto und gab ihn sonst ungewrappt zurueck - eine Zeile, die man beim
Lesen ueberspringt und die auf dem Zielsystem "im Klartext" bedeutet.

Dahinter steht jetzt ein SecretKeyRing: derselbe Hauptschluessel, mehrfach
verpackt, je Fach ein ISecretProvider (dpapi, passphrase, file). Fuer Aufrufer
aendert sich nichts - GetOrCreateKey gibt weiterhin 32 Byte zurueck. Genau dafuer
war die Naht da.

WARUM MEHRERE FAECHER UND NICHT EIN BESSERES
Ein Schluessel an der Maschine ueberlebt keinen Umzug; einer an einer Passphrase
verlangt bei jedem Start jemanden, der tippt - und Dauerbetrieb ohne Menschen ist
der Zweck (#214). Der Bund entscheidet sich nicht: Im Alltag oeffnet das
Maschinenfach ohne Rueckfrage, nach einem Umzug die Passphrase, und beide fuehren
zu DEMSELBEN Schluessel. Alle enc:v2-Werte bleiben lesbar.

Und deshalb darf die Schluesseldatei jetzt ueberhaupt in eine Sicherung: Sobald
ein Wiederherstellungsfach da ist, steht der Hauptschluessel in keiner Fassung im
Klartext darin.

Nicht zu verwechseln mit der Sicherung mit Passphrase: Die schluesselt jeden WERT
einzeln ins Archiv um, fuer den Transport einer Instanz. Das Wiederherstellungs-
fach schluesselt den HAUPTSCHLUESSEL um, damit eine bestehende Installation
weiterlaeuft - unbeaufsichtigt ueber CLAWD_SECRET_PASSPHRASE. Die Unterscheidung
steht in docs/Roadmap.md bei #190.

Beide Altformate werden uebernommen: 32 rohe Byte werden zum Fach file, ein
DPAPI-Block zum Fach dpapi. Derselbe Schluessel, nur anders verpackt.

Zwei Zusagen, mit Tests festgehalten:
- Geht KEIN Fach auf, wird abgebrochen statt ein neuer Schluessel erzeugt. Ein
  neuer Hauptschluessel machte jeden bestehenden Wert wortlos unlesbar.
- Ein Umzug legt den Schluessel NICHT im Klartext ab, nur damit der naechste Start
  bequemer wird.

Beim automatischen Weg wird nur hinzugefuegt, nie weggenommen; weggenommen wird
nur in SetRecoveryPassphrase, wo ein Mensch gerade getippt hat.

#190 bleibt offen: Unter Linux greift weiterhin das Fach file. Neu ist, dass es
beim Start als Warnung im Protokoll steht statt unsichtbar zu sein. Womit das
Maschinenfach dort gefuellt wird, ist Entscheidung #180 - diese Schnittstelle ist
die Stelle, an der die Antwort eingesetzt wird.

834 Tests gruen, 0 Warnungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-10 15:34:16 +02:00
co-authored by Claude Opus 5
parent e3403087c2
commit 99f8a89fd0
7 changed files with 1148 additions and 60 deletions
+16
View File
@@ -307,6 +307,22 @@ public sealed class AppHost : IAsyncDisposable
ResolveBackupDirectory(instance, instancePath));
}
// Der Schluesselbund, mit dem die Zugangsdaten verschluesselt sind (#190). Was
// hier fehlt, faellt im Alltag nicht auf und kostet an dem einen Tag alles: Ein
// Bund ohne Wiederherstellungsfach laeuft tadellos, bis die Maschine wechselt -
// und dann ist jeder verschluesselte Wert verloren, auch der aus einer Sicherung.
// Deshalb steht es beim Start da und nicht erst in der Fehlermeldung danach.
try
{
foreach (var problem in SecretKeyStore.Problems)
logger.LogWarning("Schlüsselbund: {Problem}", problem);
}
catch (Exception ex)
{
// Der Bund wird gleich ohnehin gebraucht; hier nur melden, nicht abbrechen.
logger.LogError(ex, "Der Schlüsselbund liess sich nicht pruefen.");
}
// ─── 4. Lizenz ───
//
// Gefuehrt ist, wer es nachweisen kann (#535). Der Schalter --managed allein