From 7ed971d2a238871e4f1464bdacb37e165669a19b Mon Sep 17 00:00:00 2001 From: Harald Date: Tue, 6 Oct 2026 19:49:25 +0200 Subject: [PATCH] =?UTF-8?q?feat(updater):=20Pflicht-Signaturpr=C3=BCfung?= =?UTF-8?q?=20der=20Releases=20mit=20Minisign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SHA256SUMS.txt muss mit dem eingebauten Minisign-Schlüssel (UPDATE_SIGNING_PUBLIC_KEY) signiert sein; fehlende oder ungültige Signaturen und Versionen ohne Schlüssel lehnen Updates ab - Prüfsummen und Signatur werden vor dem Paket geladen und geprüft - build-release.sh signiert mit MINISIGN_SECRET_KEY und prüft, dass der Schlüssel zum eingebauten öffentlichen Schlüssel passt; in der CI ist die Signatur Pflicht - publish-gitea-release.sh lädt SHA256SUMS.txt.minisig hoch - README: Einrichtung des Schlüsselpaars dokumentiert Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/release.yaml | 6 +- Cargo.lock | 7 ++ Cargo.toml | 1 + README.md | 33 +++-- scripts/build-release.sh | 28 ++++- scripts/publish-gitea-release.sh | 10 +- src/updater.rs | 207 ++++++++++++++++++++++--------- 7 files changed, 222 insertions(+), 70 deletions(-) diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index c8a35a2..5d6dede 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -38,6 +38,7 @@ jobs: squashfs-tools \ dpkg-dev \ curl \ + minisign \ python3 - name: Setup Rust Toolchain @@ -52,10 +53,13 @@ jobs: cargo clippy --all-targets -- -D warnings cargo test - - name: Build Release Artifacts (.deb, .AppImage, SHA256SUMS.txt) + - name: Build Release Artifacts (.deb, .AppImage, SHA256SUMS.txt + .minisig) run: | chmod +x scripts/*.sh ./scripts/build-release.sh + env: + MINISIGN_SECRET_KEY: ${{ secrets.MINISIGN_SECRET_KEY }} + INK_NOTES_REQUIRE_SIGNATURE: "1" - name: Publish Release to Gitea run: | diff --git a/Cargo.lock b/Cargo.lock index 1775ac7..91214ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1045,6 +1045,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "glam", + "minisign-verify", "octotablet", "pollster", "printpdf", @@ -1293,6 +1294,12 @@ dependencies = [ "paste", ] +[[package]] +name = "minisign-verify" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871285dc19d8d0ebe0eef3d0e99a205f2a71363b122632cbbfa4a6c370a960ce" + [[package]] name = "miniz_oxide" version = "0.9.1" diff --git a/Cargo.toml b/Cargo.toml index 19730fd..2ffe049 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ sha2 = "0.10" ureq = { version = "2.10", default-features = false, features = ["tls", "gzip", "json"] } url = "2.5" octotablet = "0.1.0" +minisign-verify = "0.3.0" [profile.dev] opt-level = 1 # Erhöht Rendering-Performance auch im Debug-Modus diff --git a/README.md b/README.md index b488498..27eee77 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Eine latenzoptimierte, hardwarebeschleunigte Inking- und Notizbuch-Anwendung fü - **Natives .deb-Paket**: Optimiert für Zorin OS 18 (Ubuntu 24.04 LTS Basis) mit vollständiger XDG Desktop-Integration (`.desktop`, Hicolor-Icons SVG & PNG, AppStream Metainfo, Debian Copyright & Changelog). - **Portables .AppImage**: Eigenständiges x86_64 Bundle. -- **In-App Updater (`src/updater.rs`)**: Automatischer Prüflauf gegen Gitea Releases, SemVer-Filterung, SHA-256-Prüfung und atomarer In-Place Austausch. +- **In-App Updater (`src/updater.rs`)**: Automatischer Prüflauf gegen Gitea Releases, SemVer-Filterung, Minisign-Signaturprüfung, SHA-256-Prüfung und atomarer In-Place Austausch. ### 1. Release-Pakete bauen @@ -84,6 +84,23 @@ Ergebnis in `target/`: - `ink-notes_0.1.0_amd64.deb` - `ink-notes_0.1.0_x86_64.AppImage` - `SHA256SUMS.txt` +- `SHA256SUMS.txt.minisig` (nur mit gesetztem `MINISIGN_SECRET_KEY`, siehe unten) + +### Release-Signatur einrichten (einmalig) + +In-App-Updates werden nur mit gültiger Minisign-Signatur installiert. Einmalig ein Schlüsselpaar erzeugen (ohne Passwort, da es nur als CI-Secret verwendet wird): + +```bash +minisign -G -W -p ink-notes.pub -s ink-notes.key +# oder ohne minisign-Paket, mit dem kompatiblen rsign2 (cargo install rsign2): +rsign generate -W -p ink-notes.pub -s ink-notes.key +``` + +1. Die zweite Zeile aus `ink-notes.pub` (beginnt mit `RW`) in `src/updater.rs` als `UPDATE_SIGNING_PUBLIC_KEY` eintragen und committen. +2. Den kompletten Inhalt von `ink-notes.key` in Gitea unter *Repository → Einstellungen → Actions → Secrets* als `MINISIGN_SECRET_KEY` anlegen. +3. `ink-notes.key` sicher offline aufbewahren und vom Rechner löschen. Geht der Schlüssel verloren, können bestehende Installationen keine Updates mehr prüfen. + +Lokal signiert `build-release.sh` nur, wenn `MINISIGN_SECRET_KEY` gesetzt ist. In der CI ist die Signatur Pflicht (`INK_NOTES_REQUIRE_SIGNATURE=1`), und das Skript prüft, dass der geheime Schlüssel zum eingebauten öffentlichen Schlüssel passt. ### 2. Installation auf Zorin OS 18 / Ubuntu 24.04 @@ -123,15 +140,15 @@ export GITEA_TOKEN="" Der In-App-Updater (`src/updater.rs`) folgt strengen Sicherheitsrichtlinien: -1. **SHA-256-Integritätsprüfung:** - - Vor jeder Ausführung oder Installation wird die offizielle Prüfsummendatei `SHA256SUMS.txt` aus dem Release bezogen. - - Der SHA-256-Hash des heruntergeladenen Pakets wird berechnet (`sha2`). +1. **Minisign-Signatur (Pflicht):** + - Jedes Release enthält `SHA256SUMS.txt.minisig`, eine Minisign-Signatur der Prüfsummendatei. + - Der Updater lädt `SHA256SUMS.txt` und die Signatur **vor** dem Paket und prüft sie gegen den in die App eingebauten öffentlichen Schlüssel (`UPDATE_SIGNING_PUBLIC_KEY` in `src/updater.rs`, Crate `minisign-verify`). + - Fehlt die Signatur, ist sie ungültig oder ist kein Schlüssel eingebaut, wird das Update abgelehnt. Ein kompromittierter Release-Server kann so keine manipulierten Pakete ausliefern. +2. **SHA-256-Integritätsprüfung:** + - Der SHA-256-Hash des heruntergeladenen Pakets wird mit dem Eintrag in der signierten `SHA256SUMS.txt` verglichen (`sha2`). - Weicht die Prüfsumme ab oder fehlt ein Eintrag, wird die Datei sofort gelöscht und der Installationsvorgang mit Fehlermeldung abgebrochen. - - *Hinweis zur Authentizität:* Eine Prüfsumme vom selben Release-Server garantiert Schutz gegen Übertragungsfehler und Datei-Beschädigungen, jedoch keine kryptografische Nicht-Abstreitbarkeit gegenüber einem kompromittierten Server. -2. **Signatur-Ausblick:** - - Die Updater-Architektur enthält Schnittstellen für optionale `.sig`/`.minisig`-Signaturen zur zukünftigen End-to-End-Kryptoverifikation über öffentliche Schlüssel. 3. **Redirect-Schutz & HTTPS:** - - Alle API- und Download-Endpunkte erzwingen standardmäßig HTTPS. + - Jede Anfrage erzwingt HTTPS – auch Download-Links aus der Release-API und jedes Weiterleitungsziel. - Bei HTTP-Redirects werden sensible Autorisierungs-Header (`Authorization: token ...`) verworfen, sobald die Weiterleitung auf eine andere Origin oder ein externes CDN verweist. 4. **Isolierter Download-Pfad & Path-Traversal-Schutz:** - Downloads werden in einem isolierten Benutzerverzeichnis abgelegt (`$XDG_CACHE_HOME/ink-notes/updates` bzw. `~/.cache/ink-notes/updates`, Berechtigung `0700`). diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 1d458dc..38acde4 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # ============================================================================== # Haupt-Release-Build-Skript für ink-notes (Zorin OS 18 / Linux) -# Erstellt .deb, .AppImage und SHA256-Prüfsummen in target/ +# Erstellt .deb, .AppImage, SHA256-Prüfsummen und deren Minisign-Signatur in target/ # ============================================================================== set -euo pipefail @@ -30,10 +30,36 @@ echo "=== Berechne SHA256-Prüfsummen in target/${CHECKSUM_FILE} ===" cd "${ROOT_DIR}/target" sha256sum "${DEB_NAME}" "${APPIMAGE_NAME}" > "${CHECKSUM_FILE}" +# 4. Prüfsummen mit Minisign signieren (Pflicht für In-App-Updates) +SIGNATURE_FILE="${CHECKSUM_FILE}.minisig" +rm -f "${SIGNATURE_FILE}" +EMBEDDED_PUBKEY=$(grep -m1 'pub const UPDATE_SIGNING_PUBLIC_KEY' "${ROOT_DIR}/src/updater.rs" | cut -d '"' -f2) +if [ -n "${MINISIGN_SECRET_KEY:-}" ]; then + if [ -z "${EMBEDDED_PUBKEY}" ]; then + echo "FEHLER: UPDATE_SIGNING_PUBLIC_KEY in src/updater.rs ist leer – diese Version könnte keine Updates prüfen." + exit 1 + fi + echo "=== Signiere ${CHECKSUM_FILE} mit Minisign ===" + KEY_FILE="$(mktemp)" + trap 'rm -f "${KEY_FILE}"' EXIT + printf '%s\n' "${MINISIGN_SECRET_KEY}" > "${KEY_FILE}" + minisign -S -s "${KEY_FILE}" -m "${CHECKSUM_FILE}" -t "ink-notes v${VERSION}" + rm -f "${KEY_FILE}" + # Stellt sicher, dass der geheime Schlüssel zum eingebauten öffentlichen Schlüssel passt + minisign -V -P "${EMBEDDED_PUBKEY}" -m "${CHECKSUM_FILE}" +elif [ "${INK_NOTES_REQUIRE_SIGNATURE:-0}" = "1" ]; then + echo "FEHLER: MINISIGN_SECRET_KEY ist nicht gesetzt, eine Signatur ist aber Pflicht." + exit 1 +else + echo "WARNUNG: MINISIGN_SECRET_KEY nicht gesetzt – ${CHECKSUM_FILE} bleibt unsigniert." + echo " Installierte Apps lehnen ein solches Release als Update ab." +fi + echo "======================================================================" echo "Release-Artefakte für v${VERSION} erfolgreich erstellt:" echo "======================================================================" ls -lh "${DEB_NAME}" "${APPIMAGE_NAME}" "${CHECKSUM_FILE}" +if [ -f "${SIGNATURE_FILE}" ]; then ls -lh "${SIGNATURE_FILE}"; fi echo "" echo "Inhalt von target/${CHECKSUM_FILE}:" cat "${CHECKSUM_FILE}" diff --git a/scripts/publish-gitea-release.sh b/scripts/publish-gitea-release.sh index 73e2d08..420b257 100755 --- a/scripts/publish-gitea-release.sh +++ b/scripts/publish-gitea-release.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # ============================================================================== # Veröffentlicht ein Release auf dem Gitea-Server (gitea.pansi.eu) -# Lädt .deb, .AppImage und SHA256SUMS.txt idempotent als Release-Assets hoch. +# Lädt .deb, .AppImage, SHA256SUMS.txt und SHA256SUMS.txt.minisig idempotent als Release-Assets hoch. # ============================================================================== set -euo pipefail @@ -33,6 +33,7 @@ RELEASE_TITLE="ink-notes ${TAG_NAME}" DEB_FILE="${ROOT_DIR}/target/ink-notes_${VERSION}_amd64.deb" APPIMAGE_FILE="${ROOT_DIR}/target/ink-notes_${VERSION}_x86_64.AppImage" CHECKSUMS_FILE="${ROOT_DIR}/target/SHA256SUMS.txt" +SIGNATURE_FILE="${CHECKSUMS_FILE}.minisig" TEMPLATE_FILE="${ROOT_DIR}/packaging/release-notes.md.tmpl" if [ ! -f "${TEMPLATE_FILE}" ]; then @@ -46,6 +47,12 @@ if [ ! -f "${DEB_FILE}" ] || [ ! -f "${APPIMAGE_FILE}" ] || [ ! -f "${CHECKSUMS_ "${SCRIPT_DIR}/build-release.sh" fi +# Ohne Signatur lehnen installierte Apps das Release als Update ab +if [ ! -f "${SIGNATURE_FILE}" ]; then + echo "FEHLER: '${SIGNATURE_FILE}' fehlt. Release mit gesetztem MINISIGN_SECRET_KEY bauen." + exit 1 +fi + echo "======================================================================" echo "Veröffentliche Release ${TAG_NAME} auf ${GITEA_SERVER}/${REPO_OWNER}/${REPO_NAME}" echo "======================================================================" @@ -230,6 +237,7 @@ except Exception: upload_asset "${DEB_FILE}" upload_asset "${APPIMAGE_FILE}" upload_asset "${CHECKSUMS_FILE}" +upload_asset "${SIGNATURE_FILE}" echo "======================================================================" echo "Release ${TAG_NAME} erfolgreich veröffentlicht!" diff --git a/src/updater.rs b/src/updater.rs index 687cdac..afb03df 100644 --- a/src/updater.rs +++ b/src/updater.rs @@ -7,7 +7,7 @@ //! - Sichere Asset-Namensbereinigung (Path Traversal Schutz) //! - Isolierter Benutzer-Cache-Pfad (`$XDG_CACHE_HOME/ink-notes/updates` mit 0700 Rechten) //! - Integritätsprüfung via SHA-256 (`SHA256SUMS.txt`) mit automatischem Löschen ungültiger Dateien -//! - Erweiterbare Schnittstelle für optionale Signaturprüfung (.sig / .minisig) +//! - Pflicht-Signaturprüfung der `SHA256SUMS.txt` via Minisign (eingebauter öffentlicher Schlüssel) //! - Atomares AppImage-Self-Update via `.new`-Zwischendatei, Backup-Pflicht und atomarem `fs::rename` //! - Echtes Fortschritts-Streaming (~10 Updates/s) //! - Korrekte SemVer-Prüfung via `semver` Crate (Prerelease-Schutz standardmäßig aktiv) @@ -313,22 +313,77 @@ pub fn verify_checksum( Ok(()) } -/// Status einer Signaturverifikation (.sig / .minisig). -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SignatureVerificationStatus { - NotPresent, - Verified { signer: String }, - Failed(String), +/// Öffentlicher Minisign-Schlüssel (Base64), mit dem die `SHA256SUMS.txt` jedes Releases +/// signiert sein muss. Ist er leer, lehnt diese Version alle In-App-Updates ab. +/// Erzeugen: `minisign -G -W -p ink-notes.pub -s ink-notes.key`, dann die zweite Zeile +/// aus `ink-notes.pub` hier eintragen (siehe README, Abschnitt Release-Signatur). +pub const UPDATE_SIGNING_PUBLIC_KEY: &str = ""; + +/// Name des Prüfsummen-Assets eines Releases. +pub const CHECKSUMS_ASSET_NAME: &str = "SHA256SUMS.txt"; +/// Name der Minisign-Signatur zu [`CHECKSUMS_ASSET_NAME`]. +pub const CHECKSUMS_SIGNATURE_ASSET_NAME: &str = "SHA256SUMS.txt.minisig"; + +/// Prüft die Minisign-Signatur der Prüfsummendatei. Nur aktuelle (prehashed) Signaturen +/// werden akzeptiert; ohne hinterlegten Schlüssel schlägt die Prüfung immer fehl. +pub fn verify_checksums_signature( + checksums: &[u8], + signature_text: &str, + public_key_b64: &str, +) -> Result<(), String> { + let key = public_key_b64.trim(); + if key.is_empty() { + return Err("In dieser Version ist kein Signaturschlüssel für Updates hinterlegt. Bitte das Update manuell installieren.".to_string()); + } + let public_key = minisign_verify::PublicKey::from_base64(key) + .map_err(|e| format!("Ungültiger Update-Signaturschlüssel: {e}."))?; + let signature = minisign_verify::Signature::decode(signature_text) + .map_err(|e| format!("{CHECKSUMS_SIGNATURE_ASSET_NAME} ist ungültig: {e}."))?; + public_key + .verify(checksums, &signature, false) + .map_err(|e| format!("Signaturprüfung von {CHECKSUMS_ASSET_NAME} fehlgeschlagen: {e}.")) } -/// Erweiterbare Schnittstelle für kryptografische Signaturprüfung. -pub fn verify_signature_if_present( - _asset_file: &Path, - _sig_bytes: Option<&[u8]>, -) -> Result { - // Erweiterungspunkt: Sobald ein Signaturschlüssel / Minisign-Public-Key - // im Projekt konfiguriert ist, kann hier die kryptografische Verifikation erfolgen. - Ok(SignatureVerificationStatus::NotPresent) +/// Lädt einen Text-Asset des Releases über den abgesicherten HTTP-Client. +fn fetch_release_text( + agent: &ureq::Agent, + release: &GiteaRelease, + name: &str, + config: &UpdaterConfig, +) -> Result { + let asset = release + .assets + .iter() + .find(|a| a.name == name) + .ok_or_else(|| format!("Release enthält keine {name}."))?; + http_get_with_auth_guard( + agent, + &asset.browser_download_url, + config.auth_token.as_deref(), + 5, + config.allow_insecure_http, + ) + .map_err(|e| format!("Konnte {name} nicht laden: {e}."))? + .into_string() + .map_err(|e| format!("Konnte {name} nicht dekodieren: {e}.")) +} + +/// Lädt `SHA256SUMS.txt` samt Minisign-Signatur und gibt den Inhalt nur bei gültiger +/// Signatur zurück. +fn fetch_verified_checksums( + agent: &ureq::Agent, + release: &GiteaRelease, + config: &UpdaterConfig, + public_key_b64: &str, +) -> Result { + // Ohne Schlüssel gar nicht erst Netzwerkanfragen stellen + if public_key_b64.trim().is_empty() { + return verify_checksums_signature(b"", "", public_key_b64).map(|()| String::new()); + } + let checksums = fetch_release_text(agent, release, CHECKSUMS_ASSET_NAME, config)?; + let signature = fetch_release_text(agent, release, CHECKSUMS_SIGNATURE_ASSET_NAME, config)?; + verify_checksums_signature(checksums.as_bytes(), &signature, public_key_b64)?; + Ok(checksums) } /// Event-Typen für den asynchronen Worker. @@ -583,7 +638,31 @@ impl UpdaterController { let target_file = cache_dir.join(&safe_asset_name); let agent = create_http_agent(); - // 1. Download des Release-Assets mit Fortschritts-Streaming + // 1. Signierte Prüfsummen laden und verifizieren, BEVOR das Paket geladen wird + let checksums_content = match active_release.as_ref() { + Some(release) => match fetch_verified_checksums( + &agent, + release, + &config, + UPDATE_SIGNING_PUBLIC_KEY, + ) { + Ok(text) => text, + Err(e) => { + let _ = tx.send(UpdaterEvent::DownloadError(format!( + "{e} Download abgelehnt." + ))); + return; + } + }, + None => { + let _ = tx.send(UpdaterEvent::DownloadError( + "Kein Release ausgewählt. Download abgelehnt.".to_string(), + )); + return; + } + }; + + // 2. Download des Release-Assets mit Fortschritts-Streaming let resp = match http_get_with_auth_guard( &agent, &asset.browser_download_url, @@ -667,54 +746,13 @@ impl UpdaterController { drop(file); - // 2. Integritätsprüfung gegen SHA256SUMS.txt - let checksum_asset = active_release - .as_ref() - .and_then(|r| r.assets.iter().find(|a| a.name == "SHA256SUMS.txt")); - - let checksums_content = match checksum_asset { - Some(ca) => { - match http_get_with_auth_guard( - &agent, - &ca.browser_download_url, - config.auth_token.as_deref(), - 5, - config.allow_insecure_http, - ) { - Ok(c_resp) => match c_resp.into_string() { - Ok(text) => text, - Err(e) => { - let _ = std::fs::remove_file(&target_file); - let _ = tx.send(UpdaterEvent::DownloadError(format!( - "Konnte SHA256SUMS.txt nicht dekodieren: {e}. Datei gelöscht." - ))); - return; - } - }, - Err(e) => { - let _ = std::fs::remove_file(&target_file); - let _ = tx.send(UpdaterEvent::DownloadError(format!( - "Konnte SHA256SUMS.txt nicht laden: {e}. Datei aus Sicherheitsgründen gelöscht." - ))); - return; - } - } - } - None => { - let _ = std::fs::remove_file(&target_file); - let _ = tx.send(UpdaterEvent::DownloadError( - "Release enthält keine SHA256SUMS.txt-Prüfsummen. Download abgelehnt und gelöscht.".to_string(), - )); - return; - } - }; - + // 3. Integritätsprüfung des Pakets gegen die signierte SHA256SUMS.txt if let Err(err) = verify_checksum(&target_file, &safe_asset_name, &checksums_content) { let _ = tx.send(UpdaterEvent::DownloadError(err)); return; } - // 3. Wenn es ein AppImage ist, Executable-Bit auf 0755 setzen + // 4. Wenn es ein AppImage ist, Executable-Bit auf 0755 setzen if safe_asset_name.ends_with(".AppImage") { #[cfg(unix)] { @@ -1018,6 +1056,57 @@ dcdeb7ac87d9e6cb8022e5a71bc7f2c56d779b4fce4efdd5bae5be3b0214884a ink-notes_0.1. assert!(allowed_insecure.validate_url().is_ok()); } + /// Nur für Tests erzeugtes Minisign-Schlüsselpaar (der geheime Schlüssel wurde verworfen). + const TEST_PUBLIC_KEY: &str = "RWQr5S8CVgsjS4UA3uY0xqTDua4uRSSzKWpFfJKLCypjgP3NGt9Tmdbh"; + const TEST_CHECKSUMS: &str = "abc123 ink-notes_9.9.9_amd64.deb\n"; + const TEST_SIGNATURE: &str = "untrusted comment: signature from rsign secret key +RUQr5S8CVgsjS5BYMUx1ICe1E356aP7wesQXw+oKF6Rd8WsvdF1dmCb1ffonXgJ2GhWE5iAWPXQaT8Y/Y4zLIpyS1PA8JCe8eAM= +trusted comment: ink-notes test +iw0NOedyAQg3emO36fHjyfn+AbnI7Idyl+NwFTDJX1YceLM1jSfjIfeIc0D49IQXyGTpM/QnkZJkRO/eI8ZKBQ== +"; + + #[test] + fn test_checksums_signature_verification() { + // Gültige Signatur + assert!(verify_checksums_signature( + TEST_CHECKSUMS.as_bytes(), + TEST_SIGNATURE, + TEST_PUBLIC_KEY + ) + .is_ok()); + + // Manipulierte Prüfsummen (z. B. ausgetauschter Hash) + let tampered = TEST_CHECKSUMS.replace("abc123", "abc124"); + assert!( + verify_checksums_signature(tampered.as_bytes(), TEST_SIGNATURE, TEST_PUBLIC_KEY) + .is_err() + ); + + // Fremder Schlüssel + let other_key = "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3"; + assert!( + verify_checksums_signature(TEST_CHECKSUMS.as_bytes(), TEST_SIGNATURE, other_key) + .is_err() + ); + + // Kein Schlüssel hinterlegt -> strikt ablehnen + let err = + verify_checksums_signature(TEST_CHECKSUMS.as_bytes(), TEST_SIGNATURE, "").unwrap_err(); + assert!(err.contains("kein Signaturschlüssel"), "{err}"); + + // Fehlende / kaputte Signatur + assert!( + verify_checksums_signature(TEST_CHECKSUMS.as_bytes(), "", TEST_PUBLIC_KEY).is_err() + ); + + // Altes (nicht prehashed) Signaturformat wird nicht akzeptiert + let legacy_sig = "untrusted comment: signature from minisign secret key +RWQf6LRCGA9i59SLOFxz6NxvASXDJeRtuZykwQepbDEGt87ig1BNpWaVWuNrm73YiIiJbq71Wi+dP9eKL8OC351vwIasSSbXxwA= +trusted comment: timestamp:1555779966\tfile:test +QtKMXWyYcwdpZAlPF7tE2ENJkRd1ujvKjlj1m9RtHTBnZPa5WKU5uWRs5GoP5M/VqE81QFuMKI5k/SfNQUaOAA=="; + assert!(verify_checksums_signature(b"test", legacy_sig, other_key).is_err()); + } + #[test] fn test_https_enforced_for_every_request() { let https = url::Url::parse("https://gitea.pansi.eu/a.deb").unwrap();