feat(cert-manager): Schlüssel, PFX und CSR erkennen, Passwort je Datei
- Private Schlüssel (PKCS#1, PKCS#8, SEC1; PEM und DER; unverschlüsselt, verschlüsselt, klassisch verschlüsselt) für RSA und EC über node:crypto - PKCS#12 lesen (OpenSSL 3, kompatibel, RC2; EC-Zertifikate und -Schlüssel), auch ohne Endung und im ZIP - Zertifikatsanfragen (CSR) als PEM und DER mit Inhaber, SAN und Schlüssel - Zuordnung von Schlüssel und Anfrage zum Zertifikat (checkPrivateKey, SPKI-Vergleich) - Feld passwords je Datei, gesperrte Dateien fragen nach dem Passwort; kein Passwort in Antwort oder Log - Oberfläche: Passwortfeld mit Anzeigen/Verbergen, Schlüssel- und Anfragekarten im Reiter Analysieren Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,35 @@ export const CERT_MAX_FILES = 30;
|
||||
export const CERT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
||||
export const CERT_MAX_TOTAL_BYTES = 20 * 1024 * 1024;
|
||||
|
||||
/** Das Feld `passwords`: ein JSON-Array von Zeichenketten, je Datei eines (Task 4). */
|
||||
export const CERT_MAX_PASSWORDS = 30;
|
||||
export const CERT_MAX_PASSWORD_CHARS = 1024;
|
||||
|
||||
/**
|
||||
* Liest das multipart-Feld `passwords`. Fehlt es, gibt es keine Passwoerter. Alles andere als ein
|
||||
* JSON-Array aus hoechstens 30 Zeichenketten zu je hoechstens 1024 Zeichen: 400 invalidInput.
|
||||
* Der Inhalt erscheint nie in einer Fehlermeldung.
|
||||
*/
|
||||
export function parsePasswords(raw: unknown): string[] {
|
||||
if (raw === undefined || raw === null || raw === '') return [];
|
||||
let value: unknown = raw;
|
||||
if (typeof raw === 'string') {
|
||||
try {
|
||||
value = JSON.parse(raw);
|
||||
} catch {
|
||||
certError('invalidInput', 400, 'passwords must be a JSON array of strings');
|
||||
}
|
||||
}
|
||||
if (
|
||||
!Array.isArray(value) ||
|
||||
value.length > CERT_MAX_PASSWORDS ||
|
||||
value.some((p) => typeof p !== 'string' || p.length > CERT_MAX_PASSWORD_CHARS)
|
||||
) {
|
||||
certError('invalidInput', 400, 'passwords must be a JSON array of strings');
|
||||
}
|
||||
return value as string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* multer liest Dateinamen als Latin-1. Waren es UTF-8-Bytes (Umlaute), den Namen zurueckgewinnen;
|
||||
* ist das Ergebnis kein gueltiges UTF-8, bleibt der Name wie er ist.
|
||||
@@ -29,7 +58,7 @@ export function repairFileName(name: string): string {
|
||||
* Geschuetzt durch den globalen JwtAuthGuard, den TenantGuard und @UseModule('cert-manager').
|
||||
*
|
||||
* Routen (alle POST, 200):
|
||||
* - analyze Task 1 mehrere Dateien (multipart) erkennen und zusammenfassen
|
||||
* - analyze Task 1 mehrere Dateien (multipart) erkennen und zusammenfassen; Task 4: optionales Feld `passwords`
|
||||
* - build Task 2 Ausgabe bauen (JSON, eigene Grenze 512 KiB, siehe cert-json-body.ts), ab Task 5/6 erweitert
|
||||
* - fetch-issuer Task 7 fehlendes Zwischenzertifikat nur auf Knopfdruck holen
|
||||
*/
|
||||
@@ -41,7 +70,10 @@ export class CertManagerController {
|
||||
@UseInterceptors(
|
||||
FilesInterceptor('files', CERT_MAX_FILES, { limits: { fileSize: CERT_MAX_FILE_BYTES } }),
|
||||
)
|
||||
analyze(@UploadedFiles() files: UploadedFileLike[] | undefined): AnalysisResult {
|
||||
analyze(
|
||||
@UploadedFiles() files: UploadedFileLike[] | undefined,
|
||||
@Body('passwords') rawPasswords?: unknown,
|
||||
): AnalysisResult {
|
||||
if (!files || files.length === 0) {
|
||||
certError('invalidInput', 400, 'No files provided');
|
||||
}
|
||||
@@ -49,8 +81,10 @@ export class CertManagerController {
|
||||
if (total > CERT_MAX_TOTAL_BYTES) {
|
||||
certError('tooLarge', 413, 'Files together exceed 20 MiB');
|
||||
}
|
||||
const passwords = parsePasswords(rawPasswords);
|
||||
return analyzeWorkingSet(
|
||||
files.map((f) => ({ originalname: repairFileName(f.originalname), buffer: f.buffer })),
|
||||
passwords,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user