# #006 Verschlüsselte Ablage und OpenSSH-Schlüsselformat **Status:** in Arbeit **Meilenstein:** v0.1.0 **Labels:** sicherheit **Geschätzt:** 4 h **Tatsächlich:** – ## 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 - [x] Master-Key aus Datei laden (32 Byte roh, Base64 oder Hex; Rechteprüfung) – `masterKeyLaden` - [ ] Master-Key aus `SSHKV_MASTER_KEY_FILE` nur im Worker anbinden - [x] AES-256-GCM, DEK je Schlüssel, AAD = Schlüssel-ID + Fingerprint, Master-Key-Version - [x] ed25519 und RSA-4096 erzeugen (Node `crypto`), OpenSSH-Format schreiben und lesen (optional Passphrase: bcrypt-pbkdf + aes256-ctr) - [x] Fingerprint SHA256 wie `ssh-keygen -l` - [ ] Deployment-Key beim ersten Start erzeugen - [x] Log-Redaction (`schwaerzen`; Einbau in Logger/Protokoll folgt) ## Akzeptanzkriterien - [x] `ssh-keygen -y -f` liest erzeugte Schlüssel (mit und ohne Passphrase) - [x] 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-v1` statt `ssh-keygen`: keine Private Keys als Temp-Dateien. Die Tests prüfen gegen das echte `ssh-keygen` (beide Richtungen, mit/ohne Passphrase). - Passphrase: `aes256-ctr` + `bcrypt` mit **16 Runden** (ssh-keygen nimmt heute 24). `bcrypt-pbkdf` ist 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.com` via `ssh-keygen -Z`) werden mit klarer Meldung abgelehnt. Private-Key-Import nur für ed25519 und RSA. - `SchluesselFormatFehler` trägt einen `code` (`ungueltig`, `nicht_unterstuetzt`, `passphrase_fehlt`, `passphrase_falsch`, `zu_schwach`); Meldungen sind feste Sätze ohne Eingabe. - RSA-Mindestlänge 2048 Bit standardmäßig in `publicKeyZeileLesen` und `privateKeyLesen`; `{ rsaMindestBits: 0 }` für das reine Beobachten vorhandener `authorized_keys` (Drift). - Kommentare mit Steuerzeichen werden abgelehnt, sonst ließe sich über einen Kommentar eine zweite Zeile in `authorized_keys` einschleusen. - Umschlag-Format je Puffer: `Version (1) | IV (12) | Tag (16) | Daten`. AAD Klartext = `zusatz` (vorgesehen: `schluessel::`), AAD DEK = `sshkv-dek:v`. `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 – dort `chmod 400` und passenden Eigentümer setzen. - Schwärzen greift auf Feldnamen, die einen der Wortstämme `privat`, `passwort`, `password`, `passphrase`, `secret`, `geheimnis`, `token`, `masterkey`, `authorization`, `cookie` enthalten (nicht nur als Präfix), sowie auf PEM-Private-Keys in jeder Zeichenkette (auch abgeschnitten, auch in `message`/`stack` von Fehlern). Rohe Bytes werden zu `[Binärdaten]`.