From bb86447039a1462baabee0ed86e798ea35c303a3 Mon Sep 17 00:00:00 2001 From: harald Date: Sat, 19 Sep 2026 09:45:35 +0200 Subject: [PATCH] =?UTF-8?q?fix(upgrade):=20U-03=20=E2=80=94=20configure=20?= =?UTF-8?q?secondary=20backup=20release=20public=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/upgrade.rs | 26 +++++++++++++++- tests/upgrade_security_test.rs | 56 ++++++++++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/upgrade.rs b/src/upgrade.rs index 0260ec9..f315f13 100644 --- a/src/upgrade.rs +++ b/src/upgrade.rs @@ -20,6 +20,8 @@ pub const OFFICIAL_UPGRADE_URL: &str = "https://gitea.pansi.eu"; pub const CHECKSUM_ASSET_NAME: &str = "SHA256SUMS.txt"; pub const CHECKSUM_SIG_ASSET_NAME: &str = "SHA256SUMS.txt.minisig"; pub const SANCTUM_RELEASE_PUBKEY: &str = "RWSsVphPgr8157M9rTPkWDw3c0qIjc7xi28Gmw+cIWbMipOy4L6ToJEU"; +pub const SANCTUM_RELEASE_PUBKEY_BACKUP: &str = + "RWQEeUSLlX2p4EPUPN1GjeUv34qA0p2B6wJYzcou5oOwAkkw0f+r9FmZ"; /// Repräsentiert ein Asset in einem Gitea-Release #[derive(Debug, Deserialize, Clone)] @@ -122,6 +124,28 @@ pub fn validate_trusted_comment_version( Ok(()) } +/// Überprüft eine Minisign-Signatur für ein Release-Manifest. +/// +/// Prüft primär gegen `SANCTUM_RELEASE_PUBKEY`. Falls diese Verifikation fehlschlägt, +/// wird transparent gegen den sekundären Backup-Schlüssel `SANCTUM_RELEASE_PUBKEY_BACKUP` +/// geprüft (U-03), um unterbrechungsfreie Schlüsselrotationen zu ermöglichen. +pub fn verify_release_manifest_signature(data: &[u8], sig_str: &str) -> Result { + match verify_minisign_signature(data, sig_str, SANCTUM_RELEASE_PUBKEY) { + Ok(comment) => Ok(comment), + Err(primary_err) => { + match verify_minisign_signature(data, sig_str, SANCTUM_RELEASE_PUBKEY_BACKUP) { + Ok(comment) => { + tracing::info!( + "Minisign-Signatur erfolgreich mit sekundärem Backup-Schlüssel verifiziert." + ); + Ok(comment) + } + Err(_) => Err(primary_err), + } + } + } +} + /// Erkannter Paketmanager #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PackageManager { @@ -545,7 +569,7 @@ pub fn run_upgrade(options: &UpgradeOptions) -> Result<()> { " {} Verifiziere Minisign-Signatur gegen eingebetteten Herstellerschlüssel ...", ui::dim("[-]") ); - let trusted_comment = verify_minisign_signature(sums_content.as_bytes(), &sig_content, SANCTUM_RELEASE_PUBKEY) + let trusted_comment = verify_release_manifest_signature(sums_content.as_bytes(), &sig_content) .context("Minisign-Signaturprüfung für Prüfsummendatei FEHLGESCHLAGEN! Release ist nicht vertrauenswürdig.")?; validate_trusted_comment_version(&trusted_comment, remote_tag)?; println!( diff --git a/tests/upgrade_security_test.rs b/tests/upgrade_security_test.rs index 141d5ba..5f14e71 100644 --- a/tests/upgrade_security_test.rs +++ b/tests/upgrade_security_test.rs @@ -1,6 +1,7 @@ use sanctum::upgrade::{ - run_upgrade, validate_trusted_comment_version, verify_minisign_signature, UpgradeOptions, - SANCTUM_RELEASE_PUBKEY, + run_upgrade, validate_trusted_comment_version, verify_minisign_signature, + verify_release_manifest_signature, UpgradeOptions, SANCTUM_RELEASE_PUBKEY, + SANCTUM_RELEASE_PUBKEY_BACKUP, }; #[test] @@ -144,3 +145,54 @@ fn test_u02_reject_mismatched_release_tag_replay() { assert!(res2.is_err()); } +#[test] +fn test_u03_verify_signature_with_backup_key_fallback() { + let payload = b"Hello Sanctum Release 0.8.0 Backup Key Testing\n"; + let backup_sig = "\ +untrusted comment: signature from minisign secret key\n\ +RUQEeUSLlX2p4HrXDO2TYykyNK0C41Vdrq6MIts14mQgNDClGC2RkBV3jHRbP7IclWv+h/IuzvyYi9yz8/RqfhpDVwhqtvC02gk=\n\ +trusted comment: version:0.8.0\n\ +zjZAS4uy1PH+0S9KQ0EU435lpBczU3lULyS5SkWf093iCEBc1ULiscwE5gWkEkex3ecPdRugopT+fKaZBQQnBg==\n"; + + // 1. Direct verification against primary key MUST fail (key rotation scenario) + let prim_res = verify_minisign_signature(payload, backup_sig, SANCTUM_RELEASE_PUBKEY); + assert!( + prim_res.is_err(), + "Backup-Signatur darf nicht gegen den Primärschlüssel verifizieren" + ); + + // 2. Direct verification against backup key MUST succeed + let back_res = verify_minisign_signature(payload, backup_sig, SANCTUM_RELEASE_PUBKEY_BACKUP); + assert!( + back_res.is_ok(), + "Backup-Signatur muss gegen SANCTUM_RELEASE_PUBKEY_BACKUP gültig sein: {:?}", + back_res.err() + ); + + // 3. Fallback verification via verify_release_manifest_signature MUST succeed transparently + let fallback_res = verify_release_manifest_signature(payload, backup_sig); + assert!( + fallback_res.is_ok(), + "verify_release_manifest_signature muss via Backup-Schlüssel erfolgreich sein" + ); + assert_eq!(fallback_res.unwrap(), "version:0.8.0"); +} + +#[test] +fn test_u03_verify_signature_rejects_untrusted_third_party_key() { + let fake_sig = "\ +untrusted comment: signature from minisign secret key\n\ +RUQEeUSLlX2p4HrXDO2TYykyNK0C41Vdrq6MIts14mQgNDClGC2RkBV3jHRbP7IclWv+h/IuzvyYi9yz8/RqfhpDVwhqtvC02gk=\n\ +trusted comment: version:0.8.0\n\ +zjZAS4uy1PH+0S9KQ0EU435lpBczU3lULyS5SkWf093iCEBc1ULiscwE5gWkEkex3ecPdRugopT+fKaZBQQnBg==\n"; + + // Tampered payload with neither primary nor backup key matching + let tampered = b"Tampered data not signed by either key"; + let res = verify_release_manifest_signature(tampered, fake_sig); + assert!( + res.is_err(), + "Weder Primär- noch Backup-Schlüssel dürfen manipulierte Nutzdaten akzeptieren" + ); +} + +