import { execFile } from 'child_process';
|
import { promisify } from 'util';
|
|
const execFileAsync = promisify(execFile);
|
|
export interface Signaturbefund {
|
/** Windows-Status: Valid, NotSigned, UnknownError, HashMismatch … bzw. „nicht geprüft". */
|
status: string;
|
/** Anzeigename des Signaturgebers (leer, wenn unsigniert). */
|
herausgeber: string;
|
}
|
|
const NICHT_GEPRUEFT: Signaturbefund = { status: 'nicht geprüft', herausgeber: '' };
|
|
/**
|
* Prüft die Windows-Signatur (Authenticode) einer Datei.
|
*
|
* Bewusst nur erfassend, nicht blockierend: Manche Programme werden legitim
|
* unsigniert ausgeliefert (z. B. 7-Zip). Erst ein hinterlegter Erwartungswert
|
* macht daraus eine Zusicherung — siehe `weichtHerausgeberAb`.
|
*
|
* Der Dateipfad geht über eine Umgebungsvariable an PowerShell, damit
|
* Anführungszeichen im Pfad nichts zerbrechen können.
|
*/
|
export async function pruefeSignatur(dateipfad: string): Promise<Signaturbefund> {
|
if (process.platform !== 'win32') {
|
return NICHT_GEPRUEFT;
|
}
|
const skript = [
|
'$befund = Get-AuthenticodeSignature -LiteralPath $env:ZU_PRUEFENDE_DATEI;',
|
'$name = if ($befund.SignerCertificate) { $befund.SignerCertificate.Subject } else { "" };',
|
'ConvertTo-Json -Compress @{ status = $befund.Status.ToString(); herausgeber = $name }',
|
].join(' ');
|
try {
|
const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-NonInteractive', '-Command', skript], {
|
timeout: 60000,
|
windowsHide: true,
|
env: { ...process.env, ZU_PRUEFENDE_DATEI: dateipfad },
|
});
|
const roh = JSON.parse(stdout) as { status?: string; herausgeber?: string };
|
return {
|
status: roh.status ?? 'nicht geprüft',
|
herausgeber: lesbarerHerausgeber(roh.herausgeber ?? ''),
|
};
|
} catch {
|
return NICHT_GEPRUEFT; // Prüfung selbst fehlgeschlagen — kein Grund, den Abgleich zu verwerfen
|
}
|
}
|
|
/** Zieht aus dem Zertifikats-Betreff den sprechenden Teil (CN bzw. erstes Feld). */
|
function lesbarerHerausgeber(betreff: string): string {
|
if (!betreff) {
|
return '';
|
}
|
const felder = betreff.split(',').map((feld) => feld.trim());
|
const gemeinsamerName = felder.find((feld) => feld.startsWith('CN='));
|
return (gemeinsamerName ?? felder[0]).replace(/^CN=/, '').replace(/^"|"$/g, '');
|
}
|
|
/** Weicht der gefundene Herausgeber von der Erwartung ab? Ohne Erwartung nie. */
|
export function weichtHerausgeberAb(erwartet: string, gefunden: string): boolean {
|
if (!erwartet.trim()) {
|
return false;
|
}
|
return !gefunden.toLowerCase().includes(erwartet.trim().toLowerCase());
|
}
|