sigstore_verification/
lib.rs

1use std::path::Path;
2use thiserror::Error;
3
4pub mod api;
5pub mod bundle;
6pub mod verify;
7pub mod sources;
8pub mod verifiers;
9
10// Re-export commonly used types
11pub 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
46// ===== Generic Verification Functions =====
47
48/// Verify an artifact using any source and verifier
49pub 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    // Parse the first attestation into a bundle
63    let bundle = bundle::parse_bundle(&attestations[0])?;
64
65    // Use provided policy or default
66    let default_policy = Policy::default();
67    let policy = policy.unwrap_or(&default_policy);
68
69    // Verify using the specified verifier
70    verifier.verify(&bundle, artifact_path, policy).await
71}
72
73/// Verify a Cosign signature or bundle from a file (keyless)
74pub 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
91/// Verify a Cosign signature using a public key
92pub 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
110/// Verify SLSA provenance from a file
111pub 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
134// ===== Legacy GitHub-specific function (for backwards compatibility) =====
135
136/// Verify a GitHub artifact attestation
137///
138/// # Arguments
139/// * `artifact_path` - Path to the artifact file to verify
140/// * `owner` - GitHub organization or user that owns the repository
141/// * `repo` - Repository name (without owner)
142/// * `token` - Optional GitHub token for API authentication
143/// * `signer_workflow` - Optional workflow path to verify against
144pub 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    // Calculate artifact digest
152    let digest = calculate_file_digest(artifact_path)?;
153
154    // Create API client
155    let client = AttestationClient::new(token)?;
156
157    // Fetch attestations from GitHub
158    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 attestations
173    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}