import { createHash } from 'node:crypto'; import { readFile, readdir, writeFile } from 'node:fs/promises'; import { fileURLToPath, URL } from 'node:url'; import { join } from 'node:path'; /** * Pruefsummen der Auslieferungspakete. * * Wozu das gut ist: Wer die Anwendung weitergibt oder herunterlaedt, kann damit * feststellen, dass die Datei unterwegs nicht beschaedigt oder veraendert * wurde. Bei einem Programm, dessen Ergebnisse in ein verkehrsbehoerdliches * Verfahren gehen, ist das kein Beiwerk: Ein Pruefer muss sagen koennen, mit * welchem Programmstand eine Unterlage erstellt wurde. * * Wogegen es NICHT hilft, und das steht auch in der erzeugten Datei: Wer die * Pakete austauschen kann, kann die Prueflistendatei daneben genauso * austauschen. Eine Prueflistendatei belegt Unversehrtheit nur, soweit sie * selbst aus einer Quelle stammt, der man traut. Gegen einen Angreifer, der den * Auslieferungsweg beherrscht, hilft ausschliesslich eine Codesignatur. */ const WURZEL = fileURLToPath(new URL('..', import.meta.url)); const AUSGABE = join(WURZEL, 'release'); /** Endungen, die ausgeliefert werden - Zwischenstaende bleiben aussen vor. */ const AUSGELIEFERT = /\.(exe|msi|dmg|zip|AppImage|deb|rpm|snap)$/i; const paket = JSON.parse(await readFile(join(WURZEL, 'package.json'), 'utf8')); const version = paket.version; let eintraege; try { eintraege = await readdir(AUSGABE, { withFileTypes: true }); } catch { console.error(`Kein Ordner "release". Bauen Sie zuerst ein Paket, etwa mit "npm run dist:win".`); process.exit(1); } /* * Nur die Pakete DIESER Version. * * Im Ordner liegen erfahrungsgemaess auch aeltere Staende. Eine Liste, die * fremde Versionen mitfuehrt, ist schlimmer als keine: Sie verleitet dazu, die * falsche Zeile zu vergleichen. */ const dateien = eintraege .filter((e) => e.isFile() && AUSGELIEFERT.test(e.name) && e.name.includes(version)) .map((e) => e.name) .sort(); if (dateien.length === 0) { console.error(`Keine Pakete der Version ${version} in "release" gefunden.`); process.exit(1); } const zeilen = []; for (const name of dateien) { const inhalt = await readFile(join(AUSGABE, name)); const summe = createHash('sha256').update(inhalt).digest('hex'); zeilen.push({ name, summe, bytes: inhalt.byteLength }); } /* * Oertliches Datum, nicht Weltzeit. * * Hier stand `new Date().toISOString().slice(0, 10)`. `toISOString` liefert * Weltzeit, und der Abschnitt schnitt das 'Z' ab, das den Wert als solche * ausgewiesen haette. Uebrig blieb ein nackter Kalendertag unter einer * deutschen Beschriftung - nach ISO 8601 als Ortszeit zu lesen. Deutschland * liegt oestlich von Greenwich: Zwischen 00:00 und 02:00 Ortszeit (Sommerzeit) * beziehungsweise 00:00 und 01:00 (Winterzeit) haette die Datei den Vortag * getragen, waehrend die Pakete daneben den laufenden Tag tragen. Bei einer * Datei, die als Nachweis dient, laeuft ein solcher Abgleich sonst ins Leere. */ const jetzt = new Date(); const zweistellig = (zahl) => String(zahl).padStart(2, '0'); const heute = `${jetzt.getFullYear()}-${zweistellig(jetzt.getMonth() + 1)}-${zweistellig(jetzt.getDate())}`; const text = [ `LSA-Planer Professional ${version} – Prüfsummen`, `Erstellt am ${heute}`, '', 'So prüfen Sie eine Datei unter Windows (PowerShell, im Ordner der Datei):', '', ' Get-FileHash "" -Algorithm SHA256', '', 'Die ausgegebene Zeichenkette muss mit der unten stehenden übereinstimmen.', 'Groß- und Kleinschreibung spielt dabei keine Rolle.', '', 'Was eine Prüfsumme leistet – und was nicht:', 'Sie belegt, dass die Datei genau die ist, die hier aufgeführt wurde. Damit', 'lassen sich Übertragungsfehler und nachträgliche Veränderungen erkennen.', 'Sie belegt das aber nur, soweit diese Liste selbst aus einer Quelle stammt,', 'der Sie trauen – wer die Pakete austauschen kann, kann diese Datei daneben', 'ebenso austauschen. Gegen einen beherrschten Auslieferungsweg hilft allein', 'eine Codesignatur.', '', 'SHA-256:', '', ...zeilen.map((z) => `${z.summe} ${z.name}`), '', 'Größen in Byte:', '', ...zeilen.map((z) => `${String(z.bytes).padStart(12)} ${z.name}`), '', ].join('\n'); /* * Mit Bytefolgemarke, aus demselben Grund wie beim CSV-Export: Der Text * enthaelt Umlaute, und ein Windows-Editor, der die Datei ohne Marke als * ANSI liest, zeigt daraus Buchstabensalat. Der Dateiname bleibt ASCII. */ const ziel = join(AUSGABE, `PRUEFSUMMEN-${version}.txt`); await writeFile(ziel, `${text}`, 'utf8'); console.log(`Prüfsummen für ${dateien.length} Paket(e) der Version ${version}:`); for (const z of zeilen) console.log(` ${z.summe} ${z.name}`); console.log(`geschrieben: release/PRUEFSUMMEN-${version}.txt`);