fix(quick-260921-iwr): drei bag.cert-Zusicherungen in cert-manager.service.ts zu echten Waechtern gemacht

node-forge setzt bag.cert bei einem wohlgeformten, aber nicht als X.509
lesbaren Zertifikats-Bag auf null (lib/pkcs12.js Zeile 703-709). Die drei
Zusicherungen in parseCert, mergeCerts und convertCert behaupteten also
etwas Falsches, obwohl die Folge bereits behandelt war: alle vier Pfade
antworteten schon vorher mit 400, nie mit 500 (mit einer selbst gebauten
83-Byte-PFX nachgemessen).

Ersetzt die drei Zusicherungen durch ausdrueckliche Pruefungen mit
praeziser BadRequestException. In mergeCerts wird kein Zertifikat mehr
verschluckt: alle Bags werden auf Vollstaendigkeit geprueft, bevor die
Liste ueber ein Typpraedikat zurueckgegeben wird.

RED-Tests zuerst geschrieben und mit den heutigen Meldungen ("Failed to
extract certificate details" / "Failed to create merged certificate
output" / "Failed to convert certificate to pem: serialization error")
rot bestaetigt, dann die Waechter ergaenzt: GREEN.

Verhaltensaenderung ausdruecklich beabsichtigt (D-02): nur der Text der
Fehlermeldung fuer diese eine Eingabeklasse aendert sich, der Statuscode
bleibt in allen vier Pfaden 400.

NONNULL 11 -> 8, davon 3 in cert-manager.service.ts (Zeilen 133/516/661
bleiben unveraendert — durch Hash-Laenge bzw. vorgelagerte Passwort-
Pruefung garantiert).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 14:01:36 +02:00
parent 8716fa5234
commit b4aaed4b7c
2 changed files with 284 additions and 3 deletions
@@ -204,7 +204,17 @@ export class CertManagerService {
if (bags.length === 0) {
throw new Error('No certificate bag found in PFX/PKCS12');
}
cert = bags[0].cert!;
// node-forge sets bag.cert to null when the bag's content parses as
// valid DER but is not a readable X.509 certificate (lib/pkcs12.js
// certBag decoder). An explicit guard here — not an assertion — so
// the 400 names the real cause (quick-260921-iwr, D-01/D-04).
const parsedCert = bags[0].cert;
if (!parsedCert) {
throw new BadRequestException(
'Certificate bag in PFX/PKCS12 does not contain a readable X.509 certificate',
);
}
cert = parsedCert;
} else {
// ── P7B/PKCS7 file — PEM-wrapped or binary DER (Pitfall 4) ────────
const isPemP7b = (file.buffer as Buffer)
@@ -466,7 +476,18 @@ export class CertManagerService {
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, password ?? '');
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
const bags = certBags[forge.pki.oids.certBag] ?? [];
return bags.map((bag) => bag.cert!);
// node-forge sets bag.cert to null when a bag's content parses as
// valid DER but is not a readable X.509 certificate (lib/pkcs12.js
// certBag decoder). No entry may be silently dropped (D-04) — check
// every bag and reject the whole file, naming it, before returning.
const bagCerts = bags.map((bag) => bag.cert);
const missingCertIndex = bagCerts.findIndex((c) => c === null);
if (missingCertIndex !== -1) {
throw new BadRequestException(
`Certificate bag in "${file.originalname as string}" does not contain a readable X.509 certificate`,
);
}
return bagCerts.filter((c): c is forge.pki.Certificate => c !== null);
} else {
// P7B/PKCS7 — PEM-wrapped or binary DER (Pitfall 4)
const isPemP7b = (file.buffer as Buffer)
@@ -599,7 +620,17 @@ export class CertManagerService {
if (bags.length === 0) {
throw new Error('No certificate bag found in PFX/PKCS12');
}
cert = bags[0].cert!;
// node-forge sets bag.cert to null when the bag's content parses as
// valid DER but is not a readable X.509 certificate (lib/pkcs12.js
// certBag decoder). An explicit guard here — not an assertion — so
// the 400 names the real cause (quick-260921-iwr, D-01/D-04).
const parsedCert = bags[0].cert;
if (!parsedCert) {
throw new BadRequestException(
'Certificate bag in PFX/PKCS12 does not contain a readable X.509 certificate',
);
}
cert = parsedCert;
} else {
// P7B — extract first cert
const isPemP7b = (file.buffer as Buffer)