Skip to main content

ans_verify/
dane.rs

1//! DANE/TLSA verification for certificate binding to DNS.
2//!
3//! DANE (DNS-Based Authentication of Named Entities) binds certificates to DNS names
4//! via TLSA records, providing additional verification independent of the transparency log.
5
6use crate::error::DaneError;
7use ans_types::{CertFingerprint, Fqdn};
8
9/// DANE verification policy.
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum DanePolicy {
13    /// Never check TLSA records (skip DANE verification entirely).
14    #[default]
15    Disabled,
16
17    /// Validate TLSA records if present; skip if not found.
18    /// This is a permissive mode that adds security when available.
19    ValidateIfPresent,
20
21    /// Require TLSA records to exist and validate.
22    /// Connections are rejected if TLSA records are missing or don't match.
23    Required,
24}
25
26impl DanePolicy {
27    /// Check if DANE verification should be performed.
28    pub fn should_verify(&self) -> bool {
29        !matches!(self, Self::Disabled)
30    }
31
32    /// Check if TLSA records are required.
33    pub fn is_required(&self) -> bool {
34        matches!(self, Self::Required)
35    }
36}
37
38/// TLSA certificate usage field values (RFC 6698).
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40#[repr(u8)]
41#[non_exhaustive]
42pub enum TlsaUsage {
43    /// CA constraint (PKIX-TA)
44    CaConstraint = 0,
45    /// Service certificate constraint (PKIX-EE)
46    ServiceCertificateConstraint = 1,
47    /// Trust anchor assertion (DANE-TA)
48    TrustAnchorAssertion = 2,
49    /// Domain-issued certificate (DANE-EE) - most common for ANS
50    DomainIssuedCertificate = 3,
51}
52
53impl TryFrom<u8> for TlsaUsage {
54    type Error = DaneError;
55
56    fn try_from(value: u8) -> Result<Self, Self::Error> {
57        match value {
58            0 => Ok(Self::CaConstraint),
59            1 => Ok(Self::ServiceCertificateConstraint),
60            2 => Ok(Self::TrustAnchorAssertion),
61            3 => Ok(Self::DomainIssuedCertificate),
62            _ => Err(DaneError::InvalidRecord {
63                reason: format!("invalid TLSA usage: {value}"),
64            }),
65        }
66    }
67}
68
69/// TLSA selector field values (RFC 6698).
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71#[repr(u8)]
72#[non_exhaustive]
73pub enum TlsaSelector {
74    /// Full certificate
75    FullCertificate = 0,
76    /// `SubjectPublicKeyInfo`
77    SubjectPublicKeyInfo = 1,
78}
79
80impl TryFrom<u8> for TlsaSelector {
81    type Error = DaneError;
82
83    fn try_from(value: u8) -> Result<Self, Self::Error> {
84        match value {
85            0 => Ok(Self::FullCertificate),
86            1 => Ok(Self::SubjectPublicKeyInfo),
87            _ => Err(DaneError::InvalidRecord {
88                reason: format!("invalid TLSA selector: {value}"),
89            }),
90        }
91    }
92}
93
94/// TLSA matching type field values (RFC 6698).
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96#[repr(u8)]
97#[non_exhaustive]
98pub enum TlsaMatchingType {
99    /// No hash - exact match
100    NoHash = 0,
101    /// SHA-256 hash
102    Sha256 = 1,
103    /// SHA-512 hash
104    Sha512 = 2,
105}
106
107impl TryFrom<u8> for TlsaMatchingType {
108    type Error = DaneError;
109
110    fn try_from(value: u8) -> Result<Self, Self::Error> {
111        match value {
112            0 => Ok(Self::NoHash),
113            1 => Ok(Self::Sha256),
114            2 => Ok(Self::Sha512),
115            _ => Err(DaneError::InvalidRecord {
116                reason: format!("invalid TLSA matching type: {value}"),
117            }),
118        }
119    }
120}
121
122/// A parsed TLSA record.
123///
124/// Format: `_port._tcp.hostname IN TLSA usage selector matching_type certificate_data`
125///
126/// Example: `_443._tcp.agent.example.com IN TLSA 3 0 1 <sha256-fingerprint>`
127#[derive(Debug, Clone, PartialEq, Eq)]
128#[non_exhaustive]
129pub struct TlsaRecord {
130    /// Certificate usage (0-3)
131    pub usage: TlsaUsage,
132    /// Selector (0=full cert, 1=SPKI)
133    pub selector: TlsaSelector,
134    /// Matching type (0=exact, 1=SHA-256, 2=SHA-512)
135    pub matching_type: TlsaMatchingType,
136    /// Certificate association data (fingerprint or raw data)
137    pub certificate_data: Vec<u8>,
138}
139
140impl TlsaRecord {
141    /// Create a new TLSA record from components.
142    pub fn new(
143        usage: TlsaUsage,
144        selector: TlsaSelector,
145        matching_type: TlsaMatchingType,
146        certificate_data: Vec<u8>,
147    ) -> Self {
148        Self {
149            usage,
150            selector,
151            matching_type,
152            certificate_data,
153        }
154    }
155
156    /// Parse a TLSA record from raw RDATA bytes.
157    pub fn from_rdata(rdata: &[u8]) -> Result<Self, DaneError> {
158        if rdata.len() < 4 {
159            return Err(DaneError::InvalidRecord {
160                reason: "TLSA record too short".to_string(),
161            });
162        }
163
164        let usage = TlsaUsage::try_from(rdata[0])?;
165        let selector = TlsaSelector::try_from(rdata[1])?;
166        let matching_type = TlsaMatchingType::try_from(rdata[2])?;
167        let certificate_data = rdata[3..].to_vec();
168
169        Ok(Self {
170            usage,
171            selector,
172            matching_type,
173            certificate_data,
174        })
175    }
176
177    /// Check if this TLSA record is in a format we can verify.
178    ///
179    /// Currently only supports DANE-EE (usage=3), full certificate (selector=0),
180    /// SHA-256 (`matching_type=1`) which is the ANS standard format.
181    pub fn is_verifiable(&self) -> bool {
182        self.usage == TlsaUsage::DomainIssuedCertificate
183            && self.selector == TlsaSelector::FullCertificate
184            && self.matching_type == TlsaMatchingType::Sha256
185    }
186
187    /// Check if this TLSA record matches a certificate fingerprint.
188    ///
189    /// Currently only supports DANE-EE (usage=3), full certificate (selector=0),
190    /// SHA-256 (`matching_type=1`) which is the ANS standard format.
191    ///
192    /// Returns `None` if the record format is not supported (different from not matching).
193    pub fn matches_fingerprint(&self, cert_fingerprint: &CertFingerprint) -> Option<bool> {
194        // ANS uses: DANE-EE (3), Full Certificate (0), SHA-256 (1)
195        if self.usage != TlsaUsage::DomainIssuedCertificate {
196            tracing::debug!(
197                usage = ?self.usage,
198                "TLSA usage is not DANE-EE, cannot verify"
199            );
200            return None;
201        }
202
203        if self.selector != TlsaSelector::FullCertificate {
204            tracing::debug!(
205                selector = ?self.selector,
206                "TLSA selector is not full certificate (SPKI not yet supported), cannot verify"
207            );
208            return None;
209        }
210
211        if self.matching_type != TlsaMatchingType::Sha256 {
212            tracing::debug!(
213                matching_type = ?self.matching_type,
214                "TLSA matching type is not SHA-256, cannot verify"
215            );
216            return None;
217        }
218
219        // Compare raw bytes using constant-time equality to prevent timing side-channels.
220        // Both sides are SHA-256 hashes (32 bytes). If the TLSA data has wrong length,
221        // the comparison fails (not a timing concern since length is not secret).
222        let cert_bytes = cert_fingerprint.as_bytes();
223        let matches = if self.certificate_data.len() == cert_bytes.len() {
224            use subtle::ConstantTimeEq;
225            bool::from(self.certificate_data.ct_eq(cert_bytes.as_slice()))
226        } else {
227            false
228        };
229
230        tracing::debug!(
231            tlsa_fingerprint = %hex::encode(&self.certificate_data),
232            cert_fingerprint = %cert_fingerprint,
233            matches,
234            "TLSA fingerprint comparison"
235        );
236
237        Some(matches)
238    }
239}
240
241/// Result of DANE verification.
242#[derive(Debug, Clone)]
243#[non_exhaustive]
244pub enum DaneVerificationResult {
245    /// TLSA record matched the certificate.
246    Verified {
247        /// The TLSA record that matched.
248        matched_record: TlsaRecord,
249    },
250    /// No TLSA records found (not an error if policy is `ValidateIfPresent`).
251    NoRecords,
252    /// TLSA records found but none matched.
253    Mismatch {
254        /// Number of TLSA records checked.
255        records_checked: usize,
256    },
257    /// DNSSEC validation failed.
258    DnssecFailed,
259    /// Verification was skipped (policy is Disabled).
260    Skipped,
261}
262
263impl DaneVerificationResult {
264    /// Check if verification passed or was appropriately skipped.
265    pub fn is_acceptable(&self, policy: DanePolicy) -> bool {
266        match self {
267            Self::Verified { .. } | Self::Skipped => true,
268            Self::NoRecords => !policy.is_required(),
269            Self::Mismatch { .. } | Self::DnssecFailed => false,
270        }
271    }
272}
273
274/// Verify a certificate against TLSA records.
275pub fn verify_dane(
276    records: &[TlsaRecord],
277    cert_fingerprint: &CertFingerprint,
278    policy: DanePolicy,
279    fqdn: &Fqdn,
280    port: u16,
281) -> Result<DaneVerificationResult, DaneError> {
282    if !policy.should_verify() {
283        tracing::debug!("DANE verification disabled by policy");
284        return Ok(DaneVerificationResult::Skipped);
285    }
286
287    if records.is_empty() {
288        tracing::debug!(fqdn = %fqdn, port, "No TLSA records found");
289        if policy.is_required() {
290            return Err(DaneError::NoTlsaRecords {
291                fqdn: fqdn.to_string(),
292                port,
293            });
294        }
295        return Ok(DaneVerificationResult::NoRecords);
296    }
297
298    tracing::debug!(
299        fqdn = %fqdn,
300        port,
301        record_count = records.len(),
302        "Checking TLSA records"
303    );
304
305    // Check each TLSA record
306    let mut has_unsupported = false;
307
308    for record in records {
309        match record.matches_fingerprint(cert_fingerprint) {
310            Some(true) => {
311                tracing::info!(
312                    fqdn = %fqdn,
313                    port,
314                    "DANE verification PASSED - certificate matches TLSA record"
315                );
316                return Ok(DaneVerificationResult::Verified {
317                    matched_record: record.clone(),
318                });
319            }
320            Some(false) => {
321                tracing::debug!("TLSA record checked but did not match");
322            }
323            None => {
324                // Record is in unsupported format (e.g., SPKI selector)
325                has_unsupported = true;
326                tracing::warn!(
327                    usage = ?record.usage,
328                    selector = ?record.selector,
329                    matching_type = ?record.matching_type,
330                    "TLSA record in unsupported format"
331                );
332            }
333        }
334    }
335
336    // If records exist but none matched, fail
337    // This includes both: records in supported format that didn't match,
338    // AND records in unsupported format (we can't verify them, so we fail)
339    if has_unsupported {
340        tracing::error!(
341            fqdn = %fqdn,
342            port,
343            "DANE verification FAILED - TLSA records present but in unsupported format (only DANE-EE + FullCert + SHA256 supported)"
344        );
345        return Err(DaneError::InvalidRecord {
346            reason: "TLSA record format not supported (only usage=3, selector=0, matching_type=1)"
347                .to_string(),
348        });
349    }
350
351    tracing::warn!(
352        fqdn = %fqdn,
353        port,
354        records_checked = records.len(),
355        "DANE verification FAILED - no TLSA record matched certificate"
356    );
357
358    Err(DaneError::FingerprintMismatch)
359}
360
361#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
362#[cfg(test)]
363mod tests {
364    use super::*;
365
366    #[test]
367    fn test_dane_policy_defaults_to_disabled() {
368        assert_eq!(DanePolicy::default(), DanePolicy::Disabled);
369    }
370
371    #[test]
372    fn test_dane_policy_should_verify() {
373        assert!(!DanePolicy::Disabled.should_verify());
374        assert!(DanePolicy::ValidateIfPresent.should_verify());
375        assert!(DanePolicy::Required.should_verify());
376    }
377
378    #[test]
379    fn test_dane_policy_is_required() {
380        assert!(!DanePolicy::Disabled.is_required());
381        assert!(!DanePolicy::ValidateIfPresent.is_required());
382        assert!(DanePolicy::Required.is_required());
383    }
384
385    #[test]
386    fn test_tlsa_record_from_rdata() {
387        // Usage=3, Selector=0, MatchingType=1, followed by SHA-256 hash
388        let mut rdata = vec![3, 0, 1];
389        let hash = hex::decode("e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904")
390            .unwrap();
391        rdata.extend(&hash);
392
393        let record = TlsaRecord::from_rdata(&rdata).unwrap();
394        assert_eq!(record.usage, TlsaUsage::DomainIssuedCertificate);
395        assert_eq!(record.selector, TlsaSelector::FullCertificate);
396        assert_eq!(record.matching_type, TlsaMatchingType::Sha256);
397        assert_eq!(record.certificate_data, hash);
398    }
399
400    #[test]
401    fn test_tlsa_record_matches_fingerprint() {
402        let hash = hex::decode("e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904")
403            .unwrap();
404
405        let record = TlsaRecord::new(
406            TlsaUsage::DomainIssuedCertificate,
407            TlsaSelector::FullCertificate,
408            TlsaMatchingType::Sha256,
409            hash,
410        );
411
412        let fingerprint = CertFingerprint::parse(
413            "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
414        )
415        .unwrap();
416
417        assert_eq!(record.matches_fingerprint(&fingerprint), Some(true));
418    }
419
420    #[test]
421    fn test_tlsa_record_does_not_match_different_fingerprint() {
422        let hash = hex::decode("e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904")
423            .unwrap();
424
425        let record = TlsaRecord::new(
426            TlsaUsage::DomainIssuedCertificate,
427            TlsaSelector::FullCertificate,
428            TlsaMatchingType::Sha256,
429            hash,
430        );
431
432        let fingerprint = CertFingerprint::parse(
433            "SHA256:0000000000000000000000000000000000000000000000000000000000000000",
434        )
435        .unwrap();
436
437        assert_eq!(record.matches_fingerprint(&fingerprint), Some(false));
438    }
439
440    #[test]
441    fn test_tlsa_record_unsupported_format_returns_none() {
442        let hash = hex::decode("e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904")
443            .unwrap();
444
445        // SPKI selector is not supported
446        let record = TlsaRecord::new(
447            TlsaUsage::DomainIssuedCertificate,
448            TlsaSelector::SubjectPublicKeyInfo,
449            TlsaMatchingType::Sha256,
450            hash,
451        );
452
453        let fingerprint = CertFingerprint::parse(
454            "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
455        )
456        .unwrap();
457
458        // Should return None because format is not supported
459        assert_eq!(record.matches_fingerprint(&fingerprint), None);
460    }
461
462    #[test]
463    fn test_verify_dane_disabled() {
464        let fqdn = Fqdn::new("test.example.com").unwrap();
465        let fingerprint = CertFingerprint::parse(
466            "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
467        )
468        .unwrap();
469
470        let result = verify_dane(&[], &fingerprint, DanePolicy::Disabled, &fqdn, 443).unwrap();
471        assert!(matches!(result, DaneVerificationResult::Skipped));
472    }
473
474    #[test]
475    fn test_verify_dane_no_records_validate_if_present() {
476        let fqdn = Fqdn::new("test.example.com").unwrap();
477        let fingerprint = CertFingerprint::parse(
478            "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
479        )
480        .unwrap();
481
482        let result =
483            verify_dane(&[], &fingerprint, DanePolicy::ValidateIfPresent, &fqdn, 443).unwrap();
484        assert!(matches!(result, DaneVerificationResult::NoRecords));
485        assert!(result.is_acceptable(DanePolicy::ValidateIfPresent));
486    }
487
488    #[test]
489    fn test_verify_dane_no_records_required() {
490        let fqdn = Fqdn::new("test.example.com").unwrap();
491        let fingerprint = CertFingerprint::parse(
492            "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
493        )
494        .unwrap();
495
496        let result = verify_dane(&[], &fingerprint, DanePolicy::Required, &fqdn, 443);
497        assert!(matches!(result, Err(DaneError::NoTlsaRecords { .. })));
498    }
499
500    #[test]
501    fn test_verify_dane_match() {
502        let fqdn = Fqdn::new("test.example.com").unwrap();
503        let hash = hex::decode("e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904")
504            .unwrap();
505
506        let record = TlsaRecord::new(
507            TlsaUsage::DomainIssuedCertificate,
508            TlsaSelector::FullCertificate,
509            TlsaMatchingType::Sha256,
510            hash,
511        );
512
513        let fingerprint = CertFingerprint::parse(
514            "SHA256:e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904",
515        )
516        .unwrap();
517
518        let result =
519            verify_dane(&[record], &fingerprint, DanePolicy::Required, &fqdn, 443).unwrap();
520        assert!(matches!(result, DaneVerificationResult::Verified { .. }));
521    }
522
523    #[test]
524    fn test_verify_dane_mismatch() {
525        let fqdn = Fqdn::new("test.example.com").unwrap();
526        let hash = hex::decode("e7b64d16f42055d6faf382a43dc35b98be76aba0db145a904b590a034b33b904")
527            .unwrap();
528
529        let record = TlsaRecord::new(
530            TlsaUsage::DomainIssuedCertificate,
531            TlsaSelector::FullCertificate,
532            TlsaMatchingType::Sha256,
533            hash,
534        );
535
536        let fingerprint = CertFingerprint::parse(
537            "SHA256:0000000000000000000000000000000000000000000000000000000000000000",
538        )
539        .unwrap();
540
541        let result = verify_dane(&[record], &fingerprint, DanePolicy::Required, &fqdn, 443);
542        assert!(matches!(result, Err(DaneError::FingerprintMismatch)));
543    }
544
545    #[test]
546    fn test_verification_result_is_acceptable() {
547        let record = TlsaRecord::new(
548            TlsaUsage::DomainIssuedCertificate,
549            TlsaSelector::FullCertificate,
550            TlsaMatchingType::Sha256,
551            vec![0; 32],
552        );
553
554        // Verified is always acceptable
555        let verified = DaneVerificationResult::Verified {
556            matched_record: record,
557        };
558        assert!(verified.is_acceptable(DanePolicy::Disabled));
559        assert!(verified.is_acceptable(DanePolicy::ValidateIfPresent));
560        assert!(verified.is_acceptable(DanePolicy::Required));
561
562        // NoRecords is acceptable unless Required
563        let no_records = DaneVerificationResult::NoRecords;
564        assert!(no_records.is_acceptable(DanePolicy::Disabled));
565        assert!(no_records.is_acceptable(DanePolicy::ValidateIfPresent));
566        assert!(!no_records.is_acceptable(DanePolicy::Required));
567
568        // Skipped is always acceptable
569        let skipped = DaneVerificationResult::Skipped;
570        assert!(skipped.is_acceptable(DanePolicy::Disabled));
571        assert!(skipped.is_acceptable(DanePolicy::ValidateIfPresent));
572        assert!(skipped.is_acceptable(DanePolicy::Required));
573
574        // Mismatch is never acceptable
575        let mismatch = DaneVerificationResult::Mismatch { records_checked: 1 };
576        assert!(!mismatch.is_acceptable(DanePolicy::Disabled));
577        assert!(!mismatch.is_acceptable(DanePolicy::ValidateIfPresent));
578        assert!(!mismatch.is_acceptable(DanePolicy::Required));
579    }
580
581    // ── TlsaRecord::from_rdata edge cases ────────────────────────────
582
583    #[test]
584    fn test_tlsa_from_rdata_too_short() {
585        let result = TlsaRecord::from_rdata(&[3, 0]);
586        assert!(result.is_err());
587        assert!(matches!(
588            result.unwrap_err(),
589            DaneError::InvalidRecord { .. }
590        ));
591    }
592
593    #[test]
594    fn test_tlsa_from_rdata_empty() {
595        let result = TlsaRecord::from_rdata(&[]);
596        assert!(result.is_err());
597    }
598
599    // ── TryFrom<u8> invalid values ───────────────────────────────────
600
601    #[test]
602    fn test_tlsa_usage_invalid() {
603        let result = TlsaUsage::try_from(4_u8);
604        assert!(result.is_err());
605    }
606
607    #[test]
608    fn test_tlsa_selector_invalid() {
609        let result = TlsaSelector::try_from(2_u8);
610        assert!(result.is_err());
611    }
612
613    #[test]
614    fn test_tlsa_matching_type_invalid() {
615        let result = TlsaMatchingType::try_from(3_u8);
616        assert!(result.is_err());
617    }
618
619    // ── is_verifiable ────────────────────────────────────────────────
620
621    #[test]
622    fn test_is_verifiable_true() {
623        let record = TlsaRecord::new(
624            TlsaUsage::DomainIssuedCertificate,
625            TlsaSelector::FullCertificate,
626            TlsaMatchingType::Sha256,
627            vec![0; 32],
628        );
629        assert!(record.is_verifiable());
630    }
631
632    #[test]
633    fn test_is_verifiable_wrong_usage() {
634        let record = TlsaRecord::new(
635            TlsaUsage::CaConstraint,
636            TlsaSelector::FullCertificate,
637            TlsaMatchingType::Sha256,
638            vec![0; 32],
639        );
640        assert!(!record.is_verifiable());
641    }
642
643    #[test]
644    fn test_is_verifiable_wrong_selector() {
645        let record = TlsaRecord::new(
646            TlsaUsage::DomainIssuedCertificate,
647            TlsaSelector::SubjectPublicKeyInfo,
648            TlsaMatchingType::Sha256,
649            vec![0; 32],
650        );
651        assert!(!record.is_verifiable());
652    }
653
654    #[test]
655    fn test_is_verifiable_wrong_matching_type() {
656        let record = TlsaRecord::new(
657            TlsaUsage::DomainIssuedCertificate,
658            TlsaSelector::FullCertificate,
659            TlsaMatchingType::Sha512,
660            vec![0; 64],
661        );
662        assert!(!record.is_verifiable());
663    }
664
665    // ── matches_fingerprint edge cases ───────────────────────────────
666
667    #[test]
668    fn test_matches_fingerprint_non_dane_ee() {
669        let hash = vec![0u8; 32];
670        let record = TlsaRecord::new(
671            TlsaUsage::CaConstraint,
672            TlsaSelector::FullCertificate,
673            TlsaMatchingType::Sha256,
674            hash,
675        );
676        let fp = CertFingerprint::from_bytes([0u8; 32]);
677        assert_eq!(record.matches_fingerprint(&fp), None);
678    }
679
680    #[test]
681    fn test_matches_fingerprint_non_sha256() {
682        let hash = vec![0u8; 64];
683        let record = TlsaRecord::new(
684            TlsaUsage::DomainIssuedCertificate,
685            TlsaSelector::FullCertificate,
686            TlsaMatchingType::Sha512,
687            hash,
688        );
689        let fp = CertFingerprint::from_bytes([0u8; 32]);
690        assert_eq!(record.matches_fingerprint(&fp), None);
691    }
692
693    // ── DaneVerificationResult::DnssecFailed ─────────────────────────
694
695    #[test]
696    fn test_dnssec_failed_is_not_acceptable() {
697        let result = DaneVerificationResult::DnssecFailed;
698        assert!(!result.is_acceptable(DanePolicy::Disabled));
699        assert!(!result.is_acceptable(DanePolicy::ValidateIfPresent));
700        assert!(!result.is_acceptable(DanePolicy::Required));
701    }
702}