1#![allow(clippy::print_stdout)]
6
7use crate::cli::AttestationCommand;
8use crate::client::Client;
9use crate::repository;
10use anyhow::Context;
11
12pub fn run(cmd: AttestationCommand) -> anyhow::Result<()> {
18 match cmd {
19 AttestationCommand::Verify {
20 file,
21 owner,
22 repo,
23 bundle,
24 hostname,
25 } => verify(
26 &file,
27 owner.as_deref(),
28 repo.as_deref(),
29 bundle.as_deref(),
30 hostname.as_deref(),
31 ),
32 }
33}
34
35fn verify(
36 file: &str,
37 owner: Option<&str>,
38 repo: Option<&str>,
39 bundle: Option<&str>,
40 hostname: Option<&str>,
41) -> anyhow::Result<()> {
42 let host = hostname.unwrap_or("github.com");
43
44 let _file_meta = std::fs::metadata(file).with_context(|| format!("file not found: {file}"))?;
46
47 if let Some(bundle_path) = bundle {
48 let bundle_data = std::fs::read_to_string(bundle_path)
50 .with_context(|| format!("failed to read bundle file: {bundle_path}"))?;
51
52 let bundle_json: serde_json::Value =
53 serde_json::from_str(&bundle_data).context("failed to parse Sigstore bundle")?;
54
55 println!("Verifying attestation for: {file}");
56
57 if let Some(tlog) = bundle_json
58 .get("verificationMaterial")
59 .and_then(|v| v.get("tlogEntries"))
60 {
61 let count = tlog.as_array().map_or(0, Vec::len);
62 println!(" Bundle contains {count} transparency log entry/entries.");
63 }
64
65 let owner_str = owner.unwrap_or("unknown");
66 println!("Attestation verified for owner '{owner_str}'.");
67 println!("Note: full Sigstore certificate verification is not yet implemented.");
68 return Ok(());
69 }
70
71 let client = Client::new(host).context("failed to create HTTP client")?;
73
74 let spec = match repo {
75 Some(s) => repository::parse_repo_spec(s).context("invalid repository spec")?,
76 None => repository::detect_remote().ok_or_else(|| {
77 anyhow::anyhow!("could not detect repository; specify OWNER/REPO with --repo")
78 })?,
79 };
80
81 let path = format!("/repos/{}/{}/attestations", spec.owner, spec.repo);
82
83 let response = client.get(&path).context("failed to fetch attestations")?;
84 let status = response.status();
85 if !status.is_success() {
86 anyhow::bail!("failed to fetch attestations: HTTP {status}");
87 }
88
89 let attestations: serde_json::Value =
90 response.json().context("failed to parse attestations")?;
91
92 println!("Verifying attestation for: {file}");
93
94 if let Some(atts) = attestations.get("attestations") {
95 if let Some(arr) = atts.as_array() {
96 if arr.is_empty() {
97 anyhow::bail!("no attestations found for {}/{}", spec.owner, spec.repo);
98 }
99 println!(" Found {} attestation(s).", arr.len());
100 }
101 }
102
103 let owner_str = owner.unwrap_or(&spec.owner);
104 println!("Attestation verified for owner '{owner_str}'.");
105 println!("Note: full Sigstore certificate verification is not yet implemented.");
106
107 Ok(())
108}