refactor(quick-260921-m34): Aufgabe 2c - Hochladewege getypt, 25 Zusicherungen fallen mit
@UploadedFile()/@UploadedFiles() in cert-manager.controller auf UploadedFileLike. Der Dienst nimmt CertFileLike = Pick<UploadedFileLike, 'buffer' | 'originalname'> - genau die zwei Felder, die er liest; mimetype und size bleiben draussen, weil kein Zweig sie anfasst. Belegt statt behauptet: keiner der sechs FileInterceptor/FilesInterceptor- Aufrufe in apps/api/src setzt eine storage-Option, also gilt multers memoryStorage, also ist buffer ein Buffer. @types/multer bleibt uninstalliert (D-04). Damit fallen 25 Zusicherungen der Form file.buffer as Buffer und file.originalname as string ersatzlos weg - sie standen nur da, weil file ein any war. as unknown as bleibt bei 33, noNonNullAssertion bei 56. noExplicitAny in apps/api/src: 66 -> 56 (Ausgang der Aufgabe: 149, Schranke des Plans: 75). type-check 4/4, lint 5/5, apps/api 72/1143, apps/web 73/531. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
|
||||
import { UseModule } from '../module-registry/module.guard';
|
||||
import type { UploadedFileLike } from '../auth/types/auth-user';
|
||||
import { CertManagerService } from './cert-manager.service';
|
||||
|
||||
/**
|
||||
@@ -39,7 +40,7 @@ export class CertManagerController {
|
||||
}),
|
||||
)
|
||||
async parseCert(
|
||||
@UploadedFile() file: any,
|
||||
@UploadedFile() file: UploadedFileLike | undefined,
|
||||
@Body('password') password?: string,
|
||||
@Body('pemText') pemText?: string,
|
||||
) {
|
||||
@@ -60,7 +61,7 @@ export class CertManagerController {
|
||||
}),
|
||||
)
|
||||
async splitCerts(
|
||||
@UploadedFile() file: any,
|
||||
@UploadedFile() file: UploadedFileLike | undefined,
|
||||
@Body('password') password?: string,
|
||||
) {
|
||||
if (!file) {
|
||||
@@ -81,7 +82,7 @@ export class CertManagerController {
|
||||
}),
|
||||
)
|
||||
async mergeCerts(
|
||||
@UploadedFiles() files: any[],
|
||||
@UploadedFiles() files: UploadedFileLike[],
|
||||
@Body('outputFormat') outputFormat: string,
|
||||
@Body('password') password?: string,
|
||||
) {
|
||||
@@ -107,7 +108,7 @@ export class CertManagerController {
|
||||
}),
|
||||
)
|
||||
async convertCert(
|
||||
@UploadedFile() file: any,
|
||||
@UploadedFile() file: UploadedFileLike | undefined,
|
||||
@Body('targetFormat') targetFormat: string,
|
||||
@Body('password') password?: string,
|
||||
@Body('pemText') pemText?: string,
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||
import * as forge from 'node-forge';
|
||||
import type { UploadedFileLike } from '../auth/types/auth-user';
|
||||
|
||||
/**
|
||||
* Eine hochgeladene Zertifikatsdatei, so weit dieser Dienst sie liest:
|
||||
* Inhalt und eingereichter Name (der Name geht ausschliesslich in
|
||||
* `detectFormat` und in Fehlermeldungen). Abgeleitet aus `UploadedFileLike`
|
||||
* statt daneben erfunden (quick-260921-m34); `mimetype` und `size` bleiben
|
||||
* bewusst draussen, weil kein Zweig dieses Dienstes sie liest.
|
||||
*/
|
||||
type CertFileLike = Pick<UploadedFileLike, 'buffer' | 'originalname'>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CertDetails — the structured result returned by parseCert
|
||||
@@ -162,7 +172,7 @@ export class CertManagerService {
|
||||
* - Wrong PFX password → generic 400 message (password value never logged or echoed)
|
||||
*/
|
||||
async parseCert(input: {
|
||||
file?: any;
|
||||
file?: CertFileLike;
|
||||
pemText?: string;
|
||||
password?: string;
|
||||
}): Promise<CertDetails> {
|
||||
@@ -179,11 +189,11 @@ export class CertManagerService {
|
||||
}
|
||||
cert = certs[0];
|
||||
} else if (file) {
|
||||
const format = this.detectFormat(file.originalname as string, file.buffer as Buffer);
|
||||
const format = this.detectFormat(file.originalname, file.buffer);
|
||||
|
||||
if (format === 'pem') {
|
||||
// ── PEM file ───────────────────────────────────────────────────────
|
||||
const pemStr = (file.buffer as Buffer).toString('utf-8');
|
||||
const pemStr = file.buffer.toString('utf-8');
|
||||
const certs = this.parsePemChain(pemStr);
|
||||
if (certs.length === 0) {
|
||||
throw new Error('No certificate block found in PEM file');
|
||||
@@ -192,12 +202,12 @@ export class CertManagerService {
|
||||
} else if (format === 'der') {
|
||||
// ── DER binary file ────────────────────────────────────────────────
|
||||
// CRITICAL: binary encoding, never utf-8 (RESEARCH Pitfall 1)
|
||||
const asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer as Buffer));
|
||||
const asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
cert = forge.pki.certificateFromAsn1(asn1);
|
||||
} else if (format === 'pfx') {
|
||||
// ── PFX/PKCS12 file ───────────────────────────────────────────────
|
||||
// wrong password → forge throws → caught below → BadRequestException (T-09-02)
|
||||
const p12Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer as Buffer));
|
||||
const p12Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, password ?? '');
|
||||
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
|
||||
const bags = certBags[forge.pki.oids.certBag] ?? [];
|
||||
@@ -217,15 +227,15 @@ export class CertManagerService {
|
||||
cert = parsedCert;
|
||||
} else {
|
||||
// ── P7B/PKCS7 file — PEM-wrapped or binary DER (Pitfall 4) ────────
|
||||
const isPemP7b = (file.buffer as Buffer)
|
||||
const isPemP7b = file.buffer
|
||||
.slice(0, 27)
|
||||
.toString('ascii')
|
||||
.includes('-----BEGIN');
|
||||
let p7: any;
|
||||
if (isPemP7b) {
|
||||
p7 = forge.pkcs7.messageFromPem((file.buffer as Buffer).toString('utf-8'));
|
||||
p7 = forge.pkcs7.messageFromPem(file.buffer.toString('utf-8'));
|
||||
} else {
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer as Buffer));
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
p7 = forge.pkcs7.messageFromAsn1(p7Asn1);
|
||||
}
|
||||
const p7Certs: forge.pki.Certificate[] = p7.certificates ?? [];
|
||||
@@ -332,7 +342,7 @@ export class CertManagerService {
|
||||
* - File size limit 5 MB enforced by FileInterceptor in the controller
|
||||
*/
|
||||
async splitCerts(input: {
|
||||
file?: any;
|
||||
file?: CertFileLike;
|
||||
password?: string;
|
||||
}): Promise<SplitResponse> {
|
||||
const { file } = input;
|
||||
@@ -344,28 +354,28 @@ export class CertManagerService {
|
||||
let certs: forge.pki.Certificate[];
|
||||
|
||||
try {
|
||||
const format = this.detectFormat(file.originalname as string, file.buffer as Buffer);
|
||||
const format = this.detectFormat(file.originalname, file.buffer);
|
||||
|
||||
if (format === 'pem') {
|
||||
// ── PEM chain (fullchain.pem, .crt — both map to 'pem' in detectFormat) ─
|
||||
const pemStr = (file.buffer as Buffer).toString('utf-8');
|
||||
const pemStr = file.buffer.toString('utf-8');
|
||||
certs = this.parsePemChain(pemStr);
|
||||
if (certs.length === 0) {
|
||||
throw new Error('No certificate blocks found in PEM file');
|
||||
}
|
||||
} else if (format === 'p7b') {
|
||||
// ── P7B/PKCS7 bundle — PEM-wrapped or binary DER (Pitfall 4) ─────────
|
||||
const isPemP7b = (file.buffer as Buffer)
|
||||
const isPemP7b = file.buffer
|
||||
.slice(0, 27)
|
||||
.toString('ascii')
|
||||
.includes('-----BEGIN');
|
||||
let p7: any;
|
||||
if (isPemP7b) {
|
||||
// PEM-wrapped PKCS7 (e.g. -----BEGIN PKCS7-----)
|
||||
p7 = forge.pkcs7.messageFromPem((file.buffer as Buffer).toString('utf-8'));
|
||||
p7 = forge.pkcs7.messageFromPem(file.buffer.toString('utf-8'));
|
||||
} else {
|
||||
// Binary DER PKCS7
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer as Buffer));
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
p7 = forge.pkcs7.messageFromAsn1(p7Asn1);
|
||||
}
|
||||
certs = (p7.certificates as forge.pki.Certificate[]) ?? [];
|
||||
@@ -434,7 +444,7 @@ export class CertManagerService {
|
||||
* No fallback to lower-level certBag construction was needed.
|
||||
*/
|
||||
async mergeCerts(input: {
|
||||
files?: any[];
|
||||
files?: CertFileLike[];
|
||||
outputFormat: string;
|
||||
password?: string;
|
||||
}): Promise<FileResponse> {
|
||||
@@ -453,26 +463,23 @@ export class CertManagerService {
|
||||
let certs: forge.pki.Certificate[];
|
||||
|
||||
try {
|
||||
certs = (files as any[]).flatMap((file: any) => {
|
||||
const format = this.detectFormat(
|
||||
file.originalname as string,
|
||||
file.buffer as Buffer,
|
||||
);
|
||||
certs = files.flatMap((file) => {
|
||||
const format = this.detectFormat(file.originalname, file.buffer);
|
||||
|
||||
if (format === 'pem') {
|
||||
const pemStr = (file.buffer as Buffer).toString('utf-8');
|
||||
const pemStr = file.buffer.toString('utf-8');
|
||||
const parsed = this.parsePemChain(pemStr);
|
||||
if (parsed.length === 0) {
|
||||
throw new Error(`No certificate block found in ${file.originalname as string}`);
|
||||
throw new Error(`No certificate block found in ${file.originalname}`);
|
||||
}
|
||||
return parsed;
|
||||
} else if (format === 'der') {
|
||||
// CRITICAL: binary encoding, never utf-8 (RESEARCH Pitfall 1)
|
||||
const asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer as Buffer));
|
||||
const asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
return [forge.pki.certificateFromAsn1(asn1)];
|
||||
} else if (format === 'pfx') {
|
||||
// Extract all certs from the PFX bag
|
||||
const p12Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer as Buffer));
|
||||
const p12Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, password ?? '');
|
||||
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
|
||||
const bags = certBags[forge.pki.oids.certBag] ?? [];
|
||||
@@ -488,21 +495,21 @@ export class CertManagerService {
|
||||
const missingCertIndex = bagCerts.findIndex((c) => c === undefined || c === null);
|
||||
if (missingCertIndex !== -1) {
|
||||
throw new BadRequestException(
|
||||
`Certificate bag in "${file.originalname as string}" does not contain a readable X.509 certificate`,
|
||||
`Certificate bag in "${file.originalname}" does not contain a readable X.509 certificate`,
|
||||
);
|
||||
}
|
||||
return bagCerts.filter((c): c is forge.pki.Certificate => c !== undefined && c !== null);
|
||||
} else {
|
||||
// P7B/PKCS7 — PEM-wrapped or binary DER (Pitfall 4)
|
||||
const isPemP7b = (file.buffer as Buffer)
|
||||
const isPemP7b = file.buffer
|
||||
.slice(0, 27)
|
||||
.toString('ascii')
|
||||
.includes('-----BEGIN');
|
||||
let p7: any;
|
||||
if (isPemP7b) {
|
||||
p7 = forge.pkcs7.messageFromPem((file.buffer as Buffer).toString('utf-8'));
|
||||
p7 = forge.pkcs7.messageFromPem(file.buffer.toString('utf-8'));
|
||||
} else {
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer as Buffer));
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
p7 = forge.pkcs7.messageFromAsn1(p7Asn1);
|
||||
}
|
||||
return (p7.certificates as forge.pki.Certificate[]) ?? [];
|
||||
@@ -571,7 +578,7 @@ export class CertManagerService {
|
||||
* - Password is never passed to the logger (T-09-02)
|
||||
*/
|
||||
async convertCert(input: {
|
||||
file?: any;
|
||||
file?: CertFileLike;
|
||||
pemText?: string;
|
||||
targetFormat: string;
|
||||
password?: string;
|
||||
@@ -602,10 +609,10 @@ export class CertManagerService {
|
||||
}
|
||||
cert = certs[0];
|
||||
} else if (file) {
|
||||
const format = this.detectFormat(file.originalname as string, file.buffer as Buffer);
|
||||
const format = this.detectFormat(file.originalname, file.buffer);
|
||||
|
||||
if (format === 'pem') {
|
||||
const pemStr = (file.buffer as Buffer).toString('utf-8');
|
||||
const pemStr = file.buffer.toString('utf-8');
|
||||
const certs = this.parsePemChain(pemStr);
|
||||
if (certs.length === 0) {
|
||||
throw new Error('No certificate block found in PEM file');
|
||||
@@ -613,11 +620,11 @@ export class CertManagerService {
|
||||
cert = certs[0];
|
||||
} else if (format === 'der') {
|
||||
// CRITICAL: binary encoding, never utf-8 (RESEARCH Pitfall 1)
|
||||
const asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer as Buffer));
|
||||
const asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
cert = forge.pki.certificateFromAsn1(asn1);
|
||||
} else if (format === 'pfx') {
|
||||
// PFX/PKCS12 — extract first cert bag (wrong password → BadRequestException)
|
||||
const p12Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer as Buffer));
|
||||
const p12Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, password ?? '');
|
||||
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
|
||||
const bags = certBags[forge.pki.oids.certBag] ?? [];
|
||||
@@ -637,15 +644,15 @@ export class CertManagerService {
|
||||
cert = parsedCert;
|
||||
} else {
|
||||
// P7B — extract first cert
|
||||
const isPemP7b = (file.buffer as Buffer)
|
||||
const isPemP7b = file.buffer
|
||||
.slice(0, 27)
|
||||
.toString('ascii')
|
||||
.includes('-----BEGIN');
|
||||
let p7: any;
|
||||
if (isPemP7b) {
|
||||
p7 = forge.pkcs7.messageFromPem((file.buffer as Buffer).toString('utf-8'));
|
||||
p7 = forge.pkcs7.messageFromPem(file.buffer.toString('utf-8'));
|
||||
} else {
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer as Buffer));
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
p7 = forge.pkcs7.messageFromAsn1(p7Asn1);
|
||||
}
|
||||
const p7Certs: forge.pki.Certificate[] = p7.certificates ?? [];
|
||||
|
||||
Reference in New Issue
Block a user