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}