Files
tessera-ctl/.planning/phases/09-cert-manager-module/09-05-SUMMARY.md
T

9.8 KiB
Raw Blame History

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics, requirements, status
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics requirements status
09-cert-manager-module 05 api+frontend
cert-manager
convertCert
node-forge
p7b
convert-tab
tdd
vitest
file-response
requires provides affects
09-01
09-02
09-03
09-04
cert-manager-convert-endpoint
file-response-interface
convert-tab-ui
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
added patterns
tdd-red-green
node-forge-der-hex-base64
file-response-download
vi.spyOn-downloadBase64
created modified
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
DER output: bytesToHex → Buffer.from(hex,'hex') → base64 to avoid utf-8 corruption (Pitfall 1, T-09-06)
P7B output: pkcs7.createSignedData + asn1.toDer + pem.encode (PEM-wrapped PKCS7)
FORMAT_MIME map at module level for pem/der/p7b mimeType lookup
Controller convert: adds @Body('pemText') so text-paste path works; rejects when neither file nor pemText
ConvertTab shows empty state + format selector simultaneously (mirrors InspectTab button-always-visible pattern)
duration completed tasks_completed files_created files_modified
~8 minutes 2026-07-02T07:39:00Z 3 0 6
CERT-04
complete

Phase 09 Plan 05: Convert Vertical Slice Summary

One-liner: convertCert converts PEM/DER/P7B with byte-identical round trips using bytesToHex→hex→base64 for DER safety; POST /convert wired; ConvertTab renders format selector + Konvertieren button + blob download.

Objective

Third functional vertical slice: certificate format conversion. Delivers CERT-04 (PEM/DER/P7B targets). Reuses parse helpers from Plans 03–04 and downloadBase64 pattern from Plan 04.

Tasks Completed

# Name Type Commit Status
1 RED — failing convertCert spec test 37db58b done
2 GREEN — implement convertCert + wire POST /convert feat 5969464 done
3 Convert tab UI + convertCertAction + render test feat f89d656 done

What Was Built

Task 1: RED — failing convertCert spec

Added 4 new convertCert tests to cert-manager.service.spec.ts:

  • PEM→DER round-trip: calls convertCert({ pemText, targetFormat: 'der' }), decodes base64 DER, re-parses via forge.asn1.fromDer + forge.pki.certificateFromAsn1, asserts CN matches original
  • DER→PEM round-trip: calls convertCert({ file: { originalname: 'c.der', buffer }, targetFormat: 'pem' }), decodes base64 PEM, re-parses via forge.pki.certificateFromPem, asserts CN matches
  • PEM→P7B: calls convertCert({ pemText, targetFormat: 'p7b' }), decodes base64 PKCS7 PEM, parses via forge.pkcs7.messageFromPem, asserts ≥1 certificate enclosed
  • Malformed input: expects BadRequestException for garbage PEM text

All 4 fail against NotImplementedException stub (RED confirmed). 19 prior tests remain green.

Task 2: GREEN — convertCert implementation

cert-manager.service.ts:

  • Exported FileResponse interface: { filename, content (base64), mimeType }
  • Added FORMAT_MIME map: { pem: 'application/x-pem-file', der: 'application/x-x509-ca-cert', p7b: 'application/x-pkcs7-certificates' }
  • Implemented convertCert({ file?, pemText?, targetFormat, password? }): Promise<FileResponse>:
    • Validates targetFormat against FORMAT_MIME (400 if unsupported)
    • Input resolution: PEM text → parsePemChain; DER file → forge.asn1.fromDer(toForgeBuffer(...)); PFX → pkcs12FromAsn1 + getBags; P7B → sniff + messageFromPem/messageFromAsn1
    • PEM output: certificateToPem(cert) → Buffer.from(str, 'utf-8').toString('base64')
    • DER output: forge.util.bytesToHex(toDer(certificateToAsn1(cert)).getBytes()) → Buffer.from(hex, 'hex').toString('base64') — avoids Pitfall 1 / T-09-06
    • P7B output: pkcs7.createSignedData() + addCertificate(cert) + asn1.toDer(toAsn1()) + pem.encode({ type:'PKCS7' }) → base64
    • All forge ops in try/catch → BadRequestException

cert-manager.controller.ts:

  • Added @Body('pemText') pemText?: string to convertCert endpoint
  • Changed guard from "reject if no file" to "reject if no file AND no pemText"
  • Passes pemText through to service

Result: all 23 API tests pass (19 prior + 4 new convertCert).

Task 3: ConvertTab UI + convertCertAction + render tests

