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 limits publicly-trusted certificates to
96    /// 398 days (roughly 13 months).  Private CAs may use longer periods.
97    ///
98    /// Default: 365 days.
99    #[serde(default = "default_validity_days")]
100    pub validity_days: u32,
101
102    /// Hash algorithm for certificate and CRL signing.
103    ///
104    /// Supported: `"sha256"`, `"sha384"`, `"sha512"`.
105    /// Default: `"sha256"`.
106    #[serde(default = "default_hash_algorithm")]
107    pub hash_algorithm: String,
108
109    /// CRL distribution point URL embedded in issued certificates.
110    pub crl_url: Option<String>,
111
112    /// OCSP responder URL embedded in issued certificates.
113    pub ocsp_url: Option<String>,
114
115    /// Subject Common Name for auto-generated CA certificates.
116    #[serde(default = "default_common_name")]
117    pub common_name: String,
118
119    /// Subject Organization for auto-generated CA certificates.
120    #[serde(default = "default_organization")]
121    pub organization: String,
122
123    /// CRL validity period in seconds.
124    ///
125    /// Determines the `nextUpdate` field in generated CRLs.
126    /// Default: 86400 (24 hours).
127    #[serde(default = "default_crl_lifetime_secs")]
128    pub crl_lifetime_secs: u64,
129
130    /// CA/B Forum compliance mode.
131    ///
132    /// When `true`, the server enforces:
133    /// - Maximum 398-day end-entity certificate validity
134    /// - Required key usage and extended key usage extensions
135    /// - Minimum RSA 2048-bit key size in CSRs
136    #[serde(default)]
137    pub cab_forum_compliant: bool,
138}
139
140fn default_key_type() -> String {
141    "ec:P-256".to_string()
142}
143
144fn default_validity_days() -> u32 {
145    365
146}
147
148fn default_hash_algorithm() -> String {
149    "sha256".to_string()
150}
151
152fn default_common_name() -> String {
153    "Kipuka EST CA".to_string()
154}
155
156fn default_organization() -> String {
157    "Kipuka EST Server".to_string()
158}
159
160fn default_crl_lifetime_secs() -> u64 {
161    86400
162}
163
164impl CaConfig {
165    /// Returns `true` when this CA uses an HSM-backed key via PKCS#11.
166    pub fn is_hsm_backed(&self) -> bool {
167        self.pkcs11_uri.is_some()
168    }
169}