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 { 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()) }