actions.ts:

  • Added FileResponse interface (mirrors API response)
  • Added convertCertAction(input, targetFormat): Promise<FileResponse> — builds FormData with file/pemText/password + targetFormat, delegates to postForm('convert', form)

components/ConvertTab.tsx:

  • 'use client' component with useState for loading/error/targetFormat
  • Native <select> offering pem/der/p7b options (labels PEM/DER/P7B)
  • Konvertieren button: disabled when no file or pemText or loading; label swaps to t('actions.processing')
  • On success: calls downloadBase64(response.filename, response.content, response.mimeType) directly
  • Error classification: 'password'/'passwort' → wrongPassword; 'format'/'invalid'/'unknown' → unknownFormat; else → generic
  • Empty state shown when no error (empty state + format selector always visible together)

cert-manager.test.tsx:

  • 3 new ConvertTab tests: format selector renders pem/der/p7b options; downloadBase64 called on success (mocked via vi.spyOn(actions, 'downloadBase64')); text-destructive error on format rejection
  • Import ConvertTab from components

Result: all 14 cert-manager web tests pass (11 prior + 3 new ConvertTab).

Verification Results

Check Status Notes
pnpm --filter @tessera/api exec vitest run cert-manager PASS 23/23 tests
pnpm --filter @tessera/web exec vitest run cert-manager PASS 14/14 tests
pnpm --filter @tessera/api exec tsc --noEmit PASS 0 errors
pnpm --filter @tessera/web exec tsc --noEmit (prod files) PASS 0 errors in production files
DER path uses bytesToHex → Buffer.from(hex,'hex') → base64 PASS T-09-06 compliant
grep -q "convertCertAction" actions.ts PASS Action wired
ConvertTab renders pem/der/p7b options PASS Verified by test
BadRequestException on malformed input PASS Verified by test

Note on web type-check: Pre-existing toBeInTheDocument type augmentation errors (same as Plans 03–04) still present — not a regression. All production source files added in Plan 05 are type-clean.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] NotImplementedException import removed prematurely

  • Found during: Task 2 — tsc reported TS2304 (Cannot find name 'NotImplementedException')
  • Issue: Removed NotImplementedException from import when cleaning up, but mergeCerts stub still uses it
  • Fix: Re-added NotImplementedException to the import
  • Files modified: apps/api/src/cert-manager/cert-manager.service.ts
  • Commit: 5969464

2. [Rule 1 - Bug] Test error message contained 'password' causing wrong error classification

  • Found during: Task 3 — ConvertTab error test used 'invalid format or wrong password' which matched the 'password' branch before the 'invalid' branch
  • Issue: Error classification checks 'password' first; message containing both keywords showed wrongPassword instead of unknownFormat
  • Fix: Changed test error message to 'unknown format or invalid certificate' (no 'password' substring)
  • Files modified: apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx
  • Commit: f89d656

Known Stubs

File Stub Reason
cert-manager.service.ts mergeCerts throws NotImplementedException Implemented in Plan 06 (Merge/PFX slice)

Threat Model Compliance

Threat ID Status
T-09-01 (malformed input → unhandled throw) Mitigated — try/catch on all forge operations → BadRequestException
T-09-06 (binary encoding on DER output) Mitigated — bytesToHex → Buffer.from(hex,'hex') → base64; never utf-8 round-trip
T-09-03 (oversized upload → OOM) Mitigated — FileInterceptor fileSize 5MB (wired in Plan 01)
T-09-04 (unauthenticated cert processing) Mitigated — global JwtAuthGuard + @UseModule guard (wired in Plan 01)

Self-Check: PASSED

Check Result
apps/api/src/cert-manager/cert-manager.service.ts FOUND + MODIFIED
apps/api/src/cert-manager/cert-manager.controller.ts FOUND + MODIFIED
apps/api/src/cert-manager/cert-manager.service.spec.ts FOUND + MODIFIED
apps/web/src/app/(portal)/modules/cert-manager/actions.ts FOUND + MODIFIED
apps/web/src/app/(portal)/modules/cert-manager/components/ConvertTab.tsx FOUND + MODIFIED
apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx FOUND + MODIFIED
Commit 37db58b (RED convertCert spec) FOUND
Commit 5969464 (GREEN convertCert) FOUND
Commit f89d656 (ConvertTab UI + tests) FOUND
23/23 API cert-manager tests passing VERIFIED
14/14 web cert-manager tests passing VERIFIED
DER output uses bytesToHex→hex→base64 (not utf-8) VERIFIED
convertCertAction in actions.ts VERIFIED
BadRequestException on malformed input VERIFIED