76a30458fc
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
490 lines
62 KiB
Markdown
490 lines
62 KiB
Markdown
---
|
||
phase: quick-261009-ikt
|
||
plan: 01
|
||
status: complete
|
||
quick_id: 261009-ikt
|
||
tasks_done: [1, 2, 3, 4, 5, 6, 7, 8]
|
||
---
|
||
|
||
# Quick 261009-ikt: Zertifikat-Manager Umbau - Summary
|
||
|
||
## Task 1
|
||
|
||
**Commit:** 75ea83f `feat(cert-manager): Reiter „Dateien“ mit mehreren Dateien und ein Parser für RSA und EC – Durchstich` (84 Dateien, 16 Löschungen alle beabsichtigt: 7 API, 9 Web). Nicht gepusht.
|
||
|
||
### Gates (gemessen, Stack neu gebaut mit `docker compose up -d --build api web`)
|
||
|
||
| Gate | Ergebnis |
|
||
|------|----------|
|
||
| api vitest `src/cert-manager src/module-registry` | 10 Dateien, 255 Tests grün |
|
||
| web vitest `modules/cert-manager src/messages` | 7 Dateien, 41 Tests grün (inkl. umlaut-guard) |
|
||
| tsc api / web | ohne Fehler |
|
||
| biome lint beide Modulordner | sauber |
|
||
| biome check eigene Dateien (api cert-manager, web components, page, actions, working-set(+test), use-cert-workspace, Seitentest, de/en.json) | sauber (7 eigene Dateien mit `--write` formatiert, nie ganze Ordner außerhalb des Moduls) |
|
||
| check-cert-messages.cjs 15 | `messages ok 51` |
|
||
| Löschungs- und grep-Gates (alte Dateien weg, keine forge-Zertifikatparser, keine alten Routen im Controller) | grün |
|
||
| Fixtures: keine ignorierten Dateien, Pflichtdateien vorhanden | grün |
|
||
| `e2e-cert.sh all` (Abschnitt files) | `e2e cert files ok` |
|
||
| gesamte `<automated>`-Kette | letzte Zeile `task1 ok` |
|
||
|
||
### Fixtures (Output-Punkt 1)
|
||
|
||
OpenSSL 3.5.7 (9 Jun 2026). Erzeugt mit `__fixtures__/make-fixtures.sh` (47 Dateien im Ordner inkl. Skript und README):
|
||
|
||
- RSA-PKI: rsa-root, rsa-root2, rsa-inter, rsa-inter-cross, rsa-inter-expired, rsa-inter-decoy (gleicher Name UND gleiche SubjectKeyIdentifier wie rsa-inter, anderer Schlüssel: checkIssued true, verify false), rsa-leaf (.pem/.cer), rsa-leaf-noaki, rsa-fullchain, rsa-chain.p7b/.p7c, rsa-trusted
|
||
- EC-PKI: ec-root (P-384), ec-inter (P-384), ec-leaf (P-256, .pem/.cer), ec-fullchain, ec-chain.p7b
|
||
- Sonstige Zertifikate: selfsigned-leaf, aia-private-leaf (127.0.0.1, 169.254.169.254, ldap://)
|
||
- Schlüssel RSA: rsa-leaf-key (PKCS#8), -pkcs1, -enc-pkcs8, -enc-trad, -pkcs8.der, -pkcs1.der
|
||
- Schlüssel EC: ec-leaf-key (PKCS#8), -sec1, -enc-pkcs8 (.pem/.der, 3DES), -enc-trad, -sec1.der
|
||
- CSR: rsa-leaf.csr/.csr.der, ec-leaf.csr/.csr.der
|
||
- PFX: rsa-modern, rsa-compat, rsa-legacy, rsa-nopass (leeres Passwort), rsa-modern.bin, ec-modern, ec-compat
|
||
- encrypted-entry.zip (ZipCrypto, Passwort Test-Pass-123)
|
||
- Passwort aller geschützten Dateien: `Test-Pass-123`. CA-Schlüssel werden nach der Erzeugung gelöscht.
|
||
|
||
### Abweichungen
|
||
|
||
1. **[Rule 1 - Bug] selfSigned-Berechnung.** D-18 verlangt `checkIssued(self) && verify(self)`. OpenSSLs checkIssued lehnt ein Zertifikat ab, dessen Schlüsselverwendung kein keyCertSign enthält; ein selbstsigniertes Serverzertifikat mit `digitalSignature,keyEncipherment` wäre dann nicht „selbstsigniert“ (die Kette würde fälschlich „Zwischenzertifikat fehlt“ melden). Umsetzung: `verify(eigener Schlüssel)` UND (`checkIssued(self)` ODER `subject === issuer`). Erfüllt weiterhin die Spec-Anforderung (selfsigned-leaf: selfSigned true, Rolle end-entity). Datei: cert-model.ts, Funktion `isSelfSigned`.
|
||
2. **Dateinamen aus multer.** multer liest Dateinamen als Latin-1; `repairFileName` im Controller gewinnt UTF-8-Umlaute zurück (mit Spec). Nicht im Plan, rein für die Anzeige.
|
||
3. Der Plan nennt `certError(code, status, message)`; so umgesetzt. Keine `CERT_ERROR_STATUS`-Tabelle.
|
||
4. `cert-manager.module.ts`: Providerliste entfernt, Kommentar angepasst (Seeding und Logzeile unverändert).
|
||
5. Keine Stubs: `detectZip`, `detectPkcs12`, `detectPkcs7`, `detectPrivateKey`, `detectCsr` sind laut Plan benannte leere Stufen (Tasks 3 und 4), `analyzeWorkingSet` liefert `chains`/`locked` leer bis Task 2/4. Nichts davon erreicht die Oberfläche als Platzhaltertext.
|
||
|
||
### Bedrohungsstatus
|
||
|
||
- T-ikt-02 (Upload-Größe): multer 30 x 5 MiB, Gesamtsumme 20 MiB -> 413 tooLarge (Controller-Spec); Web lehnt vorab ab (30 Einträge, 5 MiB, 10 MiB gesamt).
|
||
- T-ikt-03 (kaputte Eingaben): jede Stufe in try/catch, Spec mit Zufallsbytes, leerer Datei, abgeschnittenem DER, kaputtem Base64: nie ein Fehler, nur `unknown`.
|
||
- T-ikt-08 (Schlüssel/Passwörter): Passwort-Feld im Zustand vorhanden, aber noch nirgends befüllt oder gesendet; kein Logger-Aufruf mit Inhalten.
|
||
- T-ikt-11 (Zugriff): Controller-Spec prüft Klassenpfad, Modul-Slug und die exakte Handlerliste; alte Routen liefern live 404.
|
||
- T-ikt-12/13: Namen nur zur Anzeige (Steuerzeichen entfernt, max. 255), React-Text ohne `dangerouslySetInnerHTML`.
|
||
|
||
### Hinweise für Task 2
|
||
|
||
- API: `cert-types.ts` enthält schon alle Typen (BuildInput, ChainInfo, CertErrorCode, `certError(code, status, message)`). `cert-output.ts` enthält nur `safeBaseName`; `buildOutput` kommt dazu. `analyzeWorkingSet` in `cert-analyze.ts` setzt `chains: []` - dort `buildChains` einhängen.
|
||
- `CertItem.keyId` ist die Schlüsselkennung `k-<16 Hex von sha256(SPKI-DER)>` (gleiche Form wie KeyItem.id/CsrItem.keyId); `selfSigned` und `role` kommen schon aus `certItemFromDer`. Hilfsfunktionen in cert-model.ts exportiert: `describeKey`, `keyIdOf`, `sha256Hex`, `roleOf`, `leadingDerSequence`.
|
||
- Fixtures für Task 2: rsa-inter-cross (andere Wurzel rsa-root2), rsa-inter-expired, rsa-inter-decoy (gleicher Name und gleiche SKI, anderer Schlüssel: Pflichttest für checkIssued + verify), rsa-leaf-noaki, ec-*, selfsigned-leaf. Fixture-Zertifikate sind ab "jetzt" (2026-10-09) 100 Jahre gültig, rsa-inter-expired 2020 bis 2021.
|
||
- Web: `useCertWorkspace()` liefert `{ entries, analysis, analysisIds, status, errorKey, addFiles, remove, clear, retry }`; `analysisIds[source.file]` ist die Eintrags-Kennung zum Analysezeitpunkt (Antworten mit alter Reihenfolge bleiben korrekt zuordenbar). `actions.ts` hat `certErrorKey(error)` (Code, 413 ohne Code -> tooLarge, sonst generic) für `certManager.errors.<key>`. `ROLE_STYLES` ist aus `components/FilesTab.tsx` exportiert. `page.tsx` kennt `TabId = 'files'`; neue Reiter dort in Reihenfolge einfügen und `EmptyWorkspace` bei leerer Liste.
|
||
- Messages: Namespace `certManager` ist neu strukturiert (`tabs`, `roles`, `files`, `errors` mit allen D-24-Codes); `check-cert-messages.cjs <min>` zählt jetzt 51 Schlüssel, Mindestanzahl in späteren Tasks erhöhen. Neue deutsche Wörter mit ae/oe/ue/ss in `umlaut-dictionary.ts` (UMLAUT_ALLOWLIST) eintragen, wenn der Guard sie meldet.
|
||
- e2e-cert.sh: `BUILT="files"`; neue Abschnitte als Funktion `section_<name>` anlegen, in `run_section` freischalten und an `BUILT` hängen. Setup prüft, dass `POST parse` 404 liefert (Hinweis zum Neubau). Hilfsfunktion `analyze <out> <fixture>:<anzeigename>...`.
|
||
- Live-Befund: API-Image-Neubau dauert über 2 Minuten; `docker compose up -d --build api web` im Hintergrund starten.
|
||
|
||
## Task 2
|
||
|
||
**Commit:** 30118a2 `feat(cert-manager): Zusammenführen mit Fullchain und Nur Kette` (24 Dateien, keine Löschungen, 10 neu). Nicht gepusht. Das e2e-Skript unter .planning ist wie vorgegeben nicht eingecheckt.
|
||
|
||
### Gates (gemessen, Stack neu gebaut mit `docker compose up -d --build api web`)
|
||
|
||
| Gate | Ergebnis |
|
||
|------|----------|
|
||
| api vitest `src/cert-manager src/module-registry` | 12 Dateien, 284 Tests grün |
|
||
| web vitest `modules/cert-manager src/messages` | 8 Dateien, 55 Tests grün (inkl. umlaut-guard) |
|
||
| tsc api / web | ohne Fehler |
|
||
| biome lint beide Modulordner, biome check eigene Dateien (api cert-manager + main.ts, web components, page, actions, working-set, use-cert-workspace, Seitentest, de/en.json) | sauber |
|
||
| check-cert-messages.cjs 25 | `messages ok 70` |
|
||
| `certBuildJsonBody` in main.ts, keine forge-Zertifikatparser | grün |
|
||
| `e2e-cert.sh all` | `e2e cert files ok`, `e2e cert fullchain ok` |
|
||
| gesamte `<automated>`-Kette | letzte Zeile `task2 ok` |
|
||
|
||
Live bewiesen (Abschnitt fullchain): openssl verify der Fullchain OK, Pool in falscher Reihenfolge plus fremdes RSA-Zwischenzertifikat ergibt genau 2 Blöcke (3 mit Wurzel, Wurzel zuletzt), Nur Kette = nur Zwischenzertifikat, RSA ohne Wurzel meldet `chainComplete false` und „Tessera Test Root RSA“, Körper von rund 330 kB innerhalb der DTO-Grenzen -> 400 notACertificate (nie 413), 600 KiB -> 413 `tooLarge`, kaputtes JSON -> 400 `invalidInput`, Anmeldung mit 150 kB -> 413 (100-kB-Grenze der anderen Routen unverändert), danach Anmeldung weiter möglich (Nests globaler JSON-Leser aktiv).
|
||
|
||
### Abweichungen
|
||
|
||
1. **[Rule 3 - Blocking] Neue Datei `cert-names.ts`.** `safeBaseName` liegt jetzt dort (cert-output.ts exportiert es weiterhin). Grund: `cert-output.ts` braucht jetzt `certItemFromDer` aus cert-model.ts, und cert-model.ts importierte `safeBaseName` aus cert-output.ts; das wäre eine Importschleife. `cert-model.ts` importiert jetzt aus `cert-names`. Verhalten unverändert, die bestehenden safeBaseName-Specs laufen unverändert gegen cert-output.
|
||
2. `buildChains(certs, headIds?)`: zusätzlicher optionaler Parameter für vorgegebene Kettenköpfe (`buildOutput` baut genau die Kette des gesendeten Serverzertifikats, auch wenn der Kopf ein Zwischenzertifikat ist). Ohne Parameter exakt das D-18-Verhalten.
|
||
3. Nur-Kette-Knopf in der Oberfläche ist deaktiviert (mit Hinweis), wenn es keinen Aussteller zum Mitnehmen gibt; die API antwortet in diesem Fall weiterhin mit 400 `noChain`.
|
||
4. Knöpfe benutzen die Design-Klassen `btn btn-primary` / `btn btn-secondary` aus globals.css.
|
||
|
||
### Bedrohungsstatus
|
||
|
||
- Body-Grenze (D-26): eigener Leser `certBuildJsonBody` (Funktionsname nie `jsonParser`), Express aus der Kopie von `@nestjs/platform-express`, `main.ts` registriert vor `app.listen`; Spec rechnet die größte DTO-Anfrage aus den exportierten Konstanten (unter 512 KiB) und prüft 413/400-Abbildung.
|
||
- Reihenfolge nie vom Browser: `buildOutput` baut immer neu über `buildChains`, Fremdzertifikate im Pool werden verworfen, Nicht-Zertifikate -> 400 notACertificate.
|
||
- Keine Passwörter oder Schlüssel in diesem Task im Spiel; keine Logger-Aufrufe mit Anfrageinhalt.
|
||
|
||
### Hinweise für Task 3
|
||
|
||
- API: `cert-model.ts` Stufen `detectZip` und `detectPkcs7` sind weiter leere Slots; PEM-Schleife in `detectPem` hat den Kommentar für PKCS7/CMS-Etiketten. `buildChains` und `analyzeWorkingSet` brauchen für ZIP/P7B keine Änderung (Ketten entstehen aus allen erkannten Zertifikaten). `cert-names.ts` ist der Ort von `safeBaseName`.
|
||
- Web: `page.tsx` hat `TabId = 'files' | 'merge'`; neue Reiter dort in der D-23-Reihenfolge einfügen (analyze, split zwischen files und merge). Für alle Nicht-Dateien-Reiter ist `EmptyWorkspace` und die Zeile „Grundlage: …“ schon in `page.tsx` verdrahtet (`workspace.basis`/`workspace.empty`/`workspace.goToFiles`); nur noch die Reiterinhalte ergänzen. `ChainView` und `ROLE_STYLES` (aus FilesTab) sind wiederverwendbar.
|
||
- Messages: Namespace hat jetzt `tabs.merge`, `workspace.*`, `merge.*`, `chain.*`; `check-cert-messages.cjs` zählt 70 Schlüssel (Mindestzahl dort erhöhen). Umlaut-Allowlist: `vertrauen` ergänzt.
|
||
- e2e-cert.sh: `BUILT="files fullchain"`; Hilfsfunktionen `build_json <out> <content> <cert> <includeRoot> <pool...>`, `post_build <req> <out>`, `pem_subjects <antwort>` (schreibt `cert-N.pem` nach `$E2E_TMP`). Der Abschnitt files prüft jetzt zwei Ketten statt leerer `chains`.
|
||
- Rebuild dauerte rund 3 Minuten; im Hintergrund starten und mit `until grep -qx done <log>` warten (Monitor ist nicht verfügbar).
|
||
|
||
## Task 3
|
||
|
||
**Commit:** 1554ae8 `feat(cert-manager): Hersteller-ZIP, PKCS#7, eingefügter Text, Analysieren und Aufteilen` (22 Dateien, 7 neu, keine Löschungen). Nicht gepusht.
|
||
|
||
### Gates (gemessen, Stack neu gebaut mit `docker compose up -d --build api web`)
|
||
|
||
| Gate | Ergebnis |
|
||
|------|----------|
|
||
| api vitest `src/cert-manager src/module-registry` | 13 Dateien, 313 Tests grün (zip-expand 15, cert-model 21, cert-analyze 12) |
|
||
| web vitest `modules/cert-manager src/messages` | 10 Dateien, 89 Tests grün (inkl. umlaut-guard) |
|
||
| tsc api / web | ohne Fehler |
|
||
| biome lint beide Modulordner, biome check eigene Dateien | sauber |
|
||
| check-cert-messages.cjs 40 | `messages ok 107` |
|
||
| keine forge-Zertifikatparser | grün |
|
||
| `e2e-cert.sh all` | `e2e cert files ok`, `e2e cert fullchain ok`, `e2e cert zip ok` |
|
||
| gesamte `<automated>`-Kette | letzte Zeile `task3 ok` |
|
||
|
||
Live bewiesen (Abschnitt zip): Hersteller-ZIP (EC-Server, Zwischen, Wurzel, `__MACOSX`, Inner-ZIP, readme.txt) plus rsa-leaf und rsa-inter ergibt fünf Zertifikate, zwei Ketten, ignored nestedZip und unknown (readme.txt), `MACOSX` kommt in der Antwort nirgends vor; dasselbe ZIP als `bundle.dat` wird geöffnet; `rsa-chain.p7c` (DER) und `ec-chain.p7b` (PEM, EC) liefern je drei Zertifikate; Fullchain aus den aus dem ZIP erkannten EC-Zertifikaten hat zwei Blöcke und besteht `openssl verify`.
|
||
|
||
### Abweichungen
|
||
|
||
1. **[Rule 3 - Blocking] `MergeTab.test.tsx` angefasst (nicht in der Dateiliste des Plans).** `CertWorkspace` hat jetzt `addText`; die Workspace-Attrappe dieses Tests brauchte eine Zeile `addText: () => null`, sonst bricht `tsc`. Sonst unverändert.
|
||
2. **`cleanSourcePath` nach `cert-names.ts` verschoben** (gleiche Importschleifen-Vermeidung wie `safeBaseName` in Task 2): `zip-expand.ts` braucht es, `cert-analyze.ts` importierte es bisher selbst. `cert-analyze.ts` exportiert es weiter (`export { cleanSourcePath }`), die Specs laufen unverändert.
|
||
3. **Typ-Umgehung bei forge:** `forge.asn1.fromDer(bytes, options)` nimmt zur Laufzeit ein Optionsobjekt, die Typen kennen nur `boolean`. `{ decodeBitStrings: false } as unknown as boolean` mit Erklärung im Code. Ohne `decodeBitStrings: false` stimmten die Zertifikatsbytes nach dem erneuten Schreiben nicht mehr mit dem Original überein (anderer Fingerabdruck); die Spec prüft die Kennungen gegen die Einzelzertifikate.
|
||
4. Das Verschachtelte-ZIP-Erkennen geschieht zuerst am Namen (`.zip`, ohne Entpacken) und danach an den Anfangsbytes des entpackten Eintrags (ohne die Endung). Beides meldet `nestedZip`.
|
||
5. Verschlüsseltes ZIP: `encrypted-entry.zip` meldet den Eintrag mit Pfad `<zip>/rsa-leaf.pem` und `encryptedZip`. Ein ZIP, aus dem gar nichts Lesbares und nichts Ausgelassenes entsteht (leeres Archiv), ergibt `unknown` für das ZIP selbst, damit die Oberfläche nie schweigt.
|
||
6. Keine Stubs. `detectPkcs12`, `detectPrivateKey`, `detectCsr` sind weiter die benannten leeren Stufen für Task 4.
|
||
|
||
### Bedrohungsstatus
|
||
|
||
- Zip-Bombe / Speicher (T-ikt-02): alle Grenzen auf den Kopfdaten vor dem ersten `getData()` (Spec zählt Entpackungen: 0 bei zipTooLarge); Einzelgröße 1 MiB, Verhältnis 100, Summe 20 MiB, 100 Einträge, eine Ebene; Ergebnis des Entpackens wird nochmals gegen die Grenze geprüft.
|
||
- Pfade (T-ikt-12/13): Eintragsnamen nur zur Anzeige, Steuerzeichen entfernt, höchstens 255 Zeichen, nie ein Dateipfad. Web zeigt sie als React-Text.
|
||
- Kaputte Eingaben (T-ikt-03): ZIP-, PKCS#7- und Texterkennung stehen in try/catch (Zufallsbytes mit ZIP-Anfang: brokenZip, abgeschnittenes PKCS#7: unknown).
|
||
- Keine Passwörter oder Schlüssel in diesem Task im Spiel; keine Logger-Aufrufe mit Inhalten. Eingefügter Text lebt nur im Browserarbeitsbereich (D-11).
|
||
|
||
### Hinweise für Task 4
|
||
|
||
- API `cert-model.ts`: Stufen `detectPkcs12`, `detectPrivateKey`, `detectCsr` sind weiter leere Slots (Reihenfolge in `STAGES`: ZIP, PEM, DER-Zertifikat, PKCS#12, PKCS#7, Schlüssel, CSR). Die PEM-Schleife in `detectPem` hat den Kommentar für PRIVATE KEY / ENCRYPTED / CERTIFICATE REQUEST. Rekursion: `detectZip` ruft `detectBlob` je ZIP-Eintrag mit `ctx.path = "zip/eintrag"` und gleichem `file` und `passwords`; Passwörter pro Datei und Container (auch im ZIP) funktionieren damit ohne weitere Änderung der ZIP-Stufe. Ein ZIP-Eintrag, der zu einer gesperrten PFX wird, liefert `locked` mit dem Pfad des Eintrags.
|
||
- `forge` ist in `cert-model.ts` als `import * as forge from 'node-forge'` eingebunden (nur ASN.1 und util). Hilfsfunktion `pkcs7Certificates(der)` zeigt den ASN.1-Lauf; für den CSR-Lauf dieselbe Optionen-Umgehung (`decodeBitStrings: false`) verwenden, wenn Bytes unverändert bleiben sollen.
|
||
- Web: `CertWorkspace` hat `addText(text, label?)`; `WorkingEntry.origin` kennt `paste`, Dateien heißen `pasted-<n>.pem` (Nummer zählt weiter nach Entfernen). `ItemCard` nimmt nur `CertItem`; Task 4 ergänzt Karten für Schlüssel und CSR (Rollenfarben in `ROLE_STYLES` sind schon da) und die Zuordnung (`certIds`, `csrIds`, `keyId`). `AnalyzeTab` filtert aktuell `kind === 'certificate'`; Schlüssel/CSR/Gesperrte kommen dort dazu (`files.ignored.unsupportedKey` existiert). `SplitTab` ist bereits über alle Arten generisch (`.crt`, `.key`, `.csr`; Schlüssel-PEM aus `item.pem`).
|
||
- Messages: `tabs.analyze`, `tabs.split`, `analyze.*`, `split.*`, `files.paste*`, `files.pastedLabel`, `files.originPaste`, `files.rejected.pasteTooLarge` neu; `check-cert-messages.cjs` zählt jetzt 107 Schlüssel (Mindestzahl 50 im Task-4-Gate ist damit schon erfüllt, ruhig höher ansetzen). Die Umlaut-Allowlist blieb unverändert.
|
||
- e2e-cert.sh: `BUILT="files fullchain zip"`; `analyze` nimmt jetzt auch absolute Pfade (z. B. ZIP in `$E2E_TMP`) in der Form `/pfad/datei:anzeigename`. Der Abschnitt zip baut sein ZIP mit `python3 -I` nach `$E2E_TMP/vendor.zip`.
|
||
- Rebuild dauerte wieder rund 3 Minuten (im Hintergrund starten, `until grep -qx done <log>`).
|
||
|
||
## Task 4
|
||
|
||
**Commit:** fcac0a3 `feat(cert-manager): Schlüssel, PFX und CSR erkennen, Passwort je Datei` (28 Dateien, 7 neu, keine Löschungen). Nicht gepusht. Das e2e-Skript unter .planning ist wie vorgegeben nicht eingecheckt.
|
||
|
||
### Gates (gemessen, Stack neu gebaut mit `docker compose up -d --build api web`)
|
||
|
||
| Gate | Ergebnis |
|
||
|------|----------|
|
||
| api vitest `src/cert-manager src/module-registry` | 16 Dateien, 389 Tests grün (cert-keys 32, cert-pkcs12 14, cert-csr 7, cert-analyze 21, cert-chain 18, controller 18) |
|
||
| web vitest `modules/cert-manager src/messages` | 10 Dateien, 106 Tests grün (inkl. umlaut-guard) |
|
||
| tsc api / web | ohne Fehler |
|
||
| biome lint beide Modulordner, biome check eigene Dateien | sauber (10 eigene Dateien mit `--write` formatiert, nur eigene Dateien) |
|
||
| check-cert-messages.cjs 50 | `messages ok 135` |
|
||
| keine forge-Zertifikat-/CSR-/PKCS#7-Leser im Produktivcode | grün |
|
||
| `e2e-cert.sh all` | `e2e cert files ok`, `fullchain ok`, `zip ok`, `inputs ok` |
|
||
| gesamte `<automated>`-Kette | letzte Zeile `task4 ok` |
|
||
|
||
Live bewiesen (Abschnitt inputs): Satz aus RSA-Server/Zwischen/Wurzel, klassisch verschlüsseltem RSA-Schlüssel, RSA-CSR, `ec-compat.pfx` und EC-CSR (DER) mit `passwords` im Takt der Dateien: sechs Zertifikate, zwei Schlüssel, zwei Anfragen, nichts gesperrt; RSA-Server trägt `keyId`, der EC-Server aus der PFX ebenfalls, beide CSRs sind Zertifikat und Schlüssel zugeordnet; `rsa-modern.pfx` ohne Passwort `passwordNeeded` (Container pkcs12), mit `falsch` `passwordWrong`; `rsa-legacy.pfx` (RC2) und `rsa-modern.bin` (ohne Endung) liefern je drei Zertifikate plus Schlüssel; verschlüsselter EC-Schlüssel (DER) gesperrt bzw. `passwordWrong`; `passwords=nope` -> 400 `invalidInput`; `docker compose logs api --since 10m` enthält weder `Test-Pass-123` noch `PRIVATE KEY`.
|
||
|
||
### Abweichungen
|
||
|
||
1. **TDD-Reihenfolge.** Die Specs für Schlüssel, PFX und CSR wurden zusammen mit der Umsetzung geschrieben, nicht vorher rot gesehen; alle Fälle der `<behavior>` sind abgedeckt und liefen beim ersten Lauf grün.
|
||
2. **`describeKey`, `keyIdOf`, `sha256Hex` nach `cert-keys.ts` verschoben** (Importschleife wie bei `cert-names` in Task 2/3: cert-keys/cert-csr/cert-pkcs12 brauchen sie, cert-model bindet sie ein). `cert-model.ts` exportiert sie weiter, alle bisherigen Importe laufen unverändert.
|
||
3. **`CertItem.keyId` und `CsrItem.keyId` sind nach dem Parser `null`** (vorher Schlüsselkennung des Zertifikats). `matchKeys` setzt sie erst, wenn der passende Schlüssel in der Menge liegt; so bedeutet „keyId gesetzt“ wirklich „passender Schlüssel vorhanden“. Die eine Prüfung in `cert-model.spec.ts` wurde angepasst.
|
||
4. **`readPkcs12(der, passwords, ownPassword = '')`:** zusätzlicher dritter Parameter, damit „Passwort falsch“ (eigenes Passwort eingegeben) von „Passwort nötig“ unterschieden wird. `DetectContext` hat dafür das optionale Feld `ownPassword`; `passwords` ist die Kandidatenliste (eigenes zuerst, höchstens 10, `candidatePasswords` in cert-keys.ts). PKCS#12 probiert zusätzlich das leere Passwort (nicht mitgezählt).
|
||
5. **Rule 3 - Blocking: `MergeTab.test.tsx` und `SplitTab.test.tsx` angefasst** (nicht in der Dateiliste): `CertWorkspace` hat jetzt `setPassword`, die Attrappen brauchten eine Zeile `setPassword: () => {}`, sonst bricht `tsc` (gleiche Lage wie `addText` in Task 3).
|
||
6. **`ItemCard` nimmt jetzt `item` (Zertifikat, Schlüssel oder Anfrage) und `items`** statt `cert`; innen gibt es `CertCard`, `KeyCard`, `CsrCard`. Einziger Aufrufer ist AnalyzeTab.
|
||
7. `forge.pki.RDNAttributesAsArray` fehlt in den Typdefinitionen: mit einer schmalen Typumgehung an einer Stelle in cert-csr.ts verwendet (liest nur den Namen; ist kein Zertifikat-/CSR-Parser).
|
||
8. Keine Stubs. `cert-types.ts` blieb unverändert (die Typen standen seit Task 1).
|
||
|
||
### Bedrohungsstatus
|
||
|
||
- T-ikt-08 (Schlüssel/Passwörter): Passwörter nur im Browserzustand und im multipart-Körper (`passwords`), nie in Adresse, Dateiname, Fehlertext, Antwort oder Log; Specs prüfen die Antwort (API) und den dargestellten Text (Web), das e2e prüft das API-Log. Fehlertexte aus OpenSSL/forge werden nicht weitergereicht.
|
||
- T-ikt-03 (kaputte Eingaben): jede Schlüssel-, PKCS#12- und CSR-Stufe in try/catch; kaputter Schlüsselblock -> `unsupportedKey`, kaputtes DER -> `unknown`; Specs mit abgeschnittenem CSR, Zufallsbytes, kaputtem Base64.
|
||
- Eingabegrenzen `passwords`: JSON-Liste aus höchstens 30 Zeichenketten zu höchstens 1024 Zeichen, sonst 400 `invalidInput` (Controller-Spec, Grenzfälle 30 x 1024 erlaubt, 31 und 1025 abgewiesen); je Datei höchstens 10 verschiedene Kandidaten (CPU-Schutz bei PBKDF).
|
||
- Der Schlüssel (unverschlüsseltes PKCS#8) steht in der `analyze`-Antwort für die Oberfläche (Task 5 braucht ihn für die Ausgabe); er wird nicht geloggt (Request-Log kennt nur Pfad und Status).
|
||
|
||
### Hinweise für Task 5
|
||
|
||
- API: `cert-keys.ts` enthält `keyItemFromObject`, `spkiDerOf`, `candidatePasswords`, `MAX_PASSWORDS`; `exportKey` ist noch nicht da (laut Plan Task 5), ebenso `writePkcs12` in `cert-pkcs12.ts` (dort `readPkcs12`, `isPkcs12Der`). `KeyItem.pem` ist immer unverschlüsseltes PKCS#8; `createPrivateKey(item.pem)` liefert das KeyObject für Export und PFX. `cert-output.ts` unverändert (Teilung/Fullchain von Task 2).
|
||
- `matchKeys(certs, keys, csrs)` in cert-chain.ts mutiert die Einträge (setzt `keyId`, `certIds`, `csrIds`) und ist wiederholbar; `analyzeWorkingSet(files, passwords)` ruft es nach dem Zusammenfassen auf. `csrPublicKeyOf(pem)` (cert-csr.ts) liefert Kennung und SPKI-DER einer Anfrage.
|
||
- Controller: `parsePasswords(raw)` (exportiert) liest das Feld `passwords`; `analyze(files, rawPasswords?)`.
|
||
- Web: `WorkingEntry.password` wird jetzt befüllt (`setPassword(id, password)` im Hook, `setEntryPassword` in working-set.ts); `toFormData` sendet `passwords` nur, wenn mindestens eins gesetzt ist. `PasswordInput` (Label, Wert, Anzeigen/Verbergen) ist wiederverwendbar, z. B. für das PFX-Passwort im Konvertieren-Reiter. `ItemCard` ist über `item`/`items` generisch; `FilesTab` zeigt gesperrte Dateien mit `UnlockPanel` (ruhiger Hinweis + „Trotzdem entsperren“, wenn schon ein Serverzertifikat mit `keyId` vorliegt und nur eine PFX ohne Passwort gesperrt ist).
|
||
- Messages: neu `files.locked.*`, `files.keyWasEncrypted`, `passwordInput.*`, `analyze.matchingKey/keysTitle/csrsTitle/lockedTitle/lockedNeeded/lockedWrong/lockedHint/wasEncrypted/belongsTo/noCertificateYet/subject/matchedKey/matchedCertificates/matchedCsr/present/absent/explainKey/explainCsr`; `check-cert-messages.cjs` zählt jetzt 135 Schlüssel (Mindestzahl in Task 5 erhöhen). Umlaut-Allowlist: `Passender`, `Passendes`, `Passende` ergänzt.
|
||
- e2e-cert.sh: `BUILT="files fullchain zip inputs"`; `analyze_pw <passwörter-json> <out> <datei[:name]>...` schickt das Feld `passwords` (nur für diesen Aufruf, lokale Variable `ANALYZE_PASSWORDS`). Abschnitt inputs prüft am Ende das API-Log der letzten 10 Minuten auf Passwort und Schlüssel.
|
||
- Rebuild dauerte wieder rund 3 Minuten (im Hintergrund starten, `until grep -qx done <log>`).
|
||
|
||
## Task 5
|
||
|
||
**Commit:** 65a1dca `feat(cert-manager): alle Ausgabeformate, Konvertieren, Bundle und PFX` (18 Dateien, 3 neu, keine Löschungen). Nicht gepusht. Das e2e-Skript unter .planning ist wie vorgegeben nicht eingecheckt.
|
||
|
||
### Gates (gemessen, Stack neu gebaut mit `docker compose up -d --build api web`)
|
||
|
||
| Gate | Ergebnis |
|
||
|------|----------|
|
||
| api vitest `src/cert-manager src/module-registry` | 16 Dateien, 449 Tests grün (cert-output 45, cert-pkcs12 22, cert-keys 39, controller 28) |
|
||
| web vitest `modules/cert-manager src/messages` | 11 Dateien, 121 Tests grün (inkl. umlaut-guard; ConvertTab 7, MergeTab 17) |
|
||
| tsc api / web | ohne Fehler |
|
||
| biome lint beide Modulordner, biome check eigene Dateien (api cert-manager, web components, page, actions, working-set(+test), use-cert-workspace, Seitentest, de/en.json, umlaut-dictionary) | sauber |
|
||
| check-cert-messages.cjs 60 | `messages ok 176` |
|
||
| keine forge-Zertifikat-/CSR-/PKCS#7-Leser im Produktivcode | grün |
|
||
| `e2e-cert.sh all` | `files`, `fullchain`, `zip`, `inputs`, `formats` ok |
|
||
| gesamte `<automated>`-Kette | letzte Zeile `task5 ok` |
|
||
|
||
### Output-Punkt 2 (openssl liest die Ausgaben, live gegen die API)
|
||
|
||
`openssl pkcs12 -info -noout -passin pass:Neu-Pass-2026` (RSA; EC verhält sich gleich und ist im Abschnitt ebenfalls geprüft):
|
||
|
||
```
|
||
kompatibel: MAC: sha1, Iteration 2048
|
||
Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048
|
||
modern: MAC: sha1, Iteration 2048
|
||
Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA1
|
||
```
|
||
|
||
`openssl pkcs7 [-inform DER] -print_certs` der Fullchain mit Wurzel, p7b | p7c, gleiche Reihenfolge Server, Zwischen, Wurzel:
|
||
|
||
```
|
||
rsa: www.example.test,Tessera Test Inter RSA,Tessera Test Root RSA | www.example.test,Tessera Test Inter RSA,Tessera Test Root RSA
|
||
ec: ec.example.test,Tessera Test Inter EC,Tessera Test Root EC | ec.example.test,Tessera Test Inter EC,Tessera Test Root EC
|
||
```
|
||
|
||
Außerdem live (RSA und EC): leaf DER (`openssl x509 -inform DER`), leaf p7b mit genau einem Zertifikat, Fullchain ohne Wurzel p7b mit zwei, Nur Kette p7c nur mit dem Zwischenzertifikat, Bundle (erstes Zertifikat der Server, zwei Zertifikate, `openssl pkey -pubout` gleich `openssl x509 -pubkey`), in beiden PFX-Profilen Schlüssel und Serverzertifikat passen zusammen, PKCS#8 mit Passwort (ENCRYPTED PRIVATE KEY, `openssl pkey -passin` ok), klassisch `BEGIN RSA PRIVATE KEY` / `BEGIN EC PRIVATE KEY`, DER mit Passwort, CSR als DER und PEM (`openssl req -subject`). Fehlercodes live: `passwordRequired`, `keyMismatch`, `keyMissing`, `invalidInput` (csr mit p7b). `docker compose logs api --since 10m` enthält weder `Test-Pass-123` noch `Neu-Pass-2026` noch `PRIVATE KEY`.
|
||
|
||
### Abweichungen
|
||
|
||
1. **TDD-Reihenfolge.** Specs für `exportKey`, `writePkcs12`, `buildOutput`, DTO und die Web-Reiter wurden vor der Umsetzung geschrieben, aber nicht einzeln rot gesehen (gleiche Lage wie Task 4); alle Fälle der `<behavior>` laufen grün.
|
||
2. **`actions.ts` unverändert.** Die Typen und `buildOutput` mit dem vollen `BuildInput` standen seit Task 1 dort; es gab nichts anzupassen, die Datei ist nicht im Commit. Aus demselben Grund fehlt `cert-json-body.spec.ts` im Commit: die Größtanfrage (Task 2) deckt keyPem, csrPem und password schon ab und läuft unverändert grün.
|
||
3. **Neue Eingabegrenze/Fehlerabbildung in `buildOutput`:** unbekannter Inhalt oder Format-Inhalt-Paar ergibt jetzt `invalidInput` (vorher `formatNotPossible`); `formatNotPossible` bleibt für „klassisch“ bei Schlüsseln, die weder RSA noch EC sind. Ein `keyPem`, den Node nicht öffnet (etwa verschlüsselt), ergibt `invalidInput`, ohne Schlüsseltext in der Meldung.
|
||
4. **Schlüssel- und Anfrage-Ausgabe ohne `certPem`:** `key` und `csr` brauchen kein Zertifikat (Konvertieren-Reiter); der Dateiname kommt aus `baseName`, sonst `schluessel` bzw. der Name der Anfrage. Nur „Zertifikat“-Inhalte verlangen `certPem`.
|
||
5. **PFX-Anzeigename als BMPString:** `friendlyName` geht UTF-16BE-kodiert an forge (forge schreibt den Wert sonst roh in ein BMPString-Feld und openssl zeigte Zeichensalat).
|
||
6. **Konvertieren sendet bei Schlüsseln `baseName` des zugehörigen Zertifikats** (aus `certIds`), damit die Datei nicht „schluessel.key“ heißt; bei Zertifikaten kein `baseName` (API nimmt den eigenen).
|
||
7. **Rule 3 - Blocking:** `umlaut-dictionary.ts` um `passwortgeschützte` und `AES` ergänzt (Guard).
|
||
8. **PFX ohne passenden Schlüssel bleibt erlaubt** (nur Zertifikate, mit ruhigem Hinweis in der Oberfläche); die API verlangt den Schlüssel dafür nicht (laut Plan: „without key → certificates only“).
|
||
9. Keine Stubs.
|
||
|
||
### Bedrohungsstatus
|
||
|
||
- T-ikt-08 (Schlüssel/Passwörter): Passwörter und Schlüssel nur im Anfragekörper; nirgends in Antwort (Spec: PFX- und Schlüssel-Antwort enthalten das Passwort nicht), Fehlertext, Dateiname oder Log (e2e prüft das API-Log). Passwortfelder der Oberfläche bleiben im Reiterzustand, Specs prüfen, dass sie nicht im dargestellten Text stehen. Fehlertexte aus node:crypto/forge werden nicht weitergereicht.
|
||
- Anfragegrenzen: DTO-Whitelist (ValidationPipe) entfernt Fremdfelder; neue Felder mit `MaxLength` (`keyPem`, `csrPem` 16 384, `password` 256), `IsIn` für Inhalt, Format und `pfxEncryption`; die Größtanfrage bleibt unter der 512-KiB-Grenze von `build` (Spec aus Task 2 läuft unverändert).
|
||
- Forge-Austausch beim PFX-Schreiben (D-20): ein einziger synchroner Aufruf, die drei Funktionen stehen im `finally` wieder an ihrem Platz; Spec prüft das nach Erfolg und nach einem Fehler mitten in `toPkcs12Asn1` (der Durchreicher war währenddessen wirklich eingesetzt).
|
||
- Zuordnung: PFX und Bundle prüfen den Schlüssel gegen das Serverzertifikat mit `checkPrivateKey` (Falsch: 400 `keyMismatch`), nie nach Namen.
|
||
|
||
### Hinweise für Task 6
|
||
|
||
- API: `buildOutput` in `cert-output.ts` ist die eine Funktion; Hilfsfunktionen darin sind `parseCertificate`, `parseKey`, `matchingKey(head, keyPem)`, `pkcs7Der`, `certListFile`, `joinPem`, `derOf`, `file(filename, data, mime)`. Die Kette (`shown` = Pfad ohne Wurzel, mit Wurzel bei `includeRoot`) wird dort einmal gebaut; Vorlagen können `path`/`shown`/`head` ebenso nutzen (einen eigenen `case` oder eine eigene Funktion `buildTemplate` in `cert-templates.ts`, die `buildOutput` für `template` aufruft). `writePkcs12({ keyObject, certDers, password, profile, friendlyName })` und `exportKey(key, 'pkcs8' | 'traditional' | 'pkcs8-der', password?)` sind fertig; für NPM: `exportKey(key, 'traditional')` liefert PKCS#1 (RSA) bzw. SEC1 (EC) unverschlüsselt. Fehler: `certError('templateNeedsKey', 400, ...)` ist im Typ schon da.
|
||
- DTO `dto/cert-build.dto.ts`: `BUILD_CONTENTS`, `BUILD_FORMATS`, `BUILD_PFX_ENCRYPTIONS`; `template` (IsIn der Vorlagenkennungen) und gegebenenfalls Inhalt `template` fehlen noch. Das `BuildInput`-Feld `template?: string` und `BuildResult.snippet` stehen schon in `cert-types.ts` und `actions.ts`. Die Formattabelle `FORMATS` in `cert-output.ts` legt je Inhalt die erlaubten Formate fest; ein neuer Inhalt `template` braucht dort einen Eintrag (oder die Vorlage läuft vor der Formatprüfung).
|
||
- Web: `PfxOptions` (`password`, `repeat`, `encryption`, Callbacks) und `pfxPasswordReady(password, repeat)` sind für den IIS- und Tomcat-Vorgabewert wiederverwendbar (Kompatibel ist Vorgabe, `PfxEncryption` exportiert). `PasswordInput` bleibt das Passwortfeld. In `page.tsx` ist `TabId` jetzt `files | analyze | split | merge | convert`; `templates` kommt hinter `convert` (D-23).
|
||
- Messages: neu `tabs.convert`, `merge.formatLabel/formats.*/bundle*/pfx*/downloadBundle/downloadPfx`, `pfx.*`, `convert.*`; `check-cert-messages.cjs` zählt jetzt 176 Schlüssel (Mindestzahl in Task 6 höher ansetzen). Umlaut-Allowlist: `passwortgeschützte`, `AES` ergänzt.
|
||
- e2e-cert.sh: `BUILT="files fullchain zip inputs formats"`; neue Hilfen `mkbody <out> '<json mit @fixture>'` (Werte, die mit `@` beginnen, werden durch den Inhalt der Fixture ersetzt), `build_files <ordner> <json>` (200 erwartet, legt alle Antwortdateien im Ordner ab) und `build_fails <status> <code> <json>`. Für Vorlagen mit ZIP-Antwort: das Archiv entsteht im Browser (fflate), die API liefert einzelne Dateien plus `snippet`.
|
||
- Rebuild dauerte wieder rund 3 Minuten (im Hintergrund starten; bei `TMPDIR`-losen Shells den Scratchpad-Pfad für das Log verwenden).
|
||
|
||
## Task 6
|
||
|
||
**Commit:** 47b2621 `feat(cert-manager): Vorlagen für Zielsysteme, Modulversion 1.2.0 und Anleitungen` (19 Dateien, 4 neu, keine Löschungen). Nicht gepusht. Das e2e-Skript unter .planning ist wie vorgegeben nicht eingecheckt.
|
||
|
||
### Gates (gemessen, Stack neu gebaut mit `docker compose up -d --build api web`)
|
||
|
||
| Gate | Ergebnis |
|
||
|------|----------|
|
||
| api vitest `src/cert-manager src/module-registry` | 17 Dateien, 476 Tests grün (cert-templates 25, cert-output 47; module-changelog.spec mit 1.2.0 grün) |
|
||
| web vitest `modules/cert-manager src/messages` | 12 Dateien, 133 Tests grün (TemplatesTab 11, Seitentest mit sechs Reitern, umlaut-guard) |
|
||
| tsc api / web | ohne Fehler |
|
||
| biome lint beide Modulordner, biome check eigene Dateien | sauber |
|
||
| check-cert-messages.cjs 80 | `messages ok 228` |
|
||
| 1.2.0 / 2026-10-09 im Changelog, CHANGELOG-Punkt, Anleitungs-Gates, keine forge-Zertifikat-/CSR-/PKCS#7-Leser | grün |
|
||
| `e2e-cert.sh all` | `files`, `fullchain`, `zip`, `inputs`, `formats`, `templates`, `version` ok |
|
||
| gesamte `<automated>`-Kette | letzte Zeile `task6 ok` |
|
||
|
||
Live bewiesen (Abschnitt templates, RSA und EC): Nginx und Apache `openssl verify` der Fullchain gegen die Wurzel (zwei Zertifikate ohne Wurzel, drei mit `includeRoot`), `privkey.pem` ist PKCS#8 und passt zum Serverzertifikat; Apache älter: `cert.pem` allein, `chain.pem` nur das Zwischenzertifikat, Verify besteht; IIS: PFX ohne `pfxEncryption` in der Anfrage, `openssl pkcs12 -info` zeigt `pbeWithSHA1And3-KeyTripleDES-CBC`, Schlüssel passt; Nginx Proxy Manager: `BEGIN RSA PRIVATE KEY` bzw. `BEGIN EC PRIVATE KEY`, `certificate.pem` und `intermediate.pem` getrennt, Schnipsel leer; HAProxy: erstes Zertifikat ist der Server, Schlüssel hinter den Zertifikaten; Tomcat: `.p12` mit Passwort lesbar, Schnipsel mit `IHR-PASSWORT` und ohne das echte Passwort. Fehler mit Code: `templateNeedsKey`, `keyMismatch`, `passwordRequired` (iis und tomcat); unbekannte Vorlage 400 (durch die DTO-Prüfung, ohne Code). API-Log ohne `Neu-Pass-2026` und `PRIVATE KEY`. Abschnitt version: Changelog erster Eintrag 1.2.0 vom 2026-10-09, danach 1.1.0; Katalog nennt cert-manager mit 1.2.0.
|
||
|
||
### Abweichungen
|
||
|
||
1. **TDD-Reihenfolge.** `cert-templates.spec.ts` wurde zusammen mit der Umsetzung geschrieben; die Fälle der `<behavior>` sind abgedeckt. Zwei Spec-Annahmen wurden im ersten Lauf korrigiert (der Anzeigename steckt verschlüsselt im Container und kommt von forge als roher BMPString zurück).
|
||
2. **Neue Dateien nicht nötig, aber `cert-templates.ts` kennt `cert-output.ts` nicht** (Importschleife): `file`, `joinPem`, `derOf` stehen dort in Kurzform noch einmal. `isTemplateId` ist exportiert, `buildOutput` prüft die Kennung vor dem Schlüssel.
|
||
3. **`BuildContent` hat den neuen Inhalt `template`** (cert-types.ts, DTO `BUILD_CONTENTS`, Web `actions.ts` mit `TemplateId`); `FORMATS.template = ['template']`, ein mitgesendetes `format` ergibt `invalidInput`. `snippet` steht nur in der Antwort von Vorlagen.
|
||
4. **Apache-legacy und Nginx Proxy Manager ohne Aussteller:** `chain.pem` bzw. `intermediate.pem` entfallen (und die Zeile `SSLCertificateChainFile`), statt eine leere Datei zu liefern. Mit Kette sind es wie geplant drei Dateien.
|
||
5. **Nginx Proxy Manager, Schlüssel:** bei Schlüsseltypen ohne klassisches Format (nicht RSA/EC) fällt die Vorlage auf PKCS#8 zurück statt mit `formatNotPossible` zu scheitern.
|
||
6. **Unbekannte Vorlagenkennung über HTTP:** die DTO-Prüfung (`IsIn`) antwortet mit Nests Standard-400 ohne Code; `invalidInput` mit Code gibt es nur direkt aus `buildOutput` (Spec). Die Oberfläche kann es nicht auslösen.
|
||
7. **CHANGELOG:** drei statt zwei Punkte unter „Neu“ (Arbeitsbereich, Zusammenführen/Formate/Konvertieren, Vorlagen) und ein Punkt unter „Behoben“; Modulversion 1.2.0 im ersten Punkt.
|
||
8. **Rule 3 - Blocking:** `umlaut-dictionary.ts` um `ssl`, `neuer`, `klassischen`, `SSLHostConfig`, `PASSWORT` ergänzt (Guard).
|
||
9. **Betriebsanleitung:** zusätzlich zur Zeile in „Fehlerbilder“ (413 beim Hochladen) ein Satz zum Rechenaufwand bei Passwörtern; Abschnitt sagt auch, dass der Zertifikat-Manager von sich aus nichts im Internet abruft (Task 7 ändert diesen Satz auf den Knopf „Fehlendes Zertifikat holen“).
|
||
10. Keine Stubs.
|
||
|
||
### Bedrohungsstatus
|
||
|
||
- T-ikt-08 (Schlüssel/Passwörter): Vorlagen-Passwörter nur im Anfragekörper; Schnipsel enthält `IHR-PASSWORT`, nie das Passwort (Spec + e2e); Passwort steht nicht im dargestellten Text der Oberfläche (Test); API-Log im e2e ohne Passwort und Schlüssel; Fehlertexte ohne Schlüssel/Passwort (Spec).
|
||
- Vorlagen verlangen immer ein passendes Schlüssel-Zertifikat-Paar (`checkPrivateKey`, sonst `keyMismatch`); die Reihenfolge der Kette baut die API selbst.
|
||
- Der Schlüssel liegt in den Dateien der Vorlagen unverschlüsselt (Zielsysteme erwarten das); Oberfläche und Anleitung weisen darauf hin, die Dateien zu schützen (HAProxy-Schritt).
|
||
|
||
### Hinweise für Task 7
|
||
|
||
- API: `cert-templates.ts`/`cert-output.ts` brauchen keine Änderung. Neue Dateien laut Plan: `common/public-url-guard.ts` (+Spec), `cert-aia.ts`, `dto/cert-fetch-issuer.dto.ts`; Controller bekommt `fetch-issuer` (Platzhalterkommentar steht schon im Klassenkopf). `ChainInfo.gap.aiaUrls` ist schon gefüllt, `CertItem.aiaIssuerUrls` ebenso.
|
||
- Web: `page.tsx` hat jetzt `TabId = files | analyze | split | merge | convert | templates`. `ChainView.tsx` zeigt die Lücke („Zwischenzertifikat fehlt“) und ist der Ort für den Knopf; `working-set.ts` braucht `origin: 'fetched'` mit Host (steht im Typ); `useCertWorkspace` hat noch keine Funktion zum Hinzufügen nachgeladener Einträge (neben `addFiles`/`addText` ergänzen). Der Knopf soll in Zusammenführen und Vorlagen (beide nutzen `ChainView`) und Analysieren erscheinen, wo `ChainView` vorkommt.
|
||
- Messages: neu `tabs.templates`, `templates.*` (Karten `nginx`, `apache`, `apacheLegacy`, `iis`, `npm`, `haproxy`, `tomcat` mit `title`, `delivers`, `steps.1..3`); `check-cert-messages.cjs` zählt 228 Schlüssel (Mindestzahl in Task 7 mindestens 228). Umlaut-Allowlist-Ergänzungen siehe oben.
|
||
- e2e-cert.sh: `BUILT="files fullchain zip inputs formats templates version"`; `run_section` hat noch den Platzhalter `aia) e2e_fail`; neuen Abschnitt `section_aia` anlegen und dort einhängen. Der Abschnitt `version` prüft jetzt Changelog-Top-Eintrag 1.2.0: Task 7 ergänzt dort ein Item (Anzahl `>= 5` bleibt gültig).
|
||
- Docs: Anwenderanleitung hat den Abschnitt „Fehlendes Zertifikat holen“ noch nicht (nur der Satz im Zusammenführen-Absatz über „Fügen Sie das fehlende Zertifikat im Reiter ‚Dateien‘ hinzu“ ist anzupassen); Betriebsanleitung „### Zertifikat-Manager“ sagt aktuell „ruft von sich aus nichts im Internet ab“ (um den Knopf und die Ports 80/443 ergänzen); Entwicklungsanleitung: neuer Absatz neben „Zertifikat-Manager, Arbeitsbereich und Ketten (quick-261009-ikt)“, der Satz über `fetch-issuer` dort steht schon.
|
||
- Rebuild dauerte wieder rund 3 Minuten (im Hintergrund starten; Log im Scratchpad).
|
||
|
||
## Task 7
|
||
|
||
**Commit:** a2fc2cb `feat(cert-manager): Fehlendes Zertifikat holen, gehärteter Adressschutz` (32 Dateien, 5 neu, keine Löschungen). Nicht gepusht. Das e2e-Skript unter .planning ist wie vorgegeben nicht eingecheckt.
|
||
|
||
### Gates (gemessen, Stack neu gebaut mit `docker compose up -d --build api web`)
|
||
|
||
| Gate | Ergebnis |
|
||
|------|----------|
|
||
| api vitest `src/cert-manager src/module-registry src/common src/favorites src/nextcloud-status` | 36 Dateien, 889 Tests grün (public-url-guard 51, cert-aia 26, controller 31; favorites und nextcloud-status unverändert grün) |
|
||
| web vitest `modules/cert-manager src/messages` | 13 Dateien, 150 Tests grün (ChainView 9, working-set +5, FilesTab +2, AnalyzeTab +1, umlaut-guard) |
|
||
| tsc api / web | ohne Fehler |
|
||
| biome lint beide Modulordner + `common`, biome check der Kettenliste | sauber (nur eigene Dateien mit `--write` formatiert) |
|
||
| check-cert-messages.cjs 85 | `messages ok 237` |
|
||
| CHANGELOG „IPv6“ unter Unveröffentlicht, Anleitung-Gate, Changelog-Gate, keine forge-Zertifikat-/CSR-/PKCS#7-Leser | grün |
|
||
| `e2e-cert.sh all` | `files`, `fullchain`, `zip`, `inputs`, `formats`, `templates`, `version`, `aia` ok |
|
||
| gesamte `<automated>`-Kette | letzte Zeile `task7 ok` |
|
||
|
||
### Output-Punkt 3: Live-AIA-Ergebnis (kein CERT_E2E_OFFLINE nötig, der api-Container kommt ins Internet)
|
||
|
||
- Blatt: das Zertifikat von letsencrypt.org (EC), Lücke `afterLeaf` mit Adresse `http://ye2.i.lencr.org/`.
|
||
- Geholt über `POST fetch-issuer`: Server **ye2.i.lencr.org**, Name **YE2**, Datei `YE2.crt`; `openssl verify -partial_chain -trusted <geholt> <blatt>` bestanden.
|
||
- Nächste Stufe: Analyse von Blatt plus YE2 ergibt den Weg `letsencrypt.org, YE2` mit Lücke `afterCa`, es fehlt „Root YE“, Adresse `http://ye.i.lencr.org/` (dort erscheint der Knopf erneut).
|
||
- Offline-Fälle live: `aia-private-leaf.pem` ergibt 422 `aiaInternal`, `rsa-leaf-noaki.pem` 422 `aiaMissing`, Text 400 `notACertificate`, eine mitgeschickte `url` ändert nichts, 16 385 Zeichen 400, ohne Anmeldung 401. API-Log der letzten 10 Minuten enthält kein `BEGIN CERTIFICATE`.
|
||
- Der echte Abruf lief durch den eigenen undici-Agent mit `createGuardedLookup` (der Lookup bei Verbindungsaufbau funktioniert also real, mit `all`-Option und ohne).
|
||
|
||
### Abweichungen
|
||
|
||
1. **TDD-Reihenfolge.** Die Guard-Umsetzung stand vor dem Guard-Spec; cert-aia.ts vor cert-aia.spec.ts. Alle Fälle der `<behavior>` sind abgedeckt und liefen grün (ein Spec-Fehler im ersten Lauf war ein Testartefakt: ein `ReadableStream` ruft `pull` einmal vorab auf, deshalb `highWaterMark: 0`).
|
||
2. **`cert-model.ts` angefasst (nicht in der Dateiliste):** `pkcs7Certificates` ist jetzt exportiert (der D-16-Walker, von cert-aia.ts benutzt; kein zweiter Leser).
|
||
3. **Fünf Test-Attrappen angefasst (Rule 3, wie in Task 3/4):** `CertWorkspace` hat jetzt `addFetched`; in `AnalyzeTab.test.tsx`, `MergeTab.test.tsx`, `ConvertTab.test.tsx`, `SplitTab.test.tsx` und `TemplatesTab.test.tsx` kam je eine Zeile `addFetched: () => 'added'` dazu. `TemplatesTab.tsx` reicht `workspace.addFetched` an `ChainView` (nicht in der Dateiliste, laut Task 6 aber ein ChainView-Nutzer, der Knopf soll auch in Vorlagen erscheinen).
|
||
4. **Guard strenger als der Plan:** der ganze Bereich `::/96` ist immer gesperrt (der Plan wollte ihn nach der eingebetteten IPv4 beurteilen); dazu `::ffff:0:0:0/96` (IPv4-übersetzt, nach eingebetteter IPv4) und `3fff::/20` (Dokumentation). `isPrivateIpv6` ist jetzt ebenfalls exportiert. Der Plan nannte nur `isPrivateIpAddress`.
|
||
5. **Port-Prüfung im Spec:** das Fixture `aia-private-leaf.pem` hat keine Adresse mit Port 8080 (CA-Schlüssel sind gelöscht, keine Neuerzeugung). Die Port-Regel (`hasDefaultPort`, gleiche Bedingung wie der Adressschutz) wird deshalb über eine Weiterleitung auf `:8080` im Spec bewiesen; für die erste Adresse nutzt der Code dieselbe Bedingung.
|
||
6. **Mehrere Adressen:** bei Misserfolg gewinnt die Meldung mit dem meisten Fortschritt (`aiaNotIssuer` vor `aiaTooLarge` vor `aiaUnreachable` vor `aiaInternal`); eine Warnzeile je Aufruf mit allen versuchten Servernamen. Der Zeitrahmen von 8 s gilt je Adresse.
|
||
7. **Antwort `host`** ist der Server der tatsächlich antwortenden Stelle (nach Weiterleitungen), nicht der erste.
|
||
8. **Web-Zusatzmeldungen:** `chain.fetchButton/fetching/fetchHint/fetchNoAddress/fetchAlready/fetchListFull/fetchListTooLarge/fetchFailed`, `files.fetchedFrom`. `WorkingEntry` hat das optionale Feld `pem` (nur bei nachgeladenen Einträgen), damit dasselbe Zertifikat nicht doppelt angehängt wird. Bei einer Lücke ohne Adresse zeigt `ChainView` nur den Hinweis zum Herunterladen (hinter einem Zwischenzertifikat bleibt der ruhige Hinweis ohne Zusatz).
|
||
9. **CHANGELOG:** der Sicherheitspunkt steht unter „Behoben“ (nicht „Neu“); das Zusammenführen-Bullet nennt den Knopf. Modul-Changelog 1.2.0 hat jetzt sechs Punkte.
|
||
10. Keine Stubs.
|
||
|
||
### Bedrohungsstatus
|
||
|
||
- SSRF: Adresse nur aus dem Zertifikat (DTO-Whitelist, Spec und e2e mit mitgeschickter `url`); Standardport, kein Login in der Adresse, ≤ 2048 Zeichen, höchstens 3 Adressen; Adressschutz vor der ersten Anfrage und vor jedem Sprung (`fetchImpl` wird bei internen Adressen nie gerufen, Spec); DNS-Rebinding: `createGuardedLookup` im undici-Agent (Spec mit nachgebildeter Auflösung, live bewiesen); 8 s, 256 KiB (vorab und beim Lesen), 3 Weiterleitungen.
|
||
- Fremdes Zertifikat: nur Aussteller mit `checkIssued` und Signaturprüfung; Spec mit EC-Zertifikat, gleichnamigem Zertifikat mit anderem Schlüssel (`rsa-inter-decoy`) und Nicht-Zertifikat-Antwort.
|
||
- Log: genau eine Warnzeile mit Servername und Code, nie PEM oder Pfad (Spec, e2e).
|
||
- Gemeinsame Nutzer des Guards (Favoriten-Symbole, Nextcloud-Logo): Specs unverändert grün; Wirkung nur strenger.
|
||
- Akzeptierter Rest (im Kopfkommentar von cert-aia.ts): jeder angemeldete Benutzer des Moduls kann einen einzigen GET an eine öffentliche Adresse aus seinem hochgeladenen Zertifikat auslösen.
|
||
|
||
### Hinweise für Task 8
|
||
|
||
- Der Knopf erscheint in Analysieren, Zusammenführen und Vorlagen (alle drei nutzen `ChainView` mit `onFetched={workspace.addFetched}`). Für den Browsernachweis: `letsencrypt.org`-Blatt aus dem e2e (`openssl s_client -connect letsencrypt.org:443 -servername letsencrypt.org`) im Reiter „Dateien“ hochladen (wird hinter `$E2E_TMP` nicht aufbewahrt, neu holen), dann Reiter „Zusammenführen“: Meldung „Zwischenzertifikat fehlt“ mit Knopf und Hinweis auf `ye2.i.lencr.org`; Klick holt YE2, Eintrag „YE2.crt“ mit „nachgeladen von ye2.i.lencr.org“, darunter erscheint der Knopf erneut (Lücke hinter YE2, „Root YE“). Screenshots dunkel zuerst.
|
||
- Gate-Mindestzahl `check-cert-messages.cjs` zählt jetzt 237 Schlüssel; `e2e-cert.sh`: `BUILT="files fullchain zip inputs formats templates version aia"`, Abschnitt `aia` ist der letzte in `all`; `CERT_E2E_OFFLINE=1` überspringt nur den Live-Teil.
|
||
- Nachweisliste für Output-Punkt 4 (Task 8): alte Routen `parse`, `split`, `merge`, `convert`, `export` liefern 404 (das Setup des e2e prüft `parse` bei jedem Lauf), keine forge-Zertifikatleser im Produktivcode (Gate grün in jedem Task).
|
||
- Checkliste für die echte Umgebung (Output-Punkt 6) um diesen Punkt ergänzen: Aus alpha muss der `api`-Container ausgehend per HTTP/HTTPS (Ports 80 und 443) ins Internet kommen, sonst meldet der Knopf „nicht erreichbar“ (Betriebsanleitung beschreibt Prüfbefehl und Fehlerbild).
|
||
- Rebuild dauerte wieder rund 3 Minuten (im Hintergrund; Log im Scratchpad).
|
||
|
||
## Task 8
|
||
|
||
**Commit:** keiner. Der Browser-Nachweis ergab keinen Befund, der eine Codeänderung nötig machte; die Anwenderanleitung stimmt mit den Beschriftungen der Seite überein. Die einzige Dateiänderung ist das Verschieben des Todos nach `.planning/todos/completed/` (laut Vorgabe nicht eingecheckt, nur umbenannt, nicht gestaged). Nichts gepusht, nichts ausgerollt.
|
||
|
||
### Gates (gemessen, Stack neu gebaut mit `docker compose up -d --build api web`, Seed-Zeile „Cert-Manager module seeded in registry“ im api-Log)
|
||
|
||
| Gate | Ergebnis |
|
||
|------|----------|
|
||
| api vitest (gesamte Suite) | 165 Dateien, 3497 Tests grün |
|
||
| web vitest (gesamte Suite) | 158 Dateien, 1915 Tests grün (siehe Hinweis zu domains-page unten) |
|
||
| tsc api / web | ohne Fehler |
|
||
| biome lint (api cert-manager + common, web Modulordner) und biome check der Dateiliste der Verify-Kette | sauber |
|
||
| check-cert-messages.cjs 85 | `messages ok 237` |
|
||
| Todo unter completed, nicht mehr unter pending | ja |
|
||
| `e2e-cert.sh all` | `files`, `fullchain`, `zip`, `inputs`, `formats`, `templates`, `version`, `aia` ok (inkl. Live-Abruf bei ye2.i.lencr.org) |
|
||
| 8 dunkle und 4 helle Bilder | 9 dunkle, 4 helle vorhanden |
|
||
| gesamte `<automated>`-Kette | letzte Zeile `task8 ok` |
|
||
|
||
Hinweis: Im ersten Lauf der Kette schlug einmal `domains-page.test.tsx` fehl (Zeile 312, `waitFor` mit `mockSaveSettings`, Zeitüberschreitung unter Last, während parallel Browser und Docker liefen). Die Datei gehört zu einem anderen Modul, wurde von diesem Auftrag nicht angefasst, lief danach dreimal einzeln grün (27 Tests) und im zweiten Gesamtlauf grün. Als zeitabhängig (flaky) eingestuft, nicht behoben (außerhalb des Auftrags).
|
||
|
||
### Output-Punkt 4: alte Routen und forge
|
||
|
||
- `POST parse`, `split`, `merge`, `convert`, `export` unter `/modules/cert-manager/` liefern 404; das e2e (Abschnitt files) prüft das bei jedem Lauf und war grün. Der Controller kennt nur `analyze`, `build` und `fetch-issuer`.
|
||
- Im Produktivcode von `apps/api/src` gibt es keine forge-Leser für Zertifikat, CSR oder PKCS#7 (grep nach `certificateFromPem/Asn1`, `certificationRequestFromPem/Asn1`, `pkcs7.messageFromPem/Asn1`: keine Treffer). forge bleibt nur für ASN.1, PKCS#12 und den Schreibweg.
|
||
|
||
### Output-Punkt 5: Browser-Nachweis
|
||
|
||
Werkzeug: playwright-core mit dem lokalen Chromium (Playwright MCP stand nicht zur Verfügung), App unter http://localhost:3000, Anmeldung admin, Skript im Scratchpad (`proof.cjs`), nichts per fetch aus der Seite gemessen. Dunkelmodus zuerst über den Theme-Knopf, danach Hellmodus. Eingabedateien aus den Fixtures: `server.crt` (rsa-leaf), `intermediate.crt` (rsa-inter), `zertifikat-paket.zip` (EC-Server als ServerCertificate.crt, EC-Zwischen, EC-Wurzel, `server.key`, ein `__MACOSX`-Eintrag), `rsa-compat.pfx`, `letsencrypt-leaf.pem` (live von letsencrypt.org geholt).
|
||
|
||
Bilder unter `/home/vicolab/projects/tessera-ctl/.playwright-mcp/cert-manager/` (Ordner ist gitignoriert):
|
||
|
||
| Datei | Zeigt |
|
||
|-------|-------|
|
||
| ikt-dark-empty.png | leerer Arbeitsbereich, Hinweis zum Browserfenster |
|
||
| ikt-dark-files.png | zwei getrennte Auswahlen (server.crt, dann intermediate.crt) beide in der Liste, dazu ZIP (je Pfad erkannt, `__MACOSX` fehlt) und entsperrte PFX (Zertifikate plus Schlüssel „war verschlüsselt“) |
|
||
| ikt-dark-analyze.png | Ketten (EC und RSA), Karten je Zertifikat mit „Passender Schlüssel vorhanden“, Schlüsselkarten |
|
||
| ikt-dark-merge.png | Zusammenführen für das EC-Serverzertifikat, Root-Haken aus, PFX-Optionen ausgefüllt (Kompatibel vorgewählt) |
|
||
| ikt-dark-convert.png | Schlüssel, Zielformat Traditionell, Passwortschutz |
|
||
| ikt-dark-templates.png | alle sieben Vorlagen, Windows-/IIS-Karte mit Passwort, Schnipsel sichtbar |
|
||
| ikt-dark-gap.png | nur das Letsencrypt-Blatt: „Zwischenzertifikat fehlt: „YE2““ mit Knopf „Fehlendes Zertifikat holen“ und Hinweis auf ye2.i.lencr.org |
|
||
| ikt-dark-fetched.png | nach dem Klick: Kette Server, YE2; darüber ruhiger Hinweis auf „Root YE“ mit erneutem Knopf (ye.i.lencr.org) |
|
||
| ikt-dark-mobile.png | Reiter Dateien bei 390 x 844, Eintrag „YE2.crt“ mit „nachgeladen von ye2.i.lencr.org“ |
|
||
| ikt-light-files.png, ikt-light-merge.png, ikt-light-templates.png, ikt-light-gap.png | dieselben Ansichten im Hellmodus |
|
||
| step-after-reload.png, step-files-after-fetch.png, step-mobile-merge.png | Hilfsbilder (leer nach Neuladen, Liste nach dem Nachladen, Zusammenführen mobil) |
|
||
|
||
Gemessene Ergebnisse des Ablaufs:
|
||
|
||
- Zwei getrennte Auswahlen bleiben beide in der Liste (der gemeldete Fehler ist behoben); ZIP und PFX kommen dazu, die PFX lässt sich mit dem Passwort entsperren (Schlüssel „RSA, 2048 Bit, war verschlüsselt“, Zertifikate erscheinen).
|
||
- Root-Haken ist standardmäßig aus. Die im Browser heruntergeladene Fullchain `ec.example.test-fullchain.pem` enthält mit `openssl` geprüft genau zwei Zertifikate (Server, Zwischen), ohne Wurzel.
|
||
- Die im Browser erzeugte PFX (Kompatibel) wurde mit `openssl pkcs12 -info` gelesen (MAC sha1, Zertifikat-Beutel); die Windows-/IIS-Vorlage liefert `pbeWithSHA1And3-KeyTripleDES-CBC` (Shrouded Keybag); die Nginx-Vorlage liefert ein ZIP aus `fullchain.pem`, `privkey.pem` und `ANLEITUNG.txt`; der Schlüssel in „Traditionell mit Passwort“ ist `BEGIN EC PRIVATE KEY` mit `Proc-Type: 4,ENCRYPTED` (AES-256-CBC) und lässt sich mit `openssl ec -passin` öffnen.
|
||
- „Fehlendes Zertifikat holen“ holte live YE2 (Server ye2.i.lencr.org); die Dateiliste zeigt „YE2.crt, nachgeladen von ye2.i.lencr.org, Zwischenzertifikat YE2“; danach erscheint der Knopf eine Ebene höher (Root YE).
|
||
- Nach dem Neuladen der Seite ist die Liste leer (kein Listenabschnitt) und der Hinweis „Die Dateien bleiben nur in diesem Browserfenster …“ sichtbar.
|
||
|
||
Design-Review gegen D-23 (jedes Bild einzeln angesehen):
|
||
|
||
- Ruhige, dichte Liste, lesbare Rollenplaketten und Warnkasten in beiden Modi (gelb auf dunklem Braun bzw. dunkles Braun auf hellem Gelb), Passwortfelder und Auswahlfelder sind in beiden Modi als Felder erkennbar, kein Großschrift-Etikett, keine Pfeilzeichen oder Mittelpunkte in der Oberfläche, formale Anrede.
|
||
- Fokus ist sichtbar (gelber Rand am Passwortfeld in ikt-dark-convert.png, hervorgehobener Hauptknopf in ikt-dark-merge.png).
|
||
- Befund: keiner, der eine Änderung verlangt. Beobachtungen ohne Handlungsbedarf: Auf 390 Pixel Breite läuft die Reiterleiste seitlich weiter („Zusammenführen“ angeschnitten; gemeinsames Reiterbauteil, wischbar); die Zeilen mit PowerShell-Zeile in den Vorlagen laufen seitlich (Schnipsel scrollt in seinem Kasten); die Seitenleiste zeigt „Cert Manager“ als Modulname (Katalog, nicht Teil dieses Auftrags).
|
||
- Zwischenstand der Bilder: Die ersten Aufnahmen zeigten bei langen Seiten die mitscrollende Kopfleiste mitten im Bild; das war ein Artefakt der Ganzseitenaufnahme (Scrollposition), nicht der Seite. Das Skript scrollt jetzt vor jeder Aufnahme nach oben, alle abgegebenen Bilder sind danach neu entstanden.
|
||
- Anwenderanleitung: alle in „…“ und fett genannten Beschriftungen der Zertifikat-Manager-Anleitung kommen im Wortlaut auf der Seite vor (Dateien, Analysieren, Aufteilen, Zusammenführen, Konvertieren, Vorlagen, „Root-Zertifikat mitnehmen“, „Kompatibel (auch ältere Windows-Server)“, „Modern (AES-256)“, „Fehlendes Zertifikat holen“, „Zwischenzertifikat fehlt“, „nachgeladen von …“). Keine Korrektur nötig.
|
||
|
||
### Abweichungen
|
||
|
||
1. Keine Codeänderung und damit kein Task-8-Commit (der Plan sah ihn nur für die Todo-Verschiebung und Review-Befunde vor; die Anweisung dieses Laufs verbietet Einchecken unter `.planning/`). Das Todo wurde mit `mv` nach `.planning/todos/completed/` verschoben, nicht mit `git mv` und nicht gestaged.
|
||
2. Playwright MCP war nicht verfügbar; Ersatz: playwright-core mit dem lokalen Chromium (so vorgegeben).
|
||
3. Ein einmaliger zeitabhängiger Fehlschlag von `domains-page.test.tsx` (siehe oben), bei Wiederholung grün.
|
||
|
||
## Gesamtübersicht
|
||
|
||
| Task | Commit | Inhalt |
|
||
|------|--------|--------|
|
||
| 1 | 75ea83f | Reiter „Dateien“, gemeinsamer Arbeitsbereich, ein Parser für RSA und EC (Durchstich) |
|
||
| 2 | 30118a2 | Zusammenführen, Kettenbildung, Fullchain und Nur Kette, eigene Körpergrenze für `build` |
|
||
| 3 | 1554ae8 | Hersteller-ZIP, PKCS#7, eingefügter Text, Analysieren, Aufteilen |
|
||
| 4 | fcac0a3 | Schlüssel, PFX, CSR, Passwort je Datei |
|
||
| 5 | 65a1dca | alle Ausgabeformate, Konvertieren, Bundle, PFX (kompatibel und modern) |
|
||
| 6 | 47b2621 | Vorlagen, Modulversion 1.2.0, Changelogs, Anleitungen |
|
||
| 7 | a2fc2cb | „Fehlendes Zertifikat holen“, gehärteter gemeinsamer Adressschutz |
|
||
| 8 | keiner | Endgates, Browser-Nachweis (13 Bilder), Todo abgeschlossen |
|
||
|
||
Alle acht Commits liegen lokal auf `main`, nichts gepusht, nichts ausgerollt. Der Stack läuft lokal mit dem Stand von Task 7.
|
||
|
||
## Checkliste für die echte Umgebung (macht der Nutzer nach dem eigenen Pull auf alpha)
|
||
|
||
1. Neubau mit `--build` bzw. `--force-recreate` (ein schlichtes `up` baut nicht neu); API-Log prüfen: „Cert-Manager module seeded in registry“, Modulversion 1.2.0 im Marktplatz.
|
||
2. Echtes Hersteller-ZIP im Reiter „Dateien“ hochladen, Zusammenführen, Fullchain herunterladen und auf dem Zielsystem prüfen (ohne Wurzel, die meisten Server brauchen sie nicht).
|
||
3. Nginx Proxy Manager: Vorlage „Nginx Proxy Manager“ benutzen und in „SSL Certificates“, „Add SSL Certificate“, „Custom“ die Dateien `certificate.pem`, `intermediate.pem` und `privkey.pem` in die Felder legen. Zu bestätigen (Annahme aus der Recherche A2): NPM akzeptiert die Dateien, und der Kopf `BEGIN RSA PRIVATE KEY` bzw. `BEGIN EC PRIVATE KEY` wird als Schlüssel angenommen. Dazu der Nginx-Proxy-Manager-Upload: große ZIPs/Dateien blockt NPM bei Uploads; die Dateien hier sind klein (wenige KB), die 413-Grenze der Plattform liegt bei 20 MiB gesamt und muss in NPM nicht angehoben werden, solange nur kleine Dateien hochgeladen werden. Bei 413 beim Hochladen einer größeren ZIP: Upload-Grenze des NPM-Hosts (`client_max_body_size`) prüfen.
|
||
4. Windows-Server: die PFX „Kompatibel (auch ältere Windows-Server)“ (3DES/SHA-1, Vorgabe in der Vorlage „Windows / IIS“) importieren und im IIS-Manager an eine Bindung hängen; zusätzlich, wenn gewünscht, die PFX „Modern (AES-256)“ auf einem aktuellen Windows testen, um zu sehen, welche Systeme sie annehmen (Recherche A1). Beide Varianten sind mit OpenSSL lesbar; ob ein bestimmter Windows-Server sie importiert, ließ sich hier nicht prüfen.
|
||
5. „Fehlendes Zertifikat holen“: aus dem `api`-Container auf alpha muss ausgehend per HTTP und HTTPS (Ports 80 und 443) ins Internet möglich sein, sonst meldet der Knopf „nicht erreichbar“ (Prüfbefehl und Fehlerbild stehen in der Betriebsanleitung). Test: Letsencrypt-Blatt hochladen, Reiter „Zusammenführen“, Knopf klicken, es muss YE2 nachgeladen werden. Interne CAs mit privaten Adressen werden absichtlich nicht angefragt (Meldung „aiaInternal“); dort das Zwischenzertifikat von Hand hinzufügen.
|
||
6. Der gehärtete gemeinsame Adressschutz (Favoriten-Symbole, Nextcloud-Logo) ist strenger geworden: kurz prüfen, dass Favoriten-Symbole und das Nextcloud-Logo auf alpha weiterhin laden.
|
||
7. Basic-Auth vor alpha bleibt wie sie ist; Updater-Verhalten ist von diesem Auftrag nicht berührt.
|
||
|
||
## Review fixes
|
||
|
||
Alle Befunde aus `261009-ikt-REVIEW.md` (CR-01 bis CR-03, WR-01 bis WR-07, IN-01 bis IN-05) sind behoben; Einzelheiten je Befund stehen im Abschnitt „Fix status“ der Review-Datei. Zwei lokale Commits, nichts gepusht, nichts ausgerollt:
|
||
|
||
- **bfcf6d4** `fix(cert-manager): ZIP-Bombe, PEM-Scanner und Umlaut-Passwörter in PFX behoben` (40 Dateien: API, 10 neue Fixtures, keine Löschungen)
|
||
- **c5bffe9** `fix(cert-manager): Reiter zeigen nur noch passende Auswertungen, Aufteilen schützt Schlüssel` (23 Dateien: Web, Meldungen, Anleitungen, CHANGELOG, keine Löschungen)
|
||
|
||
Der Trailer beider Commits ist `Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>` (laut Attributionsvorgabe der Umgebung; der Auftrag nannte „Opus 5.5“).
|
||
|
||
### Abweichungen und Entscheidungen
|
||
|
||
1. **CR-03 ohne node:crypto-Schlüsselbeutel.** Statt den PKCS#8-Beutel selbst zu bauen und zu öffnen, ersetzt `withForgeKdf` forges PBKDF2 für die Dauer des synchronen Aufrufs durch eine Ableitung aus UTF-8-Bytes mit `node:crypto` (gleiche Technik wie beim PFX-Schreiben, im `finally` zurückgesetzt, Spec prüft das). Damit stimmen Lesen und Schreiben mit OpenSSL 3 überein; alle Kombinationen (`pässwörd`, `pw€`, compat und modern) gehen, kein Passwort wird abgelehnt. Nebenwirkung: SHA-256/512-PBKDF2 läuft jetzt nativ statt in reinem JavaScript (7,5 µs je Runde vorher).
|
||
2. **Neue Fehler- und Ignorier-Codes:** `tooManyItems` (413), `aiaNotAllowed` (422), `protectionTooExpensive` (Datei übersprungen). Dazu Meldungen (de/en), `errors.buildFailed` und `workspace.stale`. `check-cert-messages.cjs` zählt jetzt 250 Schlüssel (Mindestzahl künftig mindestens 250).
|
||
3. **WR-02 als `fresh`-Feld.** `CertWorkspace.fresh` ist neu; Tests, die einen `CertWorkspace` nachbauen, brauchen das Feld (fünf Attrappen angepasst; `tsc` prüft Testdateien nicht, sie liefen sonst mit einer unsichtbaren Analyse).
|
||
4. **WR-03:** Beim Aufteilen sind Downloads jetzt asynchron (Schlüssel mit Schutz kommen von `build`); die Tests von `SplitTab` warten mit `waitFor`. Die entsperrte Schlüsselanzeige in der Analyse-Antwort bleibt unverschlüsselt (dafür gebaut, nicht geloggt).
|
||
5. **`main.ts` verschlankt:** die HTTP-Einrichtung liegt in `http-setup.ts` (`configureHttp`), damit die CORS-Reihenfolge testbar ist. Das alte Task-2-Gate „`certBuildJsonBody` in main.ts“ trifft damit nicht mehr zu (jetzt `http-setup.ts`).
|
||
6. **Fixtures:** `make-fixtures.sh` um Umlaut-/Euro-PFX, Zu-teuer-Dateien und die drei Wurzeln ohne basicConstraints ergänzt; die neuen Dateien wurden mit denselben Befehlen gegen die vorhandenen Test-Zertifikate erzeugt (die Skriptdatei erzeugt bei einem Neulauf alles neu, wie gehabt). Die Specs rufen OpenSSL weiter nie auf.
|
||
7. **Modulversion bleibt 1.2.0:** Punkte der bestehenden Einträge erweitert (Umlaut-Passwörter, geschützte Schlüssel beim Aufteilen), kein neuer Eintrag. CHANGELOG.md: Zusätze in den bestehenden Zertifikat-Manager-Punkten und im Sicherheitspunkt unter „Behoben“.
|
||
8. **e2e-cert.sh:** neuer Abschnitt `review` (CR-01, CR-02, CR-03 in beide Richtungen mit openssl, WR-01, WR-04 bis WR-07), steht in `BUILT`; Datei liegt wie die anderen unter `.planning` und ist nicht eingecheckt.
|
||
|
||
### Gates (gemessen, Stack neu gebaut mit `docker compose up -d --build api web`)
|
||
|
||
| Gate | Ergebnis |
|
||
|------|----------|
|
||
| api vitest (gesamt) | 169 Dateien, 3573 Tests grün (cert-manager neu u. a. pem-scan 10, cert-budget-Spezifisches in cert-pkcs12/cert-keys/cert-analyze, cert-upload 6, http-setup 3, cert-aia-url 13); enthält favorites, nextcloud-status, common |
|
||
| web vitest (gesamt) | 160 Dateien, 1945 Tests grün (neu: use-cert-workspace 6, stale-analysis 15, SplitTab +9) |
|
||
| tsc api / web | ohne Fehler |
|
||
| biome check auf allen geänderten und neuen Dateien (63 Pfade der Commits, davon 47 Quelldateien) | ohne Befund |
|
||
| check-cert-messages.cjs 237 | `messages ok 250` |
|
||
| `e2e-cert.sh all` | files, fullchain, zip, inputs, formats, templates, version, aia (live, ye2.i.lencr.org), review: alle ok |
|
||
| Browser (dunkel) | Datei entfernen: Zusammenführen und Aufteilen zeigen nur „Dateien werden geprüft …“ ohne Downloads, danach die neue Liste; fehlgeschlagene Prüfung: Meldung mit „Erneut versuchen“, keine Downloads; geschützter Schlüssel: Schutz vorgewählt, Datei `ENCRYPTED PRIVATE KEY`, mit neuem Passwort lesbar. Bilder in `.playwright-mcp/cert-manager/ikt-fix-*.png` (merge-before, merge-analysing, split-analysing, split-after-remove, merge-after-remove, split-error, split-key-protected, split-key-plain-warning) |
|