From eec66311a7c1a43537f9c1a532c4bbc19e9fa5cd Mon Sep 17 00:00:00 2001 From: Schalli Date: Thu, 2 Jul 2026 07:12:37 +0200 Subject: [PATCH] =?UTF-8?q?test(09-04):=20RED=20=E2=80=94=20failing=20spli?= =?UTF-8?q?tCerts=20spec=20(fullchain=20PEM,=20P7B=20bundle,=20malformed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - splitCerts fullchain PEM: expects count 2, two single-PEM-block certs with correct CN - splitCerts P7B PEM bundle: expects at least one cert in result - splitCerts malformed input: expects BadRequestException - All three tests FAIL against NotImplementedException stub (RED confirmed) - All 16 prior tests still pass --- .../cert-manager/cert-manager.service.spec.ts | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/apps/api/src/cert-manager/cert-manager.service.spec.ts b/apps/api/src/cert-manager/cert-manager.service.spec.ts index 5ea98fa..b22a1e6 100644 --- a/apps/api/src/cert-manager/cert-manager.service.spec.ts +++ b/apps/api/src/cert-manager/cert-manager.service.spec.ts @@ -220,3 +220,105 @@ describe('parseCert', () => { ).rejects.toThrow(BadRequestException); }); }); + +// --------------------------------------------------------------------------- +// splitCerts — RED tests (CERT-02) +// --------------------------------------------------------------------------- + +describe('splitCerts', () => { + let service: CertManagerService; + let cert1Pem: string; + let cert2Pem: string; + let fullchainBuffer: Buffer; + let p7bBuffer: Buffer; + const CN1 = 'split1.example.com'; + const CN2 = 'split2.example.com'; + + beforeAll(() => { + service = new CertManagerService(); + + // Build cert 1 (RSA-1024 for speed) + const keys1 = forge.pki.rsa.generateKeyPair(1024); + const c1 = forge.pki.createCertificate(); + c1.publicKey = keys1.publicKey; + c1.serialNumber = '01'; + c1.validity.notBefore = new Date(); + c1.validity.notAfter = new Date(); + c1.validity.notAfter.setFullYear(c1.validity.notBefore.getFullYear() + 1); + const attrs1 = [{ name: 'commonName', value: CN1 }]; + c1.setSubject(attrs1); + c1.setIssuer(attrs1); + c1.sign(keys1.privateKey, forge.md.sha256.create()); + cert1Pem = forge.pki.certificateToPem(c1); + + // Build cert 2 + const keys2 = forge.pki.rsa.generateKeyPair(1024); + const c2 = forge.pki.createCertificate(); + c2.publicKey = keys2.publicKey; + c2.serialNumber = '02'; + c2.validity.notBefore = new Date(); + c2.validity.notAfter = new Date(); + c2.validity.notAfter.setFullYear(c2.validity.notBefore.getFullYear() + 1); + const attrs2 = [{ name: 'commonName', value: CN2 }]; + c2.setSubject(attrs2); + c2.setIssuer(attrs2); + c2.sign(keys2.privateKey, forge.md.sha256.create()); + cert2Pem = forge.pki.certificateToPem(c2); + + // Fullchain fixture: two PEMs concatenated + const fullchainPem = cert1Pem + '\n' + cert2Pem; + fullchainBuffer = Buffer.from(fullchainPem, 'utf-8'); + + // P7B fixture: PEM-wrapped PKCS7 SignedData bundle with both certs + const p7 = forge.pkcs7.createSignedData(); + p7.addCertificate(c1); + p7.addCertificate(c2); + const p7DerBytes = forge.asn1.toDer(p7.toAsn1()).getBytes(); + const p7PemStr = forge.pem.encode({ type: 'PKCS7', body: p7DerBytes }); + p7bBuffer = Buffer.from(p7PemStr, 'utf-8'); + }, 30000); // 30s — two RSA-1024 keygens + + it('returns count 2 and two certs each with a single PEM block and correct CN for fullchain PEM', async () => { + // RED: splitCerts throws NotImplementedException — FAIL + const result = await (service.splitCerts({ + file: { originalname: 'fullchain.pem', buffer: fullchainBuffer }, + }) as Promise); + expect(result.count).toBe(2); + expect(result.certs).toHaveLength(2); + + // Each cert content decodes to exactly one BEGIN CERTIFICATE block + for (const entry of result.certs) { + const decoded = Buffer.from(entry.content, 'base64').toString('utf-8'); + const matches = decoded.match(/-----BEGIN CERTIFICATE-----/g); + expect(matches).toHaveLength(1); + } + + // CN values are present + const cns = result.certs.map((c: any) => c.subject.cn); + expect(cns).toContain(CN1); + expect(cns).toContain(CN2); + + // validity.notAfter is present and looks like ISO 8601 + for (const entry of result.certs) { + expect(entry.validity.notAfter).toMatch(/^\d{4}-\d{2}-\d{2}T/); + } + }); + + it('returns enclosed certs from a P7B PEM bundle', async () => { + // RED: splitCerts throws NotImplementedException — FAIL + const result = await (service.splitCerts({ + file: { originalname: 'bundle.p7b', buffer: p7bBuffer }, + }) as Promise); + expect(result.count).toBeGreaterThanOrEqual(1); + expect(result.certs.length).toBeGreaterThanOrEqual(1); + }); + + it('throws BadRequestException for malformed input', async () => { + // RED: splitCerts throws NotImplementedException (not BadRequestException) — FAIL + await expect( + service.splitCerts({ + file: { originalname: 'bad.pem', buffer: Buffer.from('this is garbage') }, + }), + ).rejects.toThrow(BadRequestException); + }); +});