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

8.6 KiB

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 04 api+frontend
cert-manager
splitCerts
node-forge
pkcs7
split-tab
tdd
vitest
requires provides affects
09-01
09-02
09-03
cert-manager-split-endpoint
split-response-interface
split-tab-ui
apps/api/src/cert-manager/cert-manager.service.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/SplitTab.tsx
apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx
added patterns
tdd-red-green
node-forge-pkcs7-split
parsePemChain-reuse
downloadBase64-pattern
vi.spyOn-mock
created modified
apps/api/src/cert-manager/cert-manager.service.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/SplitTab.tsx
apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx
splitCerts PEM path reuses parsePemChain helper (no duplication); P7B path sniffs first bytes for PEM vs DER
format === 'crt' comparison removed — detectFormat only returns 'pem'|'der'|'pfx'|'p7b' (Rule 1 auto-fix, TS2367)
SplitTab error classification: 'format'/'invalid'/'unknown' in message -> unknownFormat, else -> generic (mirrors InspectTab)
splitCertsAction accepts File directly (not FormData) — caller is always browser File object; postForm wraps it
duration completed tasks_completed files_created files_modified
~4 minutes 2026-07-02T05:19:00Z 3 0 5
CERT-02
complete

Phase 09 Plan 04: Split Vertical Slice Summary

One-liner: splitCerts splits fullchain PEM chains and P7B PKCS7 bundles into individually downloadable base64 PEM certs; POST /split wired; SplitTab renders per-cert download list with subject CN and expiry.

Objective

Second functional vertical slice: certificate chain/bundle splitting. Delivers CERT-02 (upload fullchain.pem or P7B, download each cert separately). Reuses parsePemChain + downloadBase64 patterns from Plan 03.

Tasks Completed

# Name Type Commit Status
1 RED — failing splitCerts spec test eec6631 done
2 GREEN — implement splitCerts + wire POST /split feat 2c4ada3 done
3 Split tab UI + splitCertsAction + render test feat 33b1bc3 done

What Was Built

Task 1: RED — failing splitCerts spec

Added 3 new splitCerts tests to cert-manager.service.spec.ts:

  • Fullchain PEM test: builds two RSA-1024 self-signed certs, concatenates PEMs, calls splitCerts, asserts count=2, each cert content is base64 of single PEM block, both CNs present, ISO notAfter format
  • P7B bundle test: builds a P7B PEM via forge.pkcs7.createSignedData() + forge.pem.encode({ type:'PKCS7', body:... }), calls splitCerts on a .p7b file, asserts at least 1 cert returned
  • Malformed input test: garbage bytes with .pem extension → expects BadRequestException

All 3 tests FAIL against the NotImplementedException stub (RED confirmed). 16 prior tests remain green.

Task 2: GREEN — splitCerts implementation

cert-manager.service.ts:

  • Exported SplitEntry interface: { index, filename, content (base64 PEM), subject.cn, validity.notAfter }
  • Exported SplitResponse interface: { count, certs: SplitEntry[] }
  • Implemented splitCerts({ file }): Promise<SplitResponse>:
    • PEM path: parsePemChain(buffer.toString('utf-8')) → array of forge certs
    • P7B path: sniff first 27 bytes for -----BEGIN → messageFromPem (PEM-wrapped) or asn1.fromDer + messageFromAsn1 (binary DER)
    • Unsupported format (pfx/der) → explicit BadRequestException
    • Each cert: certificateToPem(cert) → base64 → SplitEntry with index, filename cert-N.pem, subject.cn, notAfter
    • Outer try/catch → BadRequestException on malformed input (T-09-01)
  • POST /split controller route was already fully wired from Plan 01 (FileInterceptor, 5MB limit, T-09-03, T-09-04)

Result: all 19 API tests pass (16 prior + 3 new splitCerts).

Task 3: SplitTab UI + splitCertsAction + render tests

actions.ts:

  • Added SplitEntry + SplitResponse interfaces (mirrors API response)
  • Added splitCertsAction(file: File): Promise<SplitResponse> — builds FormData, delegates to postForm('split', form)

components/SplitTab.tsx:

  • 'use client' component with useState for loading/result/error
  • Aufteilen button: disabled when no file or loading; label swaps to t('actions.processing')
  • Empty state when no result and no error
  • Per-cert <ul> on success: each <li> shows subject.cn + validity.notAfter + Herunterladen button (bg-secondary) calling downloadBase64(filename, content, 'application/x-pem-file')
  • Error in text-sm text-destructive: 'format'/'invalid'/'unknown' → unknownFormat, else → generic

cert-manager.test.tsx:

  • 2 new SplitTab tests: success (mocked splitCertsAction resolves → 2 download buttons, 2 CN values shown) and error (rejects → text-destructive unknownFormat message)
  • Import SplitTab from components; vi.spyOn(actions, 'splitCertsAction') pattern matching InspectTab

Result: all 11 cert-manager web tests pass (9 prior + 2 new SplitTab).

Verification Results

Check Status Notes
pnpm --filter @tessera/api exec vitest run PASS 19/19 tests
pnpm --filter @tessera/web exec vitest run cert-manager PASS 11/11 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
grep -q "messageFromPem" cert-manager.service.ts PASS P7B path present
grep -q "splitCertsAction" actions.ts PASS Action wired
SplitTab renders Herunterladen per cert PASS Verified by test
BadRequestException on malformed input PASS Verified by test

Note on web type-check: Pre-existing 154 toBeInTheDocument type augmentation errors from Plan 03 still present — not a regression. All production source files added in Plan 04 are type-clean.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] TypeScript TS2367: comparison of 'pfx'|'p7b' with 'crt' has no overlap

  • Found during: Task 2 — pnpm --filter @tessera/api exec tsc --noEmit reported TS2367
  • Issue: Condition format === 'pem' || format === 'der' || format === 'crt' — detectFormat return type is 'pem'|'der'|'pfx'|'p7b'. After narrowing out 'pem' and 'der', TypeScript flags 'pfx'|'p7b' === 'crt' as unintentional.
  • Fix: Changed condition to format === 'pem' only (DER is a single-cert binary not a chain; falls through to the else BadRequestException path which is correct behavior).
  • Files modified: apps/api/src/cert-manager/cert-manager.service.ts
  • Commit: 2c4ada3

Known Stubs

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

Threat Model Compliance

Threat ID Status
T-09-01 (malformed bundle → unhandled throw) Mitigated — outer try/catch on all forge operations → BadRequestException
T-09-03 (oversized upload → OOM) Mitigated — FileInterceptor fileSize 5MB (wired in Plan 01)
T-09-04 (unauthenticated cert processing) Mitigated — credentials:'include' in postForm; ModuleGuard server-side (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.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/SplitTab.tsx FOUND + MODIFIED
apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx FOUND + MODIFIED
Commit eec6631 (RED splitCerts spec) FOUND
Commit 2c4ada3 (GREEN splitCerts) FOUND
Commit 33b1bc3 (SplitTab UI + tests) FOUND
19/19 API cert-manager tests passing VERIFIED
11/11 web cert-manager tests passing VERIFIED
messageFromPem in service (P7B path) VERIFIED
splitCertsAction in actions.ts VERIFIED
BadRequestException on malformed VERIFIED