Files
tessera-ctl/.planning/phases/09-cert-manager-module/09-03-PLAN.md
T
schalli 063666af3b
Tessera CI/CD / Lint & Type Check (push) Failing after 41s
Tessera CI/CD / Tests (push) Has been skipped
Tessera CI/CD / Build & Publish Images (push) Has been skipped
docs(09): create cert-manager phase plan (6 plans)
2026-07-01 16:24:31 +02:00

14 KiB
Raw Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
09-cert-manager-module 03 execute 2
09-01
09-02
apps/api/src/cert-manager/cert-manager.service.ts
apps/api/src/cert-manager/cert-manager.controller.ts
apps/api/src/cert-manager/cert-manager.service.spec.ts
apps/web/src/app/(portal)/modules/cert-manager/actions.ts
apps/web/src/app/(portal)/modules/cert-manager/components/InspectTab.tsx
apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx
true
CERT-01
CERT-05
truths artifacts key_links
A user uploads (or pastes) a PEM/DER/PFX/P7B certificate and sees subject, issuer, validity, SANs, key type/size, serial, signature algorithm, and SHA-1 + SHA-256 fingerprints
A password-protected PFX is parsed when the correct password is supplied; a wrong password returns HTTP 400 (not 500)
The Inspect tab renders a key-value result grid on success and a localized error on failure
CertManagerService.parseCert implemented (PEM/DER/PFX/P7B -> CertDetails)
POST /modules/cert-manager/parse wired to parseCert
InspectTab.tsx renders the CertDetails grid + inspect action
InspectTab -> inspectCertAction -> POST /modules/cert-manager/parse -> CertManagerService.parseCert
parseCert wraps node-forge in try/catch -> BadRequestException (wrong password / malformed)
First functional vertical slice: certificate inspection. Implement CertManagerService.parseCert to accept an uploaded file (PEM/DER/PFX/P7B) or pasted PEM text plus an optional PFX password, and return structured CertDetails. Wire the POST /parse endpoint and build the Inspect tab to render the result grid.

MVP: after this plan a user can activate the module, upload a cert, and read its parsed details — a complete end-to-end capability (CERT-01). Password-protected PFX open (CERT-05 read half) is covered because parsing a .pfx requires the supplied password.

Purpose: Delivers CERT-01 and the read half of CERT-05; establishes the parse-and-render pattern reused by later slices. Output: Working Inspect tab end-to-end + tested parseCert service.

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/09-cert-manager-module/09-CONTEXT.md @.planning/phases/09-cert-manager-module/09-RESEARCH.md @.planning/phases/09-cert-manager-module/09-PATTERNS.md @.planning/phases/09-cert-manager-module/09-UI-SPEC.md @.planning/phases/09-cert-manager-module/09-01-SUMMARY.md @.planning/phases/09-cert-manager-module/09-02-SUMMARY.md ## Artifacts this plan produces
  • Implemented method: CertManagerService.parseCert({ file?, pemText?, password? }): CertDetails
  • New TS interface: CertDetails (subject, issuer, validity{notBefore,notAfter,isExpired,daysLeft}, san[], keyType, keyBits, serialNumber, signatureAlgorithm, fingerprint{sha1,sha256}, pemPreview) — per RESEARCH Inspect Response Shape
  • Wired route: POST /modules/cert-manager/parse (FileInterceptor('file') + @Body pemText/password)
  • New action: inspectCertAction(input) in actions.ts (JSON path for pemText, multipart path for file)
  • Implemented component: InspectTab (key-value result grid + inspect button + loading/error)
