1#[cfg(test)]
7use std::collections::HashSet;
8use std::{
9 collections::HashMap,
10 fmt,
11 str::FromStr,
12 sync::{Arc, LazyLock, Mutex},
13};
14
15use rustls::{
16 pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
17 server::{ClientHello, ResolvesServerCert},
18 sign::CertifiedKey,
19};
20
21use crate::crypto::any_supported_type;
22use sha2::{Digest, Sha256};
23use sozu_command::{
24 certificate::{
25 CertificateError, Fingerprint, get_cn_and_san_attributes, parse_pem, parse_x509,
26 split_certificate_chain,
27 },
28 logging::ansi_palette,
29 proto::command::{AddCertificate, CertificateAndKey, ReplaceCertificate, SocketAddress},
30};
31
32use crate::metrics::names;
33use crate::router::MAX_HOSTNAME_LENGTH;
34use crate::router::pattern_trie::{InsertResult, Key, KeyValue, TrieNode};
35
36macro_rules! log_module_context {
45 () => {{
46 let (open, reset, _, _, _) = ansi_palette();
47 format!(
48 "{open}TLS-RESOLVER{reset}\t >>>",
49 open = open,
50 reset = reset
51 )
52 }};
53}
54
55static DEFAULT_CERTIFICATE: LazyLock<Option<Arc<CertifiedKey>>> = LazyLock::new(|| {
59 let add = AddCertificate {
60 certificate: CertificateAndKey {
61 certificate: include_str!("../assets/certificate.pem").to_string(),
62 certificate_chain: vec![include_str!("../assets/certificate_chain.pem").to_string()],
63 key: include_str!("../assets/key.pem").to_string(),
64 versions: vec![],
65 names: vec![],
66 },
67 address: SocketAddress::new_v4(0, 0, 0, 0, 8080), expired_at: None,
69 };
70 CertifiedKeyWrapper::try_from(&add).ok().map(|c| c.inner)
71});
72
73#[derive(thiserror::Error, Debug)]
74pub enum CertificateResolverError {
75 #[error("failed to get common name and subject alternate names from pem, {0}")]
76 InvalidCommonNameAndSubjectAlternateNames(CertificateError),
77 #[error("invalid private key: {0}")]
78 InvalidPrivateKey(String),
79 #[error("empty key")]
80 EmptyKeys,
81 #[error("error parsing x509 cert from bytes: {0}")]
82 ParseX509(CertificateError),
83 #[error("error parsing pem formated certificate from bytes: {0}")]
84 ParsePem(CertificateError),
85 #[error("error parsing overriding names in new certificate: {0}")]
86 ParseOverridingNames(CertificateError),
87 #[error("the SNI route table cannot host a certificate name, name_bytes={}", .0.len())]
88 InvalidName(String),
89}
90
91#[derive(Clone)]
95pub struct CertifiedKeyWrapper {
96 inner: Arc<CertifiedKey>,
97 names: Vec<String>,
99 expiration: i64,
100 fingerprint: Fingerprint,
101}
102
103impl fmt::Debug for CertifiedKeyWrapper {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 let names_len = self
106 .names
107 .iter()
108 .map(String::len)
109 .fold(0usize, usize::saturating_add);
110
111 f.debug_struct("CertifiedKeyWrapper")
112 .field("certificate", &"[redacted]")
113 .field("certificate_chain_count", &self.inner.cert.len())
114 .field("private_key", &"[redacted]")
115 .field("names_count", &self.names.len())
116 .field("names_len", &names_len)
117 .field("expiration", &self.expiration)
118 .field("fingerprint_bytes", &self.fingerprint.0.len())
119 .finish()
120 }
121}
122
123impl TryFrom<&AddCertificate> for CertifiedKeyWrapper {
126 type Error = CertificateResolverError;
127
128 fn try_from(add: &AddCertificate) -> Result<Self, Self::Error> {
129 let cert = add.certificate.clone();
130
131 let pem =
132 parse_pem(cert.certificate.as_bytes()).map_err(CertificateResolverError::ParsePem)?;
133
134 let x509 = parse_x509(&pem.contents).map_err(CertificateResolverError::ParseX509)?;
135
136 let overriding_names = if add.certificate.names.is_empty() {
150 get_cn_and_san_attributes(&x509)
151 } else {
152 add.certificate.names.clone()
153 };
154 let overriding_names: Vec<String> = overriding_names
155 .into_iter()
156 .map(|mut name| {
157 name.make_ascii_lowercase();
158 name
159 })
160 .collect();
161
162 let expiration = add
163 .expired_at
164 .unwrap_or(x509.validity().not_after.timestamp());
165
166 let fingerprint = Fingerprint(Sha256::digest(&pem.contents).iter().cloned().collect());
167
168 let leaf_der = pem.contents;
178 let mut chain = vec![CertificateDer::from(leaf_der.to_owned())];
179 let mut dropped_duplicates = 0usize;
180 for cert in &cert.certificate_chain {
181 for split_pem in split_certificate_chain(cert.to_owned()) {
182 let chain_link = parse_pem(split_pem.as_bytes())
183 .map_err(CertificateResolverError::ParsePem)?
184 .contents;
185
186 if chain_link == leaf_der {
187 dropped_duplicates += 1;
188 continue;
189 }
190 chain.push(CertificateDer::from(chain_link));
191 }
192 }
193 if dropped_duplicates > 0 {
194 debug!(
195 "{} dropped {} duplicate leaf certificate(s) from the supplied chain",
196 log_module_context!(),
197 dropped_duplicates
198 );
199 }
200
201 let private_key = PrivateKeyDer::from_pem_slice(cert.key.as_bytes())
210 .map_err(|_| CertificateResolverError::EmptyKeys)?;
211
212 debug_assert!(
217 !chain.is_empty(),
218 "assembled certificate chain must contain at least the leaf"
219 );
220 debug_assert_eq!(
221 chain[0].as_ref(),
222 leaf_der.as_slice(),
223 "the leaf must remain at index 0 of the chain"
224 );
225 debug_assert_eq!(
228 fingerprint.0.len(),
229 32,
230 "a SHA-256 fingerprint must be 32 bytes"
231 );
232
233 match any_supported_type(&private_key) {
234 Ok(signing_key) => {
235 let stored_certificate = CertifiedKeyWrapper {
236 inner: Arc::new(CertifiedKey::new(chain, signing_key)),
237 names: overriding_names,
238 expiration,
239 fingerprint,
240 };
241 Ok(stored_certificate)
242 }
243 Err(sign_error) => Err(CertificateResolverError::InvalidPrivateKey(
244 sign_error.to_string(),
245 )),
246 }
247 }
248}
249
250#[derive(Default, Clone)]
259pub struct CertificateResolver {
260 pub domains: TrieNode<Fingerprint>,
262 certificates: HashMap<Fingerprint, CertifiedKeyWrapper>,
264 name_fingerprint_idx: HashMap<String, Vec<(Fingerprint, i64)>>,
268}
269
270impl fmt::Debug for CertificateResolver {
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 let indexed_certificate_links = self
273 .name_fingerprint_idx
274 .values()
275 .map(Vec::len)
276 .fold(0usize, usize::saturating_add);
277
278 f.debug_struct("CertificateResolver")
279 .field("domains", &"[redacted]")
280 .field("certificates_count", &self.certificates.len())
281 .field("indexed_names_count", &self.name_fingerprint_idx.len())
282 .field("indexed_certificate_links", &indexed_certificate_links)
283 .finish()
284 }
285}
286
287impl CertificateResolver {
288 pub fn get_certificate(&self, fingerprint: &Fingerprint) -> Option<CertifiedKeyWrapper> {
290 self.certificates.get(fingerprint).map(ToOwned::to_owned)
291 }
292
293 fn publish_min_expiration_gauge(&self) {
307 let Some(min_expiration) = self.certificates.values().map(|c| c.expiration).min() else {
308 return;
316 };
317 let clamped = min_expiration.max(0) as usize;
318 gauge!(names::tls::CERT_MIN_EXPIRES_AT_SECONDS, clamped);
319 }
320
321 pub fn add_certificate(
324 &mut self,
325 add: &AddCertificate,
326 ) -> Result<Fingerprint, CertificateResolverError> {
327 let cert_to_add = CertifiedKeyWrapper::try_from(add)?;
328
329 trace!(
330 "{} adding certificate {:?}",
331 log_module_context!(),
332 cert_to_add
333 );
334
335 if self.certificates.contains_key(&cert_to_add.fingerprint) {
336 return Ok(cert_to_add.fingerprint);
337 }
338
339 {
365 let mut scratch: TrieNode<()> = TrieNode::root();
366 for name in &cert_to_add.names {
367 if name.len() > MAX_HOSTNAME_LENGTH
368 || name.contains('/')
369 || scratch.domain_insert(name.to_owned().into_bytes(), ())
370 == InsertResult::Failed
371 {
372 return Err(CertificateResolverError::InvalidName(name.to_owned()));
373 }
374 }
375 }
376
377 let certificates_before = self.certificates.len();
382 let new_fingerprint = cert_to_add.fingerprint.clone();
383 debug_assert!(
384 !self.certificates.contains_key(&new_fingerprint),
385 "add_certificate past the dedup guard must be inserting a new fingerprint"
386 );
387
388 for new_name in &cert_to_add.names {
389 let fingerprints_for_this_name = self
390 .name_fingerprint_idx
391 .entry(new_name.to_owned())
392 .or_default();
393
394 fingerprints_for_this_name
395 .push((cert_to_add.fingerprint.clone(), cert_to_add.expiration));
396
397 fingerprints_for_this_name.sort_by_key(|t| t.1);
399
400 let longest_lived_cert = match fingerprints_for_this_name.last() {
401 Some(cert) => cert,
402 None => {
403 error!(
404 "{} no fingerprint for this name, this should not happen",
405 log_module_context!()
406 );
407 continue;
408 }
409 };
410
411 self.domains.remove(&new_name.to_owned().into_bytes());
413 let insert_result = self.domains.insert(
414 new_name.to_owned().into_bytes(),
415 longest_lived_cert.0.to_owned(),
416 );
417 debug_assert_ne!(
421 insert_result,
422 InsertResult::Failed,
423 "add_certificate's names must already be validated by its dry-run"
424 );
425 if insert_result == InsertResult::Failed {
426 error!(
427 "{} the SNI trie rejected a certificate name despite passing \
428 the dry-run validation, name_bytes={}",
429 log_module_context!(),
430 new_name.len(),
431 );
432 }
433 }
434
435 self.certificates
436 .insert(cert_to_add.fingerprint.to_owned(), cert_to_add.clone());
437 self.publish_min_expiration_gauge();
438
439 debug_assert!(
443 self.certificates.contains_key(&new_fingerprint),
444 "add_certificate must store the new certificate"
445 );
446 debug_assert_eq!(
447 self.certificates.len(),
448 certificates_before + 1,
449 "add_certificate must grow the store by exactly one"
450 );
451 debug_assert!(
452 cert_to_add.names.iter().all(|name| {
453 self.name_fingerprint_idx
454 .get(name)
455 .is_some_and(|fps| fps.iter().any(|(fp, _)| *fp == new_fingerprint))
456 }),
457 "every name of the added cert must be indexed to its fingerprint"
458 );
459
460 trace!("{} {:#?}", log_module_context!(), self);
461
462 Ok(cert_to_add.fingerprint)
463 }
464
465 pub fn remove_certificate(
468 &mut self,
469 fingerprint: &Fingerprint,
470 ) -> Result<(), CertificateResolverError> {
471 let certificates_before = self.certificates.len();
476 let was_present = self.certificates.contains_key(fingerprint);
477
478 if let Some(certificate_to_remove) = self.get_certificate(fingerprint) {
479 #[cfg(debug_assertions)]
483 let removed_names = certificate_to_remove.names.clone();
484 for name in certificate_to_remove.names {
485 self.domains.domain_remove(&name.as_bytes().to_vec());
486
487 if let std::collections::hash_map::Entry::Occupied(mut entry) =
488 self.name_fingerprint_idx.entry(name.to_owned())
489 {
490 entry.get_mut().retain(|t| &t.0 != fingerprint);
492
493 if let Some(longest_lived_cert) = entry.get().last() {
495 let insert_result = self
496 .domains
497 .insert(name.as_bytes().to_vec(), longest_lived_cert.0.to_owned());
498 debug_assert_ne!(
502 insert_result,
503 InsertResult::Failed,
504 "remove_certificate re-inserts names that were validated on add"
505 );
506 if insert_result == InsertResult::Failed {
507 error!(
508 "{} the SNI trie rejected a re-inserted certificate name \
509 that was validated on add, name_bytes={}",
510 log_module_context!(),
511 name.len(),
512 );
513 }
514 }
515
516 if entry.get().is_empty() {
518 entry.remove();
519 }
520 }
521 }
522
523 self.certificates.remove(fingerprint);
524 self.publish_min_expiration_gauge();
525
526 debug_assert!(
531 !self.certificates.contains_key(fingerprint),
532 "remove_certificate must evict the fingerprint"
533 );
534 debug_assert_eq!(
535 self.certificates.len(),
536 certificates_before - 1,
537 "removing a present cert must shrink the store by exactly one"
538 );
539 #[cfg(debug_assertions)]
540 debug_assert!(
541 removed_names.iter().all(|name| {
542 self.name_fingerprint_idx
543 .get(name)
544 .is_none_or(|fps| fps.iter().all(|(fp, _)| fp != fingerprint))
545 }),
546 "no name may still index the removed fingerprint"
547 );
548 } else {
549 debug_assert!(
551 !was_present,
552 "the absent-cert branch must only run when the fingerprint was not stored"
553 );
554 debug_assert_eq!(
555 self.certificates.len(),
556 certificates_before,
557 "removing an absent cert must not change the store"
558 );
559 }
560 trace!("{} {:#?}", log_module_context!(), self);
561
562 Ok(())
563 }
564
565 pub fn replace_certificate(
569 &mut self,
570 replace: &ReplaceCertificate,
571 ) -> Result<Fingerprint, CertificateResolverError> {
572 let add = AddCertificate {
573 address: replace.address.to_owned(),
574 certificate: replace.new_certificate.to_owned(),
575 expired_at: replace.new_expired_at.to_owned(),
576 };
577
578 let new_cert = CertifiedKeyWrapper::try_from(&add)?;
593 let new_fingerprint = new_cert.fingerprint.to_owned();
594
595 if let Ok(old_fingerprint) = Fingerprint::from_str(&replace.old_fingerprint)
596 && old_fingerprint == new_fingerprint
597 {
598 let stored_before = self.certificates.contains_key(&new_fingerprint);
603 self.publish_min_expiration_gauge();
606 debug_assert_eq!(
607 self.certificates.contains_key(&new_fingerprint),
608 stored_before,
609 "idempotent replace must not change whether the cert is stored"
610 );
611 return Ok(new_fingerprint);
612 }
613
614 let new_fingerprint = self.add_certificate(&add)?;
615
616 debug_assert!(
621 self.certificates.contains_key(&new_fingerprint),
622 "the replacement certificate must be stored before the old one is removed"
623 );
624
625 match Fingerprint::from_str(&replace.old_fingerprint) {
626 Ok(old_fingerprint) => self.remove_certificate(&old_fingerprint)?,
627 Err(err) => {
628 warn!(
633 "{} new certificate added but could not remove old one: \
634 failed to parse old fingerprint, {}",
635 log_module_context!(),
636 err
637 );
638 }
639 }
640
641 Ok(new_fingerprint)
642 }
643
644 #[cfg(test)]
647 fn find_certificates_by_names(
648 &self,
649 names: &HashSet<String>,
650 ) -> Result<HashSet<Fingerprint>, CertificateResolverError> {
651 let mut fingerprints = HashSet::new();
652 for name in names {
653 if let Some(fprints) = self.name_fingerprint_idx.get(name) {
654 fprints.iter().for_each(|fingerprint| {
655 fingerprints.insert(fingerprint.to_owned().0);
656 });
657 }
658 }
659
660 Ok(fingerprints)
661 }
662
663 #[cfg(test)]
666 fn certificate_names(
667 &self,
668 fingerprint: &Fingerprint,
669 ) -> Result<HashSet<String>, CertificateResolverError> {
670 if let Some(cert) = self.certificates.get(fingerprint) {
671 return Ok(cert.names.iter().cloned().collect());
672 }
673 Ok(HashSet::new())
674 }
675
676 pub fn domain_lookup(
677 &self,
678 domain: &[u8],
679 accept_wildcard: bool,
680 ) -> Option<&KeyValue<Key, Fingerprint>> {
681 self.domains.domain_lookup(domain, accept_wildcard)
682 }
683
684 pub fn names_for_sni(&self, domain: &[u8]) -> Option<Vec<String>> {
696 let (_, fingerprint) = self.domain_lookup(domain, true)?;
697 self.certificates
698 .get(fingerprint)
699 .map(|cert| cert.names.clone())
700 }
701}
702
703#[derive(Default)]
707pub struct MutexCertificateResolver(pub Mutex<CertificateResolver>);
708
709impl ResolvesServerCert for MutexCertificateResolver {
710 fn resolve(&self, client_hello: ClientHello) -> Option<Arc<CertifiedKey>> {
711 let server_name = client_hello.server_name();
712 let sigschemes = client_hello.signature_schemes();
713
714 let Some(name) = server_name else {
715 error!(
716 "{} cannot look up certificate: no SNI from session",
717 log_module_context!()
718 );
719 return None;
720 };
721 trace!(
722 "{} trying to resolve certificate with name_bytes={} signature_schemes_count={}",
723 log_module_context!(),
724 name.len(),
725 sigschemes.len()
726 );
727 let resolver = match self.0.lock() {
737 Ok(guard) => guard,
738 Err(_poisoned) => {
739 error!(
740 "{} cert resolver mutex poisoned, returning default cert",
741 log_module_context!()
742 );
743 return DEFAULT_CERTIFICATE.clone();
744 }
745 };
746 if let Some((_, fingerprint)) = resolver.domains.domain_lookup(name.as_bytes(), true) {
747 trace!(
748 "{} looking for certificate with name_bytes={} fingerprint_bytes={}",
749 log_module_context!(),
750 name.len(),
751 fingerprint.0.len()
752 );
753
754 let cert = resolver
763 .certificates
764 .get(fingerprint)
765 .map(|cert| cert.inner.clone());
766 debug_assert!(
767 cert.is_none()
768 || resolver
769 .certificates
770 .get(fingerprint)
771 .is_some_and(|stored| Arc::ptr_eq(&stored.inner, cert.as_ref().unwrap())),
772 "resolved certificate must be the one stored under the looked-up fingerprint"
773 );
774
775 trace!(
776 "{} certificate lookup fingerprint_bytes={} found={}",
777 log_module_context!(),
778 fingerprint.0.len(),
779 cert.is_some()
780 );
781 return cert;
782 }
783 drop(resolver);
784
785 debug!(
789 "{} default certificate is used for name_bytes={}",
790 log_module_context!(),
791 name.len()
792 );
793 incr!(names::tls::DEFAULT_CERT_USED);
794 DEFAULT_CERTIFICATE.clone()
795 }
796}
797
798impl MutexCertificateResolver {
799 pub fn names_for_sni(&self, domain: &[u8]) -> Option<Vec<String>> {
805 match self.0.lock() {
806 Ok(guard) => guard.names_for_sni(domain),
807 Err(_poisoned) => {
808 error!(
809 "{} cert resolver mutex poisoned, treating as no SAN match",
810 log_module_context!()
811 );
812 None
813 }
814 }
815 }
816}
817
818impl fmt::Debug for MutexCertificateResolver {
819 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
820 f.write_str("MutexWrappedCertificateResolver")
821 }
822}
823
824#[cfg(test)]
828mod tests {
829 use std::{
830 collections::HashSet,
831 error::Error,
832 io::Cursor,
833 sync::Arc,
834 time::{Duration, SystemTime},
835 };
836
837 use sozu_command::proto::command::{
839 AddCertificate, CertificateAndKey, ReplaceCertificate, SocketAddress,
840 };
841
842 use super::{
843 CertificateResolver, CertificateResolverError, CertifiedKeyWrapper,
844 MutexCertificateResolver,
845 };
846
847 fn drive_client_hello(resolver: Arc<MutexCertificateResolver>, server_name: String) {
848 let provider = Arc::new(crate::crypto::default_provider());
849 let server_config = rustls::ServerConfig::builder_with_provider(provider.clone())
850 .with_protocol_versions(&[&rustls::version::TLS13])
851 .expect("test server provider must support TLS 1.3")
852 .with_no_client_auth()
853 .with_cert_resolver(resolver);
854 let client_config = rustls::ClientConfig::builder_with_provider(provider)
855 .with_protocol_versions(&[&rustls::version::TLS13])
856 .expect("test client provider must support TLS 1.3")
857 .with_root_certificates(rustls::RootCertStore::empty())
858 .with_no_client_auth();
859 let server_name = rustls::pki_types::ServerName::try_from(server_name)
860 .expect("test SNI must be a valid DNS name");
861 let mut client = rustls::ClientConnection::new(Arc::new(client_config), server_name)
862 .expect("test client connection must initialize");
863 let mut server = rustls::ServerConnection::new(Arc::new(server_config))
864 .expect("test server connection must initialize");
865
866 let mut client_hello = Vec::new();
867 client
868 .write_tls(&mut client_hello)
869 .expect("test client hello must serialize");
870 server
871 .read_tls(&mut Cursor::new(client_hello))
872 .expect("test server must read the client hello");
873 server
874 .process_new_packets()
875 .expect("test server must process the client hello");
876 }
877
878 #[test]
886 fn add_certificate_rejects_a_name_the_route_table_cannot_host() {
887 for name in ["sni-secret.example/", ".sni-secret.example"] {
888 let mut resolver = CertificateResolver::default();
889 let result = resolver.add_certificate(&AddCertificate {
890 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
891 certificate: CertificateAndKey {
892 certificate: include_str!("../assets/certificate.pem").to_owned(),
893 key: include_str!("../assets/key.pem").to_owned(),
894 names: vec![(*name).to_owned()],
895 ..Default::default()
896 },
897 expired_at: None,
898 });
899 assert!(result.is_err(), "{name:?} must be rejected, not stored");
900 assert!(
901 resolver.certificates.is_empty(),
902 "{name:?} was rejected but the certificate store was mutated",
903 );
904 assert!(
905 resolver.name_fingerprint_idx.is_empty(),
906 "{name:?} was rejected but the name index was mutated",
907 );
908 assert!(
909 resolver.domains.is_empty(),
910 "{name:?} was rejected but the SNI trie was mutated",
911 );
912 }
913 }
914
915 #[cfg(any(debug_assertions, feature = "logs-trace"))]
917 #[test]
918 fn certificate_resolver_logs_redact_matching_sni_and_fingerprint() {
919 const SNI_SECRET: &str = "resolver-sni-secret.example";
920
921 let certificate = CertificateAndKey {
922 certificate: include_str!("../assets/certificate.pem").to_owned(),
923 key: include_str!("../assets/key.pem").to_owned(),
924 names: vec![SNI_SECRET.to_owned()],
925 ..Default::default()
926 };
927 let fingerprint = certificate
928 .fingerprint()
929 .expect("test certificate fingerprint must be computable")
930 .to_string();
931 let output = crate::capture_test_logs_at_level("trace", move || {
932 let resolver = Arc::new(MutexCertificateResolver::default());
933 resolver
934 .0
935 .lock()
936 .expect("test resolver lock must be available")
937 .add_certificate(&AddCertificate {
938 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
939 certificate,
940 expired_at: None,
941 })
942 .expect("test certificate must load into the resolver");
943 drive_client_hello(resolver, SNI_SECRET.to_owned());
944 });
945
946 assert!(
947 !output.contains(SNI_SECRET),
948 "TLS resolver logs leaked the matching SNI: {output}"
949 );
950 assert!(
951 !output.contains(&fingerprint),
952 "TLS resolver logs leaked the matching certificate fingerprint: {output}"
953 );
954 for metadata in [
955 format!("name_bytes={}", SNI_SECRET.len()),
956 "fingerprint_bytes=32".to_owned(),
957 ] {
958 assert!(
959 output.contains(&metadata),
960 "TLS resolver logs omitted bounded metadata {metadata}: {output}"
961 );
962 }
963 assert!(
964 output.len() <= 4096,
965 "TLS resolver log capture is not bounded: {} bytes",
966 output.len()
967 );
968 }
969
970 #[cfg(any(debug_assertions, feature = "logs-trace"))]
972 #[test]
973 fn certificate_resolver_logs_redact_fallback_sni() {
974 const SNI_SECRET: &str = "fallback-sni-secret.example";
975
976 let output = crate::capture_test_logs_at_level("trace", move || {
977 drive_client_hello(
978 Arc::new(MutexCertificateResolver::default()),
979 SNI_SECRET.to_owned(),
980 );
981 });
982
983 assert!(
984 !output.contains(SNI_SECRET),
985 "TLS resolver logs leaked the fallback SNI: {output}"
986 );
987 assert!(
988 output.contains(&format!("name_bytes={}", SNI_SECRET.len())),
989 "TLS resolver fallback log omitted bounded SNI metadata: {output}"
990 );
991 assert!(
992 output.len() <= 2048,
993 "TLS resolver fallback log capture is not bounded: {} bytes",
994 output.len()
995 );
996 }
997
998 #[test]
999 fn certificate_resolver_poison_logs_are_bounded() {
1000 const SNI_SECRET: &str = "poison-sni-secret.example";
1001 const QUERY_SECRET: &str = "POISON_QUERY_SECRET_SENTINEL";
1002
1003 let query = format!("{QUERY_SECRET}{}", "x".repeat(4096));
1004 let output = crate::capture_test_logs_at_level("trace", move || {
1005 let resolver = Arc::new(MutexCertificateResolver::default());
1006 let poison_target = resolver.clone();
1007 assert!(
1008 std::thread::spawn(move || {
1009 let _guard = poison_target
1010 .0
1011 .lock()
1012 .expect("test resolver lock must initially be available");
1013 panic!("intentionally poison the test resolver lock");
1014 })
1015 .join()
1016 .is_err(),
1017 "test poison thread must panic"
1018 );
1019
1020 drive_client_hello(resolver.clone(), SNI_SECRET.to_owned());
1021 assert!(resolver.names_for_sni(query.as_bytes()).is_none());
1022 });
1023
1024 assert!(
1025 !output.contains(SNI_SECRET),
1026 "TLS resolver poison log leaked the handshake SNI: {output}"
1027 );
1028 assert!(
1029 !output.contains(QUERY_SECRET),
1030 "TLS resolver poison log leaked the 4 KiB direct query: {output}"
1031 );
1032 assert!(
1033 output.len() <= 2048,
1034 "TLS resolver poison log capture is not bounded: {} bytes",
1035 output.len()
1036 );
1037 }
1038
1039 #[test]
1040 fn certified_key_wrapper_debug_redacts_runtime_certificate_material() {
1041 const NAME_SECRET: &str = "CERTIFIED_KEY_NAME_SECRET_SENTINEL";
1042
1043 let add = AddCertificate {
1044 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
1045 certificate: CertificateAndKey {
1046 certificate: include_str!("../assets/certificate.pem").to_owned(),
1047 key: include_str!("../assets/key.pem").to_owned(),
1048 names: vec![format!("{NAME_SECRET}{}", "x".repeat(4096))],
1049 ..Default::default()
1050 },
1051 expired_at: None,
1052 };
1053 let certified_key =
1054 CertifiedKeyWrapper::try_from(&add).expect("test certificate must parse");
1055 let fingerprint = certified_key.fingerprint.to_string();
1056 let output = format!("{certified_key:?}");
1057
1058 assert!(
1059 !output.contains(NAME_SECRET),
1060 "CertifiedKeyWrapper Debug leaked an overridden certificate name: {output}"
1061 );
1062 assert!(
1063 !output.contains(&fingerprint),
1064 "CertifiedKeyWrapper Debug leaked the certificate fingerprint: {output}"
1065 );
1066 assert!(
1067 output.contains("fingerprint_bytes: 32"),
1068 "CertifiedKeyWrapper Debug omitted bounded fingerprint metadata: {output}"
1069 );
1070 assert!(
1071 output.contains("[redacted]"),
1072 "CertifiedKeyWrapper Debug must make certificate redaction explicit: {output}"
1073 );
1074 assert!(
1075 output.len() <= 1024,
1076 "CertifiedKeyWrapper Debug output is not bounded: {} bytes",
1077 output.len()
1078 );
1079 }
1080
1081 #[test]
1082 fn certificate_resolver_debug_redacts_indexed_names() {
1083 const NAME_SECRET: &str = "CERTIFICATE_RESOLVER_NAME_SECRET_SENTINEL";
1084
1085 let mut resolver = CertificateResolver::default();
1086 resolver
1087 .add_certificate(&AddCertificate {
1088 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
1089 certificate: CertificateAndKey {
1090 certificate: include_str!("../assets/certificate.pem").to_owned(),
1091 key: include_str!("../assets/key.pem").to_owned(),
1092 names: vec![format!("{NAME_SECRET}{}", "x".repeat(2048))],
1097 ..Default::default()
1098 },
1099 expired_at: None,
1100 })
1101 .expect("test certificate must load into the resolver");
1102 let output = format!("{resolver:?}");
1103
1104 assert!(
1105 !output.contains(NAME_SECRET),
1106 "CertificateResolver Debug leaked an indexed certificate name: {output}"
1107 );
1108 assert!(
1109 output.len() <= 1024,
1110 "CertificateResolver Debug output is not bounded: {} bytes",
1111 output.len()
1112 );
1113 }
1114
1115 #[test]
1116 fn lifecycle() -> Result<(), Box<dyn Error + Send + Sync>> {
1117 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1118 let mut resolver = CertificateResolver::default();
1119 let certificate_and_key = CertificateAndKey {
1120 certificate: String::from(include_str!("../assets/certificate.pem")),
1121 key: String::from(include_str!("../assets/key.pem")),
1122 ..Default::default()
1123 };
1124
1125 let fingerprint = resolver
1126 .add_certificate(&AddCertificate {
1127 address,
1128 certificate: certificate_and_key,
1129 expired_at: None,
1130 })
1131 .expect("could not add certificate");
1132
1133 if resolver.get_certificate(&fingerprint).is_none() {
1134 return Err("failed to retrieve certificate".into());
1135 }
1136
1137 let names = resolver.certificate_names(&fingerprint)?;
1139
1140 if let Err(err) = resolver.remove_certificate(&fingerprint) {
1141 return Err(format!("the certificate was not removed, {err}").into());
1142 }
1143
1144 if resolver.get_certificate(&fingerprint).is_some() {
1145 return Err("We have retrieved the certificate that should be deleted".into());
1146 }
1147
1148 if !resolver.find_certificates_by_names(&names)?.is_empty() {
1149 return Err(
1150 "The certificate should be deleted but one of its names is in the index".into(),
1151 );
1152 }
1153
1154 Ok(())
1155 }
1156
1157 #[test]
1158 fn name_override() -> Result<(), Box<dyn Error + Send + Sync>> {
1159 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1160 let mut resolver = CertificateResolver::default();
1161 let certificate_and_key = CertificateAndKey {
1162 certificate: String::from(include_str!("../assets/certificate.pem")),
1163 key: String::from(include_str!("../assets/key.pem")),
1164 names: vec!["localhost".into(), "lolcatho.st".into()],
1165 ..Default::default()
1166 };
1167
1168 let fingerprint = resolver.add_certificate(&AddCertificate {
1169 address,
1170 certificate: certificate_and_key,
1171 expired_at: None,
1172 })?;
1173
1174 if resolver.get_certificate(&fingerprint).is_none() {
1175 return Err("failed to retrieve certificate".into());
1176 }
1177
1178 let mut lolcat = HashSet::new();
1179 lolcat.insert(String::from("lolcatho.st"));
1180 if resolver.find_certificates_by_names(&lolcat)?.is_empty()
1181 || resolver.get_certificate(&fingerprint).is_none()
1182 {
1183 return Err("failed to retrieve certificate with custom names".into());
1184 }
1185
1186 if let Err(err) = resolver.remove_certificate(&fingerprint) {
1187 return Err(format!("the certificate could not be removed, {err}").into());
1188 }
1189
1190 let names = resolver.certificate_names(&fingerprint)?;
1191 if !resolver.find_certificates_by_names(&names)?.is_empty()
1192 && resolver.get_certificate(&fingerprint).is_some()
1193 {
1194 return Err("We have retrieved the certificate that should be deleted".into());
1195 }
1196
1197 Ok(())
1198 }
1199
1200 #[test]
1201 fn keep_resolving_with_wildcard() -> Result<(), Box<dyn Error + Send + Sync>> {
1202 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1203 let mut resolver = CertificateResolver::default();
1204
1205 let wildcard_example_org = CertificateAndKey {
1208 certificate: String::from(include_str!("../assets/tests/certificate-3.pem")),
1209 key: String::from(include_str!("../assets/tests/key.pem")),
1210 ..Default::default()
1211 };
1212
1213 let wildcard_example_org_fingerprint = resolver.add_certificate(&AddCertificate {
1214 address,
1215 certificate: wildcard_example_org,
1216 expired_at: Some(
1217 (SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?
1218 + Duration::from_secs(365 * 24 * 3600))
1219 .as_secs() as i64,
1220 ),
1221 })?;
1222
1223 if resolver
1224 .get_certificate(&wildcard_example_org_fingerprint)
1225 .is_none()
1226 {
1227 return Err("could not load the 2-year-valid certificate".into());
1228 }
1229
1230 let www_example_org = CertificateAndKey {
1234 certificate: String::from(include_str!("../assets/tests/certificate-2.pem")),
1235 key: String::from(include_str!("../assets/tests/key.pem")),
1236 ..Default::default()
1237 };
1238
1239 let www_example_org_fingerprint = resolver.add_certificate(&AddCertificate {
1240 address,
1241 certificate: www_example_org,
1242 expired_at: Some(
1243 (SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?
1244 + Duration::from_secs(2 * 365 * 24 * 3600))
1245 .as_secs() as i64,
1246 ),
1247 })?;
1248
1249 let www_example_org = resolver
1250 .domain_lookup("www.example.org".as_bytes(), true)
1251 .expect("there should be a www.example.org cert");
1252 assert_eq!(www_example_org.1, www_example_org_fingerprint);
1253
1254 let test_example_org = resolver
1255 .domain_lookup("test.example.org".as_bytes(), true)
1256 .expect("there should be a test.example.org cert");
1257 assert_eq!(test_example_org.1, wildcard_example_org_fingerprint);
1258
1259 let example_org = resolver
1260 .domain_lookup("example.org".as_bytes(), true)
1261 .expect("there should be a example.org cert");
1262 assert_eq!(example_org.1, www_example_org_fingerprint);
1263
1264 resolver
1267 .remove_certificate(&www_example_org_fingerprint)
1268 .expect("should be able to remove the 2-year certificate");
1269
1270 let should_be_wildcard_fingerprint = resolver
1271 .domain_lookup("www.example.org".as_bytes(), true)
1272 .expect("there should be a www.example.org cert");
1273 assert_eq!(
1274 should_be_wildcard_fingerprint.1,
1275 wildcard_example_org_fingerprint
1276 );
1277
1278 assert!(
1279 resolver
1280 .domain_lookup("example.org".as_bytes(), true)
1281 .is_none()
1282 );
1283
1284 Ok(())
1285 }
1286
1287 #[test]
1288 fn resolve_the_longer_lived_cert() -> Result<(), Box<dyn Error + Send + Sync>> {
1289 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1290 let mut resolver = CertificateResolver::default();
1291
1292 let certificate_and_key_2y = CertificateAndKey {
1295 certificate: String::from(include_str!("../assets/tests/certificate-2y.pem")),
1296 key: String::from(include_str!("../assets/tests/key-2y.pem")),
1297 ..Default::default()
1298 };
1299
1300 let fingerprint_2y = resolver.add_certificate(&AddCertificate {
1301 address,
1302 certificate: certificate_and_key_2y,
1303 expired_at: None,
1304 })?;
1305
1306 if resolver.get_certificate(&fingerprint_2y).is_none() {
1307 return Err("could not load the 2-year-valid certificate".into());
1308 }
1309
1310 let certificate_and_key_1y = CertificateAndKey {
1313 certificate: String::from(include_str!("../assets/tests/certificate-1y.pem")),
1314 key: String::from(include_str!("../assets/tests/key-1y.pem")),
1315 ..Default::default()
1316 };
1317
1318 let fingerprint_1y = resolver.add_certificate(&AddCertificate {
1319 address,
1320 certificate: certificate_and_key_1y,
1321 ..Default::default()
1322 })?;
1323
1324 let localhost_cert = resolver
1325 .domain_lookup("localhost".as_bytes(), true)
1326 .expect("there should be a localhost cert");
1327
1328 assert_eq!(localhost_cert.1, fingerprint_2y);
1329
1330 resolver
1334 .remove_certificate(&fingerprint_2y)
1335 .expect("should be able to remove the 2-year certificate");
1336
1337 let localhost_cert = resolver
1338 .domain_lookup("localhost".as_bytes(), true)
1339 .expect("there should be a localhost cert");
1340
1341 assert_eq!(localhost_cert.1, fingerprint_1y);
1342
1343 Ok(())
1344 }
1345
1346 #[test]
1347 fn expiration_override() -> Result<(), Box<dyn Error + Send + Sync>> {
1348 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1349 let mut resolver = CertificateResolver::default();
1350
1351 let certificate_and_key_1y = CertificateAndKey {
1354 certificate: String::from(include_str!("../assets/tests/certificate-1y.pem")),
1355 key: String::from(include_str!("../assets/tests/key-1y.pem")),
1356 ..Default::default()
1357 };
1358
1359 let fingerprint_1y_overriden = resolver.add_certificate(&AddCertificate {
1360 address,
1361 certificate: certificate_and_key_1y,
1362 expired_at: Some(
1363 (SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?
1364 + Duration::from_secs(3 * 365 * 24 * 3600))
1365 .as_secs() as i64,
1366 ),
1367 })?;
1368
1369 if resolver
1370 .get_certificate(&fingerprint_1y_overriden)
1371 .is_none()
1372 {
1373 return Err("failed to retrieve certificate".into());
1374 }
1375
1376 let certificate_and_key_2y = CertificateAndKey {
1379 certificate: String::from(include_str!("../assets/tests/certificate-2y.pem")),
1380 key: String::from(include_str!("../assets/tests/key-2y.pem")),
1381 ..Default::default()
1382 };
1383
1384 let fingerprint_2y = resolver.add_certificate(&AddCertificate {
1385 address,
1386 certificate: certificate_and_key_2y,
1387 expired_at: None,
1388 })?;
1389
1390 let localhost_cert = resolver
1391 .domain_lookup("localhost".as_bytes(), true)
1392 .expect("there should be a localhost cert");
1393
1394 assert_eq!(localhost_cert.1, fingerprint_1y_overriden);
1395
1396 resolver
1400 .remove_certificate(&fingerprint_1y_overriden)
1401 .expect("should be able to remove the 1-year (3-year-overriden) certificate");
1402
1403 let localhost_cert = resolver
1404 .domain_lookup("localhost".as_bytes(), true)
1405 .expect("there should be a localhost cert");
1406
1407 assert_eq!(localhost_cert.1, fingerprint_2y);
1408
1409 Ok(())
1410 }
1411
1412 #[test]
1415 fn replace_certificate_add_before_remove() -> Result<(), Box<dyn Error + Send + Sync>> {
1416 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1417 let mut resolver = CertificateResolver::default();
1418
1419 let cert_1y = CertificateAndKey {
1421 certificate: String::from(include_str!("../assets/tests/certificate-1y.pem")),
1422 key: String::from(include_str!("../assets/tests/key-1y.pem")),
1423 ..Default::default()
1424 };
1425
1426 let fingerprint_1y = resolver.add_certificate(&AddCertificate {
1427 address,
1428 certificate: cert_1y,
1429 expired_at: None,
1430 })?;
1431
1432 assert!(
1434 resolver
1435 .domain_lookup("localhost".as_bytes(), true)
1436 .is_some(),
1437 "initial certificate should be resolvable"
1438 );
1439
1440 let cert_2y = CertificateAndKey {
1442 certificate: String::from(include_str!("../assets/tests/certificate-2y.pem")),
1443 key: String::from(include_str!("../assets/tests/key-2y.pem")),
1444 ..Default::default()
1445 };
1446
1447 let new_fingerprint = resolver.replace_certificate(&ReplaceCertificate {
1448 address,
1449 new_certificate: cert_2y,
1450 old_fingerprint: fingerprint_1y.to_string(),
1451 new_expired_at: None,
1452 })?;
1453
1454 assert!(
1456 resolver.get_certificate(&fingerprint_1y).is_none(),
1457 "old certificate should have been removed"
1458 );
1459
1460 assert!(
1462 resolver.get_certificate(&new_fingerprint).is_some(),
1463 "new certificate should be present"
1464 );
1465 let resolved = resolver
1466 .domain_lookup("localhost".as_bytes(), true)
1467 .expect("a certificate should resolve for localhost");
1468 assert_eq!(
1469 resolved.1, new_fingerprint,
1470 "resolved certificate should be the replacement"
1471 );
1472
1473 Ok(())
1474 }
1475
1476 #[test]
1485 fn replace_certificate_with_same_fingerprint_is_noop()
1486 -> Result<(), Box<dyn Error + Send + Sync>> {
1487 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1488 let mut resolver = CertificateResolver::default();
1489
1490 let cert = CertificateAndKey {
1491 certificate: String::from(include_str!("../assets/tests/certificate-1y.pem")),
1492 key: String::from(include_str!("../assets/tests/key-1y.pem")),
1493 ..Default::default()
1494 };
1495
1496 let initial_fingerprint = resolver.add_certificate(&AddCertificate {
1497 address,
1498 certificate: cert.clone(),
1499 expired_at: None,
1500 })?;
1501
1502 let returned_fingerprint = resolver.replace_certificate(&ReplaceCertificate {
1504 address,
1505 new_certificate: cert,
1506 old_fingerprint: initial_fingerprint.to_string(),
1507 new_expired_at: None,
1508 })?;
1509
1510 assert_eq!(
1511 returned_fingerprint, initial_fingerprint,
1512 "idempotent replace should return the existing fingerprint"
1513 );
1514
1515 assert!(
1516 resolver.get_certificate(&initial_fingerprint).is_some(),
1517 "idempotent replace must NOT delete the existing certificate"
1518 );
1519
1520 let resolved = resolver
1521 .domain_lookup("localhost".as_bytes(), true)
1522 .expect("certificate should still resolve after idempotent replace");
1523 assert_eq!(
1524 resolved.1, initial_fingerprint,
1525 "resolver should still hand back the original fingerprint"
1526 );
1527
1528 Ok(())
1529 }
1530
1531 #[test]
1534 fn removal_cleans_up_empty_index_entries() -> Result<(), Box<dyn Error + Send + Sync>> {
1535 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1536 let mut resolver = CertificateResolver::default();
1537
1538 let cert = CertificateAndKey {
1539 certificate: String::from(include_str!("../assets/tests/certificate-1y.pem")),
1540 key: String::from(include_str!("../assets/tests/key-1y.pem")),
1541 ..Default::default()
1542 };
1543
1544 let fingerprint = resolver.add_certificate(&AddCertificate {
1545 address,
1546 certificate: cert,
1547 expired_at: None,
1548 })?;
1549
1550 let names = resolver.certificate_names(&fingerprint)?;
1552 assert!(
1553 !names.is_empty(),
1554 "certificate should have at least one name"
1555 );
1556
1557 for name in &names {
1559 assert!(
1560 resolver.name_fingerprint_idx.contains_key(name),
1561 "name_fingerprint_idx should contain '{name}' before removal"
1562 );
1563 }
1564
1565 resolver.remove_certificate(&fingerprint)?;
1566
1567 for name in &names {
1569 assert!(
1570 !resolver.name_fingerprint_idx.contains_key(name),
1571 "name_fingerprint_idx should not contain empty entry for '{name}' after removal"
1572 );
1573 }
1574
1575 Ok(())
1576 }
1577
1578 #[test]
1592 fn certificate_chain_dedup_drops_duplicate_leaf() -> Result<(), Box<dyn Error + Send + Sync>> {
1593 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1594 let mut resolver = CertificateResolver::default();
1595
1596 let leaf_pem = String::from(include_str!("../assets/certificate.pem"));
1597
1598 let cert_with_duplicated_leaf = CertificateAndKey {
1599 certificate: leaf_pem.clone(),
1600 certificate_chain: vec![leaf_pem],
1601 key: String::from(include_str!("../assets/key.pem")),
1602 ..Default::default()
1603 };
1604
1605 let fingerprint = resolver.add_certificate(&AddCertificate {
1606 address,
1607 certificate: cert_with_duplicated_leaf,
1608 expired_at: None,
1609 })?;
1610
1611 let stored = resolver
1612 .get_certificate(&fingerprint)
1613 .ok_or("resolver lost the certificate after add")?;
1614
1615 assert_eq!(
1616 stored.inner.cert.len(),
1617 1,
1618 "expected dedup to drop the duplicate leaf, got chain of {} cert(s)",
1619 stored.inner.cert.len()
1620 );
1621
1622 Ok(())
1623 }
1624
1625 #[test]
1639 fn certificate_chain_handles_multi_pem_single_entry() -> Result<(), Box<dyn Error + Send + Sync>>
1640 {
1641 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1642 let mut resolver = CertificateResolver::default();
1643
1644 let leaf_pem = String::from(include_str!("../assets/certificate.pem"));
1645 let multi_pem_chain_entry = format!("{leaf_pem}\n{leaf_pem}");
1648
1649 let cert = CertificateAndKey {
1650 certificate: leaf_pem,
1651 certificate_chain: vec![multi_pem_chain_entry],
1652 key: String::from(include_str!("../assets/key.pem")),
1653 ..Default::default()
1654 };
1655
1656 let fingerprint = resolver.add_certificate(&AddCertificate {
1657 address,
1658 certificate: cert,
1659 expired_at: None,
1660 })?;
1661
1662 let stored = resolver
1663 .get_certificate(&fingerprint)
1664 .ok_or("resolver lost the certificate after add")?;
1665
1666 assert_eq!(
1671 stored.inner.cert.len(),
1672 1,
1673 "expected split + dedup to leave only the leaf, got chain of {} cert(s)",
1674 stored.inner.cert.len()
1675 );
1676
1677 Ok(())
1678 }
1679
1680 #[test]
1707 fn a_mixed_case_certificate_name_resolves_for_the_lowercased_sni() {
1708 const MIXED: &str = "MiXeD.Example.COM";
1709 const LOWER: &str = "mixed.example.com";
1710
1711 let mut resolver = CertificateResolver::default();
1712 let fingerprint = resolver
1713 .add_certificate(&AddCertificate {
1714 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
1715 certificate: CertificateAndKey {
1716 certificate: include_str!("../assets/certificate.pem").to_owned(),
1717 key: include_str!("../assets/key.pem").to_owned(),
1718 names: vec![MIXED.to_owned()],
1719 ..Default::default()
1720 },
1721 expired_at: None,
1722 })
1723 .expect("the mixed-case certificate must load");
1724
1725 assert!(
1726 resolver.domain_lookup(LOWER.as_bytes(), true).is_some(),
1727 "rustls only ever asks for the lowercased SNI, so that is the \
1728 only key the trie can usefully hold",
1729 );
1730 assert_eq!(
1731 resolver.names_for_sni(LOWER.as_bytes()),
1732 Some(vec![LOWER.to_owned()]),
1733 "the SAN snapshot feeding the H2 `:authority` binding must \
1734 carry the normalised name",
1735 );
1736
1737 resolver
1741 .remove_certificate(&fingerprint)
1742 .expect("the mixed-case certificate must be removable");
1743 assert!(
1744 resolver.domain_lookup(LOWER.as_bytes(), true).is_none(),
1745 "removing the certificate must retire its normalised trie key",
1746 );
1747 }
1748
1749 #[test]
1783 fn a_certificate_name_carrying_a_regex_segment_is_refused() {
1784 const REGEX_NAME: &str = "/te.*/.example.com";
1785
1786 let mut resolver = CertificateResolver::default();
1787 let error = resolver
1788 .add_certificate(&AddCertificate {
1789 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
1790 certificate: CertificateAndKey {
1791 certificate: include_str!("../assets/certificate.pem").to_owned(),
1792 key: include_str!("../assets/key.pem").to_owned(),
1793 names: vec![REGEX_NAME.to_owned()],
1794 ..Default::default()
1795 },
1796 expired_at: None,
1797 })
1798 .expect_err("a certificate name carrying a regex segment must be refused");
1799
1800 assert!(
1801 matches!(&error, CertificateResolverError::InvalidName(name) if name == REGEX_NAME),
1802 "the refusal must be InvalidName, got {error:?}",
1803 );
1804
1805 assert!(
1810 resolver.certificates.is_empty(),
1811 "a refused certificate must not be stored",
1812 );
1813 assert!(
1814 resolver.name_fingerprint_idx.is_empty(),
1815 "a refused certificate must not index any name",
1816 );
1817 for sni in ["test.example.com", "tenant.example.com", REGEX_NAME] {
1818 assert!(
1819 resolver.domain_lookup(sni.as_bytes(), true).is_none(),
1820 "a refused certificate name must bind no SNI, yet {sni} resolved",
1821 );
1822 }
1823
1824 resolver
1827 .add_certificate(&AddCertificate {
1828 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
1829 certificate: CertificateAndKey {
1830 certificate: include_str!("../assets/certificate.pem").to_owned(),
1831 key: include_str!("../assets/key.pem").to_owned(),
1832 names: vec!["*.example.com".to_owned()],
1833 ..Default::default()
1834 },
1835 expired_at: None,
1836 })
1837 .expect("a wildcard certificate name must still load");
1838 assert!(
1839 resolver
1840 .domain_lookup(b"tenant.example.com", true)
1841 .is_some(),
1842 "the wildcard certificate must still serve a subdomain",
1843 );
1844 }
1845}