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 #[cfg(feature = "scitt")]
249 ScittVerified {
250 status_token: crate::scitt::VerifiedStatusToken,
252 tier: ans_types::VerificationTier,
254 matched_fingerprint: CertFingerprint,
256 badge: Option<Badge>,
258 },
259
260 #[cfg(feature = "scitt")]
262 ScittError(crate::scitt::ScittError),
263}
264
265impl VerificationOutcome {
266 pub fn is_success(&self) -> bool {
268 match self {
269 Self::Verified { .. } => true,
270 #[cfg(feature = "scitt")]
271 Self::ScittVerified { .. } => true,
272 _ => false,
273 }
274 }
275
276 pub fn is_terminal_status(&self) -> bool {
283 match self {
284 Self::InvalidStatus { status, .. } => status.should_reject(),
285 #[cfg(feature = "scitt")]
286 Self::ScittError(e) => e.is_terminal_status(),
287 _ => false,
288 }
289 }
290
291 pub fn is_not_ans_agent(&self) -> bool {
293 matches!(self, Self::NotAnsAgent { .. })
294 }
295
296 pub fn badge(&self) -> Option<&Badge> {
298 match self {
299 Self::Verified { badge, .. }
300 | Self::InvalidStatus { badge, .. }
301 | Self::FingerprintMismatch { badge, .. }
302 | Self::HostnameMismatch { badge, .. }
303 | Self::AnsNameMismatch { badge, .. } => Some(badge),
304 #[cfg(feature = "scitt")]
305 Self::ScittVerified {
306 badge: Some(badge), ..
307 } => Some(badge),
308 _ => None,
309 }
310 }
311
312 pub fn into_result(self) -> AnsResult<Badge> {
325 match self {
326 Self::Verified { badge, .. } => Ok(badge),
327 Self::NotAnsAgent { fqdn } => Err(AnsError::Dns(DnsError::NotFound { fqdn })),
328 Self::InvalidStatus { status, .. } => {
329 Err(AnsError::Verification(VerificationError::InvalidStatus {
330 status,
331 }))
332 }
333 Self::FingerprintMismatch {
334 expected, actual, ..
335 } => Err(AnsError::Verification(
336 VerificationError::FingerprintMismatch { expected, actual },
337 )),
338 Self::HostnameMismatch {
339 expected, actual, ..
340 } => Err(AnsError::Verification(
341 VerificationError::HostnameMismatch { expected, actual },
342 )),
343 Self::AnsNameMismatch {
344 expected, actual, ..
345 } => Err(AnsError::Verification(VerificationError::AnsNameMismatch {
346 expected,
347 actual,
348 })),
349 Self::DnsError(e) => Err(AnsError::Dns(e)),
350 Self::TlogError(e) => Err(AnsError::TransparencyLog(e)),
351 Self::CertError(e) => Err(AnsError::Certificate(e)),
352 Self::ParseError(e) => Err(AnsError::Parse(e)),
353 Self::DaneError(e) => Err(AnsError::Verification(
354 VerificationError::DaneVerificationFailed(e),
355 )),
356 #[cfg(feature = "scitt")]
357 Self::ScittVerified { badge: Some(b), .. } => Ok(b),
358 #[cfg(feature = "scitt")]
359 Self::ScittVerified { badge: None, .. } => Err(AnsError::Verification(
360 VerificationError::Configuration(
361 "SCITT verification succeeded without badge; use into_scitt_result() for SCITT-aware callers".to_string(),
362 ),
363 )),
364 #[cfg(feature = "scitt")]
365 Self::ScittError(e) => Err(AnsError::Scitt(e)),
366 }
367 }
368
369 #[cfg(feature = "scitt")]
378 pub fn into_scitt_result(self) -> AnsResult<Option<Badge>> {
379 match self {
380 Self::Verified { badge, .. } => Ok(Some(badge)),
381 Self::ScittVerified { badge, .. } => Ok(badge),
382 other => other.into_result().map(Some),
383 }
384 }
385}
386
387#[cfg(feature = "scitt")]
391#[derive(Debug, Clone, Copy, Default)]
392#[non_exhaustive]
393pub enum ScittTierPolicy {
394 #[default]
400 ScittWithBadgeFallback,
401
402 RequireScitt,
407
408 BadgeWithScittEnhancement,
413}
414
415#[cfg(feature = "scitt")]
417#[derive(Debug, Clone)]
418#[non_exhaustive]
419pub struct ScittConfig {
420 pub tier_policy: ScittTierPolicy,
422 pub clock_skew_tolerance: Duration,
424}
425
426#[cfg(feature = "scitt")]
427impl Default for ScittConfig {
428 fn default() -> Self {
429 Self {
430 tier_policy: ScittTierPolicy::default(),
431 clock_skew_tolerance: Duration::from_secs(60),
432 }
433 }
434}
435
436#[cfg(feature = "scitt")]
437impl ScittConfig {
438 pub fn new() -> Self {
440 Self::default()
441 }
442
443 pub fn with_tier_policy(mut self, policy: ScittTierPolicy) -> Self {
445 self.tier_policy = policy;
446 self
447 }
448
449 pub fn with_clock_skew(mut self, tolerance: Duration) -> Self {
451 self.clock_skew_tolerance = tolerance;
452 self
453 }
454}
455
456#[derive(Debug, Clone, Copy, Default)]
458#[non_exhaustive]
459pub enum FailurePolicy {
460 #[default]
462 FailClosed,
463
464 FailOpenWithCache {
466 max_staleness: Duration,
468 },
469}
470
471fn validate_badge_domain(trusted: Option<&HashSet<String>>, url: &str) -> Result<(), TlogError> {
479 let Some(trusted) = trusted else {
480 return Ok(());
481 };
482 let parsed = url::Url::parse(url)
483 .map_err(|e| TlogError::InvalidUrl(format!("Badge URL is invalid: {e}")))?;
484 let domain = parsed
485 .host_str()
486 .ok_or_else(|| TlogError::InvalidUrl(format!("Badge URL has no host: {url}")))?;
487 if trusted.contains(domain) {
488 Ok(())
489 } else {
490 Err(TlogError::UntrustedDomain {
491 domain: domain.to_string(),
492 trusted: trusted.iter().cloned().collect(),
493 })
494 }
495}
496
497pub struct ServerVerifier {
499 dns_resolver: Arc<dyn DnsResolver>,
500 tlog_client: Arc<dyn TransparencyLogClient>,
501 cache: Option<Arc<BadgeCache>>,
502 failure_policy: FailurePolicy,
503 dane_policy: DanePolicy,
504 dane_port: u16,
506 trusted_ra_domains: Option<HashSet<String>>,
508}
509
510impl fmt::Debug for ServerVerifier {
511 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512 f.debug_struct("ServerVerifier")
513 .field("failure_policy", &self.failure_policy)
514 .field("dane_policy", &self.dane_policy)
515 .field("dane_port", &self.dane_port)
516 .field("has_cache", &self.cache.is_some())
517 .field("has_trusted_ra_domains", &self.trusted_ra_domains.is_some())
518 .finish_non_exhaustive()
519 }
520}
521
522impl ServerVerifier {
523 pub fn builder() -> ServerVerifierBuilder {
525 ServerVerifierBuilder::default()
526 }
527
528 pub async fn verify(&self, fqdn: &Fqdn, server_cert: &CertIdentity) -> VerificationOutcome {
540 tracing::info!(fqdn = %fqdn, "Starting server verification");
541 tracing::debug!(
542 cert_cn = ?server_cert.common_name,
543 cert_fingerprint = %server_cert.fingerprint,
544 "Certificate details"
545 );
546
547 if let Some(cache) = &self.cache {
549 let cached_badges = cache.get_all_for_fqdn(fqdn).await;
550 if !cached_badges.is_empty() {
551 tracing::debug!(fqdn = %fqdn, count = cached_badges.len(), "Scanning cached badges");
552 for cached in &cached_badges {
553 let outcome = self.verify_against_badge(&cached.badge, server_cert, true);
554 if outcome.is_success() {
555 tracing::debug!(fqdn = %fqdn, "Cache hit — badge matched");
556 return outcome;
557 }
558 }
559 tracing::info!(fqdn = %fqdn, "No cached badge matched fingerprint, fetching fresh");
561 }
562 }
563
564 tracing::debug!(fqdn = %fqdn, "Performing DNS lookup for _ans-badge / _ra-badge");
566 let records = match self.dns_resolver.lookup_badge(fqdn).await {
567 Ok(DnsLookupResult::Found(records)) => {
568 tracing::debug!(count = records.len(), "Found badge records");
569 for (i, r) in records.iter().enumerate() {
570 tracing::debug!(index = i, version = ?r.version, url = %r.url, "Badge record");
571 }
572 records
573 }
574 Ok(DnsLookupResult::NotFound) => {
575 tracing::warn!(fqdn = %fqdn, "No badge record found - not an ANS agent");
576 return VerificationOutcome::NotAnsAgent {
577 fqdn: fqdn.to_string(),
578 };
579 }
580 Err(e) => {
581 tracing::error!(fqdn = %fqdn, error = %e, "DNS lookup failed");
582 return self.handle_dns_error(e, fqdn, server_cert).await;
583 }
584 };
585
586 let outcome = self
590 .verify_against_records(&records, fqdn, server_cert)
591 .await;
592
593 if !outcome.is_success() {
594 return outcome;
595 }
596
597 if self.dane_policy.should_verify() {
599 match self.verify_dane(fqdn, server_cert).await {
600 Ok(result) => {
601 if !result.is_acceptable(self.dane_policy) {
602 tracing::error!(
603 fqdn = %fqdn,
604 dane_policy = ?self.dane_policy,
605 "DANE verification failed"
606 );
607 return VerificationOutcome::DaneError(DaneError::FingerprintMismatch);
608 }
609 }
610 Err(e) => {
611 tracing::error!(fqdn = %fqdn, error = %e, "DANE verification error");
612 return VerificationOutcome::DaneError(e);
613 }
614 }
615 }
616
617 outcome
618 }
619
620 async fn verify_against_records(
630 &self,
631 records: &[BadgeRecord],
632 fqdn: &Fqdn,
633 server_cert: &CertIdentity,
634 ) -> VerificationOutcome {
635 let mut sorted: Vec<_> = records.iter().collect();
637 sorted.sort_by(|a, b| b.version.cmp(&a.version));
638
639 if let Some(cache) = &self.cache {
641 let versions: Vec<Version> =
642 sorted.iter().filter_map(|r| r.version().cloned()).collect();
643 if !versions.is_empty() {
644 cache.set_version_index(fqdn, versions).await;
645 }
646 }
647
648 let results = self.fetch_badges_parallel(&sorted).await;
650
651 let mut last_mismatch: Option<VerificationOutcome> = None;
652 let mut last_error: Option<AnsError> = None;
653
654 for (record, result) in results {
655 let badge = match result {
656 Ok(b) => b,
657 Err(e) => {
658 tracing::debug!(url = %record.url, error = %e, "Failed to fetch badge, trying next");
659 last_error = Some(AnsError::TransparencyLog(e));
660 continue;
661 }
662 };
663
664 tracing::debug!(
665 version = ?record.version,
666 status = ?badge.status,
667 "Checking badge record"
668 );
669
670 if let Some(cache) = &self.cache {
672 let version = record
673 .version()
674 .cloned()
675 .or_else(|| badge.agent_version().parse::<Version>().ok());
676 if let Some(v) = &version {
677 cache.insert_for_fqdn_version(fqdn, v, badge.clone()).await;
678 tracing::debug!(fqdn = %fqdn, version = %v, "Cached badge by version");
679 }
680 }
681
682 let outcome = self.verify_against_badge(&badge, server_cert, true);
683
684 match &outcome {
685 VerificationOutcome::Verified { .. } => {
686 return outcome;
687 }
688 VerificationOutcome::FingerprintMismatch { .. } => {
689 tracing::debug!(version = ?record.version, "Fingerprint mismatch, trying next record");
690 last_mismatch = Some(outcome);
691 }
692 _ => return outcome,
694 }
695 }
696
697 if last_mismatch.is_some() {
700 tracing::info!(fqdn = %fqdn, "No badge matched, attempting refresh-on-mismatch");
701 return self.verify_with_refresh(fqdn, server_cert).await;
702 }
703
704 match last_error {
706 Some(e) => self.handle_ans_error(e, fqdn, server_cert).await,
707 None => VerificationOutcome::NotAnsAgent {
708 fqdn: fqdn.to_string(),
709 },
710 }
711 }
712
713 async fn verify_dane(
715 &self,
716 fqdn: &Fqdn,
717 cert: &CertIdentity,
718 ) -> Result<DaneVerificationResult, DaneError> {
719 tracing::debug!(
720 fqdn = %fqdn,
721 port = self.dane_port,
722 policy = ?self.dane_policy,
723 "Starting DANE verification"
724 );
725
726 let tlsa_records = self
727 .dns_resolver
728 .get_tlsa_records(fqdn, self.dane_port)
729 .await?;
730
731 verify_dane(
732 &tlsa_records,
733 &cert.fingerprint,
734 self.dane_policy,
735 fqdn,
736 self.dane_port,
737 )
738 }
739
740 pub async fn prefetch(&self, fqdn: &Fqdn) -> Result<Badge, AnsError> {
745 let records = match self.dns_resolver.lookup_badge(fqdn).await {
746 Ok(DnsLookupResult::Found(records)) => records,
747 Ok(DnsLookupResult::NotFound) => {
748 return Err(AnsError::Dns(DnsError::NotFound {
749 fqdn: fqdn.to_string(),
750 }));
751 }
752 Err(e) => return Err(AnsError::Dns(e)),
753 };
754
755 let mut sorted: Vec<_> = records.iter().collect();
757 sorted.sort_by(|a, b| b.version.cmp(&a.version));
758
759 if let Some(cache) = &self.cache {
761 let versions: Vec<Version> =
762 sorted.iter().filter_map(|r| r.version().cloned()).collect();
763 if !versions.is_empty() {
764 cache.set_version_index(fqdn, versions).await;
765 }
766 }
767
768 let results = self.fetch_badges_parallel(&sorted).await;
770
771 let mut preferred: Option<Badge> = None;
772 let mut last_error = None;
773
774 for (record, result) in results {
775 match result {
776 Ok(badge) => {
777 if let Some(cache) = &self.cache {
779 let version = record
780 .version()
781 .cloned()
782 .or_else(|| badge.agent_version().parse::<Version>().ok());
783 if let Some(v) = &version {
784 cache.insert_for_fqdn_version(fqdn, v, badge.clone()).await;
785 tracing::debug!(fqdn = %fqdn, version = %v, "Prefetch: cached badge");
786 }
787 }
788
789 if preferred.is_none()
791 && (badge.status.is_active() || badge.status == BadgeStatus::Deprecated)
792 {
793 preferred = Some(badge);
794 }
795 }
796 Err(e) => {
797 last_error = Some(e);
798 }
799 }
800 }
801
802 match preferred {
803 Some(badge) => Ok(badge),
804 None => match last_error {
805 Some(e) => Err(AnsError::TransparencyLog(e)),
806 None => Err(AnsError::TransparencyLog(TlogError::InvalidResponse(
807 "no badge records available".to_string(),
808 ))),
809 },
810 }
811 }
812
813 async fn verify_with_refresh(
819 &self,
820 fqdn: &Fqdn,
821 server_cert: &CertIdentity,
822 ) -> VerificationOutcome {
823 if let Some(cache) = &self.cache {
825 cache.invalidate_fqdn(fqdn).await;
826 }
827
828 let records = match self.dns_resolver.lookup_badge(fqdn).await {
830 Ok(DnsLookupResult::Found(records)) => records,
831 Ok(DnsLookupResult::NotFound) => {
832 return VerificationOutcome::NotAnsAgent {
833 fqdn: fqdn.to_string(),
834 };
835 }
836 Err(e) => return VerificationOutcome::DnsError(e),
837 };
838
839 self.verify_against_records_final(&records, fqdn, server_cert)
841 .await
842 }
843
844 async fn verify_against_records_final(
846 &self,
847 records: &[BadgeRecord],
848 fqdn: &Fqdn,
849 server_cert: &CertIdentity,
850 ) -> VerificationOutcome {
851 let mut sorted: Vec<_> = records.iter().collect();
852 sorted.sort_by(|a, b| b.version.cmp(&a.version));
853
854 if let Some(cache) = &self.cache {
856 let versions: Vec<Version> =
857 sorted.iter().filter_map(|r| r.version().cloned()).collect();
858 if !versions.is_empty() {
859 cache.set_version_index(fqdn, versions).await;
860 }
861 }
862
863 let results = self.fetch_badges_parallel(&sorted).await;
865
866 let mut last_mismatch: Option<VerificationOutcome> = None;
867 let mut last_error: Option<AnsError> = None;
868
869 for (record, result) in results {
870 let badge = match result {
871 Ok(b) => b,
872 Err(e) => {
873 last_error = Some(AnsError::TransparencyLog(e));
874 continue;
875 }
876 };
877
878 if let Some(cache) = &self.cache {
880 let version = record
881 .version()
882 .cloned()
883 .or_else(|| badge.agent_version().parse::<Version>().ok());
884 if let Some(v) = &version {
885 cache.insert_for_fqdn_version(fqdn, v, badge.clone()).await;
886 }
887 }
888
889 let outcome = self.verify_against_badge(&badge, server_cert, true);
890
891 match &outcome {
892 VerificationOutcome::Verified { .. } => {
893 return outcome;
894 }
895 VerificationOutcome::FingerprintMismatch { .. } => {
896 last_mismatch = Some(outcome);
897 }
898 _ => return outcome,
899 }
900 }
901
902 if let Some(mismatch) = last_mismatch {
904 return mismatch;
905 }
906 match last_error {
907 Some(e) => self.handle_ans_error(e, fqdn, server_cert).await,
908 None => VerificationOutcome::NotAnsAgent {
909 fqdn: fqdn.to_string(),
910 },
911 }
912 }
913
914 async fn fetch_badges_parallel<'a>(
920 &self,
921 records: &'a [&'a BadgeRecord],
922 ) -> Vec<(&'a BadgeRecord, Result<Badge, TlogError>)> {
923 let futures: Vec<_> = records
926 .iter()
927 .map(|record| {
928 let tlog = &self.tlog_client;
929 let trusted = &self.trusted_ra_domains;
930 async move {
931 if let Err(e) = validate_badge_domain(trusted.as_ref(), &record.url) {
932 (*record, Err(e))
933 } else {
934 let result = tlog.fetch_badge(&record.url).await;
935 (*record, result)
936 }
937 }
938 })
939 .collect();
940
941 join_all(futures).await
942 }
943
944 #[allow(clippy::unused_self)] fn verify_against_badge(
946 &self,
947 badge: &Badge,
948 cert: &CertIdentity,
949 is_server: bool,
950 ) -> VerificationOutcome {
951 let cert_type = if is_server { "server" } else { "identity" };
952 tracing::debug!(cert_type, "Verifying certificate against badge");
953
954 if badge.status.should_reject() {
956 tracing::warn!(
957 status = ?badge.status,
958 "Badge status is not valid for connections"
959 );
960 return VerificationOutcome::InvalidStatus {
961 status: badge.status,
962 badge: badge.clone(),
963 };
964 }
965 tracing::debug!(status = ?badge.status, "Badge status is valid");
966
967 let expected_fp = if is_server {
969 badge.server_cert_fingerprint()
970 } else {
971 badge.identity_cert_fingerprint()
972 };
973
974 tracing::debug!(
975 expected = %expected_fp,
976 actual = %cert.fingerprint,
977 "Comparing certificate fingerprints"
978 );
979
980 if !cert.fingerprint.matches(expected_fp) {
981 tracing::error!(
982 expected = %expected_fp,
983 actual = %cert.fingerprint,
984 "Certificate fingerprint MISMATCH"
985 );
986 return VerificationOutcome::FingerprintMismatch {
987 expected: expected_fp.to_string(),
988 actual: cert.fingerprint.to_string(),
989 badge: badge.clone(),
990 };
991 }
992 tracing::debug!("Fingerprint matches");
993
994 let expected_host = badge.agent_host();
996 let actual_host = cert.fqdn().unwrap_or("");
997
998 tracing::debug!(
999 expected = %expected_host,
1000 actual = %actual_host,
1001 "Comparing hostnames"
1002 );
1003
1004 if !actual_host.eq_ignore_ascii_case(expected_host) {
1005 tracing::error!(
1006 expected = %expected_host,
1007 actual = %actual_host,
1008 "Hostname MISMATCH"
1009 );
1010 return VerificationOutcome::HostnameMismatch {
1011 expected: expected_host.to_string(),
1012 actual: actual_host.to_string(),
1013 badge: badge.clone(),
1014 };
1015 }
1016
1017 tracing::info!(
1018 agent = %badge.agent_name(),
1019 host = %badge.agent_host(),
1020 "Verification SUCCESSFUL"
1021 );
1022 VerificationOutcome::Verified {
1023 badge: badge.clone(),
1024 matched_fingerprint: cert.fingerprint.clone(),
1025 }
1026 }
1027
1028 async fn handle_dns_error(
1029 &self,
1030 error: DnsError,
1031 fqdn: &Fqdn,
1032 cert: &CertIdentity,
1033 ) -> VerificationOutcome {
1034 match self.failure_policy {
1035 FailurePolicy::FailClosed => VerificationOutcome::DnsError(error),
1036 FailurePolicy::FailOpenWithCache { max_staleness } => {
1037 if let Some(cache) = &self.cache {
1038 for cached in cache.get_all_for_fqdn(fqdn).await {
1039 if cached.fetched_at.elapsed() < max_staleness {
1040 let outcome = self.verify_against_badge(&cached.badge, cert, true);
1041 if outcome.is_success() {
1042 return outcome;
1043 }
1044 }
1045 }
1046 }
1047 VerificationOutcome::DnsError(error)
1048 }
1049 }
1050 }
1051
1052 async fn handle_ans_error(
1053 &self,
1054 error: AnsError,
1055 fqdn: &Fqdn,
1056 cert: &CertIdentity,
1057 ) -> VerificationOutcome {
1058 match self.failure_policy {
1059 FailurePolicy::FailClosed => match error {
1060 AnsError::TransparencyLog(e) => VerificationOutcome::TlogError(e),
1061 AnsError::Dns(e) => VerificationOutcome::DnsError(e),
1062 AnsError::Certificate(e) => VerificationOutcome::CertError(e),
1063 AnsError::Parse(e) => VerificationOutcome::ParseError(e),
1064 AnsError::Verification(_) => VerificationOutcome::NotAnsAgent {
1065 fqdn: fqdn.to_string(),
1066 },
1067 #[cfg(feature = "scitt")]
1070 AnsError::Scitt(ref e) => {
1071 tracing::error!(
1072 error = %e,
1073 fqdn = %fqdn,
1074 "BUG: ScittError reached badge-path error handler — treating as NotAnsAgent"
1075 );
1076 VerificationOutcome::NotAnsAgent {
1077 fqdn: fqdn.to_string(),
1078 }
1079 }
1080 },
1081 FailurePolicy::FailOpenWithCache { max_staleness } => {
1082 if let Some(cache) = &self.cache {
1083 for cached in cache.get_all_for_fqdn(fqdn).await {
1084 if cached.fetched_at.elapsed() < max_staleness {
1085 let outcome = self.verify_against_badge(&cached.badge, cert, true);
1086 if outcome.is_success() {
1087 return outcome;
1088 }
1089 }
1090 }
1091 }
1092 match error {
1093 AnsError::TransparencyLog(e) => VerificationOutcome::TlogError(e),
1094 AnsError::Dns(e) => VerificationOutcome::DnsError(e),
1095 AnsError::Certificate(e) => VerificationOutcome::CertError(e),
1096 AnsError::Parse(e) => VerificationOutcome::ParseError(e),
1097 AnsError::Verification(_) => VerificationOutcome::NotAnsAgent {
1098 fqdn: fqdn.to_string(),
1099 },
1100 #[cfg(feature = "scitt")]
1103 AnsError::Scitt(ref e) => {
1104 tracing::error!(
1105 error = %e,
1106 fqdn = %fqdn,
1107 "BUG: ScittError reached badge-path error handler — treating as NotAnsAgent"
1108 );
1109 VerificationOutcome::NotAnsAgent {
1110 fqdn: fqdn.to_string(),
1111 }
1112 }
1113 }
1114 }
1115 }
1116 }
1117}
1118
1119#[derive(Default)]
1121pub struct ServerVerifierBuilder {
1122 dns_resolver: Option<Arc<dyn DnsResolver>>,
1123 tlog_client: Option<Arc<dyn TransparencyLogClient>>,
1124 cache: Option<Arc<BadgeCache>>,
1125 failure_policy: FailurePolicy,
1126 dane_policy: DanePolicy,
1127 dane_port: Option<u16>,
1128 trusted_ra_domains: Option<HashSet<String>>,
1129}
1130
1131impl fmt::Debug for ServerVerifierBuilder {
1132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1133 f.debug_struct("ServerVerifierBuilder")
1134 .field("failure_policy", &self.failure_policy)
1135 .field("dane_policy", &self.dane_policy)
1136 .field("dane_port", &self.dane_port)
1137 .field("has_dns_resolver", &self.dns_resolver.is_some())
1138 .field("has_tlog_client", &self.tlog_client.is_some())
1139 .field("has_cache", &self.cache.is_some())
1140 .finish_non_exhaustive()
1141 }
1142}
1143
1144impl ServerVerifierBuilder {
1145 pub fn dns_resolver(mut self, resolver: Arc<dyn DnsResolver>) -> Self {
1147 self.dns_resolver = Some(resolver);
1148 self
1149 }
1150
1151 pub fn tlog_client(mut self, client: Arc<dyn TransparencyLogClient>) -> Self {
1153 self.tlog_client = Some(client);
1154 self
1155 }
1156
1157 pub fn with_cache(mut self) -> Self {
1159 self.cache = Some(Arc::new(BadgeCache::with_defaults()));
1160 self
1161 }
1162
1163 pub fn with_cache_config(mut self, config: CacheConfig) -> Self {
1165 self.cache = Some(Arc::new(BadgeCache::new(config)));
1166 self
1167 }
1168
1169 pub fn cache(mut self, cache: Arc<BadgeCache>) -> Self {
1171 self.cache = Some(cache);
1172 self
1173 }
1174
1175 pub fn failure_policy(mut self, policy: FailurePolicy) -> Self {
1177 self.failure_policy = policy;
1178 self
1179 }
1180
1181 pub fn dane_policy(mut self, policy: DanePolicy) -> Self {
1187 self.dane_policy = policy;
1188 self
1189 }
1190
1191 pub fn with_dane_if_present(mut self) -> Self {
1195 self.dane_policy = DanePolicy::ValidateIfPresent;
1196 self
1197 }
1198
1199 pub fn require_dane(mut self) -> Self {
1203 self.dane_policy = DanePolicy::Required;
1204 self
1205 }
1206
1207 pub fn dane_port(mut self, port: u16) -> Self {
1209 self.dane_port = Some(port);
1210 self
1211 }
1212
1213 pub fn trusted_ra_domains(
1222 mut self,
1223 domains: impl IntoIterator<Item = impl Into<String>>,
1224 ) -> Self {
1225 self.trusted_ra_domains = Some(domains.into_iter().map(Into::into).collect());
1226 self
1227 }
1228
1229 pub async fn build(self) -> AnsResult<ServerVerifier> {
1231 let dns_resolver = match self.dns_resolver {
1232 Some(r) => r,
1233 None => Arc::new(
1234 HickoryDnsResolver::new()
1235 .await
1236 .map_err(|e| AnsError::Dns(DnsError::ResolverError(e.to_string())))?,
1237 ),
1238 };
1239
1240 let tlog_client = self
1241 .tlog_client
1242 .unwrap_or_else(|| Arc::new(HttpTransparencyLogClient::new()));
1243
1244 Ok(ServerVerifier {
1245 dns_resolver,
1246 tlog_client,
1247 cache: self.cache,
1248 failure_policy: self.failure_policy,
1249 dane_policy: self.dane_policy,
1250 dane_port: self.dane_port.unwrap_or(443),
1251 trusted_ra_domains: self.trusted_ra_domains,
1252 })
1253 }
1254}
1255
1256pub struct ClientVerifier {
1258 dns_resolver: Arc<dyn DnsResolver>,
1259 tlog_client: Arc<dyn TransparencyLogClient>,
1260 cache: Option<Arc<BadgeCache>>,
1261 failure_policy: FailurePolicy,
1262 trusted_ra_domains: Option<HashSet<String>>,
1264}
1265
1266impl fmt::Debug for ClientVerifier {
1267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1268 f.debug_struct("ClientVerifier")
1269 .field("failure_policy", &self.failure_policy)
1270 .field("has_cache", &self.cache.is_some())
1271 .field("has_trusted_ra_domains", &self.trusted_ra_domains.is_some())
1272 .finish_non_exhaustive()
1273 }
1274}
1275
1276impl ClientVerifier {
1277 pub fn builder() -> ClientVerifierBuilder {
1279 ClientVerifierBuilder::default()
1280 }
1281
1282 #[allow(clippy::too_many_lines)] pub async fn verify(&self, client_cert: &CertIdentity) -> VerificationOutcome {
1293 tracing::info!("Starting mTLS client verification");
1294 tracing::debug!(
1295 cn = ?client_cert.common_name,
1296 dns_sans = ?client_cert.dns_sans,
1297 uri_sans = ?client_cert.uri_sans,
1298 fingerprint = %client_cert.fingerprint,
1299 "Client certificate details"
1300 );
1301
1302 let Some(fqdn_str) = client_cert.fqdn() else {
1304 tracing::error!("No CN or DNS SAN found in client certificate");
1305 return VerificationOutcome::CertError(CryptoError::NoCommonName);
1306 };
1307
1308 let fqdn = match Fqdn::new(fqdn_str) {
1309 Ok(f) => f,
1310 Err(e) => {
1311 tracing::error!(fqdn = %fqdn_str, error = %e, "Invalid FQDN in certificate");
1312 return VerificationOutcome::ParseError(e);
1313 }
1314 };
1315 tracing::debug!(fqdn = %fqdn, "Extracted FQDN from certificate");
1316
1317 let ans_name = if let Some(n) = client_cert.ans_name() {
1319 tracing::debug!(ans_name = %n, "Found ANS name in URI SAN");
1320 n
1321 } else {
1322 tracing::error!(uri_sans = ?client_cert.uri_sans, "No ANS name (ans://) found in URI SANs");
1323 return VerificationOutcome::CertError(CryptoError::NoUriSan);
1324 };
1325
1326 let version = ans_name.version().clone();
1327 tracing::debug!(version = %version, "Parsed version from ANS name");
1328
1329 if let Some(cache) = &self.cache
1331 && let Some(cached) = cache.get_by_fqdn_version(&fqdn, &version).await
1332 {
1333 tracing::debug!(fqdn = %fqdn, version = %version, "Using cached badge");
1334 let outcome = self.verify_client_against_badge(&cached.badge, client_cert, &ans_name);
1335 if matches!(outcome, VerificationOutcome::FingerprintMismatch { .. }) {
1337 tracing::info!(fqdn = %fqdn, "Fingerprint mismatch on cached badge, refreshing");
1338 return self
1339 .verify_client_with_refresh(&fqdn, &version, client_cert, &ans_name)
1340 .await;
1341 }
1342 return outcome;
1343 }
1344
1345 tracing::debug!(fqdn = %fqdn, version = %version, "Looking up badge for version");
1347 let badge_record = match self
1348 .dns_resolver
1349 .find_badge_for_version(&fqdn, &version)
1350 .await
1351 {
1352 Ok(Some(record)) => {
1353 tracing::debug!(url = %record.url, "Found badge record for version");
1354 record
1355 }
1356 Ok(None) => {
1357 tracing::debug!("No badge for specific version, trying preferred badge");
1358 match self.dns_resolver.find_preferred_badge(&fqdn).await {
1360 Ok(Some(record)) => {
1361 tracing::debug!(url = %record.url, version = ?record.version, "Using preferred badge");
1362 record
1363 }
1364 Ok(None) => {
1365 tracing::warn!(fqdn = %fqdn, "No badge record found - not an ANS agent");
1366 return VerificationOutcome::NotAnsAgent {
1367 fqdn: fqdn.to_string(),
1368 };
1369 }
1370 Err(e) => {
1371 tracing::error!(error = %e, "DNS lookup failed");
1372 return self
1373 .handle_dns_error(e, &fqdn, &version, client_cert, &ans_name)
1374 .await;
1375 }
1376 }
1377 }
1378 Err(e) => {
1379 tracing::error!(error = %e, "DNS lookup failed");
1380 return self
1381 .handle_dns_error(e, &fqdn, &version, client_cert, &ans_name)
1382 .await;
1383 }
1384 };
1385
1386 if let Err(e) = validate_badge_domain(self.trusted_ra_domains.as_ref(), &badge_record.url) {
1388 return self
1389 .handle_tlog_error(e, &fqdn, &version, client_cert, &ans_name)
1390 .await;
1391 }
1392
1393 tracing::debug!(url = %badge_record.url, "Fetching badge from transparency log");
1395 let badge = match self.tlog_client.fetch_badge(&badge_record.url).await {
1396 Ok(b) => {
1397 tracing::debug!(
1398 status = ?b.status,
1399 agent_host = %b.agent_host(),
1400 ans_name = %b.agent_name(),
1401 "Fetched badge successfully"
1402 );
1403 b
1404 }
1405 Err(e) => {
1406 tracing::error!(url = %badge_record.url, error = %e, "Failed to fetch badge");
1407 return self
1408 .handle_tlog_error(e, &fqdn, &version, client_cert, &ans_name)
1409 .await;
1410 }
1411 };
1412
1413 if let Some(cache) = &self.cache {
1415 cache
1416 .insert_for_fqdn_version(&fqdn, &version, badge.clone())
1417 .await;
1418 tracing::debug!(fqdn = %fqdn, version = %version, "Cached badge");
1419 }
1420
1421 let outcome = self.verify_client_against_badge(&badge, client_cert, &ans_name);
1422 if matches!(outcome, VerificationOutcome::FingerprintMismatch { .. }) {
1424 tracing::info!(fqdn = %fqdn, "Fingerprint mismatch, attempting refresh");
1425 return self
1426 .verify_client_with_refresh(&fqdn, &version, client_cert, &ans_name)
1427 .await;
1428 }
1429 outcome
1430 }
1431
1432 #[allow(clippy::unused_self)] fn verify_client_against_badge(
1434 &self,
1435 badge: &Badge,
1436 cert: &CertIdentity,
1437 ans_name: &AnsName,
1438 ) -> VerificationOutcome {
1439 tracing::debug!("Verifying client certificate against badge");
1440
1441 if badge.status.should_reject() {
1443 tracing::warn!(status = ?badge.status, "Badge status is not valid for connections");
1444 return VerificationOutcome::InvalidStatus {
1445 status: badge.status,
1446 badge: badge.clone(),
1447 };
1448 }
1449 tracing::debug!(status = ?badge.status, "Badge status is valid");
1450
1451 let expected_fp = badge.identity_cert_fingerprint();
1453 tracing::debug!(
1454 expected = %expected_fp,
1455 actual = %cert.fingerprint,
1456 "Comparing identity certificate fingerprints"
1457 );
1458
1459 if !cert.fingerprint.matches(expected_fp) {
1460 tracing::error!(
1461 expected = %expected_fp,
1462 actual = %cert.fingerprint,
1463 "Identity certificate fingerprint MISMATCH"
1464 );
1465 return VerificationOutcome::FingerprintMismatch {
1466 expected: expected_fp.to_string(),
1467 actual: cert.fingerprint.to_string(),
1468 badge: badge.clone(),
1469 };
1470 }
1471 tracing::debug!("Identity fingerprint matches");
1472
1473 let expected_host = badge.agent_host();
1475 let actual_host = cert.fqdn().unwrap_or("");
1476 tracing::debug!(
1477 expected = %expected_host,
1478 actual = %actual_host,
1479 "Comparing hostnames"
1480 );
1481
1482 if !actual_host.eq_ignore_ascii_case(expected_host) {
1483 tracing::error!(
1484 expected = %expected_host,
1485 actual = %actual_host,
1486 "Hostname MISMATCH"
1487 );
1488 return VerificationOutcome::HostnameMismatch {
1489 expected: expected_host.to_string(),
1490 actual: actual_host.to_string(),
1491 badge: badge.clone(),
1492 };
1493 }
1494 tracing::debug!("Hostname matches");
1495
1496 let expected_ans_name = badge.agent_name();
1498 tracing::debug!(
1499 expected = %expected_ans_name,
1500 actual = %ans_name,
1501 "Comparing ANS names"
1502 );
1503
1504 if ans_name.to_string() != expected_ans_name {
1505 tracing::error!(
1506 expected = %expected_ans_name,
1507 actual = %ans_name,
1508 "ANS name MISMATCH"
1509 );
1510 return VerificationOutcome::AnsNameMismatch {
1511 expected: expected_ans_name.to_string(),
1512 actual: ans_name.to_string(),
1513 badge: badge.clone(),
1514 };
1515 }
1516
1517 tracing::info!(
1518 agent = %badge.agent_name(),
1519 host = %badge.agent_host(),
1520 "Client verification SUCCESSFUL"
1521 );
1522 VerificationOutcome::Verified {
1523 badge: badge.clone(),
1524 matched_fingerprint: cert.fingerprint.clone(),
1525 }
1526 }
1527
1528 async fn verify_client_with_refresh(
1534 &self,
1535 fqdn: &Fqdn,
1536 version: &Version,
1537 client_cert: &CertIdentity,
1538 ans_name: &AnsName,
1539 ) -> VerificationOutcome {
1540 if let Some(cache) = &self.cache {
1542 cache
1543 .invalidate(&CacheKey::fqdn_version(fqdn, version))
1544 .await;
1545 }
1546
1547 let badge_record = match self
1549 .dns_resolver
1550 .find_badge_for_version(fqdn, version)
1551 .await
1552 {
1553 Ok(Some(record)) => record,
1554 Ok(None) => match self.dns_resolver.find_preferred_badge(fqdn).await {
1555 Ok(Some(record)) => record,
1556 Ok(None) => {
1557 return VerificationOutcome::NotAnsAgent {
1558 fqdn: fqdn.to_string(),
1559 };
1560 }
1561 Err(e) => return VerificationOutcome::DnsError(e),
1562 },
1563 Err(e) => return VerificationOutcome::DnsError(e),
1564 };
1565
1566 if let Err(e) = validate_badge_domain(self.trusted_ra_domains.as_ref(), &badge_record.url) {
1568 return VerificationOutcome::TlogError(e);
1569 }
1570
1571 let badge = match self.tlog_client.fetch_badge(&badge_record.url).await {
1573 Ok(b) => b,
1574 Err(e) => return VerificationOutcome::TlogError(e),
1575 };
1576
1577 if let Some(cache) = &self.cache {
1579 cache
1580 .insert_for_fqdn_version(fqdn, version, badge.clone())
1581 .await;
1582 }
1583
1584 self.verify_client_against_badge(&badge, client_cert, ans_name)
1586 }
1587
1588 async fn handle_dns_error(
1589 &self,
1590 error: DnsError,
1591 fqdn: &Fqdn,
1592 version: &Version,
1593 cert: &CertIdentity,
1594 ans_name: &AnsName,
1595 ) -> VerificationOutcome {
1596 match self.failure_policy {
1597 FailurePolicy::FailClosed => VerificationOutcome::DnsError(error),
1598 FailurePolicy::FailOpenWithCache { max_staleness } => {
1599 if let Some(cache) = &self.cache
1600 && let Some(cached) = cache.get_by_fqdn_version(fqdn, version).await
1601 && cached.fetched_at.elapsed() < max_staleness
1602 {
1603 return self.verify_client_against_badge(&cached.badge, cert, ans_name);
1604 }
1605 VerificationOutcome::DnsError(error)
1606 }
1607 }
1608 }
1609
1610 async fn handle_tlog_error(
1611 &self,
1612 error: TlogError,
1613 fqdn: &Fqdn,
1614 version: &Version,
1615 cert: &CertIdentity,
1616 ans_name: &AnsName,
1617 ) -> VerificationOutcome {
1618 match self.failure_policy {
1619 FailurePolicy::FailClosed => VerificationOutcome::TlogError(error),
1620 FailurePolicy::FailOpenWithCache { max_staleness } => {
1621 if let Some(cache) = &self.cache
1622 && let Some(cached) = cache.get_by_fqdn_version(fqdn, version).await
1623 && cached.fetched_at.elapsed() < max_staleness
1624 {
1625 return self.verify_client_against_badge(&cached.badge, cert, ans_name);
1626 }
1627 VerificationOutcome::TlogError(error)
1628 }
1629 }
1630 }
1631}
1632
1633#[derive(Default)]
1635pub struct ClientVerifierBuilder {
1636 dns_resolver: Option<Arc<dyn DnsResolver>>,
1637 tlog_client: Option<Arc<dyn TransparencyLogClient>>,
1638 cache: Option<Arc<BadgeCache>>,
1639 failure_policy: FailurePolicy,
1640 trusted_ra_domains: Option<HashSet<String>>,
1641}
1642
1643impl fmt::Debug for ClientVerifierBuilder {
1644 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1645 f.debug_struct("ClientVerifierBuilder")
1646 .field("failure_policy", &self.failure_policy)
1647 .field("has_dns_resolver", &self.dns_resolver.is_some())
1648 .field("has_tlog_client", &self.tlog_client.is_some())
1649 .field("has_cache", &self.cache.is_some())
1650 .finish_non_exhaustive()
1651 }
1652}
1653
1654impl ClientVerifierBuilder {
1655 pub fn dns_resolver(mut self, resolver: Arc<dyn DnsResolver>) -> Self {
1657 self.dns_resolver = Some(resolver);
1658 self
1659 }
1660
1661 pub fn tlog_client(mut self, client: Arc<dyn TransparencyLogClient>) -> Self {
1663 self.tlog_client = Some(client);
1664 self
1665 }
1666
1667 pub fn with_cache(mut self) -> Self {
1669 self.cache = Some(Arc::new(BadgeCache::with_defaults()));
1670 self
1671 }
1672
1673 pub fn with_cache_config(mut self, config: CacheConfig) -> Self {
1675 self.cache = Some(Arc::new(BadgeCache::new(config)));
1676 self
1677 }
1678
1679 pub fn cache(mut self, cache: Arc<BadgeCache>) -> Self {
1681 self.cache = Some(cache);
1682 self
1683 }
1684
1685 pub fn failure_policy(mut self, policy: FailurePolicy) -> Self {
1687 self.failure_policy = policy;
1688 self
1689 }
1690
1691 pub fn trusted_ra_domains(
1700 mut self,
1701 domains: impl IntoIterator<Item = impl Into<String>>,
1702 ) -> Self {
1703 self.trusted_ra_domains = Some(domains.into_iter().map(Into::into).collect());
1704 self
1705 }
1706
1707 pub async fn build(self) -> AnsResult<ClientVerifier> {
1709 let dns_resolver = match self.dns_resolver {
1710 Some(r) => r,
1711 None => Arc::new(
1712 HickoryDnsResolver::new()
1713 .await
1714 .map_err(|e| AnsError::Dns(DnsError::ResolverError(e.to_string())))?,
1715 ),
1716 };
1717
1718 let tlog_client = self
1719 .tlog_client
1720 .unwrap_or_else(|| Arc::new(HttpTransparencyLogClient::new()));
1721
1722 Ok(ClientVerifier {
1723 dns_resolver,
1724 tlog_client,
1725 cache: self.cache,
1726 failure_policy: self.failure_policy,
1727 trusted_ra_domains: self.trusted_ra_domains,
1728 })
1729 }
1730}
1731
1732pub struct AnsVerifier {
1734 server_verifier: ServerVerifier,
1735 client_verifier: ClientVerifier,
1736 #[cfg(feature = "rustls")]
1737 private_ca_pem: Option<Vec<u8>>,
1738 #[cfg(feature = "scitt")]
1739 scitt_config: Option<ScittConfig>,
1740 #[cfg(feature = "scitt")]
1741 scitt_key_store: Option<Arc<crate::scitt::RefreshableKeyStore>>,
1742 #[cfg(feature = "scitt")]
1743 scitt_verification_cache: Option<Arc<crate::scitt::ScittVerificationCache>>,
1744}
1745
1746impl fmt::Debug for AnsVerifier {
1747 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1748 let builder = &mut f.debug_struct("AnsVerifier");
1749 builder
1750 .field("server_verifier", &self.server_verifier)
1751 .field("client_verifier", &self.client_verifier);
1752 #[cfg(feature = "scitt")]
1753 builder.field("has_scitt_config", &self.scitt_config.is_some());
1754 #[cfg(feature = "scitt")]
1755 builder.field(
1756 "has_scitt_verification_cache",
1757 &self.scitt_verification_cache.is_some(),
1758 );
1759 builder.finish_non_exhaustive()
1760 }
1761}
1762
1763impl AnsVerifier {
1764 pub async fn new() -> AnsResult<Self> {
1766 Self::builder().build().await
1767 }
1768
1769 pub fn builder() -> AnsVerifierBuilder {
1771 AnsVerifierBuilder::default()
1772 }
1773
1774 pub async fn verify_server(
1776 &self,
1777 fqdn: impl AsRef<str>,
1778 server_cert: &CertIdentity,
1779 ) -> VerificationOutcome {
1780 let fqdn = match Fqdn::new(fqdn.as_ref()) {
1781 Ok(f) => f,
1782 Err(e) => return VerificationOutcome::ParseError(e),
1783 };
1784 self.server_verifier.verify(&fqdn, server_cert).await
1785 }
1786
1787 pub async fn verify_client(&self, client_cert: &CertIdentity) -> VerificationOutcome {
1789 self.client_verifier.verify(client_cert).await
1790 }
1791
1792 pub async fn prefetch(&self, fqdn: impl AsRef<str>) -> AnsResult<Badge> {
1794 let fqdn = Fqdn::new(fqdn.as_ref())?;
1795 self.server_verifier.prefetch(&fqdn).await
1796 }
1797
1798 #[cfg(feature = "rustls")]
1804 pub fn client_cert_verifier(&self) -> AnsResult<crate::AnsClientCertVerifier> {
1805 let pem = self.private_ca_pem.as_ref().ok_or_else(|| {
1806 AnsError::Verification(VerificationError::Configuration(
1807 "private_ca_pem is required for client_cert_verifier".into(),
1808 ))
1809 })?;
1810 crate::AnsClientCertVerifier::from_pem(pem).map_err(|e| {
1811 AnsError::Verification(VerificationError::Configuration(format!(
1812 "Failed to build client cert verifier: {e}"
1813 )))
1814 })
1815 }
1816
1817 #[cfg(feature = "rustls")]
1822 pub fn client_cert_verifier_optional(&self) -> AnsResult<crate::AnsClientCertVerifier> {
1823 let pem = self.private_ca_pem.as_ref().ok_or_else(|| {
1824 AnsError::Verification(VerificationError::Configuration(
1825 "private_ca_pem is required for client_cert_verifier_optional".into(),
1826 ))
1827 })?;
1828 crate::AnsClientCertVerifier::from_pem_optional(pem).map_err(|e| {
1829 AnsError::Verification(VerificationError::Configuration(format!(
1830 "Failed to build optional client cert verifier: {e}"
1831 )))
1832 })
1833 }
1834
1835 #[cfg(feature = "rustls")]
1840 pub fn server_cert_verifier(
1841 &self,
1842 fingerprint: &CertFingerprint,
1843 ) -> AnsResult<crate::AnsServerCertVerifier> {
1844 crate::AnsServerCertVerifier::new(fingerprint.clone()).map_err(|e| {
1845 AnsError::Verification(VerificationError::Configuration(format!(
1846 "Failed to build server cert verifier: {e}"
1847 )))
1848 })
1849 }
1850
1851 #[cfg(feature = "scitt")]
1865 pub async fn verify_server_with_scitt(
1866 &self,
1867 fqdn: impl AsRef<str>,
1868 server_cert: &CertIdentity,
1869 headers: &crate::scitt::ScittHeaders,
1870 ) -> VerificationOutcome {
1871 let fqdn_str = fqdn.as_ref();
1872 let parsed_fqdn = match Fqdn::new(fqdn_str) {
1873 Ok(f) => f,
1874 Err(e) => return VerificationOutcome::ParseError(e),
1875 };
1876
1877 let Some(config) = &self.scitt_config else {
1878 return self.server_verifier.verify(&parsed_fqdn, server_cert).await;
1880 };
1881
1882 let Some(key_store) = &self.scitt_key_store else {
1883 tracing::error!("BUG: scitt_config present but no key store — falling back to badge");
1886 return self.server_verifier.verify(&parsed_fqdn, server_cert).await;
1887 };
1888
1889 match config.tier_policy {
1890 ScittTierPolicy::ScittWithBadgeFallback => {
1891 self.verify_scitt_first(&parsed_fqdn, server_cert, headers, key_store, config, true)
1892 .await
1893 }
1894 ScittTierPolicy::RequireScitt => {
1895 self.verify_scitt_first(
1896 &parsed_fqdn,
1897 server_cert,
1898 headers,
1899 key_store,
1900 config,
1901 false,
1902 )
1903 .await
1904 }
1905 ScittTierPolicy::BadgeWithScittEnhancement => {
1906 let badge_outcome = self.server_verifier.verify(&parsed_fqdn, server_cert).await;
1908 if !badge_outcome.is_success() || headers.is_empty() {
1909 return badge_outcome;
1910 }
1911 let scitt_cache = self.scitt_verification_cache.as_deref();
1915 let scitt_outcome = Self::try_scitt_verification(
1916 server_cert,
1917 headers,
1918 key_store,
1919 config,
1920 true,
1921 scitt_cache,
1922 )
1923 .await;
1924 match scitt_outcome {
1925 Some(VerificationOutcome::ScittVerified {
1926 status_token,
1927 tier,
1928 matched_fingerprint,
1929 badge: _,
1930 }) => {
1931 let badge = badge_outcome.badge().cloned();
1933 VerificationOutcome::ScittVerified {
1934 status_token,
1935 tier,
1936 matched_fingerprint,
1937 badge,
1938 }
1939 }
1940 Some(outcome) => outcome,
1943 None => badge_outcome,
1947 }
1948 }
1949 }
1950 }
1951
1952 #[cfg(feature = "scitt")]
1957 pub async fn verify_client_with_scitt(
1958 &self,
1959 client_cert: &CertIdentity,
1960 headers: &crate::scitt::ScittHeaders,
1961 ) -> VerificationOutcome {
1962 let Some(config) = &self.scitt_config else {
1963 return self.client_verifier.verify(client_cert).await;
1964 };
1965
1966 let Some(key_store) = &self.scitt_key_store else {
1967 tracing::error!("BUG: scitt_config present but no key store — falling back to badge");
1970 return self.client_verifier.verify(client_cert).await;
1971 };
1972
1973 match config.tier_policy {
1974 ScittTierPolicy::ScittWithBadgeFallback => {
1975 self.verify_client_scitt_first(client_cert, headers, key_store, config, true)
1976 .await
1977 }
1978 ScittTierPolicy::RequireScitt => {
1979 self.verify_client_scitt_first(client_cert, headers, key_store, config, false)
1980 .await
1981 }
1982 ScittTierPolicy::BadgeWithScittEnhancement => {
1983 let badge_outcome = self.client_verifier.verify(client_cert).await;
1984 if !badge_outcome.is_success() || headers.is_empty() {
1985 return badge_outcome;
1986 }
1987 let scitt_cache = self.scitt_verification_cache.as_deref();
1989 let scitt_outcome = Self::try_scitt_verification(
1990 client_cert,
1991 headers,
1992 key_store,
1993 config,
1994 false,
1995 scitt_cache,
1996 )
1997 .await;
1998 match scitt_outcome {
1999 Some(VerificationOutcome::ScittVerified {
2000 status_token,
2001 tier,
2002 matched_fingerprint,
2003 badge: _,
2004 }) => {
2005 let badge = badge_outcome.badge().cloned();
2006 VerificationOutcome::ScittVerified {
2007 status_token,
2008 tier,
2009 matched_fingerprint,
2010 badge,
2011 }
2012 }
2013 Some(outcome) => outcome, None => badge_outcome,
2015 }
2016 }
2017 }
2018 }
2019
2020 #[cfg(feature = "scitt")]
2027 async fn verify_scitt_first(
2028 &self,
2029 fqdn: &Fqdn,
2030 server_cert: &CertIdentity,
2031 headers: &crate::scitt::ScittHeaders,
2032 key_store: &Arc<crate::scitt::RefreshableKeyStore>,
2033 config: &ScittConfig,
2034 allow_badge_fallback: bool,
2035 ) -> VerificationOutcome {
2036 if headers.is_empty() {
2038 if allow_badge_fallback {
2039 tracing::debug!(fqdn = %fqdn, "No SCITT headers — falling back to badge");
2040 return self.server_verifier.verify(fqdn, server_cert).await;
2041 }
2042 return VerificationOutcome::ScittError(crate::scitt::ScittError::MissingTokenField(
2043 "No SCITT headers present and RequireScitt policy is active".to_string(),
2044 ));
2045 }
2046
2047 let scitt_cache = self.scitt_verification_cache.as_deref();
2049 match Self::try_scitt_verification(
2050 server_cert,
2051 headers,
2052 key_store,
2053 config,
2054 true,
2055 scitt_cache,
2056 )
2057 .await
2058 {
2059 Some(outcome) => outcome,
2060 None => {
2061 VerificationOutcome::ScittError(crate::scitt::ScittError::MissingTokenField(
2063 "SCITT headers present but no valid status token found".to_string(),
2064 ))
2065 }
2066 }
2067 }
2068
2069 #[cfg(feature = "scitt")]
2072 async fn verify_client_scitt_first(
2073 &self,
2074 client_cert: &CertIdentity,
2075 headers: &crate::scitt::ScittHeaders,
2076 key_store: &Arc<crate::scitt::RefreshableKeyStore>,
2077 config: &ScittConfig,
2078 allow_badge_fallback: bool,
2079 ) -> VerificationOutcome {
2080 if headers.is_empty() {
2081 if allow_badge_fallback {
2082 tracing::debug!("No SCITT headers on client — falling back to badge");
2083 return self.client_verifier.verify(client_cert).await;
2084 }
2085 return VerificationOutcome::ScittError(crate::scitt::ScittError::MissingTokenField(
2086 "No SCITT headers present and RequireScitt policy is active".to_string(),
2087 ));
2088 }
2089
2090 let scitt_cache = self.scitt_verification_cache.as_deref();
2092 match Self::try_scitt_verification(
2093 client_cert,
2094 headers,
2095 key_store,
2096 config,
2097 false,
2098 scitt_cache,
2099 )
2100 .await
2101 {
2102 Some(outcome) => outcome,
2103 None => VerificationOutcome::ScittError(crate::scitt::ScittError::MissingTokenField(
2104 "SCITT headers present but no valid status token found".to_string(),
2105 )),
2106 }
2107 }
2108
2109 #[cfg(feature = "scitt")]
2128 #[allow(clippy::too_many_lines)] async fn try_scitt_verification(
2130 cert: &CertIdentity,
2131 headers: &crate::scitt::ScittHeaders,
2132 key_store: &Arc<crate::scitt::RefreshableKeyStore>,
2133 config: &ScittConfig,
2134 is_server: bool,
2135 cache: Option<&crate::scitt::ScittVerificationCache>,
2136 ) -> Option<VerificationOutcome> {
2137 let token_bytes = headers.status_token.as_ref()?;
2138
2139 let token_hash = crate::scitt::hash_bytes(token_bytes);
2141 let receipt_hash = headers
2142 .receipt
2143 .as_ref()
2144 .map(|b| crate::scitt::hash_bytes(b));
2145
2146 if let Some(cache) = cache
2148 && let Some(outcome) = cache
2149 .get_outcome(cert.fingerprint(), &token_hash, receipt_hash.as_ref())
2150 .await
2151 {
2152 tracing::debug!("SCITT verification cache hit (Layer 2 — full outcome)");
2153 return Some(VerificationOutcome::ScittVerified {
2154 status_token: (*outcome.verified_token).clone(),
2155 tier: outcome.tier,
2156 matched_fingerprint: outcome.matched_fingerprint.clone(),
2157 badge: None,
2158 });
2159 }
2160
2161 let verified_token = if let Some(cached_token) = match cache {
2163 Some(c) => c.get_verified_token(&token_hash).await,
2164 None => None,
2165 } {
2166 tracing::debug!("SCITT token cache hit (Layer 1 — skipping ECDSA)");
2167 (*cached_token).clone()
2168 } else {
2169 let snapshot = key_store.current_snapshot().await;
2171 let first_result = crate::scitt::verify_status_token(
2172 token_bytes,
2173 &snapshot,
2174 config.clock_skew_tolerance,
2175 );
2176
2177 let vt = match first_result {
2179 Err(original_err @ crate::scitt::ScittError::UnknownKeyId(_)) => {
2180 let refreshed = match key_store.refresh_if_cooldown_elapsed().await {
2181 Ok(did_refresh) => did_refresh,
2182 Err(refresh_err) => {
2183 tracing::warn!(error = %refresh_err, "On-demand key refresh failed");
2184 false
2185 }
2186 };
2187
2188 if refreshed {
2189 let new_snapshot = key_store.current_snapshot().await;
2190 match crate::scitt::verify_status_token(
2191 token_bytes,
2192 &new_snapshot,
2193 config.clock_skew_tolerance,
2194 ) {
2195 Ok(vt) => vt,
2196 Err(e) => return Some(VerificationOutcome::ScittError(e)),
2197 }
2198 } else {
2199 return Some(VerificationOutcome::ScittError(original_err));
2200 }
2201 }
2202 Ok(vt) => vt,
2203 Err(e) => return Some(VerificationOutcome::ScittError(e)),
2204 };
2205
2206 if let Some(cache) = cache {
2208 cache
2209 .insert_verified_token(token_hash, Arc::new(vt.clone()))
2210 .await;
2211 }
2212
2213 vt
2214 };
2215
2216 let fingerprint_matches = if is_server {
2218 crate::scitt::matches_server_cert(&verified_token.payload, cert.fingerprint())
2219 } else {
2220 crate::scitt::matches_identity_cert(&verified_token.payload, cert.fingerprint())
2221 };
2222
2223 if !fingerprint_matches {
2224 return Some(VerificationOutcome::ScittError(
2225 crate::scitt::ScittError::MissingTokenField(format!(
2226 "Certificate fingerprint {} not found in status token's {} cert list ({} entries)",
2227 cert.fingerprint(),
2228 if is_server { "server" } else { "identity" },
2229 if is_server {
2230 verified_token.payload.valid_server_certs.len()
2231 } else {
2232 verified_token.payload.valid_identity_certs.len()
2233 }
2234 )),
2235 ));
2236 }
2237
2238 let tier = if let Some(receipt_bytes) = &headers.receipt {
2240 let Some(rh) = receipt_hash.as_ref() else {
2243 tracing::warn!("receipt_hash missing despite receipt bytes present");
2245 return Some(VerificationOutcome::ScittError(
2246 crate::scitt::ScittError::MissingTokenField(
2247 "Internal error: receipt hash not computed".to_string(),
2248 ),
2249 ));
2250 };
2251
2252 if let Some(_cached_receipt) = match cache {
2253 Some(c) => c.get_verified_receipt(rh).await,
2254 None => None,
2255 } {
2256 tracing::debug!("SCITT receipt cache hit (Layer 1 — skipping Merkle)");
2257 ans_types::VerificationTier::FullScitt
2258 } else {
2259 let snapshot = key_store.current_snapshot().await;
2261 match crate::scitt::verify_receipt(receipt_bytes, &snapshot) {
2262 Ok(receipt) => {
2263 tracing::debug!("SCITT receipt verified — FullScitt tier");
2264 if let Some(cache) = cache {
2265 cache.insert_verified_receipt(*rh, Arc::new(receipt)).await;
2266 }
2267 ans_types::VerificationTier::FullScitt
2268 }
2269 Err(e) => {
2270 if matches!(config.tier_policy, ScittTierPolicy::RequireScitt) {
2271 tracing::error!(error = %e, "Receipt verification failed under RequireScitt — rejecting");
2272 return Some(VerificationOutcome::ScittError(e));
2273 }
2274 tracing::warn!(error = %e, "Receipt verification failed — StatusTokenVerified tier");
2275 ans_types::VerificationTier::StatusTokenVerified
2276 }
2277 }
2278 }
2279 } else {
2280 ans_types::VerificationTier::StatusTokenVerified
2281 };
2282
2283 if let Some(cache) = cache {
2285 cache
2286 .insert_outcome(
2287 cert.fingerprint(),
2288 &token_hash,
2289 receipt_hash.as_ref(),
2290 crate::scitt::CachedScittOutcome {
2291 verified_token: Arc::new(verified_token.clone()),
2292 tier,
2293 matched_fingerprint: cert.fingerprint().clone(),
2294 exp: verified_token.payload.exp,
2295 },
2296 )
2297 .await;
2298 }
2299
2300 Some(VerificationOutcome::ScittVerified {
2301 status_token: verified_token,
2302 tier,
2303 matched_fingerprint: cert.fingerprint().clone(),
2304 badge: None,
2305 })
2306 }
2307}
2308
2309#[derive(Default)]
2311pub struct AnsVerifierBuilder {
2312 dns_resolver: Option<Arc<dyn DnsResolver>>,
2313 dns_config: Option<DnsResolverConfig>,
2314 dns_nameservers: Option<Vec<std::net::Ipv4Addr>>,
2315 tlog_client: Option<Arc<dyn TransparencyLogClient>>,
2316 cache_config: Option<CacheConfig>,
2317 failure_policy: FailurePolicy,
2318 dane_policy: DanePolicy,
2319 dane_port: Option<u16>,
2320 trusted_ra_domains: Option<HashSet<String>>,
2321 #[cfg(feature = "rustls")]
2322 private_ca_pem: Option<Vec<u8>>,
2323 #[cfg(feature = "scitt")]
2324 scitt_config: Option<ScittConfig>,
2325 #[cfg(feature = "scitt")]
2326 scitt_key_store: Option<Arc<crate::scitt::RefreshableKeyStore>>,
2327 #[cfg(feature = "scitt")]
2328 scitt_verification_cache: Option<Arc<crate::scitt::ScittVerificationCache>>,
2329}
2330
2331impl fmt::Debug for AnsVerifierBuilder {
2332 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2333 let builder = &mut f.debug_struct("AnsVerifierBuilder");
2334 builder
2335 .field("dns_config", &self.dns_config)
2336 .field("failure_policy", &self.failure_policy)
2337 .field("dane_policy", &self.dane_policy)
2338 .field("dane_port", &self.dane_port)
2339 .field("has_dns_resolver", &self.dns_resolver.is_some())
2340 .field("has_tlog_client", &self.tlog_client.is_some())
2341 .field("has_cache_config", &self.cache_config.is_some());
2342 #[cfg(feature = "scitt")]
2343 builder
2344 .field("has_scitt_config", &self.scitt_config.is_some())
2345 .field("has_scitt_key_store", &self.scitt_key_store.is_some());
2346 builder.finish_non_exhaustive()
2347 }
2348}
2349
2350impl AnsVerifierBuilder {
2351 pub fn dns_resolver(mut self, resolver: Arc<dyn DnsResolver>) -> Self {
2353 self.dns_resolver = Some(resolver);
2354 self
2355 }
2356
2357 pub fn dns_preset(mut self, preset: DnsResolverConfig) -> Self {
2372 self.dns_config = Some(preset);
2373 self
2374 }
2375
2376 pub fn dns_cloudflare(self) -> Self {
2378 self.dns_preset(DnsResolverConfig::Cloudflare)
2379 }
2380
2381 pub fn dns_cloudflare_tls(self) -> Self {
2383 self.dns_preset(DnsResolverConfig::CloudflareTls)
2384 }
2385
2386 pub fn dns_google(self) -> Self {
2388 self.dns_preset(DnsResolverConfig::Google)
2389 }
2390
2391 pub fn dns_google_tls(self) -> Self {
2393 self.dns_preset(DnsResolverConfig::GoogleTls)
2394 }
2395
2396 pub fn dns_quad9(self) -> Self {
2398 self.dns_preset(DnsResolverConfig::Quad9)
2399 }
2400
2401 pub fn dns_nameservers(mut self, nameservers: &[std::net::Ipv4Addr]) -> Self {
2420 self.dns_nameservers = Some(nameservers.to_vec());
2421 self
2422 }
2423
2424 pub fn tlog_client(mut self, client: Arc<dyn TransparencyLogClient>) -> Self {
2426 self.tlog_client = Some(client);
2427 self
2428 }
2429
2430 pub fn with_caching(mut self) -> Self {
2432 self.cache_config = Some(CacheConfig::default());
2433 #[cfg(feature = "scitt")]
2434 if self.scitt_verification_cache.is_none() {
2435 self.scitt_verification_cache = Some(Arc::new(
2436 crate::scitt::ScittVerificationCache::with_defaults(),
2437 ));
2438 }
2439 self
2440 }
2441
2442 pub fn with_cache_config(mut self, config: CacheConfig) -> Self {
2444 self.cache_config = Some(config);
2445 self
2446 }
2447
2448 pub fn failure_policy(mut self, policy: FailurePolicy) -> Self {
2450 self.failure_policy = policy;
2451 self
2452 }
2453
2454 pub fn dane_policy(mut self, policy: DanePolicy) -> Self {
2460 self.dane_policy = policy;
2461 self
2462 }
2463
2464 pub fn with_dane_if_present(mut self) -> Self {
2466 self.dane_policy = DanePolicy::ValidateIfPresent;
2467 self
2468 }
2469
2470 pub fn require_dane(mut self) -> Self {
2472 self.dane_policy = DanePolicy::Required;
2473 self
2474 }
2475
2476 pub fn dane_port(mut self, port: u16) -> Self {
2478 self.dane_port = Some(port);
2479 self
2480 }
2481
2482 pub fn trusted_ra_domains(
2487 mut self,
2488 domains: impl IntoIterator<Item = impl Into<String>>,
2489 ) -> Self {
2490 self.trusted_ra_domains = Some(domains.into_iter().map(Into::into).collect());
2491 self
2492 }
2493
2494 #[cfg(feature = "rustls")]
2502 pub fn private_ca_pem(mut self, pem: impl Into<Vec<u8>>) -> Self {
2503 self.private_ca_pem = Some(pem.into());
2504 self
2505 }
2506
2507 #[cfg(feature = "scitt")]
2512 pub fn scitt_config(mut self, config: ScittConfig) -> Self {
2513 self.scitt_config = Some(config);
2514 self
2515 }
2516
2517 #[cfg(feature = "scitt")]
2527 #[allow(clippy::needless_pass_by_value)] pub fn scitt_key_store(mut self, key_store: Arc<crate::scitt::ScittKeyStore>) -> Self {
2529 self.scitt_key_store = Some(Arc::new(crate::scitt::RefreshableKeyStore::from_static(
2530 (*key_store).clone(),
2531 )));
2532 self
2533 }
2534
2535 #[cfg(feature = "scitt")]
2542 pub fn scitt_refreshable_key_store(
2543 mut self,
2544 key_store: Arc<crate::scitt::RefreshableKeyStore>,
2545 ) -> Self {
2546 self.scitt_key_store = Some(key_store);
2547 self
2548 }
2549
2550 #[cfg(feature = "scitt")]
2556 pub fn with_scitt_verification_cache(
2557 mut self,
2558 cache: crate::scitt::ScittVerificationCache,
2559 ) -> Self {
2560 self.scitt_verification_cache = Some(Arc::new(cache));
2561 self
2562 }
2563
2564 pub async fn build(self) -> AnsResult<AnsVerifier> {
2573 #[cfg(feature = "scitt")]
2575 if self.scitt_config.is_some() && self.scitt_key_store.is_none() {
2576 return Err(AnsError::Verification(VerificationError::Configuration(
2577 "scitt_config requires a key store — call scitt_key_store() or \
2578 scitt_refreshable_key_store() on the builder"
2579 .to_string(),
2580 )));
2581 }
2582
2583 let dns_resolver: Arc<dyn DnsResolver> = if let Some(r) = self.dns_resolver {
2585 r
2586 } else if let Some(nameservers) = self.dns_nameservers {
2587 Arc::new(
2588 HickoryDnsResolver::with_nameservers(&nameservers)
2589 .await
2590 .map_err(|e| AnsError::Dns(DnsError::ResolverError(e.to_string())))?,
2591 )
2592 } else if let Some(preset) = self.dns_config {
2593 Arc::new(
2594 HickoryDnsResolver::with_preset(preset)
2595 .await
2596 .map_err(|e| AnsError::Dns(DnsError::ResolverError(e.to_string())))?,
2597 )
2598 } else {
2599 Arc::new(
2600 HickoryDnsResolver::new()
2601 .await
2602 .map_err(|e| AnsError::Dns(DnsError::ResolverError(e.to_string())))?,
2603 )
2604 };
2605
2606 let tlog_client: Arc<dyn TransparencyLogClient> = self
2607 .tlog_client
2608 .unwrap_or_else(|| Arc::new(HttpTransparencyLogClient::new()));
2609
2610 let cache = self.cache_config.map(|c| Arc::new(BadgeCache::new(c)));
2611 let dane_port = self.dane_port.unwrap_or(443);
2612
2613 let server_verifier = ServerVerifier {
2614 dns_resolver: dns_resolver.clone(),
2615 tlog_client: tlog_client.clone(),
2616 cache: cache.clone(),
2617 failure_policy: self.failure_policy,
2618 dane_policy: self.dane_policy,
2619 dane_port,
2620 trusted_ra_domains: self.trusted_ra_domains.clone(),
2621 };
2622
2623 let client_verifier = ClientVerifier {
2624 dns_resolver,
2625 tlog_client,
2626 cache,
2627 failure_policy: self.failure_policy,
2628 trusted_ra_domains: self.trusted_ra_domains,
2629 };
2630
2631 Ok(AnsVerifier {
2632 server_verifier,
2633 client_verifier,
2634 #[cfg(feature = "rustls")]
2635 private_ca_pem: self.private_ca_pem,
2636 #[cfg(feature = "scitt")]
2637 scitt_config: self.scitt_config,
2638 #[cfg(feature = "scitt")]
2639 scitt_key_store: self.scitt_key_store,
2640 #[cfg(feature = "scitt")]
2641 scitt_verification_cache: self.scitt_verification_cache,
2642 })
2643 }
2644}
2645
2646#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
2647#[cfg(test)]
2648mod tests {
2649 use super::*;
2650 use crate::dns::MockDnsResolver;
2651 use crate::tlog::MockTransparencyLogClient;
2652 use chrono::Utc;
2653 use uuid::Uuid;
2654
2655 const fn _assert_send_sync<T: Send + Sync>() {}
2658 const _: () = _assert_send_sync::<ServerVerifier>();
2659 const _: () = _assert_send_sync::<ClientVerifier>();
2660 const _: () = _assert_send_sync::<AnsVerifier>();
2661 const _: () = _assert_send_sync::<BadgeCache>();
2662
2663 fn create_test_badge(host: &str, version: &str, server_fp: &str, identity_fp: &str) -> Badge {
2664 serde_json::from_value(serde_json::json!({
2665 "status": "ACTIVE",
2666 "schemaVersion": "V1",
2667 "payload": {
2668 "logId": Uuid::new_v4().to_string(),
2669 "producer": {
2670 "event": {
2671 "ansId": Uuid::new_v4().to_string(),
2672 "ansName": format!("ans://{version}.{host}"),
2673 "eventType": "AGENT_REGISTERED",
2674 "agent": { "host": host, "name": "Test Agent", "version": version },
2675 "attestations": {
2676 "domainValidation": "ACME-DNS-01",
2677 "identityCert": { "fingerprint": identity_fp, "type": "X509-OV-CLIENT" },
2678 "serverCert": { "fingerprint": server_fp, "type": "X509-DV-SERVER" }
2679 },
2680 "expiresAt": (Utc::now() + chrono::Duration::days(365)).to_rfc3339(),
2681 "issuedAt": Utc::now().to_rfc3339(),
2682 "raId": "test-ra",
2683 "timestamp": Utc::now().to_rfc3339()
2684 },
2685 "keyId": "test-key",
2686 "signature": "test-sig"
2687 }
2688 }
2689 })).expect("test badge JSON should be valid")
2690 }
2691
2692 fn create_test_cert_identity(cn: &str, fingerprint: &str) -> CertIdentity {
2693 CertIdentity {
2694 common_name: Some(cn.to_string()),
2695 dns_sans: vec![cn.to_string()],
2696 uri_sans: vec![],
2697 fingerprint: CertFingerprint::parse(fingerprint).unwrap(),
2698 }
2699 }
2700
2701 #[tokio::test]
2702 async fn test_server_verification_success() {
2703 let host = "test.example.com";
2704 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2705
2706 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
2707 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2708
2709 let dns_record = BadgeRecord {
2710 format_version: "ans-badge1".to_string(),
2711 version: Some(Version::new(1, 0, 0)),
2712 url: badge_url.to_string(),
2713 };
2714
2715 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2716
2717 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
2718
2719 let verifier = ServerVerifier {
2720 dns_resolver,
2721 tlog_client,
2722 cache: None,
2723 failure_policy: FailurePolicy::FailClosed,
2724 dane_policy: DanePolicy::Disabled,
2725 dane_port: 443,
2726 trusted_ra_domains: None,
2727 };
2728
2729 let cert = create_test_cert_identity(host, fingerprint);
2730 let fqdn = Fqdn::new(host).unwrap();
2731
2732 let outcome = verifier.verify(&fqdn, &cert).await;
2733 assert!(outcome.is_success());
2734 }
2735
2736 #[tokio::test]
2737 async fn test_server_verification_not_ans_agent() {
2738 let dns_resolver = Arc::new(MockDnsResolver::new());
2739 let tlog_client = Arc::new(MockTransparencyLogClient::new());
2740
2741 let verifier = ServerVerifier {
2742 dns_resolver,
2743 tlog_client,
2744 cache: None,
2745 failure_policy: FailurePolicy::FailClosed,
2746 dane_policy: DanePolicy::Disabled,
2747 dane_port: 443,
2748 trusted_ra_domains: None,
2749 };
2750
2751 let cert = create_test_cert_identity(
2752 "unknown.example.com",
2753 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2754 );
2755 let fqdn = Fqdn::new("unknown.example.com").unwrap();
2756
2757 let outcome = verifier.verify(&fqdn, &cert).await;
2758 assert!(outcome.is_not_ans_agent());
2759 }
2760
2761 #[tokio::test]
2762 async fn test_server_verification_fingerprint_mismatch() {
2763 let host = "test.example.com";
2764 let badge_fingerprint =
2765 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2766 let cert_fingerprint =
2767 "SHA256:0000000000000000000000000000000000000000000000000000000000000000";
2768
2769 let badge = create_test_badge(host, "v1.0.0", badge_fingerprint, "SHA256:aaa");
2770 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2771
2772 let dns_record = BadgeRecord {
2773 format_version: "ans-badge1".to_string(),
2774 version: Some(Version::new(1, 0, 0)),
2775 url: badge_url.to_string(),
2776 };
2777
2778 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2779
2780 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
2781
2782 let verifier = ServerVerifier {
2783 dns_resolver,
2784 tlog_client,
2785 cache: None,
2786 failure_policy: FailurePolicy::FailClosed,
2787 dane_policy: DanePolicy::Disabled,
2788 dane_port: 443,
2789 trusted_ra_domains: None,
2790 };
2791
2792 let cert = create_test_cert_identity(host, cert_fingerprint);
2793 let fqdn = Fqdn::new(host).unwrap();
2794
2795 let outcome = verifier.verify(&fqdn, &cert).await;
2796 assert!(matches!(
2797 outcome,
2798 VerificationOutcome::FingerprintMismatch { .. }
2799 ));
2800 }
2801
2802 #[tokio::test]
2803 async fn test_server_verification_invalid_status() {
2804 let host = "test.example.com";
2805 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2806
2807 let mut badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
2808 badge.status = BadgeStatus::Revoked;
2809
2810 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2811
2812 let dns_record = BadgeRecord {
2813 format_version: "ans-badge1".to_string(),
2814 version: Some(Version::new(1, 0, 0)),
2815 url: badge_url.to_string(),
2816 };
2817
2818 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2819
2820 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
2821
2822 let verifier = ServerVerifier {
2823 dns_resolver,
2824 tlog_client,
2825 cache: None,
2826 failure_policy: FailurePolicy::FailClosed,
2827 dane_policy: DanePolicy::Disabled,
2828 dane_port: 443,
2829 trusted_ra_domains: None,
2830 };
2831
2832 let cert = create_test_cert_identity(host, fingerprint);
2833 let fqdn = Fqdn::new(host).unwrap();
2834
2835 let outcome = verifier.verify(&fqdn, &cert).await;
2836 assert!(matches!(
2837 outcome,
2838 VerificationOutcome::InvalidStatus {
2839 status: BadgeStatus::Revoked,
2840 ..
2841 }
2842 ));
2843 }
2844
2845 #[tokio::test]
2846 async fn test_verification_outcome_is_success() {
2847 let badge = create_test_badge(
2848 "test.example.com",
2849 "v1.0.0",
2850 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2851 "SHA256:aaa",
2852 );
2853
2854 let outcome = VerificationOutcome::Verified {
2855 badge,
2856 matched_fingerprint: CertFingerprint::parse(
2857 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2858 )
2859 .unwrap(),
2860 };
2861
2862 assert!(outcome.is_success());
2863 assert!(!outcome.is_not_ans_agent());
2864 }
2865
2866 #[tokio::test]
2867 async fn test_verification_with_cache() {
2868 let host = "test.example.com";
2869 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2870
2871 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
2872 let cache = Arc::new(BadgeCache::with_defaults());
2873 let fqdn = Fqdn::new(host).unwrap();
2874
2875 cache
2877 .insert_for_fqdn_version(&fqdn, &Version::new(1, 0, 0), badge)
2878 .await;
2879
2880 let dns_resolver = Arc::new(MockDnsResolver::new());
2882 let tlog_client = Arc::new(MockTransparencyLogClient::new());
2883
2884 let verifier = ServerVerifier {
2885 dns_resolver,
2886 tlog_client,
2887 cache: Some(cache),
2888 failure_policy: FailurePolicy::FailClosed,
2889 dane_policy: DanePolicy::Disabled,
2890 dane_port: 443,
2891 trusted_ra_domains: None,
2892 };
2893
2894 let cert = create_test_cert_identity(host, fingerprint);
2895
2896 let outcome = verifier.verify(&fqdn, &cert).await;
2897 assert!(outcome.is_success());
2898 }
2899
2900 #[test]
2901 fn test_cert_identity_from_components() {
2902 let fingerprint = CertFingerprint::parse(
2903 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2904 )
2905 .unwrap();
2906
2907 let identity = CertIdentity::new(
2908 Some("test.example.com".to_string()),
2909 vec!["test.example.com".to_string()],
2910 vec!["ans://v1.0.0.test.example.com".to_string()],
2911 fingerprint,
2912 );
2913
2914 assert_eq!(identity.fqdn(), Some("test.example.com"));
2915 assert!(identity.ans_name().is_some());
2916 assert_eq!(identity.version(), Some(Version::new(1, 0, 0)));
2917 }
2918
2919 #[test]
2920 fn test_cert_identity_from_fingerprint_and_cn() {
2921 let fingerprint = CertFingerprint::parse(
2922 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2923 )
2924 .unwrap();
2925
2926 let identity =
2927 CertIdentity::from_fingerprint_and_cn(fingerprint, "test.example.com".to_string());
2928
2929 assert_eq!(identity.fqdn(), Some("test.example.com"));
2930 assert!(identity.ans_name().is_none()); }
2932
2933 fn create_mtls_cert_identity(host: &str, version: &str, fingerprint: &str) -> CertIdentity {
2938 CertIdentity {
2939 common_name: Some(host.to_string()),
2940 dns_sans: vec![host.to_string()],
2941 uri_sans: vec![format!("ans://{}.{}", version, host)],
2942 fingerprint: CertFingerprint::parse(fingerprint).unwrap(),
2943 }
2944 }
2945
2946 #[tokio::test]
2947 async fn test_client_verification_success() {
2948 let host = "test.example.com";
2949 let version = "v1.0.0";
2950 let identity_fp = "SHA256:aebdc9da0c20d6d5e4999a773839095ed050a9d7252bf212056fddc0c38f3496";
2951 let server_fp = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
2952
2953 let badge = create_test_badge(host, version, server_fp, identity_fp);
2954 let badge_url = "https://tlog.example.com/v1/agents/test-id";
2955
2956 let dns_record = BadgeRecord {
2957 format_version: "ans-badge1".to_string(),
2958 version: Some(Version::new(1, 0, 0)),
2959 url: badge_url.to_string(),
2960 };
2961
2962 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
2963 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
2964
2965 let verifier = ClientVerifier {
2966 dns_resolver,
2967 tlog_client,
2968 cache: None,
2969 failure_policy: FailurePolicy::FailClosed,
2970 trusted_ra_domains: None,
2971 };
2972
2973 let cert = create_mtls_cert_identity(host, version, identity_fp);
2974 let outcome = verifier.verify(&cert).await;
2975
2976 assert!(outcome.is_success(), "Expected success, got: {:?}", outcome);
2977 }
2978
2979 #[tokio::test]
2980 async fn test_client_verification_no_fqdn() {
2981 let dns_resolver = Arc::new(MockDnsResolver::new());
2982 let tlog_client = Arc::new(MockTransparencyLogClient::new());
2983
2984 let verifier = ClientVerifier {
2985 dns_resolver,
2986 tlog_client,
2987 cache: None,
2988 failure_policy: FailurePolicy::FailClosed,
2989 trusted_ra_domains: None,
2990 };
2991
2992 let cert = CertIdentity {
2994 common_name: None,
2995 dns_sans: vec![],
2996 uri_sans: vec!["ans://v1.0.0.test.example.com".to_string()],
2997 fingerprint: CertFingerprint::parse(
2998 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
2999 )
3000 .unwrap(),
3001 };
3002
3003 let outcome = verifier.verify(&cert).await;
3004 assert!(matches!(outcome, VerificationOutcome::CertError(_)));
3005 }
3006
3007 #[tokio::test]
3008 async fn test_client_verification_no_ans_name() {
3009 let dns_resolver = Arc::new(MockDnsResolver::new());
3010 let tlog_client = Arc::new(MockTransparencyLogClient::new());
3011
3012 let verifier = ClientVerifier {
3013 dns_resolver,
3014 tlog_client,
3015 cache: None,
3016 failure_policy: FailurePolicy::FailClosed,
3017 trusted_ra_domains: None,
3018 };
3019
3020 let cert = CertIdentity {
3022 common_name: Some("test.example.com".to_string()),
3023 dns_sans: vec!["test.example.com".to_string()],
3024 uri_sans: vec![],
3025 fingerprint: CertFingerprint::parse(
3026 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
3027 )
3028 .unwrap(),
3029 };
3030
3031 let outcome = verifier.verify(&cert).await;
3032 assert!(matches!(outcome, VerificationOutcome::CertError(_)));
3033 }
3034
3035 #[tokio::test]
3036 async fn test_client_verification_fingerprint_mismatch() {
3037 let host = "test.example.com";
3038 let version = "v1.0.0";
3039 let badge_identity_fp =
3040 "SHA256:aebdc9da0c20d6d5e4999a773839095ed050a9d7252bf212056fddc0c38f3496";
3041 let cert_identity_fp =
3042 "SHA256:0000000000000000000000000000000000000000000000000000000000000000";
3043
3044 let badge = create_test_badge(host, version, "SHA256:server", badge_identity_fp);
3045 let badge_url = "https://tlog.example.com/v1/agents/test-id";
3046
3047 let dns_record = BadgeRecord {
3048 format_version: "ans-badge1".to_string(),
3049 version: Some(Version::new(1, 0, 0)),
3050 url: badge_url.to_string(),
3051 };
3052
3053 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3054 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3055
3056 let verifier = ClientVerifier {
3057 dns_resolver,
3058 tlog_client,
3059 cache: None,
3060 failure_policy: FailurePolicy::FailClosed,
3061 trusted_ra_domains: None,
3062 };
3063
3064 let cert = create_mtls_cert_identity(host, version, cert_identity_fp);
3065 let outcome = verifier.verify(&cert).await;
3066
3067 assert!(matches!(
3068 outcome,
3069 VerificationOutcome::FingerprintMismatch { .. }
3070 ));
3071 }
3072
3073 #[tokio::test]
3074 async fn test_client_verification_ans_name_mismatch() {
3075 let host = "test.example.com";
3076 let badge_version = "v1.0.0";
3077 let cert_version = "v2.0.0";
3078 let identity_fp = "SHA256:aebdc9da0c20d6d5e4999a773839095ed050a9d7252bf212056fddc0c38f3496";
3079
3080 let badge = create_test_badge(host, badge_version, "SHA256:server", identity_fp);
3082 let badge_url = "https://tlog.example.com/v1/agents/test-id";
3083
3084 let dns_record = BadgeRecord {
3085 format_version: "ans-badge1".to_string(),
3086 version: Some(Version::new(2, 0, 0)),
3087 url: badge_url.to_string(),
3088 };
3089
3090 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3091 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3092
3093 let verifier = ClientVerifier {
3094 dns_resolver,
3095 tlog_client,
3096 cache: None,
3097 failure_policy: FailurePolicy::FailClosed,
3098 trusted_ra_domains: None,
3099 };
3100
3101 let cert = create_mtls_cert_identity(host, cert_version, identity_fp);
3102 let outcome = verifier.verify(&cert).await;
3103
3104 assert!(matches!(
3105 outcome,
3106 VerificationOutcome::AnsNameMismatch { .. }
3107 ));
3108 }
3109
3110 #[test]
3115 fn test_verification_outcome_badge() {
3116 let badge = create_test_badge(
3117 "test.example.com",
3118 "v1.0.0",
3119 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
3120 "SHA256:aaa",
3121 );
3122
3123 let outcome = VerificationOutcome::Verified {
3125 badge: badge.clone(),
3126 matched_fingerprint: CertFingerprint::parse(
3127 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
3128 )
3129 .unwrap(),
3130 };
3131 assert!(outcome.badge().is_some());
3132
3133 let outcome = VerificationOutcome::InvalidStatus {
3135 status: BadgeStatus::Revoked,
3136 badge: badge.clone(),
3137 };
3138 assert!(outcome.badge().is_some());
3139
3140 let outcome = VerificationOutcome::FingerprintMismatch {
3142 expected: "SHA256:a".to_string(),
3143 actual: "SHA256:b".to_string(),
3144 badge: badge.clone(),
3145 };
3146 assert!(outcome.badge().is_some());
3147
3148 let outcome = VerificationOutcome::HostnameMismatch {
3150 expected: "a.com".to_string(),
3151 actual: "b.com".to_string(),
3152 badge: badge.clone(),
3153 };
3154 assert!(outcome.badge().is_some());
3155
3156 let outcome = VerificationOutcome::AnsNameMismatch {
3158 expected: "ans://v1.0.0.a.com".to_string(),
3159 actual: "ans://v2.0.0.a.com".to_string(),
3160 badge,
3161 };
3162 assert!(outcome.badge().is_some());
3163
3164 let outcome = VerificationOutcome::NotAnsAgent {
3166 fqdn: "test.com".to_string(),
3167 };
3168 assert!(outcome.badge().is_none());
3169
3170 let outcome = VerificationOutcome::DnsError(DnsError::NotFound {
3172 fqdn: "test.com".to_string(),
3173 });
3174 assert!(outcome.badge().is_none());
3175 }
3176
3177 #[test]
3178 fn test_verification_outcome_into_result() {
3179 let badge = create_test_badge(
3180 "test.example.com",
3181 "v1.0.0",
3182 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
3183 "SHA256:aaa",
3184 );
3185
3186 let outcome = VerificationOutcome::Verified {
3188 badge: badge.clone(),
3189 matched_fingerprint: CertFingerprint::parse(
3190 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
3191 )
3192 .unwrap(),
3193 };
3194 assert!(outcome.into_result().is_ok());
3195
3196 let outcome = VerificationOutcome::NotAnsAgent {
3198 fqdn: "test.com".to_string(),
3199 };
3200 assert!(outcome.into_result().is_err());
3201
3202 let outcome = VerificationOutcome::InvalidStatus {
3204 status: BadgeStatus::Revoked,
3205 badge: badge.clone(),
3206 };
3207 assert!(outcome.into_result().is_err());
3208
3209 let outcome = VerificationOutcome::FingerprintMismatch {
3211 expected: "a".to_string(),
3212 actual: "b".to_string(),
3213 badge: badge.clone(),
3214 };
3215 assert!(outcome.into_result().is_err());
3216
3217 let outcome = VerificationOutcome::HostnameMismatch {
3219 expected: "a.com".to_string(),
3220 actual: "b.com".to_string(),
3221 badge: badge.clone(),
3222 };
3223 assert!(outcome.into_result().is_err());
3224
3225 let outcome = VerificationOutcome::AnsNameMismatch {
3227 expected: "a".to_string(),
3228 actual: "b".to_string(),
3229 badge,
3230 };
3231 assert!(outcome.into_result().is_err());
3232
3233 let outcome = VerificationOutcome::DnsError(DnsError::NotFound {
3235 fqdn: "test.com".to_string(),
3236 });
3237 assert!(outcome.into_result().is_err());
3238
3239 let outcome = VerificationOutcome::TlogError(TlogError::ServiceUnavailable);
3241 assert!(outcome.into_result().is_err());
3242
3243 let outcome = VerificationOutcome::DaneError(DaneError::FingerprintMismatch);
3245 assert!(outcome.into_result().is_err());
3246 }
3247
3248 #[tokio::test]
3253 async fn test_server_verification_hostname_mismatch() {
3254 let badge_host = "badge.example.com";
3255 let cert_host = "different.example.com";
3256 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3257
3258 let badge = create_test_badge(badge_host, "v1.0.0", fingerprint, "SHA256:aaa");
3259 let badge_url = "https://tlog.example.com/v1/agents/test-id";
3260
3261 let dns_record = BadgeRecord {
3262 format_version: "ans-badge1".to_string(),
3263 version: Some(Version::new(1, 0, 0)),
3264 url: badge_url.to_string(),
3265 };
3266
3267 let dns_resolver =
3269 Arc::new(MockDnsResolver::new().with_records(cert_host, vec![dns_record]));
3270 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3271
3272 let verifier = ServerVerifier {
3273 dns_resolver,
3274 tlog_client,
3275 cache: None,
3276 failure_policy: FailurePolicy::FailClosed,
3277 dane_policy: DanePolicy::Disabled,
3278 dane_port: 443,
3279 trusted_ra_domains: None,
3280 };
3281
3282 let cert = create_test_cert_identity(cert_host, fingerprint);
3283 let fqdn = Fqdn::new(cert_host).unwrap();
3284
3285 let outcome = verifier.verify(&fqdn, &cert).await;
3286 assert!(
3287 matches!(outcome, VerificationOutcome::HostnameMismatch { .. }),
3288 "Expected HostnameMismatch, got: {:?}",
3289 outcome
3290 );
3291 }
3292
3293 #[tokio::test]
3298 async fn test_server_verifier_prefetch_success() {
3299 let host = "test.example.com";
3300 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3301
3302 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
3303 let badge_url = "https://tlog.example.com/v1/agents/test-id";
3304
3305 let dns_record = BadgeRecord {
3306 format_version: "ans-badge1".to_string(),
3307 version: Some(Version::new(1, 0, 0)),
3308 url: badge_url.to_string(),
3309 };
3310
3311 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3312 let tlog_client =
3313 Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge.clone()));
3314
3315 let verifier = ServerVerifier {
3316 dns_resolver,
3317 tlog_client,
3318 cache: Some(Arc::new(BadgeCache::with_defaults())),
3319 failure_policy: FailurePolicy::FailClosed,
3320 dane_policy: DanePolicy::Disabled,
3321 dane_port: 443,
3322 trusted_ra_domains: None,
3323 };
3324
3325 let fqdn = Fqdn::new(host).unwrap();
3326 let result = verifier.prefetch(&fqdn).await;
3327
3328 assert!(result.is_ok());
3329 assert_eq!(result.unwrap().agent_host(), host);
3330 }
3331
3332 #[tokio::test]
3333 async fn test_server_verifier_prefetch_not_found() {
3334 let dns_resolver = Arc::new(MockDnsResolver::new());
3335 let tlog_client = Arc::new(MockTransparencyLogClient::new());
3336
3337 let verifier = ServerVerifier {
3338 dns_resolver,
3339 tlog_client,
3340 cache: None,
3341 failure_policy: FailurePolicy::FailClosed,
3342 dane_policy: DanePolicy::Disabled,
3343 dane_port: 443,
3344 trusted_ra_domains: None,
3345 };
3346
3347 let fqdn = Fqdn::new("unknown.example.com").unwrap();
3348 let result = verifier.prefetch(&fqdn).await;
3349
3350 assert!(result.is_err());
3351 assert!(matches!(result.unwrap_err(), AnsError::Dns(_)));
3352 }
3353
3354 #[tokio::test]
3359 async fn test_failure_policy_fail_open_with_cache_no_cache() {
3360 let dns_resolver = Arc::new(MockDnsResolver::new().with_error(
3361 "test.example.com",
3362 DnsError::LookupFailed {
3363 fqdn: "test.example.com".to_string(),
3364 reason: "timeout".to_string(),
3365 },
3366 ));
3367 let tlog_client = Arc::new(MockTransparencyLogClient::new());
3368
3369 let verifier = ServerVerifier {
3370 dns_resolver,
3371 tlog_client,
3372 cache: Some(Arc::new(BadgeCache::with_defaults())),
3373 failure_policy: FailurePolicy::FailOpenWithCache {
3374 max_staleness: Duration::from_secs(600),
3375 },
3376 dane_policy: DanePolicy::Disabled,
3377 dane_port: 443,
3378 trusted_ra_domains: None,
3379 };
3380
3381 let cert = create_test_cert_identity(
3382 "test.example.com",
3383 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
3384 );
3385 let fqdn = Fqdn::new("test.example.com").unwrap();
3386
3387 let outcome = verifier.verify(&fqdn, &cert).await;
3388 assert!(matches!(outcome, VerificationOutcome::DnsError(_)));
3390 }
3391
3392 #[tokio::test]
3393 async fn test_failure_policy_fail_open_with_cache_uses_cache() {
3394 let host = "test.example.com";
3395 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3396
3397 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
3398 let cache = Arc::new(BadgeCache::with_defaults());
3399 let fqdn = Fqdn::new(host).unwrap();
3400
3401 cache
3403 .insert_for_fqdn_version(&fqdn, &Version::new(1, 0, 0), badge)
3404 .await;
3405
3406 let dns_resolver = Arc::new(MockDnsResolver::new().with_error(
3407 host,
3408 DnsError::LookupFailed {
3409 fqdn: host.to_string(),
3410 reason: "timeout".to_string(),
3411 },
3412 ));
3413 let tlog_client = Arc::new(MockTransparencyLogClient::new());
3414
3415 let verifier = ServerVerifier {
3416 dns_resolver,
3417 tlog_client,
3418 cache: Some(cache),
3419 failure_policy: FailurePolicy::FailOpenWithCache {
3420 max_staleness: Duration::from_secs(600),
3421 },
3422 dane_policy: DanePolicy::Disabled,
3423 dane_port: 443,
3424 trusted_ra_domains: None,
3425 };
3426
3427 let cert = create_test_cert_identity(host, fingerprint);
3428
3429 let outcome = verifier.verify(&fqdn, &cert).await;
3430 assert!(
3432 outcome.is_success(),
3433 "Expected success with cache, got: {:?}",
3434 outcome
3435 );
3436 }
3437
3438 #[test]
3439 fn test_cert_identity_from_der_server_cert() {
3440 use rcgen::{CertificateParams, DnType, ExtendedKeyUsagePurpose, KeyPair, SanType};
3441
3442 let key_pair = KeyPair::generate().unwrap();
3444 let mut params = CertificateParams::default();
3445 params
3446 .distinguished_name
3447 .push(DnType::CommonName, "test.agent.local");
3448 params.subject_alt_names.push(SanType::DnsName(
3449 "test.agent.local".to_string().try_into().unwrap(),
3450 ));
3451 params.subject_alt_names.push(SanType::URI(
3452 "ans://v1.0.0.test.agent.local".try_into().unwrap(),
3453 ));
3454 params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth];
3455
3456 let cert = params.self_signed(&key_pair).unwrap();
3457 let der = cert.der();
3458
3459 let identity = CertIdentity::from_der(der).expect("should parse DER certificate");
3460
3461 assert_eq!(
3463 identity.common_name.as_deref(),
3464 Some("test.agent.local"),
3465 "CN should be test.agent.local"
3466 );
3467
3468 assert!(
3470 identity.dns_sans.contains(&"test.agent.local".to_string()),
3471 "DNS SANs should contain test.agent.local, got: {:?}",
3472 identity.dns_sans
3473 );
3474
3475 assert!(
3477 identity
3478 .uri_sans
3479 .contains(&"ans://v1.0.0.test.agent.local".to_string()),
3480 "URI SANs should contain ans://v1.0.0.test.agent.local, got: {:?}",
3481 identity.uri_sans
3482 );
3483
3484 let expected_fp = CertFingerprint::from_der(der);
3486 assert_eq!(
3487 identity.fingerprint, expected_fp,
3488 "Fingerprint should match computed fingerprint from same DER"
3489 );
3490
3491 assert_eq!(identity.fqdn(), Some("test.agent.local"));
3493 let ans_name = identity.ans_name().expect("should have ANS name");
3494 assert_eq!(ans_name.fqdn().as_str(), "test.agent.local");
3495 assert_eq!(identity.version(), Some(Version::new(1, 0, 0)));
3496 }
3497
3498 #[test]
3499 fn test_cert_identity_from_der_client_cert() {
3500 use rcgen::{CertificateParams, DnType, ExtendedKeyUsagePurpose, KeyPair, SanType};
3501
3502 let key_pair = KeyPair::generate().unwrap();
3504 let mut params = CertificateParams::default();
3505 params
3506 .distinguished_name
3507 .push(DnType::CommonName, "test.agent.local");
3508 params.subject_alt_names.push(SanType::DnsName(
3509 "test.agent.local".to_string().try_into().unwrap(),
3510 ));
3511 params.subject_alt_names.push(SanType::URI(
3512 "ans://v1.0.0.test.agent.local".try_into().unwrap(),
3513 ));
3514 params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth];
3515
3516 let cert = params.self_signed(&key_pair).unwrap();
3517 let der = cert.der();
3518
3519 let identity = CertIdentity::from_der(der).expect("should parse DER certificate");
3520
3521 assert_eq!(identity.common_name.as_deref(), Some("test.agent.local"));
3522 assert!(identity.dns_sans.contains(&"test.agent.local".to_string()));
3523 assert!(
3524 identity
3525 .uri_sans
3526 .contains(&"ans://v1.0.0.test.agent.local".to_string())
3527 );
3528
3529 let expected_fp = CertFingerprint::from_der(der);
3530 assert_eq!(identity.fingerprint, expected_fp);
3531 }
3532
3533 #[test]
3534 fn test_cert_identity_from_der_invalid_bytes() {
3535 let result = CertIdentity::from_der(b"not a certificate");
3536 assert!(result.is_err(), "Should fail on invalid DER bytes");
3537 }
3538
3539 #[tokio::test]
3540 async fn test_server_verifier_builder_dane_policy() {
3541 let dns = Arc::new(MockDnsResolver::new());
3542 let tlog = Arc::new(MockTransparencyLogClient::new());
3543
3544 let verifier = ServerVerifier::builder()
3546 .dns_resolver(dns.clone())
3547 .tlog_client(tlog.clone())
3548 .with_dane_if_present()
3549 .build()
3550 .await
3551 .unwrap();
3552 assert_eq!(verifier.dane_policy, DanePolicy::ValidateIfPresent);
3553
3554 let verifier = ServerVerifier::builder()
3556 .dns_resolver(dns.clone())
3557 .tlog_client(tlog.clone())
3558 .require_dane()
3559 .build()
3560 .await
3561 .unwrap();
3562 assert_eq!(verifier.dane_policy, DanePolicy::Required);
3563
3564 let verifier = ServerVerifier::builder()
3566 .dns_resolver(dns.clone())
3567 .tlog_client(tlog.clone())
3568 .dane_policy(DanePolicy::Disabled)
3569 .build()
3570 .await
3571 .unwrap();
3572 assert_eq!(verifier.dane_policy, DanePolicy::Disabled);
3573 }
3574
3575 #[tokio::test]
3576 async fn test_server_verifier_builder_dane_port() {
3577 let dns = Arc::new(MockDnsResolver::new());
3578 let tlog = Arc::new(MockTransparencyLogClient::new());
3579
3580 let verifier = ServerVerifier::builder()
3582 .dns_resolver(dns.clone())
3583 .tlog_client(tlog.clone())
3584 .build()
3585 .await
3586 .unwrap();
3587 assert_eq!(verifier.dane_port, 443);
3588
3589 let verifier = ServerVerifier::builder()
3591 .dns_resolver(dns.clone())
3592 .tlog_client(tlog.clone())
3593 .dane_port(8443)
3594 .build()
3595 .await
3596 .unwrap();
3597 assert_eq!(verifier.dane_port, 8443);
3598 }
3599
3600 #[tokio::test]
3601 async fn test_server_verifier_builder_failure_policy() {
3602 let dns = Arc::new(MockDnsResolver::new());
3603 let tlog = Arc::new(MockTransparencyLogClient::new());
3604
3605 let verifier = ServerVerifier::builder()
3606 .dns_resolver(dns)
3607 .tlog_client(tlog)
3608 .failure_policy(FailurePolicy::FailClosed)
3609 .build()
3610 .await
3611 .unwrap();
3612 assert!(matches!(verifier.failure_policy, FailurePolicy::FailClosed));
3613 }
3614
3615 #[tokio::test]
3620 async fn test_server_verification_refresh_on_mismatch_succeeds() {
3621 let host = "test.example.com";
3622 let old_fp = "SHA256:0000000000000000000000000000000000000000000000000000000000000000";
3623 let new_fp = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3624
3625 let badge_url = "https://tlog.example.com/v1/agents/test-id";
3628 let updated_badge = create_test_badge(host, "v1.0.0", new_fp, "SHA256:aaa");
3629
3630 let dns_record = BadgeRecord {
3631 format_version: "ans-badge1".to_string(),
3632 version: Some(Version::new(1, 0, 0)),
3633 url: badge_url.to_string(),
3634 };
3635
3636 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3637 let tlog_client =
3639 Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, updated_badge));
3640
3641 let cache = Arc::new(BadgeCache::with_defaults());
3642 let fqdn = Fqdn::new(host).unwrap();
3643
3644 let stale_badge = create_test_badge(host, "v1.0.0", old_fp, "SHA256:aaa");
3646 cache
3647 .insert_for_fqdn_version(&fqdn, &Version::new(1, 0, 0), stale_badge)
3648 .await;
3649
3650 let verifier = ServerVerifier {
3651 dns_resolver,
3652 tlog_client,
3653 cache: Some(cache),
3654 failure_policy: FailurePolicy::FailClosed,
3655 dane_policy: DanePolicy::Disabled,
3656 dane_port: 443,
3657 trusted_ra_domains: None,
3658 };
3659
3660 let cert = create_test_cert_identity(host, new_fp);
3662 let outcome = verifier.verify(&fqdn, &cert).await;
3663 assert!(
3664 outcome.is_success(),
3665 "Expected success after refresh, got: {:?}",
3666 outcome
3667 );
3668 }
3669
3670 #[tokio::test]
3671 async fn test_server_verification_refresh_on_mismatch_still_fails() {
3672 let host = "test.example.com";
3673 let badge_fp = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3674 let cert_fp = "SHA256:0000000000000000000000000000000000000000000000000000000000000000";
3675
3676 let badge_url = "https://tlog.example.com/v1/agents/test-id";
3677 let badge = create_test_badge(host, "v1.0.0", badge_fp, "SHA256:aaa");
3679
3680 let dns_record = BadgeRecord {
3681 format_version: "ans-badge1".to_string(),
3682 version: Some(Version::new(1, 0, 0)),
3683 url: badge_url.to_string(),
3684 };
3685
3686 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3687 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3688
3689 let verifier = ServerVerifier {
3690 dns_resolver,
3691 tlog_client,
3692 cache: None,
3693 failure_policy: FailurePolicy::FailClosed,
3694 dane_policy: DanePolicy::Disabled,
3695 dane_port: 443,
3696 trusted_ra_domains: None,
3697 };
3698
3699 let cert = create_test_cert_identity(host, cert_fp);
3700 let fqdn = Fqdn::new(host).unwrap();
3701
3702 let outcome = verifier.verify(&fqdn, &cert).await;
3703 assert!(
3704 matches!(outcome, VerificationOutcome::FingerprintMismatch { .. }),
3705 "Expected FingerprintMismatch after refresh still fails, got: {:?}",
3706 outcome
3707 );
3708 }
3709
3710 #[tokio::test]
3711 async fn test_client_verification_refresh_on_mismatch_succeeds() {
3712 let host = "test.example.com";
3713 let version = "v1.0.0";
3714 let old_identity_fp =
3715 "SHA256:0000000000000000000000000000000000000000000000000000000000000000";
3716 let new_identity_fp =
3717 "SHA256:aebdc9da0c20d6d5e4999a773839095ed050a9d7252bf212056fddc0c38f3496";
3718 let server_fp = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3719
3720 let badge_url = "https://tlog.example.com/v1/agents/test-id";
3721 let updated_badge = create_test_badge(host, version, server_fp, new_identity_fp);
3723
3724 let dns_record = BadgeRecord {
3725 format_version: "ans-badge1".to_string(),
3726 version: Some(Version::new(1, 0, 0)),
3727 url: badge_url.to_string(),
3728 };
3729
3730 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3731 let tlog_client =
3732 Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, updated_badge));
3733
3734 let cache = Arc::new(BadgeCache::with_defaults());
3735 let fqdn = Fqdn::new(host).unwrap();
3736 let ver = Version::new(1, 0, 0);
3737
3738 let stale_badge = create_test_badge(host, version, server_fp, old_identity_fp);
3740 cache
3741 .insert_for_fqdn_version(&fqdn, &ver, stale_badge)
3742 .await;
3743
3744 let verifier = ClientVerifier {
3745 dns_resolver,
3746 tlog_client,
3747 cache: Some(cache),
3748 failure_policy: FailurePolicy::FailClosed,
3749 trusted_ra_domains: None,
3750 };
3751
3752 let cert = create_mtls_cert_identity(host, version, new_identity_fp);
3754 let outcome = verifier.verify(&cert).await;
3755 assert!(
3756 outcome.is_success(),
3757 "Expected success after client refresh, got: {:?}",
3758 outcome
3759 );
3760 }
3761
3762 #[test]
3767 fn test_validate_badge_domain_unit_allows_when_none() {
3768 assert!(validate_badge_domain(None, "https://tlog.example.com/v1/agents/test").is_ok());
3769 }
3770
3771 #[test]
3772 fn test_validate_badge_domain_unit_allows_trusted() {
3773 let trusted: HashSet<String> = ["tlog.example.com".to_string()].into();
3774 assert!(
3775 validate_badge_domain(Some(&trusted), "https://tlog.example.com/v1/agents/test")
3776 .is_ok()
3777 );
3778 }
3779
3780 #[test]
3781 fn test_validate_badge_domain_unit_rejects_untrusted() {
3782 let trusted: HashSet<String> = ["tlog.example.com".to_string()].into();
3783 let err = validate_badge_domain(Some(&trusted), "https://evil.attacker.com/v1/agents/test")
3784 .unwrap_err();
3785 assert!(
3786 matches!(err, TlogError::UntrustedDomain { domain, .. } if domain == "evil.attacker.com")
3787 );
3788 }
3789
3790 #[test]
3791 fn test_validate_badge_domain_unit_multiple_trusted() {
3792 let trusted: HashSet<String> = [
3793 "tlog1.example.com".to_string(),
3794 "tlog2.example.com".to_string(),
3795 ]
3796 .into();
3797 assert!(validate_badge_domain(Some(&trusted), "https://tlog1.example.com/badge").is_ok());
3798 assert!(validate_badge_domain(Some(&trusted), "https://tlog2.example.com/badge").is_ok());
3799 assert!(validate_badge_domain(Some(&trusted), "https://tlog3.example.com/badge").is_err());
3800 }
3801
3802 #[tokio::test]
3803 async fn test_trusted_ra_none_allows_all() {
3804 let host = "test.example.com";
3805 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3806 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
3807 let badge_url = "https://any-domain.example.com/v1/agents/test-id";
3808
3809 let dns_record = BadgeRecord {
3810 format_version: "ans-badge1".to_string(),
3811 version: Some(Version::new(1, 0, 0)),
3812 url: badge_url.to_string(),
3813 };
3814 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3815 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3816
3817 let verifier = ServerVerifier {
3818 dns_resolver,
3819 tlog_client,
3820 cache: None,
3821 failure_policy: FailurePolicy::FailClosed,
3822 dane_policy: DanePolicy::Disabled,
3823 dane_port: 443,
3824 trusted_ra_domains: None,
3825 };
3826
3827 let cert = create_test_cert_identity(host, fingerprint);
3828 let fqdn = Fqdn::new(host).unwrap();
3829 let outcome = verifier.verify(&fqdn, &cert).await;
3830 assert!(outcome.is_success(), "None should allow all domains");
3831 }
3832
3833 #[tokio::test]
3834 async fn test_trusted_ra_allows_trusted_domain() {
3835 let host = "test.example.com";
3836 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3837 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
3838 let badge_url = "https://tlog.example.com/v1/agents/test-id";
3839
3840 let dns_record = BadgeRecord {
3841 format_version: "ans-badge1".to_string(),
3842 version: Some(Version::new(1, 0, 0)),
3843 url: badge_url.to_string(),
3844 };
3845 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3846 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3847
3848 let verifier = ServerVerifier {
3849 dns_resolver,
3850 tlog_client,
3851 cache: None,
3852 failure_policy: FailurePolicy::FailClosed,
3853 dane_policy: DanePolicy::Disabled,
3854 dane_port: 443,
3855 trusted_ra_domains: Some(["tlog.example.com".to_string()].into()),
3856 };
3857
3858 let cert = create_test_cert_identity(host, fingerprint);
3859 let fqdn = Fqdn::new(host).unwrap();
3860 let outcome = verifier.verify(&fqdn, &cert).await;
3861 assert!(outcome.is_success(), "Trusted domain should succeed");
3862 }
3863
3864 #[tokio::test]
3865 async fn test_trusted_ra_rejects_untrusted_domain() {
3866 let host = "test.example.com";
3867 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3868 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
3869 let badge_url = "https://evil.attacker.com/v1/agents/test-id";
3870
3871 let dns_record = BadgeRecord {
3872 format_version: "ans-badge1".to_string(),
3873 version: Some(Version::new(1, 0, 0)),
3874 url: badge_url.to_string(),
3875 };
3876 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3877 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3878
3879 let verifier = ServerVerifier {
3880 dns_resolver,
3881 tlog_client,
3882 cache: None,
3883 failure_policy: FailurePolicy::FailClosed,
3884 dane_policy: DanePolicy::Disabled,
3885 dane_port: 443,
3886 trusted_ra_domains: Some(["tlog.example.com".to_string()].into()),
3887 };
3888
3889 let cert = create_test_cert_identity(host, fingerprint);
3890 let fqdn = Fqdn::new(host).unwrap();
3891 let outcome = verifier.verify(&fqdn, &cert).await;
3892 assert!(
3893 matches!(
3894 outcome,
3895 VerificationOutcome::TlogError(TlogError::UntrustedDomain { .. })
3896 ),
3897 "Untrusted domain should be rejected, got: {:?}",
3898 outcome
3899 );
3900 }
3901
3902 #[tokio::test]
3903 async fn test_trusted_ra_client_rejects_untrusted() {
3904 let host = "test.example.com";
3905 let version = "v1.0.0";
3906 let identity_fp = "SHA256:aebdc9da0c20d6d5e4999a773839095ed050a9d7252bf212056fddc0c38f3496";
3907 let server_fp = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
3908 let badge = create_test_badge(host, version, server_fp, identity_fp);
3909 let badge_url = "https://evil.attacker.com/v1/agents/test-id";
3910
3911 let dns_record = BadgeRecord {
3912 format_version: "ans-badge1".to_string(),
3913 version: Some(Version::new(1, 0, 0)),
3914 url: badge_url.to_string(),
3915 };
3916 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
3917 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
3918
3919 let verifier = ClientVerifier {
3920 dns_resolver,
3921 tlog_client,
3922 cache: None,
3923 failure_policy: FailurePolicy::FailClosed,
3924 trusted_ra_domains: Some(["tlog.example.com".to_string()].into()),
3925 };
3926
3927 let cert = create_mtls_cert_identity(host, version, identity_fp);
3928 let outcome = verifier.verify(&cert).await;
3929 assert!(
3930 matches!(
3931 outcome,
3932 VerificationOutcome::TlogError(TlogError::UntrustedDomain { .. })
3933 ),
3934 "Client verifier should reject untrusted domain, got: {:?}",
3935 outcome
3936 );
3937 }
3938
3939 #[tokio::test]
3940 async fn test_trusted_ra_builder_propagation() {
3941 let dns_resolver = Arc::new(MockDnsResolver::new());
3942 let tlog_client = Arc::new(MockTransparencyLogClient::new());
3943
3944 let verifier = ServerVerifier::builder()
3945 .dns_resolver(dns_resolver as Arc<dyn DnsResolver>)
3946 .tlog_client(tlog_client as Arc<dyn TransparencyLogClient>)
3947 .trusted_ra_domains(["tlog.example.com", "tlog2.example.com"])
3948 .build()
3949 .await
3950 .unwrap();
3951
3952 let trusted = verifier.trusted_ra_domains.as_ref().unwrap();
3954 assert!(trusted.contains("tlog.example.com"));
3955 assert!(trusted.contains("tlog2.example.com"));
3956 assert_eq!(trusted.len(), 2);
3957 }
3958
3959 #[test]
3964 fn test_outcome_into_result_cert_error() {
3965 let outcome =
3966 VerificationOutcome::CertError(CryptoError::ParseFailed("bad cert".to_string()));
3967 let err = outcome.into_result().unwrap_err();
3968 assert!(matches!(err, AnsError::Certificate(_)));
3969 }
3970
3971 #[test]
3972 fn test_outcome_into_result_parse_error() {
3973 let outcome = VerificationOutcome::ParseError(ans_types::ParseError::InvalidFqdn(
3974 "bad fqdn".to_string(),
3975 ));
3976 let err = outcome.into_result().unwrap_err();
3977 assert!(matches!(err, AnsError::Parse(_)));
3978 }
3979
3980 #[test]
3981 fn test_outcome_into_result_dane_error() {
3982 let outcome = VerificationOutcome::DaneError(DaneError::FingerprintMismatch);
3983 let err = outcome.into_result().unwrap_err();
3984 assert!(matches!(
3985 err,
3986 AnsError::Verification(VerificationError::DaneVerificationFailed(_))
3987 ));
3988 }
3989
3990 #[test]
3991 fn test_outcome_into_result_dns_error() {
3992 let outcome = VerificationOutcome::DnsError(DnsError::Timeout {
3993 fqdn: "test.example.com".to_string(),
3994 });
3995 let err = outcome.into_result().unwrap_err();
3996 assert!(matches!(err, AnsError::Dns(DnsError::Timeout { .. })));
3997 }
3998
3999 #[test]
4000 fn test_outcome_into_result_tlog_error() {
4001 let outcome = VerificationOutcome::TlogError(TlogError::ServiceUnavailable);
4002 let err = outcome.into_result().unwrap_err();
4003 assert!(matches!(
4004 err,
4005 AnsError::TransparencyLog(TlogError::ServiceUnavailable)
4006 ));
4007 }
4008
4009 #[tokio::test]
4014 async fn test_builder_dns_cloudflare() {
4015 let dns = Arc::new(MockDnsResolver::new());
4016 let tlog = Arc::new(MockTransparencyLogClient::new());
4017
4018 let verifier = AnsVerifier::builder()
4020 .dns_resolver(dns as Arc<dyn DnsResolver>)
4021 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4022 .dns_cloudflare() .build()
4024 .await
4025 .unwrap();
4026
4027 let dbg = format!("{verifier:?}");
4029 assert!(dbg.contains("AnsVerifier"));
4030 }
4031
4032 #[tokio::test]
4033 async fn test_builder_dns_nameservers() {
4034 let tlog = Arc::new(MockTransparencyLogClient::new());
4035
4036 let verifier = AnsVerifier::builder()
4037 .dns_nameservers(&[std::net::Ipv4Addr::new(1, 1, 1, 1)])
4038 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4039 .build()
4040 .await
4041 .unwrap();
4042
4043 let dbg = format!("{verifier:?}");
4044 assert!(dbg.contains("AnsVerifier"));
4045 }
4046
4047 #[tokio::test]
4048 async fn test_builder_dns_preset_path() {
4049 let tlog = Arc::new(MockTransparencyLogClient::new());
4050
4051 let verifier = AnsVerifier::builder()
4052 .dns_preset(DnsResolverConfig::Cloudflare)
4053 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4054 .build()
4055 .await
4056 .unwrap();
4057
4058 let dbg = format!("{verifier:?}");
4059 assert!(dbg.contains("AnsVerifier"));
4060 }
4061
4062 #[cfg(feature = "rustls")]
4067 #[tokio::test]
4068 async fn test_client_cert_verifier_without_pem() {
4069 let _ = rustls::crypto::ring::default_provider().install_default();
4070 let dns = Arc::new(MockDnsResolver::new());
4071 let tlog = Arc::new(MockTransparencyLogClient::new());
4072
4073 let verifier = AnsVerifier::builder()
4074 .dns_resolver(dns as Arc<dyn DnsResolver>)
4075 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4076 .build()
4077 .await
4078 .unwrap();
4079
4080 let result = verifier.client_cert_verifier();
4081 assert!(result.is_err());
4082 }
4083
4084 #[cfg(feature = "rustls")]
4085 #[tokio::test]
4086 async fn test_client_cert_verifier_with_pem() {
4087 let _ = rustls::crypto::ring::default_provider().install_default();
4088 let ca = rcgen::generate_simple_self_signed(vec!["ANS Test CA".to_string()]).unwrap();
4089 let ca_pem = ca.cert.pem();
4090
4091 let dns = Arc::new(MockDnsResolver::new());
4092 let tlog = Arc::new(MockTransparencyLogClient::new());
4093
4094 let verifier = AnsVerifier::builder()
4095 .dns_resolver(dns as Arc<dyn DnsResolver>)
4096 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4097 .private_ca_pem(ca_pem.as_bytes().to_vec())
4098 .build()
4099 .await
4100 .unwrap();
4101
4102 let cv = verifier.client_cert_verifier().unwrap();
4103 assert!(cv.requires_client_cert());
4104 }
4105
4106 #[cfg(feature = "rustls")]
4107 #[tokio::test]
4108 async fn test_client_cert_verifier_optional_with_pem() {
4109 let _ = rustls::crypto::ring::default_provider().install_default();
4110 let ca = rcgen::generate_simple_self_signed(vec!["ANS Test CA".to_string()]).unwrap();
4111 let ca_pem = ca.cert.pem();
4112
4113 let dns = Arc::new(MockDnsResolver::new());
4114 let tlog = Arc::new(MockTransparencyLogClient::new());
4115
4116 let verifier = AnsVerifier::builder()
4117 .dns_resolver(dns as Arc<dyn DnsResolver>)
4118 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4119 .private_ca_pem(ca_pem.as_bytes().to_vec())
4120 .build()
4121 .await
4122 .unwrap();
4123
4124 let cv = verifier.client_cert_verifier_optional().unwrap();
4125 assert!(!cv.requires_client_cert());
4126 }
4127
4128 #[cfg(feature = "rustls")]
4129 #[tokio::test]
4130 async fn test_server_cert_verifier() {
4131 let _ = rustls::crypto::ring::default_provider().install_default();
4132 let dns = Arc::new(MockDnsResolver::new());
4133 let tlog = Arc::new(MockTransparencyLogClient::new());
4134
4135 let verifier = AnsVerifier::builder()
4136 .dns_resolver(dns as Arc<dyn DnsResolver>)
4137 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4138 .build()
4139 .await
4140 .unwrap();
4141
4142 let fp = CertFingerprint::parse(
4143 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
4144 )
4145 .unwrap();
4146 let sv = verifier.server_cert_verifier(&fp).unwrap();
4147 assert_eq!(sv.expected_fingerprint(), &fp);
4148 }
4149
4150 #[tokio::test]
4155 async fn test_builder_with_caching() {
4156 let dns = Arc::new(MockDnsResolver::new());
4157 let tlog = Arc::new(MockTransparencyLogClient::new());
4158
4159 let verifier = AnsVerifier::builder()
4160 .dns_resolver(dns as Arc<dyn DnsResolver>)
4161 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4162 .with_caching()
4163 .build()
4164 .await
4165 .unwrap();
4166
4167 assert!(format!("{verifier:?}").contains("has_cache"));
4169 }
4170
4171 #[tokio::test]
4172 async fn test_builder_with_cache_config() {
4173 let dns = Arc::new(MockDnsResolver::new());
4174 let tlog = Arc::new(MockTransparencyLogClient::new());
4175
4176 let verifier = AnsVerifier::builder()
4177 .dns_resolver(dns as Arc<dyn DnsResolver>)
4178 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4179 .with_cache_config(CacheConfig::default())
4180 .build()
4181 .await
4182 .unwrap();
4183
4184 assert!(format!("{verifier:?}").contains("AnsVerifier"));
4185 }
4186
4187 #[tokio::test]
4188 async fn test_builder_with_dane_if_present() {
4189 let dns = Arc::new(MockDnsResolver::new());
4190 let tlog = Arc::new(MockTransparencyLogClient::new());
4191
4192 let verifier = ServerVerifier::builder()
4193 .dns_resolver(dns as Arc<dyn DnsResolver>)
4194 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4195 .with_dane_if_present()
4196 .build()
4197 .await
4198 .unwrap();
4199
4200 assert_eq!(verifier.dane_policy, DanePolicy::ValidateIfPresent);
4201 }
4202
4203 #[tokio::test]
4204 async fn test_builder_require_dane() {
4205 let dns = Arc::new(MockDnsResolver::new());
4206 let tlog = Arc::new(MockTransparencyLogClient::new());
4207
4208 let verifier = ServerVerifier::builder()
4209 .dns_resolver(dns as Arc<dyn DnsResolver>)
4210 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4211 .require_dane()
4212 .build()
4213 .await
4214 .unwrap();
4215
4216 assert_eq!(verifier.dane_policy, DanePolicy::Required);
4217 }
4218
4219 #[tokio::test]
4220 async fn test_builder_dane_port() {
4221 let dns = Arc::new(MockDnsResolver::new());
4222 let tlog = Arc::new(MockTransparencyLogClient::new());
4223
4224 let verifier = ServerVerifier::builder()
4225 .dns_resolver(dns as Arc<dyn DnsResolver>)
4226 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4227 .dane_port(8443)
4228 .build()
4229 .await
4230 .unwrap();
4231
4232 assert_eq!(verifier.dane_port, 8443);
4233 }
4234
4235 #[tokio::test]
4236 async fn test_builder_trusted_ra_domains() {
4237 let dns = Arc::new(MockDnsResolver::new());
4238 let tlog = Arc::new(MockTransparencyLogClient::new());
4239
4240 let verifier = ServerVerifier::builder()
4241 .dns_resolver(dns as Arc<dyn DnsResolver>)
4242 .tlog_client(tlog as Arc<dyn TransparencyLogClient>)
4243 .trusted_ra_domains(["tlog.example.com"])
4244 .build()
4245 .await
4246 .unwrap();
4247
4248 assert!(verifier.trusted_ra_domains.is_some());
4249 assert!(
4250 verifier
4251 .trusted_ra_domains
4252 .unwrap()
4253 .contains("tlog.example.com")
4254 );
4255 }
4256
4257 #[tokio::test]
4262 async fn test_dane_required_no_tlsa_records() {
4263 let host = "test.example.com";
4264 let fingerprint = "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904";
4265
4266 let badge = create_test_badge(host, "v1.0.0", fingerprint, "SHA256:aaa");
4267 let badge_url = "https://tlog.example.com/v1/agents/test-id";
4268
4269 let dns_record = BadgeRecord {
4270 format_version: "ans-badge1".to_string(),
4271 version: Some(Version::new(1, 0, 0)),
4272 url: badge_url.to_string(),
4273 };
4274
4275 let dns_resolver = Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
4277 let tlog_client = Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
4278
4279 let verifier = ServerVerifier {
4280 dns_resolver,
4281 tlog_client,
4282 cache: None,
4283 failure_policy: FailurePolicy::FailClosed,
4284 dane_policy: DanePolicy::Required,
4285 dane_port: 443,
4286 trusted_ra_domains: None,
4287 };
4288
4289 let cert = create_test_cert_identity(host, fingerprint);
4290 let fqdn = Fqdn::new(host).unwrap();
4291
4292 let outcome = verifier.verify(&fqdn, &cert).await;
4293 assert!(
4294 matches!(outcome, VerificationOutcome::DaneError(_)),
4295 "Expected DaneError for required DANE with no TLSA records, got: {outcome:?}"
4296 );
4297 }
4298
4299 #[test]
4304 fn test_outcome_badge_returns_none_for_errors() {
4305 let outcome = VerificationOutcome::DnsError(DnsError::Timeout {
4306 fqdn: "test.example.com".to_string(),
4307 });
4308 assert!(outcome.badge().is_none());
4309
4310 let outcome = VerificationOutcome::NotAnsAgent {
4311 fqdn: "test.example.com".to_string(),
4312 };
4313 assert!(outcome.badge().is_none());
4314 }
4315
4316 #[test]
4317 fn test_outcome_badge_returns_some_for_mismatches() {
4318 let badge = create_test_badge(
4319 "test.example.com",
4320 "v1.0.0",
4321 "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
4322 "SHA256:aaa",
4323 );
4324
4325 let outcome = VerificationOutcome::HostnameMismatch {
4326 expected: "test.example.com".to_string(),
4327 actual: "other.example.com".to_string(),
4328 badge,
4329 };
4330 assert!(outcome.badge().is_some());
4331 }
4332
4333 #[test]
4334 fn test_server_verifier_debug_format() {
4335 let dbg = format!("{:?}", ServerVerifierBuilder::default());
4336 assert!(dbg.contains("ServerVerifierBuilder"));
4337 }
4338
4339 #[cfg(feature = "scitt")]
4342 mod scitt_integration {
4343 use super::*;
4344 use crate::scitt::{
4345 RefreshableKeyStore, ScittError, ScittHeaders, ScittKeyStore,
4346 compute_sig_structure_digest, verify_status_token,
4347 };
4348 use base64::prelude::{BASE64_STANDARD, Engine as _};
4349 use p256::ecdsa::{SigningKey, signature::hazmat::PrehashSigner as _};
4350 use p256::pkcs8::EncodePublicKey as _;
4351 use sha2::{Digest, Sha256};
4352
4353 fn make_key_and_store(seed: u8) -> (SigningKey, ScittKeyStore) {
4356 let signing_key = SigningKey::from_slice(&[seed; 32]).unwrap();
4357 let verifying_key = signing_key.verifying_key();
4358 let spki_doc = verifying_key.to_public_key_der().unwrap();
4359 let spki_der = spki_doc.as_bytes();
4360 let digest = Sha256::digest(spki_der);
4361 let kid: [u8; 4] = [digest[0], digest[1], digest[2], digest[3]];
4362 let key_hash_hex = hex::encode(kid);
4363 let spki_b64 = BASE64_STANDARD.encode(spki_der);
4364 let key_string = format!("tl.example.com+{key_hash_hex}+{spki_b64}");
4365 let store = ScittKeyStore::from_c2sp_keys(&[key_string]).unwrap();
4366 (signing_key, store)
4367 }
4368
4369 fn build_protected_bytes(signing_key: &SigningKey) -> Vec<u8> {
4370 let spki_doc = signing_key.verifying_key().to_public_key_der().unwrap();
4371 let spki_der = spki_doc.as_bytes();
4372 let digest = Sha256::digest(spki_der);
4373 let kid = vec![digest[0], digest[1], digest[2], digest[3]];
4374 let pairs = vec![
4375 (
4376 ciborium::Value::Integer(1.into()),
4377 ciborium::Value::Integer((-7_i64).into()),
4378 ),
4379 (
4380 ciborium::Value::Integer(4.into()),
4381 ciborium::Value::Bytes(kid),
4382 ),
4383 ];
4384 let map = ciborium::Value::Map(pairs);
4385 let mut buf = Vec::new();
4386 ciborium::ser::into_writer(&map, &mut buf).unwrap();
4387 buf
4388 }
4389
4390 fn build_cbor_payload(
4391 agent_id: &str,
4392 status: &str,
4393 iat: i64,
4394 exp: i64,
4395 ans_name: &str,
4396 identity_certs: &[(String, String)],
4397 server_certs: &[(String, String)],
4398 ) -> Vec<u8> {
4399 let mut pairs: Vec<(ciborium::Value, ciborium::Value)> = Vec::new();
4400 pairs.push((
4401 ciborium::Value::Integer(1.into()),
4402 ciborium::Value::Text(agent_id.to_string()),
4403 ));
4404 pairs.push((
4405 ciborium::Value::Integer(2.into()),
4406 ciborium::Value::Text(status.to_string()),
4407 ));
4408 pairs.push((
4409 ciborium::Value::Integer(3.into()),
4410 ciborium::Value::Integer(iat.into()),
4411 ));
4412 pairs.push((
4413 ciborium::Value::Integer(4.into()),
4414 ciborium::Value::Integer(exp.into()),
4415 ));
4416 pairs.push((
4417 ciborium::Value::Integer(5.into()),
4418 ciborium::Value::Text(ans_name.to_string()),
4419 ));
4420 let id_certs: Vec<ciborium::Value> = identity_certs
4421 .iter()
4422 .map(|(fp, ct)| {
4423 ciborium::Value::Map(vec![
4424 (
4425 ciborium::Value::Text("fingerprint".to_string()),
4426 ciborium::Value::Text(fp.clone()),
4427 ),
4428 (
4429 ciborium::Value::Text("cert_type".to_string()),
4430 ciborium::Value::Text(ct.clone()),
4431 ),
4432 ])
4433 })
4434 .collect();
4435 pairs.push((
4436 ciborium::Value::Integer(6.into()),
4437 ciborium::Value::Array(id_certs),
4438 ));
4439 let srv_certs: Vec<ciborium::Value> = server_certs
4440 .iter()
4441 .map(|(fp, ct)| {
4442 ciborium::Value::Map(vec![
4443 (
4444 ciborium::Value::Text("fingerprint".to_string()),
4445 ciborium::Value::Text(fp.clone()),
4446 ),
4447 (
4448 ciborium::Value::Text("cert_type".to_string()),
4449 ciborium::Value::Text(ct.clone()),
4450 ),
4451 ])
4452 })
4453 .collect();
4454 pairs.push((
4455 ciborium::Value::Integer(7.into()),
4456 ciborium::Value::Array(srv_certs),
4457 ));
4458 pairs.push((
4459 ciborium::Value::Integer(8.into()),
4460 ciborium::Value::Map(vec![]),
4461 ));
4462 let map = ciborium::Value::Map(pairs);
4463 let mut buf = Vec::new();
4464 ciborium::ser::into_writer(&map, &mut buf).unwrap();
4465 buf
4466 }
4467
4468 fn make_token(signing_key: &SigningKey, payload: &[u8]) -> Vec<u8> {
4469 let protected_bytes = build_protected_bytes(signing_key);
4470 let digest = compute_sig_structure_digest(&protected_bytes, payload).unwrap();
4471 let (sig, _): (p256::ecdsa::Signature, _) = signing_key.sign_prehash(&digest).unwrap();
4472 let sig_bytes = sig.to_bytes().to_vec();
4473 let array = ciborium::Value::Array(vec![
4474 ciborium::Value::Bytes(protected_bytes),
4475 ciborium::Value::Map(vec![]),
4476 ciborium::Value::Bytes(payload.to_vec()),
4477 ciborium::Value::Bytes(sig_bytes),
4478 ]);
4479 let mut buf = Vec::new();
4480 ciborium::ser::into_writer(&array, &mut buf).unwrap();
4481 buf
4482 }
4483
4484 fn future_exp() -> i64 {
4485 4_102_444_800 }
4487
4488 fn past_exp() -> i64 {
4489 946_684_800 }
4491
4492 fn nil_uuid() -> String {
4493 "00000000-0000-0000-0000-000000000000".to_string()
4494 }
4495
4496 fn test_fp() -> String {
4497 format!("SHA256:{}", "00".repeat(32))
4498 }
4499
4500 fn test_fp2() -> String {
4501 format!("SHA256:{}", "11".repeat(32))
4502 }
4503
4504 fn make_verifier_with_scitt(
4505 host: &str,
4506 badge_fingerprint: &str,
4507 key_store: Arc<ScittKeyStore>,
4508 tier_policy: ScittTierPolicy,
4509 ) -> AnsVerifier {
4510 let identity_fp = format!("SHA256:{}", "22".repeat(32));
4511 let badge = create_test_badge(host, "v1.0.0", badge_fingerprint, &identity_fp);
4512 let badge_url = "https://tlog.example.com/v1/agents/test-id";
4513 let dns_record = BadgeRecord {
4514 format_version: "ans-badge1".to_string(),
4515 version: Some(Version::new(1, 0, 0)),
4516 url: badge_url.to_string(),
4517 };
4518 let dns_resolver =
4519 Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
4520 let tlog_client =
4521 Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
4522
4523 let server_verifier = ServerVerifier {
4524 dns_resolver: dns_resolver.clone(),
4525 tlog_client: tlog_client.clone(),
4526 cache: None,
4527 failure_policy: FailurePolicy::FailClosed,
4528 dane_policy: DanePolicy::Disabled,
4529 dane_port: 443,
4530 trusted_ra_domains: None,
4531 };
4532 let client_verifier = ClientVerifier {
4533 dns_resolver,
4534 tlog_client,
4535 cache: None,
4536 failure_policy: FailurePolicy::FailClosed,
4537 trusted_ra_domains: None,
4538 };
4539
4540 AnsVerifier {
4541 server_verifier,
4542 client_verifier,
4543 #[cfg(feature = "rustls")]
4544 private_ca_pem: None,
4545 scitt_config: Some(ScittConfig::new().with_tier_policy(tier_policy)),
4546 scitt_key_store: Some(Arc::new(RefreshableKeyStore::from_static(
4547 (*key_store).clone(),
4548 ))),
4549 scitt_verification_cache: None,
4550 }
4551 }
4552
4553 fn make_valid_token(signing_key: &SigningKey, server_fp: &str) -> Vec<u8> {
4554 let payload = build_cbor_payload(
4555 &nil_uuid(),
4556 "ACTIVE",
4557 0,
4558 future_exp(),
4559 "ans://v1.0.0.agent.example.com",
4560 &[],
4561 &[(server_fp.to_string(), "X509-DV-SERVER".to_string())],
4562 );
4563 make_token(signing_key, &payload)
4564 }
4565
4566 fn make_valid_identity_token(signing_key: &SigningKey, identity_fp: &str) -> Vec<u8> {
4567 let payload = build_cbor_payload(
4568 &nil_uuid(),
4569 "ACTIVE",
4570 0,
4571 future_exp(),
4572 "ans://v1.0.0.agent.example.com",
4573 &[(identity_fp.to_string(), "X509-OV-CLIENT".to_string())],
4574 &[],
4575 );
4576 make_token(signing_key, &payload)
4577 }
4578
4579 #[test]
4582 fn scitt_config_default() {
4583 let config = ScittConfig::default();
4584 assert!(matches!(
4585 config.tier_policy,
4586 ScittTierPolicy::ScittWithBadgeFallback
4587 ));
4588 assert_eq!(config.clock_skew_tolerance, Duration::from_secs(60));
4589 }
4590
4591 #[test]
4592 fn scitt_config_builder_chain() {
4593 let config = ScittConfig::new()
4594 .with_tier_policy(ScittTierPolicy::RequireScitt)
4595 .with_clock_skew(Duration::from_secs(120));
4596 assert!(matches!(config.tier_policy, ScittTierPolicy::RequireScitt));
4597 assert_eq!(config.clock_skew_tolerance, Duration::from_secs(120));
4598 }
4599
4600 #[test]
4603 fn scitt_verified_is_success() {
4604 let (signing_key, store) = make_key_and_store(1);
4605 let token_bytes = make_valid_token(&signing_key, &test_fp());
4606 let verified =
4607 verify_status_token(&token_bytes, &store, Duration::from_secs(0)).unwrap();
4608
4609 let outcome = VerificationOutcome::ScittVerified {
4610 status_token: verified,
4611 tier: ans_types::VerificationTier::FullScitt,
4612 matched_fingerprint: CertFingerprint::parse(&test_fp()).unwrap(),
4613 badge: None,
4614 };
4615 assert!(outcome.is_success());
4616 assert!(!outcome.is_not_ans_agent());
4617 }
4618
4619 #[test]
4620 fn scitt_verified_badge_accessor_with_badge() {
4621 let (signing_key, store) = make_key_and_store(1);
4622 let token_bytes = make_valid_token(&signing_key, &test_fp());
4623 let verified =
4624 verify_status_token(&token_bytes, &store, Duration::from_secs(0)).unwrap();
4625
4626 let badge = create_test_badge("agent.example.com", "v1.0.0", &test_fp(), "SHA256:aaa");
4627 let outcome = VerificationOutcome::ScittVerified {
4628 status_token: verified,
4629 tier: ans_types::VerificationTier::FullScitt,
4630 matched_fingerprint: CertFingerprint::parse(&test_fp()).unwrap(),
4631 badge: Some(badge),
4632 };
4633 assert!(outcome.badge().is_some());
4634 }
4635
4636 #[test]
4637 fn scitt_verified_badge_accessor_without_badge() {
4638 let (signing_key, store) = make_key_and_store(1);
4639 let token_bytes = make_valid_token(&signing_key, &test_fp());
4640 let verified =
4641 verify_status_token(&token_bytes, &store, Duration::from_secs(0)).unwrap();
4642
4643 let outcome = VerificationOutcome::ScittVerified {
4644 status_token: verified,
4645 tier: ans_types::VerificationTier::StatusTokenVerified,
4646 matched_fingerprint: CertFingerprint::parse(&test_fp()).unwrap(),
4647 badge: None,
4648 };
4649 assert!(outcome.badge().is_none());
4650 }
4651
4652 #[test]
4653 fn scitt_error_is_not_success() {
4654 let outcome = VerificationOutcome::ScittError(ScittError::SignatureInvalid);
4655 assert!(!outcome.is_success());
4656 }
4657
4658 #[test]
4659 fn scitt_error_into_result() {
4660 let outcome = VerificationOutcome::ScittError(ScittError::SignatureInvalid);
4661 let result = outcome.into_result();
4662 assert!(result.is_err());
4663 }
4664
4665 #[test]
4666 fn scitt_verified_into_scitt_result_with_badge() {
4667 let (signing_key, store) = make_key_and_store(1);
4668 let token_bytes = make_valid_token(&signing_key, &test_fp());
4669 let verified =
4670 verify_status_token(&token_bytes, &store, Duration::from_secs(0)).unwrap();
4671 let badge = create_test_badge("agent.example.com", "v1.0.0", &test_fp(), "SHA256:aaa");
4672
4673 let outcome = VerificationOutcome::ScittVerified {
4674 status_token: verified,
4675 tier: ans_types::VerificationTier::FullScitt,
4676 matched_fingerprint: CertFingerprint::parse(&test_fp()).unwrap(),
4677 badge: Some(badge),
4678 };
4679 let result = outcome.into_scitt_result();
4680 assert!(result.is_ok());
4681 assert!(result.unwrap().is_some());
4682 }
4683
4684 #[test]
4685 fn scitt_verified_into_scitt_result_without_badge() {
4686 let (signing_key, store) = make_key_and_store(1);
4687 let token_bytes = make_valid_token(&signing_key, &test_fp());
4688 let verified =
4689 verify_status_token(&token_bytes, &store, Duration::from_secs(0)).unwrap();
4690
4691 let outcome = VerificationOutcome::ScittVerified {
4692 status_token: verified,
4693 tier: ans_types::VerificationTier::StatusTokenVerified,
4694 matched_fingerprint: CertFingerprint::parse(&test_fp()).unwrap(),
4695 badge: None,
4696 };
4697 let result = outcome.into_scitt_result();
4699 assert!(result.is_ok());
4700 assert!(result.unwrap().is_none());
4701 }
4702
4703 #[test]
4704 fn scitt_error_into_scitt_result() {
4705 let outcome = VerificationOutcome::ScittError(ScittError::SignatureInvalid);
4706 let result = outcome.into_scitt_result();
4707 assert!(result.is_err());
4708 }
4709
4710 #[tokio::test]
4713 async fn scitt_server_verification_success_token_only() {
4714 let fp = test_fp();
4715 let (signing_key, store) = make_key_and_store(1);
4716 let store = Arc::new(store);
4717 let token_bytes = make_valid_token(&signing_key, &fp);
4718 let token_b64 = BASE64_STANDARD.encode(&token_bytes);
4719
4720 let verifier = make_verifier_with_scitt(
4721 "agent.example.com",
4722 &fp,
4723 store,
4724 ScittTierPolicy::ScittWithBadgeFallback,
4725 );
4726 let cert = create_test_cert_identity("agent.example.com", &fp);
4727 let headers = ScittHeaders::from_base64(None, Some(&token_b64)).unwrap();
4728
4729 let outcome = verifier
4730 .verify_server_with_scitt("agent.example.com", &cert, &headers)
4731 .await;
4732 assert!(outcome.is_success());
4733 match outcome {
4734 VerificationOutcome::ScittVerified { tier, .. } => {
4735 assert_eq!(tier, ans_types::VerificationTier::StatusTokenVerified);
4736 }
4737 other => panic!("Expected ScittVerified, got: {other:?}"),
4738 }
4739 }
4740
4741 #[tokio::test]
4742 async fn scitt_server_no_headers_fallback_to_badge() {
4743 let fp = test_fp();
4744 let (_, store) = make_key_and_store(1);
4745 let store = Arc::new(store);
4746
4747 let verifier = make_verifier_with_scitt(
4748 "agent.example.com",
4749 &fp,
4750 store,
4751 ScittTierPolicy::ScittWithBadgeFallback,
4752 );
4753 let cert = create_test_cert_identity("agent.example.com", &fp);
4754 let headers = ScittHeaders::new(None, None);
4755
4756 let outcome = verifier
4757 .verify_server_with_scitt("agent.example.com", &cert, &headers)
4758 .await;
4759 assert!(outcome.is_success());
4761 assert!(matches!(outcome, VerificationOutcome::Verified { .. }));
4762 }
4763
4764 #[tokio::test]
4765 async fn scitt_server_no_headers_require_scitt_fails() {
4766 let fp = test_fp();
4767 let (_, store) = make_key_and_store(1);
4768 let store = Arc::new(store);
4769
4770 let verifier = make_verifier_with_scitt(
4771 "agent.example.com",
4772 &fp,
4773 store,
4774 ScittTierPolicy::RequireScitt,
4775 );
4776 let cert = create_test_cert_identity("agent.example.com", &fp);
4777 let headers = ScittHeaders::new(None, None);
4778
4779 let outcome = verifier
4780 .verify_server_with_scitt("agent.example.com", &cert, &headers)
4781 .await;
4782 assert!(!outcome.is_success());
4783 assert!(matches!(outcome, VerificationOutcome::ScittError(_)));
4784 }
4785
4786 #[tokio::test]
4787 async fn scitt_server_corrupt_token_rejects() {
4788 let fp = test_fp();
4789 let (_, store) = make_key_and_store(1);
4790 let store = Arc::new(store);
4791 let bad_token_b64 = BASE64_STANDARD.encode(b"not-a-cose-structure");
4793
4794 let verifier = make_verifier_with_scitt(
4795 "agent.example.com",
4796 &fp,
4797 store,
4798 ScittTierPolicy::ScittWithBadgeFallback,
4799 );
4800 let cert = create_test_cert_identity("agent.example.com", &fp);
4801 let headers = ScittHeaders::from_base64(None, Some(&bad_token_b64)).unwrap();
4802
4803 let outcome = verifier
4804 .verify_server_with_scitt("agent.example.com", &cert, &headers)
4805 .await;
4806 assert!(!outcome.is_success());
4808 assert!(matches!(outcome, VerificationOutcome::ScittError(_)));
4809 }
4810
4811 #[tokio::test]
4812 async fn scitt_server_fingerprint_mismatch() {
4813 let fp = test_fp();
4814 let different_fp = test_fp2();
4815 let (signing_key, store) = make_key_and_store(1);
4816 let store = Arc::new(store);
4817 let token_bytes = make_valid_token(&signing_key, &fp);
4819 let token_b64 = BASE64_STANDARD.encode(&token_bytes);
4820
4821 let verifier = make_verifier_with_scitt(
4822 "agent.example.com",
4823 &fp,
4824 store,
4825 ScittTierPolicy::ScittWithBadgeFallback,
4826 );
4827 let cert = create_test_cert_identity("agent.example.com", &different_fp);
4828 let headers = ScittHeaders::from_base64(None, Some(&token_b64)).unwrap();
4829
4830 let outcome = verifier
4831 .verify_server_with_scitt("agent.example.com", &cert, &headers)
4832 .await;
4833 assert!(!outcome.is_success());
4834 assert!(matches!(outcome, VerificationOutcome::ScittError(_)));
4835 }
4836
4837 #[tokio::test]
4838 async fn scitt_server_expired_token_with_headers_rejects() {
4839 let fp = test_fp();
4840 let (signing_key, store) = make_key_and_store(1);
4841 let store = Arc::new(store);
4842 let payload = build_cbor_payload(
4844 &nil_uuid(),
4845 "ACTIVE",
4846 0,
4847 past_exp(),
4848 "ans://v1.0.0.agent.example.com",
4849 &[],
4850 &[(fp.clone(), "X509-DV-SERVER".to_string())],
4851 );
4852 let token_bytes = make_token(&signing_key, &payload);
4853 let token_b64 = BASE64_STANDARD.encode(&token_bytes);
4854
4855 let verifier = make_verifier_with_scitt(
4856 "agent.example.com",
4857 &fp,
4858 store,
4859 ScittTierPolicy::ScittWithBadgeFallback,
4860 );
4861 let cert = create_test_cert_identity("agent.example.com", &fp);
4862 let headers = ScittHeaders::from_base64(None, Some(&token_b64)).unwrap();
4863
4864 let outcome = verifier
4865 .verify_server_with_scitt("agent.example.com", &cert, &headers)
4866 .await;
4867 assert!(!outcome.is_success());
4869 assert!(matches!(outcome, VerificationOutcome::ScittError(_)));
4870 }
4871
4872 #[tokio::test]
4873 async fn scitt_server_expired_token_require_scitt_fails() {
4874 let fp = test_fp();
4875 let (signing_key, store) = make_key_and_store(1);
4876 let store = Arc::new(store);
4877 let payload = build_cbor_payload(
4878 &nil_uuid(),
4879 "ACTIVE",
4880 0,
4881 past_exp(),
4882 "ans://v1.0.0.agent.example.com",
4883 &[],
4884 &[(fp.clone(), "X509-DV-SERVER".to_string())],
4885 );
4886 let token_bytes = make_token(&signing_key, &payload);
4887 let token_b64 = BASE64_STANDARD.encode(&token_bytes);
4888
4889 let verifier = make_verifier_with_scitt(
4890 "agent.example.com",
4891 &fp,
4892 store,
4893 ScittTierPolicy::RequireScitt,
4894 );
4895 let cert = create_test_cert_identity("agent.example.com", &fp);
4896 let headers = ScittHeaders::from_base64(None, Some(&token_b64)).unwrap();
4897
4898 let outcome = verifier
4899 .verify_server_with_scitt("agent.example.com", &cert, &headers)
4900 .await;
4901 assert!(!outcome.is_success());
4903 assert!(matches!(outcome, VerificationOutcome::ScittError(_)));
4904 }
4905
4906 #[tokio::test]
4907 async fn scitt_server_terminal_status_rejects() {
4908 let fp = test_fp();
4909 let (signing_key, store) = make_key_and_store(1);
4910 let store = Arc::new(store);
4911 let payload = build_cbor_payload(
4913 &nil_uuid(),
4914 "REVOKED",
4915 0,
4916 future_exp(),
4917 "ans://v1.0.0.agent.example.com",
4918 &[],
4919 &[(fp.clone(), "X509-DV-SERVER".to_string())],
4920 );
4921 let token_bytes = make_token(&signing_key, &payload);
4922 let token_b64 = BASE64_STANDARD.encode(&token_bytes);
4923
4924 let verifier = make_verifier_with_scitt(
4925 "agent.example.com",
4926 &fp,
4927 store,
4928 ScittTierPolicy::ScittWithBadgeFallback,
4929 );
4930 let cert = create_test_cert_identity("agent.example.com", &fp);
4931 let headers = ScittHeaders::from_base64(None, Some(&token_b64)).unwrap();
4932
4933 let outcome = verifier
4934 .verify_server_with_scitt("agent.example.com", &cert, &headers)
4935 .await;
4936 assert!(!outcome.is_success());
4938 assert!(matches!(outcome, VerificationOutcome::ScittError(_)));
4939 }
4940
4941 #[tokio::test]
4942 async fn scitt_server_badge_enhancement_policy() {
4943 let fp = test_fp();
4944 let (signing_key, store) = make_key_and_store(1);
4945 let store = Arc::new(store);
4946 let token_bytes = make_valid_token(&signing_key, &fp);
4947 let token_b64 = BASE64_STANDARD.encode(&token_bytes);
4948
4949 let verifier = make_verifier_with_scitt(
4950 "agent.example.com",
4951 &fp,
4952 store,
4953 ScittTierPolicy::BadgeWithScittEnhancement,
4954 );
4955 let cert = create_test_cert_identity("agent.example.com", &fp);
4956 let headers = ScittHeaders::from_base64(None, Some(&token_b64)).unwrap();
4957
4958 let outcome = verifier
4959 .verify_server_with_scitt("agent.example.com", &cert, &headers)
4960 .await;
4961 assert!(outcome.is_success());
4963 assert!(matches!(outcome, VerificationOutcome::ScittVerified { .. }));
4964 }
4965
4966 #[tokio::test]
4967 async fn scitt_server_badge_enhancement_no_headers() {
4968 let fp = test_fp();
4969 let (_, store) = make_key_and_store(1);
4970 let store = Arc::new(store);
4971
4972 let verifier = make_verifier_with_scitt(
4973 "agent.example.com",
4974 &fp,
4975 store,
4976 ScittTierPolicy::BadgeWithScittEnhancement,
4977 );
4978 let cert = create_test_cert_identity("agent.example.com", &fp);
4979 let headers = ScittHeaders::new(None, None);
4980
4981 let outcome = verifier
4982 .verify_server_with_scitt("agent.example.com", &cert, &headers)
4983 .await;
4984 assert!(outcome.is_success());
4986 assert!(matches!(outcome, VerificationOutcome::Verified { .. }));
4987 }
4988
4989 #[tokio::test]
4992 async fn scitt_client_no_headers_fallback_to_badge() {
4993 let identity_fp = test_fp2(); let (_, store) = make_key_and_store(1);
4995 let store = Arc::new(store);
4996
4997 let verifier = make_verifier_with_scitt(
4998 "agent.example.com",
4999 &test_fp(), store,
5001 ScittTierPolicy::ScittWithBadgeFallback,
5002 );
5003 let cert = CertIdentity {
5005 common_name: Some("agent.example.com".to_string()),
5006 dns_sans: vec!["agent.example.com".to_string()],
5007 uri_sans: vec!["ans://v1.0.0.agent.example.com".to_string()],
5008 fingerprint: CertFingerprint::parse(&identity_fp).unwrap(),
5009 };
5010 let headers = ScittHeaders::new(None, None);
5011
5012 let outcome = verifier.verify_client_with_scitt(&cert, &headers).await;
5013 assert!(!matches!(outcome, VerificationOutcome::ScittError(_)));
5020 }
5021
5022 #[tokio::test]
5023 async fn scitt_client_no_headers_require_scitt_fails() {
5024 let identity_fp = test_fp2();
5025 let (_, store) = make_key_and_store(1);
5026 let store = Arc::new(store);
5027
5028 let verifier = make_verifier_with_scitt(
5029 "agent.example.com",
5030 &test_fp(),
5031 store,
5032 ScittTierPolicy::RequireScitt,
5033 );
5034 let cert = CertIdentity {
5035 common_name: Some("agent.example.com".to_string()),
5036 dns_sans: vec![],
5037 uri_sans: vec!["ans://v1.0.0.agent.example.com".to_string()],
5038 fingerprint: CertFingerprint::parse(&identity_fp).unwrap(),
5039 };
5040 let headers = ScittHeaders::new(None, None);
5041
5042 let outcome = verifier.verify_client_with_scitt(&cert, &headers).await;
5043 assert!(!outcome.is_success());
5044 assert!(matches!(outcome, VerificationOutcome::ScittError(_)));
5045 }
5046
5047 #[tokio::test]
5048 async fn scitt_client_verification_success_with_token() {
5049 let identity_fp = test_fp2();
5050 let (signing_key, store) = make_key_and_store(1);
5051 let store = Arc::new(store);
5052 let token_bytes = make_valid_identity_token(&signing_key, &identity_fp);
5053 let token_b64 = BASE64_STANDARD.encode(&token_bytes);
5054
5055 let verifier = make_verifier_with_scitt(
5056 "agent.example.com",
5057 &test_fp(),
5058 store,
5059 ScittTierPolicy::ScittWithBadgeFallback,
5060 );
5061 let cert = CertIdentity {
5062 common_name: Some("agent.example.com".to_string()),
5063 dns_sans: vec!["agent.example.com".to_string()],
5064 uri_sans: vec!["ans://v1.0.0.agent.example.com".to_string()],
5065 fingerprint: CertFingerprint::parse(&identity_fp).unwrap(),
5066 };
5067 let headers = ScittHeaders::from_base64(None, Some(&token_b64)).unwrap();
5068
5069 let outcome = verifier.verify_client_with_scitt(&cert, &headers).await;
5070 assert!(outcome.is_success());
5071 assert!(matches!(outcome, VerificationOutcome::ScittVerified { .. }));
5072 }
5073
5074 #[test]
5077 fn builder_scitt_config_sets_field() {
5078 let builder = AnsVerifier::builder()
5079 .scitt_config(ScittConfig::new().with_tier_policy(ScittTierPolicy::RequireScitt));
5080 assert!(builder.scitt_config.is_some());
5081 assert!(matches!(
5082 builder.scitt_config.unwrap().tier_policy,
5083 ScittTierPolicy::RequireScitt
5084 ));
5085 }
5086
5087 #[test]
5088 fn builder_scitt_key_store_sets_field() {
5089 let (_, store) = make_key_and_store(1);
5090 let builder = AnsVerifier::builder().scitt_key_store(Arc::new(store));
5091 assert!(builder.scitt_key_store.is_some());
5092 }
5093
5094 #[test]
5095 fn builder_debug_includes_scitt() {
5096 let builder = AnsVerifier::builder().scitt_config(ScittConfig::default());
5097 let dbg = format!("{builder:?}");
5098 assert!(dbg.contains("has_scitt_config"));
5099 assert!(dbg.contains("true"));
5100 }
5101
5102 #[test]
5105 fn verifier_debug_includes_scitt() {
5106 let fp = test_fp();
5107 let (_, store) = make_key_and_store(1);
5108 let verifier = make_verifier_with_scitt(
5109 "agent.example.com",
5110 &fp,
5111 Arc::new(store),
5112 ScittTierPolicy::ScittWithBadgeFallback,
5113 );
5114 let dbg = format!("{verifier:?}");
5115 assert!(dbg.contains("has_scitt_config"));
5116 }
5117
5118 #[tokio::test]
5121 async fn scitt_no_key_store_falls_back_to_badge() {
5122 let fp = test_fp();
5123 let host = "agent.example.com";
5124 let badge = create_test_badge(host, "v1.0.0", &fp, "SHA256:aaa");
5125 let badge_url = "https://tlog.example.com/v1/agents/test-id";
5126 let dns_record = BadgeRecord {
5127 format_version: "ans-badge1".to_string(),
5128 version: Some(Version::new(1, 0, 0)),
5129 url: badge_url.to_string(),
5130 };
5131 let dns_resolver =
5132 Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
5133 let tlog_client =
5134 Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
5135
5136 let server_verifier = ServerVerifier {
5137 dns_resolver: dns_resolver.clone(),
5138 tlog_client: tlog_client.clone(),
5139 cache: None,
5140 failure_policy: FailurePolicy::FailClosed,
5141 dane_policy: DanePolicy::Disabled,
5142 dane_port: 443,
5143 trusted_ra_domains: None,
5144 };
5145 let client_verifier = ClientVerifier {
5146 dns_resolver,
5147 tlog_client,
5148 cache: None,
5149 failure_policy: FailurePolicy::FailClosed,
5150 trusted_ra_domains: None,
5151 };
5152
5153 let verifier = AnsVerifier {
5155 server_verifier,
5156 client_verifier,
5157 #[cfg(feature = "rustls")]
5158 private_ca_pem: None,
5159 scitt_config: Some(ScittConfig::default()),
5160 scitt_key_store: None,
5161 scitt_verification_cache: None,
5162 };
5163
5164 let cert = create_test_cert_identity(host, &fp);
5165 let headers = ScittHeaders::from_base64(None, Some("aGVsbG8=")).unwrap();
5166
5167 let outcome = verifier
5168 .verify_server_with_scitt(host, &cert, &headers)
5169 .await;
5170 assert!(outcome.is_success());
5172 assert!(matches!(outcome, VerificationOutcome::Verified { .. }));
5173 }
5174
5175 #[tokio::test]
5178 async fn scitt_no_config_passes_through() {
5179 let fp = test_fp();
5180 let host = "agent.example.com";
5181 let badge = create_test_badge(host, "v1.0.0", &fp, "SHA256:aaa");
5182 let badge_url = "https://tlog.example.com/v1/agents/test-id";
5183 let dns_record = BadgeRecord {
5184 format_version: "ans-badge1".to_string(),
5185 version: Some(Version::new(1, 0, 0)),
5186 url: badge_url.to_string(),
5187 };
5188 let dns_resolver =
5189 Arc::new(MockDnsResolver::new().with_records(host, vec![dns_record]));
5190 let tlog_client =
5191 Arc::new(MockTransparencyLogClient::new().with_badge(badge_url, badge));
5192
5193 let server_verifier = ServerVerifier {
5194 dns_resolver: dns_resolver.clone(),
5195 tlog_client: tlog_client.clone(),
5196 cache: None,
5197 failure_policy: FailurePolicy::FailClosed,
5198 dane_policy: DanePolicy::Disabled,
5199 dane_port: 443,
5200 trusted_ra_domains: None,
5201 };
5202 let client_verifier = ClientVerifier {
5203 dns_resolver,
5204 tlog_client,
5205 cache: None,
5206 failure_policy: FailurePolicy::FailClosed,
5207 trusted_ra_domains: None,
5208 };
5209
5210 let verifier = AnsVerifier {
5211 server_verifier,
5212 client_verifier,
5213 #[cfg(feature = "rustls")]
5214 private_ca_pem: None,
5215 scitt_config: None,
5216 scitt_key_store: None,
5217 scitt_verification_cache: None,
5218 };
5219
5220 let cert = create_test_cert_identity(host, &fp);
5221 let headers = ScittHeaders::from_base64(None, Some("aGVsbG8=")).unwrap();
5222
5223 let outcome = verifier
5224 .verify_server_with_scitt(host, &cert, &headers)
5225 .await;
5226 assert!(outcome.is_success());
5228 assert!(matches!(outcome, VerificationOutcome::Verified { .. }));
5229 }
5230
5231 #[tokio::test]
5234 async fn scitt_server_invalid_fqdn() {
5235 let (_, store) = make_key_and_store(1);
5236 let store = Arc::new(store);
5237
5238 let verifier = make_verifier_with_scitt(
5239 "agent.example.com",
5240 &test_fp(),
5241 store,
5242 ScittTierPolicy::ScittWithBadgeFallback,
5243 );
5244 let cert = create_test_cert_identity("agent.example.com", &test_fp());
5245 let headers = ScittHeaders::new(None, None);
5246
5247 let outcome = verifier.verify_server_with_scitt("", &cert, &headers).await;
5248 assert!(matches!(outcome, VerificationOutcome::ParseError(_)));
5249 }
5250
5251 #[tokio::test]
5254 async fn scitt_server_wrong_key_rejects() {
5255 let fp = test_fp();
5256 let (signing_key, _store) = make_key_and_store(1);
5257 let (_, wrong_store) = make_key_and_store(2); let wrong_store = Arc::new(wrong_store);
5259
5260 let token_bytes = make_valid_token(&signing_key, &fp);
5261 let token_b64 = BASE64_STANDARD.encode(&token_bytes);
5262
5263 let verifier = make_verifier_with_scitt(
5264 "agent.example.com",
5265 &fp,
5266 wrong_store,
5267 ScittTierPolicy::ScittWithBadgeFallback,
5268 );
5269 let cert = create_test_cert_identity("agent.example.com", &fp);
5270 let headers = ScittHeaders::from_base64(None, Some(&token_b64)).unwrap();
5271
5272 let outcome = verifier
5273 .verify_server_with_scitt("agent.example.com", &cert, &headers)
5274 .await;
5275 assert!(!outcome.is_success());
5277 assert!(matches!(outcome, VerificationOutcome::ScittError(_)));
5278 }
5279 }
5280}