1use sigstore_rekor::entry::LogEntry;
4use sigstore_types::{
5 bundle::{
6 CheckpointData, InclusionPromise, InclusionProof, KindVersion, LogId, MessageSignature,
7 Rfc3161Timestamp, SignatureContent, TimestampVerificationData, TransparencyLogEntry,
8 VerificationMaterial, VerificationMaterialContent,
9 },
10 Bundle, CanonicalizedBody, DerCertificate, DsseEnvelope, LogKeyId, MediaType, Sha256Hash,
11 SignatureBytes, SignedTimestamp, TimestampToken,
12};
13
14pub struct BundleBuilder {
16 version: MediaType,
18 verification_content: Option<VerificationMaterialContent>,
20 tlog_entries: Vec<TransparencyLogEntry>,
22 rfc3161_timestamps: Vec<Rfc3161Timestamp>,
24 signature_content: Option<SignatureContent>,
26}
27
28impl BundleBuilder {
29 pub fn new() -> Self {
31 Self {
32 version: MediaType::Bundle0_3,
33 verification_content: None,
34 tlog_entries: Vec::new(),
35 rfc3161_timestamps: Vec::new(),
36 signature_content: None,
37 }
38 }
39
40 pub fn version(mut self, version: MediaType) -> Self {
42 self.version = version;
43 self
44 }
45
46 pub fn certificate(mut self, cert_der: Vec<u8>) -> Self {
48 self.verification_content = Some(VerificationMaterialContent::Certificate(
49 sigstore_types::bundle::CertificateContent {
50 raw_bytes: DerCertificate::new(cert_der),
51 },
52 ));
53 self
54 }
55
56 pub fn certificate_base64(mut self, cert_b64: &str) -> Result<Self, &'static str> {
58 let cert_der = DerCertificate::from_base64(cert_b64).map_err(|_| "invalid base64")?;
59 self.verification_content = Some(VerificationMaterialContent::Certificate(
60 sigstore_types::bundle::CertificateContent {
61 raw_bytes: cert_der,
62 },
63 ));
64 Ok(self)
65 }
66
67 pub fn certificate_chain(mut self, certs_der: Vec<Vec<u8>>) -> Self {
69 self.verification_content = Some(VerificationMaterialContent::X509CertificateChain {
70 certificates: certs_der
71 .into_iter()
72 .map(|c| sigstore_types::bundle::X509Certificate {
73 raw_bytes: DerCertificate::new(c),
74 })
75 .collect(),
76 });
77 self
78 }
79
80 pub fn public_key(mut self, hint: String) -> Self {
82 self.verification_content = Some(VerificationMaterialContent::PublicKey { hint });
83 self
84 }
85
86 pub fn add_tlog_entry(mut self, entry: TransparencyLogEntry) -> Self {
88 self.tlog_entries.push(entry);
89 self
90 }
91
92 pub fn add_rfc3161_timestamp(mut self, signed_timestamp: Vec<u8>) -> Self {
94 self.rfc3161_timestamps.push(Rfc3161Timestamp {
95 signed_timestamp: TimestampToken::new(signed_timestamp),
96 });
97 self
98 }
99
100 pub fn message_signature(mut self, signature: Vec<u8>) -> Self {
102 self.signature_content = Some(SignatureContent::MessageSignature(MessageSignature {
103 message_digest: None,
104 signature: SignatureBytes::new(signature),
105 }));
106 self
107 }
108
109 pub fn message_signature_with_digest(
111 mut self,
112 signature: Vec<u8>,
113 digest: Sha256Hash,
114 algorithm: sigstore_types::HashAlgorithm,
115 ) -> Self {
116 self.signature_content = Some(SignatureContent::MessageSignature(MessageSignature {
117 message_digest: Some(sigstore_types::bundle::MessageDigest { algorithm, digest }),
118 signature: SignatureBytes::new(signature),
119 }));
120 self
121 }
122
123 pub fn dsse_envelope(mut self, envelope: DsseEnvelope) -> Self {
125 self.signature_content = Some(SignatureContent::DsseEnvelope(envelope));
126 self
127 }
128
129 pub fn build(self) -> Result<Bundle, &'static str> {
131 let verification_content = self
132 .verification_content
133 .ok_or("verification material not set")?;
134
135 let signature_content = self.signature_content.ok_or("signature content not set")?;
136
137 Ok(Bundle {
138 media_type: self.version.as_str().to_string(),
139 verification_material: VerificationMaterial {
140 content: verification_content,
141 tlog_entries: self.tlog_entries,
142 timestamp_verification_data: TimestampVerificationData {
143 rfc3161_timestamps: self.rfc3161_timestamps,
144 },
145 },
146 content: signature_content,
147 })
148 }
149}
150
151impl Default for BundleBuilder {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157pub struct TlogEntryBuilder {
159 log_index: u64,
160 log_id: String,
161 kind: String,
162 kind_version: String,
163 integrated_time: u64,
164 canonicalized_body: Vec<u8>,
165 inclusion_promise: Option<InclusionPromise>,
166 inclusion_proof: Option<InclusionProof>,
167}
168
169impl TlogEntryBuilder {
170 pub fn new() -> Self {
172 Self {
173 log_index: 0,
174 log_id: String::new(),
175 kind: "hashedrekord".to_string(),
176 kind_version: "0.0.1".to_string(),
177 integrated_time: 0,
178 canonicalized_body: Vec::new(),
179 inclusion_promise: None,
180 inclusion_proof: None,
181 }
182 }
183
184 pub fn from_log_entry(entry: &LogEntry, kind: &str, version: &str) -> Self {
194 let log_id_base64 = entry
196 .log_id
197 .to_base64()
198 .unwrap_or_else(|_| entry.log_id.to_string());
199
200 let mut builder = Self {
201 log_index: entry.log_index as u64,
202 log_id: log_id_base64,
203 kind: kind.to_string(),
204 kind_version: version.to_string(),
205 integrated_time: entry.integrated_time as u64,
206 canonicalized_body: entry.body.as_bytes().to_vec(),
207 inclusion_promise: None,
208 inclusion_proof: None,
209 };
210
211 if let Some(verification) = &entry.verification {
213 if let Some(set) = &verification.signed_entry_timestamp {
214 builder.inclusion_promise = Some(InclusionPromise {
215 signed_entry_timestamp: set.clone(),
216 });
217 }
218
219 if let Some(proof) = &verification.inclusion_proof {
220 let root_hash = Sha256Hash::from_hex(&proof.root_hash)
223 .unwrap_or_else(|_| Sha256Hash::from_bytes([0u8; 32]));
224
225 let hashes: Vec<Sha256Hash> = proof
227 .hashes
228 .iter()
229 .filter_map(|h| Sha256Hash::from_hex(h).ok())
230 .collect();
231
232 builder.inclusion_proof = Some(InclusionProof {
233 log_index: proof.log_index.to_string().into(),
234 root_hash,
235 tree_size: proof.tree_size.to_string(),
236 hashes,
237 checkpoint: CheckpointData {
238 envelope: proof.checkpoint.clone(),
239 },
240 });
241 }
242 }
243
244 builder
245 }
246
247 pub fn log_index(mut self, index: u64) -> Self {
249 self.log_index = index;
250 self
251 }
252
253 pub fn integrated_time(mut self, time: u64) -> Self {
255 self.integrated_time = time;
256 self
257 }
258
259 pub fn inclusion_promise(mut self, signed_entry_timestamp: SignedTimestamp) -> Self {
261 self.inclusion_promise = Some(InclusionPromise {
262 signed_entry_timestamp,
263 });
264 self
265 }
266
267 pub fn inclusion_proof(
276 mut self,
277 log_index: u64,
278 root_hash: Sha256Hash,
279 tree_size: u64,
280 hashes: Vec<Sha256Hash>,
281 checkpoint: String,
282 ) -> Self {
283 self.inclusion_proof = Some(InclusionProof {
284 log_index: log_index.to_string().into(),
285 root_hash,
286 tree_size: tree_size.to_string(),
287 hashes,
288 checkpoint: CheckpointData {
289 envelope: checkpoint,
290 },
291 });
292 self
293 }
294
295 pub fn build(self) -> TransparencyLogEntry {
297 TransparencyLogEntry {
298 log_index: self.log_index.to_string().into(),
299 log_id: LogId {
300 key_id: LogKeyId::new(self.log_id),
301 },
302 kind_version: KindVersion {
303 kind: self.kind,
304 version: self.kind_version,
305 },
306 integrated_time: self.integrated_time.to_string(),
307 inclusion_promise: self.inclusion_promise,
308 inclusion_proof: self.inclusion_proof,
309 canonicalized_body: CanonicalizedBody::new(self.canonicalized_body),
310 }
311 }
312}
313
314impl Default for TlogEntryBuilder {
315 fn default() -> Self {
316 Self::new()
317 }
318}