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:
@@ -159,6 +159,11 @@ export async function verifyIntegrity(req: AuthRequest, res: Response) {
|
||||
: ' ohne dokumentierte Löschung')
|
||||
: '';
|
||||
|
||||
const unverifiable = result.unverifiableEntries.length;
|
||||
const keinSchluessel = unverifiable > 0
|
||||
? ` ${unverifiable} Einträge sind HMAC-signiert und ohne konfigurierten AUDIT_HMAC_KEY nicht prüfbar.`
|
||||
: '';
|
||||
|
||||
const message = tampered > 0
|
||||
? `${tampered} MANIPULIERTE Einträge gefunden` + (gaps > 0 ? ` (zusätzlich ${luecken})` : '')
|
||||
: gaps > 0
|
||||
@@ -178,7 +183,8 @@ export async function verifyIntegrity(req: AuthRequest, res: Response) {
|
||||
// Nur Lücken ohne protokollierte Löschung sind erklärungsbedürftig.
|
||||
unexplainedGaps: result.unexplainedGaps,
|
||||
tampered: tampered > 0,
|
||||
message,
|
||||
message: message + keinSchluessel,
|
||||
unverifiableEntries: result.unverifiableEntries,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user