Add download verification with signature and SHA256 support
New verification module with GPG signature checking and SHA256 hash computation. Security tab shows verification status, embedded signature check button, and manual SHA256 input for verifying downloads.
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