Skip to main content

hoike_sign/
source.rs

1use std::collections::BTreeMap;
2use x509_cert::ext::pkix::CrlReason;
3
4use crate::error::Result;
5
6pub type Epoch = u64;
7pub type SerialBytes = Vec<u8>;
8
9#[derive(Debug, Clone)]
10pub enum CertificateStatus {
11    Good,
12    Revoked {
13        revocation_time: u64,
14        reason: Option<CrlReason>,
15    },
16}
17
18#[derive(Debug, Clone, Default)]
19pub struct StatusSnapshot {
20    pub entries: BTreeMap<SerialBytes, CertificateStatus>,
21    pub this_update: u64,
22    pub next_update: Option<u64>,
23    /// Optional per-serial certificate expiry (`notAfter`, seconds since epoch).
24    /// Only sources that carry certificate validity — the 389 DS syncrepl
25    /// source — populate this; CRL sources leave it empty. Consumed by
26    /// `archive_cutoff_secs` pruning, which never drops a serial absent here.
27    pub not_after: BTreeMap<SerialBytes, u64>,
28}
29
30#[derive(Debug, Clone)]
31pub struct StatusChange {
32    pub serial: SerialBytes,
33    pub status: CertificateStatus,
34    pub timestamp: u64,
35}
36
37#[derive(Debug, Clone)]
38pub struct CaIdentity {
39    pub label: String,
40    pub issuer_name_der: Vec<u8>,
41    pub issuer_key_bytes: Vec<u8>,
42}
43
44pub trait RevocationSource: Send + Sync {
45    fn snapshot(&self, ca: &CaIdentity) -> Result<StatusSnapshot>;
46    fn changes_since(&self, ca: &CaIdentity, since: Epoch) -> Result<Vec<StatusChange>>;
47    fn supports_streaming(&self) -> bool;
48    /// True only when the source has established complete positive issuance.
49    fn is_authoritative_complete(&self) -> bool {
50        false
51    }
52}
53
54impl StatusSnapshot {
55    /// Validate source evidence at a supplied clock instant, returning its hard expiry.
56    pub fn validate_at(&self, now: u64) -> Result<u64> {
57        let end = self
58            .next_update
59            .ok_or_else(|| crate::error::SignError::Config("source has no nextUpdate".into()))?;
60        if self.this_update > now || end <= now || end <= self.this_update {
61            return Err(crate::error::SignError::Config(
62                "source validity is expired, future-dated or inconsistent".into(),
63            ));
64        }
65        Ok(end)
66    }
67}
68
69pub fn unix_now() -> Result<u64> {
70    std::time::SystemTime::now()
71        .duration_since(std::time::UNIX_EPOCH)
72        .map(|v| v.as_secs())
73        .map_err(|_| crate::error::SignError::Config("clock is before Unix epoch".into()))
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    #[test]
80    fn freshness_boundaries() {
81        let mut s = StatusSnapshot {
82            entries: BTreeMap::new(),
83            this_update: 10,
84            next_update: Some(20),
85            ..Default::default()
86        };
87        assert_eq!(s.validate_at(10).unwrap(), 20);
88        assert!(s.validate_at(9).is_err());
89        assert!(s.validate_at(20).is_err());
90        s.next_update = None;
91        assert!(s.validate_at(11).is_err());
92    }
93}