Files
tessera-ctl/.planning/phases/09-cert-manager-module/09-05-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

11 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 05 execute 4
09-04
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/ConvertTab.tsx
apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx
true
CERT-04
truths artifacts key_links
A user uploads a certificate in any supported format and downloads it converted to a chosen target format (PEM, DER, P7B)
A PEM -> DER -> PEM round trip yields a byte-identical certificate
The Convert tab offers a target-format selector and a download button for the converted file
CertManagerService.convertCert implemented (any input -> PEM/DER/P7B target)
POST /modules/cert-manager/convert wired to convertCert
ConvertTab.tsx renders format selector + download
ConvertTab -> convertCertAction -> POST /modules/cert-manager/convert -> CertManagerService.convertCert
convertCert returns { filename, content(base64), mimeType } -> downloadBase64
Vertical slice: convert a certificate between formats. Implement CertManagerService.convertCert (parse any supported input, re-serialize to the chosen target), wire POST /convert, and build the Convert tab with a target-format selector and download.

MVP: after this plan a user can upload a cert and download it in a different format — a complete capability (CERT-04). (PFX output as a convert target is delivered together with the PFX-create logic in Plan 06; this plan covers PEM/DER/P7B targets.)

Purpose: Delivers CERT-04 for PEM/DER/P7B round-trips, reusing parse helpers + base64-download. Output: Working Convert tab end-to-end + tested convertCert 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-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-04-SUMMARY.md ## Artifacts this plan produces
  • Implemented method: CertManagerService.convertCert({ file?, pemText?, targetFormat, password? }): FileResponse
  • New TS interface: FileResponse ({ filename, content(base64), mimeType }) per RESEARCH Convert/Merge Response Shape
  • Target-format -> mimeType map (pem: application/x-pem-file, der: application/x-x509-ca-cert, p7b: application/x-pkcs7-certificates)
  • Wired route: POST /modules/cert-manager/convert (FileInterceptor('file') + @Body targetFormat/password)
  • New action: convertCertAction(input, targetFormat) in actions.ts
  • Implemented component: ConvertTab (format selector + convert button + download)
