pkgwatch/src/verifier.rs

94 lines
2.9 KiB
Rust
Raw Normal View History

use crate::config::Verification;
use crate::fetcher;
use anyhow::{Context, Result};
use sha2::{Digest, Sha256};
use std::path::Path;
use std::process::Command;
pub struct VerificationResult {
pub tier: u8,
pub passed: bool,
pub justification: String,
}
/// Runs the verification method declared for a package against a
/// downloaded artifact. See SPEC.md > Verification trust tiers for what
/// each tier does and does not prove.
pub fn verify(
client: &reqwest::blocking::Client,
verification: &Verification,
repo: &str,
tag: &str,
artifact_path: &Path,
dest_dir: &Path,
) -> Result<VerificationResult> {
match verification {
Verification::SameOriginSha256 {
checksum_asset_pattern,
} => {
let checksum_path = fetcher::download_asset(
client,
repo,
tag,
checksum_asset_pattern,
dest_dir,
)?;
let checksum_text = std::fs::read_to_string(&checksum_path)?;
let expected = checksum_text
.split_whitespace()
.next()
.context("empty checksum file")?
.to_lowercase();
let data = std::fs::read(artifact_path)?;
let actual = sha256_hex(&data);
let passed = actual == expected;
Ok(VerificationResult {
tier: verification.tier(),
passed,
justification: if passed {
"same-origin sha256 matched — proves transport integrity only, \
not authorship (see tier 4 in SPEC.md)"
.into()
} else {
format!("sha256 mismatch: expected {expected}, got {actual}")
},
})
}
Verification::GithubAttestation => {
let output = Command::new("gh")
.args([
"attestation",
"verify",
&artifact_path.to_string_lossy(),
"-R",
repo,
])
.output()
.context("running `gh attestation verify` (is `gh` installed and authenticated?)")?;
let passed = output.status.success();
Ok(VerificationResult {
tier: verification.tier(),
passed,
justification: if passed {
"GitHub build-provenance attestation verified via `gh attestation verify`"
.into()
} else {
format!(
"gh attestation verify failed: {}",
String::from_utf8_lossy(&output.stderr).trim()
)
},
})
}
}
}
fn sha256_hex(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
hex::encode(hasher.finalize())
}