use std::collections::HashMap;
use minisign_verify::{PublicKey, Signature};
use sha2::{Digest, Sha256};
pub const BUF_MINISIGN_PUBLIC_KEY_B64: &str =
"RWQ/i9xseZwBVE7pEniCNjlNOeeyp4BQgdZDLQcAohxEAH5Uj5DEKjv6";
pub const PREHASHED_MINISIGN_MIN_VERSION: &str = "1.12.0";
pub fn verify_minisign_signature(
data: &[u8],
minisig_text: &str,
public_key_b64: &str,
allow_legacy: bool,
) -> Result<(), String> {
let pk =
PublicKey::from_base64(public_key_b64).map_err(|e| format!("parse public key: {e}"))?;
let sig = Signature::decode(minisig_text)
.map_err(|e| format!("parse minisig signature text: {e}"))?;
pk.verify(data, &sig, allow_legacy)
.map_err(|e| format!("minisign verify failed: {e}"))
}
pub fn parse_sha256_list(data: &[u8]) -> Result<HashMap<String, String>, String> {
let text = std::str::from_utf8(data).map_err(|e| e.to_string())?;
let mut m = HashMap::new();
for line in text.lines() {
let line = line.trim_end();
if line.is_empty() {
continue;
}
let (hash, name) = line.split_once(" ").ok_or_else(|| {
format!("invalid sha256.txt line (expected 'HASH filename'): {line:?}")
})?;
if hash.len() != 64 || !hash.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(format!("bad hash in line: {line:?}"));
}
m.insert(name.trim().to_string(), hash.to_ascii_lowercase());
}
Ok(m)
}
pub fn sha256_hex(bytes: &[u8]) -> String {
hex::encode(Sha256::digest(bytes))
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::{BUF_MINISIGN_PUBLIC_KEY_B64, verify_minisign_signature};
fn fixture(name: &str) -> Option<String> {
let md = std::env::var("CARGO_MANIFEST_DIR").ok()?;
let p = Path::new(&md);
for dir in [
p.join("build_support/test_fixtures"),
p.join("../buf-tools/build_support/test_fixtures"),
] {
let path = dir.join(name);
if let Ok(s) = std::fs::read_to_string(&path) {
return Some(s.replace("\r\n", "\n"));
}
}
None
}
#[test]
fn modern_strict_verifies() {
let Some(data) = fixture("v1_69_0_sha256.txt") else {
return;
};
let Some(sig) = fixture("v1_69_0_sha256.txt.minisig") else {
return;
};
verify_minisign_signature(data.as_bytes(), &sig, BUF_MINISIGN_PUBLIC_KEY_B64, false)
.expect("v1.69.0 fixture must verify in strict mode");
}
#[test]
fn legacy_with_allow_legacy_verifies() {
let Some(data) = fixture("v1_0_0_sha256.txt") else {
return;
};
let Some(sig) = fixture("v1_0_0_sha256.txt.minisig") else {
return;
};
verify_minisign_signature(data.as_bytes(), &sig, BUF_MINISIGN_PUBLIC_KEY_B64, true)
.expect("v1.0.0 fixture must verify with allow_legacy");
}
#[test]
fn legacy_without_allow_legacy_rejects() {
let Some(data) = fixture("v1_0_0_sha256.txt") else {
return;
};
let Some(sig) = fixture("v1_0_0_sha256.txt.minisig") else {
return;
};
let err =
verify_minisign_signature(data.as_bytes(), &sig, BUF_MINISIGN_PUBLIC_KEY_B64, false)
.expect_err("v1.0.0 raw-Ed25519 minisig must fail strict mode");
assert!(
err.contains("minisign verify failed") || err.contains("UnexpectedAlgorithm"),
"unexpected error: {err}"
);
}
}