Files
tessera-ctl/apps/api/src/cert-manager/cert-csr.spec.ts
T
schalli fcac0a3bfd 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>
2026-10-09 15:31:54 +02:00

85 lines
3.3 KiB
TypeScript

import { X509Certificate } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
import { csrPublicKeyOf } from './cert-csr';
import { keyIdOf } from './cert-keys';
import { detectBlob } from './cert-model';
import type { CsrItem } from './cert-types';
const fx = (name: string) => readFileSync(join(__dirname, '__fixtures__', name));
function csrs(name: string): CsrItem[] {
const r = detectBlob(fx(name), { file: 0, path: name, passwords: [] });
return r.items.filter((i): i is CsrItem => i.kind === 'csr');
}
describe('Zertifikatsanfragen (CSR)', () => {
it('RSA-CSR als PEM: Inhaber, SAN aus der extensionRequest, Schluessel', () => {
const [csr] = csrs('rsa-leaf.csr');
expect(csr).toBeDefined();
expect(csr.kind).toBe('csr');
expect(csr.cn).toBe('www.example.test');
expect(csr.organization).toBe('Tessera Test');
expect(csr.san).toEqual(['www.example.test', 'example.test']);
expect(csr.keyType).toBe('RSA');
expect(csr.keyBits).toBe(2048);
expect(csr.curve).toBeNull();
expect(csr.id).toMatch(/^r-[0-9a-f]{16}$/);
expect(csr.pem.startsWith('-----BEGIN CERTIFICATE REQUEST-----')).toBe(true);
expect(csr.baseName).toBe('www.example.test');
expect(csr.sources).toEqual([{ file: 0, path: 'rsa-leaf.csr' }]);
});
it('EC-CSR als PEM: P-256', () => {
const [csr] = csrs('ec-leaf.csr');
expect(csr.cn).toBe('ec.example.test');
expect(csr.keyType).toBe('EC');
expect(csr.curve).toBe('P-256');
expect(csr.keyBits).toBe(256);
expect(csr.san).toContain('ec.example.test');
});
it('PEM und DER derselben Anfrage teilen die Kennung', () => {
expect(csrs('rsa-leaf.csr')[0].id).toBe(csrs('rsa-leaf.csr.der')[0].id);
expect(csrs('ec-leaf.csr')[0].id).toBe(csrs('ec-leaf.csr.der')[0].id);
expect(csrs('rsa-leaf.csr')[0].id).not.toBe(csrs('ec-leaf.csr')[0].id);
});
it('der Schluessel der Anfrage ist der des Zertifikats (SPKI)', () => {
for (const [csr, cert] of [
['rsa-leaf.csr', 'rsa-leaf.pem'],
['ec-leaf.csr.der', 'ec-leaf.pem'],
]) {
const [item] = csrs(csr);
const { id, spki } = csrPublicKeyOf(item.pem);
const x = new X509Certificate(fx(cert));
expect(id).toBe(keyIdOf(x.publicKey));
expect(spki.equals(x.publicKey.export({ type: 'spki', format: 'der' }))).toBe(true);
}
});
it('keyId und certIds setzt erst die Zuordnung', () => {
const [csr] = csrs('rsa-leaf.csr');
expect(csr.keyId).toBeNull();
expect(csr.certIds).toEqual([]);
});
it('ein Zertifikat oder Schluessel wird nie als CSR gelesen', () => {
for (const name of ['rsa-leaf.cer', 'ec-leaf-key-sec1.der', 'rsa-chain.p7c']) {
expect(csrs(name)).toEqual([]);
}
});
it('abgeschnittene und kaputte Anfragen ergeben nur „unbekannt“', () => {
const half = fx('rsa-leaf.csr.der').subarray(0, 200);
const r = detectBlob(half, { file: 4, path: 'halb.csr', passwords: [] });
expect(r.items).toEqual([]);
expect(r.ignored).toEqual([{ file: 4, path: 'halb.csr', reason: 'unknown' }]);
const brokenPem = Buffer.from(
'-----BEGIN CERTIFICATE REQUEST-----\nQUJDREVGR0hJSktMTU5PUA==\n-----END CERTIFICATE REQUEST-----\n',
);
expect(detectBlob(brokenPem, { file: 0, path: 'k.csr', passwords: [] }).items).toEqual([]);
});
});