1use crate::FaucetError;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default, PartialEq, Eq)]
20pub struct TlsClientConfig {
21 pub client_cert: Option<String>,
23 pub client_key: Option<String>,
25 pub client_identity_pkcs12: Option<String>,
28 pub pkcs12_password: Option<String>,
30 pub min_version: Option<String>,
33}
34
35impl TlsClientConfig {
36 pub fn validate(&self) -> Result<(), FaucetError> {
41 let has_pem = self.client_cert.is_some() || self.client_key.is_some();
42 let has_p12 = self.client_identity_pkcs12.is_some();
43 if has_pem && has_p12 {
44 return Err(FaucetError::Config(
45 "tls: specify either a PEM pair (client_cert + client_key) or \
46 client_identity_pkcs12, not both"
47 .into(),
48 ));
49 }
50 if !has_pem && !has_p12 {
51 return Err(FaucetError::Config(
52 "tls: provide a PEM pair (client_cert + client_key) or \
53 client_identity_pkcs12"
54 .into(),
55 ));
56 }
57 if has_pem && (self.client_cert.is_none() || self.client_key.is_none()) {
58 return Err(FaucetError::Config(
59 "tls: client_cert and client_key must be provided together".into(),
60 ));
61 }
62 if let Some(v) = &self.min_version
63 && !matches!(v.as_str(), "1.2" | "1.3")
64 {
65 return Err(FaucetError::Config(format!(
66 "tls: unsupported min_version {v:?} (expected \"1.2\" or \"1.3\")"
67 )));
68 }
69 Ok(())
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::TlsClientConfig;
76
77 fn pem() -> TlsClientConfig {
78 TlsClientConfig {
79 client_cert: Some("cert".into()),
80 client_key: Some("key".into()),
81 ..Default::default()
82 }
83 }
84
85 #[test]
86 fn valid_pem_pair_passes() {
87 assert!(pem().validate().is_ok());
88 }
89
90 #[test]
91 fn valid_pkcs12_passes() {
92 let c = TlsClientConfig {
93 client_identity_pkcs12: Some("/path/id.p12".into()),
94 pkcs12_password: Some("pw".into()),
95 ..Default::default()
96 };
97 assert!(c.validate().is_ok());
98 }
99
100 #[test]
101 fn pem_and_pkcs12_together_is_rejected() {
102 let c = TlsClientConfig {
103 client_cert: Some("cert".into()),
104 client_key: Some("key".into()),
105 client_identity_pkcs12: Some("/path/id.p12".into()),
106 ..Default::default()
107 };
108 assert!(c.validate().is_err());
109 }
110
111 #[test]
112 fn empty_config_is_rejected() {
113 assert!(TlsClientConfig::default().validate().is_err());
114 }
115
116 #[test]
117 fn half_pem_pair_is_rejected() {
118 let cert_only = TlsClientConfig {
119 client_cert: Some("cert".into()),
120 ..Default::default()
121 };
122 assert!(cert_only.validate().is_err());
123 let key_only = TlsClientConfig {
124 client_key: Some("key".into()),
125 ..Default::default()
126 };
127 assert!(key_only.validate().is_err());
128 }
129
130 #[test]
131 fn min_version_is_validated() {
132 let mut c = pem();
133 c.min_version = Some("1.3".into());
134 assert!(c.validate().is_ok());
135 c.min_version = Some("1.2".into());
136 assert!(c.validate().is_ok());
137 c.min_version = Some("1.1".into());
138 assert!(c.validate().is_err());
139 }
140}