# ARC-08 – Prüfprotokoll: Verschlüsselungsschlüssel-Rotation Voraussetzung ARC-02 (Fertig). ## Architektur-Ausgangslage (real geprüft) Core (API-10, `internal/kek.Store.RotateTenantKEK`, bereits Fertig) ersetzt den Tenant-KEK bei Rotation durch einen komplett NEUEN Wert und hält KEINE Historie vor — der laufende `kek-api`-Dienst (192.168.1.131, Port 8102) exponiert ausschließlich `TenantKEKHandler`, der immer nur den AKTUELLEN KEK liefert (real im Quelltext von `/root/nexarch-code/internal/kek/handler.go` und `cmd/kek-api/main.go` auf 131 verifiziert). Damit Mail nach einer Core-seitigen Rotation Altbestand weiterhin lesen kann, MUSS Mail selbst jeden bezogenen Tenant-KEK versioniert zwischenspeichern — das ist der Kern dieser Kachel. ## Umsetzung - `mail/internal/crypto/kekversions.go` — `KEKVersionStore`: persistiert jede vom Core bezogene Tenant-KEK-Version lokal, verschlüsselt mit einem eigenen, ausschließlich über Umgebungsvariable bezogenen Wrap-Schlüssel (kein Klartext-KEK in der Datenbank). `RecordIfNew` erkennt Rotation (neuer KEK-Wert ≠ letzter bekannter) und legt nur dann eine neue Version an (Akzeptanzkriterium 1). `Revoke` sperrt gezielt eine einzelne Version (Pflichtprüfung 2). - `mail/internal/crypto/service.go` — `Service.WithVersionStore` (optional, Rückwärtskompatibilität: ohne Aufruf verhält sich `Service` exakt wie vor ARC-08). `Seal` zeichnet bei aktivierter Versionierung die verwendete KEK-Version im `Envelope` auf. Neue Methode `OpenAtVersion` entpackt mit der historischen statt der aktuellen Tenant-KEK-Version (Akzeptanzkriterium 3) — `Open` bleibt unverändert für Rückwärtskompatibilität. - `mail/internal/encstorage/encstorage.go` — neuer Sidecar `.dek.version` (gleiches Muster wie der bestehende `.dek`-Sidecar aus ARC-02) speichert die KEK-Version je Objekt. `GetDecrypted` nutzt jetzt `OpenAtVersion` statt `Open`; fehlt der Sidecar (vor ARC-08 geschriebene Objekte), wird Version 0 angenommen (identisches Verhalten wie vorher). - Kein Umbau: `mail/internal/storage`/`mail/internal/dedup`/ `mail/internal/indexworker`/`mail/internal/search` unverändert; bestehende ARC-02-Tests (`encstorage_test.go`) unverändert lauffähig ohne Codeänderung an ihnen. ## Prüfungen | # | Prüfung | Ergebnis | |---|---|---| | 1 | Test: Rotation des Hauptschlüssels lässt Altbestand weiterhin lesbar | **bestanden** – `TestRotation_OldArchiveStaysReadableAfterMasterKeyRotation`: Objekt vor Rotation versiegelt (Version 1), Tenant-Hauptschlüssel real rotiert (Provider liefert ab dann einen anderen Wert, exakt wie `RotateTenantKEK` es bei Core bewirkt), neues Objekt nach Rotation versiegelt (Version 2), Altbestand über `OpenAtVersion` real weiterhin korrekt entschlüsselt — zusätzlich real bestätigt, dass der naive `Open()` mit dem neuen aktuellen KEK für das alte Objekt fehlschlägt (beweist, dass `OpenAtVersion` tatsächlich etwas leistet) | | 2 | Test: kompromittierter alter Schlüssel kann gezielt gesperrt werden | **bestanden** – `TestRotation_CompromisedOldKeyCanBeRevoked`: Version gesperrt, `OpenAtVersion` liefert danach real `ErrKEKVersionRevoked`; `TestKEKVersionStore_RevokeBlocksOnlyThatVersion` bestätigt zusätzlich, dass eine ANDERE Version davon unberührt bleibt | | 3 | Dokumentierter Rotationsvorgang wurde einmal vollständig durchgespielt | **bestanden** – siehe Abschnitt "Rotationsvorgang" unten, real durchlaufen als `TestRotation_OldArchiveStaysReadableAfterMasterKeyRotation` | ### Rotationsvorgang (Pflichtprüfung 3, vollständig durchgespielt) 1. Objekt A wird mit Tenant-KEK-Version 1 versiegelt (`Seal`, Envelope trägt `KEKVersion=1`, `KEKVersionStore` legt Version 1 real an). 2. Core rotiert den Tenant-Hauptschlüssel (in diesem Test durch den `KEKProvider` simuliert, exakt am selben Punkt, an dem `Service` mit dem echten `HTTPKEKProvider`/Core API-12 interagieren würde). 3. Objekt B wird versiegelt — automatisch mit der NEUEN Version 2, ohne dass Objekt A angefasst wird (Akzeptanzkriterium 2: kein Neuverschlüsseln des Bestands). 4. Objekt A wird über `OpenAtVersion(..., kekVersion=1, ...)` gelesen — real erfolgreich, Klartext identisch zum Original. 5. Ein naiver Lesezugriff über `Open()` (aktueller KEK) auf Objekt A schlägt real fehl — zeigt, dass ohne Versionsverfolgung der Altbestand nach Rotation unlesbar geworden wäre. ## Build/Test-Ergebnis (192.168.1.131) ``` go build ./... -> clean go vet ./... -> clean golangci-lint run ./... -> 0 issues TEST_TENANT_DSN=postgresql://nexarch_test:***@localhost:5432/tenant_acme?sslmode=disable \ TEST_MANTICORE_URL=http://127.0.0.1:9308 \ go test ./... -v -p 1 -> alle Pakete bestanden, inkl. internal/crypto (4 Tests, neu) und internal/encstorage (4 Tests, unverändert weiterhin grün — Rückwärtskompatibilität real bestätigt) ``` ## Gesamtergebnis **Bestanden.** Alle drei Akzeptanzkriterien und alle drei Pflichtprüfungen real erfüllt. Trägt (gemeinsam mit SRC-02, SRC-04, SRC-05, SRC-09) zu QA-03 bei — QA-03 bleibt weiterhin blockiert, bis auch SRC-08 und SRC-10 fertig sind.