use serde::{Deserialize, Serialize};
use wsc_attestation::{SignatureStatus, TransformationAttestation};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum SlsaLevel {
L0 = 0,
L1 = 1,
L2 = 2,
L3 = 3,
L4 = 4,
}
impl SlsaLevel {
pub fn as_u8(&self) -> u8 {
*self as u8
}
pub fn from_u8(value: u8) -> Self {
match value {
0 => SlsaLevel::L0,
1 => SlsaLevel::L1,
2 => SlsaLevel::L2,
3 => SlsaLevel::L3,
_ => SlsaLevel::L4,
}
}
pub fn description(&self) -> &'static str {
match self {
SlsaLevel::L0 => "No protection",
SlsaLevel::L1 => "Provenance exists",
SlsaLevel::L2 => "Signed provenance",
SlsaLevel::L3 => "Hardened build",
SlsaLevel::L4 => "Hermetic build",
}
}
pub fn meets(&self, required: SlsaLevel) -> bool {
*self >= required
}
}
impl std::fmt::Display for SlsaLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SLSA L{}", self.as_u8())
}
}
impl Default for SlsaLevel {
fn default() -> Self {
SlsaLevel::L0
}
}
#[derive(Debug, Clone)]
pub struct SlsaLevelAnalysis {
pub level: SlsaLevel,
pub has_provenance: bool,
pub is_signed: bool,
pub has_builder_identity: bool,
pub has_transparency_log: bool,
pub all_inputs_pinned: bool,
pub reasons: Vec<String>,
}
impl SlsaLevelAnalysis {
pub fn suggestions_for_next_level(&self) -> Vec<String> {
match self.level {
SlsaLevel::L0 => vec!["Add transformation attestation to the module".to_string()],
SlsaLevel::L1 => vec![
"Sign the attestation with a trusted key".to_string(),
"Use keyless signing with OIDC identity".to_string(),
],
SlsaLevel::L2 => vec![
"Log signature to Rekor transparency log".to_string(),
"Use Sigstore keyless signing".to_string(),
],
SlsaLevel::L3 => vec![
"Ensure all input components have verified signatures".to_string(),
"Pin all dependencies with cryptographic hashes".to_string(),
],
SlsaLevel::L4 => vec![], }
}
}
pub fn detect_slsa_level(attestation: &TransformationAttestation) -> SlsaLevel {
detect_slsa_level_detailed(attestation).level
}
pub fn detect_slsa_level_detailed(attestation: &TransformationAttestation) -> SlsaLevelAnalysis {
let mut analysis = SlsaLevelAnalysis {
level: SlsaLevel::L0,
has_provenance: true, is_signed: false,
has_builder_identity: false,
has_transparency_log: false,
all_inputs_pinned: false,
reasons: Vec::new(),
};
analysis.reasons.push("Attestation present → L1".to_string());
analysis.level = SlsaLevel::L1;
let is_signed = attestation.attestation_signature.algorithm != "unsigned"
&& !attestation.attestation_signature.signature.is_empty();
let has_builder_identity = attestation.attestation_signature.signer_identity.is_some()
|| attestation.attestation_signature.certificate_chain.is_some()
|| attestation.attestation_signature.public_key.is_some();
analysis.is_signed = is_signed;
analysis.has_builder_identity = has_builder_identity;
if is_signed && has_builder_identity {
analysis.reasons.push("Signed with builder identity → L2".to_string());
analysis.level = SlsaLevel::L2;
} else if is_signed {
analysis.reasons.push("Signed but no builder identity → L1".to_string());
}
let has_rekor = attestation.attestation_signature.rekor_uuid.is_some();
analysis.has_transparency_log = has_rekor;
if has_rekor && is_signed {
analysis.reasons.push("Logged to Rekor transparency log → L3".to_string());
analysis.level = SlsaLevel::L3;
}
let all_inputs_verified = !attestation.inputs.is_empty()
&& attestation.inputs.iter().all(|input| {
input.signature_status == SignatureStatus::Verified
});
analysis.all_inputs_pinned = all_inputs_verified;
if all_inputs_verified && has_rekor && is_signed {
analysis.reasons.push("All inputs verified → L4".to_string());
analysis.level = SlsaLevel::L4;
} else if !attestation.inputs.is_empty() && !all_inputs_verified {
let unverified_count = attestation
.inputs
.iter()
.filter(|i| i.signature_status != SignatureStatus::Verified)
.count();
analysis.reasons.push(format!(
"{} of {} inputs not verified (blocks L4)",
unverified_count,
attestation.inputs.len()
));
}
analysis
}
pub fn meets_slsa_level(attestation: &TransformationAttestation, required: SlsaLevel) -> bool {
detect_slsa_level(attestation) >= required
}
#[cfg(test)]
mod tests {
use super::*;
use wsc_attestation::TransformationAttestationBuilder;
fn create_unsigned_attestation() -> TransformationAttestation {
TransformationAttestationBuilder::new_optimization("test-tool", "1.0.0")
.add_input_unsigned(b"input", "input.wasm")
.build(b"output", "output.wasm")
}
#[test]
fn test_slsa_level_ordering() {
assert!(SlsaLevel::L0 < SlsaLevel::L1);
assert!(SlsaLevel::L1 < SlsaLevel::L2);
assert!(SlsaLevel::L2 < SlsaLevel::L3);
assert!(SlsaLevel::L3 < SlsaLevel::L4);
}
#[test]
fn test_slsa_level_display() {
assert_eq!(SlsaLevel::L0.to_string(), "SLSA L0");
assert_eq!(SlsaLevel::L3.to_string(), "SLSA L3");
}
#[test]
fn test_slsa_level_from_u8() {
assert_eq!(SlsaLevel::from_u8(0), SlsaLevel::L0);
assert_eq!(SlsaLevel::from_u8(2), SlsaLevel::L2);
assert_eq!(SlsaLevel::from_u8(99), SlsaLevel::L4); }
#[test]
fn test_slsa_level_meets() {
assert!(SlsaLevel::L3.meets(SlsaLevel::L2));
assert!(SlsaLevel::L2.meets(SlsaLevel::L2));
assert!(!SlsaLevel::L1.meets(SlsaLevel::L2));
}
#[test]
fn test_detect_unsigned_attestation_l1() {
let attestation = create_unsigned_attestation();
let level = detect_slsa_level(&attestation);
assert_eq!(level, SlsaLevel::L1);
}
#[test]
fn test_detailed_analysis() {
let attestation = create_unsigned_attestation();
let analysis = detect_slsa_level_detailed(&attestation);
assert!(analysis.has_provenance);
assert!(!analysis.is_signed);
assert!(!analysis.has_transparency_log);
assert!(!analysis.all_inputs_pinned);
assert!(!analysis.reasons.is_empty());
}
#[test]
fn test_suggestions_for_next_level() {
let analysis = SlsaLevelAnalysis {
level: SlsaLevel::L1,
has_provenance: true,
is_signed: false,
has_builder_identity: false,
has_transparency_log: false,
all_inputs_pinned: false,
reasons: vec![],
};
let suggestions = analysis.suggestions_for_next_level();
assert!(!suggestions.is_empty());
assert!(suggestions.iter().any(|s| s.contains("Sign")));
}
#[test]
fn test_meets_slsa_level() {
let attestation = create_unsigned_attestation();
assert!(meets_slsa_level(&attestation, SlsaLevel::L0));
assert!(meets_slsa_level(&attestation, SlsaLevel::L1));
assert!(!meets_slsa_level(&attestation, SlsaLevel::L2));
}
}