sigstore_verification/
lib.rs1use std::path::Path;
2use thiserror::Error;
3
4pub mod api;
5pub mod bundle;
6pub mod verify;
7pub mod sources;
8pub mod verifiers;
9
10pub use api::{AttestationClient, FetchParams, Attestation};
12pub use bundle::{ParsedBundle, SlsaProvenance};
13pub use sources::{ArtifactRef, AttestationSource};
14pub use verifiers::{Policy, VerificationResult, Verifier};
15pub use verify::verify_attestations;
16
17#[derive(Debug, Error)]
18pub enum AttestationError {
19 #[error("API error: {0}")]
20 Api(String),
21
22 #[error("Verification failed: {0}")]
23 Verification(String),
24
25 #[error("No attestations found")]
26 NoAttestations,
27
28 #[error("Invalid digest format: {0}")]
29 InvalidDigest(String),
30
31 #[error("IO error: {0}")]
32 Io(#[from] std::io::Error),
33
34 #[error("HTTP error: {0}")]
35 Http(#[from] reqwest::Error),
36
37 #[error("JSON error: {0}")]
38 Json(#[from] serde_json::Error),
39
40 #[error("Sigstore error: {0}")]
41 Sigstore(String),
42}
43
44pub type Result<T> = std::result::Result<T, AttestationError>;
45
46pub async fn verify_artifact(
50 artifact_path: &Path,
51 source: &dyn AttestationSource,
52 verifier: &dyn Verifier,
53 policy: Option<&Policy>,
54) -> Result<VerificationResult> {
55 let artifact_ref = ArtifactRef::from_path(artifact_path)?;
56 let attestations = source.fetch_attestations(&artifact_ref).await?;
57
58 if attestations.is_empty() {
59 return Err(AttestationError::NoAttestations);
60 }
61
62 let bundle = bundle::parse_bundle(&attestations[0])?;
64
65 let default_policy = Policy::default();
67 let policy = policy.unwrap_or(&default_policy);
68
69 verifier.verify(&bundle, artifact_path, policy).await
71}
72
73pub async fn verify_cosign_signature(
75 artifact_path: &Path,
76 sig_or_bundle_path: &Path,
77) -> Result<bool> {
78 let source = sources::file::FileSource::new(sig_or_bundle_path);
79 let verifier = verifiers::cosign::CosignVerifier::new_keyless();
80
81 let result = verify_artifact(
82 artifact_path,
83 &source,
84 &verifier,
85 None,
86 ).await?;
87
88 Ok(result.success)
89}
90
91pub async fn verify_cosign_signature_with_key(
93 artifact_path: &Path,
94 sig_or_bundle_path: &Path,
95 public_key_path: &Path,
96) -> Result<bool> {
97 let source = sources::file::FileSource::new(sig_or_bundle_path);
98 let verifier = verifiers::cosign::CosignVerifier::new_with_key_file(public_key_path).await?;
99
100 let result = verify_artifact(
101 artifact_path,
102 &source,
103 &verifier,
104 None,
105 ).await?;
106
107 Ok(result.success)
108}
109
110pub async fn verify_slsa_provenance(
112 artifact_path: &Path,
113 provenance_path: &Path,
114 min_level: u8,
115) -> Result<bool> {
116 let source = sources::file::FileSource::new(provenance_path);
117 let verifier = verifiers::slsa::SlsaVerifier::new(min_level);
118
119 let policy = Policy {
120 slsa_level: Some(min_level),
121 ..Default::default()
122 };
123
124 let result = verify_artifact(
125 artifact_path,
126 &source,
127 &verifier,
128 Some(&policy),
129 ).await?;
130
131 Ok(result.success)
132}
133
134pub async fn verify_github_attestation(
145 artifact_path: &Path,
146 owner: &str,
147 repo: &str,
148 token: Option<&str>,
149 signer_workflow: Option<&str>,
150) -> Result<bool> {
151 let digest = calculate_file_digest(artifact_path)?;
153
154 let client = AttestationClient::new(token)?;
156
157 let params = FetchParams {
159 owner: owner.to_string(),
160 repo: Some(format!("{}/{}", owner, repo)),
161 digest: format!("sha256:{}", digest),
162 limit: 30,
163 predicate_type: Some("https://slsa.dev/provenance/v1".to_string()),
164 };
165
166 let attestations = client.fetch_attestations(params).await?;
167
168 if attestations.is_empty() {
169 return Err(AttestationError::NoAttestations);
170 }
171
172 verify::verify_attestations(&attestations, artifact_path, signer_workflow).await?;
174
175 Ok(true)
176}
177
178pub fn calculate_file_digest(path: &Path) -> Result<String> {
179 use sha2::{Sha256, Digest};
180 use std::fs::File;
181 use std::io::Read;
182
183 let mut file = File::open(path)?;
184 let mut hasher = Sha256::new();
185 let mut buffer = [0; 8192];
186
187 loop {
188 let bytes_read = file.read(&mut buffer)?;
189 if bytes_read == 0 {
190 break;
191 }
192 hasher.update(&buffer[..bytes_read]);
193 }
194
195 Ok(hex::encode(hasher.finalize()))
196}