1use crate::checkpoint::Checkpoint;
8use crate::dsse::DsseEnvelope;
9use crate::encoding::{
10 string_i64, CanonicalizedBody, DerCertificate, LogIndex, LogKeyId, Sha256Hash, SignatureBytes,
11 SignedTimestamp, TimestampToken,
12};
13use crate::error::{Error, Result};
14use crate::hash::HashAlgorithm;
15use serde::{Deserialize, Deserializer, Serialize};
16use std::str::FromStr;
17
18fn deserialize_null_as_default<'de, D, T>(deserializer: D) -> std::result::Result<T, D::Error>
20where
21 D: Deserializer<'de>,
22 T: Default + Deserialize<'de>,
23{
24 let opt = Option::deserialize(deserializer)?;
25 Ok(opt.unwrap_or_default())
26}
27
28fn is_zero(value: &i64) -> bool {
30 *value == 0
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum MediaType {
36 Bundle0_1,
38 Bundle0_2,
40 Bundle0_3,
42}
43
44impl MediaType {
45 pub fn as_str(&self) -> &'static str {
47 match self {
48 MediaType::Bundle0_1 => "application/vnd.dev.sigstore.bundle+json;version=0.1",
49 MediaType::Bundle0_2 => "application/vnd.dev.sigstore.bundle+json;version=0.2",
50 MediaType::Bundle0_3 => "application/vnd.dev.sigstore.bundle.v0.3+json",
51 }
52 }
53}
54
55impl FromStr for MediaType {
56 type Err = Error;
57
58 fn from_str(s: &str) -> Result<Self> {
59 match s {
60 "application/vnd.dev.sigstore.bundle+json;version=0.1" => Ok(MediaType::Bundle0_1),
61 "application/vnd.dev.sigstore.bundle+json;version=0.2" => Ok(MediaType::Bundle0_2),
62 "application/vnd.dev.sigstore.bundle.v0.3+json" => Ok(MediaType::Bundle0_3),
63 "application/vnd.dev.sigstore.bundle+json;version=0.3" => Ok(MediaType::Bundle0_3),
65 _ => Err(Error::InvalidMediaType(s.to_string())),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
72pub enum BundleVersion {
73 #[serde(rename = "0.1")]
75 V0_1,
76 #[serde(rename = "0.2")]
78 V0_2,
79 #[serde(rename = "0.3")]
81 V0_3,
82}
83
84#[derive(Debug, Clone, PartialEq, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub struct Bundle {
88 pub media_type: String,
90 pub verification_material: VerificationMaterial,
92 #[serde(flatten)]
94 pub content: SignatureContent,
95}
96
97impl Bundle {
98 pub fn from_json(json: &str) -> Result<Self> {
100 serde_json::from_str(json).map_err(Error::Json)
101 }
102
103 pub fn to_json(&self) -> Result<String> {
105 serde_json::to_string(self).map_err(Error::Json)
106 }
107
108 pub fn to_json_pretty(&self) -> Result<String> {
110 serde_json::to_string_pretty(self).map_err(Error::Json)
111 }
112
113 pub fn version(&self) -> Result<MediaType> {
115 MediaType::from_str(&self.media_type)
116 }
117
118 pub fn signing_certificate(&self) -> Option<&DerCertificate> {
120 match &self.verification_material.content {
121 VerificationMaterialContent::Certificate(cert) => Some(&cert.raw_bytes),
122 VerificationMaterialContent::X509CertificateChain { certificates } => {
123 certificates.first().map(|c| &c.raw_bytes)
124 }
125 VerificationMaterialContent::PublicKey { .. } => None,
126 }
127 }
128
129 pub fn has_inclusion_proof(&self) -> bool {
131 self.verification_material
132 .tlog_entries
133 .iter()
134 .any(|e| e.inclusion_proof.is_some())
135 }
136
137 pub fn has_inclusion_promise(&self) -> bool {
139 self.verification_material
140 .tlog_entries
141 .iter()
142 .any(|e| e.inclusion_promise.is_some())
143 }
144}
145
146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub enum SignatureContent {
150 MessageSignature(MessageSignature),
152 DsseEnvelope(DsseEnvelope),
154}
155
156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
158#[serde(rename_all = "camelCase")]
159pub struct MessageSignature {
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub message_digest: Option<MessageDigest>,
163 pub signature: SignatureBytes,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct MessageDigest {
171 pub algorithm: HashAlgorithm,
173 pub digest: Sha256Hash,
175}
176
177#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct VerificationMaterial {
181 #[serde(flatten)]
183 pub content: VerificationMaterialContent,
184 #[serde(default)]
186 pub tlog_entries: Vec<TransparencyLogEntry>,
187 #[serde(default, deserialize_with = "deserialize_null_as_default")]
189 pub timestamp_verification_data: TimestampVerificationData,
190}
191
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub enum VerificationMaterialContent {
201 Certificate(CertificateContent),
203 X509CertificateChain {
205 certificates: Vec<X509Certificate>,
207 },
208 PublicKey {
210 hint: String,
212 },
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
217#[serde(rename_all = "camelCase")]
218pub struct CertificateContent {
219 pub raw_bytes: DerCertificate,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
225#[serde(rename_all = "camelCase")]
226pub struct X509Certificate {
227 pub raw_bytes: DerCertificate,
229}
230
231#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234pub struct TransparencyLogEntry {
235 pub log_index: LogIndex,
237 pub log_id: LogId,
239 pub kind_version: KindVersion,
241 #[serde(default, with = "string_i64", skip_serializing_if = "is_zero")]
244 pub integrated_time: i64,
245 #[serde(skip_serializing_if = "Option::is_none")]
247 pub inclusion_promise: Option<InclusionPromise>,
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub inclusion_proof: Option<InclusionProof>,
251 pub canonicalized_body: CanonicalizedBody,
253}
254
255#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258pub struct LogId {
259 pub key_id: LogKeyId,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct KindVersion {
267 pub kind: String,
269 pub version: String,
271}
272
273#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
275#[serde(rename_all = "camelCase")]
276pub struct InclusionPromise {
277 pub signed_entry_timestamp: SignedTimestamp,
279}
280
281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
283#[serde(rename_all = "camelCase")]
284pub struct InclusionProof {
285 pub log_index: LogIndex,
287 pub root_hash: Sha256Hash,
289 #[serde(with = "string_i64")]
291 pub tree_size: i64,
292 #[serde(with = "sha256_hash_vec")]
294 pub hashes: Vec<Sha256Hash>,
295 #[serde(default, skip_serializing_if = "CheckpointData::is_empty")]
297 pub checkpoint: CheckpointData,
298}
299
300mod sha256_hash_vec {
302 use super::Sha256Hash;
303 use serde::{Deserialize, Deserializer, Serialize, Serializer};
304
305 pub fn serialize<S>(hashes: &[Sha256Hash], serializer: S) -> Result<S::Ok, S::Error>
306 where
307 S: Serializer,
308 {
309 hashes.serialize(serializer)
311 }
312
313 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Sha256Hash>, D::Error>
314 where
315 D: Deserializer<'de>,
316 {
317 Vec::<Sha256Hash>::deserialize(deserializer)
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
323#[serde(rename_all = "camelCase")]
324pub struct CheckpointData {
325 #[serde(default)]
327 pub envelope: String,
328}
329
330impl CheckpointData {
331 pub fn parse(&self) -> Result<Checkpoint> {
333 Checkpoint::from_text(&self.envelope)
334 }
335
336 pub fn is_empty(&self) -> bool {
338 self.envelope.is_empty()
339 }
340}
341
342#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
344#[serde(rename_all = "camelCase")]
345pub struct TimestampVerificationData {
346 #[serde(default, skip_serializing_if = "Vec::is_empty")]
348 pub rfc3161_timestamps: Vec<Rfc3161Timestamp>,
349}
350
351#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
353#[serde(rename_all = "camelCase")]
354pub struct Rfc3161Timestamp {
355 pub signed_timestamp: TimestampToken,
357}
358
359fn default_media_type() -> String {
361 "application/vnd.dev.sigstore.bundle+json;version=0.1".to_string()
362}
363
364impl<'de> Deserialize<'de> for Bundle {
366 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
367 where
368 D: serde::Deserializer<'de>,
369 {
370 #[derive(Deserialize)]
371 #[serde(rename_all = "camelCase")]
372 struct BundleHelper {
373 #[serde(default = "default_media_type")]
375 media_type: String,
376 verification_material: VerificationMaterial,
377 #[serde(flatten)]
378 content: SignatureContent,
379 }
380
381 let helper = BundleHelper::deserialize(deserializer)?;
382
383 Ok(Bundle {
384 media_type: helper.media_type,
385 verification_material: helper.verification_material,
386 content: helper.content,
387 })
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394
395 #[test]
396 fn test_media_type_parsing() {
397 assert_eq!(
398 MediaType::from_str("application/vnd.dev.sigstore.bundle+json;version=0.1").unwrap(),
399 MediaType::Bundle0_1
400 );
401 assert_eq!(
402 MediaType::from_str("application/vnd.dev.sigstore.bundle+json;version=0.2").unwrap(),
403 MediaType::Bundle0_2
404 );
405 assert_eq!(
406 MediaType::from_str("application/vnd.dev.sigstore.bundle.v0.3+json").unwrap(),
407 MediaType::Bundle0_3
408 );
409 }
410
411 #[test]
412 fn test_media_type_invalid() {
413 assert!(MediaType::from_str("invalid").is_err());
414 }
415}