1use crate::{Error, Result};
4use chrono::{DateTime, Utc};
5use rustls_pki_types::CertificateDer;
6use serde::{Deserialize, Serialize};
7use sigstore_types::{DerCertificate, DerPublicKey, HashAlgorithm, KeyHint, LogId, LogKeyId};
8use std::collections::HashMap;
9
10pub type TsaCertWithValidity = (
12 CertificateDer<'static>,
13 Option<DateTime<Utc>>,
14 Option<DateTime<Utc>>,
15);
16
17#[derive(Debug, Clone, Deserialize, Serialize)]
19#[serde(rename_all = "camelCase")]
20pub struct TrustedRoot {
21 pub media_type: String,
23
24 #[serde(default)]
26 pub tlogs: Vec<TransparencyLog>,
27
28 #[serde(default)]
30 pub certificate_authorities: Vec<CertificateAuthority>,
31
32 #[serde(default)]
34 pub ctlogs: Vec<CertificateTransparencyLog>,
35
36 #[serde(default)]
38 pub timestamp_authorities: Vec<TimestampAuthority>,
39}
40
41#[derive(Debug, Clone, Deserialize, Serialize)]
43#[serde(rename_all = "camelCase")]
44pub struct TransparencyLog {
45 pub base_url: String,
47
48 pub hash_algorithm: HashAlgorithm,
50
51 pub public_key: PublicKey,
53
54 pub log_id: LogId,
56}
57
58#[derive(Debug, Clone, Deserialize, Serialize)]
60#[serde(rename_all = "camelCase")]
61pub struct CertificateAuthority {
62 #[serde(default)]
64 pub subject: CertificateSubject,
65
66 pub uri: String,
68
69 pub cert_chain: CertChain,
71
72 #[serde(default)]
74 pub valid_for: Option<ValidityPeriod>,
75}
76
77#[derive(Debug, Clone, Deserialize, Serialize)]
79#[serde(rename_all = "camelCase")]
80pub struct CertificateTransparencyLog {
81 pub base_url: String,
83
84 pub hash_algorithm: HashAlgorithm,
86
87 pub public_key: PublicKey,
89
90 pub log_id: LogId,
92}
93
94#[derive(Debug, Clone, Deserialize, Serialize)]
96#[serde(rename_all = "camelCase")]
97pub struct TimestampAuthority {
98 #[serde(default)]
100 pub subject: CertificateSubject,
101
102 #[serde(default)]
104 pub uri: Option<String>,
105
106 pub cert_chain: CertChain,
108
109 #[serde(default)]
111 pub valid_for: Option<ValidityPeriod>,
112}
113
114#[derive(Debug, Clone, Deserialize, Serialize)]
116#[serde(rename_all = "camelCase")]
117pub struct PublicKey {
118 pub raw_bytes: DerPublicKey,
120
121 pub key_details: String,
123
124 #[serde(default)]
126 pub valid_for: Option<ValidityPeriod>,
127}
128
129#[derive(Debug, Clone, Default, Deserialize, Serialize)]
134#[serde(rename_all = "camelCase")]
135pub struct CertificateSubject {
136 #[serde(default)]
138 pub organization: Option<String>,
139
140 #[serde(default)]
142 pub common_name: Option<String>,
143}
144
145#[derive(Debug, Clone, Deserialize, Serialize)]
147#[serde(rename_all = "camelCase")]
148pub struct CertChain {
149 pub certificates: Vec<CertificateEntry>,
151}
152
153#[derive(Debug, Clone, Deserialize, Serialize)]
155#[serde(rename_all = "camelCase")]
156pub struct CertificateEntry {
157 pub raw_bytes: DerCertificate,
159}
160
161#[derive(Debug, Clone, Deserialize, Serialize)]
163#[serde(rename_all = "camelCase")]
164pub struct ValidityPeriod {
165 #[serde(default)]
167 pub start: Option<String>,
168
169 #[serde(default)]
171 pub end: Option<String>,
172}
173
174impl TrustedRoot {
175 pub fn from_json(json: &str) -> Result<Self> {
177 Ok(serde_json::from_str(json)?)
178 }
179
180 pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self> {
182 let json =
183 std::fs::read_to_string(path).map_err(|e| Error::Json(serde_json::Error::io(e)))?;
184 Self::from_json(&json)
185 }
186
187 pub fn fulcio_certs(&self) -> Result<Vec<CertificateDer<'static>>> {
189 let mut certs = Vec::new();
190 for ca in &self.certificate_authorities {
191 for cert_entry in &ca.cert_chain.certificates {
192 certs.push(CertificateDer::from(cert_entry.raw_bytes.as_bytes()).into_owned());
193 }
194 }
195 Ok(certs)
196 }
197
198 pub fn rekor_keys(&self) -> Result<HashMap<String, Vec<u8>>> {
200 let mut keys = HashMap::new();
201 for tlog in &self.tlogs {
202 keys.insert(
203 tlog.log_id.key_id.to_string(),
204 tlog.public_key.raw_bytes.as_bytes().to_vec(),
205 );
206 }
207 Ok(keys)
208 }
209
210 pub fn rekor_keys_with_hints(&self) -> Result<Vec<(KeyHint, DerPublicKey)>> {
215 let mut keys = Vec::new();
216 for tlog in &self.tlogs {
217 let key_id_bytes = tlog.log_id.key_id.decode()?;
219
220 if key_id_bytes.len() >= 4 {
221 let key_hint = KeyHint::new([
222 key_id_bytes[0],
223 key_id_bytes[1],
224 key_id_bytes[2],
225 key_id_bytes[3],
226 ]);
227 keys.push((key_hint, tlog.public_key.raw_bytes.clone()));
228 }
229 }
230 Ok(keys)
231 }
232
233 pub fn rekor_key_for_log(&self, log_id: &LogKeyId) -> Result<DerPublicKey> {
235 for tlog in &self.tlogs {
236 if &tlog.log_id.key_id == log_id {
237 return Ok(tlog.public_key.raw_bytes.clone());
238 }
239 }
240 Err(Error::KeyNotFound(log_id.to_string()))
241 }
242
243 pub fn ctfe_keys(&self) -> Result<HashMap<LogKeyId, DerPublicKey>> {
245 let mut keys = HashMap::new();
246 for ctlog in &self.ctlogs {
247 keys.insert(
248 ctlog.log_id.key_id.clone(),
249 ctlog.public_key.raw_bytes.clone(),
250 );
251 }
252 Ok(keys)
253 }
254
255 pub fn ctfe_keys_with_ids(&self) -> Result<Vec<(Vec<u8>, DerPublicKey)>> {
259 let mut result = Vec::new();
260 for ctlog in &self.ctlogs {
261 let key_bytes = ctlog.public_key.raw_bytes.as_bytes();
262 let log_id = sigstore_crypto::sha256(key_bytes).as_bytes().to_vec();
264 result.push((log_id, ctlog.public_key.raw_bytes.clone()));
265 }
266 Ok(result)
267 }
268
269 pub fn tsa_certs_with_validity(&self) -> Result<Vec<TsaCertWithValidity>> {
271 let mut result = Vec::new();
272
273 for tsa in &self.timestamp_authorities {
274 for cert_entry in &tsa.cert_chain.certificates {
275 let cert_der = cert_entry.raw_bytes.as_bytes().to_vec();
276
277 let (start, end) = if let Some(valid_for) = &tsa.valid_for {
279 let start = valid_for
280 .start
281 .as_ref()
282 .and_then(|s| DateTime::parse_from_rfc3339(s).ok())
283 .map(|dt| dt.with_timezone(&Utc));
284 let end = valid_for
285 .end
286 .as_ref()
287 .and_then(|s| DateTime::parse_from_rfc3339(s).ok())
288 .map(|dt| dt.with_timezone(&Utc));
289 (start, end)
290 } else {
291 (None, None)
292 };
293
294 result.push((CertificateDer::from(&cert_der[..]).into_owned(), start, end));
295 }
296 }
297
298 Ok(result)
299 }
300
301 pub fn tsa_root_certs(&self) -> Result<Vec<CertificateDer<'static>>> {
303 let mut roots = Vec::new();
304 for tsa in &self.timestamp_authorities {
305 if let Some(cert_entry) = tsa.cert_chain.certificates.last() {
307 roots.push(CertificateDer::from(cert_entry.raw_bytes.as_bytes()).into_owned());
308 }
309 }
310 Ok(roots)
311 }
312
313 pub fn tsa_intermediate_certs(&self) -> Result<Vec<CertificateDer<'static>>> {
315 let mut intermediates = Vec::new();
316 for tsa in &self.timestamp_authorities {
317 let chain_len = tsa.cert_chain.certificates.len();
319 if chain_len > 2 {
320 for cert_entry in &tsa.cert_chain.certificates[1..chain_len - 1] {
321 intermediates
322 .push(CertificateDer::from(cert_entry.raw_bytes.as_bytes()).into_owned());
323 }
324 }
325 }
326 Ok(intermediates)
327 }
328
329 pub fn tsa_leaf_certs(&self) -> Result<Vec<CertificateDer<'static>>> {
332 let mut leaves = Vec::new();
333 for tsa in &self.timestamp_authorities {
334 if let Some(cert_entry) = tsa.cert_chain.certificates.first() {
336 leaves.push(CertificateDer::from(cert_entry.raw_bytes.as_bytes()).into_owned());
337 }
338 }
339 Ok(leaves)
340 }
341
342 pub fn has_rekor_key(&self, key_id: &LogKeyId) -> bool {
344 self.tlogs.iter().any(|tlog| &tlog.log_id.key_id == key_id)
345 }
346
347 pub fn tsa_validity_for_time(
349 &self,
350 timestamp: DateTime<Utc>,
351 ) -> Result<Option<(DateTime<Utc>, DateTime<Utc>)>> {
352 for tsa in &self.timestamp_authorities {
353 if let Some(valid_for) = &tsa.valid_for {
354 let start = valid_for
355 .start
356 .as_ref()
357 .and_then(|s| DateTime::parse_from_rfc3339(s).ok())
358 .map(|dt| dt.with_timezone(&Utc));
359 let end = valid_for
360 .end
361 .as_ref()
362 .and_then(|s| DateTime::parse_from_rfc3339(s).ok())
363 .map(|dt| dt.with_timezone(&Utc));
364
365 if let (Some(start_time), Some(end_time)) = (start, end) {
367 if timestamp >= start_time && timestamp <= end_time {
368 return Ok(Some((start_time, end_time)));
369 }
370 } else if let Some(start_time) = start {
371 if timestamp >= start_time {
373 return Ok(start.zip(end));
374 }
375 }
376 }
377 }
378 Ok(None)
379 }
380
381 pub fn is_timestamp_within_tsa_validity(&self, timestamp: DateTime<Utc>) -> bool {
391 if self.timestamp_authorities.is_empty() {
393 return true;
394 }
395
396 for tsa in &self.timestamp_authorities {
397 let Some(valid_for) = &tsa.valid_for else {
399 return true;
400 };
401
402 let start = valid_for
403 .start
404 .as_ref()
405 .and_then(|s| DateTime::parse_from_rfc3339(s).ok())
406 .map(|dt| dt.with_timezone(&Utc));
407 let end = valid_for
408 .end
409 .as_ref()
410 .and_then(|s| DateTime::parse_from_rfc3339(s).ok())
411 .map(|dt| dt.with_timezone(&Utc));
412
413 let after_start = start.map_or(true, |s| timestamp >= s);
415 let before_end = end.map_or(true, |e| timestamp <= e);
416
417 if after_start && before_end {
418 return true;
419 }
420 }
421
422 false
424 }
425}
426
427pub const SIGSTORE_PRODUCTION_TRUSTED_ROOT: &str = include_str!("trusted_root.json");
430
431pub const SIGSTORE_STAGING_TRUSTED_ROOT: &str = include_str!("trusted_root_staging.json");
434
435impl TrustedRoot {
436 pub fn production() -> Result<Self> {
438 Self::from_json(SIGSTORE_PRODUCTION_TRUSTED_ROOT)
439 }
440
441 pub fn staging() -> Result<Self> {
446 Self::from_json(SIGSTORE_STAGING_TRUSTED_ROOT)
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453
454 const SAMPLE_TRUSTED_ROOT: &str = r#"{
455 "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1",
456 "tlogs": [{
457 "baseUrl": "https://rekor.sigstore.dev",
458 "hashAlgorithm": "SHA2_256",
459 "publicKey": {
460 "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYI4heOTrNrZO27elFE8ynfrdPMikttRkbe+vJKQ50G6bfwQ3WyhLpRwwwohelDAm8xRzJ56nYsIa3VHivVvpmA==",
461 "keyDetails": "PKIX_ECDSA_P256_SHA_256"
462 },
463 "logId": {
464 "keyId": "test-key-id"
465 }
466 }],
467 "certificateAuthorities": [],
468 "ctlogs": [],
469 "timestampAuthorities": []
470 }"#;
471
472 #[test]
473 fn test_parse_trusted_root() {
474 let root = TrustedRoot::from_json(SAMPLE_TRUSTED_ROOT).unwrap();
475 assert_eq!(root.tlogs.len(), 1);
476 assert_eq!(
477 root.tlogs[0].log_id.key_id,
478 LogKeyId::new("test-key-id".to_string())
479 );
480 }
481
482 #[test]
483 fn test_rekor_keys() {
484 let root = TrustedRoot::from_json(SAMPLE_TRUSTED_ROOT).unwrap();
485 let keys = root.rekor_keys().unwrap();
486 assert_eq!(keys.len(), 1);
487 assert!(keys.contains_key("test-key-id"));
488 }
489
490 #[test]
491 fn test_has_rekor_key() {
492 let root = TrustedRoot::from_json(SAMPLE_TRUSTED_ROOT).unwrap();
493 assert!(root.has_rekor_key(&LogKeyId::new("test-key-id".to_string())));
494 assert!(!root.has_rekor_key(&LogKeyId::new("non-existent".to_string())));
495 }
496
497 #[test]
498 fn test_production_trusted_root() {
499 let root = TrustedRoot::production().unwrap();
500 assert!(!root.tlogs.is_empty());
501 assert!(!root.certificate_authorities.is_empty());
502 assert!(!root.ctlogs.is_empty());
503 }
504
505 #[test]
506 fn test_staging_trusted_root() {
507 let root = TrustedRoot::staging().unwrap();
508 assert!(!root.tlogs.is_empty());
509 assert!(!root.certificate_authorities.is_empty());
510 assert!(!root.ctlogs.is_empty());
511 assert!(root.tlogs[0].base_url.contains("sigstage.dev"));
513 }
514}