use minisign_verify::{PublicKey, Signature};
use sha2::{Digest, Sha256};
use crate::error::UpdateError;
pub fn minisign(
asset_filename: &str,
asset_bytes: &[u8],
sig_bytes: &[u8],
trusted_keys: &[String],
) -> crate::error::Result<()> {
if trusted_keys.is_empty() {
return Err(UpdateError::NoPublicKey);
}
let bad = || UpdateError::BadSignature { asset: asset_filename.to_string() };
let sig_text = std::str::from_utf8(sig_bytes).map_err(|_| bad())?;
let signature = Signature::decode(sig_text).map_err(|_| bad())?;
let mut any_key_parsed = false;
for key_b64 in trusted_keys {
let Ok(public_key) = PublicKey::from_base64(key_b64.trim()) else {
continue;
};
any_key_parsed = true;
if public_key.verify(asset_bytes, &signature, false).is_ok() {
return Ok(());
}
}
if !any_key_parsed {
return Err(UpdateError::MalformedPublicKey);
}
Err(bad())
}
#[must_use]
pub fn sha256_hex(bytes: &[u8]) -> String {
let digest = Sha256::digest(bytes);
let mut out = String::with_capacity(digest.len() * 2);
for byte in digest {
use std::fmt::Write as _;
let _ = write!(out, "{byte:02x}");
}
out
}
pub fn checksums(
asset_filename: &str,
asset_bytes: &[u8],
checksums_file: &str,
) -> crate::error::Result<()> {
let actual = sha256_hex(asset_bytes);
let needle = std::path::Path::new(asset_filename)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(asset_filename);
for line in checksums_file.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let mut parts = line.splitn(2, char::is_whitespace);
let Some(hex) = parts.next() else { continue };
let Some(file) = parts.next() else { continue };
let file = file.trim_start().trim_start_matches('*').trim();
if file == needle {
return if hex.eq_ignore_ascii_case(&actual) {
Ok(())
} else {
Err(UpdateError::BadChecksum { asset: asset_filename.to_string() })
};
}
}
Err(UpdateError::BadChecksum { asset: asset_filename.to_string() })
}