9.8 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 | 05 | api+frontend |
|
|
|
|
|
|
|
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 viaforge.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 viaforge.pki.certificateFromPem, asserts CN matches - PEM→P7B: calls
convertCert({ pemText, targetFormat: 'p7b' }), decodes base64 PKCS7 PEM, parses viaforge.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
FileResponseinterface:{ filename, content (base64), mimeType } - Added
FORMAT_MIMEmap:{ 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
targetFormatagainst 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
- Validates
cert-manager.controller.ts:
- Added
@Body('pemText') pemText?: stringtoconvertCertendpoint - Changed guard from "reject if no file" to "reject if no file AND no pemText"
- Passes
pemTextthrough to service
Result: all 23 API tests pass (19 prior + 4 new convertCert).
Task 3: ConvertTab UI + convertCertAction + render tests
actions.ts:
- Added
FileResponseinterface (mirrors API response) - Added
convertCertAction(input, targetFormat): Promise<FileResponse>— builds FormData with file/pemText/password + targetFormat, delegates topostForm('convert', form)
components/ConvertTab.tsx:
'use client'component withuseStatefor 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;
downloadBase64called on success (mocked viavi.spyOn(actions, 'downloadBase64')); text-destructive error on format rejection - Import
ConvertTabfrom 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
NotImplementedExceptionfrom import when cleaning up, butmergeCertsstub still uses it - Fix: Re-added
NotImplementedExceptionto 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 |