fix(upgrade): U-02 — validate version in minisign trusted comment against release tag
This commit is contained in:
@@ -115,7 +115,7 @@ $SigFile = Join-Path $DistDir "SHA256SUMS.txt.minisig"
|
|||||||
if (Test-Path $KeyFile) {
|
if (Test-Path $KeyFile) {
|
||||||
if (Test-Path $MinisignExe) {
|
if (Test-Path $MinisignExe) {
|
||||||
if (Test-Path $SigFile) { Remove-Item $SigFile -Force }
|
if (Test-Path $SigFile) { Remove-Item $SigFile -Force }
|
||||||
& $MinisignExe -S -s $KeyFile -m $ChecksumFile -W -x $SigFile
|
& $MinisignExe -S -s $KeyFile -m $ChecksumFile -W -x $SigFile -t "version:$Version"
|
||||||
if ($LASTEXITCODE -eq 0 -and (Test-Path $SigFile)) {
|
if ($LASTEXITCODE -eq 0 -and (Test-Path $SigFile)) {
|
||||||
Write-Host "[OK] Minisign-Signatur aktualisiert: dist\SHA256SUMS.txt.minisig" -ForegroundColor Green
|
Write-Host "[OK] Minisign-Signatur aktualisiert: dist\SHA256SUMS.txt.minisig" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ $SigFile = Join-Path $DistDir "SHA256SUMS.txt.minisig"
|
|||||||
if (Test-Path $KeyFile) {
|
if (Test-Path $KeyFile) {
|
||||||
if (Test-Path $MinisignExe) {
|
if (Test-Path $MinisignExe) {
|
||||||
if (Test-Path $SigFile) { Remove-Item $SigFile -Force }
|
if (Test-Path $SigFile) { Remove-Item $SigFile -Force }
|
||||||
& $MinisignExe -S -s $KeyFile -m $ChecksumFile -W -x $SigFile
|
& $MinisignExe -S -s $KeyFile -m $ChecksumFile -W -x $SigFile -t "version:$Version"
|
||||||
if ($LASTEXITCODE -eq 0 -and (Test-Path $SigFile)) {
|
if ($LASTEXITCODE -eq 0 -and (Test-Path $SigFile)) {
|
||||||
Write-Host "[OK] Minisign-Signatur erstellt: dist\SHA256SUMS.txt.minisig" -ForegroundColor Green
|
Write-Host "[OK] Minisign-Signatur erstellt: dist\SHA256SUMS.txt.minisig" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+48
-3
@@ -67,7 +67,8 @@ impl Default for UpgradeOptions {
|
|||||||
/// Überprüft eine Minisign-Signatur für gegebene Binärdaten mit dem angegebenen Minisign Public Key (U-01).
|
/// Überprüft eine Minisign-Signatur für gegebene Binärdaten mit dem angegebenen Minisign Public Key (U-01).
|
||||||
///
|
///
|
||||||
/// Erzwingt strikt Pre-Hashing (`allow_legacy = false`), um Malleability- und Längenangriffe auszuschließen.
|
/// Erzwingt strikt Pre-Hashing (`allow_legacy = false`), um Malleability- und Längenangriffe auszuschließen.
|
||||||
pub fn verify_minisign_signature(data: &[u8], sig_str: &str, pubkey_str: &str) -> Result<()> {
|
/// Gibt bei erfolgreicher Verifikation den kryptografisch authentifizierten `trusted comment` zurück.
|
||||||
|
pub fn verify_minisign_signature(data: &[u8], sig_str: &str, pubkey_str: &str) -> Result<String> {
|
||||||
let pubkey = minisign_verify::PublicKey::from_base64(pubkey_str)
|
let pubkey = minisign_verify::PublicKey::from_base64(pubkey_str)
|
||||||
.map_err(|e| anyhow::anyhow!("Ungültiger öffentlicher Minisign-Schlüssel: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("Ungültiger öffentlicher Minisign-Schlüssel: {e}"))?;
|
||||||
let signature = minisign_verify::Signature::decode(sig_str)
|
let signature = minisign_verify::Signature::decode(sig_str)
|
||||||
@@ -75,6 +76,49 @@ pub fn verify_minisign_signature(data: &[u8], sig_str: &str, pubkey_str: &str) -
|
|||||||
pubkey
|
pubkey
|
||||||
.verify(data, &signature, false)
|
.verify(data, &signature, false)
|
||||||
.map_err(|e| anyhow::anyhow!("Minisign-Signaturprüfung FEHLGESCHLAGEN: {e}. Das Release-Manifest ist nicht vertrauenswürdig!"))?;
|
.map_err(|e| anyhow::anyhow!("Minisign-Signaturprüfung FEHLGESCHLAGEN: {e}. Das Release-Manifest ist nicht vertrauenswürdig!"))?;
|
||||||
|
Ok(signature.trusted_comment().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extrahiert die Versionsnummer aus dem Trusted Comment einer Minisign-Signatur und
|
||||||
|
/// validiert sie gegen das erwartete Release-Tag (U-02).
|
||||||
|
pub fn validate_trusted_comment_version(
|
||||||
|
trusted_comment: &str,
|
||||||
|
expected_release: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let clean_expected = expected_release
|
||||||
|
.trim()
|
||||||
|
.trim_start_matches(|c| c == 'v' || c == 'V');
|
||||||
|
|
||||||
|
if clean_expected.is_empty() {
|
||||||
|
bail!("Erwartetes Release-Tag darf nicht leer sein.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Durchsuche die Tokens des Trusted Comments (getrennt durch Tabs oder Leerzeichen)
|
||||||
|
let found = trusted_comment.split_whitespace().any(|token| {
|
||||||
|
let val = if let Some(stripped) = token.strip_prefix("version:") {
|
||||||
|
stripped
|
||||||
|
} else if let Some(stripped) = token.strip_prefix("release:") {
|
||||||
|
stripped
|
||||||
|
} else if let Some(stripped) = token.strip_prefix("tag:") {
|
||||||
|
stripped
|
||||||
|
} else {
|
||||||
|
token
|
||||||
|
};
|
||||||
|
|
||||||
|
let clean_val = val.trim_start_matches(|c| c == 'v' || c == 'V');
|
||||||
|
clean_val == clean_expected
|
||||||
|
});
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
bail!(
|
||||||
|
"Signatur-Validierungsfehler (U-02): Der kryptografisch gesicherte Trusted Comment \
|
||||||
|
'{}' stimmt nicht mit der erwarteten Release-Version '{}' überein! \
|
||||||
|
Möglicher Replay-Angriff mit einer älteren signierten Prüfsummendatei.",
|
||||||
|
trusted_comment.trim(),
|
||||||
|
expected_release
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,10 +545,11 @@ pub fn run_upgrade(options: &UpgradeOptions) -> Result<()> {
|
|||||||
" {} Verifiziere Minisign-Signatur gegen eingebetteten Herstellerschlüssel ...",
|
" {} Verifiziere Minisign-Signatur gegen eingebetteten Herstellerschlüssel ...",
|
||||||
ui::dim("[-]")
|
ui::dim("[-]")
|
||||||
);
|
);
|
||||||
verify_minisign_signature(sums_content.as_bytes(), &sig_content, SANCTUM_RELEASE_PUBKEY)
|
let trusted_comment = verify_minisign_signature(sums_content.as_bytes(), &sig_content, SANCTUM_RELEASE_PUBKEY)
|
||||||
.context("Minisign-Signaturprüfung für Prüfsummendatei FEHLGESCHLAGEN! Release ist nicht vertrauenswürdig.")?;
|
.context("Minisign-Signaturprüfung für Prüfsummendatei FEHLGESCHLAGEN! Release ist nicht vertrauenswürdig.")?;
|
||||||
|
validate_trusted_comment_version(&trusted_comment, remote_tag)?;
|
||||||
println!(
|
println!(
|
||||||
" • Signatur: {} (Minisign Ed25519)",
|
" • Signatur: {} (Minisign Ed25519, Version validiert)",
|
||||||
ui::green("Gültig")
|
ui::green("Gültig")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use sanctum::upgrade::{
|
use sanctum::upgrade::{
|
||||||
run_upgrade, verify_minisign_signature, UpgradeOptions, SANCTUM_RELEASE_PUBKEY,
|
run_upgrade, validate_trusted_comment_version, verify_minisign_signature, UpgradeOptions,
|
||||||
|
SANCTUM_RELEASE_PUBKEY,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -108,3 +109,38 @@ trusted comment: timestamp:1789750897\tfile:test_payload.txt\thashed\n\
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u02_validate_trusted_comment_version_matching() {
|
||||||
|
// Standard Minisign format with -t "version:0.8.0"
|
||||||
|
let tc1 = "timestamp:1789750897\tversion:0.8.0\tfile:SHA256SUMS.txt\thashed";
|
||||||
|
assert!(validate_trusted_comment_version(tc1, "0.8.0").is_ok());
|
||||||
|
assert!(validate_trusted_comment_version(tc1, "v0.8.0").is_ok());
|
||||||
|
|
||||||
|
// Alternative tags like tag:v0.8.0 or release:0.8.0
|
||||||
|
let tc2 = "release:0.8.0";
|
||||||
|
assert!(validate_trusted_comment_version(tc2, "0.8.0").is_ok());
|
||||||
|
let tc3 = "tag:v0.8.0";
|
||||||
|
assert!(validate_trusted_comment_version(tc3, "0.8.0").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u02_reject_mismatched_release_tag_replay() {
|
||||||
|
// An attacker replays a valid signature from v0.7.0 for an update claiming to be v0.8.0
|
||||||
|
let tc_old = "timestamp:1789750897\tversion:0.7.0\tfile:SHA256SUMS.txt\thashed";
|
||||||
|
let res = validate_trusted_comment_version(tc_old, "v0.8.0");
|
||||||
|
assert!(
|
||||||
|
res.is_err(),
|
||||||
|
"U-02: Ältere oder abweichende Version im Trusted Comment muss abgewiesen werden"
|
||||||
|
);
|
||||||
|
let err_msg = res.unwrap_err().to_string();
|
||||||
|
assert!(
|
||||||
|
err_msg.contains("U-02") && err_msg.contains("Replay-Angriff"),
|
||||||
|
"Fehlermeldung muss auf Replay-Angriff und U-02 hinweisen: {err_msg}"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Empty or completely missing version token
|
||||||
|
let tc_no_ver = "timestamp:1789750897\tfile:SHA256SUMS.txt\thashed";
|
||||||
|
let res2 = validate_trusted_comment_version(tc_no_ver, "0.8.0");
|
||||||
|
assert!(res2.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user