1use std::collections::HashSet;
9use std::fmt;
10use std::sync::Arc;
11use std::time::Duration;
12
13use futures_util::future::join_all;
14
15use crate::cache::{BadgeCache, CacheConfig, CacheKey};
16use crate::dane::{DanePolicy, DaneVerificationResult, verify_dane};
17use crate::dns::{
18 BadgeRecord, DnsLookupResult, DnsResolver, DnsResolverConfig, HickoryDnsResolver,
19};
20use crate::error::{AnsError, AnsResult, DaneError, DnsError, TlogError, VerificationError};
21use crate::tlog::{HttpTransparencyLogClient, TransparencyLogClient};
22use ans_types::{AnsName, Badge, BadgeStatus, CertFingerprint, CryptoError, Fqdn, Version};
23
24type ParsedCertData = (Option<String>, Vec<String>, Vec<String>);
26
27#[derive(Debug, Clone)]
36pub struct CertIdentity {
37 pub(crate) common_name: Option<String>,
39 pub(crate) dns_sans: Vec<String>,
41 pub(crate) uri_sans: Vec<String>,
43 pub(crate) fingerprint: CertFingerprint,
45}
46
47impl CertIdentity {
48 pub fn common_name(&self) -> Option<&str> {
50 self.common_name.as_deref()
51 }
52
53 pub fn dns_sans(&self) -> &[String] {
55 &self.dns_sans
56 }
57
58 pub fn uri_sans(&self) -> &[String] {
60 &self.uri_sans
61 }
62
63 pub fn fingerprint(&self) -> &CertFingerprint {
65 &self.fingerprint
66 }
67
68 pub fn new(
74 common_name: Option<String>,
75 dns_sans: Vec<String>,
76 uri_sans: Vec<String>,
77 fingerprint: CertFingerprint,
78 ) -> Self {
79 Self {
80 common_name,
81 dns_sans,
82 uri_sans,
83 fingerprint,
84 }
85 }
86
87 pub fn from_der(der: &[u8]) -> Result<Self, CryptoError> {
92 let fingerprint = CertFingerprint::from_der(der);
93 let (common_name, dns_sans, uri_sans) = Self::parse_cert_der(der)?;
94
95 Ok(Self {
96 common_name,
97 dns_sans,
98 uri_sans,
99 fingerprint,
100 })
101 }
102
103 pub fn from_fingerprint_and_cn(fingerprint: CertFingerprint, cn: String) -> Self {
108 Self {
109 common_name: Some(cn.clone()),
110 dns_sans: vec![cn],
111 uri_sans: vec![],
112 fingerprint,
113 }
114 }
115
116 fn parse_cert_der(der: &[u8]) -> Result<ParsedCertData, CryptoError> {
118 use x509_parser::prelude::*;
119
120 let (_, cert) = X509Certificate::from_der(der)
121 .map_err(|e| CryptoError::ParseFailed(format!("X.509 parse error: {e}")))?;
122
123 let cn = cert
125 .subject()
126 .iter_common_name()
127 .next()
128 .and_then(|attr| attr.as_str().ok())
129 .map(String::from);
130
131 let mut dns_sans = Vec::new();
133 let mut uri_sans = Vec::new();
134
135 if let Ok(Some(san_ext)) = cert.subject_alternative_name() {
136 for name in &san_ext.value.general_names {
137 match name {
138 GeneralName::DNSName(dns) => dns_sans.push((*dns).to_string()),
139 GeneralName::URI(uri) => uri_sans.push((*uri).to_string()),
140 _ => {}
141 }
142 }
143 }
144
145 Ok((cn, dns_sans, uri_sans))
146 }
147
148 pub fn fqdn(&self) -> Option<&str> {
152 self.dns_sans
153 .first()
154 .map(std::string::String::as_str)
155 .or(self.common_name.as_deref())
156 }
157
158 pub fn ans_name(&self) -> Option<AnsName> {
160 self.uri_sans
161 .iter()
162 .filter(|uri| uri.starts_with("ans://"))
163 .find_map(|uri| AnsName::parse(uri).ok())
164 }
165
166 pub fn version(&self) -> Option<Version> {
168 self.ans_name().map(|name| name.version().clone())
169 }
170}
171
172#[derive(Debug)]
174#[non_exhaustive]
175pub enum VerificationOutcome {
176 Verified {
178 badge: Badge,
180 matched_fingerprint: CertFingerprint,
182 },
183
184 NotAnsAgent {
186 fqdn: String,
188 },
189
190 InvalidStatus {
192 status: BadgeStatus,
194 badge: Badge,
196 },
197
198 FingerprintMismatch {
200 expected: String,
202 actual: String,
204 badge: Badge,
206 },
207
208 HostnameMismatch {
210 expected: String,
212 actual: String,
214 badge: Badge,
216 },
217
218 AnsNameMismatch {
220 expected: String,
222 actual: String,
224 badge: Badge,
226 },
227
228 DnsError(DnsError),
230
231 TlogError(TlogError),
233
234 CertError(CryptoError),
236
237 ParseError(ans_types::ParseError),
239
240 DaneError(DaneError),
242}
243
244impl VerificationOutcome {
245 pub fn is_success(&self) -> bool {
247 matches!(self, Self::Verified { .. })
248 }
249
250 pub fn is_not_ans_agent(&self) -> bool {
252 matches!(self, Self::NotAnsAgent { .. })
253 }
254
255 pub fn badge(&self) -> Option<&Badge> {
257 match self {
258 Self::Verified { badge, .. }
259 | Self::InvalidStatus { badge, .. }
260 | Self::FingerprintMismatch { badge, .. }
261 | Self::HostnameMismatch { badge, .. }
262 | Self::AnsNameMismatch { badge, .. } => Some(badge),
263 _ => None,
264 }
265 }
266
267 pub fn into_result(self) -> AnsResult<Badge> {
269 match self {
270 Self::Verified { badge, .. } => Ok(badge),
271 Self::NotAnsAgent { fqdn } => Err(AnsError::Dns(DnsError::NotFound { fqdn })),
272 Self::InvalidStatus { status, .. } => {
273 Err(AnsError::Verification(VerificationError::InvalidStatus {
274 status,
275 }))
276 }
277 Self::FingerprintMismatch {
278 expected, actual, ..
279 } => Err(AnsError::Verification(
280 VerificationError::FingerprintMismatch { expected, actual },
281 )),
282 Self::HostnameMismatch {
283 expected, actual, ..
284 } => Err(AnsError::Verification(
285 VerificationError::HostnameMismatch { expected, actual },
286 )),
287 Self::AnsNameMismatch {
288 expected, actual, ..
289 } => Err(AnsError::Verification(VerificationError::AnsNameMismatch {
290 expected,
291 actual,
292 })),
293 Self::DnsError(e) => Err(AnsError::Dns(e)),
294 Self::TlogError(e) => Err(AnsError::TransparencyLog(e)),
295 Self::CertError(e) => Err(AnsError::Certificate(e)),
296 Self::ParseError(e) => Err(AnsError::Parse(e)),
297 Self::DaneError(e) => Err(AnsError::Verification(
298 VerificationError::DaneVerificationFailed(e),
299 )),
300 }
301 }
302}
303
304#[derive(Debug, Clone, Copy, Default)]
306#[non_exhaustive]
307pub enum FailurePolicy {
308 #[default]
310 FailClosed,
311
312 FailOpenWithCache {
314 max_staleness: Duration,
316 },
317}
318
319fn validate_badge_domain(trusted: Option<&HashSet<String>>, url: &str) -> Result<(), TlogError> {
327 let Some(trusted) = trusted else {
328 return Ok(());
329 };
330 let parsed = url::Url::parse(url)
331 .map_err(|e| TlogError::InvalidUrl(format!("Badge URL is invalid: {e}")))?;
332 let domain = parsed
333 .host_str()
334 .ok_or_else(|| TlogError::InvalidUrl(format!("Badge URL has no host: {url}")))?;
335 if trusted.contains(domain) {
336 Ok(())
337 } else {
338 Err(TlogError::UntrustedDomain {
339 domain: domain.to_string(),
340 trusted: trusted.iter().cloned().collect(),
341 })
342 }
343}
344
345pub struct ServerVerifier {
347 dns_resolver: Arc<dyn DnsResolver>,
348 tlog_client: Arc<dyn TransparencyLogClient>,
349 cache: Option<Arc<BadgeCache>>,
350 failure_policy: FailurePolicy,
351 dane_policy: DanePolicy,
352 dane_port: u16,
354 trusted_ra_domains: Option<HashSet<String>>,
356}
357
358impl fmt::Debug for ServerVerifier {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 f.debug_struct("ServerVerifier")
361 .field("failure_policy", &self.failure_policy)
362 .field("dane_policy", &self.dane_policy)
363 .field("dane_port", &self.dane_port)
364 .field("has_cache", &self.cache.is_some())
365 .field("has_trusted_ra_domains", &self.trusted_ra_domains.is_some())
366 .finish_non_exhaustive()
367 }
368}
369
370impl ServerVerifier {
371 pub fn builder() -> ServerVerifierBuilder {
373 ServerVerifierBuilder::default()
374 }
375
376 pub async fn verify(&self, fqdn: &Fqdn, server_cert: &CertIdentity) -> VerificationOutcome {
388 tracing::info!(fqdn = %fqdn, "Starting server verification");
389 tracing::debug!(
390 cert_cn = ?server_cert.common_name,
391 cert_fingerprint = %server_cert.fingerprint,
392 "Certificate details"
393 );
394
395 if let Some(cache) = &self.cache {
397 let cached_badges = cache.get_all_for_fqdn(fqdn).await;
398 if !cached_badges.is_empty() {
399 tracing::debug!(fqdn = %fqdn, count = cached_badges.len(), "Scanning cached badges");
400 for cached in &cached_badges {
401 let outcome = self.verify_against_badge(&cached.badge, server_cert, true);
402 if outcome.is_success() {
403 tracing::debug!(fqdn = %fqdn, "Cache hit — badge matched");
404 return outcome;
405 }
406 }
407 tracing::info!(fqdn = %fqdn, "No cached badge matched fingerprint, fetching fresh");
409 }
410 }
411
412 tracing::debug!(fqdn = %fqdn, "Performing DNS lookup for _ans-badge / _ra-badge");
414 let records = match self.dns_resolver.lookup_badge(fqdn).await {
415 Ok(DnsLookupResult::Found(records)) => {
416 tracing::debug!(count = records.len(), "Found badge records");
417 for (i, r) in records.iter().enumerate() {
418 tracing::debug!(index = i, version = ?r.version, url = %r.url, "Badge record");
419 }
420 records
421 }
422 Ok(DnsLookupResult::NotFound) => {
423 tracing::warn!(fqdn = %fqdn, "No badge record found - not an ANS agent");
424 return VerificationOutcome::NotAnsAgent {
425 fqdn: fqdn.to_string(),
426 };
427 }
428 Err(e) => {
429 tracing::error!(fqdn = %fqdn, error = %e, "DNS lookup failed");
430 return self.handle_dns_error(e, fqdn, server_cert).await;
431 }
432 };
433
434 let outcome = self
438 .verify_against_records(&records, fqdn, server_cert)
439 .await;
440
441 if !outcome.is_success() {
442 return outcome;
443 }
444
445 if self.dane_policy.should_verify() {
447 match self.verify_dane(fqdn, server_cert).await {
448 Ok(result) => {
449 if !result.is_acceptable(self.dane_policy) {
450 tracing::error!(
451 fqdn = %fqdn,
452 dane_policy = ?self.dane_policy,
453 "DANE verification failed"
454 );
455 return VerificationOutcome::DaneError(DaneError::FingerprintMismatch);
456 }
457 }
458 Err(e) => {
459 tracing::error!(fqdn = %fqdn, error = %e, "DANE verification error");
460 return VerificationOutcome::DaneError(e);
461 }
462 }
463 }
464
465 outcome
466 }
467
468 async fn verify_against_records(
478 &self,
479 records: &[BadgeRecord],
480 fqdn: &Fqdn,
481 server_cert: &CertIdentity,
482 ) -> VerificationOutcome {
483 let mut sorted: Vec<_> = records.iter().collect();
485 sorted.sort_by(|a, b| b.version.cmp(&a.version));
486
487 if let Some(cache) = &self.cache {
489 let versions: Vec<Version> =
490 sorted.iter().filter_map(|r| r.version().cloned()).collect();
491 if !versions.is_empty() {
492 cache.set_version_index(fqdn, versions).await;
493 }
494 }
495
496 let results = self.fetch_badges_parallel(&sorted).await;
498
499 let mut last_mismatch: Option<VerificationOutcome> = None;
500 let mut last_error: Option<AnsError> = None;
501
502 for (record, result) in results {
503 let badge = match result {
504 Ok(b) => b,
505 Err(e) => {
506 tracing::debug!(url = %record.url, error = %e, "Failed to fetch badge, trying next");
507 last_error = Some(AnsError::TransparencyLog(e));
508 continue;
509 }
510 };
511
512 tracing::debug!(
513 version = ?record.version,
514 status = ?badge.status,
515 "Checking badge record"
516 );
517
518 if let Some(cache) = &self.cache {
520 let version = record
521 .version()
522 .cloned()
523 .or_else(|| badge.agent_version().parse::<Version>().ok());
524 if let Some(v) = &version {
525 cache.insert_for_fqdn_version(fqdn, v, badge.clone()).await;
526 tracing::debug!(fqdn = %fqdn, version = %v, "Cached badge by version");
527 }
528 }
529
530 let outcome = self.verify_against_badge(&badge, server_cert, true);
531
532 match &outcome {
533 VerificationOutcome::Verified { .. } => {
534 return outcome;
535 }
536 VerificationOutcome::FingerprintMismatch { .. } => {
537 tracing::debug!(version = ?record.version, "Fingerprint mismatch, trying next record");
538 last_mismatch = Some(outcome);
539 }
540 _ => return outcome,
542 }
543 }
544
545 if last_mismatch.is_some() {
548 tracing::info!(fqdn = %fqdn, "No badge matched, attempting refresh-on-mismatch");
549 return self.verify_with_refresh(fqdn, server_cert).await;
550 }
551
552 match last_error {
554 Some(e) => self.handle_ans_error(e, fqdn, server_cert).await,
555 None => VerificationOutcome::NotAnsAgent {
556 fqdn: fqdn.to_string(),
557 },
558 }
559 }
560
561 async fn verify_dane(
563 &self,
564 fqdn: &Fqdn,
565 cert: &CertIdentity,
566 ) -> Result<DaneVerificationResult, DaneError> {
567 tracing::debug!(
568 fqdn = %fqdn,
569 port = self.dane_port,
570 policy = ?self.dane_policy,
571 "Starting DANE verification"
572 );
573
574 let tlsa_records = self
575 .dns_resolver
576 .get_tlsa_records(fqdn, self.dane_port)
577 .await?;
578
579 verify_dane(
580 &tlsa_records,
581 &cert.fingerprint,
582 self.dane_policy,
583 fqdn,
584 self.dane_port,
585 )
586 }
587
588 pub async fn prefetch(&self, fqdn: &Fqdn) -> Result<Badge, AnsError> {
593 let records = match self.dns_resolver.lookup_badge(fqdn).await {
594 Ok(DnsLookupResult::Found(records)) => records,
595 Ok(DnsLookupResult::NotFound) => {
596 return Err(AnsError::Dns(DnsError::NotFound {
597 fqdn: fqdn.to_string(),
598 }));
599 }
600 Err(e) => return Err(AnsError::Dns(e)),
601 };
602
603 let mut sorted: Vec<_> = records.iter().collect();
605 sorted.sort_by(|a, b| b.version.cmp(&a.version));
606
607 if let Some(cache) = &self.cache {
609 let versions: Vec<Version> =
610 sorted.iter().filter_map(|r| r.version().cloned()).collect();
611 if !versions.is_empty() {
612 cache.set_version_index(fqdn, versions).await;
613 }
614 }
615
616 let results = self.fetch_badges_parallel(&sorted).await;
618
619 let mut preferred: Option<Badge> = None;
620 let mut last_error = None;
621
622 for (record, result) in results {
623 match result {
624 Ok(badge) => {
625 if let Some(cache) = &self.cache {
627 let version = record
628 .version()
629 .cloned()
630 .or_else(|| badge.agent_version().parse::<Version>().ok());
631 if let Some(v) = &version {
632 cache.insert_for_fqdn_version(fqdn, v, badge.clone()).await;
633 tracing::debug!(fqdn = %fqdn, version = %v, "Prefetch: cached badge");
634 }
635 }
636
637 if preferred.is_none()
639 && (badge.status.is_active() || badge.status == BadgeStatus::Deprecated)
640 {
641 preferred = Some(badge);
642 }
643 }
644 Err(e) => {
645 last_error = Some(e);
646 }
647 }
648 }
649
650 match preferred {
651 Some(badge) => Ok(badge),
652 None => match last_error {
653 Some(e) => Err(AnsError::TransparencyLog(e)),
654 None => Err(AnsError::TransparencyLog(TlogError::InvalidResponse(
655 "no badge records available".to_string(),
656 ))),
657 },
658 }
659 }
660
661 async fn verify_with_refresh(
667 &self,
668 fqdn: &Fqdn,
669 server_cert: &CertIdentity,
670 ) -> VerificationOutcome {
671 if let Some(cache) = &self.cache {
673 cache.invalidate_fqdn(fqdn).await;
674 }
675
676 let records = match self.dns_resolver.lookup_badge(fqdn).await {
678 Ok(DnsLookupResult::Found(records)) => records,
679 Ok(DnsLookupResult::NotFound) => {
680 return VerificationOutcome::NotAnsAgent {
681 fqdn: fqdn.to_string(),
682 };
683 }
684 Err(e) => return VerificationOutcome::DnsError(e),
685 };
686
687 self.verify_against_records_final(&records, fqdn, server_cert)
689 .await
690 }
691
692 async fn verify_against_records_final(
694 &self,
695 records: &[BadgeRecord],
696 fqdn: &Fqdn,
697 server_cert: &CertIdentity,
698 ) -> VerificationOutcome {
699 let mut sorted: Vec<_> = records.iter().collect();
700 sorted.sort_by(|a, b| b.version.cmp(&a.version));
701
702 if let Some(cache) = &self.cache {
704 let versions: Vec<Version> =
705 sorted.iter().filter_map(|r| r.version().cloned()).collect();
706 if !versions.is_empty() {
707 cache.set_version_index(fqdn, versions).await;
708 }
709 }
710
711 let results = self.fetch_badges_parallel(&sorted).await;
713
714 let mut last_mismatch: Option<VerificationOutcome> = None;
715 let mut last_error: Option<AnsError> = None;
716
717 for (record, result) in results {
718 let badge = match result {
719 Ok(b) => b,
720 Err(e) => {
721 last_error = Some(AnsError::TransparencyLog(e));
722 continue;
723 }
724 };
725
726 if let Some(cache) = &self.cache {
728 let version = record
729 .version()
730 .cloned()
731 .or_else(|| badge.agent_version().parse::<Version>().ok());
732 if let Some(v) = &version {
733 cache.insert_for_fqdn_version(fqdn, v, badge.clone()).await;
734 }
735 }
736
737 let outcome = self.verify_against_badge(&badge, server_cert, true);
738
739 match &outcome {
740 VerificationOutcome::Verified { .. } => {
741 return outcome;
742 }
743 VerificationOutcome::FingerprintMismatch { .. } => {
744 last_mismatch = Some(outcome);
745 }
746 _ => return outcome,
747 }
748 }
749
750 if let Some(mismatch) = last_mismatch {
752 return mismatch;
753 }
754 match last_error {
755 Some(e) => self.handle_ans_error(e, fqdn, server_cert).await,
756 None => VerificationOutcome::NotAnsAgent {
757 fqdn: fqdn.to_string(),
758 },
759 }
760 }
761
762 async fn fetch_badges_parallel<'a>(
768 &self,
769 records: &'a [&'a BadgeRecord],
770 ) -> Vec<(&'a BadgeRecord, Result<Badge, TlogError>)> {
771 let futures: Vec<_> = records
774 .iter()
775 .map(|record| {
776 let tlog = &self.tlog_client;
777 let trusted = &self.trusted_ra_domains;
778 async move {
779 if let Err(e) = validate_badge_domain(trusted.as_ref(), &record.url) {
780 (*record, Err(e))
781 } else {
782 let result = tlog.fetch_badge(&record.url).await;
783 (*record, result)
784 }
785 }
786 })
787 .collect();
788
789 join_all(futures).await
790 }
791
792 #[allow(clippy::unused_self)] fn verify_against_badge(
794 &self,
795 badge: &Badge,
796 cert: &CertIdentity,
797 is_server: bool,
798 ) -> VerificationOutcome {
799 let cert_type = if is_server { "server" } else { "identity" };
800 tracing::debug!(cert_type, "Verifying certificate against badge");
801
802 if badge.status.should_reject() {
804 tracing::warn!(
805 status = ?badge.status,
806 "Badge status is not valid for connections"
807 );
808 return VerificationOutcome::InvalidStatus {
809 status: badge.status,
810 badge: badge.clone(),
811 };
812 }
813 tracing::debug!(status = ?badge.status, "Badge status is valid");
814
815 let expected_fp = if is_server {
817 badge.server_cert_fingerprint()
818 } else {
819 badge.identity_cert_fingerprint()
820 };
821
822 tracing::debug!(
823 expected = %expected_fp,
824 actual = %cert.fingerprint,
825 "Comparing certificate fingerprints"
826 );
827
828 if !cert.fingerprint.matches(expected_fp) {
829 tracing::error!(
830 expected = %expected_fp,
831 actual = %cert.fingerprint,
832 "Certificate fingerprint MISMATCH"
833 );
834 return VerificationOutcome::FingerprintMismatch {
835 expected: expected_fp.to_string(),
836 actual: cert.fingerprint.to_string(),
837 badge: badge.clone(),
838 };
839 }
840 tracing::debug!("Fingerprint matches");
841
842 let expected_host = badge.agent_host();
844 let actual_host = cert.fqdn().unwrap_or("");
845
846 tracing::debug!(
847 expected = %expected_host,
848 actual = %actual_host,
849 "Comparing hostnames"
850 );
851
852 if !actual_host.eq_ignore_ascii_case(expected_host) {
853 tracing::error!(
854 expected = %expected_host,
855 actual = %actual_host,
856 "Hostname MISMATCH"
857 );
858 return VerificationOutcome::HostnameMismatch {
859 expected: expected_host.to_string(),
860 actual: actual_host.to_string(),
861 badge: badge.clone(),
862 };
863 }
864
865 tracing::info!(
866 agent = %badge.agent_name(),
867 host = %badge.agent_host(),
868 "Verification SUCCESSFUL"
869 );
870 VerificationOutcome::Verified {
871 badge: badge.clone(),
872 matched_fingerprint: cert.fingerprint.clone(),
873 }
874 }
875
876 async fn handle_dns_error(
877 &self,
878 error: DnsError,
879 fqdn: &Fqdn,
880 cert: &CertIdentity,
881 ) -> VerificationOutcome {
882 match self.failure_policy {
883 FailurePolicy::FailClosed => VerificationOutcome::DnsError(error),
884 FailurePolicy::FailOpenWithCache { max_staleness } => {
885 if let Some(cache) = &self.cache {
886 for cached in cache.get_all_for_fqdn(fqdn).await {
887 if cached.fetched_at.elapsed() < max_staleness {
888 let outcome = self.verify_against_badge(&cached.badge, cert, true);
889 if outcome.is_success() {
890 return outcome;
891 }
892 }
893 }
894 }
895 VerificationOutcome::DnsError(error)
896 }
897 }
898 }
899
900 async fn handle_ans_error(
901 &self,
902 error: AnsError,
903 fqdn: &Fqdn,
904 cert: &CertIdentity,
905 ) -> VerificationOutcome {
906 match self.failure_policy {
907 FailurePolicy::FailClosed => match error {
908 AnsError::TransparencyLog(e) => VerificationOutcome::TlogError(e),
909 AnsError::Dns(e) => VerificationOutcome::DnsError(e),
910 AnsError::Certificate(e) => VerificationOutcome::CertError(e),
911 AnsError::Parse(e) => VerificationOutcome::ParseError(e),
912 AnsError::Verification(_) => VerificationOutcome::NotAnsAgent {
913 fqdn: fqdn.to_string(),
914 },
915 },
916 FailurePolicy::FailOpenWithCache { max_staleness } => {
917 if let Some(cache) = &self.cache {
918 for cached in cache.get_all_for_fqdn(fqdn).await {
919 if cached.fetched_at.elapsed() < max_staleness {
920 let outcome = self.verify_against_badge(&cached.badge, cert, true);
921 if outcome.is_success() {
922 return outcome;
923 }
924 }
925 }
926 }
927 match error {
928 AnsError::TransparencyLog(e) => VerificationOutcome::TlogError(e),
929 AnsError::Dns(e) => VerificationOutcome::DnsError(e),
930 AnsError::Certificate(e) => VerificationOutcome::CertError(e),
931 AnsError::Parse(e) => VerificationOutcome::ParseError(e),
932 AnsError::Verification(_) => VerificationOutcome::NotAnsAgent {
933 fqdn: fqdn.to_string(),
934 },
935 }
936 }
937 }
938 }
939}
940
941#[derive(Default)]
943pub struct ServerVerifierBuilder {
944 dns_resolver: Option<Arc<dyn DnsResolver>>,
945 tlog_client: Option<Arc<dyn TransparencyLogClient>>,
946 cache: Option<Arc<BadgeCache>>,
947 failure_policy: FailurePolicy,
948 dane_policy: DanePolicy,
949 dane_port: Option<u16>,
950 trusted_ra_domains: Option<HashSet<String>>,
951}
952
953impl fmt::Debug for ServerVerifierBuilder {
954 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
955 f.debug_struct("ServerVerifierBuilder")
956 .field("failure_policy", &self.failure_policy)
957 .field("dane_policy", &self.dane_policy)
958 .field("dane_port", &self.dane_port)
959 .field("has_dns_resolver", &self.dns_resolver.is_some())
960 .field("has_tlog_client", &self.tlog_client.is_some())
961 .field("has_cache", &self.cache.is_some())
962 .finish_non_exhaustive()
963 }
964}
965
966impl ServerVerifierBuilder {
967 pub fn dns_resolver(mut self, resolver: Arc<dyn DnsResolver>) -> Self {
969 self.dns_resolver = Some(resolver);
970 self
971 }
972
973 pub fn tlog_client(mut self, client: Arc<dyn TransparencyLogClient>) -> Self {
975 self.tlog_client = Some(client);
976 self
977 }
978
979 pub fn with_cache(mut self) -> Self {
981 self.cache = Some(Arc::new(BadgeCache::with_defaults()));
982 self
983 }
984
985 pub fn with_cache_config(mut self, config: CacheConfig) -> Self {
987 self.cache = Some(Arc::new(BadgeCache::new(config)));
988 self
989 }
990
991 pub fn cache(mut self, cache: Arc<BadgeCache>) -> Self {
993 self.cache = Some(cache);
994 self
995 }
996
997 pub fn failure_policy(mut self, policy: FailurePolicy) -> Self {
999 self.failure_policy = policy;
1000 self
1001 }
1002
1003 pub fn dane_policy(mut self, policy: DanePolicy) -> Self {
1009 self.dane_policy = policy;
1010 self
1011 }
1012
1013 pub fn with_dane_if_present(mut self) -> Self {
1017 self.dane_policy = DanePolicy::ValidateIfPresent;
1018 self
1019 }
1020
1021 pub fn require_dane(mut self) -> Self {
1025 self.dane_policy = DanePolicy::Required;
1026 self
1027 }
1028
1029 pub fn dane_port(mut self, port: u16) -> Self {
1031 self.dane_port = Some(port);
1032 self
1033 }
1034
1035 pub fn trusted_ra_domains(
1044 mut self,
1045 domains: impl IntoIterator<Item = impl Into<String>>,
1046 ) -> Self {
1047 self.trusted_ra_domains = Some(domains.into_iter().map(Into::into).collect());
1048 self
1049 }
1050
1051 pub async fn build(self) -> AnsResult<ServerVerifier> {
1053 let dns_resolver = match self.dns_resolver {
1054 Some(r) => r,
1055 None => Arc::new(
1056 HickoryDnsResolver::new()
1057 .await
1058 .map_err(|e| AnsError::Dns(DnsError::ResolverError(e.to_string())))?,
1059 ),
1060 };
1061
1062 let tlog_client = self
1063 .tlog_client
1064 .unwrap_or_else(|| Arc::new(HttpTransparencyLogClient::new()));
1065
1066 Ok(ServerVerifier {
1067 dns_resolver,
1068 tlog_client,
1069 cache: self.cache,
1070 failure_policy: self.failure_policy,
1071 dane_policy: self.dane_policy,
1072 dane_port: self.dane_port.unwrap_or(443),
1073 trusted_ra_domains: self.trusted_ra_domains,
1074 })
1075 }
1076}
1077
1078pub struct ClientVerifier {
1080 dns_resolver: Arc<dyn DnsResolver>,
1081 tlog_client: Arc<dyn TransparencyLogClient>,
1082 cache: Option<Arc<BadgeCache>>,
1083 failure_policy: FailurePolicy,
1084 trusted_ra_domains: Option<HashSet<String>>,
1086}
1087
1088impl fmt::Debug for ClientVerifier {
1089 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1090 f.debug_struct("ClientVerifier")
1091 .field("failure_policy", &self.failure_policy)
1092 .field("has_cache", &self.cache.is_some())
1093 .field("has_trusted_ra_domains", &self.trusted_ra_domains.is_some())
1094 .finish_non_exhaustive()
1095 }
1096}
1097
1098impl ClientVerifier {
1099 pub fn builder() -> ClientVerifierBuilder {
1101 ClientVerifierBuilder::default()
1102 }
1103
1104 #[allow(clippy::too_many_lines)] pub async fn verify(&self, client_cert: &CertIdentity) -> VerificationOutcome {
1115 tracing::info!("Starting mTLS client verification");
1116 tracing::debug!(
1117 cn = ?client_cert.common_name,
1118 dns_sans = ?client_cert.dns_sans,
1119 uri_sans = ?client_cert.uri_sans,
1120 fingerprint = %client_cert.fingerprint,
1121 "Client certificate details"
1122 );
1123
1124 let Some(fqdn_str) = client_cert.fqdn() else {
1126 tracing::error!("No CN or DNS SAN found in client certificate");
1127 return VerificationOutcome::CertError(CryptoError::NoCommonName);
1128 };
1129
1130 let fqdn = match Fqdn::new(fqdn_str) {
1131 Ok(f) => f,
1132 Err(e) => {
1133 tracing::error!(fqdn = %fqdn_str, error = %e, "Invalid FQDN in certificate");
1134 return VerificationOutcome::ParseError(e);
1135 }
1136 };
1137 tracing::debug!(fqdn = %fqdn, "Extracted FQDN from certificate");
1138
1139 let ans_name = if let Some(n) = client_cert.ans_name() {
1141 tracing::debug!(ans_name = %n, "Found ANS name in URI SAN");
1142 n
1143 } else {
1144 tracing::error!(uri_sans = ?client_cert.uri_sans, "No ANS name (ans://) found in URI SANs");
1145 return VerificationOutcome::CertError(CryptoError::NoUriSan);
1146 };
1147
1148 let version = ans_name.version().clone();
1149 tracing::debug!(version = %version, "Parsed version from ANS name");
1150
1151 if let Some(cache) = &self.cache
1153 && let Some(cached) = cache.get_by_fqdn_version(&fqdn, &version).await
1154 {
1155 tracing::debug!(fqdn = %fqdn, version = %version, "Using cached badge");
1156 let outcome = self.verify_client_against_badge(&cached.badge, client_cert, &ans_name);
1157 if matches!(outcome, VerificationOutcome::FingerprintMismatch { .. }) {
1159 tracing::info!(fqdn = %fqdn, "Fingerprint mismatch on cached badge, refreshing");
1160 return self
1161 .verify_client_with_refresh(&fqdn, &version, client_cert, &ans_name)
1162 .await;
1163 }
1164 return outcome;
1165 }
1166
1167 tracing::debug!(fqdn = %fqdn, version = %version, "Looking up badge for version");
1169 let badge_record = match self
1170 .dns_resolver
1171 .find_badge_for_version(&fqdn, &version)
1172 .await
1173 {
1174 Ok(Some(record)) => {
1175 tracing::debug!(url = %record.url, "Found badge record for version");
1176 record
1177 }
1178 Ok(None) => {
1179 tracing::debug!("No badge for specific version, trying preferred badge");
1180 match self.dns_resolver.find_preferred_badge(&fqdn).await {
1182 Ok(Some(record)) => {
1183 tracing::debug!(url = %record.url, version = ?record.version, "Using preferred badge");
1184 record
1185 }
1186 Ok(None) => {
1187 tracing::warn!(fqdn = %fqdn, "No badge record found - not an ANS agent");
1188 return VerificationOutcome::NotAnsAgent {
1189 fqdn: fqdn.to_string(),
1190 };
1191 }
1192 Err(e) => {
1193 tracing::error!(error = %e, "DNS lookup failed");
1194 return self
1195 .handle_dns_error(e, &fqdn, &version, client_cert, &ans_name)
1196 .await;
1197 }
1198 }
1199 }
1200 Err(e) => {
1201 tracing::error!(error = %e, "DNS lookup failed");
1202 return self
1203 .handle_dns_error(e, &fqdn, &version, client_cert, &ans_name)
1204 .await;
1205 }
1206 };
1207
1208 if let Err(e) = validate_badge_domain(self.trusted_ra_domains.as_ref(), &badge_record.url) {
1210 return self
1211 .handle_tlog_error(e, &fqdn, &version, client_cert, &ans_name)
1212 .await;
1213 }
1214
1215 tracing::debug!(url = %badge_record.url, "Fetching badge from transparency log");
1217 let badge = match self.tlog_client.fetch_badge(&badge_record.url).await {
1218 Ok(b) => {
1219 tracing::debug!(
1220 status = ?b.status,
1221 agent_host = %b.agent_host(),
1222 ans_name = %b.agent_name(),
1223 "Fetched badge successfully"
1224 );
1225 b
1226 }
1227 Err(e) => {
1228 tracing::error!(url = %badge_record.url, error = %e, "Failed to fetch badge");
1229 return self
1230 .handle_tlog_error(e, &fqdn, &version, client_cert, &ans_name)
1231 .await;
1232 }
1233 };
1234
1235 if let Some(cache) = &self.cache {
1237 cache
1238 .insert_for_fqdn_version(&fqdn, &version, badge.clone())
1239 .await;
1240 tracing::debug!(fqdn = %fqdn, version = %version, "Cached badge");
1241 }
1242
1243 let outcome = self.verify_client_against_badge(&badge, client_cert, &ans_name);
1244 if matches!(outcome, VerificationOutcome::FingerprintMismatch { .. }) {
1246 tracing::info!(fqdn = %fqdn, "Fingerprint mismatch, attempting refresh");
1247 return self
1248 .verify_client_with_refresh(&fqdn, &version, client_cert, &ans_name)
1249 .await;
1250 }
1251 outcome
1252 }
1253
1254 #[allow(clippy::unused_self)] fn verify_client_against_badge(
1256 &self,
1257 badge: &Badge,
1258 cert: &CertIdentity,
1259 ans_name: &AnsName,
1260 ) -> VerificationOutcome {
1261 tracing::debug!("Verifying client certificate against badge");
1262
1263 if badge.status.should_reject() {
1265 tracing::warn!(status = ?badge.status, "Badge status is not valid for connections");
1266 return VerificationOutcome::InvalidStatus {
1267 status: badge.status,
1268 badge: badge.clone(),
1269 };
1270 }
1271 tracing::debug!(status = ?badge.status, "Badge status is valid");
1272
1273 let expected_fp = badge.identity_cert_fingerprint();
1275 tracing::debug!(
1276 expected = %expected_fp,
1277 actual = %cert.fingerprint,
1278 "Comparing identity certificate fingerprints"
1279 );
1280
1281 if !cert.fingerprint.matches(expected_fp) {
1282 tracing::error!(
1283 expected = %expected_fp,
1284 actual = %cert.fingerprint,
1285 "Identity certificate fingerprint MISMATCH"
1286 );
1287 return VerificationOutcome::FingerprintMismatch {
1288 expected: expected_fp.to_string(),
1289 actual: cert.fingerprint.to_string(),
1290 badge: badge.clone(),
1291 };
1292 }
1293 tracing::debug!("Identity fingerprint matches");
1294
1295 let expected_host = badge.agent_host();
1297 let actual_host = cert.fqdn().unwrap_or("");
1298 tracing::debug!(
1299 expected = %expected_host,
1300 actual = %actual_host,
1301 "Comparing hostnames"
1302 );
1303
1304 if !actual_host.eq_ignore_ascii_case(expected_host) {
1305 tracing::error!(
1306 expected = %expected_host,
1307 actual = %actual_host,
1308 "Hostname MISMATCH"
1309 );
1310 return VerificationOutcome::HostnameMismatch {
1311 expected: expected_host.to_string(),
1312 actual: actual_host.to_string(),
1313 badge: badge.clone(),
1314 };
1315 }
1316 tracing::debug!("Hostname matches");
1317
1318 let expected_ans_name = badge.agent_name();
1320 tracing::debug!(
1321 expected = %expected_ans_name,
1322 actual = %ans_name,
1323 "Comparing ANS names"
1324 );
1325
1326 if ans_name.to_string() != expected_ans_name {
1327 tracing::error!(
1328 expected = %expected_ans_name,
1329 actual = %ans_name,
1330 "ANS name MISMATCH"
1331 );
1332 return VerificationOutcome::AnsNameMismatch {
1333 expected: expected_ans_name.to_string(),
1334 actual: ans_name.to_string(),
1335 badge: badge.clone(),
1336 };
1337 }
1338
1339 tracing::info!(
1340 agent = %badge.agent_name(),
1341 host = %badge.agent_host(),
1342 "Client verification SUCCESSFUL"
1343 );
1344 VerificationOutcome::Verified {
1345 badge: badge.clone(),
1346 matched_fingerprint: cert.fingerprint.clone(),
1347 }
1348 }
1349
1350 async fn verify_client_with_refresh(
1356 &self,
1357 fqdn: &Fqdn,
1358 version: &Version,
1359 client_cert: &CertIdentity,
1360 ans_name: &AnsName,
1361 ) -> VerificationOutcome {
1362 if let Some(cache) = &self.cache {
1364 cache
1365 .invalidate(&CacheKey::fqdn_version(fqdn, version))
1366 .await;
1367 }
1368
1369 let badge_record = match self
1371 .dns_resolver
1372 .find_badge_for_version(fqdn, version)
1373 .await
1374 {
1375 Ok(Some(record)) => record,
1376 Ok(None) => match self.dns_resolver.find_preferred_badge(fqdn).await {
1377 Ok(Some(record)) => record,
1378 Ok(None) => {
1379 return VerificationOutcome::NotAnsAgent {
1380 fqdn: fqdn.to_string(),
1381 };
1382 }
1383 Err(e) => return VerificationOutcome::DnsError(e),
1384 },
1385 Err(e) => return VerificationOutcome::DnsError(e),
1386 };
1387
1388 if let Err(e) = validate_badge_domain(self.trusted_ra_domains.as_ref(), &badge_record.url) {
1390 return VerificationOutcome::TlogError(e);
1391 }
1392
1393 let badge = match self.tlog_client.fetch_badge(&badge_record.url).await {
1395 Ok(b) => b,
1396 Err(e) => return VerificationOutcome::TlogError(e),
1397 };
1398
1399 if let Some(cache) = &self.cache {
1401 cache
1402 .insert_for_fqdn_version(fqdn, version, badge.clone())
1403 .await;
1404 }
1405
1406 self.verify_client_against_badge(&badge, client_cert, ans_name)
1408 }
1409
1410 async fn handle_dns_error(
1411 &self,
1412 error: DnsError,
1413 fqdn: &Fqdn,
1414 version: &Version,
1415 cert: &CertIdentity,
1416 ans_name: &AnsName,
1417 ) -> VerificationOutcome {
1418 match self.failure_policy {
1419 FailurePolicy::FailClosed => VerificationOutcome::DnsError(error),
1420 FailurePolicy::FailOpenWithCache { max_staleness } => {
1421 if let Some(cache) = &self.cache
1422 && let Some(cached) = cache.get_by_fqdn_version(fqdn, version).await
1423 && cached.fetched_at.elapsed() < max_staleness
1424 {
1425 return self.verify_client_against_badge(&cached.badge, cert, ans_name);
1426 }
1427 VerificationOutcome::DnsError(error)
1428 }
1429 }
1430 }
1431
1432 async fn handle_tlog_error(
1433 &self,
1434 error: TlogError,
1435 fqdn: &Fqdn,
1436 version: &Version,
1437 cert: &CertIdentity,
1438 ans_name: &AnsName,
1439 ) -> VerificationOutcome {
1440 match self.failure_policy {
1441 FailurePolicy::FailClosed => VerificationOutcome::TlogError(error),
1442 FailurePolicy::FailOpenWithCache { max_staleness } => {
1443 if let Some(cache) = &self.cache
1444 && let Some(cached) = cache.get_by_fqdn_version(fqdn, version).await
1445 && cached.fetched_at.elapsed() < max_staleness
1446 {
1447 return self.verify_client_against_badge(&cached.badge, cert, ans_name);
1448 }
1449 VerificationOutcome::TlogError(error)
1450 }
1451 }
1452 }
1453}
1454
1455#[derive(Default)]
1457pub struct ClientVerifierBuilder {
1458 dns_resolver: Option<Arc<dyn DnsResolver>>,
1459 tlog_client: Option<Arc<dyn TransparencyLogClient>>,
1460 cache: Option<Arc<BadgeCache>>,
1461 failure_policy: FailurePolicy,
1462 trusted_ra_domains: Option<HashSet<String>>,
1463}
1464
1465impl fmt::Debug for ClientVerifierBuilder {
1466 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1467 f.debug_struct("ClientVerifierBuilder")
1468 .field("failure_policy", &self.failure_policy)
1469 .field("has_dns_resolver", &self.dns_resolver.is_some())
1470 .field("has_tlog_client", &self.tlog_client.is_some())
1471 .field("has_cache", &self.cache.is_some())
1472 .finish_non_exhaustive()
1473 }
1474}
1475
1476impl ClientVerifierBuilder {
1477 pub fn dns_resolver(mut self, resolver: Arc<dyn DnsResolver>) -> Self {
1479 self.dns_resolver = Some(resolver);
1480 self
1481 }
1482
1483 pub fn tlog_client(mut self, client: Arc<dyn TransparencyLogClient>) -> Self {
1485 self.tlog_client = Some(client);
1486 self
1487 }
1488
1489 pub fn with_cache(mut self) -> Self {
1491 self.cache = Some(Arc::new(BadgeCache::with_defaults()));
1492 self
1493 }
1494
1495 pub fn with_cache_config(mut self, config: CacheConfig) -> Self {
1497 self.cache = Some(Arc::new(BadgeCache::new(config)));
1498 self
1499 }
1500
1501 pub fn cache(mut self, cache: Arc<BadgeCache>) -> Self {
1503 self.cache = Some(cache);
1504 self
1505 }
1506
1507 pub fn failure_policy(mut self, policy: FailurePolicy) -> Self {
1509 self.failure_policy = policy;
1510 self
1511 }
1512
1513 pub fn trusted_ra_domains(
1522 mut self,
1523 domains: impl IntoIterator<Item = impl Into<String>>,
1524 ) -> Self {
1525 self.trusted_ra_domains = Some(domains.into_iter().map(Into::into).collect());
1526 self
1527 }
1528
1529 pub async fn build(self) -> AnsResult<ClientVerifier> {
1531 let dns_resolver = match self.dns_resolver {
1532 Some(r) => r,
1533 None => Arc::new(
1534 HickoryDnsResolver::new()
1535 .await
1536 .map_err(|e| AnsError::Dns(DnsError::ResolverError(e.to_string())))?,
1537 ),
1538 };
1539
1540 let tlog_client = self
1541 .tlog_client
1542 .unwrap_or_else(|| Arc::new(HttpTransparencyLogClient::new()));
1543
1544 Ok(ClientVerifier {
1545 dns_resolver,
1546 tlog_client,
1547 cache: self.cache,
1548 failure_policy: self.failure_policy,
1549 trusted_ra_domains: self.trusted_ra_domains,
1550 })
1551 }
1552}
1553
1554pub struct AnsVerifier {
1556 server_verifier: ServerVerifier,
1557 client_verifier: ClientVerifier,
1558 #[cfg(feature = "rustls")]
1559 private_ca_pem: Option<Vec<u8>>,
1560}
1561
1562impl fmt::Debug for AnsVerifier {
1563 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1564 f.debug_struct("AnsVerifier")
1565 .field("server_verifier", &self.server_verifier)
1566 .field("client_verifier", &self.client_verifier)
1567 .finish_non_exhaustive()
1568 }
1569}
1570
1571impl AnsVerifier {
1572 pub async fn new() -> AnsResult<Self> {
1574 Self::builder().build().await
1575 }
1576
1577 pub fn builder() -> AnsVerifierBuilder {
1579 AnsVerifierBuilder::default()
1580 }
1581
1582 pub async fn verify_server(
1584 &self,
1585 fqdn: impl AsRef<str>,
1586 server_cert: &CertIdentity,
1587 ) -> VerificationOutcome {
1588 let fqdn = match Fqdn::new(fqdn.as_ref()) {
1589 Ok(f) => f,
1590 Err(e) => return VerificationOutcome::ParseError(e),
1591 };
1592 self.server_verifier.verify(&fqdn, server_cert).await
1593 }
1594
1595 pub async fn verify_client(&self, client_cert: &CertIdentity) -> VerificationOutcome {
1597 self.client_verifier.verify(client_cert).await
1598 }
1599
1600 pub async fn prefetch(&self, fqdn: impl AsRef<str>) -> AnsResult<Badge> {
1602 let fqdn = Fqdn::new(fqdn.as_ref())?;
1603 self.server_verifier.prefetch(&fqdn).await
1604 }
1605
1606 #[cfg(feature = "rustls")]
1612 pub fn client_cert_verifier(&self) -> AnsResult<crate::AnsClientCertVerifier> {
1613 let pem = self.private_ca_pem.as_ref().ok_or_else(|| {
1614 AnsError::Verification(VerificationError::Configuration(
1615 "private_ca_pem is required for client_cert_verifier".into(),
1616 ))
1617 })?;
1618 crate::AnsClientCertVerifier::from_pem(pem).map_err(|e| {
1619 AnsError::Verification(VerificationError::Configuration(format!(
1620 "Failed to build client cert verifier: {e}"
1621 )))
1622 })
1623 }
1624
1625 #[cfg(feature = "rustls")]
1630 pub fn client_cert_verifier_optional(&self) -> AnsResult<crate::AnsClientCertVerifier> {
1631 let pem = self.private_ca_pem.as_ref().ok_or_else(|| {
1632 AnsError::Verification(VerificationError::Configuration(
1633 "private_ca_pem is required for client_cert_verifier_optional".into(),
1634 ))
1635 })?;
1636 crate::AnsClientCertVerifier::from_pem_optional(pem).map_err(|e| {
1637 AnsError::Verification(VerificationError::Configuration(format!(
1638 "Failed to build optional client cert verifier: {e}"
1639 )))
1640 })
1641 }
1642
1643 #[cfg(feature = "rustls")]
1648 pub fn server_cert_verifier(
1649 &self,
1650 fingerprint: &CertFingerprint,
1651 ) -> AnsResult<crate::AnsServerCertVerifier> {
1652 crate::AnsServerCertVerifier::new(fingerprint.clone()).map_err(|e| {
1653 AnsError::Verification(VerificationError::Configuration(format!(
1654 "Failed to build server cert verifier: {e}"
1655 )))
1656 })
1657 }
1658}
1659
1660#[derive(Default)]
1662pub struct AnsVerifierBuilder {
1663 dns_resolver: Option<Arc<dyn DnsResolver>>,
1664 dns_config: Option<DnsResolverConfig>,
1665 dns_nameservers: Option<Vec<std::net::Ipv4Addr>>,
1666 tlog_client: Option<Arc<dyn TransparencyLogClient>>,
1667 cache_config: Option<CacheConfig>,
1668 failure_policy: FailurePolicy,
1669 dane_policy: DanePolicy,
1670 dane_port: Option<u16>,
1671 trusted_ra_domains: Option<HashSet<String>>,
1672 #[cfg(feature = "rustls")]
1673 private_ca_pem: Option<Vec<u8>>,
1674}
1675
1676impl fmt::Debug for AnsVerifierBuilder {
1677 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1678 f.debug_struct("AnsVerifierBuilder")
1679 .field("dns_config", &self.dns_config)
1680 .field("failure_policy", &self.failure_policy)
1681 .field("dane_policy", &self.dane_policy)
1682 .field("dane_port", &self.dane_port)
1683 .field("has_dns_resolver", &self.dns_resolver.is_some())
1684 .field("has_tlog_client", &self.tlog_client.is_some())
1685 .field("has_cache_config", &self.cache_config.is_some())
1686 .finish_non_exhaustive()
1687 }
1688}
1689
1690impl AnsVerifierBuilder {
1691 pub fn dns_resolver(mut self, resolver: Arc<dyn DnsResolver>) -> Self {
1693 self.dns_resolver = Some(resolver);
1694 self
1695 }
1696
1697 pub fn dns_preset(mut self, preset: DnsResolverConfig) -> Self {
1712 self.dns_config = Some(preset);
1713 self
1714 }
1715
1716 pub fn dns_cloudflare(self) -> Self {
1718 self.dns_preset(DnsResolverConfig::Cloudflare)
1719 }
1720
1721 pub fn dns_cloudflare_tls(self) -> Self {
1723 self.dns_preset(DnsResolverConfig::CloudflareTls)
1724 }
1725
1726 pub fn dns_google(self) -> Self {
1728 self.dns_preset(DnsResolverConfig::Google)
1729 }
1730
1731 pub fn dns_google_tls(self) -> Self {
1733 self.dns_preset(DnsResolverConfig::GoogleTls)
1734 }
1735
1736 pub fn dns_quad9(self) -> Self {
1738 self.dns_preset(DnsResolverConfig::Quad9)
1739 }
1740
1741 pub fn dns_nameservers(mut self, nameservers: &[std::net::Ipv4Addr]) -> Self {
1760 self.dns_nameservers = Some(nameservers.to_vec());
1761 self
1762 }
1763
1764 pub fn tlog_client(mut self, client: Arc<dyn TransparencyLogClient>) -> Self {
1766 self.tlog_client = Some(client);
1767 self
1768 }
1769
1770 pub fn with_caching(mut self) -> Self {
1772 self.cache_config = Some(CacheConfig::default());
1773 self
1774 }
1775
1776 pub fn with_cache_config(mut self, config: CacheConfig) -> Self {
1778 self.cache_config = Some(config);
1779 self
1780 }
1781
1782 pub fn failure_policy(mut self, policy: FailurePolicy) -> Self {
1784 self.failure_policy = policy;
1785 self
1786 }
1787
1788 pub fn dane_policy(mut self, policy: DanePolicy) -> Self {
1794 self.dane_policy = policy;
1795 self
1796 }
1797
1798 pub fn with_dane_if_present(mut self) -> Self {
1800 self.dane_policy = DanePolicy::ValidateIfPresent;
1801 self
1802 }
1803
1804 pub fn require_dane(mut self) -> Self {
1806 self.dane_policy = DanePolicy::Required;
1807 self
1808 }
1809
1810 pub fn dane_port(mut self, port: u16) -> Self {
1812 self.dane_port = Some(port);
1813 self
1814 }
1815
1816 pub fn trusted_ra_domains(
1821 mut self,
1822 domains: impl IntoIterator<Item = impl Into<String>>,
1823 ) -> Self {
1824 self.trusted_ra_domains = Some(domains.into_iter().map(Into::into).collect());
1825 self
1826 }
1827
1828 #[cfg(feature = "rustls")]
1836 pub fn private_ca_pem(mut self, pem: impl Into<Vec<u8>>) -> Self {
1837 self.private_ca_pem = Some(pem.into());
1838 self
1839 }
1840
1841 pub async fn build(self) -> AnsResult<AnsVerifier> {
1843 let dns_resolver: Arc<dyn DnsResolver> = if let Some(r) = self.dns_resolver {
1845 r
1846 } else if let Some(nameservers) = self.dns_nameservers {
1847 Arc::new(
1848 HickoryDnsResolver::with_nameservers(&nameservers)
1849 .await
1850 .map_err(|e| AnsError::Dns(DnsError::ResolverError(e.to_string())))?,
1851 )
1852 } else if let Some(preset) = self.dns_config {
1853 Arc::new(
1854 HickoryDnsResolver::with_preset(preset)
1855 .await
1856 .map_err(|e| AnsError::Dns(DnsError::ResolverError(e.to_string())))?,
1857 )
1858 } else {
1859 Arc::new(
1860 HickoryDnsResolver::new()
1861 .await
1862 .map_err(|e| AnsError::Dns(DnsError::ResolverError(e.to_string())))?,
1863 )
1864 };
1865
1866 let tlog_client: Arc<dyn TransparencyLogClient> = self
1867 .tlog_client
1868 .unwrap_or_else(|| Arc::new(HttpTransparencyLogClient::new()));
1869
1870 let cache = self.cache_config.map(|c| Arc::new(BadgeCache::new(c)));
1871 let dane_port = self.dane_port.unwrap_or(443);
1872
1873 let server_verifier = ServerVerifier {
1874 dns_resolver: dns_resolver.clone(),
1875 tlog_client: tlog_client.clone(),
1876 cache: cache.clone(),
1877 failure_policy: self.failure_policy,
1878 dane_policy: self.dane_policy,
1879 dane_port,
1880 trusted_ra_domains: self.trusted_ra_domains.clone(),
1881 };
1882
1883 let client_verifier = ClientVerifier {
1884 dns_resolver,
1885 tlog_client,
1886 cache,
1887 failure_policy: self.failure_policy,
1888 trusted_ra_domains: self.trusted_ra_domains,
1889 };
1890
1891 Ok(AnsVerifier {
1892 server_verifier,
1893 client_verifier,
1894 #[cfg(feature = "rustls")]
1895 private_ca_pem: self.private_ca_pem,
1896 })
1897 }
1898}
1899
1900#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
1901#[cfg(test)]
1902mod tests {
1903 use super::*;
1904 use crate::dns::MockDnsResolver;
1905 use crate::tlog::MockTransparencyLogClient;
1906 use chrono::Utc;
1907 use uuid::Uuid;
1908
1909 const fn _assert_send_sync<T: Send + Sync>() {}
1912 const _: () = _assert_send_sync::<ServerVerifier>();
1913 const _: () = _assert_send_sync::<ClientVerifier>();
1914 const _: () = _assert_send_sync::<AnsVerifier>();
1915 const _: () = _assert_send_sync::<BadgeCache>();
1916
1917 fn create_test_badge(host: &str, version: &str, server_fp: &str, identity_fp: &str) -> Badge {
1918 serde_json::from_value(serde_json::json!({
1919 "status": "ACTIVE",
1920 "schemaVersion": "V1",
1921 "payload": {
1922 "logId": Uuid::new_v4().to_string(),
1923 "producer": {
1924 "event": {
1925 "ansId": Uuid::new_v4().to_string(),
1926 "ansName": format!("ans://{version}.{host}"),
1927 "eventType": "AGENT_REGISTERED",
1928 "agent": { "host": host, "name": "Test Agent", "version": version },
1929 "attestations": {
1930 "domainValidation": "ACME-DNS-01",
1931 "identityCert": { "fingerprint": identity_fp, "type": "X509-OV-CLIENT" },
1932 "serverCert": { "fingerprint": server_fp, "type": "X509-DV-SERVER" }
1933 },
1934 "expiresAt": (Utc::now() + chrono::Duration::days(365)).to_rfc3339(),
1935 "issuedAt": Utc::now().to_rfc3339(),
1936 "raId": "test-ra",
1937 "timestamp": Utc::now().to_rfc3339()
1938 },
1939 "keyId": "test-key",
1940 "signature": "test-sig"
1941 }
1942 }
1943 })).expect("test badge JSON should be valid")
1944 }
1945
1946 fn create_test_cert_identity(cn: &str, fingerprint: &str) -> CertIdentity {
1947 CertIdentity {
1948 common_name: Some(cn.to_string()),
1949 dns_sans: vec![cn.to_string()],
1950 uri_sans: vec![],
1951 fingerprint: CertFingerprint::parse(fingerprint).unwrap(),
1952 }
1953 }
1954
1955 #[tokio::test]
1956 async fn test_server_verification_success() {
1957 let host = "test.example.com";
1958 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
1959
1960 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
1961 let badge_url = "https://tlog.example.com/v1/agents/test-id";
1962
1963 let dns_record = BadgeRecord {
1964 format_version: "ans-badge1".to_string(),
1965 version: Some(Version::new(1, 0, 0)),
1966 url: badge_url.to_string(),
1967 };
1968
1969 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
1970
1971 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
1972
1973 let verifier = ServerVerifier {
1974 dns_resolver,
1975 tlog_client,
1976 cache: None,
1977 failure_policy: FailurePolicy::FailClosed,
1978 dane_policy: DanePolicy::Disabled,
1979 dane_port: 443,
1980 trusted_ra_domains: None,
1981 };
1982
1983 let cert = create_test_cert_identity(host, fingerprint);
1984 let fqdn = Fqdn::new(host).unwrap();
1985
1986 let outcome = verifier.verify(&fqdn, &cert).await;
1987 assert!(outcome.is_success());
1988 }
1989
1990 #[tokio::test]
1991 async fn test_server_verification_not_ans_agent() {
1992 let dns_resolver = Arc::new(MockDnsResolver::new());
1993 let tlog_client = Arc::new(MockTransparencyLogClient::new());
1994
1995 let verifier = ServerVerifier {
1996 dns_resolver,
1997 tlog_client,
1998 cache: None,
1999 failure_policy: FailurePolicy::FailClosed,
2000 dane_policy: DanePolicy::Disabled,
2001 dane_port: 443,
2002 trusted_ra_domains: None,
2003 };
2004
2005 let cert = create_test_cert_identity(
2006 "unknown.example.com",
2007 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2008 );
2009 let fqdn = Fqdn::new("unknown.example.com").unwrap();
2010
2011 let outcome = verifier.verify(&fqdn, &cert).await;
2012 assert!(outcome.is_not_ans_agent());
2013 }
2014
2015 #[tokio::test]
2016 async fn test_server_verification_fingerprint_mismatch() {
2017 let host = "test.example.com";
2018 let badge_fingerprint =
2019 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2020 let cert_fingerprint =
2021 "SHA256:0000000000000000000000000000000000000000000000000000000000000000";
2022
2023 let badge = create_test_badge(host, "v1.0.0", badge_fingerprint, "SHA256:aaa");
2024 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2025
2026 let dns_record = BadgeRecord {
2027 format_version: "ans-badge1".to_string(),
2028 version: Some(Version::new(1, 0, 0)),
2029 url: badge_url.to_string(),
2030 };
2031
2032 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2033
2034 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
2035
2036 let verifier = ServerVerifier {
2037 dns_resolver,
2038 tlog_client,
2039 cache: None,
2040 failure_policy: FailurePolicy::FailClosed,
2041 dane_policy: DanePolicy::Disabled,
2042 dane_port: 443,
2043 trusted_ra_domains: None,
2044 };
2045
2046 let cert = create_test_cert_identity(host, cert_fingerprint);
2047 let fqdn = Fqdn::new(host).unwrap();
2048
2049 let outcome = verifier.verify(&fqdn, &cert).await;
2050 assert!(matches!(
2051 outcome,
2052 VerificationOutcome::FingerprintMismatch { .. }
2053 ));
2054 }
2055
2056 #[tokio::test]
2057 async fn test_server_verification_invalid_status() {
2058 let host = "test.example.com";
2059 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2060
2061 let mut badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
2062 badge.status = BadgeStatus::Revoked;
2063
2064 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2065
2066 let dns_record = BadgeRecord {
2067 format_version: "ans-badge1".to_string(),
2068 version: Some(Version::new(1, 0, 0)),
2069 url: badge_url.to_string(),
2070 };
2071
2072 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2073
2074 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
2075
2076 let verifier = ServerVerifier {
2077 dns_resolver,
2078 tlog_client,
2079 cache: None,
2080 failure_policy: FailurePolicy::FailClosed,
2081 dane_policy: DanePolicy::Disabled,
2082 dane_port: 443,
2083 trusted_ra_domains: None,
2084 };
2085
2086 let cert = create_test_cert_identity(host, fingerprint);
2087 let fqdn = Fqdn::new(host).unwrap();
2088
2089 let outcome = verifier.verify(&fqdn, &cert).await;
2090 assert!(matches!(
2091 outcome,
2092 VerificationOutcome::InvalidStatus {
2093 status: BadgeStatus::Revoked,
2094 ..
2095 }
2096 ));
2097 }
2098
2099 #[tokio::test]
2100 async fn test_verification_outcome_is_success() {
2101 let badge = create_test_badge(
2102 "test.example.com",
2103 "v1.0.0",
2104 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2105 "SHA256:aaa",
2106 );
2107
2108 let outcome = VerificationOutcome::Verified {
2109 badge,
2110 matched_fingerprint: CertFingerprint::parse(
2111 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2112 )
2113 .unwrap(),
2114 };
2115
2116 assert!(outcome.is_success());
2117 assert!(!outcome.is_not_ans_agent());
2118 }
2119
2120 #[tokio::test]
2121 async fn test_verification_with_cache() {
2122 let host = "test.example.com";
2123 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2124
2125 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
2126 let cache = Arc::new(BadgeCache::with_defaults());
2127 let fqdn = Fqdn::new(host).unwrap();
2128
2129 cache
2131 .insert_for_fqdn_version(&fqdn, &Version::new(1, 0, 0), badge)
2132 .await;
2133
2134 let dns_resolver = Arc::new(MockDnsResolver::new());
2136 let tlog_client = Arc::new(MockTransparencyLogClient::new());
2137
2138 let verifier = ServerVerifier {
2139 dns_resolver,
2140 tlog_client,
2141 cache: Some(cache),
2142 failure_policy: FailurePolicy::FailClosed,
2143 dane_policy: DanePolicy::Disabled,
2144 dane_port: 443,
2145 trusted_ra_domains: None,
2146 };
2147
2148 let cert = create_test_cert_identity(host, fingerprint);
2149
2150 let outcome = verifier.verify(&fqdn, &cert).await;
2151 assert!(outcome.is_success());
2152 }
2153
2154 #[test]
2155 fn test_cert_identity_from_components() {
2156 let fingerprint = CertFingerprint::parse(
2157 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2158 )
2159 .unwrap();
2160
2161 let identity = CertIdentity::new(
2162 Some("test.example.com".to_string()),
2163 vec!["test.example.com".to_string()],
2164 vec!["ans://v1.0.0.test.example.com".to_string()],
2165 fingerprint,
2166 );
2167
2168 assert_eq!(identity.fqdn(), Some("test.example.com"));
2169 assert!(identity.ans_name().is_some());
2170 assert_eq!(identity.version(), Some(Version::new(1, 0, 0)));
2171 }
2172
2173 #[test]
2174 fn test_cert_identity_from_fingerprint_and_cn() {
2175 let fingerprint = CertFingerprint::parse(
2176 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2177 )
2178 .unwrap();
2179
2180 let identity =
2181 CertIdentity::from_fingerprint_and_cn(fingerprint, "test.example.com".to_string());
2182
2183 assert_eq!(identity.fqdn(), Some("test.example.com"));
2184 assert!(identity.ans_name().is_none()); }
2186
2187 fn create_mtls_cert_identity(host: &str, version: &str, fingerprint: &str) -> CertIdentity {
2192 CertIdentity {
2193 common_name: Some(host.to_string()),
2194 dns_sans: vec![host.to_string()],
2195 uri_sans: vec![format!("ans://{}.{}", version, host)],
2196 fingerprint: CertFingerprint::parse(fingerprint).unwrap(),
2197 }
2198 }
2199
2200 #[tokio::test]
2201 async fn test_client_verification_success() {
2202 let host = "test.example.com";
2203 let version = "v1.0.0";
2204 let identity_fp = "SHA256:aebdc9da0c20d6d5e4999a773839095ed050a9d7252bf212056fddc0c38f3496";
2205 let server_fp = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2206
2207 let badge = create_test_badge(host, version, server_fp, identity_fp);
2208 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2209
2210 let dns_record = BadgeRecord {
2211 format_version: "ans-badge1".to_string(),
2212 version: Some(Version::new(1, 0, 0)),
2213 url: badge_url.to_string(),
2214 };
2215
2216 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2217 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
2218
2219 let verifier = ClientVerifier {
2220 dns_resolver,
2221 tlog_client,
2222 cache: None,
2223 failure_policy: FailurePolicy::FailClosed,
2224 trusted_ra_domains: None,
2225 };
2226
2227 let cert = create_mtls_cert_identity(host, version, identity_fp);
2228 let outcome = verifier.verify(&cert).await;
2229
2230 assert!(outcome.is_success(), "Expected success, got: {:?}", outcome);
2231 }
2232
2233 #[tokio::test]
2234 async fn test_client_verification_no_fqdn() {
2235 let dns_resolver = Arc::new(MockDnsResolver::new());
2236 let tlog_client = Arc::new(MockTransparencyLogClient::new());
2237
2238 let verifier = ClientVerifier {
2239 dns_resolver,
2240 tlog_client,
2241 cache: None,
2242 failure_policy: FailurePolicy::FailClosed,
2243 trusted_ra_domains: None,
2244 };
2245
2246 let cert = CertIdentity {
2248 common_name: None,
2249 dns_sans: vec![],
2250 uri_sans: vec!["ans://v1.0.0.test.example.com".to_string()],
2251 fingerprint: CertFingerprint::parse(
2252 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2253 )
2254 .unwrap(),
2255 };
2256
2257 let outcome = verifier.verify(&cert).await;
2258 assert!(matches!(outcome, VerificationOutcome::CertError(_)));
2259 }
2260
2261 #[tokio::test]
2262 async fn test_client_verification_no_ans_name() {
2263 let dns_resolver = Arc::new(MockDnsResolver::new());
2264 let tlog_client = Arc::new(MockTransparencyLogClient::new());
2265
2266 let verifier = ClientVerifier {
2267 dns_resolver,
2268 tlog_client,
2269 cache: None,
2270 failure_policy: FailurePolicy::FailClosed,
2271 trusted_ra_domains: None,
2272 };
2273
2274 let cert = CertIdentity {
2276 common_name: Some("test.example.com".to_string()),
2277 dns_sans: vec!["test.example.com".to_string()],
2278 uri_sans: vec![],
2279 fingerprint: CertFingerprint::parse(
2280 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2281 )
2282 .unwrap(),
2283 };
2284
2285 let outcome = verifier.verify(&cert).await;
2286 assert!(matches!(outcome, VerificationOutcome::CertError(_)));
2287 }
2288
2289 #[tokio::test]
2290 async fn test_client_verification_fingerprint_mismatch() {
2291 let host = "test.example.com";
2292 let version = "v1.0.0";
2293 let badge_identity_fp =
2294 "SHA256:aebdc9da0c20d6d5e4999a773839095ed050a9d7252bf212056fddc0c38f3496";
2295 let cert_identity_fp =
2296 "SHA256:0000000000000000000000000000000000000000000000000000000000000000";
2297
2298 let badge = create_test_badge(host, version, "SHA256:server", badge_identity_fp);
2299 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2300
2301 let dns_record = BadgeRecord {
2302 format_version: "ans-badge1".to_string(),
2303 version: Some(Version::new(1, 0, 0)),
2304 url: badge_url.to_string(),
2305 };
2306
2307 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2308 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
2309
2310 let verifier = ClientVerifier {
2311 dns_resolver,
2312 tlog_client,
2313 cache: None,
2314 failure_policy: FailurePolicy::FailClosed,
2315 trusted_ra_domains: None,
2316 };
2317
2318 let cert = create_mtls_cert_identity(host, version, cert_identity_fp);
2319 let outcome = verifier.verify(&cert).await;
2320
2321 assert!(matches!(
2322 outcome,
2323 VerificationOutcome::FingerprintMismatch { .. }
2324 ));
2325 }
2326
2327 #[tokio::test]
2328 async fn test_client_verification_ans_name_mismatch() {
2329 let host = "test.example.com";
2330 let badge_version = "v1.0.0";
2331 let cert_version = "v2.0.0";
2332 let identity_fp = "SHA256:aebdc9da0c20d6d5e4999a773839095ed050a9d7252bf212056fddc0c38f3496";
2333
2334 let badge = create_test_badge(host, badge_version, "SHA256:server", identity_fp);
2336 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2337
2338 let dns_record = BadgeRecord {
2339 format_version: "ans-badge1".to_string(),
2340 version: Some(Version::new(2, 0, 0)),
2341 url: badge_url.to_string(),
2342 };
2343
2344 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2345 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
2346
2347 let verifier = ClientVerifier {
2348 dns_resolver,
2349 tlog_client,
2350 cache: None,
2351 failure_policy: FailurePolicy::FailClosed,
2352 trusted_ra_domains: None,
2353 };
2354
2355 let cert = create_mtls_cert_identity(host, cert_version, identity_fp);
2356 let outcome = verifier.verify(&cert).await;
2357
2358 assert!(matches!(
2359 outcome,
2360 VerificationOutcome::AnsNameMismatch { .. }
2361 ));
2362 }
2363
2364 #[test]
2369 fn test_verification_outcome_badge() {
2370 let badge = create_test_badge(
2371 "test.example.com",
2372 "v1.0.0",
2373 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2374 "SHA256:aaa",
2375 );
2376
2377 let outcome = VerificationOutcome::Verified {
2379 badge: badge.clone(),
2380 matched_fingerprint: CertFingerprint::parse(
2381 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2382 )
2383 .unwrap(),
2384 };
2385 assert!(outcome.badge().is_some());
2386
2387 let outcome = VerificationOutcome::InvalidStatus {
2389 status: BadgeStatus::Revoked,
2390 badge: badge.clone(),
2391 };
2392 assert!(outcome.badge().is_some());
2393
2394 let outcome = VerificationOutcome::FingerprintMismatch {
2396 expected: "SHA256:a".to_string(),
2397 actual: "SHA256:b".to_string(),
2398 badge: badge.clone(),
2399 };
2400 assert!(outcome.badge().is_some());
2401
2402 let outcome = VerificationOutcome::HostnameMismatch {
2404 expected: "a.com".to_string(),
2405 actual: "b.com".to_string(),
2406 badge: badge.clone(),
2407 };
2408 assert!(outcome.badge().is_some());
2409
2410 let outcome = VerificationOutcome::AnsNameMismatch {
2412 expected: "ans://v1.0.0.a.com".to_string(),
2413 actual: "ans://v2.0.0.a.com".to_string(),
2414 badge,
2415 };
2416 assert!(outcome.badge().is_some());
2417
2418 let outcome = VerificationOutcome::NotAnsAgent {
2420 fqdn: "test.com".to_string(),
2421 };
2422 assert!(outcome.badge().is_none());
2423
2424 let outcome = VerificationOutcome::DnsError(DnsError::NotFound {
2426 fqdn: "test.com".to_string(),
2427 });
2428 assert!(outcome.badge().is_none());
2429 }
2430
2431 #[test]
2432 fn test_verification_outcome_into_result() {
2433 let badge = create_test_badge(
2434 "test.example.com",
2435 "v1.0.0",
2436 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2437 "SHA256:aaa",
2438 );
2439
2440 let outcome = VerificationOutcome::Verified {
2442 badge: badge.clone(),
2443 matched_fingerprint: CertFingerprint::parse(
2444 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2445 )
2446 .unwrap(),
2447 };
2448 assert!(outcome.into_result().is_ok());
2449
2450 let outcome = VerificationOutcome::NotAnsAgent {
2452 fqdn: "test.com".to_string(),
2453 };
2454 assert!(outcome.into_result().is_err());
2455
2456 let outcome = VerificationOutcome::InvalidStatus {
2458 status: BadgeStatus::Revoked,
2459 badge: badge.clone(),
2460 };
2461 assert!(outcome.into_result().is_err());
2462
2463 let outcome = VerificationOutcome::FingerprintMismatch {
2465 expected: "a".to_string(),
2466 actual: "b".to_string(),
2467 badge: badge.clone(),
2468 };
2469 assert!(outcome.into_result().is_err());
2470
2471 let outcome = VerificationOutcome::HostnameMismatch {
2473 expected: "a.com".to_string(),
2474 actual: "b.com".to_string(),
2475 badge: badge.clone(),
2476 };
2477 assert!(outcome.into_result().is_err());
2478
2479 let outcome = VerificationOutcome::AnsNameMismatch {
2481 expected: "a".to_string(),
2482 actual: "b".to_string(),
2483 badge,
2484 };
2485 assert!(outcome.into_result().is_err());
2486
2487 let outcome = VerificationOutcome::DnsError(DnsError::NotFound {
2489 fqdn: "test.com".to_string(),
2490 });
2491 assert!(outcome.into_result().is_err());
2492
2493 let outcome = VerificationOutcome::TlogError(TlogError::ServiceUnavailable);
2495 assert!(outcome.into_result().is_err());
2496
2497 let outcome = VerificationOutcome::DaneError(DaneError::FingerprintMismatch);
2499 assert!(outcome.into_result().is_err());
2500 }
2501
2502 #[tokio::test]
2507 async fn test_server_verification_hostname_mismatch() {
2508 let badge_host = "badge.example.com";
2509 let cert_host = "different.example.com";
2510 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2511
2512 let badge = create_test_badge(badge_host, "v1.0.0", fingerprint, "SHA256:aaa");
2513 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2514
2515 let dns_record = BadgeRecord {
2516 format_version: "ans-badge1".to_string(),
2517 version: Some(Version::new(1, 0, 0)),
2518 url: badge_url.to_string(),
2519 };
2520
2521 let dns_resolver =
2523 Arc::new(MockDnsResolver::new().with_records(cert_host, vec![dns_record]));
2524 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
2525
2526 let verifier = ServerVerifier {
2527 dns_resolver,
2528 tlog_client,
2529 cache: None,
2530 failure_policy: FailurePolicy::FailClosed,
2531 dane_policy: DanePolicy::Disabled,
2532 dane_port: 443,
2533 trusted_ra_domains: None,
2534 };
2535
2536 let cert = create_test_cert_identity(cert_host, fingerprint);
2537 let fqdn = Fqdn::new(cert_host).unwrap();
2538
2539 let outcome = verifier.verify(&fqdn, &cert).await;
2540 assert!(
2541 matches!(outcome, VerificationOutcome::HostnameMismatch { .. }),
2542 "Expected HostnameMismatch, got: {:?}",
2543 outcome
2544 );
2545 }
2546
2547 #[tokio::test]
2552 async fn test_server_verifier_prefetch_success() {
2553 let host = "test.example.com";
2554 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2555
2556 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
2557 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2558
2559 let dns_record = BadgeRecord {
2560 format_version: "ans-badge1".to_string(),
2561 version: Some(Version::new(1, 0, 0)),
2562 url: badge_url.to_string(),
2563 };
2564
2565 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2566 let tlog_client =
2567 Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge.clone()));
2568
2569 let verifier = ServerVerifier {
2570 dns_resolver,
2571 tlog_client,
2572 cache: Some(Arc::new(BadgeCache::with_defaults())),
2573 failure_policy: FailurePolicy::FailClosed,
2574 dane_policy: DanePolicy::Disabled,
2575 dane_port: 443,
2576 trusted_ra_domains: None,
2577 };
2578
2579 let fqdn = Fqdn::new(host).unwrap();
2580 let result = verifier.prefetch(&fqdn).await;
2581
2582 assert!(result.is_ok());
2583 assert_eq!(result.unwrap().agent_host(), host);
2584 }
2585
2586 #[tokio::test]
2587 async fn test_server_verifier_prefetch_not_found() {
2588 let dns_resolver = Arc::new(MockDnsResolver::new());
2589 let tlog_client = Arc::new(MockTransparencyLogClient::new());
2590
2591 let verifier = ServerVerifier {
2592 dns_resolver,
2593 tlog_client,
2594 cache: None,
2595 failure_policy: FailurePolicy::FailClosed,
2596 dane_policy: DanePolicy::Disabled,
2597 dane_port: 443,
2598 trusted_ra_domains: None,
2599 };
2600
2601 let fqdn = Fqdn::new("unknown.example.com").unwrap();
2602 let result = verifier.prefetch(&fqdn).await;
2603
2604 assert!(result.is_err());
2605 assert!(matches!(result.unwrap_err(), AnsError::Dns(_)));
2606 }
2607
2608 #[tokio::test]
2613 async fn test_failure_policy_fail_open_with_cache_no_cache() {
2614 let dns_resolver = Arc::new(MockDnsResolver::new().with_error(
2615 "test.example.com",
2616 DnsError::LookupFailed {
2617 fqdn: "test.example.com".to_string(),
2618 reason: "timeout".to_string(),
2619 },
2620 ));
2621 let tlog_client = Arc::new(MockTransparencyLogClient::new());
2622
2623 let verifier = ServerVerifier {
2624 dns_resolver,
2625 tlog_client,
2626 cache: Some(Arc::new(BadgeCache::with_defaults())),
2627 failure_policy: FailurePolicy::FailOpenWithCache {
2628 max_staleness: Duration::from_secs(600),
2629 },
2630 dane_policy: DanePolicy::Disabled,
2631 dane_port: 443,
2632 trusted_ra_domains: None,
2633 };
2634
2635 let cert = create_test_cert_identity(
2636 "test.example.com",
2637 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2638 );
2639 let fqdn = Fqdn::new("test.example.com").unwrap();
2640
2641 let outcome = verifier.verify(&fqdn, &cert).await;
2642 assert!(matches!(outcome, VerificationOutcome::DnsError(_)));
2644 }
2645
2646 #[tokio::test]
2647 async fn test_failure_policy_fail_open_with_cache_uses_cache() {
2648 let host = "test.example.com";
2649 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2650
2651 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
2652 let cache = Arc::new(BadgeCache::with_defaults());
2653 let fqdn = Fqdn::new(host).unwrap();
2654
2655 cache
2657 .insert_for_fqdn_version(&fqdn, &Version::new(1, 0, 0), badge)
2658 .await;
2659
2660 let dns_resolver = Arc::new(MockDnsResolver::new().with_error(
2661 host,
2662 DnsError::LookupFailed {
2663 fqdn: host.to_string(),
2664 reason: "timeout".to_string(),
2665 },
2666 ));
2667 let tlog_client = Arc::new(MockTransparencyLogClient::new());
2668
2669 let verifier = ServerVerifier {
2670 dns_resolver,
2671 tlog_client,
2672 cache: Some(cache),
2673 failure_policy: FailurePolicy::FailOpenWithCache {
2674 max_staleness: Duration::from_secs(600),
2675 },
2676 dane_policy: DanePolicy::Disabled,
2677 dane_port: 443,
2678 trusted_ra_domains: None,
2679 };
2680
2681 let cert = create_test_cert_identity(host, fingerprint);
2682
2683 let outcome = verifier.verify(&fqdn, &cert).await;
2684 assert!(
2686 outcome.is_success(),
2687 "Expected success with cache, got: {:?}",
2688 outcome
2689 );
2690 }
2691
2692 #[test]
2693 fn test_cert_identity_from_der_server_cert() {
2694 use rcgen::{CertificateParams, DnType, ExtendedKeyUsagePurpose, KeyPair, SanType};
2695
2696 let key_pair = KeyPair::generate().unwrap();
2698 let mut params = CertificateParams::default();
2699 params
2700 .distinguished_name
2701 .push(DnType::CommonName, "test.agent.local");
2702 params.subject_alt_names.push(SanType::DnsName(
2703 "test.agent.local".to_string().try_into().unwrap(),
2704 ));
2705 params.subject_alt_names.push(SanType::URI(
2706 "ans://v1.0.0.test.agent.local".try_into().unwrap(),
2707 ));
2708 params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth];
2709
2710 let cert = params.self_signed(&key_pair).unwrap();
2711 let der = cert.der();
2712
2713 let identity = CertIdentity::from_der(der).expect("should parse DER certificate");
2714
2715 assert_eq!(
2717 identity.common_name.as_deref(),
2718 Some("test.agent.local"),
2719 "CN should be test.agent.local"
2720 );
2721
2722 assert!(
2724 identity.dns_sans.contains(&"test.agent.local".to_string()),
2725 "DNS SANs should contain test.agent.local, got: {:?}",
2726 identity.dns_sans
2727 );
2728
2729 assert!(
2731 identity
2732 .uri_sans
2733 .contains(&"ans://v1.0.0.test.agent.local".to_string()),
2734 "URI SANs should contain ans://v1.0.0.test.agent.local, got: {:?}",
2735 identity.uri_sans
2736 );
2737
2738 let expected_fp = CertFingerprint::from_der(der);
2740 assert_eq!(
2741 identity.fingerprint, expected_fp,
2742 "Fingerprint should match computed fingerprint from same DER"
2743 );
2744
2745 assert_eq!(identity.fqdn(), Some("test.agent.local"));
2747 let ans_name = identity.ans_name().expect("should have ANS name");
2748 assert_eq!(ans_name.fqdn().as_str(), "test.agent.local");
2749 assert_eq!(identity.version(), Some(Version::new(1, 0, 0)));
2750 }
2751
2752 #[test]
2753 fn test_cert_identity_from_der_client_cert() {
2754 use rcgen::{CertificateParams, DnType, ExtendedKeyUsagePurpose, KeyPair, SanType};
2755
2756 let key_pair = KeyPair::generate().unwrap();
2758 let mut params = CertificateParams::default();
2759 params
2760 .distinguished_name
2761 .push(DnType::CommonName, "test.agent.local");
2762 params.subject_alt_names.push(SanType::DnsName(
2763 "test.agent.local".to_string().try_into().unwrap(),
2764 ));
2765 params.subject_alt_names.push(SanType::URI(
2766 "ans://v1.0.0.test.agent.local".try_into().unwrap(),
2767 ));
2768 params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth];
2769
2770 let cert = params.self_signed(&key_pair).unwrap();
2771 let der = cert.der();
2772
2773 let identity = CertIdentity::from_der(der).expect("should parse DER certificate");
2774
2775 assert_eq!(identity.common_name.as_deref(), Some("test.agent.local"));
2776 assert!(identity.dns_sans.contains(&"test.agent.local".to_string()));
2777 assert!(
2778 identity
2779 .uri_sans
2780 .contains(&"ans://v1.0.0.test.agent.local".to_string())
2781 );
2782
2783 let expected_fp = CertFingerprint::from_der(der);
2784 assert_eq!(identity.fingerprint, expected_fp);
2785 }
2786
2787 #[test]
2788 fn test_cert_identity_from_der_invalid_bytes() {
2789 let result = CertIdentity::from_der(b"not a certificate");
2790 assert!(result.is_err(), "Should fail on invalid DER bytes");
2791 }
2792
2793 #[tokio::test]
2794 async fn test_server_verifier_builder_dane_policy() {
2795 let dns = Arc::new(MockDnsResolver::new());
2796 let tlog = Arc::new(MockTransparencyLogClient::new());
2797
2798 let verifier = ServerVerifier::builder()
2800 .dns_resolver(dns.clone())
2801 .tlog_client(tlog.clone())
2802 .with_dane_if_present()
2803 .build()
2804 .await
2805 .unwrap();
2806 assert_eq!(verifier.dane_policy, DanePolicy::ValidateIfPresent);
2807
2808 let verifier = ServerVerifier::builder()
2810 .dns_resolver(dns.clone())
2811 .tlog_client(tlog.clone())
2812 .require_dane()
2813 .build()
2814 .await
2815 .unwrap();
2816 assert_eq!(verifier.dane_policy, DanePolicy::Required);
2817
2818 let verifier = ServerVerifier::builder()
2820 .dns_resolver(dns.clone())
2821 .tlog_client(tlog.clone())
2822 .dane_policy(DanePolicy::Disabled)
2823 .build()
2824 .await
2825 .unwrap();
2826 assert_eq!(verifier.dane_policy, DanePolicy::Disabled);
2827 }
2828
2829 #[tokio::test]
2830 async fn test_server_verifier_builder_dane_port() {
2831 let dns = Arc::new(MockDnsResolver::new());
2832 let tlog = Arc::new(MockTransparencyLogClient::new());
2833
2834 let verifier = ServerVerifier::builder()
2836 .dns_resolver(dns.clone())
2837 .tlog_client(tlog.clone())
2838 .build()
2839 .await
2840 .unwrap();
2841 assert_eq!(verifier.dane_port, 443);
2842
2843 let verifier = ServerVerifier::builder()
2845 .dns_resolver(dns.clone())
2846 .tlog_client(tlog.clone())
2847 .dane_port(8443)
2848 .build()
2849 .await
2850 .unwrap();
2851 assert_eq!(verifier.dane_port, 8443);
2852 }
2853
2854 #[tokio::test]
2855 async fn test_server_verifier_builder_failure_policy() {
2856 let dns = Arc::new(MockDnsResolver::new());
2857 let tlog = Arc::new(MockTransparencyLogClient::new());
2858
2859 let verifier = ServerVerifier::builder()
2860 .dns_resolver(dns)
2861 .tlog_client(tlog)
2862 .failure_policy(FailurePolicy::FailClosed)
2863 .build()
2864 .await
2865 .unwrap();
2866 assert!(matches!(verifier.failure_policy, FailurePolicy::FailClosed));
2867 }
2868
2869 #[tokio::test]
2874 async fn test_server_verification_refresh_on_mismatch_succeeds() {
2875 let host = "test.example.com";
2876 let old_fp = "SHA256:0000000000000000000000000000000000000000000000000000000000000000";
2877 let new_fp = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2878
2879 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2882 let updated_badge = create_test_badge(host, "v1.0.0", new_fp, "SHA256:aaa");
2883
2884 let dns_record = BadgeRecord {
2885 format_version: "ans-badge1".to_string(),
2886 version: Some(Version::new(1, 0, 0)),
2887 url: badge_url.to_string(),
2888 };
2889
2890 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2891 let tlog_client =
2893 Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, updated_badge));
2894
2895 let cache = Arc::new(BadgeCache::with_defaults());
2896 let fqdn = Fqdn::new(host).unwrap();
2897
2898 let stale_badge = create_test_badge(host, "v1.0.0", old_fp, "SHA256:aaa");
2900 cache
2901 .insert_for_fqdn_version(&fqdn, &Version::new(1, 0, 0), stale_badge)
2902 .await;
2903
2904 let verifier = ServerVerifier {
2905 dns_resolver,
2906 tlog_client,
2907 cache: Some(cache),
2908 failure_policy: FailurePolicy::FailClosed,
2909 dane_policy: DanePolicy::Disabled,
2910 dane_port: 443,
2911 trusted_ra_domains: None,
2912 };
2913
2914 let cert = create_test_cert_identity(host, new_fp);
2916 let outcome = verifier.verify(&fqdn, &cert).await;
2917 assert!(
2918 outcome.is_success(),
2919 "Expected success after refresh, got: {:?}",
2920 outcome
2921 );
2922 }
2923
2924 #[tokio::test]
2925 async fn test_server_verification_refresh_on_mismatch_still_fails() {
2926 let host = "test.example.com";
2927 let badge_fp = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2928 let cert_fp = "SHA256:0000000000000000000000000000000000000000000000000000000000000000";
2929
2930 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2931 let badge = create_test_badge(host, "v1.0.0", badge_fp, "SHA256:aaa");
2933
2934 let dns_record = BadgeRecord {
2935 format_version: "ans-badge1".to_string(),
2936 version: Some(Version::new(1, 0, 0)),
2937 url: badge_url.to_string(),
2938 };
2939
2940 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2941 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
2942
2943 let verifier = ServerVerifier {
2944 dns_resolver,
2945 tlog_client,
2946 cache: None,
2947 failure_policy: FailurePolicy::FailClosed,
2948 dane_policy: DanePolicy::Disabled,
2949 dane_port: 443,
2950 trusted_ra_domains: None,
2951 };
2952
2953 let cert = create_test_cert_identity(host, cert_fp);
2954 let fqdn = Fqdn::new(host).unwrap();
2955
2956 let outcome = verifier.verify(&fqdn, &cert).await;
2957 assert!(
2958 matches!(outcome, VerificationOutcome::FingerprintMismatch { .. }),
2959 "Expected FingerprintMismatch after refresh still fails, got: {:?}",
2960 outcome
2961 );
2962 }
2963
2964 #[tokio::test]
2965 async fn test_client_verification_refresh_on_mismatch_succeeds() {
2966 let host = "test.example.com";
2967 let version = "v1.0.0";
2968 let old_identity_fp =
2969 "SHA256:0000000000000000000000000000000000000000000000000000000000000000";
2970 let new_identity_fp =
2971 "SHA256:aebdc9da0c20d6d5e4999a773839095ed050a9d7252bf212056fddc0c38f3496";
2972 let server_fp = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2973
2974 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2975 let updated_badge = create_test_badge(host, version, server_fp, new_identity_fp);
2977
2978 let dns_record = BadgeRecord {
2979 format_version: "ans-badge1".to_string(),
2980 version: Some(Version::new(1, 0, 0)),
2981 url: badge_url.to_string(),
2982 };
2983
2984 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2985 let tlog_client =
2986 Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, updated_badge));
2987
2988 let cache = Arc::new(BadgeCache::with_defaults());
2989 let fqdn = Fqdn::new(host).unwrap();
2990 let ver = Version::new(1, 0, 0);
2991
2992 let stale_badge = create_test_badge(host, version, server_fp, old_identity_fp);
2994 cache
2995 .insert_for_fqdn_version(&fqdn, &ver, stale_badge)
2996 .await;
2997
2998 let verifier = ClientVerifier {
2999 dns_resolver,
3000 tlog_client,
3001 cache: Some(cache),
3002 failure_policy: FailurePolicy::FailClosed,
3003 trusted_ra_domains: None,
3004 };
3005
3006 let cert = create_mtls_cert_identity(host, version, new_identity_fp);
3008 let outcome = verifier.verify(&cert).await;
3009 assert!(
3010 outcome.is_success(),
3011 "Expected success after client refresh, got: {:?}",
3012 outcome
3013 );
3014 }
3015
3016 #[test]
3021 fn test_validate_badge_domain_unit_allows_when_none() {
3022 assert!(validate_badge_domain(None, "https://tlog.example.com/v1/agents/test").is_ok());
3023 }
3024
3025 #[test]
3026 fn test_validate_badge_domain_unit_allows_trusted() {
3027 let trusted: HashSet<String> = ["tlog.example.com".to_string()].into();
3028 assert!(
3029 validate_badge_domain(Some(&trusted), "https://tlog.example.com/v1/agents/test")
3030 .is_ok()
3031 );
3032 }
3033
3034 #[test]
3035 fn test_validate_badge_domain_unit_rejects_untrusted() {
3036 let trusted: HashSet<String> = ["tlog.example.com".to_string()].into();
3037 let err = validate_badge_domain(Some(&trusted), "https://evil.attacker.com/v1/agents/test")
3038 .unwrap_err();
3039 assert!(
3040 matches!(err, TlogError::UntrustedDomain { domain, .. } if domain == "evil.attacker.com")
3041 );
3042 }
3043
3044 #[test]
3045 fn test_validate_badge_domain_unit_multiple_trusted() {
3046 let trusted: HashSet<String> = [
3047 "tlog1.example.com".to_string(),
3048 "tlog2.example.com".to_string(),
3049 ]
3050 .into();
3051 assert!(validate_badge_domain(Some(&trusted), "https://tlog1.example.com/badge").is_ok());
3052 assert!(validate_badge_domain(Some(&trusted), "https://tlog2.example.com/badge").is_ok());
3053 assert!(validate_badge_domain(Some(&trusted), "https://tlog3.example.com/badge").is_err());
3054 }
3055
3056 #[tokio::test]
3057 async fn test_trusted_ra_none_allows_all() {
3058 let host = "test.example.com";
3059 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3060 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
3061 let badge_url = "https://any-domain.example.com/v1/agents/test-id";
3062
3063 let dns_record = BadgeRecord {
3064 format_version: "ans-badge1".to_string(),
3065 version: Some(Version::new(1, 0, 0)),
3066 url: badge_url.to_string(),
3067 };
3068 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3069 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3070
3071 let verifier = ServerVerifier {
3072 dns_resolver,
3073 tlog_client,
3074 cache: None,
3075 failure_policy: FailurePolicy::FailClosed,
3076 dane_policy: DanePolicy::Disabled,
3077 dane_port: 443,
3078 trusted_ra_domains: None,
3079 };
3080
3081 let cert = create_test_cert_identity(host, fingerprint);
3082 let fqdn = Fqdn::new(host).unwrap();
3083 let outcome = verifier.verify(&fqdn, &cert).await;
3084 assert!(outcome.is_success(), "None should allow all domains");
3085 }
3086
3087 #[tokio::test]
3088 async fn test_trusted_ra_allows_trusted_domain() {
3089 let host = "test.example.com";
3090 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3091 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
3092 let badge_url = "https://tlog.example.com/v1/agents/test-id";
3093
3094 let dns_record = BadgeRecord {
3095 format_version: "ans-badge1".to_string(),
3096 version: Some(Version::new(1, 0, 0)),
3097 url: badge_url.to_string(),
3098 };
3099 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3100 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3101
3102 let verifier = ServerVerifier {
3103 dns_resolver,
3104 tlog_client,
3105 cache: None,
3106 failure_policy: FailurePolicy::FailClosed,
3107 dane_policy: DanePolicy::Disabled,
3108 dane_port: 443,
3109 trusted_ra_domains: Some(["tlog.example.com".to_string()].into()),
3110 };
3111
3112 let cert = create_test_cert_identity(host, fingerprint);
3113 let fqdn = Fqdn::new(host).unwrap();
3114 let outcome = verifier.verify(&fqdn, &cert).await;
3115 assert!(outcome.is_success(), "Trusted domain should succeed");
3116 }
3117
3118 #[tokio::test]
3119 async fn test_trusted_ra_rejects_untrusted_domain() {
3120 let host = "test.example.com";
3121 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3122 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
3123 let badge_url = "https://evil.attacker.com/v1/agents/test-id";
3124
3125 let dns_record = BadgeRecord {
3126 format_version: "ans-badge1".to_string(),
3127 version: Some(Version::new(1, 0, 0)),
3128 url: badge_url.to_string(),
3129 };
3130 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3131 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3132
3133 let verifier = ServerVerifier {
3134 dns_resolver,
3135 tlog_client,
3136 cache: None,
3137 failure_policy: FailurePolicy::FailClosed,
3138 dane_policy: DanePolicy::Disabled,
3139 dane_port: 443,
3140 trusted_ra_domains: Some(["tlog.example.com".to_string()].into()),
3141 };
3142
3143 let cert = create_test_cert_identity(host, fingerprint);
3144 let fqdn = Fqdn::new(host).unwrap();
3145 let outcome = verifier.verify(&fqdn, &cert).await;
3146 assert!(
3147 matches!(
3148 outcome,
3149 VerificationOutcome::TlogError(TlogError::UntrustedDomain { .. })
3150 ),
3151 "Untrusted domain should be rejected, got: {:?}",
3152 outcome
3153 );
3154 }
3155
3156 #[tokio::test]
3157 async fn test_trusted_ra_client_rejects_untrusted() {
3158 let host = "test.example.com";
3159 let version = "v1.0.0";
3160 let identity_fp = "SHA256:aebdc9da0c20d6d5e4999a773839095ed050a9d7252bf212056fddc0c38f3496";
3161 let server_fp = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3162 let badge = create_test_badge(host, version, server_fp, identity_fp);
3163 let badge_url = "https://evil.attacker.com/v1/agents/test-id";
3164
3165 let dns_record = BadgeRecord {
3166 format_version: "ans-badge1".to_string(),
3167 version: Some(Version::new(1, 0, 0)),
3168 url: badge_url.to_string(),
3169 };
3170 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3171 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3172
3173 let verifier = ClientVerifier {
3174 dns_resolver,
3175 tlog_client,
3176 cache: None,
3177 failure_policy: FailurePolicy::FailClosed,
3178 trusted_ra_domains: Some(["tlog.example.com".to_string()].into()),
3179 };
3180
3181 let cert = create_mtls_cert_identity(host, version, identity_fp);
3182 let outcome = verifier.verify(&cert).await;
3183 assert!(
3184 matches!(
3185 outcome,
3186 VerificationOutcome::TlogError(TlogError::UntrustedDomain { .. })
3187 ),
3188 "Client verifier should reject untrusted domain, got: {:?}",
3189 outcome
3190 );
3191 }
3192
3193 #[tokio::test]
3194 async fn test_trusted_ra_builder_propagation() {
3195 let dns_resolver = Arc::new(MockDnsResolver::new());
3196 let tlog_client = Arc::new(MockTransparencyLogClient::new());
3197
3198 let verifier = ServerVerifier::builder()
3199 .dns_resolver(dns_resolver as Arc<dyn DnsResolver>)
3200 .tlog_client(tlog_client as Arc<dyn TransparencyLogClient>)
3201 .trusted_ra_domains(["tlog.example.com", "tlog2.example.com"])
3202 .build()
3203 .await
3204 .unwrap();
3205
3206 let trusted = verifier.trusted_ra_domains.as_ref().unwrap();
3208 assert!(trusted.contains("tlog.example.com"));
3209 assert!(trusted.contains("tlog2.example.com"));
3210 assert_eq!(trusted.len(), 2);
3211 }
3212
3213 #[test]
3218 fn test_outcome_into_result_cert_error() {
3219 let outcome =
3220 VerificationOutcome::CertError(CryptoError::ParseFailed("bad cert".to_string()));
3221 let err = outcome.into_result().unwrap_err();
3222 assert!(matches!(err, AnsError::Certificate(_)));
3223 }
3224
3225 #[test]
3226 fn test_outcome_into_result_parse_error() {
3227 let outcome = VerificationOutcome::ParseError(ans_types::ParseError::InvalidFqdn(
3228 "bad fqdn".to_string(),
3229 ));
3230 let err = outcome.into_result().unwrap_err();
3231 assert!(matches!(err, AnsError::Parse(_)));
3232 }
3233
3234 #[test]
3235 fn test_outcome_into_result_dane_error() {
3236 let outcome = VerificationOutcome::DaneError(DaneError::FingerprintMismatch);
3237 let err = outcome.into_result().unwrap_err();
3238 assert!(matches!(
3239 err,
3240 AnsError::Verification(VerificationError::DaneVerificationFailed(_))
3241 ));
3242 }
3243
3244 #[test]
3245 fn test_outcome_into_result_dns_error() {
3246 let outcome = VerificationOutcome::DnsError(DnsError::Timeout {
3247 fqdn: "test.example.com".to_string(),
3248 });
3249 let err = outcome.into_result().unwrap_err();
3250 assert!(matches!(err, AnsError::Dns(DnsError::Timeout { .. })));
3251 }
3252
3253 #[test]
3254 fn test_outcome_into_result_tlog_error() {
3255 let outcome = VerificationOutcome::TlogError(TlogError::ServiceUnavailable);
3256 let err = outcome.into_result().unwrap_err();
3257 assert!(matches!(
3258 err,
3259 AnsError::TransparencyLog(TlogError::ServiceUnavailable)
3260 ));
3261 }
3262
3263 #[tokio::test]
3268 async fn test_builder_dns_cloudflare() {
3269 let dns = Arc::new(MockDnsResolver::new());
3270 let tlog = Arc::new(MockTransparencyLogClient::new());
3271
3272 let verifier = AnsVerifier::builder()
3274 .dns_resolver(dns as Arc<dyn DnsResolver>)
3275 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3276 .dns_cloudflare() .build()
3278 .await
3279 .unwrap();
3280
3281 let dbg = format!("{verifier:?}");
3283 assert!(dbg.contains("AnsVerifier"));
3284 }
3285
3286 #[tokio::test]
3287 async fn test_builder_dns_nameservers() {
3288 let tlog = Arc::new(MockTransparencyLogClient::new());
3289
3290 let verifier = AnsVerifier::builder()
3291 .dns_nameservers(&[std::net::Ipv4Addr::new(1, 1, 1, 1)])
3292 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3293 .build()
3294 .await
3295 .unwrap();
3296
3297 let dbg = format!("{verifier:?}");
3298 assert!(dbg.contains("AnsVerifier"));
3299 }
3300
3301 #[tokio::test]
3302 async fn test_builder_dns_preset_path() {
3303 let tlog = Arc::new(MockTransparencyLogClient::new());
3304
3305 let verifier = AnsVerifier::builder()
3306 .dns_preset(DnsResolverConfig::Cloudflare)
3307 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3308 .build()
3309 .await
3310 .unwrap();
3311
3312 let dbg = format!("{verifier:?}");
3313 assert!(dbg.contains("AnsVerifier"));
3314 }
3315
3316 #[cfg(feature = "rustls")]
3321 #[tokio::test]
3322 async fn test_client_cert_verifier_without_pem() {
3323 let _ = rustls::crypto::ring::default_provider().install_default();
3324 let dns = Arc::new(MockDnsResolver::new());
3325 let tlog = Arc::new(MockTransparencyLogClient::new());
3326
3327 let verifier = AnsVerifier::builder()
3328 .dns_resolver(dns as Arc<dyn DnsResolver>)
3329 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3330 .build()
3331 .await
3332 .unwrap();
3333
3334 let result = verifier.client_cert_verifier();
3335 assert!(result.is_err());
3336 }
3337
3338 #[cfg(feature = "rustls")]
3339 #[tokio::test]
3340 async fn test_client_cert_verifier_with_pem() {
3341 let _ = rustls::crypto::ring::default_provider().install_default();
3342 let ca = rcgen::generate_simple_self_signed(vec!["ANS Test CA".to_string()]).unwrap();
3343 let ca_pem = ca.cert.pem();
3344
3345 let dns = Arc::new(MockDnsResolver::new());
3346 let tlog = Arc::new(MockTransparencyLogClient::new());
3347
3348 let verifier = AnsVerifier::builder()
3349 .dns_resolver(dns as Arc<dyn DnsResolver>)
3350 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3351 .private_ca_pem(ca_pem.as_bytes().to_vec())
3352 .build()
3353 .await
3354 .unwrap();
3355
3356 let cv = verifier.client_cert_verifier().unwrap();
3357 assert!(cv.requires_client_cert());
3358 }
3359
3360 #[cfg(feature = "rustls")]
3361 #[tokio::test]
3362 async fn test_client_cert_verifier_optional_with_pem() {
3363 let _ = rustls::crypto::ring::default_provider().install_default();
3364 let ca = rcgen::generate_simple_self_signed(vec!["ANS Test CA".to_string()]).unwrap();
3365 let ca_pem = ca.cert.pem();
3366
3367 let dns = Arc::new(MockDnsResolver::new());
3368 let tlog = Arc::new(MockTransparencyLogClient::new());
3369
3370 let verifier = AnsVerifier::builder()
3371 .dns_resolver(dns as Arc<dyn DnsResolver>)
3372 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3373 .private_ca_pem(ca_pem.as_bytes().to_vec())
3374 .build()
3375 .await
3376 .unwrap();
3377
3378 let cv = verifier.client_cert_verifier_optional().unwrap();
3379 assert!(!cv.requires_client_cert());
3380 }
3381
3382 #[cfg(feature = "rustls")]
3383 #[tokio::test]
3384 async fn test_server_cert_verifier() {
3385 let _ = rustls::crypto::ring::default_provider().install_default();
3386 let dns = Arc::new(MockDnsResolver::new());
3387 let tlog = Arc::new(MockTransparencyLogClient::new());
3388
3389 let verifier = AnsVerifier::builder()
3390 .dns_resolver(dns as Arc<dyn DnsResolver>)
3391 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3392 .build()
3393 .await
3394 .unwrap();
3395
3396 let fp = CertFingerprint::parse(
3397 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
3398 )
3399 .unwrap();
3400 let sv = verifier.server_cert_verifier(&fp).unwrap();
3401 assert_eq!(sv.expected_fingerprint(), &fp);
3402 }
3403
3404 #[tokio::test]
3409 async fn test_builder_with_caching() {
3410 let dns = Arc::new(MockDnsResolver::new());
3411 let tlog = Arc::new(MockTransparencyLogClient::new());
3412
3413 let verifier = AnsVerifier::builder()
3414 .dns_resolver(dns as Arc<dyn DnsResolver>)
3415 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3416 .with_caching()
3417 .build()
3418 .await
3419 .unwrap();
3420
3421 assert!(format!("{verifier:?}").contains("has_cache"));
3423 }
3424
3425 #[tokio::test]
3426 async fn test_builder_with_cache_config() {
3427 let dns = Arc::new(MockDnsResolver::new());
3428 let tlog = Arc::new(MockTransparencyLogClient::new());
3429
3430 let verifier = AnsVerifier::builder()
3431 .dns_resolver(dns as Arc<dyn DnsResolver>)
3432 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3433 .with_cache_config(CacheConfig::default())
3434 .build()
3435 .await
3436 .unwrap();
3437
3438 assert!(format!("{verifier:?}").contains("AnsVerifier"));
3439 }
3440
3441 #[tokio::test]
3442 async fn test_builder_with_dane_if_present() {
3443 let dns = Arc::new(MockDnsResolver::new());
3444 let tlog = Arc::new(MockTransparencyLogClient::new());
3445
3446 let verifier = ServerVerifier::builder()
3447 .dns_resolver(dns as Arc<dyn DnsResolver>)
3448 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3449 .with_dane_if_present()
3450 .build()
3451 .await
3452 .unwrap();
3453
3454 assert_eq!(verifier.dane_policy, DanePolicy::ValidateIfPresent);
3455 }
3456
3457 #[tokio::test]
3458 async fn test_builder_require_dane() {
3459 let dns = Arc::new(MockDnsResolver::new());
3460 let tlog = Arc::new(MockTransparencyLogClient::new());
3461
3462 let verifier = ServerVerifier::builder()
3463 .dns_resolver(dns as Arc<dyn DnsResolver>)
3464 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3465 .require_dane()
3466 .build()
3467 .await
3468 .unwrap();
3469
3470 assert_eq!(verifier.dane_policy, DanePolicy::Required);
3471 }
3472
3473 #[tokio::test]
3474 async fn test_builder_dane_port() {
3475 let dns = Arc::new(MockDnsResolver::new());
3476 let tlog = Arc::new(MockTransparencyLogClient::new());
3477
3478 let verifier = ServerVerifier::builder()
3479 .dns_resolver(dns as Arc<dyn DnsResolver>)
3480 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3481 .dane_port(8443)
3482 .build()
3483 .await
3484 .unwrap();
3485
3486 assert_eq!(verifier.dane_port, 8443);
3487 }
3488
3489 #[tokio::test]
3490 async fn test_builder_trusted_ra_domains() {
3491 let dns = Arc::new(MockDnsResolver::new());
3492 let tlog = Arc::new(MockTransparencyLogClient::new());
3493
3494 let verifier = ServerVerifier::builder()
3495 .dns_resolver(dns as Arc<dyn DnsResolver>)
3496 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
3497 .trusted_ra_domains(["tlog.example.com"])
3498 .build()
3499 .await
3500 .unwrap();
3501
3502 assert!(verifier.trusted_ra_domains.is_some());
3503 assert!(
3504 verifier
3505 .trusted_ra_domains
3506 .unwrap()
3507 .contains("tlog.example.com")
3508 );
3509 }
3510
3511 #[tokio::test]
3516 async fn test_dane_required_no_tlsa_records() {
3517 let host = "test.example.com";
3518 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3519
3520 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
3521 let badge_url = "https://tlog.example.com/v1/agents/test-id";
3522
3523 let dns_record = BadgeRecord {
3524 format_version: "ans-badge1".to_string(),
3525 version: Some(Version::new(1, 0, 0)),
3526 url: badge_url.to_string(),
3527 };
3528
3529 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3531 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3532
3533 let verifier = ServerVerifier {
3534 dns_resolver,
3535 tlog_client,
3536 cache: None,
3537 failure_policy: FailurePolicy::FailClosed,
3538 dane_policy: DanePolicy::Required,
3539 dane_port: 443,
3540 trusted_ra_domains: None,
3541 };
3542
3543 let cert = create_test_cert_identity(host, fingerprint);
3544 let fqdn = Fqdn::new(host).unwrap();
3545
3546 let outcome = verifier.verify(&fqdn, &cert).await;
3547 assert!(
3548 matches!(outcome, VerificationOutcome::DaneError(_)),
3549 "Expected DaneError for required DANE with no TLSA records, got: {outcome:?}"
3550 );
3551 }
3552
3553 #[test]
3558 fn test_outcome_badge_returns_none_for_errors() {
3559 let outcome = VerificationOutcome::DnsError(DnsError::Timeout {
3560 fqdn: "test.example.com".to_string(),
3561 });
3562 assert!(outcome.badge().is_none());
3563
3564 let outcome = VerificationOutcome::NotAnsAgent {
3565 fqdn: "test.example.com".to_string(),
3566 };
3567 assert!(outcome.badge().is_none());
3568 }
3569
3570 #[test]
3571 fn test_outcome_badge_returns_some_for_mismatches() {
3572 let badge = create_test_badge(
3573 "test.example.com",
3574 "v1.0.0",
3575 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
3576 "SHA256:aaa",
3577 );
3578
3579 let outcome = VerificationOutcome::HostnameMismatch {
3580 expected: "test.example.com".to_string(),
3581 actual: "other.example.com".to_string(),
3582 badge,
3583 };
3584 assert!(outcome.badge().is_some());
3585 }
3586
3587 #[test]
3588 fn test_server_verifier_debug_format() {
3589 let dbg = format!("{:?}", ServerVerifierBuilder::default());
3590 assert!(dbg.contains("ServerVerifierBuilder"));
3591 }
3592}