Task 1: RED — failing parseCert spec apps/api/src/cert-manager/cert-manager.service.spec.ts - apps/api/src/cert-manager/cert-manager.service.spec.ts (existing helper/seed tests + beforeAll self-signed cert generator from Plan 01) - apps/api/src/cert-manager/cert-manager.service.ts (current parseCert stub throwing NotImplementedException + helpers) - .planning/phases/09-cert-manager-module/09-RESEARCH.md (Pattern 5 node-forge parse APIs; Inspect Response Shape; Pitfall 1 binary encoding) - Test: parseCert({ pemText: }) returns CertDetails with subject.cn matching the generated CN, fingerprint.sha256 matching /^[0-9A-F]{2}(:[0-9A-F]{2})+$/, keyType 'RSA', keyBits 2048, and validity.isExpired false. - Test: parseCert({ file: { originalname:'c.der', buffer: } }) returns the same subject.cn (DER path uses 'binary' encoding). - Test: parseCert({ file: { originalname:'c.pfx', buffer: }, password: 'secret' }) returns CertDetails for the enclosed cert. - Test: parseCert({ file: { originalname:'c.pfx', buffer: }, password: 'wrong' }) throws BadRequestException (asserted via rejects.toThrow / expect(() => ...).toThrow with the Nest exception). - Test: parseCert({ pemText: 'not a cert' }) throws BadRequestException. Extend cert-manager.service.spec.ts with the Behavior tests above. Build the DER and password-protected PFX fixtures in the spec from the beforeAll self-signed cert using node-forge (forge.asn1.toDer + forge.pkcs12.toPkcs12Asn1 with password 'secret'). Run the suite and confirm these new tests FAIL against the current parseCert stub (RED). Do not implement parseCert in this task. pnpm --filter @tessera/api test cert-manager --run 2>&1 | grep -Eiq 'fail|NotImplemented|✗|×' && echo RED_CONFIRMED - New parseCert tests exist in cert-manager.service.spec.ts covering PEM, DER, PFX-correct-password, PFX-wrong-password (BadRequestException), and malformed input - Running the suite shows the parseCert tests failing (RED) while the Plan 01 helper/seed tests still pass Failing parseCert spec committed (RED) covering all input formats + wrong-password + malformed cases. Task 2: GREEN — implement parseCert + wire POST /parse apps/api/src/cert-manager/cert-manager.service.ts, apps/api/src/cert-manager/cert-manager.controller.ts - apps/api/src/cert-manager/cert-manager.service.ts (helpers detectFormat/toForgeBuffer/getFingerprint/parsePemChain from Plan 01) - apps/api/src/cert-manager/cert-manager.controller.ts (parse route stub + FileInterceptor from Plan 01) - apps/api/src/cert-manager/cert-manager.service.spec.ts (the RED tests from Task 1 — target contract) - .planning/phases/09-cert-manager-module/09-RESEARCH.md (Pattern 5 full node-forge API: certificateFromPem, fromDer, pkcs12FromAsn1, subject/issuer getField, SAN extraction, RSA bitLength; Inspect Response Shape) Implement CertManagerService.parseCert to: resolve input (pemText -> parse as PEM/chain; file -> detectFormat, then PEM via certificateFromPem, DER via asn1.fromDer(toForgeBuffer(...)) + certificateFromAsn1, PFX via pkcs12FromAsn1(asn1, password ?? '') then extract certBag, P7B via messageFromPem or messageFromAsn1 depending on content sniff). Build CertDetails: subject/issuer CN/O/OU/C via cert.subject.getField / cert.issuer.getField; validity.notBefore/notAfter from cert.validity, isExpired and daysLeft computed against now; san[] from the subjectAltName extension; keyType 'RSA'/'EC' and keyBits from the public key bitLength; serialNumber; signatureAlgorithm from the cert; fingerprint.sha1 and .sha256 via getFingerprint; pemPreview via certificateToPem. Wrap ALL node-forge calls in try/catch and throw BadRequestException with a generic message on failure (covers malformed cert AND wrong PFX password -> 400, threat T-09-01/T-09-02). Never log the password. Define and export the CertDetails interface (co-located in the service or a types file). In cert-manager.controller.ts, ensure POST parse uses FileInterceptor('file', { limits: { fileSize: 5*1024*1024 } }), reads @Body('pemText') and @Body('password'), rejects when neither file nor pemText present (BadRequestException), and delegates to parseCert. Run the suite until all parseCert tests pass (GREEN). pnpm --filter @tessera/api test cert-manager --run - `pnpm --filter @tessera/api test cert-manager --run` exits 0 with all parseCert tests passing - `grep -q "BadRequestException" apps/api/src/cert-manager/cert-manager.service.ts` in the parseCert catch path - No `console.log`/logger call in the service references the password value (grep shows no `password` argument passed to logger) - `grep -q "fileSize: 5" apps/api/src/cert-manager/cert-manager.controller.ts` - `pnpm --filter @tessera/api type-check` exits 0 parseCert returns full CertDetails for PEM/DER/PFX/P7B, throws 400 on wrong password/malformed input, and POST /parse is wired; API tests green. Task 3: Inspect tab UI + action + render test apps/web/src/app/(portal)/modules/cert-manager/actions.ts, apps/web/src/app/(portal)/modules/cert-manager/components/InspectTab.tsx, apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx - apps/web/src/app/(portal)/modules/cert-manager/components/InspectTab.tsx (empty-state stub from Plan 02) - apps/web/src/app/(portal)/modules/cert-manager/actions.ts (API_URL, postForm, downloadBase64 from Plan 02) - apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx (shell tests + i18n wiring from Plan 02) - apps/web/src/app/(portal)/modules/domaincheck/page.tsx (loading/error state pattern) - .planning/phases/09-cert-manager-module/09-UI-SPEC.md (Analysieren result = key-value grid grid-cols-2 gap-2 text-sm; loading label swap; error text-destructive) Add inspectCertAction to actions.ts: if pemText is present, POST JSON { pemText, password } to /modules/cert-manager/parse with Content-Type application/json; else build FormData with file + optional password and use the postForm('parse', form) helper. Return the parsed CertDetails JSON; throw on !ok (reuse postForm error behavior for the multipart path). Implement InspectTab: accept { file, pemText, password }. Render a primary 'Analysieren' button (t('actions.inspect'), disabled + label t('actions.processing') while loading, per UI-SPEC). On click call inspectCertAction and store the result; on error store a localized message (wrong-password -> t('error.wrongPassword'), unknown format -> t('error.unknownFormat'), else t('error.generic')). Render the empty state (t('emptyState.inspect')) when no result; render a grid grid-cols-2 gap-2 text-sm of subject/issuer/validity/SANs/keyType/keyBits/serial/signatureAlgorithm/fingerprint.sha1/fingerprint.sha256 on success; render error in text-sm text-destructive. All labels via t(). No shadcn. Extend cert-manager.test.tsx with a test that mocks inspectCertAction to resolve a CertDetails object and asserts the InspectTab renders the subject CN and the sha256 fingerprint after clicking Analysieren; and a test that mocks a rejection and asserts a text-destructive error is shown. pnpm --filter @tessera/web test cert-manager --run - `grep -q "inspectCertAction" apps/web/src/app/(portal)/modules/cert-manager/actions.ts` - InspectTab shows the empty state before a result and a key-value grid (grid-cols-2) after a successful inspect - `pnpm --filter @tessera/web test cert-manager --run` exits 0 including the new success + error InspectTab tests - `pnpm --filter @tessera/web type-check` exits 0 Inspect tab loads a cert end-to-end, renders the details grid on success and a localized destructive error on failure; web tests green.

