Skip to main content

kipuka/config/
cms_est.rs

1//! CMS-wrapped EST configuration (experimental CMS EST extension).
2//!
3//! The `[cms_est]` section enables EST endpoints that use CMS
4//! message-level security instead of TLS for authentication and
5//! confidentiality.  This supports disconnected/air-gapped
6//! deployments where a TLS-terminating proxy handles transport.
7
8use serde::Deserialize;
9
10/// `[cms_est]` section — CMS message-level security for EST.
11///
12/// ```toml
13/// [cms_est]
14/// enabled = true
15/// require_signed_requests = true
16/// encrypt_responses = true
17/// allowed_content_encryption = ["AES-256-GCM", "AES-128-GCM"]
18/// ```
19#[derive(Debug, Clone, Deserialize)]
20#[serde(deny_unknown_fields)]
21pub struct CmsEstConfig {
22    /// Enable CMS-wrapped EST endpoints.
23    #[serde(default)]
24    pub enabled: bool,
25
26    /// Require CMS SignedData wrapping on all requests.
27    #[serde(default = "default_true")]
28    pub require_signed_requests: bool,
29
30    /// Encrypt responses using CMS EnvelopedData.
31    #[serde(default = "default_true")]
32    pub encrypt_responses: bool,
33
34    /// Allowed response encryption algorithms. AES-GCM returns CMS
35    /// AuthEnvelopedData; explicitly configured AES-CBC returns EnvelopedData.
36    #[serde(default = "default_allowed_content_encryption")]
37    pub allowed_content_encryption: Vec<String>,
38}
39
40fn default_true() -> bool {
41    true
42}
43
44fn default_allowed_content_encryption() -> Vec<String> {
45    vec!["AES-256-GCM".to_string(), "AES-128-GCM".to_string()]
46}
47
48impl Default for CmsEstConfig {
49    fn default() -> Self {
50        Self {
51            enabled: false,
52            require_signed_requests: true,
53            encrypt_responses: true,
54            allowed_content_encryption: default_allowed_content_encryption(),
55        }
56    }
57}
58
59impl CmsEstConfig {
60    /// Validate CMS-EST configuration constraints.
61    pub fn validate(&self) -> Result<(), String> {
62        if self.encrypt_responses && self.allowed_content_encryption.is_empty() {
63            return Err(
64                "[cms_est].allowed_content_encryption must not be empty when encrypt_responses is true"
65                    .into(),
66            );
67        }
68
69        Ok(())
70    }
71}