Skip to main content

kipuka/config/
est.rs

1//! EST protocol configuration.
2//!
3//! The `[est]` section controls which EST operations are enabled globally,
4//! and `[[est.label]]` entries define per-label enrollment profiles with
5//! CA routing and authentication policies.
6//!
7//! # EST labels (RFC 7030 §3.2.2)
8//!
9//! EST labels provide a namespace mechanism for multiple enrollment profiles
10//! under the same server.  Each label maps to a URL path segment:
11//!
12//! ```text
13//! https://est.example.com/.well-known/est/{label}/simpleenroll
14//! ```
15//!
16//! When no label is specified in the URL, the default label configuration
17//! applies.
18
19use serde::Deserialize;
20
21/// CSR template mode configuration (draft-ietf-lamps-rfc7030-csrattrs / RFC 9908).
22///
23/// When configured, the `/csrattrs` response includes a
24/// `CertificationRequestInfoTemplate` attribute alongside the OID list,
25/// guiding clients on expected subject DN, key algorithm, and extensions.
26///
27/// The template is backward-compatible: clients that do not understand
28/// the template attribute will ignore it and process only the OID list.
29///
30/// ```toml
31/// [est.csr_template]
32/// key_algorithm = "ec:P-256"
33/// required_extensions = ["2.5.29.17"]
34///
35/// [[est.csr_template.subject]]
36/// oid = "2.5.4.10"
37/// value = "Example Corp"
38///
39/// [[est.csr_template.subject]]
40/// oid = "2.5.4.3"
41/// ```
42#[derive(Debug, Clone, Deserialize)]
43#[serde(deny_unknown_fields)]
44pub struct CsrTemplate {
45    /// Required subject DN components.
46    ///
47    /// Each entry specifies an OID and an optional value. When `value` is
48    /// `None`, the client must supply its own value for that RDN component.
49    #[serde(default)]
50    pub subject: Vec<CsrTemplateRdn>,
51
52    /// Required key algorithm constraint.
53    ///
54    /// Format: `"ec:P-256"`, `"ec:P-384"`, `"rsa:2048"`, `"rsa:4096"`.
55    /// When set, encodes a `SubjectPublicKeyInfoTemplate` in the template.
56    #[serde(default)]
57    pub key_algorithm: Option<String>,
58
59    /// Required X.509 extension OIDs (client fills values).
60    ///
61    /// Each entry is a dotted-decimal OID string (e.g., `"2.5.29.17"`
62    /// for subjectAltName).
63    #[serde(default)]
64    pub required_extensions: Vec<String>,
65}
66
67impl CsrTemplate {
68    /// Returns `true` when at least one template field carries content.
69    pub fn has_content(&self) -> bool {
70        !self.subject.is_empty()
71            || self.key_algorithm.is_some()
72            || !self.required_extensions.is_empty()
73    }
74}
75
76/// A single RDN component in the CSR template subject.
77#[derive(Debug, Clone, Deserialize)]
78#[serde(deny_unknown_fields)]
79pub struct CsrTemplateRdn {
80    /// OID of the attribute type (dotted-decimal, e.g., `"2.5.4.3"` for CN).
81    pub oid: String,
82    /// Pre-filled value. When `None`, the client must provide this field.
83    pub value: Option<String>,
84}
85
86/// Authentication method for EST enrollment requests.
87///
88/// RFC 7030 §3.2.3 defines several client authentication mechanisms.
89/// Each EST label can require a specific method or accept multiple.
90#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
91#[serde(rename_all = "lowercase")]
92pub enum EstAuthMethod {
93    /// CMS message authentication for the custom CMS endpoints.
94    Cms,
95    /// GSSAPI over TLS with channel binding.
96    Gssapi,
97    /// mTLS client certificate authentication (RFC 7030 §3.3.2).
98    Mtls,
99    /// HTTP Basic authentication with OTP (RHELBU-3536 R7).
100    Otp,
101    /// HTTP Basic authentication with static credentials.
102    Basic,
103    /// Certificate-based re-enrollment (existing certificate proves identity).
104    Certificate,
105}
106
107/// `[est]` section — global EST protocol settings.
108#[derive(Debug, Clone, Deserialize)]
109#[serde(deny_unknown_fields)]
110pub struct EstConfig {
111    /// Enable the `/simpleenroll` endpoint (RFC 7030 §4.2).
112    #[serde(default = "bool_true")]
113    pub simpleenroll: bool,
114
115    /// Enable the `/simplereenroll` endpoint (RFC 7030 §4.2.2).
116    #[serde(default = "bool_true")]
117    pub simplereenroll: bool,
118
119    /// Enable the `/fullcmc` endpoint (RFC 7030 §4.3).
120    ///
121    /// Full CMC is rarely needed; disabled by default.
122    #[serde(default)]
123    pub fullcmc: bool,
124
125    /// PEM file containing trust anchors for CMC RA signer verification.
126    ///
127    /// When set, `/fullcmc` verifies the CMS SignedData signature against
128    /// these certificates instead of only the target CA cert. This allows
129    /// RA certificates issued by a different CA or intermediate to be
130    /// accepted. When absent, the target CA certificate is used as the
131    /// sole trust anchor.
132    #[serde(default)]
133    pub cmc_truststore_file: Option<String>,
134
135    /// Enable the `/serverkeygen` endpoint (RFC 7030 §4.4).
136    ///
137    /// Server-side key generation requires HSM integration.
138    /// Disabled by default.
139    #[serde(default)]
140    pub serverkeygen: bool,
141
142    /// Enable the `/csrattrs` endpoint (RFC 7030 §4.5).
143    #[serde(default = "bool_true")]
144    pub csrattrs: bool,
145
146    /// Default enrollment profile applied when no label is specified.
147    ///
148    /// When absent, enrollment requests without a label use the default
149    /// CA and authentication policy.
150    #[serde(default)]
151    pub default_profile: Option<String>,
152
153    /// CSR attribute hints returned by `/csrattrs`.
154    ///
155    /// Each entry is an OID string (e.g., `"1.2.840.113549.1.9.14"` for
156    /// the Certificate Extensions Request attribute).
157    #[serde(default)]
158    pub csr_attributes: Vec<String>,
159
160    /// CSR template mode (draft-ietf-lamps-rfc7030-csrattrs / RFC 9908).
161    ///
162    /// When set, the `/csrattrs` response includes a
163    /// `CertificationRequestInfoTemplate` attribute that tells the client
164    /// which subject DN fields, key algorithm, and extensions are required.
165    /// This coexists with the OID-list mode.
166    #[serde(default)]
167    pub csr_template: Option<CsrTemplate>,
168
169    /// Per-label enrollment configurations.
170    #[serde(default, rename = "label")]
171    pub labels: Vec<EstLabelConfig>,
172
173    /// Disconnected mode: accept enrollment requests without upstream
174    /// CA connectivity (RHELBU-3536 R7-Disconnected).
175    ///
176    /// When `true`, the server queues CSRs for deferred signing and
177    /// returns `202 Accepted` with a `Retry-After` header instead of
178    /// the signed certificate.
179    #[serde(default)]
180    pub disconnected: bool,
181
182    /// Retry-After value (seconds) returned in disconnected mode.
183    /// Default: 300 (5 minutes).
184    #[serde(default = "default_retry_after_secs")]
185    pub disconnected_retry_after_secs: u64,
186
187    /// Number of days before certificate expiry to start the suggested
188    /// renewal window (draft-ietf-lamps-est-renewal-info).
189    ///
190    /// The renewal window starts at `not_after - renewal_window_days` and
191    /// ends one day before `not_after`.
192    #[serde(default = "default_renewal_window_days")]
193    pub renewal_window_days: u32,
194
195    /// `Retry-After` value (seconds) returned in renewal-info responses
196    /// to control client polling frequency.
197    ///
198    /// Default: 86400 (1 day).
199    #[serde(default = "default_renewal_retry_after")]
200    pub renewal_retry_after_secs: u64,
201}
202
203/// `[[est.label]]` — per-label enrollment profile.
204///
205/// Each label provides an independent enrollment namespace with its own
206/// CA routing, authentication requirements, and CSR attribute set.
207///
208/// ```toml
209/// [[est.label]]
210/// name = "devices"
211/// ca_id = "device-ca"
212/// auth_methods = ["mtls", "otp"]
213/// require_cn_match = true
214/// ```
215#[derive(Debug, Clone, Deserialize)]
216#[serde(deny_unknown_fields)]
217pub struct EstLabelConfig {
218    /// Label name used in the URL path.
219    ///
220    /// Must be a non-empty string matching `^[a-z0-9][a-z0-9_-]*$`.
221    pub name: String,
222
223    /// CA identifier to use for enrollments under this label.
224    ///
225    /// Must reference a `[[ca]]` entry by its `id` field.
226    /// When absent, the default CA is used.
227    pub ca_id: Option<String>,
228
229    /// Explicit authorization for alternate issuers under this label.
230    #[serde(default)]
231    pub ca_pool: Vec<String>,
232
233    /// Allowed authentication methods for this label.
234    ///
235    /// When empty, all globally-enabled auth methods are accepted.
236    #[serde(default)]
237    pub auth_methods: Vec<EstAuthMethod>,
238
239    /// Per-label CSR attribute hints (overrides global `csr_attributes`
240    /// for this label).
241    #[serde(default)]
242    pub csr_attributes: Vec<String>,
243
244    /// Per-label CSR template (overrides global `csr_template` for this label).
245    #[serde(default)]
246    pub csr_template: Option<CsrTemplate>,
247
248    /// Require that the CSR Common Name matches the authenticated identity.
249    ///
250    /// When `true`, the server rejects CSRs where the CN does not match
251    /// the client's authenticated principal name.  NIAP CA PP FDP_ACF.1
252    /// identity binding — enforced in `crate::auth::enroll_authz`.
253    #[serde(default)]
254    pub require_cn_match: bool,
255
256    /// Require the authenticated identity to appear as a Subject Alternative
257    /// Name entry (dNSName, rfc822Name, or iPAddress, matched by type).
258    ///
259    /// NIAP CA PP FDP_ACF.1 identity binding.  Complements
260    /// `require_cn_match`; both may be set.  Default: `false`.
261    #[serde(default)]
262    pub require_san_match: bool,
263
264    /// Name-authorization allowlist of permitted dNSName patterns (RFC 6125,
265    /// wildcards allowed).  When non-empty, every dNSName SAN in the CSR must
266    /// match one entry, bounding this label to a fixed namespace.
267    #[serde(default)]
268    pub permitted_dns_names: Vec<String>,
269
270    /// Name-authorization allowlist of permitted iPAddress SANs (textual
271    /// form, e.g. `"10.0.0.1"` or `"2001:db8::1"`).  When non-empty, every
272    /// iPAddress SAN in the CSR must equal one entry.
273    #[serde(default)]
274    pub permitted_ip_addresses: Vec<String>,
275
276    /// Name-authorization allowlist of permitted rfc822Name patterns (full
277    /// address or bare domain).  When non-empty, every rfc822Name SAN in the
278    /// CSR must match one entry.
279    #[serde(default)]
280    pub permitted_emails: Vec<String>,
281
282    /// Maximum validity period (days) for certificates issued under this label.
283    ///
284    /// Overrides the CA's default `validity_days` for this label.
285    pub max_validity_days: Option<u32>,
286
287    /// Enable disconnected mode for this specific label.
288    /// Overrides the global `[est].disconnected` setting.
289    pub disconnected: Option<bool>,
290}
291
292fn bool_true() -> bool {
293    true
294}
295
296fn default_retry_after_secs() -> u64 {
297    300
298}
299
300fn default_renewal_window_days() -> u32 {
301    30
302}
303
304fn default_renewal_retry_after() -> u64 {
305    86400
306}
307
308impl Default for EstConfig {
309    fn default() -> Self {
310        Self {
311            simpleenroll: true,
312            simplereenroll: true,
313            fullcmc: false,
314            cmc_truststore_file: None,
315            serverkeygen: false,
316            csrattrs: true,
317            default_profile: None,
318            csr_attributes: Vec::new(),
319            csr_template: None,
320            labels: Vec::new(),
321            disconnected: false,
322            disconnected_retry_after_secs: default_retry_after_secs(),
323            renewal_window_days: default_renewal_window_days(),
324            renewal_retry_after_secs: default_renewal_retry_after(),
325        }
326    }
327}