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 InconsistentKeys,
17 pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
18 server::{ClientHello, ResolvesServerCert},
19 sign::CertifiedKey,
20};
21
22use crate::crypto::any_supported_type;
23use sha2::{Digest, Sha256};
24use sozu_command::{
25 certificate::{
26 CertificateError, Fingerprint, get_cn_and_san_attributes, parse_pem, parse_x509,
27 split_certificate_chain,
28 },
29 logging::ansi_palette,
30 proto::command::{AddCertificate, CertificateAndKey, ReplaceCertificate, SocketAddress},
31};
32
33use crate::metrics::names;
34use crate::router::MAX_HOSTNAME_LENGTH;
35use crate::router::pattern_trie::{InsertResult, Key, KeyValue, TrieNode};
36
37macro_rules! log_module_context {
46 () => {{
47 let (open, reset, _, _, _) = ansi_palette();
48 format!(
49 "{open}TLS-RESOLVER{reset}\t >>>",
50 open = open,
51 reset = reset
52 )
53 }};
54}
55
56static DEFAULT_CERTIFICATE: LazyLock<Option<Arc<CertifiedKey>>> = LazyLock::new(|| {
60 let add = AddCertificate {
61 certificate: CertificateAndKey {
62 certificate: include_str!("../assets/certificate.pem").to_string(),
63 certificate_chain: vec![include_str!("../assets/certificate_chain.pem").to_string()],
64 key: include_str!("../assets/key.pem").to_string(),
65 versions: vec![],
66 names: vec![],
67 },
68 address: SocketAddress::new_v4(0, 0, 0, 0, 8080), expired_at: None,
70 };
71 build_validated_certificate(&add).ok().map(|c| c.inner)
72});
73
74#[derive(thiserror::Error, Debug)]
75pub enum CertificateResolverError {
76 #[error("failed to get common name and subject alternate names from pem, {0}")]
77 InvalidCommonNameAndSubjectAlternateNames(CertificateError),
78 #[error("invalid private key: {0}")]
79 InvalidPrivateKey(String),
80 #[error("empty key")]
81 EmptyKeys,
82 #[error("error parsing x509 cert from bytes: {0}")]
83 ParseX509(CertificateError),
84 #[error("error parsing pem formated certificate from bytes: {0}")]
85 ParsePem(CertificateError),
86 #[error("error parsing overriding names in new certificate: {0}")]
87 ParseOverridingNames(CertificateError),
88 #[error("the SNI route table cannot host a certificate name, name_bytes={}", .0.len())]
89 InvalidName(String),
90}
91
92#[derive(Clone)]
96pub struct CertifiedKeyWrapper {
97 inner: Arc<CertifiedKey>,
98 names: Vec<String>,
100 expiration: i64,
101 fingerprint: Fingerprint,
102 material_digest: [u8; 32],
106}
107
108impl fmt::Debug for CertifiedKeyWrapper {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 let names_len = self
111 .names
112 .iter()
113 .map(String::len)
114 .fold(0usize, usize::saturating_add);
115
116 f.debug_struct("CertifiedKeyWrapper")
117 .field("certificate", &"[redacted]")
118 .field("certificate_chain_count", &self.inner.cert.len())
119 .field("private_key", &"[redacted]")
120 .field("names_count", &self.names.len())
121 .field("names_len", &names_len)
122 .field("expiration", &self.expiration)
123 .field("fingerprint_bytes", &self.fingerprint.0.len())
124 .finish()
125 }
126}
127
128fn update_digest_bytes(hasher: &mut Sha256, bytes: &[u8]) {
129 hasher.update((bytes.len() as u64).to_be_bytes());
130 hasher.update(bytes);
131}
132
133fn certificate_material_digest(
134 certificate: &CertificateAndKey,
135 canonical_names: &[String],
136) -> [u8; 32] {
137 let mut hasher = Sha256::new();
138 hasher.update(b"sozu-certificate-material-v1");
139 update_digest_bytes(&mut hasher, certificate.certificate.as_bytes());
140 hasher.update((certificate.certificate_chain.len() as u64).to_be_bytes());
141 for chain_entry in &certificate.certificate_chain {
142 update_digest_bytes(&mut hasher, chain_entry.as_bytes());
143 }
144 update_digest_bytes(&mut hasher, certificate.key.as_bytes());
145 hasher.update((certificate.versions.len() as u64).to_be_bytes());
146 for version in &certificate.versions {
147 hasher.update(version.to_be_bytes());
148 }
149 hasher.update((canonical_names.len() as u64).to_be_bytes());
150 for name in canonical_names {
151 update_digest_bytes(&mut hasher, name.as_bytes());
152 }
153 hasher.finalize().into()
154}
155
156impl TryFrom<&AddCertificate> for CertifiedKeyWrapper {
159 type Error = CertificateResolverError;
160
161 fn try_from(add: &AddCertificate) -> Result<Self, Self::Error> {
162 let cert = add.certificate.clone();
163
164 let pem =
165 parse_pem(cert.certificate.as_bytes()).map_err(CertificateResolverError::ParsePem)?;
166
167 let x509 = parse_x509(&pem.contents).map_err(CertificateResolverError::ParseX509)?;
168
169 let canonical_names = if add.certificate.names.is_empty() {
183 get_cn_and_san_attributes(&x509)
184 } else {
185 add.certificate.names.clone()
186 };
187 let material_digest = certificate_material_digest(&cert, &canonical_names);
188 let overriding_names: Vec<String> = canonical_names
189 .into_iter()
190 .map(|mut name| {
191 name.make_ascii_lowercase();
192 name
193 })
194 .collect();
195
196 let expiration = add
197 .expired_at
198 .unwrap_or(x509.validity().not_after.timestamp());
199
200 let fingerprint = Fingerprint(Sha256::digest(&pem.contents).iter().cloned().collect());
201
202 let leaf_der = pem.contents;
212 let mut chain = vec![CertificateDer::from(leaf_der.to_owned())];
213 let mut dropped_duplicates = 0usize;
214 for cert in &cert.certificate_chain {
215 for split_pem in split_certificate_chain(cert.to_owned()) {
216 let chain_link = parse_pem(split_pem.as_bytes())
217 .map_err(CertificateResolverError::ParsePem)?
218 .contents;
219
220 if chain_link == leaf_der {
221 dropped_duplicates += 1;
222 continue;
223 }
224 chain.push(CertificateDer::from(chain_link));
225 }
226 }
227 if dropped_duplicates > 0 {
228 debug!(
229 "{} dropped {} duplicate leaf certificate(s) from the supplied chain",
230 log_module_context!(),
231 dropped_duplicates
232 );
233 }
234
235 let private_key = PrivateKeyDer::from_pem_slice(cert.key.as_bytes())
244 .map_err(|_| CertificateResolverError::EmptyKeys)?;
245
246 debug_assert!(
251 !chain.is_empty(),
252 "assembled certificate chain must contain at least the leaf"
253 );
254 debug_assert_eq!(
255 chain[0].as_ref(),
256 leaf_der.as_slice(),
257 "the leaf must remain at index 0 of the chain"
258 );
259 debug_assert_eq!(
262 fingerprint.0.len(),
263 32,
264 "a SHA-256 fingerprint must be 32 bytes"
265 );
266
267 match any_supported_type(&private_key) {
268 Ok(signing_key) => {
269 let stored_certificate = CertifiedKeyWrapper {
270 inner: Arc::new(CertifiedKey::new(chain, signing_key)),
271 names: overriding_names,
272 expiration,
273 fingerprint,
274 material_digest,
275 };
276 Ok(stored_certificate)
277 }
278 Err(sign_error) => Err(CertificateResolverError::InvalidPrivateKey(
279 sign_error.to_string(),
280 )),
281 }
282 }
283}
284
285pub fn build_validated_certificate(
293 add: &AddCertificate,
294) -> Result<CertifiedKeyWrapper, CertificateResolverError> {
295 let certificate = CertifiedKeyWrapper::try_from(add)?;
296
297 match certificate.inner.keys_match() {
298 Ok(()) | Err(rustls::Error::InconsistentKeys(InconsistentKeys::Unknown)) => {}
299 Err(error) => {
300 return Err(CertificateResolverError::InvalidPrivateKey(
301 error.to_string(),
302 ));
303 }
304 }
305
306 let mut scratch: TrieNode<()> = TrieNode::root();
307 for name in &certificate.names {
308 if name.len() > MAX_HOSTNAME_LENGTH
309 || name.contains('/')
310 || scratch.domain_insert(name.to_owned().into_bytes(), ()) == InsertResult::Failed
311 {
312 return Err(CertificateResolverError::InvalidName(name.to_owned()));
313 }
314 }
315
316 Ok(certificate)
317}
318
319#[derive(Default, Clone)]
328pub struct CertificateResolver {
329 pub domains: TrieNode<Fingerprint>,
331 certificates: HashMap<Fingerprint, CertifiedKeyWrapper>,
333 name_fingerprint_idx: HashMap<String, Vec<(Fingerprint, i64)>>,
337}
338
339impl fmt::Debug for CertificateResolver {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 let indexed_certificate_links = self
342 .name_fingerprint_idx
343 .values()
344 .map(Vec::len)
345 .fold(0usize, usize::saturating_add);
346
347 f.debug_struct("CertificateResolver")
348 .field("domains", &"[redacted]")
349 .field("certificates_count", &self.certificates.len())
350 .field("indexed_names_count", &self.name_fingerprint_idx.len())
351 .field("indexed_certificate_links", &indexed_certificate_links)
352 .finish()
353 }
354}
355
356impl CertificateResolver {
357 pub fn get_certificate(&self, fingerprint: &Fingerprint) -> Option<CertifiedKeyWrapper> {
359 self.certificates.get(fingerprint).map(ToOwned::to_owned)
360 }
361
362 fn publish_min_expiration_gauge(&self) {
376 let Some(min_expiration) = self.certificates.values().map(|c| c.expiration).min() else {
377 return;
385 };
386 let clamped = min_expiration.max(0) as usize;
387 gauge!(names::tls::CERT_MIN_EXPIRES_AT_SECONDS, clamped);
388 }
389
390 fn insert_validated_certificate(&mut self, certificate: CertifiedKeyWrapper) {
393 for name in &certificate.names {
394 let fingerprints_for_this_name = self
395 .name_fingerprint_idx
396 .entry(name.to_owned())
397 .or_default();
398 fingerprints_for_this_name
399 .push((certificate.fingerprint.to_owned(), certificate.expiration));
400 fingerprints_for_this_name.sort_by_key(|(_, expiration)| *expiration);
401
402 let longest_lived_cert = fingerprints_for_this_name
403 .last()
404 .expect("inserting a certificate must leave its name index non-empty");
405 self.domains.remove(&name.to_owned().into_bytes());
406 let insert_result = self.domains.insert(
407 name.to_owned().into_bytes(),
408 longest_lived_cert.0.to_owned(),
409 );
410 debug_assert_ne!(
411 insert_result,
412 InsertResult::Failed,
413 "validated certificate names must remain insertable"
414 );
415 if insert_result == InsertResult::Failed {
416 error!(
417 "{} the SNI trie rejected a certificate name despite passing \
418 the shared preflight validation, name_bytes={}",
419 log_module_context!(),
420 name.len(),
421 );
422 }
423 }
424
425 self.certificates
426 .insert(certificate.fingerprint.to_owned(), certificate);
427 }
428
429 fn remove_certificate_without_publishing(&mut self, fingerprint: &Fingerprint) -> bool {
432 let Some(certificate_to_remove) = self.certificates.get(fingerprint).cloned() else {
433 return false;
434 };
435
436 for name in certificate_to_remove.names {
437 self.domains.domain_remove(&name.as_bytes().to_vec());
438
439 if let std::collections::hash_map::Entry::Occupied(mut entry) =
440 self.name_fingerprint_idx.entry(name.to_owned())
441 {
442 entry.get_mut().retain(|(stored, _)| stored != fingerprint);
443 if let Some((longest_lived, _)) = entry.get().last() {
444 let insert_result = self
445 .domains
446 .insert(name.as_bytes().to_vec(), longest_lived.to_owned());
447 debug_assert_ne!(
448 insert_result,
449 InsertResult::Failed,
450 "validated certificate names must remain insertable"
451 );
452 if insert_result == InsertResult::Failed {
453 error!(
454 "{} the SNI trie rejected a re-inserted certificate name \
455 that was validated on add, name_bytes={}",
456 log_module_context!(),
457 name.len(),
458 );
459 }
460 }
461 if entry.get().is_empty() {
462 entry.remove();
463 }
464 }
465 }
466
467 self.certificates.remove(fingerprint);
468 true
469 }
470
471 pub fn add_certificate(
474 &mut self,
475 add: &AddCertificate,
476 ) -> Result<Fingerprint, CertificateResolverError> {
477 let cert_to_add = build_validated_certificate(add)?;
478
479 trace!(
480 "{} adding certificate {:?}",
481 log_module_context!(),
482 cert_to_add
483 );
484
485 if self.certificates.contains_key(&cert_to_add.fingerprint) {
486 return Ok(cert_to_add.fingerprint);
487 }
488
489 let certificates_before = self.certificates.len();
494 let new_fingerprint = cert_to_add.fingerprint.clone();
495 debug_assert!(
496 !self.certificates.contains_key(&new_fingerprint),
497 "add_certificate past the dedup guard must be inserting a new fingerprint"
498 );
499
500 self.insert_validated_certificate(cert_to_add.to_owned());
501 self.publish_min_expiration_gauge();
502
503 debug_assert!(
507 self.certificates.contains_key(&new_fingerprint),
508 "add_certificate must store the new certificate"
509 );
510 debug_assert_eq!(
511 self.certificates.len(),
512 certificates_before + 1,
513 "add_certificate must grow the store by exactly one"
514 );
515 debug_assert!(
516 cert_to_add.names.iter().all(|name| {
517 self.name_fingerprint_idx
518 .get(name)
519 .is_some_and(|fps| fps.iter().any(|(fp, _)| *fp == new_fingerprint))
520 }),
521 "every name of the added cert must be indexed to its fingerprint"
522 );
523
524 trace!("{} {:#?}", log_module_context!(), self);
525
526 Ok(cert_to_add.fingerprint)
527 }
528
529 pub fn remove_certificate(
532 &mut self,
533 fingerprint: &Fingerprint,
534 ) -> Result<(), CertificateResolverError> {
535 let certificates_before = self.certificates.len();
540 let was_present = self.certificates.contains_key(fingerprint);
541 #[cfg(debug_assertions)]
542 let removed_names = self
543 .certificates
544 .get(fingerprint)
545 .map(|certificate| certificate.names.to_owned());
546 let removed = self.remove_certificate_without_publishing(fingerprint);
547 if removed {
548 self.publish_min_expiration_gauge();
549
550 debug_assert!(
555 !self.certificates.contains_key(fingerprint),
556 "remove_certificate must evict the fingerprint"
557 );
558 debug_assert_eq!(
559 self.certificates.len(),
560 certificates_before - 1,
561 "removing a present cert must shrink the store by exactly one"
562 );
563 #[cfg(debug_assertions)]
564 debug_assert!(
565 removed_names
566 .as_ref()
567 .is_some_and(|names| names.iter().all(|name| {
568 self.name_fingerprint_idx
569 .get(name)
570 .is_none_or(|fingerprints| {
571 fingerprints.iter().all(|(stored, _)| stored != fingerprint)
572 })
573 })),
574 "no name may still index the removed fingerprint"
575 );
576 } else {
577 debug_assert!(
579 !was_present,
580 "the absent-cert branch must only run when the fingerprint was not stored"
581 );
582 debug_assert_eq!(
583 self.certificates.len(),
584 certificates_before,
585 "removing an absent cert must not change the store"
586 );
587 }
588 trace!("{} {:#?}", log_module_context!(), self);
589
590 Ok(())
591 }
592
593 pub fn replace_certificate(
598 &mut self,
599 replace: &ReplaceCertificate,
600 ) -> Result<Fingerprint, CertificateResolverError> {
601 let add = AddCertificate {
602 address: replace.address.to_owned(),
603 certificate: replace.new_certificate.to_owned(),
604 expired_at: replace.new_expired_at.to_owned(),
605 };
606 let new_cert = build_validated_certificate(&add)?;
607 let new_fingerprint = new_cert.fingerprint.to_owned();
608 let old_fingerprint = Fingerprint::from_str(&replace.old_fingerprint);
615 let new_material_already_stored =
616 self.certificates
617 .get(&new_fingerprint)
618 .is_some_and(|stored| {
619 stored.material_digest == new_cert.material_digest
620 && stored.expiration == new_cert.expiration
621 });
622
623 if old_fingerprint
624 .as_ref()
625 .is_ok_and(|old| old == &new_fingerprint)
626 && new_material_already_stored
627 {
628 return Ok(new_fingerprint);
629 }
630
631 let mut resolver_changed = false;
632 if !new_material_already_stored {
633 self.remove_certificate_without_publishing(&new_fingerprint);
634 self.insert_validated_certificate(new_cert);
635 resolver_changed = true;
636 }
637 if let Ok(old_fingerprint) = old_fingerprint.as_ref()
638 && old_fingerprint != &new_fingerprint
639 {
640 resolver_changed |= self.remove_certificate_without_publishing(old_fingerprint);
641 }
642 if resolver_changed {
643 self.publish_min_expiration_gauge();
644 }
645
646 debug_assert!(
647 self.certificates.contains_key(&new_fingerprint),
648 "replace_certificate must leave the replacement stored"
649 );
650 debug_assert!(
651 old_fingerprint.as_ref().is_err()
652 || old_fingerprint.as_ref().is_ok_and(|old| {
653 old == &new_fingerprint || !self.certificates.contains_key(old)
654 }),
655 "replace_certificate must remove a distinct old fingerprint"
656 );
657
658 if let Err(error) = old_fingerprint {
659 warn!(
660 "{} new certificate stored but could not remove old one: \
661 failed to parse old fingerprint, {}",
662 log_module_context!(),
663 error
664 );
665 }
666
667 Ok(new_fingerprint)
668 }
669
670 #[cfg(test)]
673 fn find_certificates_by_names(
674 &self,
675 names: &HashSet<String>,
676 ) -> Result<HashSet<Fingerprint>, CertificateResolverError> {
677 let mut fingerprints = HashSet::new();
678 for name in names {
679 if let Some(fprints) = self.name_fingerprint_idx.get(name) {
680 fprints.iter().for_each(|fingerprint| {
681 fingerprints.insert(fingerprint.to_owned().0);
682 });
683 }
684 }
685
686 Ok(fingerprints)
687 }
688
689 #[cfg(test)]
692 fn certificate_names(
693 &self,
694 fingerprint: &Fingerprint,
695 ) -> Result<HashSet<String>, CertificateResolverError> {
696 if let Some(cert) = self.certificates.get(fingerprint) {
697 return Ok(cert.names.iter().cloned().collect());
698 }
699 Ok(HashSet::new())
700 }
701
702 pub fn domain_lookup(
703 &self,
704 domain: &[u8],
705 accept_wildcard: bool,
706 ) -> Option<&KeyValue<Key, Fingerprint>> {
707 self.domains.domain_lookup(domain, accept_wildcard)
708 }
709
710 pub fn names_for_sni(&self, domain: &[u8]) -> Option<Vec<String>> {
722 let (_, fingerprint) = self.domain_lookup(domain, true)?;
723 self.certificates
724 .get(fingerprint)
725 .map(|cert| cert.names.clone())
726 }
727}
728
729#[derive(Default)]
733pub struct MutexCertificateResolver(pub Mutex<CertificateResolver>);
734
735impl ResolvesServerCert for MutexCertificateResolver {
736 fn resolve(&self, client_hello: ClientHello) -> Option<Arc<CertifiedKey>> {
737 let server_name = client_hello.server_name();
738 let sigschemes = client_hello.signature_schemes();
739
740 let Some(name) = server_name else {
741 error!(
742 "{} cannot look up certificate: no SNI from session",
743 log_module_context!()
744 );
745 return None;
746 };
747 trace!(
748 "{} trying to resolve certificate with name_bytes={} signature_schemes_count={}",
749 log_module_context!(),
750 name.len(),
751 sigschemes.len()
752 );
753 let resolver = match self.0.lock() {
763 Ok(guard) => guard,
764 Err(_poisoned) => {
765 error!(
766 "{} cert resolver mutex poisoned, returning default cert",
767 log_module_context!()
768 );
769 return DEFAULT_CERTIFICATE.clone();
770 }
771 };
772 if let Some((_, fingerprint)) = resolver.domains.domain_lookup(name.as_bytes(), true) {
773 trace!(
774 "{} looking for certificate with name_bytes={} fingerprint_bytes={}",
775 log_module_context!(),
776 name.len(),
777 fingerprint.0.len()
778 );
779
780 let cert = resolver
789 .certificates
790 .get(fingerprint)
791 .map(|cert| cert.inner.clone());
792 debug_assert!(
793 cert.is_none()
794 || resolver
795 .certificates
796 .get(fingerprint)
797 .is_some_and(|stored| Arc::ptr_eq(&stored.inner, cert.as_ref().unwrap())),
798 "resolved certificate must be the one stored under the looked-up fingerprint"
799 );
800
801 trace!(
802 "{} certificate lookup fingerprint_bytes={} found={}",
803 log_module_context!(),
804 fingerprint.0.len(),
805 cert.is_some()
806 );
807 return cert;
808 }
809 drop(resolver);
810
811 debug!(
815 "{} default certificate is used for name_bytes={}",
816 log_module_context!(),
817 name.len()
818 );
819 incr!(names::tls::DEFAULT_CERT_USED);
820 DEFAULT_CERTIFICATE.clone()
821 }
822}
823
824impl MutexCertificateResolver {
825 pub fn names_for_sni(&self, domain: &[u8]) -> Option<Vec<String>> {
831 match self.0.lock() {
832 Ok(guard) => guard.names_for_sni(domain),
833 Err(_poisoned) => {
834 error!(
835 "{} cert resolver mutex poisoned, treating as no SAN match",
836 log_module_context!()
837 );
838 None
839 }
840 }
841 }
842}
843
844impl fmt::Debug for MutexCertificateResolver {
845 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
846 f.write_str("MutexWrappedCertificateResolver")
847 }
848}
849
850#[cfg(test)]
854mod tests {
855 use std::{
856 collections::HashSet,
857 error::Error,
858 io::Cursor,
859 sync::Arc,
860 time::{Duration, SystemTime},
861 };
862
863 use sozu_command::proto::command::{
865 AddCertificate, CertificateAndKey, ReplaceCertificate, SocketAddress,
866 };
867
868 use super::{
869 CertificateResolver, CertificateResolverError, CertifiedKeyWrapper,
870 MutexCertificateResolver, build_validated_certificate,
871 };
872
873 fn drive_client_hello(resolver: Arc<MutexCertificateResolver>, server_name: String) {
874 let provider = Arc::new(crate::crypto::default_provider());
875 let server_config = rustls::ServerConfig::builder_with_provider(provider.clone())
876 .with_protocol_versions(&[&rustls::version::TLS13])
877 .expect("test server provider must support TLS 1.3")
878 .with_no_client_auth()
879 .with_cert_resolver(resolver);
880 let client_config = rustls::ClientConfig::builder_with_provider(provider)
881 .with_protocol_versions(&[&rustls::version::TLS13])
882 .expect("test client provider must support TLS 1.3")
883 .with_root_certificates(rustls::RootCertStore::empty())
884 .with_no_client_auth();
885 let server_name = rustls::pki_types::ServerName::try_from(server_name)
886 .expect("test SNI must be a valid DNS name");
887 let mut client = rustls::ClientConnection::new(Arc::new(client_config), server_name)
888 .expect("test client connection must initialize");
889 let mut server = rustls::ServerConnection::new(Arc::new(server_config))
890 .expect("test server connection must initialize");
891
892 let mut client_hello = Vec::new();
893 client
894 .write_tls(&mut client_hello)
895 .expect("test client hello must serialize");
896 server
897 .read_tls(&mut Cursor::new(client_hello))
898 .expect("test server must read the client hello");
899 server
900 .process_new_packets()
901 .expect("test server must process the client hello");
902 }
903
904 #[test]
912 fn add_certificate_rejects_a_name_the_route_table_cannot_host() {
913 for name in ["sni-secret.example/", ".sni-secret.example"] {
914 let mut resolver = CertificateResolver::default();
915 let result = resolver.add_certificate(&AddCertificate {
916 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
917 certificate: CertificateAndKey {
918 certificate: include_str!("../assets/certificate.pem").to_owned(),
919 key: include_str!("../assets/key.pem").to_owned(),
920 names: vec![(*name).to_owned()],
921 ..Default::default()
922 },
923 expired_at: None,
924 });
925 assert!(result.is_err(), "{name:?} must be rejected, not stored");
926 assert!(
927 resolver.certificates.is_empty(),
928 "{name:?} was rejected but the certificate store was mutated",
929 );
930 assert!(
931 resolver.name_fingerprint_idx.is_empty(),
932 "{name:?} was rejected but the name index was mutated",
933 );
934 assert!(
935 resolver.domains.is_empty(),
936 "{name:?} was rejected but the SNI trie was mutated",
937 );
938 }
939 }
940
941 #[test]
942 fn shared_preflight_rejects_a_certificate_with_a_mismatched_private_key() {
943 let result = build_validated_certificate(&AddCertificate {
944 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
945 certificate: CertificateAndKey {
946 certificate: include_str!("../assets/tests/certificate-1y.pem").to_owned(),
947 key: include_str!("../assets/tests/key-2y.pem").to_owned(),
948 names: vec!["mismatch.example.com".to_owned()],
949 ..Default::default()
950 },
951 expired_at: None,
952 });
953
954 assert!(
955 matches!(result, Err(CertificateResolverError::InvalidPrivateKey(_))),
956 "a mismatched certificate/private-key pair must be refused, got {result:?}"
957 );
958 }
959
960 #[cfg(any(debug_assertions, feature = "logs-trace"))]
962 #[test]
963 fn certificate_resolver_logs_redact_matching_sni_and_fingerprint() {
964 const SNI_SECRET: &str = "resolver-sni-secret.example";
965
966 let certificate = CertificateAndKey {
967 certificate: include_str!("../assets/certificate.pem").to_owned(),
968 key: include_str!("../assets/key.pem").to_owned(),
969 names: vec![SNI_SECRET.to_owned()],
970 ..Default::default()
971 };
972 let fingerprint = certificate
973 .fingerprint()
974 .expect("test certificate fingerprint must be computable")
975 .to_string();
976 let output = crate::capture_test_logs_at_level("trace", move || {
977 let resolver = Arc::new(MutexCertificateResolver::default());
978 resolver
979 .0
980 .lock()
981 .expect("test resolver lock must be available")
982 .add_certificate(&AddCertificate {
983 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
984 certificate,
985 expired_at: None,
986 })
987 .expect("test certificate must load into the resolver");
988 drive_client_hello(resolver, SNI_SECRET.to_owned());
989 });
990
991 assert!(
992 !output.contains(SNI_SECRET),
993 "TLS resolver logs leaked the matching SNI: {output}"
994 );
995 assert!(
996 !output.contains(&fingerprint),
997 "TLS resolver logs leaked the matching certificate fingerprint: {output}"
998 );
999 for metadata in [
1000 format!("name_bytes={}", SNI_SECRET.len()),
1001 "fingerprint_bytes=32".to_owned(),
1002 ] {
1003 assert!(
1004 output.contains(&metadata),
1005 "TLS resolver logs omitted bounded metadata {metadata}: {output}"
1006 );
1007 }
1008 assert!(
1009 output.len() <= 4096,
1010 "TLS resolver log capture is not bounded: {} bytes",
1011 output.len()
1012 );
1013 }
1014
1015 #[cfg(any(debug_assertions, feature = "logs-trace"))]
1017 #[test]
1018 fn certificate_resolver_logs_redact_fallback_sni() {
1019 const SNI_SECRET: &str = "fallback-sni-secret.example";
1020
1021 let output = crate::capture_test_logs_at_level("trace", move || {
1022 drive_client_hello(
1023 Arc::new(MutexCertificateResolver::default()),
1024 SNI_SECRET.to_owned(),
1025 );
1026 });
1027
1028 assert!(
1029 !output.contains(SNI_SECRET),
1030 "TLS resolver logs leaked the fallback SNI: {output}"
1031 );
1032 assert!(
1033 output.contains(&format!("name_bytes={}", SNI_SECRET.len())),
1034 "TLS resolver fallback log omitted bounded SNI metadata: {output}"
1035 );
1036 assert!(
1037 output.len() <= 2048,
1038 "TLS resolver fallback log capture is not bounded: {} bytes",
1039 output.len()
1040 );
1041 }
1042
1043 #[test]
1044 fn certificate_resolver_poison_logs_are_bounded() {
1045 const SNI_SECRET: &str = "poison-sni-secret.example";
1046 const QUERY_SECRET: &str = "POISON_QUERY_SECRET_SENTINEL";
1047
1048 let query = format!("{QUERY_SECRET}{}", "x".repeat(4096));
1049 let output = crate::capture_test_logs_at_level("trace", move || {
1050 let resolver = Arc::new(MutexCertificateResolver::default());
1051 let poison_target = resolver.clone();
1052 assert!(
1053 std::thread::spawn(move || {
1054 let _guard = poison_target
1055 .0
1056 .lock()
1057 .expect("test resolver lock must initially be available");
1058 panic!("intentionally poison the test resolver lock");
1059 })
1060 .join()
1061 .is_err(),
1062 "test poison thread must panic"
1063 );
1064
1065 drive_client_hello(resolver.clone(), SNI_SECRET.to_owned());
1066 assert!(resolver.names_for_sni(query.as_bytes()).is_none());
1067 });
1068
1069 assert!(
1070 !output.contains(SNI_SECRET),
1071 "TLS resolver poison log leaked the handshake SNI: {output}"
1072 );
1073 assert!(
1074 !output.contains(QUERY_SECRET),
1075 "TLS resolver poison log leaked the 4 KiB direct query: {output}"
1076 );
1077 assert!(
1078 output.len() <= 2048,
1079 "TLS resolver poison log capture is not bounded: {} bytes",
1080 output.len()
1081 );
1082 }
1083
1084 #[test]
1085 fn certified_key_wrapper_debug_redacts_runtime_certificate_material() {
1086 const NAME_SECRET: &str = "CERTIFIED_KEY_NAME_SECRET_SENTINEL";
1087
1088 let add = AddCertificate {
1089 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
1090 certificate: CertificateAndKey {
1091 certificate: include_str!("../assets/certificate.pem").to_owned(),
1092 key: include_str!("../assets/key.pem").to_owned(),
1093 names: vec![format!("{NAME_SECRET}{}", "x".repeat(4096))],
1094 ..Default::default()
1095 },
1096 expired_at: None,
1097 };
1098 let certified_key =
1099 CertifiedKeyWrapper::try_from(&add).expect("test certificate must parse");
1100 let fingerprint = certified_key.fingerprint.to_string();
1101 let output = format!("{certified_key:?}");
1102
1103 assert!(
1104 !output.contains(NAME_SECRET),
1105 "CertifiedKeyWrapper Debug leaked an overridden certificate name: {output}"
1106 );
1107 assert!(
1108 !output.contains(&fingerprint),
1109 "CertifiedKeyWrapper Debug leaked the certificate fingerprint: {output}"
1110 );
1111 assert!(
1112 output.contains("fingerprint_bytes: 32"),
1113 "CertifiedKeyWrapper Debug omitted bounded fingerprint metadata: {output}"
1114 );
1115 assert!(
1116 output.contains("[redacted]"),
1117 "CertifiedKeyWrapper Debug must make certificate redaction explicit: {output}"
1118 );
1119 assert!(
1120 output.len() <= 1024,
1121 "CertifiedKeyWrapper Debug output is not bounded: {} bytes",
1122 output.len()
1123 );
1124 }
1125
1126 #[test]
1127 fn certificate_resolver_debug_redacts_indexed_names() {
1128 const NAME_SECRET: &str = "CERTIFICATE_RESOLVER_NAME_SECRET_SENTINEL";
1129
1130 let mut resolver = CertificateResolver::default();
1131 resolver
1132 .add_certificate(&AddCertificate {
1133 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
1134 certificate: CertificateAndKey {
1135 certificate: include_str!("../assets/certificate.pem").to_owned(),
1136 key: include_str!("../assets/key.pem").to_owned(),
1137 names: vec![format!("{NAME_SECRET}{}", "x".repeat(2048))],
1142 ..Default::default()
1143 },
1144 expired_at: None,
1145 })
1146 .expect("test certificate must load into the resolver");
1147 let output = format!("{resolver:?}");
1148
1149 assert!(
1150 !output.contains(NAME_SECRET),
1151 "CertificateResolver Debug leaked an indexed certificate name: {output}"
1152 );
1153 assert!(
1154 output.len() <= 1024,
1155 "CertificateResolver Debug output is not bounded: {} bytes",
1156 output.len()
1157 );
1158 }
1159
1160 #[test]
1161 fn lifecycle() -> Result<(), Box<dyn Error + Send + Sync>> {
1162 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1163 let mut resolver = CertificateResolver::default();
1164 let certificate_and_key = CertificateAndKey {
1165 certificate: String::from(include_str!("../assets/certificate.pem")),
1166 key: String::from(include_str!("../assets/key.pem")),
1167 ..Default::default()
1168 };
1169
1170 let fingerprint = resolver
1171 .add_certificate(&AddCertificate {
1172 address,
1173 certificate: certificate_and_key,
1174 expired_at: None,
1175 })
1176 .expect("could not add certificate");
1177
1178 if resolver.get_certificate(&fingerprint).is_none() {
1179 return Err("failed to retrieve certificate".into());
1180 }
1181
1182 let names = resolver.certificate_names(&fingerprint)?;
1184
1185 if let Err(err) = resolver.remove_certificate(&fingerprint) {
1186 return Err(format!("the certificate was not removed, {err}").into());
1187 }
1188
1189 if resolver.get_certificate(&fingerprint).is_some() {
1190 return Err("We have retrieved the certificate that should be deleted".into());
1191 }
1192
1193 if !resolver.find_certificates_by_names(&names)?.is_empty() {
1194 return Err(
1195 "The certificate should be deleted but one of its names is in the index".into(),
1196 );
1197 }
1198
1199 Ok(())
1200 }
1201
1202 #[test]
1203 fn name_override() -> Result<(), Box<dyn Error + Send + Sync>> {
1204 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1205 let mut resolver = CertificateResolver::default();
1206 let certificate_and_key = CertificateAndKey {
1207 certificate: String::from(include_str!("../assets/certificate.pem")),
1208 key: String::from(include_str!("../assets/key.pem")),
1209 names: vec!["localhost".into(), "lolcatho.st".into()],
1210 ..Default::default()
1211 };
1212
1213 let fingerprint = resolver.add_certificate(&AddCertificate {
1214 address,
1215 certificate: certificate_and_key,
1216 expired_at: None,
1217 })?;
1218
1219 if resolver.get_certificate(&fingerprint).is_none() {
1220 return Err("failed to retrieve certificate".into());
1221 }
1222
1223 let mut lolcat = HashSet::new();
1224 lolcat.insert(String::from("lolcatho.st"));
1225 if resolver.find_certificates_by_names(&lolcat)?.is_empty()
1226 || resolver.get_certificate(&fingerprint).is_none()
1227 {
1228 return Err("failed to retrieve certificate with custom names".into());
1229 }
1230
1231 if let Err(err) = resolver.remove_certificate(&fingerprint) {
1232 return Err(format!("the certificate could not be removed, {err}").into());
1233 }
1234
1235 let names = resolver.certificate_names(&fingerprint)?;
1236 if !resolver.find_certificates_by_names(&names)?.is_empty()
1237 && resolver.get_certificate(&fingerprint).is_some()
1238 {
1239 return Err("We have retrieved the certificate that should be deleted".into());
1240 }
1241
1242 Ok(())
1243 }
1244
1245 #[test]
1246 fn keep_resolving_with_wildcard() -> Result<(), Box<dyn Error + Send + Sync>> {
1247 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1248 let mut resolver = CertificateResolver::default();
1249
1250 let wildcard_example_org = CertificateAndKey {
1253 certificate: String::from(include_str!("../assets/tests/certificate-3.pem")),
1254 key: String::from(include_str!("../assets/tests/key.pem")),
1255 ..Default::default()
1256 };
1257
1258 let wildcard_example_org_fingerprint = resolver.add_certificate(&AddCertificate {
1259 address,
1260 certificate: wildcard_example_org,
1261 expired_at: Some(
1262 (SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?
1263 + Duration::from_secs(365 * 24 * 3600))
1264 .as_secs() as i64,
1265 ),
1266 })?;
1267
1268 if resolver
1269 .get_certificate(&wildcard_example_org_fingerprint)
1270 .is_none()
1271 {
1272 return Err("could not load the 2-year-valid certificate".into());
1273 }
1274
1275 let www_example_org = CertificateAndKey {
1279 certificate: String::from(include_str!("../assets/tests/certificate-2.pem")),
1280 key: String::from(include_str!("../assets/tests/key.pem")),
1281 ..Default::default()
1282 };
1283
1284 let www_example_org_fingerprint = resolver.add_certificate(&AddCertificate {
1285 address,
1286 certificate: www_example_org,
1287 expired_at: Some(
1288 (SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?
1289 + Duration::from_secs(2 * 365 * 24 * 3600))
1290 .as_secs() as i64,
1291 ),
1292 })?;
1293
1294 let www_example_org = resolver
1295 .domain_lookup("www.example.org".as_bytes(), true)
1296 .expect("there should be a www.example.org cert");
1297 assert_eq!(www_example_org.1, www_example_org_fingerprint);
1298
1299 let test_example_org = resolver
1300 .domain_lookup("test.example.org".as_bytes(), true)
1301 .expect("there should be a test.example.org cert");
1302 assert_eq!(test_example_org.1, wildcard_example_org_fingerprint);
1303
1304 let example_org = resolver
1305 .domain_lookup("example.org".as_bytes(), true)
1306 .expect("there should be a example.org cert");
1307 assert_eq!(example_org.1, www_example_org_fingerprint);
1308
1309 resolver
1312 .remove_certificate(&www_example_org_fingerprint)
1313 .expect("should be able to remove the 2-year certificate");
1314
1315 let should_be_wildcard_fingerprint = resolver
1316 .domain_lookup("www.example.org".as_bytes(), true)
1317 .expect("there should be a www.example.org cert");
1318 assert_eq!(
1319 should_be_wildcard_fingerprint.1,
1320 wildcard_example_org_fingerprint
1321 );
1322
1323 assert!(
1324 resolver
1325 .domain_lookup("example.org".as_bytes(), true)
1326 .is_none()
1327 );
1328
1329 Ok(())
1330 }
1331
1332 #[test]
1333 fn resolve_the_longer_lived_cert() -> Result<(), Box<dyn Error + Send + Sync>> {
1334 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1335 let mut resolver = CertificateResolver::default();
1336
1337 let certificate_and_key_2y = CertificateAndKey {
1340 certificate: String::from(include_str!("../assets/tests/certificate-2y.pem")),
1341 key: String::from(include_str!("../assets/tests/key-2y.pem")),
1342 ..Default::default()
1343 };
1344
1345 let fingerprint_2y = resolver.add_certificate(&AddCertificate {
1346 address,
1347 certificate: certificate_and_key_2y,
1348 expired_at: None,
1349 })?;
1350
1351 if resolver.get_certificate(&fingerprint_2y).is_none() {
1352 return Err("could not load the 2-year-valid certificate".into());
1353 }
1354
1355 let certificate_and_key_1y = CertificateAndKey {
1358 certificate: String::from(include_str!("../assets/tests/certificate-1y.pem")),
1359 key: String::from(include_str!("../assets/tests/key-1y.pem")),
1360 ..Default::default()
1361 };
1362
1363 let fingerprint_1y = resolver.add_certificate(&AddCertificate {
1364 address,
1365 certificate: certificate_and_key_1y,
1366 ..Default::default()
1367 })?;
1368
1369 let localhost_cert = resolver
1370 .domain_lookup("localhost".as_bytes(), true)
1371 .expect("there should be a localhost cert");
1372
1373 assert_eq!(localhost_cert.1, fingerprint_2y);
1374
1375 resolver
1379 .remove_certificate(&fingerprint_2y)
1380 .expect("should be able to remove the 2-year certificate");
1381
1382 let localhost_cert = resolver
1383 .domain_lookup("localhost".as_bytes(), true)
1384 .expect("there should be a localhost cert");
1385
1386 assert_eq!(localhost_cert.1, fingerprint_1y);
1387
1388 Ok(())
1389 }
1390
1391 #[test]
1392 fn expiration_override() -> Result<(), Box<dyn Error + Send + Sync>> {
1393 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1394 let mut resolver = CertificateResolver::default();
1395
1396 let certificate_and_key_1y = CertificateAndKey {
1399 certificate: String::from(include_str!("../assets/tests/certificate-1y.pem")),
1400 key: String::from(include_str!("../assets/tests/key-1y.pem")),
1401 ..Default::default()
1402 };
1403
1404 let fingerprint_1y_overriden = resolver.add_certificate(&AddCertificate {
1405 address,
1406 certificate: certificate_and_key_1y,
1407 expired_at: Some(
1408 (SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?
1409 + Duration::from_secs(3 * 365 * 24 * 3600))
1410 .as_secs() as i64,
1411 ),
1412 })?;
1413
1414 if resolver
1415 .get_certificate(&fingerprint_1y_overriden)
1416 .is_none()
1417 {
1418 return Err("failed to retrieve certificate".into());
1419 }
1420
1421 let certificate_and_key_2y = CertificateAndKey {
1424 certificate: String::from(include_str!("../assets/tests/certificate-2y.pem")),
1425 key: String::from(include_str!("../assets/tests/key-2y.pem")),
1426 ..Default::default()
1427 };
1428
1429 let fingerprint_2y = resolver.add_certificate(&AddCertificate {
1430 address,
1431 certificate: certificate_and_key_2y,
1432 expired_at: None,
1433 })?;
1434
1435 let localhost_cert = resolver
1436 .domain_lookup("localhost".as_bytes(), true)
1437 .expect("there should be a localhost cert");
1438
1439 assert_eq!(localhost_cert.1, fingerprint_1y_overriden);
1440
1441 resolver
1445 .remove_certificate(&fingerprint_1y_overriden)
1446 .expect("should be able to remove the 1-year (3-year-overriden) certificate");
1447
1448 let localhost_cert = resolver
1449 .domain_lookup("localhost".as_bytes(), true)
1450 .expect("there should be a localhost cert");
1451
1452 assert_eq!(localhost_cert.1, fingerprint_2y);
1453
1454 Ok(())
1455 }
1456
1457 #[test]
1460 fn replace_certificate_add_before_remove() -> Result<(), Box<dyn Error + Send + Sync>> {
1461 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1462 let mut resolver = CertificateResolver::default();
1463
1464 let cert_1y = CertificateAndKey {
1466 certificate: String::from(include_str!("../assets/tests/certificate-1y.pem")),
1467 key: String::from(include_str!("../assets/tests/key-1y.pem")),
1468 ..Default::default()
1469 };
1470
1471 let fingerprint_1y = resolver.add_certificate(&AddCertificate {
1472 address,
1473 certificate: cert_1y,
1474 expired_at: None,
1475 })?;
1476
1477 assert!(
1479 resolver
1480 .domain_lookup("localhost".as_bytes(), true)
1481 .is_some(),
1482 "initial certificate should be resolvable"
1483 );
1484
1485 let cert_2y = CertificateAndKey {
1487 certificate: String::from(include_str!("../assets/tests/certificate-2y.pem")),
1488 key: String::from(include_str!("../assets/tests/key-2y.pem")),
1489 ..Default::default()
1490 };
1491
1492 let new_fingerprint = resolver.replace_certificate(&ReplaceCertificate {
1493 address,
1494 new_certificate: cert_2y,
1495 old_fingerprint: fingerprint_1y.to_string(),
1496 new_expired_at: None,
1497 })?;
1498
1499 assert!(
1501 resolver.get_certificate(&fingerprint_1y).is_none(),
1502 "old certificate should have been removed"
1503 );
1504
1505 assert!(
1507 resolver.get_certificate(&new_fingerprint).is_some(),
1508 "new certificate should be present"
1509 );
1510 let resolved = resolver
1511 .domain_lookup("localhost".as_bytes(), true)
1512 .expect("a certificate should resolve for localhost");
1513 assert_eq!(
1514 resolved.1, new_fingerprint,
1515 "resolved certificate should be the replacement"
1516 );
1517
1518 Ok(())
1519 }
1520
1521 #[test]
1527 fn replace_certificate_with_invalid_old_fingerprint_keeps_compatibility()
1528 -> Result<(), Box<dyn Error + Send + Sync>> {
1529 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1530 let mut resolver = CertificateResolver::default();
1531 let old_fingerprint = resolver.add_certificate(&AddCertificate {
1532 address,
1533 certificate: CertificateAndKey {
1534 certificate: include_str!("../assets/tests/certificate-1y.pem").to_owned(),
1535 key: include_str!("../assets/tests/key-1y.pem").to_owned(),
1536 ..Default::default()
1537 },
1538 expired_at: None,
1539 })?;
1540
1541 let new_fingerprint = resolver.replace_certificate(&ReplaceCertificate {
1542 address,
1543 new_certificate: CertificateAndKey {
1544 certificate: include_str!("../assets/tests/certificate-2y.pem").to_owned(),
1545 key: include_str!("../assets/tests/key-2y.pem").to_owned(),
1546 ..Default::default()
1547 },
1548 old_fingerprint: "not-a-hex-fingerprint".to_owned(),
1549 new_expired_at: None,
1550 })?;
1551
1552 assert!(resolver.certificates.contains_key(&old_fingerprint));
1553 assert!(resolver.certificates.contains_key(&new_fingerprint));
1554
1555 Ok(())
1556 }
1557
1558 #[test]
1567 fn replace_certificate_with_same_fingerprint_is_noop()
1568 -> Result<(), Box<dyn Error + Send + Sync>> {
1569 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1570 let mut resolver = CertificateResolver::default();
1571
1572 let cert = CertificateAndKey {
1573 certificate: String::from(include_str!("../assets/tests/certificate-1y.pem")),
1574 key: String::from(include_str!("../assets/tests/key-1y.pem")),
1575 ..Default::default()
1576 };
1577
1578 let initial_fingerprint = resolver.add_certificate(&AddCertificate {
1579 address,
1580 certificate: cert.clone(),
1581 expired_at: None,
1582 })?;
1583
1584 let returned_fingerprint = resolver.replace_certificate(&ReplaceCertificate {
1586 address,
1587 new_certificate: cert,
1588 old_fingerprint: initial_fingerprint.to_string(),
1589 new_expired_at: None,
1590 })?;
1591
1592 assert_eq!(
1593 returned_fingerprint, initial_fingerprint,
1594 "idempotent replace should return the existing fingerprint"
1595 );
1596
1597 assert!(
1598 resolver.get_certificate(&initial_fingerprint).is_some(),
1599 "idempotent replace must NOT delete the existing certificate"
1600 );
1601
1602 let resolved = resolver
1603 .domain_lookup("localhost".as_bytes(), true)
1604 .expect("certificate should still resolve after idempotent replace");
1605 assert_eq!(
1606 resolved.1, initial_fingerprint,
1607 "resolver should still hand back the original fingerprint"
1608 );
1609
1610 Ok(())
1611 }
1612
1613 #[test]
1618 fn strict_same_fingerprint_retry_preserves_tie_break_and_wrapper()
1619 -> Result<(), Box<dyn Error + Send + Sync>> {
1620 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1621 let mut resolver = CertificateResolver::default();
1622 let shared_expiration = 2_000_000_000;
1623
1624 let other_certificate = CertificateAndKey {
1625 certificate: include_str!("../assets/tests/certificate-2y.pem").to_owned(),
1626 key: include_str!("../assets/tests/key-2y.pem").to_owned(),
1627 names: vec!["tie.example.com".to_owned()],
1628 ..Default::default()
1629 };
1630 resolver.add_certificate(&AddCertificate {
1631 address,
1632 certificate: other_certificate,
1633 expired_at: Some(shared_expiration),
1634 })?;
1635
1636 let target_certificate = CertificateAndKey {
1637 certificate: include_str!("../assets/tests/certificate-1y.pem").to_owned(),
1638 key: include_str!("../assets/tests/key-1y.pem").to_owned(),
1639 names: vec!["tie.example.com".to_owned()],
1640 ..Default::default()
1641 };
1642 let target_fingerprint = resolver.add_certificate(&AddCertificate {
1643 address,
1644 certificate: target_certificate.to_owned(),
1645 expired_at: Some(shared_expiration),
1646 })?;
1647
1648 let index_before = resolver.name_fingerprint_idx.to_owned();
1649 let domains_before = format!("{:?}", resolver.domains);
1650 let winner_before = resolver
1651 .domain_lookup(b"tie.example.com", true)
1652 .expect("the tied certificates must have a selected winner")
1653 .1
1654 .to_owned();
1655 let wrapper_before = resolver
1656 .certificates
1657 .get(&target_fingerprint)
1658 .expect("the target certificate must be stored")
1659 .inner
1660 .to_owned();
1661
1662 let returned_fingerprint = resolver.replace_certificate(&ReplaceCertificate {
1663 address,
1664 new_certificate: target_certificate,
1665 old_fingerprint: target_fingerprint.to_string(),
1666 new_expired_at: Some(shared_expiration),
1667 })?;
1668
1669 assert_eq!(returned_fingerprint, target_fingerprint);
1670 assert_eq!(winner_before, target_fingerprint);
1671 assert_eq!(resolver.name_fingerprint_idx, index_before);
1672 assert_eq!(format!("{:?}", resolver.domains), domains_before);
1673 assert_eq!(
1674 resolver
1675 .domain_lookup(b"tie.example.com", true)
1676 .expect("the strict retry must preserve the selected winner")
1677 .1,
1678 target_fingerprint,
1679 );
1680 assert!(Arc::ptr_eq(
1681 &wrapper_before,
1682 &resolver
1683 .certificates
1684 .get(&target_fingerprint)
1685 .expect("the strict retry must preserve the stored certificate")
1686 .inner,
1687 ));
1688
1689 Ok(())
1690 }
1691
1692 #[test]
1695 fn removal_cleans_up_empty_index_entries() -> Result<(), Box<dyn Error + Send + Sync>> {
1696 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1697 let mut resolver = CertificateResolver::default();
1698
1699 let cert = CertificateAndKey {
1700 certificate: String::from(include_str!("../assets/tests/certificate-1y.pem")),
1701 key: String::from(include_str!("../assets/tests/key-1y.pem")),
1702 ..Default::default()
1703 };
1704
1705 let fingerprint = resolver.add_certificate(&AddCertificate {
1706 address,
1707 certificate: cert,
1708 expired_at: None,
1709 })?;
1710
1711 let names = resolver.certificate_names(&fingerprint)?;
1713 assert!(
1714 !names.is_empty(),
1715 "certificate should have at least one name"
1716 );
1717
1718 for name in &names {
1720 assert!(
1721 resolver.name_fingerprint_idx.contains_key(name),
1722 "name_fingerprint_idx should contain '{name}' before removal"
1723 );
1724 }
1725
1726 resolver.remove_certificate(&fingerprint)?;
1727
1728 for name in &names {
1730 assert!(
1731 !resolver.name_fingerprint_idx.contains_key(name),
1732 "name_fingerprint_idx should not contain empty entry for '{name}' after removal"
1733 );
1734 }
1735
1736 Ok(())
1737 }
1738
1739 #[test]
1753 fn certificate_chain_dedup_drops_duplicate_leaf() -> Result<(), Box<dyn Error + Send + Sync>> {
1754 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1755 let mut resolver = CertificateResolver::default();
1756
1757 let leaf_pem = String::from(include_str!("../assets/certificate.pem"));
1758
1759 let cert_with_duplicated_leaf = CertificateAndKey {
1760 certificate: leaf_pem.clone(),
1761 certificate_chain: vec![leaf_pem],
1762 key: String::from(include_str!("../assets/key.pem")),
1763 ..Default::default()
1764 };
1765
1766 let fingerprint = resolver.add_certificate(&AddCertificate {
1767 address,
1768 certificate: cert_with_duplicated_leaf,
1769 expired_at: None,
1770 })?;
1771
1772 let stored = resolver
1773 .get_certificate(&fingerprint)
1774 .ok_or("resolver lost the certificate after add")?;
1775
1776 assert_eq!(
1777 stored.inner.cert.len(),
1778 1,
1779 "expected dedup to drop the duplicate leaf, got chain of {} cert(s)",
1780 stored.inner.cert.len()
1781 );
1782
1783 Ok(())
1784 }
1785
1786 #[test]
1800 fn certificate_chain_handles_multi_pem_single_entry() -> Result<(), Box<dyn Error + Send + Sync>>
1801 {
1802 let address = SocketAddress::new_v4(127, 0, 0, 1, 8080);
1803 let mut resolver = CertificateResolver::default();
1804
1805 let leaf_pem = String::from(include_str!("../assets/certificate.pem"));
1806 let multi_pem_chain_entry = format!("{leaf_pem}\n{leaf_pem}");
1809
1810 let cert = CertificateAndKey {
1811 certificate: leaf_pem,
1812 certificate_chain: vec![multi_pem_chain_entry],
1813 key: String::from(include_str!("../assets/key.pem")),
1814 ..Default::default()
1815 };
1816
1817 let fingerprint = resolver.add_certificate(&AddCertificate {
1818 address,
1819 certificate: cert,
1820 expired_at: None,
1821 })?;
1822
1823 let stored = resolver
1824 .get_certificate(&fingerprint)
1825 .ok_or("resolver lost the certificate after add")?;
1826
1827 assert_eq!(
1832 stored.inner.cert.len(),
1833 1,
1834 "expected split + dedup to leave only the leaf, got chain of {} cert(s)",
1835 stored.inner.cert.len()
1836 );
1837
1838 Ok(())
1839 }
1840
1841 #[test]
1868 fn a_mixed_case_certificate_name_resolves_for_the_lowercased_sni() {
1869 const MIXED: &str = "MiXeD.Example.COM";
1870 const LOWER: &str = "mixed.example.com";
1871
1872 let mut resolver = CertificateResolver::default();
1873 let fingerprint = resolver
1874 .add_certificate(&AddCertificate {
1875 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
1876 certificate: CertificateAndKey {
1877 certificate: include_str!("../assets/certificate.pem").to_owned(),
1878 key: include_str!("../assets/key.pem").to_owned(),
1879 names: vec![MIXED.to_owned()],
1880 ..Default::default()
1881 },
1882 expired_at: None,
1883 })
1884 .expect("the mixed-case certificate must load");
1885
1886 assert!(
1887 resolver.domain_lookup(LOWER.as_bytes(), true).is_some(),
1888 "rustls only ever asks for the lowercased SNI, so that is the \
1889 only key the trie can usefully hold",
1890 );
1891 assert_eq!(
1892 resolver.names_for_sni(LOWER.as_bytes()),
1893 Some(vec![LOWER.to_owned()]),
1894 "the SAN snapshot feeding the H2 `:authority` binding must \
1895 carry the normalised name",
1896 );
1897
1898 resolver
1902 .remove_certificate(&fingerprint)
1903 .expect("the mixed-case certificate must be removable");
1904 assert!(
1905 resolver.domain_lookup(LOWER.as_bytes(), true).is_none(),
1906 "removing the certificate must retire its normalised trie key",
1907 );
1908 }
1909
1910 #[test]
1943 fn a_certificate_name_carrying_a_regex_segment_is_refused() {
1944 const REGEX_NAME: &str = "/te.*/.example.com";
1945
1946 let mut resolver = CertificateResolver::default();
1947 let error = resolver
1948 .add_certificate(&AddCertificate {
1949 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
1950 certificate: CertificateAndKey {
1951 certificate: include_str!("../assets/certificate.pem").to_owned(),
1952 key: include_str!("../assets/key.pem").to_owned(),
1953 names: vec![REGEX_NAME.to_owned()],
1954 ..Default::default()
1955 },
1956 expired_at: None,
1957 })
1958 .expect_err("a certificate name carrying a regex segment must be refused");
1959
1960 assert!(
1961 matches!(&error, CertificateResolverError::InvalidName(name) if name == REGEX_NAME),
1962 "the refusal must be InvalidName, got {error:?}",
1963 );
1964
1965 assert!(
1970 resolver.certificates.is_empty(),
1971 "a refused certificate must not be stored",
1972 );
1973 assert!(
1974 resolver.name_fingerprint_idx.is_empty(),
1975 "a refused certificate must not index any name",
1976 );
1977 for sni in ["test.example.com", "tenant.example.com", REGEX_NAME] {
1978 assert!(
1979 resolver.domain_lookup(sni.as_bytes(), true).is_none(),
1980 "a refused certificate name must bind no SNI, yet {sni} resolved",
1981 );
1982 }
1983
1984 resolver
1987 .add_certificate(&AddCertificate {
1988 address: SocketAddress::new_v4(127, 0, 0, 1, 8443),
1989 certificate: CertificateAndKey {
1990 certificate: include_str!("../assets/certificate.pem").to_owned(),
1991 key: include_str!("../assets/key.pem").to_owned(),
1992 names: vec!["*.example.com".to_owned()],
1993 ..Default::default()
1994 },
1995 expired_at: None,
1996 })
1997 .expect("a wildcard certificate name must still load");
1998 assert!(
1999 resolver
2000 .domain_lookup(b"tenant.example.com", true)
2001 .is_some(),
2002 "the wildcard certificate must still serve a subdomain",
2003 );
2004 }
2005}