Skip to main content

kipuka/config/
ca.rs

1//! Multi-CA configuration.
2//!
3//! Each `[[ca]]` entry in the TOML config defines one Certificate Authority
4//! with its own key material, validity policy, and certificate profile.
5//!
6//! EST labels (see `EstLabelConfig`) reference CAs by their `id` field,
7//! enabling per-label CA routing (e.g., different CAs for different device
8//! classes or enrollment profiles).
9
10use serde::Deserialize;
11
12/// `[[ca]]` section — per-CA key material and issuance policy.
13///
14/// Multiple CAs are supported via the TOML array-of-tables syntax:
15///
16/// ```toml
17/// [[ca]]
18/// id = "production"
19/// is_default = true
20/// key_file = "/etc/kipuka/ca-prod.key"
21/// cert_file = "/etc/kipuka/ca-prod.crt"
22///
23/// [[ca]]
24/// id = "dev"
25/// key_file = "/etc/kipuka/ca-dev.key"
26/// cert_file = "/etc/kipuka/ca-dev.crt"
27/// validity_days = 30
28/// ```
29#[derive(Debug, Clone, Deserialize)]
30#[serde(deny_unknown_fields)]
31pub struct CaConfig {
32    /// Unique identifier for this CA.
33    ///
34    /// Used in EST label configurations to route enrollment requests to
35    /// the appropriate CA.  Must match `^[a-z0-9][a-z0-9_-]*$` and be
36    /// at most 64 characters.
37    #[serde(default)]
38    pub id: String,
39
40    /// Whether this CA is the default for EST labels that do not specify
41    /// a `ca_id`.  Exactly one CA must be marked as default when multiple
42    /// CAs are configured.
43    #[serde(default)]
44    pub is_default: bool,
45
46    /// Path to the CA private key in PEM format.
47    ///
48    /// Mutually exclusive with `pkcs11_uri`: when `pkcs11_uri` is set,
49    /// the key is accessed via the HSM and this field is ignored.
50    pub key_file: String,
51
52    /// Path to the CA certificate (or chain) in PEM format.
53    ///
54    /// The file should contain the CA's end-entity certificate first,
55    /// followed by any intermediates up to (but not including) the root.
56    pub cert_file: String,
57
58    /// Key type for CA key generation (used only when `key_file` does
59    /// not exist and auto-generation is requested).
60    ///
61    /// Supported values:
62    ///
63    /// Classical:
64    /// - `"rsa:2048"`, `"rsa:3072"`, `"rsa:4096"`
65    /// - `"ec:P-256"`, `"ec:P-384"`, `"ec:P-521"`
66    /// - `"ed25519"`
67    ///
68    /// Post-Quantum (FIPS 204 — ML-DSA standalone):
69    /// - `"ml-dsa-44"` (NIST Security Level 2, ~2.5 KB sig)
70    /// - `"ml-dsa-65"` (NIST Security Level 3, ~3.3 KB sig)
71    /// - `"ml-dsa-87"` (NIST Security Level 5, ~4.6 KB sig)
72    ///
73    /// Composite (draft-ietf-lamps-pq-composite-sigs-19):
74    /// - `"ml-dsa-44-with-rsa-2048"`, `"ml-dsa-44-with-rsa-3072"`
75    /// - `"ml-dsa-44-with-ec-P-256"`
76    /// - `"ml-dsa-65-with-ec-P-384"`
77    /// - `"ml-dsa-65-with-rsa-3072"`, `"ml-dsa-65-with-rsa-4096"`
78    /// - `"ml-dsa-87-with-ec-P-384"`
79    /// - `"ml-dsa-87-with-ed448"`
80    ///
81    /// Default: `"ec:P-256"`.
82    #[serde(default = "default_key_type")]
83    pub key_type: String,
84
85    /// PKCS#11 URI for HSM-backed CA key.
86    ///
87    /// When set, the CA private key is accessed via the configured HSM
88    /// (`[hsm]` section) instead of reading `key_file` from disk.
89    ///
90    /// Example: `"pkcs11:token=kipuka;object=ca-key;type=private"`
91    pub pkcs11_uri: Option<String>,
92
93    /// Default validity period for issued end-entity certificates (days).
94    ///
95    /// CA/B Forum BR §6.3.2 (Ballot SC-081v3) enforces a stepped schedule:
96    /// 200 days (from 2026-03-15), 100 days (from 2027-03-15), 47 days
97    /// (from 2029-03-15). Private CAs may use longer periods.
98    ///
99    /// Default: 365 days.
100    #[serde(default = "default_validity_days")]
101    pub validity_days: u32,
102
103    /// Hash algorithm for certificate and CRL signing.
104    ///
105    /// Supported: `"sha256"`, `"sha384"`, `"sha512"`.
106    /// For ML-DSA CAs, set to `"none"` — the hash is built into the
107    /// algorithm per FIPS 204. Auto-detected when `key_type` starts
108    /// with `"ml-dsa"`.
109    /// Default: `"sha256"`.
110    #[serde(default = "default_hash_algorithm")]
111    pub hash_algorithm: String,
112
113    /// CRL distribution point URL embedded in issued certificates.
114    pub crl_url: Option<String>,
115
116    /// OCSP responder URL embedded in issued certificates.
117    pub ocsp_url: Option<String>,
118
119    /// Subject Common Name for auto-generated CA certificates.
120    #[serde(default = "default_common_name")]
121    pub common_name: String,
122
123    /// Subject Organization for auto-generated CA certificates.
124    #[serde(default = "default_organization")]
125    pub organization: String,
126
127    /// CRL validity period in seconds.
128    ///
129    /// Determines the `nextUpdate` field in generated CRLs.
130    /// Default: 86400 (24 hours).
131    #[serde(default = "default_crl_lifetime_secs")]
132    pub crl_lifetime_secs: u64,
133
134    /// CA/B Forum compliance mode.
135    ///
136    /// When `true`, the server enforces:
137    /// - Maximum validity per Ballot SC-081v3 schedule (200 days as of 2026-03-15)
138    /// - Required key usage and extended key usage extensions
139    /// - Minimum RSA 2048-bit key size in CSRs
140    #[serde(default)]
141    pub cab_forum_compliant: bool,
142}
143
144fn default_key_type() -> String {
145    "ec:P-256".to_string()
146}
147
148fn default_validity_days() -> u32 {
149    365
150}
151
152fn default_hash_algorithm() -> String {
153    "sha256".to_string()
154}
155
156fn default_common_name() -> String {
157    "Kipuka EST CA".to_string()
158}
159
160fn default_organization() -> String {
161    "Kipuka EST Server".to_string()
162}
163
164fn default_crl_lifetime_secs() -> u64 {
165    86400
166}
167
168impl CaConfig {
169    /// Returns `true` when this CA uses an HSM-backed key via PKCS#11.
170    pub fn is_hsm_backed(&self) -> bool {
171        self.pkcs11_uri.is_some()
172    }
173}