sigstore_verification/
bundle.rs1pub use crate::api::DsseEnvelope;
2use crate::api::Attestation;
3use crate::{AttestationError, Result};
4use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
5use serde_json::Value;
6
7pub fn parse_bundle(attestation: &Attestation) -> Result<ParsedBundle> {
9 let bundle = attestation
10 .bundle
11 .as_ref()
12 .ok_or_else(|| AttestationError::Verification("No bundle found in attestation".into()))?;
13
14 let payload = decode_payload(&bundle.dsse_envelope.payload)?;
15
16 let certificate = extract_certificate_from_bundle(attestation)?;
18
19 let tlog_entries = extract_tlog_entries_from_bundle(attestation)?;
21
22 Ok(ParsedBundle {
23 payload,
24 dsse_envelope: Some(bundle.dsse_envelope.clone()),
25 certificate,
26 media_type: bundle.media_type.clone(),
27 tlog_entries,
28 })
29}
30
31fn decode_payload(payload: &str) -> Result<Vec<u8>> {
33 BASE64
34 .decode(payload)
35 .map_err(|e| AttestationError::Verification(format!("Failed to decode payload: {}", e)))
36}
37
38fn extract_certificate_from_bundle(attestation: &Attestation) -> Result<Option<String>> {
40 let bundle = attestation.bundle.as_ref().ok_or_else(|| {
41 AttestationError::Verification("No bundle found in attestation".into())
42 })?;
43
44 if let Some(verification_material) = &bundle.verification_material {
45 if let Some(cert) = verification_material.get("certificate") {
46 if let Some(raw_bytes) = cert.get("rawBytes") {
47 if let Some(cert_str) = raw_bytes.as_str() {
48 return Ok(Some(cert_str.to_string()));
49 }
50 }
51 }
52 }
53
54 Ok(None)
55}
56
57fn extract_tlog_entries_from_bundle(attestation: &Attestation) -> Result<Option<Vec<serde_json::Value>>> {
59 let bundle = attestation.bundle.as_ref().ok_or_else(|| {
60 AttestationError::Verification("No bundle found in attestation".into())
61 })?;
62
63 if let Some(verification_material) = &bundle.verification_material {
64 if let Some(tlog_entries) = verification_material.get("tlogEntries") {
65 if let Some(tlog_array) = tlog_entries.as_array() {
66 return Ok(Some(tlog_array.clone()));
67 }
68 }
69 }
70
71 Ok(None)
72}
73
74pub fn parse_slsa_provenance(payload: &[u8]) -> Result<SlsaProvenance> {
76 let statement: Value = serde_json::from_slice(payload)
77 .map_err(|e| AttestationError::Verification(format!("Failed to parse payload: {}", e)))?;
78
79 if let Some(type_field) = statement.get("_type") {
81 let type_str = type_field.as_str().unwrap_or("");
82 if !type_str.starts_with("https://in-toto.io/Statement/v") {
83 return Err(AttestationError::Verification(
84 format!("Not an in-toto statement: {}", type_str)
85 ));
86 }
87 }
88
89 let predicate_type = statement
91 .get("predicateType")
92 .and_then(|v| v.as_str())
93 .ok_or_else(|| AttestationError::Verification("Missing predicateType".into()))?;
94
95 if !predicate_type.starts_with("https://slsa.dev/provenance/") {
96 return Err(AttestationError::Verification(format!(
97 "Not a SLSA provenance statement: {}",
98 predicate_type
99 )));
100 }
101
102 let predicate = statement
104 .get("predicate")
105 .ok_or_else(|| AttestationError::Verification("Missing predicate".into()))?;
106
107 let workflow_ref = extract_workflow_ref(predicate)?;
108
109 Ok(SlsaProvenance {
110 predicate_type: predicate_type.to_string(),
111 workflow_ref,
112 })
113}
114
115fn extract_workflow_ref(predicate: &Value) -> Result<Option<String>> {
117 if let Some(build_def) = predicate.get("buildDefinition") {
119 if let Some(ext_params) = build_def.get("externalParameters") {
120 if let Some(workflow) = ext_params.get("workflow") {
121 if let Some(path) = workflow.get("path") {
122 if let Some(path_str) = path.as_str() {
123 return Ok(Some(path_str.to_string()));
124 }
125 }
126 }
127 }
128 }
129
130 if let Some(invocation) = predicate.get("invocation") {
132 if let Some(config_source) = invocation.get("configSource") {
133 if let Some(path) = config_source.get("entryPoint") {
134 if let Some(path_str) = path.as_str() {
135 return Ok(Some(path_str.to_string()));
136 }
137 }
138 }
139 }
140
141 Ok(None)
142}
143
144#[derive(Debug)]
145pub struct ParsedBundle {
146 pub payload: Vec<u8>,
147 pub dsse_envelope: Option<DsseEnvelope>,
148 pub certificate: Option<String>,
149 pub media_type: String,
150 pub tlog_entries: Option<Vec<serde_json::Value>>,
151}
152
153#[derive(Debug, Clone)]
154pub struct SlsaProvenance {
155 pub predicate_type: String,
156 pub workflow_ref: Option<String>,
157}