Add download verification with signature and SHA256 support

This commit is contained in:
2026-02-28 00:05:43 +02:00
parent e52397f64b
commit a515ee6b4f
4 changed files with 310 additions and 0 deletions

View File

@@ -1408,6 +1408,141 @@ fn build_security_tab(record: &AppImageRecord, db: &Rc<Database>) -> gtk::Box {
.spacing(24)
.build();
// Verification group
let verify_group = adw::PreferencesGroup::builder()
.title("Verification")
.description("Check the integrity and authenticity of this AppImage")
.build();
// Signature status
let sig_label = if record.has_signature { "Signature present" } else { "No signature" };
let sig_badge_class = if record.has_signature { "success" } else { "neutral" };
let sig_row = adw::ActionRow::builder()
.title("Digital signature")
.subtitle(sig_label)
.build();
let sig_badge = widgets::status_badge(sig_label, sig_badge_class);
sig_badge.set_valign(gtk::Align::Center);
sig_row.add_suffix(&sig_badge);
verify_group.add(&sig_row);
// SHA256 hash
if let Some(ref hash) = record.sha256 {
let sha_row = adw::ActionRow::builder()
.title("SHA256")
.subtitle(hash)
.subtitle_selectable(true)
.build();
verify_group.add(&sha_row);
}
// Verification status
let verify_status = record.verification_status.as_deref().unwrap_or("not_checked");
let verify_row = adw::ActionRow::builder()
.title("Verification")
.subtitle(match verify_status {
"signed_valid" => "Signed and verified",
"signed_invalid" => "Signature present but invalid",
"checksum_match" => "Checksum verified",
"checksum_mismatch" => "Checksum mismatch - file may be corrupted",
_ => "Not verified",
})
.build();
let verify_badge = widgets::status_badge(
match verify_status {
"signed_valid" | "checksum_match" => "Verified",
"signed_invalid" | "checksum_mismatch" => "Failed",
_ => "Unchecked",
},
match verify_status {
"signed_valid" | "checksum_match" => "success",
"signed_invalid" | "checksum_mismatch" => "error",
_ => "neutral",
},
);
verify_badge.set_valign(gtk::Align::Center);
verify_row.add_suffix(&verify_badge);
verify_group.add(&verify_row);
// Manual SHA256 verification input
let sha_input = adw::EntryRow::builder()
.title("Verify SHA256")
.show_apply_button(true)
.build();
sha_input.set_tooltip_text(Some("Paste an expected SHA256 hash to verify this file"));
let record_path = record.path.clone();
let record_id_v = record.id;
let db_verify = db.clone();
sha_input.connect_apply(move |row| {
let expected = row.text().to_string();
if expected.is_empty() {
return;
}
let path = record_path.clone();
let db_ref = db_verify.clone();
let row_ref = row.clone();
glib::spawn_future_local(async move {
let result = gio::spawn_blocking(move || {
let p = std::path::Path::new(&path);
crate::core::verification::verify_sha256(p, &expected)
})
.await;
if let Ok(status) = result {
let label = status.label();
row_ref.set_title(&format!("Verify SHA256 - {}", label));
db_ref.set_verification_status(record_id_v, status.as_str()).ok();
}
});
});
verify_group.add(&sha_input);
// Check signature button
let check_sig_row = adw::ActionRow::builder()
.title("Check embedded signature")
.subtitle("Verify GPG signature if present")
.activatable(true)
.build();
let sig_arrow = gtk::Image::from_icon_name("go-next-symbolic");
sig_arrow.set_valign(gtk::Align::Center);
check_sig_row.add_suffix(&sig_arrow);
let record_path_sig = record.path.clone();
let record_id_sig = record.id;
let db_sig = db.clone();
check_sig_row.connect_activated(move |row| {
row.set_sensitive(false);
row.set_subtitle("Checking...");
let path = record_path_sig.clone();
let db_ref = db_sig.clone();
let row_ref = row.clone();
glib::spawn_future_local(async move {
let result = gio::spawn_blocking(move || {
let p = std::path::Path::new(&path);
crate::core::verification::check_embedded_signature(p)
})
.await;
if let Ok(status) = result {
row_ref.set_subtitle(&status.label());
db_ref.set_verification_status(record_id_sig, status.as_str()).ok();
let result_badge = widgets::status_badge(
match status.badge_class() {
"success" => "Verified",
"error" => "Failed",
_ => "Unknown",
},
status.badge_class(),
);
result_badge.set_valign(gtk::Align::Center);
row_ref.add_suffix(&result_badge);
}
row_ref.set_sensitive(true);
});
});
verify_group.add(&check_sig_row);
inner.append(&verify_group);
let group = adw::PreferencesGroup::builder()
.title("Security Check")
.description(