zerodds_bridge_security/
tls.rs1use std::fs::File;
14use std::io::BufReader;
15use std::path::Path;
16use std::sync::Arc;
17
18use rustls::ServerConfig;
19use rustls_pemfile::Item;
20use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
21
22#[derive(Debug)]
24pub enum TlsConfigError {
25 CertFileRead(String),
27 KeyFileRead(String),
29 NoCertificateInPem,
31 NoSupportedPrivateKeyInPem,
33 Rustls(String),
36}
37
38impl core::fmt::Display for TlsConfigError {
39 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40 match self {
41 Self::CertFileRead(m) => write!(f, "cert file read: {m}"),
42 Self::KeyFileRead(m) => write!(f, "key file read: {m}"),
43 Self::NoCertificateInPem => f.write_str("PEM had no CERTIFICATE block"),
44 Self::NoSupportedPrivateKeyInPem => {
45 f.write_str("PEM had no PKCS#8 / RSA / EC private key")
46 }
47 Self::Rustls(m) => write!(f, "rustls build: {m}"),
48 }
49 }
50}
51
52impl std::error::Error for TlsConfigError {}
53
54pub fn load_server_config(
61 cert_pem_path: &Path,
62 key_pem_path: &Path,
63) -> Result<Arc<ServerConfig>, TlsConfigError> {
64 let certs = read_certs(cert_pem_path)?;
65 let key = read_private_key(key_pem_path)?;
66 let provider = crate::tls_provider();
67 let cfg = ServerConfig::builder_with_provider(Arc::new(provider))
68 .with_safe_default_protocol_versions()
69 .map_err(|e| TlsConfigError::Rustls(format!("{e}")))?
70 .with_no_client_auth()
71 .with_single_cert(certs, key)
72 .map_err(|e| TlsConfigError::Rustls(format!("{e}")))?;
73 Ok(Arc::new(cfg))
74}
75
76pub fn load_server_config_with_client_auth(
86 cert_pem_path: &Path,
87 key_pem_path: &Path,
88 client_ca_pem_path: &Path,
89) -> Result<Arc<ServerConfig>, TlsConfigError> {
90 let certs = read_certs(cert_pem_path)?;
91 let key = read_private_key(key_pem_path)?;
92 let client_cas = read_certs(client_ca_pem_path)?;
93
94 let mut roots = rustls::RootCertStore::empty();
95 for c in client_cas {
96 roots
97 .add(c)
98 .map_err(|e| TlsConfigError::Rustls(format!("client CA add: {e}")))?;
99 }
100 let provider = Arc::new(crate::tls_provider());
104 let verifier = rustls::server::WebPkiClientVerifier::builder_with_provider(
105 Arc::new(roots),
106 provider.clone(),
107 )
108 .build()
109 .map_err(|e| TlsConfigError::Rustls(format!("client verifier: {e}")))?;
110
111 let cfg = ServerConfig::builder_with_provider(provider)
112 .with_safe_default_protocol_versions()
113 .map_err(|e| TlsConfigError::Rustls(format!("{e}")))?
114 .with_client_cert_verifier(verifier)
115 .with_single_cert(certs, key)
116 .map_err(|e| TlsConfigError::Rustls(format!("{e}")))?;
117 Ok(Arc::new(cfg))
118}
119
120pub(crate) fn read_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>, TlsConfigError> {
121 let f = File::open(path)
122 .map_err(|e| TlsConfigError::CertFileRead(format!("{}: {e}", path.display())))?;
123 let mut br = BufReader::new(f);
124 let mut out = Vec::new();
125 for item in rustls_pemfile::read_all(&mut br) {
126 let item = item.map_err(|e| TlsConfigError::CertFileRead(format!("{e}")))?;
127 if let Item::X509Certificate(d) = item {
128 out.push(d);
129 }
130 }
131 if out.is_empty() {
132 return Err(TlsConfigError::NoCertificateInPem);
133 }
134 Ok(out)
135}
136
137pub(crate) fn read_private_key(path: &Path) -> Result<PrivateKeyDer<'static>, TlsConfigError> {
138 let f = File::open(path)
139 .map_err(|e| TlsConfigError::KeyFileRead(format!("{}: {e}", path.display())))?;
140 let mut br = BufReader::new(f);
141 for item in rustls_pemfile::read_all(&mut br) {
142 let item = item.map_err(|e| TlsConfigError::KeyFileRead(format!("{e}")))?;
143 match item {
144 Item::Pkcs8Key(k) => return Ok(PrivateKeyDer::Pkcs8(k)),
145 Item::Pkcs1Key(k) => return Ok(PrivateKeyDer::Pkcs1(k)),
146 Item::Sec1Key(k) => return Ok(PrivateKeyDer::Sec1(k)),
147 _ => {}
148 }
149 }
150 let _ = PrivatePkcs8KeyDer::from(Vec::<u8>::new()); Err(TlsConfigError::NoSupportedPrivateKeyInPem)
152}
153
154#[cfg(test)]
155#[allow(clippy::expect_used, clippy::unwrap_used)]
156mod tests {
157 use super::*;
158 use std::io::Write;
159
160 fn write_temp(name: &str, body: &[u8]) -> std::path::PathBuf {
161 let dir =
162 std::env::temp_dir().join(format!("zd-bridge-sec-{}-{}", name, std::process::id()));
163 let _ = std::fs::create_dir_all(&dir);
164 let p = dir.join(name);
165 let mut f = std::fs::File::create(&p).unwrap();
166 f.write_all(body).unwrap();
167 p
168 }
169
170 fn gen_self_signed() -> (String, String) {
171 let ck = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap();
172 (ck.cert.pem(), ck.key_pair.serialize_pem())
173 }
174
175 #[test]
176 fn load_self_signed_cert_succeeds() {
177 let (cert_pem, key_pem) = gen_self_signed();
178 let c = write_temp("cert.pem", cert_pem.as_bytes());
179 let k = write_temp("key.pem", key_pem.as_bytes());
180 let cfg = load_server_config(&c, &k).expect("ServerConfig");
181 assert!(Arc::strong_count(&cfg) >= 1);
183 }
184
185 #[test]
186 fn missing_cert_file_returns_err() {
187 let p = std::path::PathBuf::from("/no/such/file.pem");
188 let err = load_server_config(&p, &p).unwrap_err();
189 assert!(matches!(err, TlsConfigError::CertFileRead(_)));
190 }
191
192 #[test]
193 fn empty_pem_rejected_as_no_cert() {
194 let c = write_temp(
195 "empty.pem",
196 b"-----BEGIN GARBAGE-----\nXX\n-----END GARBAGE-----\n",
197 );
198 let k = c.clone();
199 let err = load_server_config(&c, &k).unwrap_err();
200 assert!(matches!(err, TlsConfigError::NoCertificateInPem));
201 }
202
203 #[test]
204 fn key_pem_without_supported_block_rejected() {
205 let (cert_pem, _) = gen_self_signed();
206 let c = write_temp("c2.pem", cert_pem.as_bytes());
207 let k = write_temp(
208 "k2.pem",
209 b"-----BEGIN GARBAGE-----\nXX\n-----END GARBAGE-----\n",
210 );
211 let err = load_server_config(&c, &k).unwrap_err();
212 assert!(matches!(err, TlsConfigError::NoSupportedPrivateKeyInPem));
213 }
214
215 #[test]
216 fn mtls_config_loads_with_client_ca() {
217 let (cert_pem, key_pem) = gen_self_signed();
218 let c = write_temp("c3.pem", cert_pem.as_bytes());
219 let k = write_temp("k3.pem", key_pem.as_bytes());
220 let ca = write_temp("ca.pem", cert_pem.as_bytes());
223 let cfg = load_server_config_with_client_auth(&c, &k, &ca).expect("mtls cfg");
224 assert!(Arc::strong_count(&cfg) >= 1);
225 }
226}