tako_rs_server/builder/
tls_cert.rs1#[cfg(feature = "tls")]
5use std::sync::Arc;
6
7#[cfg(feature = "tls")]
14#[derive(Clone)]
15pub enum ClientAuth {
16 Optional(Arc<rustls::RootCertStore>),
18 Required(Arc<rustls::RootCertStore>),
20}
21
22#[cfg(feature = "tls")]
23impl std::fmt::Debug for ClientAuth {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 ClientAuth::Optional(_) => f.debug_tuple("Optional").field(&"<root_store>").finish(),
27 ClientAuth::Required(_) => f.debug_tuple("Required").field(&"<root_store>").finish(),
28 }
29 }
30}
31
32#[derive(Clone)]
41pub enum TlsCert {
42 PemPaths {
44 cert_path: String,
46 key_path: String,
48 #[cfg(feature = "tls")]
50 client_auth: Option<ClientAuth>,
51 },
52 #[cfg(feature = "tls")]
55 Der {
56 certs: Arc<Vec<rustls::pki_types::CertificateDer<'static>>>,
58 key: Arc<rustls::pki_types::PrivateKeyDer<'static>>,
60 client_auth: Option<ClientAuth>,
62 },
63 #[cfg(feature = "tls")]
67 Resolver {
68 resolver: Arc<dyn rustls::server::ResolvesServerCert>,
70 client_auth: Option<ClientAuth>,
72 },
73}
74
75impl std::fmt::Debug for TlsCert {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match self {
78 TlsCert::PemPaths {
79 cert_path,
80 key_path,
81 ..
82 } => f
83 .debug_struct("PemPaths")
84 .field("cert_path", cert_path)
85 .field("key_path", key_path)
86 .finish_non_exhaustive(),
87 #[cfg(feature = "tls")]
88 TlsCert::Der { client_auth, .. } => f
89 .debug_struct("Der")
90 .field("client_auth", client_auth)
91 .finish_non_exhaustive(),
92 #[cfg(feature = "tls")]
93 TlsCert::Resolver { client_auth, .. } => f
94 .debug_struct("Resolver")
95 .field("client_auth", client_auth)
96 .finish_non_exhaustive(),
97 }
98 }
99}
100
101impl TlsCert {
102 pub fn pem_paths(cert: impl Into<String>, key: impl Into<String>) -> Self {
104 Self::PemPaths {
105 cert_path: cert.into(),
106 key_path: key.into(),
107 #[cfg(feature = "tls")]
108 client_auth: None,
109 }
110 }
111
112 #[cfg(feature = "tls")]
114 pub fn pem_paths_with_client_auth(
115 cert: impl Into<String>,
116 key: impl Into<String>,
117 client_auth: ClientAuth,
118 ) -> Self {
119 Self::PemPaths {
120 cert_path: cert.into(),
121 key_path: key.into(),
122 client_auth: Some(client_auth),
123 }
124 }
125
126 #[cfg(feature = "tls")]
128 pub fn der(
129 certs: Vec<rustls::pki_types::CertificateDer<'static>>,
130 key: rustls::pki_types::PrivateKeyDer<'static>,
131 ) -> Self {
132 Self::Der {
133 certs: Arc::new(certs),
134 key: Arc::new(key),
135 client_auth: None,
136 }
137 }
138
139 #[cfg(feature = "tls")]
142 pub fn resolver(resolver: Arc<dyn rustls::server::ResolvesServerCert>) -> Self {
143 Self::Resolver {
144 resolver,
145 client_auth: None,
146 }
147 }
148
149 #[cfg(feature = "tls")]
155 pub fn with_client_auth(mut self, auth: ClientAuth) -> Self {
156 match &mut self {
157 TlsCert::PemPaths { client_auth, .. }
158 | TlsCert::Der { client_auth, .. }
159 | TlsCert::Resolver { client_auth, .. } => *client_auth = Some(auth),
160 }
161 self
162 }
163}
164
165#[cfg(feature = "tls")]
190pub struct ReloadableResolver {
191 current: arc_swap::ArcSwap<rustls::sign::CertifiedKey>,
192}
193
194#[cfg(feature = "tls")]
195impl std::fmt::Debug for ReloadableResolver {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 f.debug_struct("ReloadableResolver").finish_non_exhaustive()
198 }
199}
200
201#[cfg(feature = "tls")]
202impl ReloadableResolver {
203 pub fn from_pem(cert_path: &str, key_path: &str) -> anyhow::Result<Self> {
205 let ck = build_certified_key(cert_path, key_path)?;
206 Ok(Self {
207 current: arc_swap::ArcSwap::from_pointee(ck),
208 })
209 }
210
211 pub fn reload_from_pem(&self, cert_path: &str, key_path: &str) -> anyhow::Result<()> {
216 let ck = build_certified_key(cert_path, key_path)?;
217 self.current.store(Arc::new(ck));
218 Ok(())
219 }
220
221 pub fn reload(&self, ck: rustls::sign::CertifiedKey) {
223 self.current.store(Arc::new(ck));
224 }
225}
226
227#[cfg(feature = "tls")]
228impl rustls::server::ResolvesServerCert for ReloadableResolver {
229 fn resolve(
230 &self,
231 _client_hello: rustls::server::ClientHello<'_>,
232 ) -> Option<Arc<rustls::sign::CertifiedKey>> {
233 Some(self.current.load_full())
234 }
235}
236
237#[cfg(feature = "tls")]
238fn build_certified_key(
239 cert_path: &str,
240 key_path: &str,
241) -> anyhow::Result<rustls::sign::CertifiedKey> {
242 let certs = tako_rs_core::tls::load_certs(cert_path)?;
243 let key = tako_rs_core::tls::load_key(key_path)?;
244
245 let we_installed = if rustls::crypto::CryptoProvider::get_default().is_none() {
257 rustls::crypto::aws_lc_rs::default_provider()
258 .install_default()
259 .is_ok()
260 } else {
261 false
262 };
263 if !we_installed {
264 static WARNED: std::sync::Once = std::sync::Once::new();
269 WARNED.call_once(|| {
270 tracing::warn!(
271 "tako-server: a rustls CryptoProvider was already installed before \
272 `build_certified_key` ran — Tako will use that provider for key \
273 loading instead of installing aws-lc-rs. If signing behavior is \
274 not what you expect (e.g. h3 installed `ring` first), pin the \
275 provider at process startup with `rustls::crypto::aws_lc_rs::\
276 default_provider().install_default()` BEFORE constructing the \
277 server."
278 );
279 });
280 }
281 let provider = rustls::crypto::CryptoProvider::get_default().ok_or_else(|| {
282 anyhow::anyhow!(
283 "no rustls CryptoProvider installed — enable rustls's `aws_lc_rs` or `ring` feature"
284 )
285 })?;
286 let signer = provider
287 .key_provider
288 .load_private_key(key)
289 .map_err(|e| anyhow::anyhow!("failed to load signing key from '{key_path}': {e}"))?;
290 Ok(rustls::sign::CertifiedKey::new(certs, signer))
291}
292
293#[cfg(feature = "tls")]
300pub fn build_rustls_server_config(
301 cert: &TlsCert,
302 alpn: Vec<Vec<u8>>,
303) -> anyhow::Result<Arc<rustls::ServerConfig>> {
304 use rustls::ServerConfig as RustlsServerConfig;
305
306 let builder = RustlsServerConfig::builder();
307
308 let client_auth = match cert {
312 TlsCert::PemPaths { client_auth, .. }
313 | TlsCert::Der { client_auth, .. }
314 | TlsCert::Resolver { client_auth, .. } => client_auth.clone(),
315 };
316
317 let builder_with_auth = match client_auth {
318 Some(ClientAuth::Optional(roots)) => {
319 let verifier = rustls::server::WebPkiClientVerifier::builder(roots)
320 .allow_unauthenticated()
321 .build()
322 .map_err(|e| anyhow::anyhow!("WebPkiClientVerifier build failed: {e}"))?;
323 builder.with_client_cert_verifier(verifier)
324 }
325 Some(ClientAuth::Required(roots)) => {
326 let verifier = rustls::server::WebPkiClientVerifier::builder(roots)
327 .build()
328 .map_err(|e| anyhow::anyhow!("WebPkiClientVerifier build failed: {e}"))?;
329 builder.with_client_cert_verifier(verifier)
330 }
331 None => builder.with_no_client_auth(),
332 };
333
334 let mut config = match cert {
335 TlsCert::PemPaths {
336 cert_path,
337 key_path,
338 ..
339 } => {
340 let certs = tako_rs_core::tls::load_certs(cert_path)?;
341 let key = tako_rs_core::tls::load_key(key_path)?;
342 builder_with_auth
343 .with_single_cert(certs, key)
344 .map_err(|e| anyhow::anyhow!("rustls config build failed: {e}"))?
345 }
346 TlsCert::Der { certs, key, .. } => {
347 let certs = certs.as_ref().clone();
348 let key = key.as_ref().clone_key();
349 builder_with_auth
350 .with_single_cert(certs, key)
351 .map_err(|e| anyhow::anyhow!("rustls config build failed: {e}"))?
352 }
353 TlsCert::Resolver { resolver, .. } => builder_with_auth.with_cert_resolver(resolver.clone()),
354 };
355
356 config.alpn_protocols = alpn;
357 if config.alpn_protocols.iter().any(|p| p.as_slice() == b"h3") {
364 config.max_early_data_size = 0;
365 }
366 Ok(Arc::new(config))
367}