Die Pruefung vor dem Packen suchte die Werte aus config.local.cmake in den Rohbytes jeder Archivdatei. Ein Abbild ist aber 56 Byte Kopf, dann ein gzip-Strom, dann - je nach Bildart - 512 Byte Trailer bzw. Signatur; im komprimierten Strom steht kein Klartext. Derselbe Pruefkoerper meldete deshalb auch fuer einen Bildsatz aus build/, der die Zugangsdaten nachweislich traegt, "sauber (2 geprueft)". Er haette nie Alarm geschlagen. Jetzt wird jede Datei weiterhin roh geprueft (Kopf, Trailer und die Nicht-Abbilder wie README.md, onboard.sh und bruno/ sind unkomprimiert) und jedes Abbild zusaetzlich entpackt. Ein Abbild, das sich nicht entpacken laesst, bricht den Release ab, statt ungeprueft durchzugehen; das gilt auch fuer einen abgeschnittenen gzip-Strom, den decompressobj sonst stillschweigend annimmt. Entpackt wird ab Byte 56 ohne festes Abschneiden des Dateiendes. Anders als in tools/station/deploy.sh liegen hier drei Bildarten nebeneinander: flash/_gz.img traegt gar keinen Trailer, ota/_ota.img einen 512-Byte-Trailer, migrate/*.img eine 512-Byte-Signatur. Ein raw[56:-512] wie dort haette jedes _gz.img fuer unentpackbar gehalten und jeden Release abgebrochen. Der Teststub war der zweite Fehler und verdeckte den ersten: er legte die Zugangsdaten als Klartext in eine Datei, die er nur "..._ota.img" nannte. Damit fand die rohe Suche sie, und der Fall war gruen, obwohl er eine Welt prueft, die es nicht gibt. Der Stub baut jetzt Abbilder wie der echte Bau - 56 Byte Kopf, gzip des Rumpfes mit den Zugangsdaten INNERHALB des komprimierten Rumpfes, beim _ota.img 512 Byte Trailer mit NINL - und kontrolliert selbst, dass der Klartext nicht roh in der Datei steht. Der Rumpf ist gross und gleichfoermig genug, dass deflate ihn wirklich komprimiert statt ihn als "stored block" durchzureichen. Neu sind Szenarien fuer Klartext in einer Nicht-Abbild-Datei, ein nicht entpackbares Abbild und den Meldungstext; die Gegenprobe prueft jetzt auch den Aufbau des Stub-Abbilds. Szenario 6 (Zugangsdaten im komprimierten Rumpf) und 6c sind gegen den alten Pruefkoerper rot. Die Meldung nennt jetzt die Zahl der Dateien und der entpackten Abbilder statt der Zahl der Suchbegriffe. Gefundene Werte werden weiterhin nie ausgegeben, nur ihr Name und die Datei.
368 lines
16 KiB
Bash
Executable File
368 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Baut ein Auslieferungsarchiv: alle acht Ziele, OHNE einkompilierte WLAN-Zugangsdaten
|
|
# (die Lampen starten also im Onboarding-AP), in allen drei Bildformen - OTA, Migration
|
|
# (dev und prod signiert) und serielle Erstbespielung.
|
|
#
|
|
# Aufruf: tools/release.sh <version>
|
|
# Ergebnis: dist/ninlight-<version>.tar.gz
|
|
#
|
|
# Umgebungsvariablen:
|
|
# NINLIGHT_SIGN_DIR Schluesselverzeichnis (Vorgabe ~/workspace/fw-sign)
|
|
# NINLIGHT_NOCREDS_BUILD_REL Build-Verzeichnis relativ zur Repo-Wurzel (s. build_nocreds.sh)
|
|
# NINLIGHT_DOCKER Build-Vorspann (s. build_nocreds.sh); Tests stubben ihn
|
|
set -euo pipefail
|
|
|
|
VERSION=${1:?Aufruf: tools/release.sh <version>}
|
|
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
TARGETS=(evk e27_rgb e14_rgb gu10_rgb e27_cw e14_cw gu10_cw g95_cw)
|
|
BUILD_REL=${NINLIGHT_NOCREDS_BUILD_REL:-build-nocreds}
|
|
BUILD_DIR="$ROOT/$BUILD_REL"
|
|
NAME="ninlight-$VERSION"
|
|
TGZ="$ROOT/dist/$NAME.tar.gz"
|
|
|
|
# Ein vorhandenes Archiv wird NICHT ueberschrieben: eine schon weitergegebene Version
|
|
# still durch einen anderen Inhalt zu ersetzen, ist der Fehler, den man am spaetesten
|
|
# bemerkt.
|
|
[ -e "$TGZ" ] && { echo "Fehler: $TGZ existiert bereits - Version erhoehen oder Datei entfernen" >&2; exit 1; }
|
|
|
|
# sha256: GNU zuerst (Debian/Pi), BSD/macOS als Rueckfall.
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
SHA_CMD="sha256sum"
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
SHA_CMD="shasum -a 256"
|
|
else
|
|
echo "Fehler: weder sha256sum noch shasum gefunden" >&2; exit 1
|
|
fi
|
|
|
|
echo "== Firmware ohne Zugangsdaten bauen =="
|
|
# Der Bauweg liegt bewusst in einem eigenen, wiederverwendbaren Skript (die
|
|
# Migrationsstation braucht dieselben Abzuege) - inklusive des Beiseitelegens von
|
|
# config.local.cmake, das jedes -D schlaegt.
|
|
"$ROOT/tools/build_nocreds.sh" "${TARGETS[@]}"
|
|
|
|
STAGE=$(mktemp -d)
|
|
trap 'rm -rf "$STAGE"' EXIT
|
|
OUT="$STAGE/$NAME"
|
|
mkdir -p "$OUT/ota" "$OUT/flash"
|
|
|
|
echo "== Images einsammeln =="
|
|
for t in "${TARGETS[@]}"; do
|
|
ota="$BUILD_DIR/$t/ninlight_${t}_ota.img"
|
|
gz="$BUILD_DIR/$t/ninlight_${t}_gz.img"
|
|
[ -f "$ota" ] || { echo "Fehler: $ota fehlt" >&2; exit 1; }
|
|
[ -f "$gz" ] || { echo "Fehler: $gz fehlt" >&2; exit 1; }
|
|
cp "$ota" "$OUT/ota/"
|
|
cp "$gz" "$OUT/flash/"
|
|
done
|
|
|
|
echo "== Migrationsimages signieren =="
|
|
# Wie tools/station/deploy.sh: die Schluessel werden nur gelesen, sie gehoeren nie ins
|
|
# Repo, nie auf den Pi und schon gar nicht ins Archiv. Fehlt das Verzeichnis oder ein
|
|
# einzelner Schluessel, entfaellt die jeweilige Variante mit einer Meldung - das Archiv
|
|
# entsteht trotzdem, und README.md sagt dann, warum migrate/ fehlt.
|
|
# ${HOME:-} statt $HOME, damit `set -u` unter env -i/cron nicht unnoetig abbricht.
|
|
SIGN_DIR=${NINLIGHT_SIGN_DIR:-${HOME:-}/workspace/fw-sign}
|
|
KEY_PROD="$SIGN_DIR/QCX_Device_CA/qcx_firmware_signing_key.pem"
|
|
KEY_DEV="$SIGN_DIR/fw_sign.pem"
|
|
NL_SIGN="$ROOT/tools/mkimage/nl_sign.py"
|
|
MIGRATE_NOTE=""
|
|
HAVE_PROD=0
|
|
HAVE_DEV=0
|
|
if [ -d "$SIGN_DIR" ]; then
|
|
if [ -f "$KEY_PROD" ]; then HAVE_PROD=1; else
|
|
echo "Hinweis: Produktionsschluessel fehlt - keine _prod.img" >&2
|
|
fi
|
|
if [ -f "$KEY_DEV" ]; then HAVE_DEV=1; else
|
|
echo "Hinweis: Entwicklungsschluessel fehlt - keine _dev.img" >&2
|
|
fi
|
|
else
|
|
echo "Hinweis: Schluesselverzeichnis $SIGN_DIR nicht gefunden - keine Migrationsimages erzeugt" >&2
|
|
fi
|
|
if [ "$HAVE_PROD" -eq 1 ] || [ "$HAVE_DEV" -eq 1 ]; then
|
|
mkdir -p "$OUT/migrate"
|
|
for t in "${TARGETS[@]}"; do
|
|
# Signiert wird IMMER das rohe _gz.img, niemals das fertige _ota.img: beide belegen
|
|
# denselben 512-Byte-Block am Dateiende (Signatur bzw. Trailer).
|
|
gz="$BUILD_DIR/$t/ninlight_${t}_gz.img"
|
|
if [ "$HAVE_PROD" -eq 1 ]; then
|
|
python3 "$NL_SIGN" sign --key "$KEY_PROD" --in "$gz" --out "$OUT/migrate/${t}_prod.img" \
|
|
|| { echo "Fehler: Signieren von $t (prod) fehlgeschlagen" >&2; exit 1; }
|
|
fi
|
|
if [ "$HAVE_DEV" -eq 1 ]; then
|
|
python3 "$NL_SIGN" sign --key "$KEY_DEV" --in "$gz" --out "$OUT/migrate/${t}_dev.img" \
|
|
|| { echo "Fehler: Signieren von $t (dev) fehlgeschlagen" >&2; exit 1; }
|
|
fi
|
|
done
|
|
fi
|
|
if [ "$HAVE_PROD" -eq 0 ] && [ "$HAVE_DEV" -eq 0 ]; then
|
|
MIGRATE_NOTE="**\`migrate/\` fehlt in diesem Archiv.** Beim Bauen war kein Signierschluessel
|
|
erreichbar (\`$SIGN_DIR\`). Ohne Signatur nimmt die alte Klyqa-Firmware kein Image an;
|
|
alte Lampen lassen sich mit diesem Archiv also nicht migrieren. Die Schluessel gehoeren
|
|
nie in ein Archiv - wer sie hat, baut sich das Archiv mit \`tools/release.sh\` neu."
|
|
elif [ "$HAVE_PROD" -eq 0 ]; then
|
|
MIGRATE_NOTE="**In \`migrate/\` fehlen die \`_prod.img\`.** Beim Bauen war nur der
|
|
Entwicklungsschluessel erreichbar. Lampen mit Produktionssignatur lassen sich damit nicht migrieren."
|
|
elif [ "$HAVE_DEV" -eq 0 ]; then
|
|
MIGRATE_NOTE="**In \`migrate/\` fehlen die \`_dev.img\`.** Beim Bauen war nur der
|
|
Produktionsschluessel erreichbar. Lampen mit Entwicklungssignatur lassen sich damit nicht migrieren."
|
|
fi
|
|
|
|
echo "== Bruno-Collection, Onboarding-Skript und README =="
|
|
mkdir -p "$OUT/bruno"
|
|
cp "$ROOT/bruno/ninlight.yml" "$OUT/bruno/"
|
|
|
|
cat >"$OUT/onboard.sh" <<'ONBOARD'
|
|
#!/usr/bin/env bash
|
|
# Bringt eine frisch geflashte ninlight-Lampe ins WLAN.
|
|
#
|
|
# Aufruf: ./onboard.sh <ssid> [wlan-passwort]
|
|
#
|
|
# VORHER VON HAND (das kann dir kein Skript portabel abnehmen - sich per Skript mit
|
|
# einem WLAN zu verbinden ist auf jedem Betriebssystem anders):
|
|
# 1. Lampe einschalten. Ohne gespeicherte Zugangsdaten pulsiert sie warmweiss und
|
|
# spannt den Zugangspunkt "ninlight-<mac4>" auf (Passwort: ninlight).
|
|
# 2. Diesen Rechner mit genau diesem WLAN verbinden.
|
|
# 3. Dieses Skript aufrufen.
|
|
# 4. Danach wieder ins normale WLAN wechseln; die Lampe ist per mDNS
|
|
# (ninlight-<mac4>.local, Port 3333) erreichbar.
|
|
#
|
|
# Optionen:
|
|
# --host <ip> Adresse der Lampe im AP (Vorgabe 192.168.4.1)
|
|
# --port <port> REST-Port (Vorgabe 3333)
|
|
# --token <token> API-Token. Im unkonfigurierten Onboarding-AP NICHT noetig (der
|
|
# Zugang ist dort offen). Noetig im Fallback-AP, den eine Lampe
|
|
# aufspannt, die bereits Zugangsdaten hat (Router > 5 min weg) -
|
|
# dort ist system/settings wie ueberall tokengeschuetzt.
|
|
# Vorgabewert der Firmware: aabbccddeeff0011223344
|
|
set -euo pipefail
|
|
|
|
command -v curl >/dev/null 2>&1 || { echo "Fehler: curl wird gebraucht" >&2; exit 1; }
|
|
command -v python3 >/dev/null 2>&1 || { echo "Fehler: python3 wird gebraucht (JSON-Body)" >&2; exit 1; }
|
|
|
|
HOST=192.168.4.1
|
|
PORT=3333
|
|
TOKEN=""
|
|
SSID=""
|
|
PASSWORD=""
|
|
# Hilfetext ist der Kommentarblock oben - keine feste Zeilennummer, die beim naechsten
|
|
# Satz im Kopf stillschweigend falsch wird.
|
|
usage() { awk 'NR>1 && /^#/ {sub(/^# ?/, ""); print; next} NR>1 {exit}' "$0"; }
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--host) HOST=${2:?--host braucht einen Wert}; shift 2 ;;
|
|
--port) PORT=${2:?--port braucht einen Wert}; shift 2 ;;
|
|
--token) TOKEN=${2:?--token braucht einen Wert}; shift 2 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
-*) echo "Unbekannte Option: $1" >&2; usage >&2; exit 2 ;;
|
|
*) if [ -z "$SSID" ]; then SSID=$1; else PASSWORD=$1; fi; shift ;;
|
|
esac
|
|
done
|
|
[ -n "$SSID" ] || { echo "Fehler: SSID fehlt" >&2; usage >&2; exit 2; }
|
|
|
|
URL="http://$HOST:$PORT/api/v1/system/settings"
|
|
BODY=$(SSID="$SSID" PASSWORD="$PASSWORD" python3 -c '
|
|
import json, os
|
|
print(json.dumps({"type": "wifi", "ssid": os.environ["SSID"], "password": os.environ["PASSWORD"]}))')
|
|
|
|
set -- -X PUT "$URL" -H "Content-Type: application/json" -d "$BODY" --max-time 15
|
|
[ -n "$TOKEN" ] && set -- "$@" -H "Authorization: $TOKEN"
|
|
echo "== WLAN setzen ($URL) =="
|
|
curl -fsS "$@" || {
|
|
echo "Fehler: die Lampe hat die Zugangsdaten nicht angenommen." >&2
|
|
echo " 401 -> Fallback-AP: --token <token> mitgeben." >&2
|
|
echo " keine Verbindung -> bist du wirklich im WLAN ninlight-<mac4>?" >&2
|
|
exit 1
|
|
}
|
|
echo
|
|
echo "== Verbindung pruefen (system/info) =="
|
|
# Die Lampe braucht ein paar Sekunden, bis sie im neuen WLAN haengt; danach schliesst
|
|
# der AP nach 60 s. wifi.sta_ip zeigt die neue Adresse, wifi.error den Grund, falls es
|
|
# nicht geklappt hat (z.B. falsches Passwort). Wartezeit fuer Tests ueberschreibbar.
|
|
sleep "${NINLIGHT_ONBOARD_WAIT:-8}"
|
|
curl -fsS "http://$HOST:$PORT/api/v1/system/info" --max-time 15 || {
|
|
echo "Hinweis: system/info nicht mehr erreichbar - meist ein gutes Zeichen (der AP ist" >&2
|
|
echo " zu, die Lampe haengt im neuen WLAN). Im normalen WLAN nachsehen." >&2
|
|
}
|
|
echo
|
|
ONBOARD
|
|
chmod +x "$OUT/onboard.sh"
|
|
|
|
cat >"$OUT/README.md" <<README
|
|
# ninlight $VERSION
|
|
|
|
Ersatzfirmware fuer die abgekuendigten Klyqa-Lampen (WinnerMicro W600). Alle Images in
|
|
diesem Archiv sind **ohne einkompilierte WLAN-Zugangsdaten** gebaut: eine damit bespielte
|
|
Lampe startet im Onboarding-Zugangspunkt \`ninlight-<mac4>\` (Passwort \`ninlight\`) und
|
|
wird mit \`onboard.sh\` ins WLAN gebracht.
|
|
|
|
Ziele: evk, e27_rgb, e14_rgb, gu10_rgb, e27_cw, e14_cw, gu10_cw, g95_cw.
|
|
|
|
## Was liegt wo
|
|
|
|
| Verzeichnis | Inhalt | wofuer |
|
|
|---|---|---|
|
|
| \`ota/\` | \`ninlight_<ziel>_ota.img\` | Update einer Lampe, auf der ninlight schon laeuft (REST \`fw_update\`), und die Auslieferung an die Rettungsstation |
|
|
| \`migrate/\` | \`<ziel>_prod.img\`, \`<ziel>_dev.img\` | Erstmigration einer Lampe, auf der noch die alte Klyqa-Firmware laeuft |
|
|
| \`flash/\` | \`ninlight_<ziel>_gz.img\` | serielle Erstbespielung ueber die Platine |
|
|
| \`bruno/\` | \`ninlight.yml\` | REST-Schnittstelle als Bruno-Collection |
|
|
|
|
\`SHA256SUMS\` deckt jede Datei ab: \`sha256sum -c SHA256SUMS\` (macOS: \`shasum -a 256 -c SHA256SUMS\`).
|
|
|
|
$MIGRATE_NOTE
|
|
|
|
## Alte Klyqa-Lampe migrieren
|
|
|
|
Die alte Firmware nimmt nur signierte Images an; welche Signatur (\`_prod\` oder \`_dev\`)
|
|
gilt, haengt an Produkt-ID und Version der Lampe. Der Weg laeuft ueber die
|
|
Migrationsstation (\`tools/station/\` im Repo, ein Raspberry Pi mit zwei Zugangspunkten):
|
|
|
|
1. Station einschalten und \`ninlight-station migrate on\` (oder den Taster druecken).
|
|
2. Lampe einschalten - sie waehlt sich in den Klyqa-Zugangspunkt ein und holt das
|
|
passende Image aus \`migrate/\`.
|
|
3. Nach dem Neustart laeuft ninlight; die Lampe spannt ihren Onboarding-AP auf.
|
|
|
|
## Frisch geflashte Lampe onboarden
|
|
|
|
./onboard.sh "MeinWLAN" "meinpasswort"
|
|
|
|
Vorher von Hand: Lampe einschalten (sie pulsiert warmweiss), diesen Rechner mit dem WLAN
|
|
\`ninlight-<mac4>\` verbinden (Passwort \`ninlight\`). \`./onboard.sh --help\` zeigt die
|
|
Schritte und die Optionen (\`--host\`, \`--port\`, \`--token\`). Im unkonfigurierten
|
|
Onboarding-AP ist der Zugang offen, im Fallback-AP einer bereits onboardeten Lampe ist das
|
|
Token noetig.
|
|
|
|
Serielle Erstbespielung: \`flash/ninlight_<ziel>_gz.img\` mit \`tools/flash.sh\` aus dem Repo
|
|
bzw. \`wm_tool\` auf die Platine schreiben.
|
|
|
|
## Vertrauensgrenze
|
|
|
|
Der 512-Byte-Trailer der \`_ota.img\` prueft Geraetetyp und SHA256 - das ist eine
|
|
Integritaets- und Geraetetyppruefung, **kein kryptografisches Signaturverfahren**. Der
|
|
Download laeuft ueber \`http://\` ohne TLS, und der Rettungskanal ist nur durch ein WLAN-
|
|
Passwort geschuetzt, das mit einem oeffentlichen Default im Repo steht. Wer im selben WLAN
|
|
ist und das API-Token kennt (bzw. beim Recovery in Funkreichweite ist und das Passwort
|
|
kennt), kann der Lampe beliebige Firmware unterschieben. Das ist eine bewusste
|
|
Entscheidung dieses Projekts: Ziel ist, die Signaturpruefung der alten, abgekuendigten
|
|
Original-Firmware zu umgehen, damit die Lampen ueberhaupt weiter nutzbar bleiben - nicht,
|
|
ein neues, gehaertetes Sicherheitsmodell zu bauen.
|
|
|
|
Die RSA-Signatur der \`migrate/\`-Images ist die der ALTEN Firmware und existiert nur, damit
|
|
die alte Firmware das Image ueberhaupt annimmt. Die privaten Schluessel liegen nie in
|
|
diesem Archiv.
|
|
README
|
|
|
|
echo "== Auf Zugangsdaten pruefen =="
|
|
# Doppelter Boden zum Beiseitelegen von config.local.cmake: bevor irgendetwas gepackt
|
|
# wird, wird JEDE Datei des Archivs gegen die echten Werte aus config.local.cmake
|
|
# geprueft. Lieber ein abgebrochener Release als ein Archiv mit Michaels WLAN-Passwort
|
|
# bei Dritten - dieses Archiv geht an Fremde, und wiederholen laesst sich das nicht.
|
|
#
|
|
# Gesucht wird in ZWEI Sichten:
|
|
# roh - Kopf, Trailer und die Nicht-Abbilder (README.md, onboard.sh, bruno/) sind
|
|
# unkomprimiert; eine versehentlich notierte Zeile faellt nur so auf.
|
|
# entpackt- jedes Abbild ist 56 Byte Kopf, dann ein gzip-Strom, dann (beim _ota.img
|
|
# bzw. den signierten migrate/-Bildern) 512 Byte Trailer bzw. Signatur. Im
|
|
# komprimierten Strom steht kein Klartext. Die rohe Suche allein meldete
|
|
# deshalb IMMER "sauber" - auch fuer Abzuege aus build/, die die
|
|
# Zugangsdaten nachweislich tragen. Sie hat nie etwas geprueft.
|
|
#
|
|
# Entpackt wird ab Byte 56 OHNE das Dateiende abzuschneiden: die drei Bildarten im
|
|
# Archiv haben unterschiedliche Enden (flash/_gz.img traegt gar keinen Trailer,
|
|
# ota/_ota.img einen 512-Byte-Trailer, migrate/*.img eine 512-Byte-Signatur). Ein
|
|
# festes raw[56:-512] wie in tools/station/deploy.sh - wo nur Abbilder MIT Trailer
|
|
# vorkommen - wuerde hier jedes _gz.img fuer unentpackbar halten und jeden Release
|
|
# abbrechen. decompressobj hoert am Ende des gzip-Stroms von selbst auf; was danach
|
|
# kommt, ist ohnehin schon roh geprueft.
|
|
#
|
|
# Ein Abbild, das sich nicht entpacken laesst, ist ein FEHLER und bricht ab - nicht
|
|
# "dann eben ungeprueft durchlassen". Das gilt auch fuer einen abgeschnittenen
|
|
# gzip-Strom, den decompressobj sonst stillschweigend annimmt.
|
|
LOCAL="$ROOT/config.local.cmake"
|
|
if [ -f "$LOCAL" ]; then
|
|
python3 - "$OUT" "$LOCAL" <<'PY' || exit 1
|
|
import os
|
|
import re
|
|
import sys
|
|
import zlib
|
|
|
|
stage, local = sys.argv[1], sys.argv[2]
|
|
text = open(local, encoding="utf-8", errors="replace").read()
|
|
# Nur die drei Werte, die wirklich geheim sind. NINLIGHT_AP_PASSWORD und der
|
|
# Vorgabetoken stehen oeffentlich im Repo (README, Bruno) - sie hier mitzupruefen
|
|
# haette nur falsche Alarme erzeugt.
|
|
SECRETS = ("NINLIGHT_WIFI_SSID", "NINLIGHT_WIFI_PASSWORD", "NINLIGHT_TOKEN")
|
|
PUBLIC = {"", "ninlight", "aabbccddeeff0011223344", "ninlight-recovery", "change-me-please"}
|
|
# Kopflaenge des vom wm_tool erzeugten Abbilds; danach beginnt der gzip-Strom.
|
|
HEADER_LEN = 56
|
|
needles = []
|
|
for name in SECRETS:
|
|
m = re.search(r'^\s*set\s*\(\s*%s\s+"([^"]*)"' % name, text, re.M)
|
|
if m and m.group(1) not in PUBLIC:
|
|
needles.append((name, m.group(1).encode()))
|
|
|
|
|
|
def unpack(raw):
|
|
"""Entpackt den gzip-Rumpf eines Abbilds. Wirft bei allem, was nicht passt."""
|
|
if len(raw) <= HEADER_LEN:
|
|
raise ValueError("Datei ist kuerzer als der Kopf")
|
|
dec = zlib.decompressobj(zlib.MAX_WBITS | 16)
|
|
body = dec.decompress(raw[HEADER_LEN:])
|
|
# Ohne diese Abfrage waere ein ABGESCHNITTENER gzip-Strom kein Fehler: decompressobj
|
|
# liefert stillschweigend so viel, wie es lesen konnte. Der ungelesene Rest bliebe
|
|
# ungeprueft - und genau dort koennte das Passwort stehen.
|
|
if not dec.eof:
|
|
raise ValueError("gzip-Strom ist unvollstaendig")
|
|
return body
|
|
|
|
|
|
hits = []
|
|
broken = []
|
|
checked = 0
|
|
images = 0
|
|
for root, _dirs, files in os.walk(stage):
|
|
for fn in sorted(files):
|
|
path = os.path.join(root, fn)
|
|
rel = os.path.relpath(path, stage)
|
|
with open(path, "rb") as fh:
|
|
data = fh.read()
|
|
checked += 1
|
|
views = [data]
|
|
if fn.endswith(".img"):
|
|
try:
|
|
views.append(unpack(data))
|
|
except Exception as exc:
|
|
broken.append("%s (%s)" % (rel, exc))
|
|
continue
|
|
images += 1
|
|
for name, needle in needles:
|
|
if any(needle in view for view in views):
|
|
# NUR der Name des Wertes und die Datei - niemals der Wert selbst.
|
|
hits.append("%s in %s" % (name, rel))
|
|
if broken:
|
|
sys.stderr.write(
|
|
"Fehler: Abbild laesst sich nicht entpacken, also auch nicht auf Zugangsdaten "
|
|
"pruefen - nichts gepackt:\n " + "\n ".join(broken) + "\n")
|
|
sys.exit(1)
|
|
if hits:
|
|
sys.stderr.write(
|
|
"Fehler: Zugangsdaten aus config.local.cmake im Archiv gefunden, nichts gepackt:\n "
|
|
+ "\n ".join(hits) + "\n")
|
|
sys.exit(1)
|
|
print(" sauber (%d Dateien geprueft, davon %d Abbilder entpackt)" % (checked, images))
|
|
PY
|
|
fi
|
|
|
|
echo "== SHA256SUMS =="
|
|
(
|
|
cd "$OUT"
|
|
# $SHA_CMD absichtlich ohne Anfuehrungszeichen: "shasum -a 256" sind drei Woerter.
|
|
# shellcheck disable=SC2086
|
|
find . -type f ! -name SHA256SUMS | sed 's|^\./||' | sort | xargs $SHA_CMD >SHA256SUMS
|
|
)
|
|
|
|
echo "== packen =="
|
|
mkdir -p "$ROOT/dist"
|
|
tar czf "$TGZ" -C "$STAGE" "$NAME"
|
|
echo "fertig: $TGZ"
|