Add download verification with signature and SHA256 support
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user