use base64::Engine;
use sha2::{Digest, Sha512};
pub fn verify(
name: &str,
bytes: &[u8],
integrity: &str,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let expected_b64 = integrity
.split_whitespace()
.find_map(|token| token.strip_prefix("sha512-"))
.ok_or_else(|| format!("package `{name}`: no sha512 integrity to verify against"))?;
let expected = base64::engine::general_purpose::STANDARD
.decode(expected_b64)
.map_err(|e| format!("package `{name}`: malformed sha512 integrity: {e}"))?;
if expected.as_slice() != Sha512::digest(bytes).as_slice() {
return Err(format!(
"package `{name}`: integrity mismatch — the downloaded tarball does not match \
the expected sha512"
)
.into());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_checks_sha512_and_rejects_tampering() {
let bytes = b"a downloaded tarball's bytes";
let good = format!(
"sha512-{}",
base64::engine::general_purpose::STANDARD.encode(Sha512::digest(bytes))
);
verify("p", bytes, &good).expect("matching sha512 passes");
let mut tampered = bytes.to_vec();
tampered[0] ^= 0xff;
assert!(verify("p", &tampered, &good).is_err(), "flipped byte fails");
assert!(verify("p", bytes, "sha1-deadbeef").is_err());
}
#[test]
fn verify_rejects_malformed_base64() {
assert!(verify("p", b"x", "sha512-@@@@").is_err());
}
#[test]
fn verify_finds_sha512_among_algorithms_and_tolerates_whitespace() {
let bytes = b"multi-algorithm payload";
let b64 = base64::engine::general_purpose::STANDARD.encode(Sha512::digest(bytes));
let integrity = format!(" sha1-deadbeef sha512-{b64} ");
verify("p", bytes, &integrity).expect("sha512 is found among the listed algorithms");
}
#[test]
fn verify_rejects_a_short_digest() {
let short = base64::engine::general_purpose::STANDARD.encode(b"only nine");
let integrity = format!("sha512-{short}");
assert!(
verify("p", b"a downloaded tarball's bytes", &integrity).is_err(),
"a sub-64-byte digest cannot match"
);
}
#[test]
fn verify_accepts_exactly_the_pinned_bytes_and_nothing_else() {
for payload in [b"".as_slice(), b"x", b"a slightly longer tarball payload"] {
let good = format!(
"sha512-{}",
base64::engine::general_purpose::STANDARD.encode(Sha512::digest(payload))
);
verify("p", payload, &good).expect("the exact payload verifies");
for i in 0..payload.len() {
let mut tampered = payload.to_vec();
tampered[i] ^= 0xff;
assert!(
verify("p", &tampered, &good).is_err(),
"flipping byte {i} must fail"
);
}
}
}
}