lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ scripts pruefsummen.mjs

4,7 KB Rohdatei
scripts/pruefsummen.mjs — 117 Zeilen
1 import { createHash } from 'node:crypto';
2 import { readFile, readdir, writeFile } from 'node:fs/promises';
3 import { fileURLToPath, URL } from 'node:url';
4 import { join } from 'node:path';
5
6 /**
7 * Pruefsummen der Auslieferungspakete.
8 *
9 * Wozu das gut ist: Wer die Anwendung weitergibt oder herunterlaedt, kann damit
10 * feststellen, dass die Datei unterwegs nicht beschaedigt oder veraendert
11 * wurde. Bei einem Programm, dessen Ergebnisse in ein verkehrsbehoerdliches
12 * Verfahren gehen, ist das kein Beiwerk: Ein Pruefer muss sagen koennen, mit
13 * welchem Programmstand eine Unterlage erstellt wurde.
14 *
15 * Wogegen es NICHT hilft, und das steht auch in der erzeugten Datei: Wer die
16 * Pakete austauschen kann, kann die Prueflistendatei daneben genauso
17 * austauschen. Eine Prueflistendatei belegt Unversehrtheit nur, soweit sie
18 * selbst aus einer Quelle stammt, der man traut. Gegen einen Angreifer, der den
19 * Auslieferungsweg beherrscht, hilft ausschliesslich eine Codesignatur.
20 */
21
22 const WURZEL = fileURLToPath(new URL('..', import.meta.url));
23 const AUSGABE = join(WURZEL, 'release');
24
25 /** Endungen, die ausgeliefert werden - Zwischenstaende bleiben aussen vor. */
26 const AUSGELIEFERT = /\.(exe|msi|dmg|zip|AppImage|deb|rpm|snap)$/i;
27
28 const paket = JSON.parse(await readFile(join(WURZEL, 'package.json'), 'utf8'));
29 const version = paket.version;
30
31 let eintraege;
32 try {
33 eintraege = await readdir(AUSGABE, { withFileTypes: true });
34 } catch {
35 console.error(`Kein Ordner "release". Bauen Sie zuerst ein Paket, etwa mit "npm run dist:win".`);
36 process.exit(1);
37 }
38
39 /*
40 * Nur die Pakete DIESER Version.
41 *
42 * Im Ordner liegen erfahrungsgemaess auch aeltere Staende. Eine Liste, die
43 * fremde Versionen mitfuehrt, ist schlimmer als keine: Sie verleitet dazu, die
44 * falsche Zeile zu vergleichen.
45 */
46 const dateien = eintraege
47 .filter((e) => e.isFile() && AUSGELIEFERT.test(e.name) && e.name.includes(version))
48 .map((e) => e.name)
49 .sort();
50
51 if (dateien.length === 0) {
52 console.error(`Keine Pakete der Version ${version} in "release" gefunden.`);
53 process.exit(1);
54 }
55
56 const zeilen = [];
57 for (const name of dateien) {
58 const inhalt = await readFile(join(AUSGABE, name));
59 const summe = createHash('sha256').update(inhalt).digest('hex');
60 zeilen.push({ name, summe, bytes: inhalt.byteLength });
61 }
62
63 /*
64 * Oertliches Datum, nicht Weltzeit.
65 *
66 * Hier stand `new Date().toISOString().slice(0, 10)`. `toISOString` liefert
67 * Weltzeit, und der Abschnitt schnitt das 'Z' ab, das den Wert als solche
68 * ausgewiesen haette. Uebrig blieb ein nackter Kalendertag unter einer
69 * deutschen Beschriftung - nach ISO 8601 als Ortszeit zu lesen. Deutschland
70 * liegt oestlich von Greenwich: Zwischen 00:00 und 02:00 Ortszeit (Sommerzeit)
71 * beziehungsweise 00:00 und 01:00 (Winterzeit) haette die Datei den Vortag
72 * getragen, waehrend die Pakete daneben den laufenden Tag tragen. Bei einer
73 * Datei, die als Nachweis dient, laeuft ein solcher Abgleich sonst ins Leere.
74 */
75 const jetzt = new Date();
76 const zweistellig = (zahl) => String(zahl).padStart(2, '0');
77 const heute = `${jetzt.getFullYear()}-${zweistellig(jetzt.getMonth() + 1)}-${zweistellig(jetzt.getDate())}`;
78 const text = [
79 `LSA-Planer Professional ${version} – Prüfsummen`,
80 `Erstellt am ${heute}`,
81 '',
82 'So prüfen Sie eine Datei unter Windows (PowerShell, im Ordner der Datei):',
83 '',
84 ' Get-FileHash "<Dateiname>" -Algorithm SHA256',
85 '',
86 'Die ausgegebene Zeichenkette muss mit der unten stehenden übereinstimmen.',
87 'Groß- und Kleinschreibung spielt dabei keine Rolle.',
88 '',
89 'Was eine Prüfsumme leistet – und was nicht:',
90 'Sie belegt, dass die Datei genau die ist, die hier aufgeführt wurde. Damit',
91 'lassen sich Übertragungsfehler und nachträgliche Veränderungen erkennen.',
92 'Sie belegt das aber nur, soweit diese Liste selbst aus einer Quelle stammt,',
93 'der Sie trauen – wer die Pakete austauschen kann, kann diese Datei daneben',
94 'ebenso austauschen. Gegen einen beherrschten Auslieferungsweg hilft allein',
95 'eine Codesignatur.',
96 '',
97 'SHA-256:',
98 '',
99 ...zeilen.map((z) => `${z.summe} ${z.name}`),
100 '',
101 'Größen in Byte:',
102 '',
103 ...zeilen.map((z) => `${String(z.bytes).padStart(12)} ${z.name}`),
104 '',
105 ].join('\n');
106
107 /*
108 * Mit Bytefolgemarke, aus demselben Grund wie beim CSV-Export: Der Text
109 * enthaelt Umlaute, und ein Windows-Editor, der die Datei ohne Marke als
110 * ANSI liest, zeigt daraus Buchstabensalat. Der Dateiname bleibt ASCII.
111 */
112 const ziel = join(AUSGABE, `PRUEFSUMMEN-${version}.txt`);
113 await writeFile(ziel, `${text}`, 'utf8');
114
115 console.log(`Prüfsummen für ${dateien.length} Paket(e) der Version ${version}:`);
116 for (const z of zeilen) console.log(` ${z.summe} ${z.name}`);
117 console.log(`geschrieben: release/PRUEFSUMMEN-${version}.txt`);