Externer Anker: Audit-Kette HMAC-signiert (Hash-Version 3)
Schliesst den nach R166/R167 verbliebenen Grenzfall: Bis Version 2 war die Kette selbsttragend - wer die DB schreiben kann, konnte jede Zeile aendern und alle Folgehashes konsistent nachziehen, die Pruefung meldete "gueltig". Version 3 signiert denselben Inhalt per HMAC-SHA256 mit AUDIT_HMAC_KEY, einem Schluessel ausserhalb der Datenbank. Ohne ihn laesst sich keine gueltige Signatur erzeugen; reiner DB-Schreibzugriff genuegt nicht mehr. Fail-safe: Ohne Schluessel wird weiter Version 2 geschrieben, es faellt nichts aus. Signierte Zeilen gelten dann als nicht pruefbar (unverifiableEntries) und ausdruecklich nicht als manipuliert. AUDIT_HMAC_KEY_OLD erlaubt einen Schluesselwechsel ohne Rehash. Restluecke der Versionsgrenze geschlossen: Wird die FRUEHESTE Zeile einer Stufe herabgestuft, wandert MIN(id) mit - die Grenze allein haette den Downgrade durchgewunken (der erste Testlauf fiel genau darauf durch). Der Nachfolger ist jedoch HMAC-signiert und sein previousHash ohne Schluessel nicht faelschbar; eine unerklaerte Luecke vor einer signierten Zeile gilt deshalb als Befund. Verifiziert: Inhalt geaendert -> erkannt; Downgrade 3->2 auf der fruehesten V3-Zeile -> erkannt; dasselbe auf der letzten V3-Zeile (kein Nachfolger) -> erkannt; ohne Schluessel 0 manipuliert / 2 nicht pruefbar; 40 parallele Schreiber -> 40/40, 0 Forks, alle V3. tsc + vite build gruen. AUDIT_HMAC_KEY in .env.example dokumentiert. Der Schluessel selbst liegt nur lokal in .env (gitignored). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,39 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **⚓ Externer Anker: Audit-Kette HMAC-signiert (Hash-Version 3)** (2026-08-18)
|
||||
- Schliesst den nach R166/R167 verbliebenen Grenzfall: Bis Version 2 war die
|
||||
Kette selbsttragend – wer die DB schreiben kann, konnte jede Zeile aendern
|
||||
und alle Folgehashes konsistent nachziehen, die Pruefung meldete „gueltig“.
|
||||
- Version 3 signiert denselben Inhalt per **HMAC-SHA256** mit `AUDIT_HMAC_KEY`
|
||||
– einem Schluessel, der NICHT in der Datenbank liegt. Ohne ihn laesst sich
|
||||
keine gueltige Signatur erzeugen; reiner DB-Schreibzugriff genuegt nicht mehr.
|
||||
- **Fail-safe:** Ohne Schluessel schreibt das Audit-Log weiter Version 2, es
|
||||
faellt nichts aus. Signierte Zeilen gelten dann als **nicht pruefbar**
|
||||
(eigener Topf `unverifiableEntries`) und ausdruecklich NICHT als
|
||||
manipuliert – eine fehlende Konfiguration darf kein Fehlalarm ueber das
|
||||
gesamte Log sein.
|
||||
- **Schluesselwechsel ohne Rehash:** `AUDIT_HMAC_KEY_OLD` wird bei der
|
||||
Pruefung zusaetzlich akzeptiert. Verifiziert: mit KEY_OLD 0 Befunde, ohne
|
||||
KEY_OLD werden die alt signierten Zeilen erwartungsgemaess auffaellig.
|
||||
- **Restluecke der Versionsgrenze geschlossen (selbst gefunden):** Wird die
|
||||
FRUEHESTE Zeile einer Stufe herabgestuft, wandert `MIN(id)` mit – die
|
||||
Grenze allein haette den Downgrade durchgewunken (mein erster Testlauf fiel
|
||||
genau darauf durch). Der Nachfolger ist jedoch HMAC-signiert, sein
|
||||
`previousHash` ist ohne Schluessel nicht faelschbar. Eine unerklaerte Luecke
|
||||
vor einer signierten Zeile gilt deshalb jetzt als Befund, nicht als
|
||||
struktureller Zufall.
|
||||
- Verifiziert: Inhalt geaendert + Hash beliebig → erkannt; Downgrade 3→2 mit
|
||||
gueltigem V2-Hash auf der **fruehesten** V3-Zeile → erkannt; dasselbe auf
|
||||
der **letzten** V3-Zeile (kein Nachfolger) → erkannt; ohne Schluessel
|
||||
0 manipuliert / 2 nicht pruefbar; 40 parallele Schreiber → 40/40, 0 Forks,
|
||||
alle V3. `tsc` + `vite build` gruen.
|
||||
- **Betrieb:** `AUDIT_HMAC_KEY` in `.env.example` dokumentiert
|
||||
(`openssl rand -hex 32`). Schluessel sichern – geht er verloren, sind die
|
||||
damit signierten Eintraege dauerhaft nicht mehr pruefbar (aber nicht als
|
||||
manipuliert gemeldet). Der Dev-Schluessel liegt nur lokal in `.env`
|
||||
(gitignored) und ist NICHT der Prod-Schluessel.
|
||||
|
||||
- [x] **🔒 hashVersion-Downgrade geschlossen (Pentest R167-01, HIGH)** (2026-08-18)
|
||||
- Der Pentester hat den Angriffsweg, den ich beim Uebergeben von R166-02
|
||||
selbst als naechsten benannt hatte, als **exploitbar bewiesen** (PoC gegen
|
||||
|
||||
Reference in New Issue
Block a user