tako_rs_server/builder/
tls_cert.rs1#[cfg(feature = "tls")]
2use std::sync::Arc;
3
4#[cfg(feature = "tls")]
6#[derive(Clone)]
7pub enum ClientAuth {
8 Optional(Arc<rustls::RootCertStore>),
10 Required(Arc<rustls::RootCertStore>),
12}
13
14#[cfg(feature = "tls")]
15impl std::fmt::Debug for ClientAuth {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 match self {
18 ClientAuth::Optional(_) => f.debug_tuple("Optional").field(&"<root_store>").finish(),
19 ClientAuth::Required(_) => f.debug_tuple("Required").field(&"<root_store>").finish(),
20 }
21 }
22}
23
24#[derive(Clone)]
26pub enum TlsCert {
27 PemPaths {
29 cert_path: String,
31 key_path: String,
33 #[cfg(feature = "tls")]
35 client_auth: Option<ClientAuth>,
36 },
37 #[cfg(feature = "tls")]
39 Der {
40 certs: Arc<Vec<rustls::pki_types::CertificateDer<'static>>>,
42 key: Arc<rustls::pki_types::PrivateKeyDer<'static>>,
44 client_auth: Option<ClientAuth>,
46 },
47 #[cfg(feature = "tls")]
50 Resolver {
51 resolver: Arc<dyn rustls::server::ResolvesServerCert>,
53 client_auth: Option<ClientAuth>,
55 },
56}
57
58impl std::fmt::Debug for TlsCert {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 match self {
61 TlsCert::PemPaths {
62 cert_path,
63 key_path,
64 ..
65 } => f
66 .debug_struct("PemPaths")
67 .field("cert_path", cert_path)
68 .field("key_path", key_path)
69 .finish_non_exhaustive(),
70 #[cfg(feature = "tls")]
71 TlsCert::Der { client_auth, .. } => f
72 .debug_struct("Der")
73 .field("client_auth", client_auth)
74 .finish_non_exhaustive(),
75 #[cfg(feature = "tls")]
76 TlsCert::Resolver { client_auth, .. } => f
77 .debug_struct("Resolver")
78 .field("client_auth", client_auth)
79 .finish_non_exhaustive(),
80 }
81 }
82}
83
84impl TlsCert {
85 pub fn pem_paths(cert: impl Into<String>, key: impl Into<String>) -> Self {
87 Self::PemPaths {
88 cert_path: cert.into(),
89 key_path: key.into(),
90 #[cfg(feature = "tls")]
91 client_auth: None,
92 }
93 }
94
95 #[cfg(feature = "tls")]
97 pub fn pem_paths_with_client_auth(
98 cert: impl Into<String>,
99 key: impl Into<String>,
100 client_auth: ClientAuth,
101 ) -> Self {
102 Self::PemPaths {
103 cert_path: cert.into(),
104 key_path: key.into(),
105 client_auth: Some(client_auth),
106 }
107 }
108
109 #[cfg(feature = "tls")]
111 pub fn der(
112 certs: Vec<rustls::pki_types::CertificateDer<'static>>,
113 key: rustls::pki_types::PrivateKeyDer<'static>,
114 ) -> Self {
115 Self::Der {
116 certs: Arc::new(certs),
117 key: Arc::new(key),
118 client_auth: None,
119 }
120 }
121
122 #[cfg(feature = "tls")]
124 pub fn resolver(resolver: Arc<dyn rustls::server::ResolvesServerCert>) -> Self {
125 Self::Resolver {
126 resolver,
127 client_auth: None,
128 }
129 }
130
131 #[cfg(feature = "tls")]
133 pub fn with_client_auth(mut self, auth: ClientAuth) -> Self {
134 match &mut self {
135 TlsCert::PemPaths { client_auth, .. }
136 | TlsCert::Der { client_auth, .. }
137 | TlsCert::Resolver { client_auth, .. } => *client_auth = Some(auth),
138 }
139 self
140 }
141}
142
143#[cfg(feature = "tls")]
166pub struct ReloadableResolver {
167 current: arc_swap::ArcSwap<rustls::sign::CertifiedKey>,
168}
169
170#[cfg(feature = "tls")]
171impl std::fmt::Debug for ReloadableResolver {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 f.debug_struct("ReloadableResolver").finish_non_exhaustive()
174 }
175}
176
177#[cfg(feature = "tls")]
178impl ReloadableResolver {
179 pub fn from_pem(cert_path: &str, key_path: &str) -> anyhow::Result<Self> {
181 let ck = build_certified_key(cert_path, key_path)?;
182 Ok(Self {
183 current: arc_swap::ArcSwap::from_pointee(ck),
184 })
185 }
186
187 pub fn reload_from_pem(&self, cert_path: &str, key_path: &str) -> anyhow::Result<()> {
190 let ck = build_certified_key(cert_path, key_path)?;
191 self.current.store(Arc::new(ck));
192 Ok(())
193 }
194
195 pub fn reload(&self, ck: rustls::sign::CertifiedKey) {
197 self.current.store(Arc::new(ck));
198 }
199}
200
201#[cfg(feature = "tls")]
202impl rustls::server::ResolvesServerCert for ReloadableResolver {
203 fn resolve(
204 &self,
205 _client_hello: rustls::server::ClientHello<'_>,
206 ) -> Option<Arc<rustls::sign::CertifiedKey>> {
207 Some(self.current.load_full())
208 }
209}
210
211#[cfg(feature = "tls")]
212fn build_certified_key(
213 cert_path: &str,
214 key_path: &str,
215) -> anyhow::Result<rustls::sign::CertifiedKey> {
216 let certs = tako_rs_core::tls::load_certs(cert_path)?;
217 let key = tako_rs_core::tls::load_key(key_path)?;
218
219 let we_installed = if rustls::crypto::CryptoProvider::get_default().is_none() {
222 rustls::crypto::aws_lc_rs::default_provider()
223 .install_default()
224 .is_ok()
225 } else {
226 false
227 };
228 if !we_installed {
229 static WARNED: std::sync::Once = std::sync::Once::new();
230 WARNED.call_once(|| {
231 tracing::warn!(
232 "tako-server: a rustls CryptoProvider was already installed before \
233 `build_certified_key` ran — Tako will use that provider for key \
234 loading instead of installing aws-lc-rs. If signing behavior is \
235 not what you expect (e.g. h3 installed `ring` first), pin the \
236 provider at process startup with `rustls::crypto::aws_lc_rs::\
237 default_provider().install_default()` BEFORE constructing the \
238 server."
239 );
240 });
241 }
242 let provider = rustls::crypto::CryptoProvider::get_default().ok_or_else(|| {
243 anyhow::anyhow!(
244 "no rustls CryptoProvider installed — enable rustls's `aws_lc_rs` or `ring` feature"
245 )
246 })?;
247 let signer = provider
248 .key_provider
249 .load_private_key(key)
250 .map_err(|e| anyhow::anyhow!("failed to load signing key from '{key_path}': {e}"))?;
251 Ok(rustls::sign::CertifiedKey::new(certs, signer))
252}
253
254#[cfg(feature = "tls")]
257pub fn build_rustls_server_config(
258 cert: &TlsCert,
259 alpn: Vec<Vec<u8>>,
260) -> anyhow::Result<Arc<rustls::ServerConfig>> {
261 use rustls::ServerConfig as RustlsServerConfig;
262
263 if rustls::crypto::CryptoProvider::get_default().is_none() {
266 let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
267 }
268 let builder = RustlsServerConfig::builder();
269
270 let client_auth = match cert {
271 TlsCert::PemPaths { client_auth, .. }
272 | TlsCert::Der { client_auth, .. }
273 | TlsCert::Resolver { client_auth, .. } => client_auth.clone(),
274 };
275
276 let builder_with_auth = match client_auth {
277 Some(ClientAuth::Optional(roots)) => {
278 let verifier = rustls::server::WebPkiClientVerifier::builder(roots)
279 .allow_unauthenticated()
280 .build()
281 .map_err(|e| anyhow::anyhow!("WebPkiClientVerifier build failed: {e}"))?;
282 builder.with_client_cert_verifier(verifier)
283 }
284 Some(ClientAuth::Required(roots)) => {
285 let verifier = rustls::server::WebPkiClientVerifier::builder(roots)
286 .build()
287 .map_err(|e| anyhow::anyhow!("WebPkiClientVerifier build failed: {e}"))?;
288 builder.with_client_cert_verifier(verifier)
289 }
290 None => builder.with_no_client_auth(),
291 };
292
293 let mut config = match cert {
294 TlsCert::PemPaths {
295 cert_path,
296 key_path,
297 ..
298 } => {
299 let certs = tako_rs_core::tls::load_certs(cert_path)?;
300 let key = tako_rs_core::tls::load_key(key_path)?;
301 builder_with_auth
302 .with_single_cert(certs, key)
303 .map_err(|e| anyhow::anyhow!("rustls config build failed: {e}"))?
304 }
305 TlsCert::Der { certs, key, .. } => {
306 let certs = certs.as_ref().clone();
307 let key = key.as_ref().clone_key();
308 builder_with_auth
309 .with_single_cert(certs, key)
310 .map_err(|e| anyhow::anyhow!("rustls config build failed: {e}"))?
311 }
312 TlsCert::Resolver { resolver, .. } => builder_with_auth.with_cert_resolver(resolver.clone()),
313 };
314
315 config.alpn_protocols = alpn;
316 if config.alpn_protocols.iter().any(|p| p.as_slice() == b"h3") {
318 config.max_early_data_size = 0;
319 }
320 Ok(Arc::new(config))
321}