1use std::collections::HashMap;
17use std::hash::{Hash, Hasher};
18use std::sync::{Arc, OnceLock, RwLock};
19
20use rustls::pki_types::{CertificateDer, PrivateKeyDer};
21
22#[derive(Debug)]
23pub struct UpstreamTls {
24 client: Option<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>,
25 ca: Option<Vec<CertificateDer<'static>>>,
26 pub verify: bool,
29 key: u64,
30}
31
32fn registry() -> &'static RwLock<HashMap<u64, Arc<UpstreamTls>>> {
35 static REGISTRY: OnceLock<RwLock<HashMap<u64, Arc<UpstreamTls>>>> = OnceLock::new();
36 REGISTRY.get_or_init(|| RwLock::new(HashMap::new()))
37}
38
39impl UpstreamTls {
40 pub fn load(
46 client_paths: Option<(&str, &str)>,
47 ca_path: Option<&str>,
48 verify: bool,
49 ) -> Result<Arc<Self>, String> {
50 let mut hasher = std::collections::hash_map::DefaultHasher::new();
51 verify.hash(&mut hasher);
52
53 let client = match client_paths {
54 Some((cert_path, key_path)) => {
55 let cert_pem = std::fs::read(cert_path)
56 .map_err(|e| format!("client_cert_path '{}': {}", cert_path, e))?;
57 let key_pem = std::fs::read(key_path)
58 .map_err(|e| format!("client_key_path '{}': {}", key_path, e))?;
59 cert_pem.hash(&mut hasher);
60 key_pem.hash(&mut hasher);
61 let certs = rustls_pemfile::certs(&mut std::io::BufReader::new(&cert_pem[..]))
62 .collect::<Result<Vec<_>, _>>()
63 .map_err(|e| format!("client_cert_path '{}': {}", cert_path, e))?;
64 if certs.is_empty() {
65 return Err(format!(
66 "client_cert_path '{}': no certificates found",
67 cert_path
68 ));
69 }
70 let key = rustls_pemfile::private_key(&mut std::io::BufReader::new(&key_pem[..]))
71 .map_err(|e| format!("client_key_path '{}': {}", key_path, e))?
72 .ok_or_else(|| {
73 format!("client_key_path '{}': no private key found", key_path)
74 })?;
75 Some((certs, key))
76 }
77 None => None,
78 };
79
80 let ca = match ca_path {
81 Some(path) => {
82 let pem =
83 std::fs::read(path).map_err(|e| format!("ca_cert_path '{}': {}", path, e))?;
84 pem.hash(&mut hasher);
85 let certs = rustls_pemfile::certs(&mut std::io::BufReader::new(&pem[..]))
86 .collect::<Result<Vec<_>, _>>()
87 .map_err(|e| format!("ca_cert_path '{}': {}", path, e))?;
88 if certs.is_empty() {
89 return Err(format!("ca_cert_path '{}': no certificates found", path));
90 }
91 Some(certs)
92 }
93 None => None,
94 };
95
96 let identity = Arc::new(Self {
97 client,
98 ca,
99 verify,
100 key: hasher.finish(),
101 });
102 identity.client_config()?; Ok(identity)
104 }
105
106 pub fn cache_key(&self) -> u64 {
109 self.key
110 }
111
112 pub fn client_config(&self) -> Result<rustls::ClientConfig, String> {
115 crate::server::tls::install_crypto_provider();
116
117 let builder = if self.verify {
118 let mut roots = rustls::RootCertStore::empty();
119 match &self.ca {
120 Some(certs) => {
122 let (added, _ignored) = roots.add_parsable_certificates(certs.iter().cloned());
123 if added == 0 {
124 return Err("ca_cert_path: no usable certificates".to_string());
125 }
126 }
127 None => {
128 let loaded = rustls_native_certs::load_native_certs();
129 let (added, _ignored) = roots.add_parsable_certificates(loaded.certs);
130 if added == 0 {
131 return Err(format!(
132 "no usable native root certificates ({} load error(s))",
133 loaded.errors.len()
134 ));
135 }
136 }
137 }
138 rustls::ClientConfig::builder().with_root_certificates(roots)
139 } else {
140 rustls::ClientConfig::builder()
141 .dangerous()
142 .with_custom_certificate_verifier(Arc::new(super::NoVerification(
143 rustls::crypto::ring::default_provider(),
144 )))
145 };
146
147 match &self.client {
148 Some((certs, key)) => builder
149 .with_client_auth_cert(certs.clone(), key.clone_key())
150 .map_err(|e| format!("client cert/key rejected: {}", e)),
151 None => Ok(builder.with_no_client_auth()),
152 }
153 }
154
155 pub fn register(this: &Arc<Self>) {
157 registry()
158 .write()
159 .unwrap()
160 .entry(this.key)
161 .or_insert_with(|| this.clone());
162 }
163
164 pub fn lookup(key: u64) -> Option<Arc<UpstreamTls>> {
166 registry().read().unwrap().get(&key).cloned()
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 fn write_identity(tag: &str) -> (String, String, String) {
177 let mut ca_params = rcgen::CertificateParams::new(Vec::<String>::new()).unwrap();
178 ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
179 let ca_key = rcgen::KeyPair::generate().unwrap();
180 let ca_cert = ca_params.self_signed(&ca_key).unwrap();
181
182 let leaf_params = rcgen::CertificateParams::new(vec!["client".to_string()]).unwrap();
183 let leaf_key = rcgen::KeyPair::generate().unwrap();
184 let leaf_cert = leaf_params.signed_by(&leaf_key, &ca_cert, &ca_key).unwrap();
185
186 let dir = std::env::temp_dir();
187 let pid = std::process::id();
188 let cert = dir.join(format!("featherbit_utls_{}_{}.crt", tag, pid));
189 let key = dir.join(format!("featherbit_utls_{}_{}.key", tag, pid));
190 let ca = dir.join(format!("featherbit_utls_{}_{}.ca.crt", tag, pid));
191 std::fs::write(&cert, leaf_cert.pem()).unwrap();
192 std::fs::write(&key, leaf_key.serialize_pem()).unwrap();
193 std::fs::write(&ca, ca_cert.pem()).unwrap();
194 (
195 cert.to_str().unwrap().to_string(),
196 key.to_str().unwrap().to_string(),
197 ca.to_str().unwrap().to_string(),
198 )
199 }
200
201 #[test]
202 fn test_upstream_tls_load_client_and_ca() {
203 let (cert, key, ca) = write_identity("happy");
204 let id = UpstreamTls::load(Some((&cert, &key)), Some(&ca), true).unwrap();
205 assert!(id.verify);
206 assert!(id.client_config().is_ok());
208 }
209
210 #[test]
211 fn test_upstream_tls_load_ca_only() {
212 let (_, _, ca) = write_identity("caonly");
213 assert!(UpstreamTls::load(None, Some(&ca), true).is_ok());
214 }
215
216 #[test]
217 fn test_upstream_tls_load_missing_file_errors() {
218 let err = UpstreamTls::load(Some(("/nonexistent.crt", "/nonexistent.key")), None, true)
219 .unwrap_err();
220 assert!(err.contains("/nonexistent.crt"), "err was: {}", err);
221 }
222
223 #[test]
224 fn test_upstream_tls_load_garbage_key_errors() {
225 let (cert, key, _) = write_identity("garbage");
226 std::fs::write(&key, "not a pem").unwrap();
227 assert!(UpstreamTls::load(Some((&cert, &key)), None, true).is_err());
228 }
229
230 #[test]
231 fn test_upstream_tls_load_mismatched_key_errors() {
232 let (cert, _, _) = write_identity("mismatch_a");
235 let (_, other_key, _) = write_identity("mismatch_b");
236 assert!(UpstreamTls::load(Some((&cert, &other_key)), None, true).is_err());
237 }
238
239 #[test]
240 fn test_upstream_tls_cache_key_content_hash() {
241 let (cert, key, ca) = write_identity("hash");
242 let a = UpstreamTls::load(Some((&cert, &key)), Some(&ca), true).unwrap();
243 let b = UpstreamTls::load(Some((&cert, &key)), Some(&ca), true).unwrap();
244 assert_eq!(a.cache_key(), b.cache_key());
247 let c = UpstreamTls::load(Some((&cert, &key)), Some(&ca), false).unwrap();
248 assert_ne!(a.cache_key(), c.cache_key());
249 let (cert2, key2, _) = write_identity("hash2");
250 let d = UpstreamTls::load(Some((&cert2, &key2)), None, true).unwrap();
251 assert_ne!(a.cache_key(), d.cache_key());
252 }
253
254 #[test]
255 fn test_upstream_tls_registry_roundtrip() {
256 let (cert, key, _) = write_identity("registry");
257 let id = UpstreamTls::load(Some((&cert, &key)), None, true).unwrap();
258 UpstreamTls::register(&id);
259 let found = UpstreamTls::lookup(id.cache_key()).expect("registered identity");
260 assert_eq!(found.cache_key(), id.cache_key());
261 assert!(UpstreamTls::lookup(id.cache_key().wrapping_add(1)).is_none());
262 }
263}