Skip to main content

libverify_core/
slsa.rs

1//! SLSA v1.2 track and level definitions.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7use crate::control::{ControlId, builtin};
8
9/// SLSA specification tracks per v1.2.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum SlsaTrack {
13    Source,
14    Build,
15    /// SLSA Dependencies Track: verifies integrity and provenance of dependencies.
16    Dependencies,
17}
18
19impl fmt::Display for SlsaTrack {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            Self::Source => f.write_str("source"),
23            Self::Build => f.write_str("build"),
24            Self::Dependencies => f.write_str("dependencies"),
25        }
26    }
27}
28
29/// SLSA levels within a track (v1.2).
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum SlsaLevel {
33    L0,
34    L1,
35    L2,
36    L3,
37    L4,
38}
39
40impl SlsaLevel {
41    pub fn is_valid_for_track(self, track: SlsaTrack) -> bool {
42        match track {
43            SlsaTrack::Source => true,
44            SlsaTrack::Build => self <= SlsaLevel::L3,
45            SlsaTrack::Dependencies => true,
46        }
47    }
48}
49
50impl fmt::Display for SlsaLevel {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            Self::L0 => f.write_str("L0"),
54            Self::L1 => f.write_str("L1"),
55            Self::L2 => f.write_str("L2"),
56            Self::L3 => f.write_str("L3"),
57            Self::L4 => f.write_str("L4"),
58        }
59    }
60}
61
62/// Mapping of a control to its SLSA track and minimum level.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct SlsaMapping {
65    pub track: SlsaTrack,
66    pub level: SlsaLevel,
67}
68
69/// Returns the SLSA track and minimum level for a built-in control.
70///
71/// Platform-specific controls not in the SLSA framework return `None`.
72pub fn control_slsa_mapping(id: &ControlId) -> Option<SlsaMapping> {
73    match id.as_str() {
74        // Source Track
75        builtin::SOURCE_AUTHENTICITY => Some(SlsaMapping {
76            track: SlsaTrack::Source,
77            level: SlsaLevel::L1,
78        }),
79        builtin::REVIEW_INDEPENDENCE => Some(SlsaMapping {
80            track: SlsaTrack::Source,
81            level: SlsaLevel::L1,
82        }),
83        builtin::BRANCH_HISTORY_INTEGRITY => Some(SlsaMapping {
84            track: SlsaTrack::Source,
85            level: SlsaLevel::L2,
86        }),
87        builtin::BRANCH_PROTECTION_ENFORCEMENT => Some(SlsaMapping {
88            track: SlsaTrack::Source,
89            level: SlsaLevel::L3,
90        }),
91        builtin::TWO_PARTY_REVIEW => Some(SlsaMapping {
92            track: SlsaTrack::Source,
93            level: SlsaLevel::L4,
94        }),
95
96        // Build Track
97        builtin::BUILD_PROVENANCE => Some(SlsaMapping {
98            track: SlsaTrack::Build,
99            level: SlsaLevel::L1,
100        }),
101        builtin::REQUIRED_STATUS_CHECKS => Some(SlsaMapping {
102            track: SlsaTrack::Build,
103            level: SlsaLevel::L1,
104        }),
105        builtin::HOSTED_BUILD_PLATFORM => Some(SlsaMapping {
106            track: SlsaTrack::Build,
107            level: SlsaLevel::L2,
108        }),
109        builtin::PROVENANCE_AUTHENTICITY => Some(SlsaMapping {
110            track: SlsaTrack::Build,
111            level: SlsaLevel::L2,
112        }),
113        builtin::BUILD_ISOLATION => Some(SlsaMapping {
114            track: SlsaTrack::Build,
115            level: SlsaLevel::L3,
116        }),
117
118        // Dependencies Track
119        builtin::DEPENDENCY_SIGNATURE => Some(SlsaMapping {
120            track: SlsaTrack::Dependencies,
121            level: SlsaLevel::L1,
122        }),
123        builtin::DEPENDENCY_PROVENANCE_CHECK => Some(SlsaMapping {
124            track: SlsaTrack::Dependencies,
125            level: SlsaLevel::L2,
126        }),
127        builtin::DEPENDENCY_SIGNER_VERIFIED => Some(SlsaMapping {
128            track: SlsaTrack::Dependencies,
129            level: SlsaLevel::L3,
130        }),
131        builtin::DEPENDENCY_COMPLETENESS => Some(SlsaMapping {
132            track: SlsaTrack::Dependencies,
133            level: SlsaLevel::L4,
134        }),
135
136        // Compliance and platform-specific controls have no SLSA mapping
137        _ => None,
138    }
139}
140
141/// Returns all built-in SLSA control IDs at or below the given level for a track.
142pub fn controls_for_level(track: SlsaTrack, level: SlsaLevel) -> Vec<ControlId> {
143    ALL_SLSA_CONTROLS
144        .iter()
145        .map(|&s| ControlId::new(s))
146        .filter(|id| control_slsa_mapping(id).is_some_and(|m| m.track == track && m.level <= level))
147        .collect()
148}
149
150const ALL_SLSA_CONTROLS: &[&str] = &[
151    builtin::SOURCE_AUTHENTICITY,
152    builtin::REVIEW_INDEPENDENCE,
153    builtin::BRANCH_HISTORY_INTEGRITY,
154    builtin::BRANCH_PROTECTION_ENFORCEMENT,
155    builtin::TWO_PARTY_REVIEW,
156    builtin::BUILD_PROVENANCE,
157    builtin::REQUIRED_STATUS_CHECKS,
158    builtin::HOSTED_BUILD_PLATFORM,
159    builtin::PROVENANCE_AUTHENTICITY,
160    builtin::BUILD_ISOLATION,
161    builtin::DEPENDENCY_SIGNATURE,
162    builtin::DEPENDENCY_PROVENANCE_CHECK,
163    builtin::DEPENDENCY_SIGNER_VERIFIED,
164    builtin::DEPENDENCY_COMPLETENESS,
165];
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn source_l1_controls() {
173        let controls = controls_for_level(SlsaTrack::Source, SlsaLevel::L1);
174        let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
175        assert!(ids.contains(&builtin::SOURCE_AUTHENTICITY));
176        assert!(ids.contains(&builtin::REVIEW_INDEPENDENCE));
177        assert!(!ids.contains(&builtin::BRANCH_HISTORY_INTEGRITY));
178    }
179
180    #[test]
181    fn source_l4_includes_all_source_controls() {
182        let controls = controls_for_level(SlsaTrack::Source, SlsaLevel::L4);
183        assert_eq!(controls.len(), 5);
184    }
185
186    #[test]
187    fn build_l3_includes_all_build_controls() {
188        let controls = controls_for_level(SlsaTrack::Build, SlsaLevel::L3);
189        assert_eq!(controls.len(), 5);
190    }
191
192    #[test]
193    fn compliance_controls_have_no_slsa_mapping() {
194        assert!(control_slsa_mapping(&ControlId::new(builtin::CHANGE_REQUEST_SIZE)).is_none());
195        assert!(control_slsa_mapping(&ControlId::new(builtin::TEST_COVERAGE)).is_none());
196    }
197
198    #[test]
199    fn l4_not_valid_for_build_track() {
200        assert!(!SlsaLevel::L4.is_valid_for_track(SlsaTrack::Build));
201        assert!(SlsaLevel::L4.is_valid_for_track(SlsaTrack::Source));
202    }
203
204    #[test]
205    fn dependencies_l1_includes_signature_only() {
206        let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L1);
207        let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
208        assert!(ids.contains(&builtin::DEPENDENCY_SIGNATURE));
209        assert_eq!(controls.len(), 1);
210    }
211
212    #[test]
213    fn dependencies_l2_includes_provenance() {
214        let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L2);
215        let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
216        assert!(ids.contains(&builtin::DEPENDENCY_SIGNATURE));
217        assert!(ids.contains(&builtin::DEPENDENCY_PROVENANCE_CHECK));
218        assert_eq!(controls.len(), 2);
219    }
220
221    #[test]
222    fn dependencies_l3_includes_signer_verified() {
223        let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L3);
224        assert_eq!(controls.len(), 3);
225    }
226
227    #[test]
228    fn dependencies_l4_includes_all_4_controls() {
229        let controls = controls_for_level(SlsaTrack::Dependencies, SlsaLevel::L4);
230        let ids: Vec<&str> = controls.iter().map(|c| c.as_str()).collect();
231        assert!(ids.contains(&builtin::DEPENDENCY_COMPLETENESS));
232        assert_eq!(controls.len(), 4);
233    }
234
235    #[test]
236    fn dependency_controls_have_slsa_mapping() {
237        for &id in &[
238            builtin::DEPENDENCY_SIGNATURE,
239            builtin::DEPENDENCY_PROVENANCE_CHECK,
240            builtin::DEPENDENCY_SIGNER_VERIFIED,
241            builtin::DEPENDENCY_COMPLETENESS,
242        ] {
243            assert!(
244                control_slsa_mapping(&ControlId::new(id)).is_some(),
245                "{id} should have SLSA mapping"
246            );
247        }
248    }
249}