sigstore_verification/
bundle.rs1use crate::api::Attestation;
2pub use crate::api::DsseEnvelope;
3use crate::{AttestationError, Result};
4use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
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 certificate = extract_certificate_from_bundle(attestation)?;
16 let tlog_entries = extract_tlog_entries_from_bundle(attestation)?;
17
18 if let Some(message_signature) = &bundle.message_signature {
20 return Ok(ParsedBundle {
21 payload: Vec::new(), dsse_envelope: None,
23 certificate,
24 media_type: bundle.media_type.clone(),
25 tlog_entries,
26 message_signature: Some(crate::api::MessageSignature {
27 message_digest: crate::api::MessageDigest {
28 algorithm: message_signature.message_digest.algorithm.clone(),
29 digest: message_signature.message_digest.digest.clone(),
30 },
31 signature: message_signature.signature.clone(),
32 }),
33 });
34 }
35
36 if let Some(dsse_envelope) = &bundle.dsse_envelope {
38 if dsse_envelope.payload.is_empty()
40 && dsse_envelope.payload_type == "application/vnd.dev.sigstore.cosign"
41 && bundle.verification_material.is_some()
42 {
43 return Ok(ParsedBundle {
45 payload: Vec::new(), dsse_envelope: Some(dsse_envelope.clone()),
47 certificate,
48 media_type: bundle.media_type.clone(),
49 tlog_entries,
50 message_signature: None,
51 });
52 }
53
54 let payload = decode_payload(&dsse_envelope.payload)?;
56
57 return Ok(ParsedBundle {
58 payload,
59 dsse_envelope: Some(dsse_envelope.clone()),
60 certificate,
61 media_type: bundle.media_type.clone(),
62 tlog_entries,
63 message_signature: None,
64 });
65 }
66
67 Err(AttestationError::Verification(
69 "Bundle has neither DSSE envelope nor message signature".into(),
70 ))
71}
72
73fn decode_payload(payload: &str) -> Result<Vec<u8>> {
75 BASE64
76 .decode(payload)
77 .map_err(|e| AttestationError::Verification(format!("Failed to decode payload: {}", e)))
78}
79
80fn extract_certificate_from_bundle(attestation: &Attestation) -> Result<Option<String>> {
82 let bundle = attestation
83 .bundle
84 .as_ref()
85 .ok_or_else(|| AttestationError::Verification("No bundle found in attestation".into()))?;
86
87 if let Some(verification_material) = &bundle.verification_material {
88 if let Some(cert) = verification_material.get("certificate") {
89 if let Some(raw_bytes) = cert.get("rawBytes") {
90 if let Some(cert_str) = raw_bytes.as_str() {
91 return Ok(Some(cert_str.to_string()));
92 }
93 }
94 }
95 }
96
97 Ok(None)
98}
99
100fn extract_tlog_entries_from_bundle(
102 attestation: &Attestation,
103) -> Result<Option<Vec<serde_json::Value>>> {
104 let bundle = attestation
105 .bundle
106 .as_ref()
107 .ok_or_else(|| AttestationError::Verification("No bundle found in attestation".into()))?;
108
109 if let Some(verification_material) = &bundle.verification_material {
110 if let Some(rekor_bundle) = verification_material.get("rekorBundle") {
112 return Ok(Some(vec![rekor_bundle.clone()]));
113 }
114
115 if let Some(tlog_entries) = verification_material.get("tlogEntries") {
117 if let Some(tlog_array) = tlog_entries.as_array() {
118 return Ok(Some(tlog_array.clone()));
119 }
120 }
121 }
122
123 Ok(None)
124}
125
126pub fn parse_slsa_provenance(payload: &[u8]) -> Result<SlsaProvenance> {
128 let statement: Value = serde_json::from_slice(payload)
129 .map_err(|e| AttestationError::Verification(format!("Failed to parse payload: {}", e)))?;
130
131 if let Some(type_field) = statement.get("_type") {
133 let type_str = type_field.as_str().unwrap_or("");
134 if !type_str.starts_with("https://in-toto.io/Statement/v") {
135 return Err(AttestationError::Verification(format!(
136 "Not an in-toto statement: {}",
137 type_str
138 )));
139 }
140 }
141
142 let predicate_type = statement
144 .get("predicateType")
145 .and_then(|v| v.as_str())
146 .ok_or_else(|| AttestationError::Verification("Missing predicateType".into()))?;
147
148 if !predicate_type.starts_with("https://slsa.dev/provenance/") {
149 return Err(AttestationError::Verification(format!(
150 "Not a SLSA provenance statement: {}",
151 predicate_type
152 )));
153 }
154
155 let predicate = statement
157 .get("predicate")
158 .ok_or_else(|| AttestationError::Verification("Missing predicate".into()))?;
159
160 let workflow_ref = extract_workflow_ref(predicate)?;
161
162 Ok(SlsaProvenance {
163 predicate_type: predicate_type.to_string(),
164 workflow_ref,
165 })
166}
167
168fn extract_workflow_ref(predicate: &Value) -> Result<Option<String>> {
170 if let Some(build_def) = predicate.get("buildDefinition") {
172 if let Some(ext_params) = build_def.get("externalParameters") {
173 if let Some(workflow) = ext_params.get("workflow") {
174 if let Some(path) = workflow.get("path") {
175 if let Some(path_str) = path.as_str() {
176 return Ok(Some(path_str.to_string()));
177 }
178 }
179 }
180 }
181 }
182
183 if let Some(invocation) = predicate.get("invocation") {
185 if let Some(config_source) = invocation.get("configSource") {
186 if let Some(path) = config_source.get("entryPoint") {
187 if let Some(path_str) = path.as_str() {
188 return Ok(Some(path_str.to_string()));
189 }
190 }
191 }
192 }
193
194 Ok(None)
195}
196
197#[derive(Debug)]
198pub struct ParsedBundle {
199 pub payload: Vec<u8>,
200 pub dsse_envelope: Option<DsseEnvelope>,
201 pub certificate: Option<String>,
202 pub media_type: String,
203 pub tlog_entries: Option<Vec<serde_json::Value>>,
204 pub message_signature: Option<crate::api::MessageSignature>,
206}
207
208#[derive(Debug, Clone)]
209pub struct SlsaProvenance {
210 pub predicate_type: String,
211 pub workflow_ref: Option<String>,
212}