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 if bundle.dsse_envelope.payload.is_empty()
16 && bundle.dsse_envelope.payload_type == "application/vnd.dev.sigstore.cosign"
17 && bundle.verification_material.is_some()
18 {
19 let certificate = extract_certificate_from_bundle(attestation)?;
21 let tlog_entries = extract_tlog_entries_from_bundle(attestation)?;
22
23 return Ok(ParsedBundle {
24 payload: Vec::new(), dsse_envelope: Some(bundle.dsse_envelope.clone()),
26 certificate,
27 media_type: bundle.media_type.clone(),
28 tlog_entries,
29 });
30 }
31
32 let payload = decode_payload(&bundle.dsse_envelope.payload)?;
34
35 let certificate = extract_certificate_from_bundle(attestation)?;
37
38 let tlog_entries = extract_tlog_entries_from_bundle(attestation)?;
40
41 Ok(ParsedBundle {
42 payload,
43 dsse_envelope: Some(bundle.dsse_envelope.clone()),
44 certificate,
45 media_type: bundle.media_type.clone(),
46 tlog_entries,
47 })
48}
49
50fn decode_payload(payload: &str) -> Result<Vec<u8>> {
52 BASE64
53 .decode(payload)
54 .map_err(|e| AttestationError::Verification(format!("Failed to decode payload: {}", e)))
55}
56
57fn extract_certificate_from_bundle(attestation: &Attestation) -> Result<Option<String>> {
59 let bundle = attestation
60 .bundle
61 .as_ref()
62 .ok_or_else(|| AttestationError::Verification("No bundle found in attestation".into()))?;
63
64 if let Some(verification_material) = &bundle.verification_material {
65 if let Some(cert) = verification_material.get("certificate") {
66 if let Some(raw_bytes) = cert.get("rawBytes") {
67 if let Some(cert_str) = raw_bytes.as_str() {
68 return Ok(Some(cert_str.to_string()));
69 }
70 }
71 }
72 }
73
74 Ok(None)
75}
76
77fn extract_tlog_entries_from_bundle(
79 attestation: &Attestation,
80) -> Result<Option<Vec<serde_json::Value>>> {
81 let bundle = attestation
82 .bundle
83 .as_ref()
84 .ok_or_else(|| AttestationError::Verification("No bundle found in attestation".into()))?;
85
86 if let Some(verification_material) = &bundle.verification_material {
87 if let Some(rekor_bundle) = verification_material.get("rekorBundle") {
89 return Ok(Some(vec![rekor_bundle.clone()]));
90 }
91
92 if let Some(tlog_entries) = verification_material.get("tlogEntries") {
94 if let Some(tlog_array) = tlog_entries.as_array() {
95 return Ok(Some(tlog_array.clone()));
96 }
97 }
98 }
99
100 Ok(None)
101}
102
103pub fn parse_slsa_provenance(payload: &[u8]) -> Result<SlsaProvenance> {
105 let statement: Value = serde_json::from_slice(payload)
106 .map_err(|e| AttestationError::Verification(format!("Failed to parse payload: {}", e)))?;
107
108 if let Some(type_field) = statement.get("_type") {
110 let type_str = type_field.as_str().unwrap_or("");
111 if !type_str.starts_with("https://in-toto.io/Statement/v") {
112 return Err(AttestationError::Verification(format!(
113 "Not an in-toto statement: {}",
114 type_str
115 )));
116 }
117 }
118
119 let predicate_type = statement
121 .get("predicateType")
122 .and_then(|v| v.as_str())
123 .ok_or_else(|| AttestationError::Verification("Missing predicateType".into()))?;
124
125 if !predicate_type.starts_with("https://slsa.dev/provenance/") {
126 return Err(AttestationError::Verification(format!(
127 "Not a SLSA provenance statement: {}",
128 predicate_type
129 )));
130 }
131
132 let predicate = statement
134 .get("predicate")
135 .ok_or_else(|| AttestationError::Verification("Missing predicate".into()))?;
136
137 let workflow_ref = extract_workflow_ref(predicate)?;
138
139 Ok(SlsaProvenance {
140 predicate_type: predicate_type.to_string(),
141 workflow_ref,
142 })
143}
144
145fn extract_workflow_ref(predicate: &Value) -> Result<Option<String>> {
147 if let Some(build_def) = predicate.get("buildDefinition") {
149 if let Some(ext_params) = build_def.get("externalParameters") {
150 if let Some(workflow) = ext_params.get("workflow") {
151 if let Some(path) = workflow.get("path") {
152 if let Some(path_str) = path.as_str() {
153 return Ok(Some(path_str.to_string()));
154 }
155 }
156 }
157 }
158 }
159
160 if let Some(invocation) = predicate.get("invocation") {
162 if let Some(config_source) = invocation.get("configSource") {
163 if let Some(path) = config_source.get("entryPoint") {
164 if let Some(path_str) = path.as_str() {
165 return Ok(Some(path_str.to_string()));
166 }
167 }
168 }
169 }
170
171 Ok(None)
172}
173
174#[derive(Debug)]
175pub struct ParsedBundle {
176 pub payload: Vec<u8>,
177 pub dsse_envelope: Option<DsseEnvelope>,
178 pub certificate: Option<String>,
179 pub media_type: String,
180 pub tlog_entries: Option<Vec<serde_json::Value>>,
181}
182
183#[derive(Debug, Clone)]
184pub struct SlsaProvenance {
185 pub predicate_type: String,
186 pub workflow_ref: Option<String>,
187}