Task 1: RED — failing convertCert spec apps/api/src/cert-manager/cert-manager.service.spec.ts - apps/api/src/cert-manager/cert-manager.service.spec.ts (fixtures + prior tests) - apps/api/src/cert-manager/cert-manager.service.ts (convertCert stub + parse helpers reused from parseCert) - .planning/phases/09-cert-manager-module/09-RESEARCH.md (Pattern 5 certificateToPem / certificateToAsn1 -> toDer; Convert Response Shape; Pitfall 1 binary encoding) - Test: convertCert({ pemText: , targetFormat: 'der' }) returns FileResponse with mimeType application/x-x509-ca-cert and content that base64-decodes to DER bytes which, re-parsed, equal the original cert (round-trip). - Test: convertCert({ file: { originalname:'c.der', buffer: }, targetFormat: 'pem' }) returns a PEM whose parsed cert subject.cn equals the original (DER->PEM round trip identical). - Test: convertCert({ pemText: , targetFormat: 'p7b' }) returns a P7B whose enclosed cert count is 1. - Test: convertCert({ pemText: 'garbage', targetFormat: 'der' }) throws BadRequestException. Add the Behavior tests to cert-manager.service.spec.ts, building DER fixtures from the self-signed cert. Assert round-trip identity by re-parsing the converted output and comparing the DER bytes (or subject + fingerprint). Confirm RED against the convertCert stub. Do not implement convertCert here. pnpm --filter @tessera/api test cert-manager --run 2>&1 | grep -Eiq 'fail|NotImplemented|✗|×' && echo RED_CONFIRMED - convertCert tests exist for PEM->DER, DER->PEM (identity), PEM->P7B, and malformed input - Suite shows convertCert tests failing while all prior tests pass Failing convertCert spec committed (RED) including a round-trip identity assertion. Task 2: GREEN — implement convertCert + wire POST /convert 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 (parse helpers, detectFormat, toForgeBuffer) - apps/api/src/cert-manager/cert-manager.controller.ts (convert route stub) - apps/api/src/cert-manager/cert-manager.service.spec.ts (RED contract) - .planning/phases/09-cert-manager-module/09-RESEARCH.md (Pattern 5 serialization: certificateToPem, certificateToAsn1 -> asn1.toDer -> bytesToHex -> Buffer; pkcs7 create for P7B) Implement CertManagerService.convertCert: parse the input to a forge cert reusing the same input-resolution logic as parseCert (extract a shared private helper if helpful). Serialize to targetFormat: 'pem' via certificateToPem; 'der' via asn1.toDer(certificateToAsn1(cert)).getBytes() -> Buffer.from(bytesToHex, 'hex'); 'p7b' via forge.pkcs7.createSignedData / addCertificate then messageToPem (or asn1 -> DER). Build FileResponse: filename `converted.${targetFormat}`, content = base64 of the output bytes (for PEM/P7B text use Buffer.from(str,'utf-8').toString('base64'); for DER use derBuffer.toString('base64')), mimeType from the target->mime map. Reject an unsupported targetFormat and wrap all forge calls in try/catch -> BadRequestException. Never log password. In the controller, POST convert uses FileInterceptor('file', { limits: { fileSize: 5*1024*1024 } }), reads @Body('targetFormat') and @Body('password'), rejects when neither file nor pemText present. Run suite to GREEN. pnpm --filter @tessera/api test cert-manager --run - `pnpm --filter @tessera/api test cert-manager --run` exits 0 with convertCert tests passing including the round-trip identity test - `grep -q "converted." apps/api/src/cert-manager/cert-manager.service.ts` (filename built) and DER path uses toString('base64') on a Buffer, not 'utf-8' - `pnpm --filter @tessera/api type-check` exits 0 convertCert converts between PEM/DER/P7B with byte-identical round trips and 400 on malformed input; POST /convert wired; API tests green. Task 3: Convert tab UI + action + render test apps/web/src/app/(portal)/modules/cert-manager/actions.ts, apps/web/src/app/(portal)/modules/cert-manager/components/ConvertTab.tsx, apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx - apps/web/src/app/(portal)/modules/cert-manager/components/ConvertTab.tsx (empty-state stub) - apps/web/src/app/(portal)/modules/cert-manager/components/InspectTab.tsx (result/loading/error pattern) - apps/web/src/app/(portal)/modules/cert-manager/actions.ts (postForm, downloadBase64) - apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx (wiring) - .planning/phases/09-cert-manager-module/09-UI-SPEC.md (Konvertieren = format selector + single download button; empty state 'Keine Datei geladen.' + 'waehle ein Ausgabeformat') Add convertCertAction(input, targetFormat) to actions.ts: build FormData with file (or send JSON with pemText) plus targetFormat and optional password; call postForm('convert', form); return FileResponse. Implement ConvertTab: accept { file, pemText, password }. Render a target-format selector (native select) offering pem, der, p7b (labels localized; PFX intentionally not offered here — added in Plan 06). Primary 'Konvertieren' button (t('actions.convert'), loading label swap). On success call downloadBase64(filename, content, mimeType) from the FileResponse. Empty state t('emptyState.convert'); localized error in text-destructive on failure. Extend cert-manager.test.tsx: assert the format selector renders pem/der/p7b options; mock convertCertAction to resolve a FileResponse and assert downloadBase64 is invoked after clicking Konvertieren. pnpm --filter @tessera/web test cert-manager --run - `grep -q "convertCertAction" apps/web/src/app/(portal)/modules/cert-manager/actions.ts` - ConvertTab format selector renders pem, der, p7b options - `pnpm --filter @tessera/web test cert-manager --run` exits 0 including the new ConvertTab tests - `pnpm --filter @tessera/web type-check` exits 0 Convert tab converts and downloads end-to-end for PEM/DER/P7B; web tests green.

<threat_model>

Trust Boundaries

Boundary Description
client -> API /convert Untrusted cert bytes enter node-forge parse + re-serialize

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-09-01 Tampering convertCert (node-forge) medium mitigate try/catch around parse + serialize -> BadRequestException; unsupported targetFormat rejected as 400
T-09-06 Tampering binary encoding on DER output medium mitigate DER built via bytesToHex -> Buffer.from(hex) -> base64; never utf-8 round-trip (Pitfall 1)
T-09-03 Denial of Service POST /convert upload high mitigate FileInterceptor limits.fileSize = 5 MB
T-09-04 Elevation of Privilege POST /convert high mitigate Global JwtAuthGuard + @UseModule('cert-manager')
</threat_model>
- `pnpm --filter @tessera/api test cert-manager --run` — convertCert green incl. round-trip identity - `pnpm --filter @tessera/web test cert-manager --run` — ConvertTab green - type-checks clean - Manual (phase gate): convert a real PEM to DER, re-upload the DER to Inspect, confirm identical cert

<success_criteria>

  • convertCert converts PEM/DER/P7B with byte-identical round trips (CERT-04)
  • Convert tab works end-to-end
  • All tests green; type-checks clean </success_criteria>
Create `.planning/phases/09-cert-manager-module/09-05-SUMMARY.md` when done