Der zweite Teil von #006 bindet den Kern an Datenbank und Worker. Nur src/worker/geheimnisse.ts liest Chiffrat und Data-Key; ein struktureller Test haelt fest, dass der Webprozess nichts von dort importiert und so nie an den Master-Key kommt. Die Zusatzdaten binden jedes Chiffrat an ID und Fingerprint seines Schluessels: kopiert man es in eine andere Zeile, laesst es sich dort nicht oeffnen. Der Deployment-Key entsteht beim ersten Start; ein Test prueft, dass ein pg_dump keinen Klartext enthaelt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4.3 KiB
4.3 KiB
#006 Verschlüsselte Ablage und OpenSSH-Schlüsselformat
Status: erledigt Meilenstein: v0.1.0 Labels: sicherheit Geschätzt: 4 h Tatsächlich: 0 h 25 min
Ziel
Envelope Encryption für Private Keys mit Master-Key aus Datei; eigene Erzeugung und Serialisierung von OpenSSH-Schlüsseln ohne temporäre Dateien; Deployment-Key der Instanz.
Aufgaben
- Master-Key aus Datei laden (32 Byte roh, Base64 oder Hex; Rechteprüfung) –
masterKeyLaden - Master-Key aus
SSHKV_MASTER_KEY_FILEnur im Worker anbinden - AES-256-GCM, DEK je Schlüssel, AAD = Schlüssel-ID + Fingerprint, Master-Key-Version
- ed25519 und RSA-4096 erzeugen (Node
crypto), OpenSSH-Format schreiben und lesen (optional Passphrase: bcrypt-pbkdf + aes256-ctr) - Fingerprint SHA256 wie
ssh-keygen -l - Deployment-Key beim ersten Start erzeugen
- Log-Redaction (
schwaerzen; Einbau in Logger/Protokoll folgt)
Akzeptanzkriterien
ssh-keygen -y -fliest erzeugte Schlüssel (mit und ohne Passphrase)- Fingerprints identisch zu
ssh-keygen -lf - Datenbank-Dump enthält keinen Klartext-Schlüssel
Abhängigkeiten
#003
Notizen / Entscheidungen
- Kern fertig; Anbindung an Datenbank/Worker folgt mit #007. Offen sind Deployment-Key beim ersten Start, Master-Key nur im Worker laden und der Nachweis „Datenbank-Dump enthält keinen Klartext“.
- Module:
src/lib/ssh/openssh.ts(Erzeugen/Schreiben/Lesen, Fingerprint, Public-Key-Zeilen),src/lib/krypto/umschlag.ts(Envelope Encryption, Master-Key laden),src/lib/protokoll-schwaerzen.ts(Redaction). Alle framework-frei. - Eigener Serializer für
openssh-key-v1stattssh-keygen: keine Private Keys als Temp-Dateien. Die Tests prüfen gegen das echtessh-keygen(beide Richtungen, mit/ohne Passphrase). - Passphrase:
aes256-ctr+bcryptmit 16 Runden (ssh-keygen nimmt heute 24).bcrypt-pbkdfist reines JavaScript; 16 Runden halten das im Worker unter einer Sekunde. Beim Lesen gilt die Rundenzahl aus der Datei (Obergrenze 1000 gegen absichtlich teure Importe). Andere Cipher (z. B.aes256-gcm@openssh.comviassh-keygen -Z) werden mit klarer Meldung abgelehnt. Private-Key-Import nur für ed25519 und RSA. SchluesselFormatFehlerträgt einencode(ungueltig,nicht_unterstuetzt,passphrase_fehlt,passphrase_falsch,zu_schwach); Meldungen sind feste Sätze ohne Eingabe.- RSA-Mindestlänge 2048 Bit standardmäßig in
publicKeyZeileLesenundprivateKeyLesen;{ rsaMindestBits: 0 }für das reine Beobachten vorhandenerauthorized_keys(Drift). - Kommentare mit Steuerzeichen werden abgelehnt, sonst ließe sich über einen Kommentar eine zweite
Zeile in
authorized_keyseinschleusen. - Umschlag-Format je Puffer:
Version (1) | IV (12) | Tag (16) | Daten. AAD Klartext =zusatz(vorgesehen:schluessel:<id>:<fingerprint>), AAD DEK =sshkv-dek:v<masterKeyVersion>.versiegeln(..., masterKeyVersion = 1); beim Öffnen muss der Aufrufer den Master-Key passend zur Version wählen. - Master-Key-Datei mit Gruppe/Welt-Leserecht → Abbruch, außer
SSHKV_MASTER_KEY_UNSICHER_ERLAUBT=1(dann Warnung). Achtung Docker: Secrets ohne Swarm sind Bind-Mounts mit den Rechten der Host-Datei – dortchmod 400und passenden Eigentümer setzen. - Schwärzen greift auf Feldnamen, die einen der Wortstämme
privat,passwort,password,passphrase,secret,geheimnis,token,masterkey,authorization,cookieenthalten (nicht nur als Präfix), sowie auf PEM-Private-Keys in jeder Zeichenkette (auch abgeschnitten, auch inmessage/stackvon Fehlern). Rohe Bytes werden zu[Binärdaten]. - Anbindung im Worker (Teil 2):
src/worker/geheimnisse.tsist die einzige Stelle, dieprivatVerschluesselt/dekVerschluesseltliest;tests/geheimnisse-grenze.test.tshält das strukturell fest, ebenso dass nichts außerhalb vonsrc/workervon dort importiert. - Zusatzdaten
schluessel:<id>:<fingerprint>: Die ID wird deshalb vor dem Versiegeln vergeben (randomUUID), nicht von Prisma. - Deployment-Key entsteht beim Start des Workers (ed25519, Kommentar
sshkv-deployment@<rechner>). Gleichzeitige Starts: der partielle Index lässt nur einen zu, der zweite liest den ersten. - Dump-Test:
pg_dump --data-onlyder Testdatenbank enthält weder „PRIVATE KEY“ noch den Schlüsselkern.