Skip to main content

kipuka/config/
star.rs

1//! STAR certificate configuration (RFC 8739).
2//!
3//! The `[star]` section enables Short-Term Automatic Renewal certificates.
4//! When enabled, clients can request STAR orders that automatically renew
5//! short-lived certificates without client interaction.  This is the
6//! server-side answer to the CA/B Forum 47-day validity mandate (March 2029).
7
8use serde::Deserialize;
9
10/// `[star]` section — Short-Term Automatic Renewal certificates.
11///
12/// ```toml
13/// [star]
14/// enabled = true
15/// min_renewal_interval_secs = 3600
16/// max_renewal_interval_secs = 604800
17/// default_renewal_interval_secs = 86400
18/// max_lifetime_days = 365
19/// max_active_orders = 10000
20/// pre_renewal_factor = 0.5
21/// ```
22#[derive(Debug, Clone, Deserialize)]
23#[serde(deny_unknown_fields)]
24pub struct StarConfig {
25    /// Enable STAR certificate support.
26    #[serde(default)]
27    pub enabled: bool,
28
29    /// Minimum renewal interval in seconds (floor: 3600 = 1 hour).
30    #[serde(default = "default_min_renewal_interval_secs")]
31    pub min_renewal_interval_secs: u64,
32
33    /// Maximum renewal interval in seconds (ceiling: 604800 = 7 days).
34    #[serde(default = "default_max_renewal_interval_secs")]
35    pub max_renewal_interval_secs: u64,
36
37    /// Default renewal interval when the client does not specify one.
38    #[serde(default = "default_default_renewal_interval_secs")]
39    pub default_renewal_interval_secs: u64,
40
41    /// Maximum total STAR order lifetime in days.
42    #[serde(default = "default_max_lifetime_days")]
43    pub max_lifetime_days: u32,
44
45    /// Maximum number of active STAR orders (resource exhaustion guard).
46    #[serde(default = "default_max_active_orders")]
47    pub max_active_orders: usize,
48
49    /// Renew when this fraction of the interval remains (0.1–0.9).
50    #[serde(default = "default_pre_renewal_factor")]
51    pub pre_renewal_factor: f64,
52}
53
54fn default_min_renewal_interval_secs() -> u64 {
55    3600
56}
57
58fn default_max_renewal_interval_secs() -> u64 {
59    604800
60}
61
62fn default_default_renewal_interval_secs() -> u64 {
63    86400
64}
65
66fn default_max_lifetime_days() -> u32 {
67    365
68}
69
70fn default_max_active_orders() -> usize {
71    10000
72}
73
74fn default_pre_renewal_factor() -> f64 {
75    0.5
76}
77
78impl Default for StarConfig {
79    fn default() -> Self {
80        Self {
81            enabled: false,
82            min_renewal_interval_secs: default_min_renewal_interval_secs(),
83            max_renewal_interval_secs: default_max_renewal_interval_secs(),
84            default_renewal_interval_secs: default_default_renewal_interval_secs(),
85            max_lifetime_days: default_max_lifetime_days(),
86            max_active_orders: default_max_active_orders(),
87            pre_renewal_factor: default_pre_renewal_factor(),
88        }
89    }
90}
91
92impl StarConfig {
93    /// Validate STAR configuration constraints.
94    pub fn validate(&self) -> Result<(), String> {
95        if self.min_renewal_interval_secs < 3600 {
96            return Err(format!(
97                "[star].min_renewal_interval_secs must be >= 3600 (1 hour), got {}",
98                self.min_renewal_interval_secs
99            ));
100        }
101
102        if self.max_renewal_interval_secs > 604800 {
103            return Err(format!(
104                "[star].max_renewal_interval_secs must be <= 604800 (7 days), got {}",
105                self.max_renewal_interval_secs
106            ));
107        }
108
109        if self.min_renewal_interval_secs > self.max_renewal_interval_secs {
110            return Err(format!(
111                "[star].min_renewal_interval_secs ({}) must be <= max_renewal_interval_secs ({})",
112                self.min_renewal_interval_secs, self.max_renewal_interval_secs
113            ));
114        }
115
116        if self.default_renewal_interval_secs < self.min_renewal_interval_secs
117            || self.default_renewal_interval_secs > self.max_renewal_interval_secs
118        {
119            return Err(format!(
120                "[star].default_renewal_interval_secs ({}) must be between \
121                 min_renewal_interval_secs ({}) and max_renewal_interval_secs ({})",
122                self.default_renewal_interval_secs,
123                self.min_renewal_interval_secs,
124                self.max_renewal_interval_secs
125            ));
126        }
127
128        if self.max_lifetime_days < 1 {
129            return Err("[star].max_lifetime_days must be >= 1".into());
130        }
131
132        if self.max_active_orders < 1 {
133            return Err("[star].max_active_orders must be >= 1".into());
134        }
135
136        if !(0.1..=0.9).contains(&self.pre_renewal_factor) {
137            return Err(format!(
138                "[star].pre_renewal_factor must be between 0.1 and 0.9, got {}",
139                self.pre_renewal_factor
140            ));
141        }
142
143        Ok(())
144    }
145}