<threat_model>

Trust Boundaries

Boundary Description
client -> API /parse Untrusted cert bytes + optional PFX password enter node-forge parsing

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-09-01 Tampering CertManagerService.parseCert (node-forge) medium mitigate Every node-forge call wrapped in try/catch; malformed cert -> BadRequestException (400), never an unhandled 500
T-09-02 Information Disclosure parseCert password handling high mitigate Wrong PFX password caught -> generic 400 message; password value never logged and never echoed in the response
T-09-03 Denial of Service POST /parse upload high mitigate FileInterceptor limits.fileSize = 5 MB caps in-memory buffer
T-09-04 Elevation of Privilege POST /parse high mitigate Global JwtAuthGuard + @UseModule('cert-manager') on the controller
</threat_model>
- `pnpm --filter @tessera/api test cert-manager --run` — parseCert suite green (all formats + wrong password 400) - `pnpm --filter @tessera/web test cert-manager --run` — InspectTab success + error tests green - `pnpm --filter @tessera/api type-check` and `pnpm --filter @tessera/web type-check` clean - Manual (phase gate): upload a real cert, verify grid; upload a password PFX with wrong then right password

<success_criteria>

  • parseCert returns full CertDetails for PEM/DER/PFX/P7B (CERT-01) and opens password PFX with correct password / 400 on wrong (CERT-05 read)
  • Inspect tab works end-to-end with localized errors
  • All API + web tests green; type-checks clean </success_criteria>
Create `.planning/phases/09-cert-manager-module/09-03-SUMMARY.md` when done