use anyhow::{Context, bail};
use minisign_verify::{Error as MinisignError, PublicKey, Signature};
use sha2::{Digest, Sha256};
use super::release::Manifest;
pub(crate) const TRUSTED_KEYS: &[&str] = &[include_str!("../../../minisign-thiras.pub")];
pub(crate) fn trusted_keys() -> anyhow::Result<Vec<PublicKey>> {
TRUSTED_KEYS
.iter()
.map(|text| PublicKey::decode(text).context("embedded minisign public key is malformed"))
.collect()
}
pub(crate) fn verify_manifest_with(
keys: &[PublicKey],
sums: &[u8],
minisig: &str,
) -> anyhow::Result<Manifest> {
let signature = Signature::decode(minisig)
.map_err(|e| anyhow::anyhow!("SHA256SUMS.minisig is not a minisign signature: {e}"))?;
for key in keys {
match key.verify(sums, &signature, false) {
Ok(()) => {
return Manifest::parse(
std::str::from_utf8(sums).context("SHA256SUMS is not UTF-8")?,
);
}
Err(MinisignError::UnexpectedKeyId) => {}
Err(e) => bail!("SHA256SUMS does not verify against SHA256SUMS.minisig: {e}"),
}
}
bail!("SHA256SUMS.minisig is not signed by a key this decdn build trusts")
}
pub(crate) fn sha256_hex(bytes: &[u8]) -> String {
alloy::hex::encode(Sha256::digest(bytes))
}
pub(crate) fn verify_archive(manifest: &Manifest, name: &str, bytes: &[u8]) -> anyhow::Result<()> {
let expected = manifest
.digest_for(name)
.with_context(|| format!("{name} is not listed in SHA256SUMS"))?;
let actual = sha256_hex(bytes);
if actual != expected {
bail!("{name} does not match SHA256SUMS (expected {expected}, got {actual})");
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] mod tests;