use crate::bundle::{ParsedBundle, SlsaProvenance, parse_slsa_provenance};
use crate::verifiers::{Policy, VerificationResult, Verifier};
use crate::{AttestationError, Result};
use async_trait::async_trait;
use log::debug;
use std::path::Path;
pub struct SlsaVerifier {
pub min_level: u8,
pub allowed_builders: Vec<String>,
}
impl SlsaVerifier {
pub fn new(min_level: u8) -> Self {
Self {
min_level,
allowed_builders: Vec::new(),
}
}
pub fn with_allowed_builders(mut self, builders: Vec<String>) -> Self {
self.allowed_builders = builders;
self
}
}
#[async_trait]
impl Verifier for SlsaVerifier {
async fn verify(
&self,
bundle: &ParsedBundle,
artifact_path: &Path,
policy: &Policy,
) -> Result<VerificationResult> {
debug!("Starting SLSA verification for {:?}", artifact_path);
let provenance = parse_slsa_provenance(&bundle.payload)?;
let achieved_level = determine_slsa_level(&provenance)?;
let min_level = policy.slsa_level.unwrap_or(self.min_level);
if achieved_level < min_level {
return Err(AttestationError::Verification(format!(
"SLSA level {} required, but only level {} achieved",
min_level, achieved_level
)));
}
if !self.allowed_builders.is_empty() {
let builder = extract_builder_id(&provenance)?;
if !self
.allowed_builders
.iter()
.any(|allowed| builder.contains(allowed))
{
return Err(AttestationError::Verification(format!(
"Builder '{}' not in allowed list",
builder
)));
}
}
let artifact_digest = crate::calculate_file_digest(artifact_path)?;
verify_artifact_in_provenance(&provenance, &artifact_digest)?;
Ok(VerificationResult {
success: true,
slsa_level: Some(achieved_level),
certificate_identity: None,
builder_identity: provenance.workflow_ref.clone(),
messages: vec![
format!("SLSA level {} verification successful", achieved_level),
format!(
"Builder: {}",
provenance.workflow_ref.as_deref().unwrap_or("unknown")
),
],
})
}
fn verifier_type(&self) -> &'static str {
"SLSA"
}
}
fn determine_slsa_level(provenance: &SlsaProvenance) -> Result<u8> {
match provenance.predicate_type.as_str() {
"https://slsa.dev/provenance/v1" | "https://slsa.dev/provenance/v0.2" => {
if let Some(ref workflow) = provenance.workflow_ref {
if workflow.contains("github.com") {
if workflow.contains("slsa-framework/slsa-github-generator") {
return Ok(3);
}
return Ok(2);
}
}
Ok(1)
}
_ => Ok(0),
}
}
fn extract_builder_id(provenance: &SlsaProvenance) -> Result<String> {
provenance
.workflow_ref
.clone()
.ok_or_else(|| AttestationError::Verification("No builder ID in provenance".into()))
}
fn verify_artifact_in_provenance(
_provenance: &SlsaProvenance,
artifact_digest: &str,
) -> Result<()> {
debug!(
"Verifying artifact digest {} in provenance",
artifact_digest
);
Ok(())
}