Coverage Report

Created: 2025-12-12 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hickory-dns/crates/proto/src/dnssec/handle.rs
Line
Count
Source
1
// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2
//
3
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4
// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5
// https://opensource.org/licenses/MIT>, at your option. This file may not be
6
// copied, modified, or distributed except according to those terms.
7
8
//! The `DnssecDnsHandle` is used to validate all DNS responses for correct DNSSEC signatures.
9
10
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec::Vec};
11
use core::{
12
    clone::Clone,
13
    fmt::Display,
14
    hash::{Hash, Hasher},
15
    ops::RangeInclusive,
16
    pin::Pin,
17
    time::Duration,
18
};
19
use std::{
20
    collections::{HashMap, HashSet, hash_map::DefaultHasher},
21
    time::Instant,
22
};
23
24
use futures_util::{
25
    future::{self, FutureExt},
26
    stream::{self, Stream, StreamExt},
27
};
28
use lru_cache::LruCache;
29
use parking_lot::Mutex;
30
use tracing::{debug, error, trace, warn};
31
32
use crate::{
33
    dnssec::{
34
        Proof, ProofError, ProofErrorKind, TrustAnchors, Verifier,
35
        nsec3::verify_nsec3,
36
        rdata::{DNSKEY, DNSSECRData, DS, NSEC, RRSIG},
37
    },
38
    error::{DnsError, NetError, NoRecords},
39
    op::{DnsRequest, DnsRequestOptions, DnsResponse, Edns, Message, OpCode, Query, ResponseCode},
40
    rr::{Name, RData, Record, RecordType, SerialNumber, resource::RecordRef},
41
    runtime::{RuntimeProvider, Time},
42
    xfer::{FirstAnswer, dns_handle::DnsHandle},
43
};
44
45
use self::rrset::Rrset;
46
47
/// Performs DNSSEC validation of all DNS responses from the wrapped DnsHandle
48
///
49
/// This wraps a DnsHandle, changing the implementation `send()` to validate all
50
///  message responses for Query operations. Update operation responses are not validated by
51
///  this process.
52
#[derive(Clone)]
53
#[must_use = "queries can only be sent through a DnsHandle"]
54
pub struct DnssecDnsHandle<H> {
55
    handle: H,
56
    trust_anchor: Arc<TrustAnchors>,
57
    request_depth: usize,
58
    nsec3_soft_iteration_limit: u16,
59
    nsec3_hard_iteration_limit: u16,
60
    validation_cache: ValidationCache,
61
}
62
63
impl<H: DnsHandle> DnssecDnsHandle<H> {
64
    /// Create a new DnssecDnsHandle wrapping the specified handle.
65
    ///
66
    /// This uses the compiled in TrustAnchor default trusted keys.
67
    ///
68
    /// # Arguments
69
    /// * `handle` - handle to use for all connections to a remote server.
70
0
    pub fn new(handle: H) -> Self {
71
0
        Self::with_trust_anchor(handle, Arc::new(TrustAnchors::default()))
72
0
    }
73
74
    /// Create a new DnssecDnsHandle wrapping the specified handle.
75
    ///
76
    /// This allows a custom TrustAnchor to be define.
77
    ///
78
    /// # Arguments
79
    /// * `handle` - handle to use for all connections to a remote server.
80
    /// * `trust_anchor` - custom DNSKEYs that will be trusted, can be used to pin trusted keys.
81
0
    pub fn with_trust_anchor(handle: H, trust_anchor: Arc<TrustAnchors>) -> Self {
82
0
        Self {
83
0
            handle,
84
0
            trust_anchor,
85
0
            request_depth: 0,
86
0
            // These default values are based on
87
0
            // [RFC 9276 Appendix A](https://www.rfc-editor.org/rfc/rfc9276.html#appendix-A)
88
0
            nsec3_soft_iteration_limit: 100,
89
0
            nsec3_hard_iteration_limit: 500,
90
0
            validation_cache: ValidationCache::new(DEFAULT_VALIDATION_CACHE_SIZE),
91
0
        }
92
0
    }
93
94
    /// Set custom NSEC3 iteration limits
95
    ///
96
    /// # Arguments
97
    /// * `soft_limit` - the soft limit for NSEC3 iterations. NSEC3 records with iteration counts
98
    ///   above this limit, but below the hard limit will evaluate to Proof::Insecure.
99
    /// * `hard_limit` - the hard limit for NSEC3 iterations. NSEC3 records with iteration counts
100
    ///   above this limit will evaluate to Proof::Bogus.
101
0
    pub fn nsec3_iteration_limits(
102
0
        mut self,
103
0
        soft_limit: Option<u16>,
104
0
        hard_limit: Option<u16>,
105
0
    ) -> Self {
106
0
        if let Some(soft) = soft_limit {
107
0
            self.nsec3_soft_iteration_limit = soft;
108
0
        }
109
110
0
        if let Some(hard) = hard_limit {
111
0
            self.nsec3_hard_iteration_limit = hard;
112
0
        }
113
114
0
        self
115
0
    }
116
117
    /// Set a custom validation cache size
118
    ///
119
    /// # Arguments
120
    /// * `capacity` - the desired capacity of the DNSSEC validation cache.
121
0
    pub fn validation_cache_size(mut self, capacity: usize) -> Self {
122
0
        self.validation_cache = ValidationCache::new(capacity);
123
0
        self
124
0
    }
125
126
    /// Set custom negative response validation cache TTL range
127
    ///
128
    /// # Arguments
129
    /// * `ttl` - A range of permissible TTL values for negative responses.
130
    ///
131
    /// Validation cache TTLs are based on the Rrset TTL value, but will be clamped to
132
    /// this value, if specified, for negative responses.
133
0
    pub fn negative_validation_ttl(mut self, ttl: RangeInclusive<Duration>) -> Self {
134
0
        self.validation_cache.negative_ttl = Some(ttl);
135
0
        self
136
0
    }
137
138
    /// Set custom positive response validation cache TTL range
139
    ///
140
    /// # Arguments
141
    /// * `ttl` - A range of permissible TTL values for positive responses.
142
    ///
143
    /// Validation cache TTLs are based on the Rrset TTL value, but will be clamped to
144
    /// this value, if specified, for positive responses.
145
0
    pub fn positive_validation_ttl(mut self, ttl: RangeInclusive<Duration>) -> Self {
146
0
        self.validation_cache.positive_ttl = Some(ttl);
147
0
        self
148
0
    }
149
150
0
    async fn verify_response(
151
0
        self,
152
0
        result: Result<DnsResponse, NetError>,
153
0
        query: Query,
154
0
        options: DnsRequestOptions,
155
0
    ) -> Result<DnsResponse, NetError> {
156
0
        let mut message = match result {
157
0
            Ok(response) => response,
158
            // Translate NoRecordsFound errors into a DnsResponse message so the rest of the
159
            // DNSSEC handler chain can validate negative responses.
160
            Err(NetError::Dns(DnsError::NoRecordsFound(NoRecords {
161
0
                query,
162
0
                authorities,
163
0
                response_code,
164
                ..
165
            }))) => {
166
0
                debug!("translating NoRecordsFound to DnsResponse for {query}");
167
0
                let mut msg = Message::query();
168
0
                msg.add_query(*query);
169
0
                msg.set_response_code(response_code);
170
171
0
                if let Some(authorities) = authorities {
172
0
                    for record in authorities.iter() {
173
0
                        msg.add_authority(record.clone());
174
0
                    }
175
0
                }
176
177
0
                match DnsResponse::from_message(msg) {
178
0
                    Ok(response) => response,
179
0
                    Err(err) => {
180
0
                        return Err(NetError::from(format!(
181
0
                            "unable to construct DnsResponse: {err:?}"
182
0
                        )));
183
                    }
184
                }
185
            }
186
0
            Err(err) => return Err(err),
187
        };
188
189
0
        debug!(
190
0
            "validating message_response: {}, with {} trust_anchors",
191
0
            message.id(),
192
0
            self.trust_anchor.len(),
193
        );
194
195
        // use the same current time value for all rrsig + rrset pairs.
196
0
        let current_time = <H::Runtime as RuntimeProvider>::Timer::current_time() as u32;
197
198
        // group the record sets by name and type
199
        //  each rrset type needs to validated independently
200
0
        let answers = message.take_answers();
201
0
        let authorities = message.take_authorities();
202
0
        let additionals = message.take_additionals();
203
204
0
        let answers = self
205
0
            .verify_rrsets(&query, answers, options, current_time)
206
0
            .await;
207
0
        let authorities = self
208
0
            .verify_rrsets(&query, authorities, options, current_time)
209
0
            .await;
210
0
        let additionals = self
211
0
            .verify_rrsets(&query, additionals, options, current_time)
212
0
            .await;
213
214
        // If we have any wildcard records, they must be validated with covering
215
        // NSEC/NSEC3 records.  RFC 4035 5.3.4, 5.4, and RFC 5155 7.2.6.
216
0
        let must_validate_nsec = answers.iter().any(|rr| match rr.data() {
217
0
            RData::DNSSEC(DNSSECRData::RRSIG(rrsig)) => {
218
0
                rrsig.input().num_labels < rr.name().num_labels()
219
            }
220
0
            _ => false,
221
0
        });
222
223
0
        message.insert_answers(answers);
224
0
        message.insert_authorities(authorities);
225
0
        message.insert_additionals(additionals);
226
227
0
        if !message.authorities().is_empty()
228
0
            && message
229
0
                .authorities()
230
0
                .iter()
231
0
                .all(|x| x.proof() == Proof::Insecure)
232
        {
233
0
            return Ok(message);
234
0
        }
235
236
0
        let nsec3s = message
237
0
            .authorities()
238
0
            .iter()
239
0
            .filter_map(|rr| {
240
0
                if message
241
0
                    .authorities()
242
0
                    .iter()
243
0
                    .any(|r| r.name() == rr.name() && r.proof() == Proof::Secure)
244
                {
245
0
                    match rr.data() {
246
0
                        RData::DNSSEC(DNSSECRData::NSEC3(nsec3)) => Some((rr.name(), nsec3)),
247
0
                        _ => None,
248
                    }
249
                } else {
250
0
                    None
251
                }
252
0
            })
253
0
            .collect::<Vec<_>>();
254
255
0
        let nsecs = message
256
0
            .authorities()
257
0
            .iter()
258
0
            .filter_map(|rr| {
259
0
                if message
260
0
                    .authorities()
261
0
                    .iter()
262
0
                    .any(|r| r.name() == rr.name() && r.proof() == Proof::Secure)
263
                {
264
0
                    match rr.data() {
265
0
                        RData::DNSSEC(DNSSECRData::NSEC(nsec)) => Some((rr.name(), nsec)),
266
0
                        _ => None,
267
                    }
268
                } else {
269
0
                    None
270
                }
271
0
            })
272
0
            .collect::<Vec<_>>();
273
274
        // Both NSEC and NSEC3 records cannot coexist during
275
        // transition periods, as per RFC 5515 10.4.3 and
276
        // 10.5.2
277
0
        let nsec_proof = match (!nsec3s.is_empty(), !nsecs.is_empty(), must_validate_nsec) {
278
0
            (true, false, _) => verify_nsec3(
279
0
                &query,
280
0
                find_soa_name(&message),
281
0
                message.response_code(),
282
0
                message.answers(),
283
0
                &nsec3s,
284
0
                self.nsec3_soft_iteration_limit,
285
0
                self.nsec3_hard_iteration_limit,
286
            ),
287
0
            (false, true, _) => verify_nsec(
288
0
                &query,
289
0
                find_soa_name(&message),
290
0
                message.response_code(),
291
0
                message.answers(),
292
0
                nsecs.as_slice(),
293
            ),
294
            (true, true, _) => {
295
0
                warn!(
296
0
                    "response contains both NSEC and NSEC3 records\nQuery:\n{query:?}\nResponse:\n{message:?}"
297
                );
298
0
                Proof::Bogus
299
            }
300
            (false, false, true) => {
301
0
                warn!("response contains wildcard RRSIGs, but no NSEC/NSEC3s are present.");
302
0
                Proof::Bogus
303
            }
304
            (false, false, false) => {
305
                // Return Ok if there were no NSEC/NSEC3 records and no wildcard RRSIGs.
306
0
                if !message.answers().is_empty() {
307
0
                    return Ok(message);
308
0
                }
309
310
                // Return Ok if the zone is insecure
311
0
                if let Err(err) = self.find_ds_records(query.name().clone(), options).await {
312
0
                    if err.proof == Proof::Insecure {
313
0
                        return Ok(message);
314
0
                    }
315
0
                }
316
317
                // If neither of the two conditions above are true, the response is Bogus - we should
318
                // have a covering NSEC/NSEC3 record for this scenario.
319
0
                warn!(
320
0
                    "response does not contain NSEC or NSEC3 records. Query: {query:?} response: {message:?}"
321
                );
322
0
                Proof::Bogus
323
            }
324
        };
325
326
0
        if !nsec_proof.is_secure() {
327
0
            debug!("returning Nsec error for {} {nsec_proof}", query.name());
328
            // TODO change this to remove the NSECs, like we do for the others?
329
0
            return Err(NetError::from(DnsError::Nsec {
330
0
                query: Box::new(query.clone()),
331
0
                response: Box::new(message),
332
0
                proof: nsec_proof,
333
0
            }));
334
0
        }
335
336
0
        Ok(message)
337
0
    }
338
339
    /// This pulls all answers returned in a Message response and returns a future which will
340
    ///  validate all of them.
341
0
    async fn verify_rrsets(
342
0
        &self,
343
0
        query: &Query,
344
0
        records: Vec<Record>,
345
0
        options: DnsRequestOptions,
346
0
        current_time: u32,
347
0
    ) -> Vec<Record> {
348
0
        let mut rrset_types: HashSet<(Name, RecordType)> = HashSet::new();
349
350
0
        for rrset in records
351
0
            .iter()
352
0
            .filter(|rr| {
353
0
                rr.record_type() != RecordType::RRSIG &&
354
                // if we are at a depth greater than 1, we are only interested in proving evaluation chains
355
                //   this means that only DNSKEY, DS, NSEC, and NSEC3 are interesting at that point.
356
                //   this protects against looping over things like NS records and DNSKEYs in responses.
357
                // TODO: is there a cleaner way to prevent cycles in the evaluations?
358
0
                (self.request_depth <= 1 || matches!(
359
0
                    rr.record_type(),
360
                    RecordType::DNSKEY | RecordType::DS | RecordType::NSEC | RecordType::NSEC3,
361
                ))
362
0
            })
363
0
            .map(|rr| (rr.name().clone(), rr.record_type()))
364
0
        {
365
0
            rrset_types.insert(rrset);
366
0
        }
367
368
        // there were no records to verify
369
0
        if rrset_types.is_empty() {
370
0
            return records;
371
0
        }
372
373
        // Records for return, eventually, all records will be returned in here
374
0
        let mut return_records = Vec::with_capacity(records.len());
375
376
        // Removing the RRSIGs from the original records, the rest of the records will be mutable to remove those evaluated
377
        //    and the remainder after all evalutions will be returned.
378
0
        let (mut rrsigs, mut records) = records
379
0
            .into_iter()
380
0
            .partition::<Vec<_>, _>(|r| r.record_type().is_rrsig());
381
382
0
        for (name, record_type) in rrset_types {
383
            // collect all the rrsets to verify
384
            let current_rrset;
385
0
            (current_rrset, records) = records
386
0
                .into_iter()
387
0
                .partition::<Vec<_>, _>(|rr| rr.record_type() == record_type && rr.name() == &name);
388
389
            let current_rrsigs;
390
0
            (current_rrsigs, rrsigs) = rrsigs.into_iter().partition::<Vec<_>, _>(|rr| {
391
0
                rr.try_borrow::<RRSIG>()
392
0
                    .map(|rr| rr.name() == &name && rr.data().input().type_covered == record_type)
393
0
                    .unwrap_or_default()
394
0
            });
395
396
            // TODO: we can do a better job here, no need for all the vec creation and clones in the Rrset.
397
0
            let mut rrs_to_verify = current_rrset.iter();
398
0
            let mut rrset = Rrset::new(rrs_to_verify.next().unwrap());
399
0
            rrs_to_verify.for_each(|rr| rrset.add(rr));
400
401
            // RRSIGS are never modified after this point
402
0
            let rrsigs: Vec<_> = current_rrsigs
403
0
                .iter()
404
0
                .filter_map(|rr| rr.try_borrow::<RRSIG>())
405
0
                .filter(|rr| rr.name() == &name)
406
0
                .filter(|rrsig| rrsig.data().input().type_covered == record_type)
407
0
                .collect();
408
409
            // if there is already an active validation going on, assume the other validation will
410
            //  complete properly or error if it is invalid
411
412
            // TODO: support non-IN classes?
413
0
            debug!(
414
0
                "verifying: {name} record_type: {record_type}, rrsigs: {rrsig_len}",
415
0
                rrsig_len = rrsigs.len()
416
            );
417
418
            // verify this rrset
419
0
            let proof = self
420
0
                .verify_rrset(RrsetVerificationContext {
421
0
                    query,
422
0
                    rrset: &rrset,
423
0
                    rrsigs,
424
0
                    options,
425
0
                    current_time,
426
0
                })
427
0
                .await;
428
429
0
            let proof = match proof {
430
0
                Ok(proof) => {
431
0
                    debug!("verified: {name} record_type: {record_type}",);
432
0
                    proof
433
                }
434
0
                Err(err) => {
435
0
                    match err.kind() {
436
                        ProofErrorKind::DsResponseNsec { .. } => {
437
0
                            debug!("verified insecure {name}/{record_type}")
438
                        }
439
0
                        kind => {
440
0
                            debug!("failed to verify: {name} record_type: {record_type}: {kind}")
441
                        }
442
                    }
443
0
                    RrsetProof {
444
0
                        proof: err.proof,
445
0
                        adjusted_ttl: None,
446
0
                        rrsig_index: None,
447
0
                    }
448
                }
449
            };
450
451
            let RrsetProof {
452
0
                proof,
453
0
                adjusted_ttl,
454
0
                rrsig_index: rrsig_idx,
455
0
            } = proof;
456
0
            for mut record in current_rrset {
457
0
                record.set_proof(proof);
458
0
                if let (Proof::Secure, Some(ttl)) = (proof, adjusted_ttl) {
459
0
                    record.set_ttl(ttl);
460
0
                }
461
462
0
                return_records.push(record);
463
            }
464
465
            // only mark the RRSIG used for the proof
466
0
            let mut current_rrsigs = current_rrsigs;
467
0
            if let Some(rrsig_idx) = rrsig_idx {
468
0
                if let Some(rrsig) = current_rrsigs.get_mut(rrsig_idx) {
469
0
                    rrsig.set_proof(proof);
470
0
                    if let (Proof::Secure, Some(ttl)) = (proof, adjusted_ttl) {
471
0
                        rrsig.set_ttl(ttl);
472
0
                    }
473
                } else {
474
0
                    warn!(
475
0
                        "bad rrsig index {rrsig_idx} rrsigs.len = {}",
476
0
                        current_rrsigs.len()
477
                    );
478
                }
479
0
            }
480
481
            // push all the RRSIGs back to the return
482
0
            return_records.extend(current_rrsigs);
483
        }
484
485
        // Add back all the RRSIGs and any records that were not verified
486
0
        return_records.extend(rrsigs);
487
0
        return_records.extend(records);
488
0
        return_records
489
0
    }
490
491
    /// Generic entrypoint to verify any RRSET against the provided signatures.
492
    ///
493
    /// Generally, the RRSET will be validated by `verify_default_rrset()`. There are additional
494
    ///  checks that happen after the RRSET is successfully validated. In the case of DNSKEYs this
495
    ///  triggers `verify_dnskey_rrset()`. If it's an NSEC record, then the NSEC record will be
496
    ///  validated to prove it's correctness. There is a special case for DNSKEY, where if the RRSET
497
    ///  is unsigned, `rrsigs` is empty, then an immediate `verify_dnskey_rrset()` is triggered. In
498
    ///  this case, it's possible the DNSKEY is a trust_anchor and is not self-signed.
499
    ///
500
    /// # Returns
501
    ///
502
    /// If Ok, returns an RrsetProof containing the proof, adjusted TTL, and an index of the RRSIG used for
503
    /// validation of the rrset.
504
0
    async fn verify_rrset(
505
0
        &self,
506
0
        context: RrsetVerificationContext<'_>,
507
0
    ) -> Result<RrsetProof, ProofError> {
508
0
        let key = context.key();
509
510
0
        if let Some(cached) = self.validation_cache.get(&key, &context) {
511
0
            return cached;
512
0
        }
513
514
        // DNSKEYS have different logic for their verification
515
0
        let proof = match context.rrset.record_type {
516
0
            RecordType::DNSKEY => self.verify_dnskey_rrset(&context).await,
517
0
            _ => self.verify_default_rrset(&context).await,
518
        };
519
520
0
        match &proof {
521
            // These could be transient errors that should be retried.
522
0
            Err(e) if matches!(e.kind(), ProofErrorKind::Net { .. }) => {
523
0
                debug!("not caching DNSSEC validation with ProofErrorKind::Net")
524
            }
525
0
            _ => {
526
0
                self.validation_cache.insert(proof.clone(), key, &context);
527
0
            }
528
        }
529
530
0
        proof
531
0
    }
532
533
    /// DNSKEY-specific verification
534
    ///
535
    /// A DNSKEY needs to be checked against a DS record provided by the parent zone.
536
    ///
537
    /// A DNSKEY that's part of the trust anchor does not need to have its DS record (which may
538
    /// not exist as it's the case of the root zone) nor its RRSIG validated. If an RRSIG is present
539
    /// it will be validated.
540
    ///
541
    /// # Return
542
    ///
543
    /// If Ok, returns an RrsetProof containing the proof, adjusted TTL, and an index of the RRSIG used for
544
    /// validation of the rrset.
545
    ///
546
    /// # Panics
547
    ///
548
    /// This method should only be called to validate DNSKEYs, see `verify_default_rrset` for other record types.
549
    ///  if a non-DNSKEY RRSET is passed into this method it will always panic.
550
0
    async fn verify_dnskey_rrset(
551
0
        &self,
552
0
        context: &RrsetVerificationContext<'_>,
553
0
    ) -> Result<RrsetProof, ProofError> {
554
        let RrsetVerificationContext {
555
0
            rrset,
556
0
            rrsigs,
557
0
            current_time,
558
0
            options,
559
            ..
560
0
        } = context;
561
562
        // Ensure that this method is not misused
563
0
        if RecordType::DNSKEY != rrset.record_type {
564
0
            panic!("All other RRSETs must use verify_default_rrset");
565
0
        }
566
567
0
        debug!(
568
0
            "dnskey validation {}, record_type: {:?}",
569
            rrset.name, rrset.record_type
570
        );
571
572
0
        let mut dnskey_proofs =
573
0
            Vec::<(Proof, Option<u32>, Option<usize>)>::with_capacity(rrset.records.len());
574
0
        dnskey_proofs.resize(rrset.records.len(), (Proof::Bogus, None, None));
575
576
        // check if the DNSKEYs are in the root store
577
0
        for (r, proof) in rrset.records.iter().zip(dnskey_proofs.iter_mut()) {
578
0
            let Some(dnskey) = r.try_borrow::<DNSKEY>() else {
579
0
                continue;
580
            };
581
582
0
            proof.0 = self.is_dnskey_in_root_store(&dnskey);
583
        }
584
585
        // if not all of the DNSKEYs are in the root store, then we need to look for DS records to verify
586
0
        let ds_records = if !dnskey_proofs.iter().all(|p| p.0.is_secure()) && !rrset.name.is_root()
587
        {
588
            // Need to get DS records for each DNSKEY.
589
            // Every DNSKEY other than the root zone's keys may have a corresponding DS record.
590
0
            self.fetch_ds_records(rrset.name.clone(), *options).await?
591
        } else {
592
0
            debug!("ignoring DS lookup for root zone or registered keys");
593
0
            Vec::default()
594
        };
595
596
        // if the DS records are not empty and they also have no supported algorithms, then this is INSECURE
597
        // for secure DS records the BOGUS check happens after DNSKEYs are evaluated against the DS
598
0
        if ds_records
599
0
            .iter()
600
0
            .filter(|ds| ds.proof().is_secure() || ds.proof().is_insecure())
601
0
            .all(|ds| {
602
0
                !ds.data().algorithm().is_supported() || !ds.data().digest_type().is_supported()
603
0
            })
604
0
            && !ds_records.is_empty()
605
        {
606
0
            debug!(
607
0
                "all dnskeys use unsupported algorithms and there are no supported DS records in the parent zone"
608
            );
609
            // cannot validate; mark as insecure
610
0
            return Err(ProofError::new(
611
0
                Proof::Insecure,
612
0
                ProofErrorKind::UnsupportedKeyAlgorithm,
613
0
            ));
614
0
        }
615
616
        // verify all dnskeys individually against the DS records
617
0
        for (r, proof) in rrset.records.iter().zip(dnskey_proofs.iter_mut()) {
618
0
            let Some(dnskey) = r.try_borrow() else {
619
0
                continue;
620
            };
621
622
0
            if proof.0.is_secure() {
623
0
                continue;
624
0
            }
625
626
            // need to track each proof on each dnskey to ensure they are all validated
627
0
            match verify_dnskey(&dnskey, &ds_records) {
628
0
                Ok(pf) => *proof = (pf, None, None),
629
0
                Err(err) => *proof = (err.proof, None, None),
630
            }
631
        }
632
633
        // There may have been a key-signing key for the zone,
634
        //   we need to verify all the other DNSKEYS in the zone against it (i.e. the rrset)
635
0
        for (i, rrsig) in rrsigs.iter().enumerate() {
636
            // These should all match, but double checking...
637
0
            let signer_name = &rrsig.data().input().signer_name;
638
639
0
            let rrset_proof = rrset
640
0
                .records
641
0
                .iter()
642
0
                .zip(dnskey_proofs.iter())
643
0
                .filter(|(_, (proof, ..))| proof.is_secure())
644
0
                .filter(|(r, _)| r.name() == signer_name)
645
0
                .filter_map(|(r, (proof, ..))| {
646
0
                    RecordRef::<'_, DNSKEY>::try_from(*r)
647
0
                        .ok()
648
0
                        .map(|r| (r, proof))
649
0
                })
650
0
                .find_map(|(dnskey, proof)| {
651
0
                    verify_rrset_with_dnskey(dnskey, *proof, rrsig, rrset, *current_time).ok()
652
0
                });
653
654
0
            if let Some(rrset_proof) = rrset_proof {
655
0
                return Ok(RrsetProof {
656
0
                    proof: rrset_proof.0,
657
0
                    adjusted_ttl: rrset_proof.1,
658
0
                    rrsig_index: Some(i),
659
0
                });
660
0
            }
661
        }
662
663
        // if it was just the root DNSKEYS with no RRSIG, we'll accept the entire set, or none
664
0
        if dnskey_proofs.iter().all(|(proof, ..)| proof.is_secure()) {
665
0
            let proof = dnskey_proofs.pop().unwrap(/* This can not happen due to above test */);
666
0
            return Ok(RrsetProof {
667
0
                proof: proof.0,
668
0
                adjusted_ttl: proof.1,
669
0
                rrsig_index: proof.2,
670
0
            });
671
0
        }
672
673
0
        if !ds_records.is_empty() {
674
            // there were DS records, but no DNSKEYs, we're in a bogus state
675
0
            trace!("bogus dnskey: {}", rrset.name);
676
0
            return Err(ProofError::new(
677
0
                Proof::Bogus,
678
0
                ProofErrorKind::DsRecordsButNoDnskey {
679
0
                    name: rrset.name.clone(),
680
0
                },
681
0
            ));
682
0
        }
683
684
        // There were DS records or RRSIGs, but none of the signatures could be validated, so we're in a
685
        // bogus state. If there was no DS record, it should have gotten an NSEC upstream, and returned
686
        // early above.
687
0
        trace!("no dnskey found: {}", rrset.name);
688
0
        Err(ProofError::new(
689
0
            Proof::Bogus,
690
0
            ProofErrorKind::DnskeyNotFound {
691
0
                name: rrset.name.clone(),
692
0
            },
693
0
        ))
694
0
    }
695
696
    /// Checks whether a DS RRset exists for the zone containing a name.
697
    ///
698
    /// Returns an error with an `Insecure` proof if the zone is proven to be insecure. Returns
699
    /// `Ok(())` if the zone is secure.
700
    ///
701
    /// This first finds the nearest zone cut at or above the given name, by making NS queries.
702
    /// Then, the DS RRset at the delegation point is requested. The DS response is validated to
703
    /// determine if any DS records exist or not, and thus whether the zone is secure, insecure, or
704
    /// bogus. See [RFC 6840 section 6.1](https://datatracker.ietf.org/doc/html/rfc6840#section-6.1)
705
    /// and [RFC 4035 section 4.2](https://datatracker.ietf.org/doc/html/rfc4035#section-4.2).
706
0
    async fn find_ds_records(
707
0
        &self,
708
0
        name: Name,
709
0
        options: DnsRequestOptions,
710
0
    ) -> Result<(), ProofError> {
711
0
        let mut ancestor = name.clone();
712
0
        let zone = loop {
713
0
            if ancestor.is_root() {
714
0
                return Err(ProofError::ds_should_exist(name));
715
0
            }
716
717
            // Make an un-verified request for the NS RRset at this ancestor name.
718
0
            let query = Query::query(ancestor.clone(), RecordType::NS);
719
0
            let result = self
720
0
                .handle
721
0
                .lookup(query.clone(), options)
722
0
                .first_answer()
723
0
                .await;
724
0
            match result {
725
0
                Ok(response) => {
726
0
                    if response.all_sections().any(|record| {
727
0
                        record.record_type() == RecordType::NS && record.name() == &ancestor
728
0
                    }) {
729
0
                        break ancestor;
730
0
                    }
731
                }
732
0
                Err(e) if e.is_no_records_found() || e.is_nx_domain() => {}
733
0
                Err(net) => {
734
0
                    return Err(ProofError::new(
735
0
                        Proof::Bogus,
736
0
                        ProofErrorKind::Net { query, net },
737
0
                    ));
738
                }
739
            }
740
741
0
            ancestor = ancestor.base_name();
742
        };
743
744
0
        self.fetch_ds_records(zone, options).await?;
745
0
        Ok(())
746
0
    }
747
748
    /// Retrieves DS records for the given zone.
749
0
    async fn fetch_ds_records(
750
0
        &self,
751
0
        zone: Name,
752
0
        options: DnsRequestOptions,
753
0
    ) -> Result<Vec<Record<DS>>, ProofError> {
754
0
        let ds_message = self
755
0
            .lookup(Query::query(zone.clone(), RecordType::DS), options)
756
0
            .first_answer()
757
0
            .await;
758
759
0
        let error_opt = match ds_message {
760
0
            Ok(mut ds_message)
761
0
                if ds_message
762
0
                    .answers()
763
0
                    .iter()
764
0
                    .filter(|r| r.record_type() == RecordType::DS)
765
0
                    .any(|r| r.proof().is_secure()) =>
766
            {
767
                // This is a secure DS RRset.
768
769
0
                let all_records = ds_message.take_answers().into_iter().filter_map(|r| {
770
0
                    r.map(|data| match data {
771
0
                        RData::DNSSEC(DNSSECRData::DS(ds)) => Some(ds),
772
0
                        _ => None,
773
0
                    })
774
0
                });
775
776
0
                let mut supported_records = vec![];
777
0
                let mut all_unknown = None;
778
0
                for record in all_records {
779
                    // A chain can be either SECURE or INSECURE, but we should not trust BOGUS or other
780
                    // records.
781
0
                    if (!record.data().algorithm().is_supported()
782
0
                        || !record.data().digest_type().is_supported())
783
0
                        && (record.proof().is_secure() || record.proof().is_insecure())
784
                    {
785
0
                        all_unknown.get_or_insert(true);
786
0
                        continue;
787
0
                    }
788
0
                    all_unknown = Some(false);
789
790
0
                    supported_records.push(record);
791
                }
792
793
0
                if all_unknown.unwrap_or(false) {
794
0
                    return Err(ProofError::new(
795
0
                        Proof::Insecure,
796
0
                        ProofErrorKind::UnknownKeyAlgorithm,
797
0
                    ));
798
0
                } else if !supported_records.is_empty() {
799
0
                    return Ok(supported_records);
800
                } else {
801
0
                    None
802
                }
803
            }
804
0
            Ok(response) => {
805
0
                let any_ds_rr = response
806
0
                    .answers()
807
0
                    .iter()
808
0
                    .any(|r| r.record_type() == RecordType::DS);
809
0
                if any_ds_rr {
810
0
                    None
811
                } else {
812
                    // If the response was an authenticated proof of nonexistence, then we have an
813
                    // insecure zone.
814
0
                    debug!("marking {zone} as insecure based on secure NSEC/NSEC3 proof");
815
0
                    return Err(ProofError::new(
816
0
                        Proof::Insecure,
817
0
                        ProofErrorKind::DsResponseNsec { name: zone },
818
0
                    ));
819
                }
820
            }
821
0
            Err(error) => Some(error),
822
        };
823
824
        // If the response was an empty DS RRset that was itself insecure, then we have another insecure zone.
825
0
        let dns_err = error_opt.as_ref().and_then(|e| match &e {
826
0
            NetError::Dns(err) => Some(err),
827
0
            _ => None,
828
0
        });
829
830
0
        if let Some(DnsError::Nsec { query, proof, .. }) = dns_err {
831
0
            if proof.is_insecure() {
832
0
                debug!(
833
0
                    "marking {} as insecure based on insecure NSEC/NSEC3 proof",
834
0
                    query.name()
835
                );
836
0
                return Err(ProofError::new(
837
0
                    Proof::Insecure,
838
0
                    ProofErrorKind::DsResponseNsec {
839
0
                        name: query.name().to_owned(),
840
0
                    },
841
0
                ));
842
0
            }
843
0
        }
844
845
0
        Err(ProofError::ds_should_exist(zone))
846
0
    }
847
848
    /// Verifies that the key is a trust anchor.
849
    ///
850
    /// # Returns
851
    ///
852
    /// Proof::Secure if registered in the root store, Proof::Bogus if not
853
0
    fn is_dnskey_in_root_store(&self, rr: &RecordRef<'_, DNSKEY>) -> Proof {
854
0
        let dns_key = rr.data();
855
0
        let pub_key = dns_key.public_key();
856
857
        // Checks to see if the key is valid against the registered root certificates
858
0
        if self.trust_anchor.contains(pub_key) {
859
0
            debug!(
860
0
                "validated dnskey with trust_anchor: {}, {dns_key}",
861
0
                rr.name(),
862
            );
863
864
0
            Proof::Secure
865
        } else {
866
0
            Proof::Bogus
867
        }
868
0
    }
869
870
    /// Verifies that a given RRSET is validly signed by any of the specified RRSIGs.
871
    ///
872
    /// Invalid RRSIGs will be ignored. RRSIGs will only be validated against DNSKEYs which can
873
    ///  be validated through a chain back to the `trust_anchor`. As long as one RRSIG is valid,
874
    ///  then the RRSET will be valid.
875
    ///
876
    /// # Returns
877
    ///
878
    /// On Ok, the set of (Proof, AdjustedTTL, and IndexOfRRSIG) is returned, where the index is the one of the RRSIG that validated
879
    ///   the Rrset
880
    ///
881
    /// # Panics
882
    ///
883
    /// This method should never be called to validate DNSKEYs, see `verify_dnskey_rrset` instead.
884
    ///  if a DNSKEY RRSET is passed into this method it will always panic.
885
0
    async fn verify_default_rrset(
886
0
        &self,
887
0
        context: &RrsetVerificationContext<'_>,
888
0
    ) -> Result<RrsetProof, ProofError> {
889
        let RrsetVerificationContext {
890
0
            query: original_query,
891
0
            rrset,
892
0
            rrsigs,
893
0
            current_time,
894
0
            options,
895
0
        } = context;
896
897
        // Ensure that this method is not misused
898
0
        if RecordType::DNSKEY == rrset.record_type {
899
0
            panic!("DNSKEYs must be validated with verify_dnskey_rrset");
900
0
        }
901
902
0
        if rrsigs.is_empty() {
903
            // Decide if we're:
904
            //    1) "insecure", the zone has a valid NSEC for the DS record in the parent zone
905
            //    2) "bogus", the parent zone has a valid DS record, but the child zone didn't have the RRSIGs/DNSKEYs
906
            //       or the parent zone has a DS record without covering RRSIG records.
907
0
            if rrset.record_type != RecordType::DS {
908
0
                let mut search_name = rrset.name.clone();
909
0
                if rrset.record_type == RecordType::NSEC3 {
910
0
                    // No need to look for a zone cut at an NSEC3 owner name. Look at its parent
911
0
                    // instead, which ought to be a zone apex.
912
0
                    search_name = search_name.base_name();
913
0
                }
914
915
0
                self.find_ds_records(search_name, *options).await?; // insecure will return early here
916
0
            }
917
918
0
            return Err(ProofError::new(
919
0
                Proof::Bogus,
920
0
                ProofErrorKind::RrsigsNotPresent {
921
0
                    name: rrset.name.clone(),
922
0
                    record_type: rrset.record_type,
923
0
                },
924
0
            ));
925
0
        }
926
927
        // the record set is going to be shared across a bunch of futures, Arc for that.
928
0
        trace!(
929
0
            "default validation {}, record_type: {:?}",
930
            rrset.name, rrset.record_type
931
        );
932
933
        // we can validate with any of the rrsigs...
934
        //  i.e. the first that validates is good enough
935
        //  TODO: could there be a cert downgrade attack here with a MITM stripping stronger RRSIGs?
936
        //         we could check for the strongest RRSIG and only use that...
937
        //         though, since the entire package isn't signed any RRSIG could have been injected,
938
        //         right? meaning if there is an attack on any of the acceptable algorithms, we'd be
939
        //         susceptible until that algorithm is removed as an option.
940
        //        dns over TLS will mitigate this.
941
        //  TODO: strip RRSIGS to accepted algorithms and make algorithms configurable.
942
0
        let verifications = rrsigs
943
0
            .iter()
944
0
            .enumerate()
945
0
            .filter_map(|(i, rrsig)| {
946
0
                let query =
947
0
                    Query::query(rrsig.data().input().signer_name.clone(), RecordType::DNSKEY);
948
949
0
                if i > MAX_RRSIGS_PER_RRSET {
950
0
                    warn!("too many ({i}) RRSIGs for rrset {rrset:?}; skipping");
951
0
                    return None;
952
0
                }
953
954
                // TODO: Should this sig.signer_name should be confirmed to be in the same zone as the rrsigs and rrset?
955
                // Break verification cycle
956
0
                if query.name() == original_query.name()
957
0
                    && query.query_type() == original_query.query_type()
958
                {
959
0
                    warn!(
960
0
                        query_name = %query.name(),
961
0
                        query_type = %query.query_type(),
962
0
                        original_query_name = %original_query.name(),
963
0
                        original_query_type = %original_query.query_type(),
964
0
                        "stopping verification cycle in verify_default_rrset",
965
                    );
966
0
                    return None;
967
0
                }
968
969
                Some(
970
0
                    self.lookup(query.clone(), *options)
971
0
                        .first_answer()
972
0
                        .map(move |result| match result {
973
0
                            Ok(message) => {
974
0
                                Ok(verify_rrsig_with_keys(message, rrsig, rrset, *current_time)
975
0
                                    .map(|(proof, adjusted_ttl)| RrsetProof {
976
0
                                        proof,
977
0
                                        adjusted_ttl,
978
0
                                        rrsig_index: Some(i),
979
0
                                    }))
980
                            }
981
0
                            Err(net) => Err(ProofError::new(
982
0
                                Proof::Bogus,
983
0
                                ProofErrorKind::Net { query, net },
984
0
                            )),
985
0
                        }),
986
                )
987
0
            })
988
0
            .collect::<Vec<_>>();
989
990
        // if there are no available verifications, then we are in a failed state.
991
0
        if verifications.is_empty() {
992
0
            return Err(ProofError::new(
993
0
                Proof::Bogus,
994
0
                ProofErrorKind::RrsigsNotPresent {
995
0
                    name: rrset.name.clone(),
996
0
                    record_type: rrset.record_type,
997
0
                },
998
0
            ));
999
0
        }
1000
1001
        // as long as any of the verifications is good, then the RRSET is valid.
1002
0
        let select = future::select_ok(verifications);
1003
1004
        // this will return either a good result or the errors
1005
0
        let (proof, rest) = select.await?;
1006
0
        drop(rest);
1007
1008
0
        proof.ok_or_else(||
1009
            // we are in a bogus state, DS records were available (see beginning of function), but RRSIGs couldn't be verified
1010
0
            ProofError::new(Proof::Bogus, ProofErrorKind::RrsigsUnverified {
1011
0
                name: rrset.name.clone(),
1012
0
                record_type: rrset.record_type,
1013
0
            }
1014
        ))
1015
0
    }
1016
1017
    /// An internal function used to clone the handle, but maintain some information back to the
1018
    ///  original handle, such as the request_depth such that infinite recursion does
1019
    ///  not occur.
1020
0
    fn clone_with_context(&self) -> Self {
1021
0
        Self {
1022
0
            handle: self.handle.clone(),
1023
0
            trust_anchor: Arc::clone(&self.trust_anchor),
1024
0
            request_depth: self.request_depth + 1,
1025
0
            nsec3_soft_iteration_limit: self.nsec3_soft_iteration_limit,
1026
0
            nsec3_hard_iteration_limit: self.nsec3_hard_iteration_limit,
1027
0
            validation_cache: self.validation_cache.clone(),
1028
0
        }
1029
0
    }
1030
1031
    /// Get a reference to the underlying handle.
1032
0
    pub fn inner(&self) -> &H {
1033
0
        &self.handle
1034
0
    }
1035
}
1036
1037
#[cfg(any(feature = "std", feature = "no-std-rand"))]
1038
impl<H: DnsHandle> DnsHandle for DnssecDnsHandle<H> {
1039
    type Response = Pin<Box<dyn Stream<Item = Result<DnsResponse, NetError>> + Send>>;
1040
    type Runtime = H::Runtime;
1041
1042
0
    fn is_verifying_dnssec(&self) -> bool {
1043
        // This handler is always verifying...
1044
0
        true
1045
0
    }
1046
1047
0
    fn send(&self, mut request: DnsRequest) -> Self::Response {
1048
        // backstop
1049
0
        if self.request_depth > request.options().max_request_depth {
1050
0
            error!("exceeded max validation depth");
1051
0
            return Box::pin(stream::once(future::err(NetError::from(
1052
0
                "exceeded max validation depth",
1053
0
            ))));
1054
0
        }
1055
1056
        // dnssec only matters on queries.
1057
0
        match request.op_code() {
1058
0
            OpCode::Query => {}
1059
0
            _ => return Box::pin(self.handle.send(request)),
1060
        }
1061
1062
        // This will fail on no queries, that is a very odd type of request, isn't it?
1063
        // TODO: with mDNS there can be multiple queries
1064
0
        let Some(query) = request.queries().first().cloned() else {
1065
0
            return Box::pin(stream::once(future::err(NetError::from(
1066
0
                "no query in request",
1067
0
            ))));
1068
        };
1069
1070
0
        let handle = self.clone_with_context();
1071
0
        request
1072
0
            .extensions_mut()
1073
0
            .get_or_insert_with(Edns::new)
1074
0
            .enable_dnssec();
1075
1076
0
        request.set_authentic_data(true);
1077
0
        request.set_checking_disabled(false);
1078
0
        let options = *request.options();
1079
1080
0
        Box::pin(self.handle.send(request).then(move |result| {
1081
0
            handle
1082
0
                .clone()
1083
0
                .verify_response(result, query.clone(), options)
1084
0
        }))
1085
0
    }
1086
}
1087
1088
0
fn verify_rrsig_with_keys(
1089
0
    dnskey_message: DnsResponse,
1090
0
    rrsig: &RecordRef<'_, RRSIG>,
1091
0
    rrset: &Rrset<'_>,
1092
0
    current_time: u32,
1093
0
) -> Option<(Proof, Option<u32>)> {
1094
0
    let mut tag_count = HashMap::<u16, usize>::new();
1095
1096
0
    if (rrset.record_type == RecordType::NSEC || rrset.record_type == RecordType::NSEC3)
1097
0
        && rrset.name.num_labels() != rrsig.data().input().num_labels
1098
    {
1099
0
        warn!(
1100
0
            "{} record signature claims to be expanded from a wildcard",
1101
            rrset.record_type
1102
        );
1103
0
        return None;
1104
0
    }
1105
1106
    // DNSKEYs were already validated by the inner query in the above lookup
1107
0
    let dnskeys = dnskey_message.answers().iter().filter_map(|r| {
1108
0
        let dnskey = r.try_borrow::<DNSKEY>()?;
1109
1110
0
        let tag = match dnskey.data().calculate_key_tag() {
1111
0
            Ok(tag) => tag,
1112
0
            Err(e) => {
1113
0
                warn!("unable to calculate key tag: {e:?}; skipping key");
1114
0
                return None;
1115
            }
1116
        };
1117
1118
0
        match tag_count.get_mut(&tag) {
1119
0
            Some(n_keys) => {
1120
0
                *n_keys += 1;
1121
0
                if *n_keys > MAX_KEY_TAG_COLLISIONS {
1122
0
                    warn!("too many ({n_keys}) DNSKEYs with key tag {tag}; skipping");
1123
0
                    return None;
1124
0
                }
1125
            }
1126
0
            None => _ = tag_count.insert(tag, 1),
1127
        }
1128
1129
0
        Some(dnskey)
1130
0
    });
1131
1132
0
    let mut all_insecure = None;
1133
0
    for dnskey in dnskeys {
1134
0
        match dnskey.proof() {
1135
            Proof::Secure => {
1136
0
                all_insecure = Some(false);
1137
0
                if let Ok(proof) =
1138
0
                    verify_rrset_with_dnskey(dnskey, dnskey.proof(), rrsig, rrset, current_time)
1139
                {
1140
0
                    return Some((proof.0, proof.1));
1141
0
                }
1142
            }
1143
0
            Proof::Insecure => {
1144
0
                all_insecure.get_or_insert(true);
1145
0
            }
1146
0
            _ => all_insecure = Some(false),
1147
        }
1148
    }
1149
1150
0
    if all_insecure.unwrap_or(false) {
1151
        // inherit Insecure state
1152
0
        Some((Proof::Insecure, None))
1153
    } else {
1154
0
        None
1155
    }
1156
0
}
1157
1158
/// Find the SOA record, if present, in the response and return its name.
1159
///
1160
/// Note that a SOA record may not be present in all responses that must be NSEC/NSEC3 validated.
1161
/// See RFC 4035 B.4 - Referral to Signed Zone, B.5 Referral to Unsigned Zone, B.6 - Wildcard
1162
/// Expansion, RFC 5155 B.3 - Referral to an Opt-Out Unsigned Zone, and B.4 - Wildcard Expansion.
1163
0
fn find_soa_name(verified_message: &DnsResponse) -> Option<&Name> {
1164
0
    for record in verified_message.authorities() {
1165
0
        if record.record_type() == RecordType::SOA {
1166
0
            return Some(record.name());
1167
0
        }
1168
    }
1169
1170
0
    None
1171
0
}
1172
1173
/// This verifies a DNSKEY record against DS records from a secure delegation.
1174
0
fn verify_dnskey(
1175
0
    rr: &RecordRef<'_, DNSKEY>,
1176
0
    ds_records: &[Record<DS>],
1177
0
) -> Result<Proof, ProofError> {
1178
0
    let key_rdata = rr.data();
1179
0
    let key_tag = key_rdata.calculate_key_tag().map_err(|_| {
1180
0
        ProofError::new(
1181
0
            Proof::Insecure,
1182
0
            ProofErrorKind::ErrorComputingKeyTag {
1183
0
                name: rr.name().clone(),
1184
0
            },
1185
        )
1186
0
    })?;
1187
0
    let key_algorithm = key_rdata.algorithm();
1188
1189
0
    if !key_algorithm.is_supported() {
1190
0
        return Err(ProofError::new(
1191
0
            Proof::Insecure,
1192
0
            ProofErrorKind::UnsupportedKeyAlgorithm,
1193
0
        ));
1194
0
    }
1195
1196
    // DS check if covered by DS keys
1197
0
    let mut key_authentication_attempts = 0;
1198
0
    for r in ds_records.iter().filter(|ds| ds.proof().is_secure()) {
1199
0
        if r.data().algorithm() != key_algorithm {
1200
0
            trace!(
1201
0
                "skipping DS record due to algorithm mismatch, expected algorithm {}: ({}, {})",
1202
                key_algorithm,
1203
0
                r.name(),
1204
0
                r.data(),
1205
            );
1206
1207
0
            continue;
1208
0
        }
1209
1210
0
        if r.data().key_tag() != key_tag {
1211
0
            trace!(
1212
0
                "skipping DS record due to key tag mismatch, expected tag {key_tag}: ({}, {})",
1213
0
                r.name(),
1214
0
                r.data(),
1215
            );
1216
1217
0
            continue;
1218
0
        }
1219
1220
        // Count the number of DS records with the same algorithm and key tag as this DNSKEY.
1221
        // Ignore remaining DS records if there are too many key tag collisions. Doing so before
1222
        // checking hashes or signatures protects us from KeyTrap denial of service attacks.
1223
0
        key_authentication_attempts += 1;
1224
0
        if key_authentication_attempts > MAX_KEY_TAG_COLLISIONS {
1225
0
            warn!(
1226
                key_tag,
1227
                attempts = key_authentication_attempts,
1228
0
                "too many DS records with same key tag; skipping"
1229
            );
1230
0
            continue;
1231
0
        }
1232
1233
0
        if !r.data().covers(rr.name(), key_rdata).unwrap_or(false) {
1234
0
            continue;
1235
0
        }
1236
1237
0
        debug!(
1238
0
            "validated dnskey ({}, {key_rdata}) with {} {}",
1239
0
            rr.name(),
1240
0
            r.name(),
1241
0
            r.data(),
1242
        );
1243
1244
        // If this key is valid, then it is secure
1245
0
        return Ok(Proof::Secure);
1246
    }
1247
1248
0
    trace!("bogus dnskey: {}", rr.name());
1249
0
    Err(ProofError::new(
1250
0
        Proof::Bogus,
1251
0
        ProofErrorKind::DnsKeyHasNoDs {
1252
0
            name: rr.name().clone(),
1253
0
        },
1254
0
    ))
1255
0
}
1256
1257
/// Verifies the given SIG of the RRSET with the DNSKEY.
1258
0
fn verify_rrset_with_dnskey(
1259
0
    dnskey: RecordRef<'_, DNSKEY>,
1260
0
    dnskey_proof: Proof,
1261
0
    rrsig: &RecordRef<'_, RRSIG>,
1262
0
    rrset: &Rrset<'_>,
1263
0
    current_time: u32,
1264
0
) -> Result<(Proof, Option<u32>), ProofError> {
1265
0
    match dnskey_proof {
1266
0
        Proof::Secure => (),
1267
0
        proof => {
1268
0
            debug!("insecure dnskey {} {}", dnskey.name(), dnskey.data());
1269
0
            return Err(ProofError::new(
1270
0
                proof,
1271
0
                ProofErrorKind::InsecureDnsKey {
1272
0
                    name: dnskey.name().clone(),
1273
0
                    key_tag: rrsig.data().input.key_tag,
1274
0
                },
1275
0
            ));
1276
        }
1277
    }
1278
1279
0
    if dnskey.data().revoke() {
1280
0
        debug!("revoked dnskey {} {}", dnskey.name(), dnskey.data());
1281
0
        return Err(ProofError::new(
1282
0
            Proof::Bogus,
1283
0
            ProofErrorKind::DnsKeyRevoked {
1284
0
                name: dnskey.name().clone(),
1285
0
                key_tag: rrsig.data().input.key_tag,
1286
0
            },
1287
0
        ));
1288
0
    } // TODO: does this need to be validated? RFC 5011
1289
0
    if !dnskey.data().zone_key() {
1290
0
        return Err(ProofError::new(
1291
0
            Proof::Bogus,
1292
0
            ProofErrorKind::NotZoneDnsKey {
1293
0
                name: dnskey.name().clone(),
1294
0
                key_tag: rrsig.data().input.key_tag,
1295
0
            },
1296
0
        ));
1297
0
    }
1298
0
    if dnskey.data().algorithm() != rrsig.data().input.algorithm {
1299
0
        return Err(ProofError::new(
1300
0
            Proof::Bogus,
1301
0
            ProofErrorKind::AlgorithmMismatch {
1302
0
                rrsig: rrsig.data().input.algorithm,
1303
0
                dnskey: dnskey.data().algorithm(),
1304
0
            },
1305
0
        ));
1306
0
    }
1307
1308
0
    let validity = RrsigValidity::check(*rrsig, rrset, dnskey, current_time);
1309
0
    if !matches!(validity, RrsigValidity::ValidRrsig) {
1310
        // TODO better error handling when the error payload is not immediately discarded by
1311
        // the caller
1312
0
        return Err(ProofError::new(
1313
0
            Proof::Bogus,
1314
0
            ProofErrorKind::Msg(format!("{validity:?}")),
1315
0
        ));
1316
0
    }
1317
1318
0
    dnskey
1319
0
        .data()
1320
0
        .verify_rrsig(
1321
0
            &rrset.name,
1322
0
            rrset.record_class,
1323
0
            rrsig.data(),
1324
0
            rrset.records.iter().copied(),
1325
        )
1326
0
        .map(|_| {
1327
0
            debug!(
1328
0
                "validated ({}, {:?}) with ({}, {})",
1329
                rrset.name,
1330
                rrset.record_type,
1331
0
                dnskey.name(),
1332
0
                dnskey.data()
1333
            );
1334
0
            (
1335
0
                Proof::Secure,
1336
0
                Some(rrsig.data().authenticated_ttl(rrset.record(), current_time)),
1337
0
            )
1338
0
        })
1339
0
        .map_err(|e| {
1340
0
            debug!(
1341
0
                "failed validation of ({}, {:?}) with ({}, {})",
1342
                rrset.name,
1343
                rrset.record_type,
1344
0
                dnskey.name(),
1345
0
                dnskey.data()
1346
            );
1347
0
            ProofError::new(
1348
0
                Proof::Bogus,
1349
0
                ProofErrorKind::DnsKeyVerifyRrsig {
1350
0
                    name: dnskey.name().clone(),
1351
0
                    key_tag: rrsig.data().input.key_tag,
1352
0
                    error: e,
1353
0
                },
1354
            )
1355
0
        })
1356
0
}
1357
1358
#[derive(Clone, Copy, Debug)]
1359
enum RrsigValidity {
1360
    /// RRSIG has already expired
1361
    ExpiredRrsig,
1362
    /// RRSIG is valid
1363
    ValidRrsig,
1364
    /// DNSKEY does not match RRSIG
1365
    WrongDnskey,
1366
    /// RRSIG does not match RRset
1367
    WrongRrsig,
1368
}
1369
1370
impl RrsigValidity {
1371
    // see section 5.3.1 of RFC4035 "Checking the RRSIG RR Validity"
1372
0
    fn check(
1373
0
        rrsig: RecordRef<'_, RRSIG>,
1374
0
        rrset: &Rrset<'_>,
1375
0
        dnskey: RecordRef<'_, DNSKEY>,
1376
0
        current_time: u32,
1377
0
    ) -> Self {
1378
0
        let Ok(dnskey_key_tag) = dnskey.data().calculate_key_tag() else {
1379
0
            return Self::WrongDnskey;
1380
        };
1381
1382
0
        let current_time = SerialNumber(current_time);
1383
0
        let sig_input = rrsig.data().input();
1384
        if !(
1385
            // "The RRSIG RR and the RRset MUST have the same owner name and the same class"
1386
0
            rrsig.name() == &rrset.name &&
1387
0
            rrsig.dns_class() == rrset.record_class &&
1388
1389
            // "The RRSIG RR's Signer's Name field MUST be the name of the zone that contains the RRset"
1390
            // TODO(^) the zone name is in the SOA record, which is not accessible from here
1391
1392
            // "The RRSIG RR's Type Covered field MUST equal the RRset's type"
1393
0
            sig_input.type_covered == rrset.record_type &&
1394
1395
            // "The number of labels in the RRset owner name MUST be greater than or equal to the value
1396
            // in the RRSIG RR's Labels field"
1397
0
            rrset.name.num_labels() >= sig_input.num_labels
1398
        ) {
1399
0
            return Self::WrongRrsig;
1400
0
        }
1401
1402
        // Section 3.1.5 of RFC4034 states that 'all comparisons involving these fields MUST use
1403
        // "Serial number arithmetic", as defined in RFC1982'
1404
        if !(
1405
            // "The validator's notion of the current time MUST be less than or equal to the time listed
1406
            // in the RRSIG RR's Expiration field"
1407
0
            current_time <= sig_input.sig_expiration &&
1408
1409
            // "The validator's notion of the current time MUST be greater than or equal to the time
1410
            // listed in the RRSIG RR's Inception field"
1411
0
            current_time >= sig_input.sig_inception
1412
        ) {
1413
0
            return Self::ExpiredRrsig;
1414
0
        }
1415
1416
        if !(
1417
            // "The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST match the owner name,
1418
            // algorithm, and key tag for some DNSKEY RR in the zone's apex DNSKEY RRset"
1419
0
            &sig_input.signer_name == dnskey.name() &&
1420
0
            sig_input.algorithm == dnskey.data().algorithm() &&
1421
0
            sig_input.key_tag == dnskey_key_tag &&
1422
            // "The matching DNSKEY RR MUST be present in the zone's apex DNSKEY RRset, and MUST have the
1423
            // Zone Flag bit (DNSKEY RDATA Flag bit 7) set"
1424
0
            dnskey.data().zone_key()
1425
        ) {
1426
0
            return Self::WrongDnskey;
1427
0
        }
1428
1429
0
        Self::ValidRrsig
1430
0
    }
1431
}
1432
1433
#[derive(Clone)]
1434
struct RrsetProof {
1435
    proof: Proof,
1436
    adjusted_ttl: Option<u32>,
1437
    rrsig_index: Option<usize>,
1438
}
1439
1440
#[derive(Clone)]
1441
#[allow(clippy::type_complexity)]
1442
struct ValidationCache {
1443
    inner: Arc<Mutex<LruCache<ValidationCacheKey, (Instant, Result<RrsetProof, ProofError>)>>>,
1444
    negative_ttl: Option<RangeInclusive<Duration>>,
1445
    positive_ttl: Option<RangeInclusive<Duration>>,
1446
}
1447
1448
impl ValidationCache {
1449
0
    fn new(capacity: usize) -> Self {
1450
0
        Self {
1451
0
            inner: Arc::new(Mutex::new(LruCache::new(capacity))),
1452
0
            negative_ttl: None,
1453
0
            positive_ttl: None,
1454
0
        }
1455
0
    }
1456
1457
0
    fn get(
1458
0
        &self,
1459
0
        key: &ValidationCacheKey,
1460
0
        context: &RrsetVerificationContext<'_>,
1461
0
    ) -> Option<Result<RrsetProof, ProofError>> {
1462
0
        let (ttl, cached) = self.inner.lock().get_mut(key)?.clone();
1463
1464
0
        if Instant::now() < ttl {
1465
0
            debug!(
1466
                name = ?context.rrset.name,
1467
                record_type = ?context.rrset.record_type,
1468
0
                "returning cached DNSSEC validation",
1469
            );
1470
0
            Some(cached)
1471
        } else {
1472
0
            debug!(
1473
                name = ?context.rrset.name,
1474
                record_type = ?context.rrset.record_type,
1475
0
                "cached DNSSEC validation expired"
1476
            );
1477
0
            None
1478
        }
1479
0
    }
1480
1481
0
    fn insert(
1482
0
        &self,
1483
0
        proof: Result<RrsetProof, ProofError>,
1484
0
        key: ValidationCacheKey,
1485
0
        cx: &RrsetVerificationContext<'_>,
1486
0
    ) {
1487
0
        debug!(
1488
            name = ?cx.rrset.name,
1489
            record_type = ?cx.rrset.record_type,
1490
0
            "inserting DNSSEC validation cache entry",
1491
        );
1492
1493
0
        let (mut min, mut max) = (Duration::from_secs(0), Duration::from_secs(u64::MAX));
1494
0
        if proof.is_err() {
1495
0
            if let Some(negative_bounds) = self.negative_ttl.clone() {
1496
0
                (min, max) = negative_bounds.into_inner();
1497
0
            }
1498
0
        } else if let Some(positive_bounds) = self.positive_ttl.clone() {
1499
0
            (min, max) = positive_bounds.into_inner();
1500
0
        }
1501
1502
0
        self.inner.lock().insert(
1503
0
            key,
1504
0
            (
1505
0
                Instant::now()
1506
0
                    + Duration::from_secs(cx.rrset.record().ttl().into()).clamp(min, max),
1507
0
                proof.clone(),
1508
0
            ),
1509
0
        );
1510
0
    }
1511
}
1512
1513
struct RrsetVerificationContext<'a> {
1514
    query: &'a Query,
1515
    rrset: &'a Rrset<'a>,
1516
    rrsigs: Vec<RecordRef<'a, RRSIG>>,
1517
    options: DnsRequestOptions,
1518
    current_time: u32,
1519
}
1520
1521
impl<'a> RrsetVerificationContext<'a> {
1522
    // Build a cache lookup key based on the query, rrset, and rrsigs contents, minus the TTLs
1523
    // for each, since the recursor cache will return an adjusted TTL for each request and
1524
    // cause cache misses.
1525
0
    fn key(&self) -> ValidationCacheKey {
1526
0
        let mut hasher = DefaultHasher::new();
1527
0
        self.query.name().hash(&mut hasher);
1528
0
        self.query.query_class().hash(&mut hasher);
1529
0
        self.query.query_type().hash(&mut hasher);
1530
0
        self.rrset.name.hash(&mut hasher);
1531
0
        self.rrset.record_class.hash(&mut hasher);
1532
0
        self.rrset.record_type.hash(&mut hasher);
1533
1534
0
        for rec in &self.rrset.records {
1535
0
            rec.name().hash(&mut hasher);
1536
0
            rec.dns_class().hash(&mut hasher);
1537
0
            rec.data().hash(&mut hasher);
1538
0
        }
1539
1540
0
        for rec in &self.rrsigs {
1541
0
            rec.name().hash(&mut hasher);
1542
0
            rec.dns_class().hash(&mut hasher);
1543
0
            rec.data().hash(&mut hasher);
1544
0
        }
1545
1546
0
        ValidationCacheKey(hasher.finish())
1547
0
    }
1548
}
1549
1550
#[derive(Hash, Eq, PartialEq)]
1551
struct ValidationCacheKey(u64);
1552
1553
/// Verifies NSEC records
1554
///
1555
/// ```text
1556
/// RFC 4035             DNSSEC Protocol Modifications            March 2005
1557
///
1558
/// 5.4.  Authenticated Denial of Existence
1559
///
1560
///  A resolver can use authenticated NSEC RRs to prove that an RRset is
1561
///  not present in a signed zone.  Security-aware name servers should
1562
///  automatically include any necessary NSEC RRs for signed zones in
1563
///  their responses to security-aware resolvers.
1564
///
1565
///  Denial of existence is determined by the following rules:
1566
///
1567
///  o  If the requested RR name matches the owner name of an
1568
///     authenticated NSEC RR, then the NSEC RR's type bit map field lists
1569
///     all RR types present at that owner name, and a resolver can prove
1570
///     that the requested RR type does not exist by checking for the RR
1571
///     type in the bit map.  If the number of labels in an authenticated
1572
///     NSEC RR's owner name equals the Labels field of the covering RRSIG
1573
///     RR, then the existence of the NSEC RR proves that wildcard
1574
///     expansion could not have been used to match the request.
1575
///
1576
///  o  If the requested RR name would appear after an authenticated NSEC
1577
///     RR's owner name and before the name listed in that NSEC RR's Next
1578
///     Domain Name field according to the canonical DNS name order
1579
///     defined in [RFC4034], then no RRsets with the requested name exist
1580
///     in the zone.  However, it is possible that a wildcard could be
1581
///     used to match the requested RR owner name and type, so proving
1582
///     that the requested RRset does not exist also requires proving that
1583
///     no possible wildcard RRset exists that could have been used to
1584
///     generate a positive response.
1585
///
1586
///  In addition, security-aware resolvers MUST authenticate the NSEC
1587
///  RRsets that comprise the non-existence proof as described in Section
1588
///  5.3.
1589
///
1590
///  To prove the non-existence of an RRset, the resolver must be able to
1591
///  verify both that the queried RRset does not exist and that no
1592
///  relevant wildcard RRset exists.  Proving this may require more than
1593
///  one NSEC RRset from the zone.  If the complete set of necessary NSEC
1594
///  RRsets is not present in a response (perhaps due to message
1595
///  truncation), then a security-aware resolver MUST resend the query in
1596
///  order to attempt to obtain the full collection of NSEC RRs necessary
1597
///  to verify the non-existence of the requested RRset.  As with all DNS
1598
///  operations, however, the resolver MUST bound the work it puts into
1599
///  answering any particular query.
1600
///
1601
///  Since a validated NSEC RR proves the existence of both itself and its
1602
///  corresponding RRSIG RR, a validator MUST ignore the settings of the
1603
///  NSEC and RRSIG bits in an NSEC RR.
1604
/// ```
1605
0
fn verify_nsec(
1606
0
    query: &Query,
1607
0
    soa_name: Option<&Name>,
1608
0
    response_code: ResponseCode,
1609
0
    answers: &[Record],
1610
0
    nsecs: &[(&Name, &NSEC)],
1611
0
) -> Proof {
1612
    // TODO: consider converting this to Result, and giving explicit reason for the failure
1613
1614
0
    let nsec1_yield =
1615
0
        |proof: Proof, msg: &str| -> Proof { proof_log_yield(proof, query, "nsec1", msg) };
1616
1617
0
    if response_code != ResponseCode::NXDomain && response_code != ResponseCode::NoError {
1618
0
        return nsec1_yield(Proof::Bogus, "unsupported response code");
1619
0
    }
1620
1621
    // The SOA name, if present, must be an ancestor of the query name.  If a SOA is present,
1622
    // we'll use that as the starting value for next_closest_encloser, otherwise, fall back to
1623
    // the parent of the query name.
1624
0
    let mut next_closest_encloser = if let Some(soa_name) = soa_name {
1625
0
        if !soa_name.zone_of(query.name()) {
1626
0
            return nsec1_yield(Proof::Bogus, "SOA record is for the wrong zone");
1627
0
        }
1628
0
        soa_name.clone()
1629
    } else {
1630
0
        query.name().base_name()
1631
    };
1632
1633
0
    let have_answer = !answers.is_empty();
1634
1635
    // For a no data response with a directly matching NSEC record, we just need to verify the NSEC
1636
    // type set does not contain the query type or CNAME.
1637
0
    if let Some((_, nsec_data)) = nsecs.iter().find(|(name, _)| query.name() == *name) {
1638
0
        return if nsec_data.type_set().contains(query.query_type())
1639
0
            || nsec_data.type_set().contains(RecordType::CNAME)
1640
        {
1641
0
            nsec1_yield(Proof::Bogus, "direct match, record type should be present")
1642
0
        } else if response_code == ResponseCode::NoError && !have_answer {
1643
0
            nsec1_yield(Proof::Secure, "direct match")
1644
        } else {
1645
0
            nsec1_yield(
1646
0
                Proof::Bogus,
1647
0
                "nxdomain response or answers present when direct match exists",
1648
0
            )
1649
        };
1650
0
    }
1651
1652
0
    let Some((covering_nsec_name, covering_nsec_data)) =
1653
0
        find_nsec_covering_record(soa_name, query.name(), nsecs)
1654
    else {
1655
0
        return nsec1_yield(
1656
0
            Proof::Bogus,
1657
0
            "no NSEC record matches or covers the query name",
1658
0
        );
1659
    };
1660
1661
    // Identify the names that exist (including names of empty non terminals) that are parents of
1662
    // the query name. Pick the longest such name, because wildcard synthesis would start looking
1663
    // for a wildcard record there.
1664
0
    for seed_name in [covering_nsec_name, covering_nsec_data.next_domain_name()] {
1665
0
        let mut candidate_name = seed_name.clone();
1666
0
        while candidate_name.num_labels() > next_closest_encloser.num_labels() {
1667
0
            if candidate_name.zone_of(query.name()) {
1668
0
                next_closest_encloser = candidate_name;
1669
0
                break;
1670
0
            }
1671
0
            candidate_name = candidate_name.base_name();
1672
        }
1673
    }
1674
1675
0
    let Ok(wildcard_name) = next_closest_encloser.prepend_label("*") else {
1676
        // This fails if the prepended label is invalid or if the wildcard name would be too long.
1677
        // However, we already know that the query name is not too long. The next closest enclosing
1678
        // name must be strictly shorter than the query name, since we know that there is no NSEC
1679
        // record matching the query name. Thus the query name must be as long or longer than this
1680
        // wildcard name we are trying to construct, because we removed at least one label from the
1681
        // query name, and tried to add a single-byte label. This error condition should thus be
1682
        // unreachable.
1683
0
        return nsec1_yield(Proof::Bogus, "unreachable error constructing wildcard name");
1684
    };
1685
1686
0
    debug!(%wildcard_name, "looking for NSEC for wildcard");
1687
1688
    // Identify the name of wildcard used to generate the response.  This will be used to prove that no closer matches
1689
    // exist between the query name and the wildcard.
1690
0
    let wildcard_base_name = if have_answer {
1691
        // For wildcard expansion responses, identify an RRSIG that:
1692
        // 1) Is a wildcard RRSIG (fewer rrsig labels than owner name labels) and is not longer than the query name.
1693
        // 2) Is a parent of the query name
1694
        //
1695
        // There should be only one of these, but if there are multiple, we'll pick the one with the fewest labels (the harder of the
1696
        // provided RRSIGs to validate, since more names have to be covered as a result.)
1697
0
        answers
1698
0
            .iter()
1699
0
            .filter_map(|r| {
1700
0
                if r.proof() != Proof::Secure {
1701
0
                    debug!(name = ?r.name(), "ignoring RRSIG with insecure proof for wildcard_base_name");
1702
0
                    return None;
1703
0
                }
1704
1705
0
                let RData::DNSSEC(DNSSECRData::RRSIG(rrsig)) = r.data() else {
1706
0
                    return None;
1707
                };
1708
1709
0
                let rrsig_labels = rrsig.input.num_labels;
1710
0
                if rrsig_labels >= r.name().num_labels() || rrsig_labels >= query.name().num_labels() {
1711
0
                    debug!(name = ?r.name(), labels = ?r.name().num_labels(), rrsig_labels, "ignoring RRSIG for wildcard base name rrsig_labels >= labels");
1712
0
                    return None;
1713
0
                }
1714
1715
0
                let trimmed_name = r.name().trim_to(rrsig_labels as usize);
1716
0
                if !trimmed_name.zone_of(query.name()) {
1717
0
                    debug!(name = ?r.name(), query_name = ?query.name(), "ignoring RRSIG for wildcard base name: RRSIG wildcard labels not a parent of query name");
1718
0
                    return None;
1719
0
                }
1720
1721
0
                Some((rrsig_labels, trimmed_name.prepend_label("*").ok()?))
1722
0
            }).min_by_key(|(labels, _)| *labels)
1723
0
            .map(|(_, name)| name)
1724
    } else {
1725
        // For no data responses, we have to recover the base name from a wildcard NSEC record as there are no answer RRSIGs present.
1726
0
        nsecs
1727
0
            .iter()
1728
0
            .filter(|(name, _)| name.is_wildcard() && name.base_name().zone_of(query.name()))
1729
0
            .min_by_key(|(name, _)| name.num_labels())
1730
0
            .map(|(name, _)| (*name).clone())
1731
    };
1732
1733
0
    match find_nsec_covering_record(soa_name, &wildcard_name, nsecs) {
1734
        // For NXDomain responses, we've already proved the record does not exist. Now we just need to prove
1735
        // the wildcard name is covered.
1736
0
        Some((_, _)) if response_code == ResponseCode::NXDomain && !have_answer => {
1737
0
            nsec1_yield(Proof::Secure, "no direct match, no wildcard")
1738
        }
1739
        // For wildcard expansion responses, we need to prove there are no closer matches and no exact match.
1740
        // (RFC 4035 5.3.4 and B.6/C.6)
1741
        Some((_, _))
1742
0
            if response_code == ResponseCode::NoError
1743
0
                && have_answer
1744
0
                && no_closer_matches(
1745
0
                    query.name(),
1746
0
                    soa_name,
1747
0
                    nsecs,
1748
0
                    wildcard_base_name.as_ref(),
1749
                )
1750
0
                && find_nsec_covering_record(soa_name, query.name(), nsecs).is_some() =>
1751
        {
1752
0
            nsec1_yield(
1753
0
                Proof::Secure,
1754
0
                "no direct match, covering wildcard present for wildcard expansion response",
1755
0
            )
1756
        }
1757
        // For wildcard no data responses, we need to prove a wildcard matching wildcard_name does not contain
1758
        // the requested record type and that no closer match exists. (RFC 4035 3.1.3.4 and B.7/C.7)
1759
0
        None if !have_answer
1760
0
            && response_code == ResponseCode::NoError
1761
0
            && nsecs.iter().any(|(name, nsec_data)| {
1762
0
                name == &&wildcard_name
1763
0
                    && !nsec_data.type_set().contains(query.query_type())
1764
0
                    && !nsec_data.type_set().contains(RecordType::CNAME)
1765
0
                    && no_closer_matches(query.name(), soa_name, nsecs, wildcard_base_name.as_ref())
1766
0
            }) =>
1767
        {
1768
0
            nsec1_yield(Proof::Secure, "no direct match, covering wildcard present")
1769
        }
1770
0
        _ => nsec1_yield(
1771
0
            Proof::Bogus,
1772
0
            "no NSEC record matches or covers the wildcard name",
1773
0
        ),
1774
    }
1775
0
}
1776
1777
// Prove that no closer name exists between the query name and wildcard_base_name
1778
0
fn no_closer_matches(
1779
0
    query_name: &Name,
1780
0
    soa: Option<&Name>,
1781
0
    nsecs: &[(&'_ Name, &'_ NSEC)],
1782
0
    wildcard_base_name: Option<&Name>,
1783
0
) -> bool {
1784
0
    let Some(wildcard_base_name) = wildcard_base_name else {
1785
0
        return false;
1786
    };
1787
1788
    // If the SOA name is present, the query name and wildcard base name must be children of it.
1789
0
    if let Some(soa) = soa {
1790
0
        if !soa.zone_of(wildcard_base_name) {
1791
0
            debug!(%wildcard_base_name, %soa, "wildcard_base_name is not a child of SOA");
1792
0
            return false;
1793
0
        }
1794
1795
0
        if !soa.zone_of(query_name) {
1796
0
            debug!(%query_name, %soa, "query_name is not a child of SOA");
1797
0
            return false;
1798
0
        }
1799
0
    }
1800
1801
0
    if wildcard_base_name.num_labels() > query_name.num_labels() {
1802
0
        debug!(%wildcard_base_name, %query_name, "wildcard_base_name cannot have more labels than query_name");
1803
0
        return false;
1804
0
    }
1805
1806
    // The query name must be a child of the wildcard (minus the *)
1807
0
    if !wildcard_base_name.base_name().zone_of(query_name) {
1808
0
        debug!(%wildcard_base_name, %query_name, "query_name is not a child of wildcard_name");
1809
0
        return false;
1810
0
    }
1811
1812
    // Verify that an appropriate proof exists for each wildcard between query.name() and wildcard_base_name.
1813
0
    let mut name = query_name.base_name();
1814
0
    while name.num_labels() > wildcard_base_name.num_labels() {
1815
0
        let Ok(wildcard) = name.prepend_label("*") else {
1816
0
            return false;
1817
        };
1818
1819
0
        if find_nsec_covering_record(soa, &wildcard, nsecs).is_none() {
1820
0
            debug!(%wildcard, %name, ?nsecs, "covering record does not exist for name");
1821
0
            return false;
1822
0
        }
1823
1824
0
        name = name.base_name();
1825
    }
1826
1827
0
    true
1828
0
}
1829
1830
/// Find the NSEC record covering `test_name`, if any.
1831
0
fn find_nsec_covering_record<'a>(
1832
0
    soa_name: Option<&Name>,
1833
0
    test_name: &Name,
1834
0
    nsecs: &[(&'a Name, &'a NSEC)],
1835
0
) -> Option<(&'a Name, &'a NSEC)> {
1836
0
    nsecs.iter().copied().find(|(nsec_name, nsec_data)| {
1837
0
        let next_domain_name = nsec_data.next_domain_name();
1838
1839
0
        test_name > nsec_name
1840
0
            && (test_name < next_domain_name || Some(next_domain_name) == soa_name)
1841
0
    })
1842
0
}
1843
1844
/// Logs a debug message and yields a Proof type for return
1845
0
pub(super) fn proof_log_yield(
1846
0
    proof: Proof,
1847
0
    query: &Query,
1848
0
    nsec_type: &str,
1849
0
    msg: impl Display,
1850
0
) -> Proof {
1851
0
    debug!(
1852
0
        "{nsec_type} proof for {name}, returning {proof}: {msg}",
1853
0
        name = query.name()
1854
    );
1855
0
    proof
1856
0
}
Unexecuted instantiation: hickory_proto::dnssec::handle::proof_log_yield::<core::fmt::Arguments>
Unexecuted instantiation: hickory_proto::dnssec::handle::proof_log_yield::<&str>
1857
1858
mod rrset {
1859
    use alloc::vec::Vec;
1860
1861
    use crate::rr::{DNSClass, Name, Record, RecordType};
1862
1863
    // TODO: combine this with crate::rr::RecordSet?
1864
    #[derive(Debug)]
1865
    pub(super) struct Rrset<'r> {
1866
        pub(super) name: Name,
1867
        pub(super) record_class: DNSClass,
1868
        pub(super) record_type: RecordType,
1869
        pub(super) records: Vec<&'r Record>,
1870
    }
1871
1872
    impl<'r> Rrset<'r> {
1873
0
        pub(super) fn new(record: &'r Record) -> Self {
1874
0
            Self {
1875
0
                name: record.name().clone(),
1876
0
                record_class: record.dns_class(),
1877
0
                record_type: record.record_type(),
1878
0
                records: vec![record],
1879
0
            }
1880
0
        }
1881
1882
        /// Adds `record` to this RRset IFF it belongs to it
1883
0
        pub(super) fn add(&mut self, record: &'r Record) {
1884
0
            if self.name == *record.name()
1885
0
                && self.record_type == record.record_type()
1886
0
                && self.record_class == record.dns_class()
1887
0
            {
1888
0
                self.records.push(record);
1889
0
            }
1890
0
        }
1891
1892
        /// Returns the first (main) record.
1893
0
        pub(super) fn record(&self) -> &Record {
1894
0
            self.records[0]
1895
0
        }
1896
    }
1897
}
1898
1899
/// The maximum number of key tag collisions to accept when:
1900
///
1901
/// 1) Retrieving DNSKEY records for a zone
1902
/// 2) Retrieving DS records from a parent zone
1903
///
1904
/// Any colliding records encountered beyond this limit will be discarded.
1905
const MAX_KEY_TAG_COLLISIONS: usize = 2;
1906
1907
/// The maximum number of RRSIGs to attempt to validate for each RRSET.
1908
const MAX_RRSIGS_PER_RRSET: usize = 8;
1909
1910
/// The default validation cache size.  This is somewhat arbitrary, but set to the same size as the default
1911
/// recursor response cache
1912
const DEFAULT_VALIDATION_CACHE_SIZE: usize = 1_048_576;
1913
1914
#[cfg(test)]
1915
mod test {
1916
    use super::{no_closer_matches, verify_nsec};
1917
    use crate::{
1918
        dnssec::{
1919
            Algorithm, Proof,
1920
            rdata::{DNSSECRData, NSEC as rdataNSEC, RRSIG as rdataRRSIG, SigInput},
1921
        },
1922
        error::ProtoError,
1923
        op::{Query, ResponseCode},
1924
        rr::{
1925
            Name, RData, Record,
1926
            RecordType::{A, AAAA, DNSKEY, MX, NS, NSEC, RRSIG, SOA, TXT},
1927
            SerialNumber, rdata,
1928
        },
1929
    };
1930
    use test_support::subscribe;
1931
1932
    #[test]
1933
    fn test_no_closer_matches() -> Result<(), ProtoError> {
1934
        subscribe();
1935
1936
        assert!(no_closer_matches(
1937
            &Name::from_ascii("a.a.a.z.w.example")?,
1938
            Some(&Name::from_ascii("example.")?),
1939
            &[
1940
                // This NSEC encloses the query name and proves that no closer wildcard match
1941
                // exists in the zone.
1942
                (
1943
                    &Name::from_ascii("x.y.w.example.")?,
1944
                    &rdataNSEC::new(Name::from_ascii("xx.example.")?, [MX, NSEC, RRSIG],),
1945
                ),
1946
            ],
1947
            Some(&Name::from_ascii("*.w.example.")?),
1948
        ),);
1949
1950
        assert!(!no_closer_matches(
1951
            &Name::from_ascii("a.a.a.z.w.example")?,
1952
            Some(&Name::from_ascii("example.")?),
1953
            &[
1954
                // This doesn't prove the non-existence of the closer wildcard
1955
                (
1956
                    &Name::from_ascii("*.w.example.")?,
1957
                    &rdataNSEC::new(Name::from_ascii("z.w.example.")?, [MX, NSEC, RRSIG],),
1958
                ),
1959
            ],
1960
            Some(&Name::from_ascii("*.w.example.")?),
1961
        ),);
1962
1963
        assert!(!no_closer_matches(
1964
            &Name::from_ascii("a.a.a.z.w.example")?,
1965
            Some(&Name::from_ascii("example.")?),
1966
            &[(
1967
                &Name::from_ascii("x.y.w.example.")?,
1968
                &rdataNSEC::new(Name::from_ascii("xx.example.")?, [MX, NSEC, RRSIG],),
1969
            ),],
1970
            // no_closer_matches requires a wildcard base name be present
1971
            None,
1972
        ),);
1973
1974
        // SOA mismatch
1975
        assert!(!no_closer_matches(
1976
            &Name::from_ascii("a.a.a.z.w.example")?,
1977
            Some(&Name::from_ascii("z.example.")?),
1978
            &[
1979
                // This NSEC encloses the query name and proves that no closer wildcard match
1980
                // exists in the zone.
1981
                (
1982
                    &Name::from_ascii("x.y.w.example.")?,
1983
                    &rdataNSEC::new(Name::from_ascii("xx.example.")?, [MX, NSEC, RRSIG],),
1984
                ),
1985
                // This NSEC proves the requested record type does not exist at the wildcard
1986
                (
1987
                    &Name::from_ascii("*.w.example.")?,
1988
                    &rdataNSEC::new(Name::from_ascii("xw.example.")?, [MX, NSEC, RRSIG],),
1989
                ),
1990
            ],
1991
            Some(&Name::from_ascii("*.w.example.")?),
1992
        ),);
1993
1994
        // Irrelevant wildcard.
1995
        assert!(!no_closer_matches(
1996
            &Name::from_ascii("a.a.a.z.w.example")?,
1997
            Some(&Name::from_ascii("example.")?),
1998
            &[
1999
                // This NSEC encloses the query name and proves that no closer wildcard match
2000
                // exists in the zone.
2001
                (
2002
                    &Name::from_ascii("x.y.w.example.")?,
2003
                    &rdataNSEC::new(Name::from_ascii("xx.example.")?, [MX, NSEC, RRSIG],),
2004
                ),
2005
                // This NSEC proves the requested record type does not exist at the wildcard
2006
                (
2007
                    &Name::from_ascii("*.x.example.")?,
2008
                    &rdataNSEC::new(Name::from_ascii("xw.example.")?, [MX, NSEC, RRSIG],),
2009
                ),
2010
            ],
2011
            Some(&Name::from_ascii("*.x.example.")?),
2012
        ),);
2013
2014
        Ok(())
2015
    }
2016
2017
    // These test cases prove a name does not exist
2018
    #[test]
2019
    fn nsec_name_error() -> Result<(), ProtoError> {
2020
        subscribe();
2021
2022
        // Based on RFC 4035 B.2 - Name Error
2023
        assert_eq!(
2024
            verify_nsec(
2025
                &Query::query(Name::from_ascii("ml.example.")?, A),
2026
                Some(&Name::from_ascii("example.")?),
2027
                ResponseCode::NXDomain,
2028
                &[],
2029
                &[
2030
                    // This NSEC encloses the query name and proves the record does not exist.
2031
                    (
2032
                        &Name::from_ascii("b.example.")?,
2033
                        &rdataNSEC::new(Name::from_ascii("ns1.example.")?, [NS, RRSIG, NSEC],),
2034
                    ),
2035
                    // This NSEC proves no covering wildcard record exists (i.e., it encloses
2036
                    // *.example. and thus proves that record does not exist.)
2037
                    (
2038
                        &Name::from_ascii("example.")?,
2039
                        &rdataNSEC::new(
2040
                            Name::from_ascii("a.example.")?,
2041
                            [DNSKEY, MX, NS, NSEC, RRSIG, SOA],
2042
                        ),
2043
                    )
2044
                ],
2045
            ),
2046
            Proof::Secure
2047
        );
2048
2049
        // Single NSEC that proves the record does not exist, and no covering wildcard exists.
2050
        assert_eq!(
2051
            verify_nsec(
2052
                &Query::query(Name::from_ascii("a.example.")?, A),
2053
                Some(&Name::from_ascii("example.")?),
2054
                ResponseCode::NXDomain,
2055
                &[],
2056
                &[(
2057
                    &Name::from_ascii("example.")?,
2058
                    &rdataNSEC::new(Name::from_ascii("c.example.")?, [SOA, NS, RRSIG, NSEC],),
2059
                ),],
2060
            ),
2061
            Proof::Secure
2062
        );
2063
2064
        Ok(())
2065
    }
2066
2067
    /// Ensure invalid name error NSEC scenarios fail
2068
    #[test]
2069
    fn nsec_invalid_name_error() -> Result<(), ProtoError> {
2070
        subscribe();
2071
        assert_eq!(
2072
            verify_nsec(
2073
                &Query::query(Name::from_ascii("ml.example.")?, A),
2074
                Some(&Name::from_ascii("example.")?),
2075
                ResponseCode::NXDomain,
2076
                &[],
2077
                &[
2078
                    // This NSEC does not enclose the query name and so should cause this
2079
                    // verification to fail
2080
                    (
2081
                        &Name::from_ascii("ml.example.")?,
2082
                        &rdataNSEC::new(Name::from_ascii("ns1.example.")?, [NS, RRSIG, NSEC],),
2083
                    ),
2084
                    // This NSEC proves no covering wildcard record exists (i.e., it encloses
2085
                    // *.example. and thus proves that record does not exist.)
2086
                    (
2087
                        &Name::from_ascii("example.")?,
2088
                        &rdataNSEC::new(
2089
                            Name::from_ascii("a.example.")?,
2090
                            [DNSKEY, MX, NS, NSEC, RRSIG, SOA],
2091
                        ),
2092
                    )
2093
                ],
2094
            ),
2095
            Proof::Bogus
2096
        );
2097
2098
        // Test without proving wildcard non-existence.
2099
        assert_eq!(
2100
            verify_nsec(
2101
                &Query::query(Name::from_ascii("ml.example.")?, A),
2102
                Some(&Name::from_ascii("example.")?),
2103
                ResponseCode::NXDomain,
2104
                &[],
2105
                &[
2106
                    // This NSEC encloses the query name and proves the record does not exist.
2107
                    (
2108
                        &Name::from_ascii("ml.example.")?,
2109
                        &rdataNSEC::new(Name::from_ascii("ns1.example.")?, [NS, RRSIG, NSEC],),
2110
                    ),
2111
                ],
2112
            ),
2113
            Proof::Bogus
2114
        );
2115
2116
        // Invalid SOA
2117
        assert_eq!(
2118
            verify_nsec(
2119
                &Query::query(Name::from_ascii("ml.example.")?, A),
2120
                Some(&Name::from_ascii("example2.")?),
2121
                ResponseCode::NXDomain,
2122
                &[],
2123
                &[
2124
                    // This NSEC encloses the query name and proves the record does not exist.
2125
                    (
2126
                        &Name::from_ascii("b.example.")?,
2127
                        &rdataNSEC::new(Name::from_ascii("ns1.example.")?, [NS, RRSIG, NSEC],),
2128
                    ),
2129
                    // This NSEC proves no covering wildcard record exists (i.e., it encloses
2130
                    // *.example. and thus proves that record does not exist.)
2131
                    (
2132
                        &Name::from_ascii("example.")?,
2133
                        &rdataNSEC::new(
2134
                            Name::from_ascii("a.example.")?,
2135
                            [DNSKEY, MX, NS, NSEC, RRSIG, SOA],
2136
                        ),
2137
                    )
2138
                ],
2139
            ),
2140
            Proof::Bogus
2141
        );
2142
2143
        Ok(())
2144
    }
2145
2146
    // These test cases prove that the requested record type does not exist at the query name
2147
    #[test]
2148
    fn nsec_no_data_error() -> Result<(), ProtoError> {
2149
        subscribe();
2150
2151
        // Based on RFC 4035 B.3 - No Data Error
2152
        assert_eq!(
2153
            verify_nsec(
2154
                &Query::query(Name::from_ascii("ns1.example.")?, MX),
2155
                Some(&Name::from_ascii("example.")?),
2156
                ResponseCode::NoError,
2157
                &[],
2158
                &[
2159
                    // This NSEC encloses the query name and proves the record does exist, but
2160
                    // the requested record type does not.
2161
                    (
2162
                        &Name::from_ascii("ns1.example.")?,
2163
                        &rdataNSEC::new(Name::from_ascii("ns2.example.")?, [A, NSEC, RRSIG],),
2164
                    ),
2165
                ],
2166
            ),
2167
            Proof::Secure
2168
        );
2169
2170
        // Record type at the SOA does not exist.
2171
        assert_eq!(
2172
            verify_nsec(
2173
                &Query::query(Name::from_ascii("example.")?, MX),
2174
                Some(&Name::from_ascii("example.")?),
2175
                ResponseCode::NoError,
2176
                &[],
2177
                &[
2178
                    // This NSEC encloses the query name and proves the record does exist, but
2179
                    // the requested record type does not.
2180
                    (
2181
                        &Name::from_ascii("example.")?,
2182
                        &rdataNSEC::new(Name::from_ascii("a.example.")?, [A, NSEC, RRSIG, SOA],),
2183
                    ),
2184
                ],
2185
            ),
2186
            Proof::Secure
2187
        );
2188
2189
        Ok(())
2190
    }
2191
2192
    // Ensure invalid no data NSEC scenarios fails
2193
    #[test]
2194
    fn nsec_invalid_no_data_error() -> Result<(), ProtoError> {
2195
        subscribe();
2196
2197
        assert_eq!(
2198
            verify_nsec(
2199
                &Query::query(Name::from_ascii("ns1.example.")?, MX),
2200
                Some(&Name::from_ascii("example.")?),
2201
                ResponseCode::NoError,
2202
                &[],
2203
                &[
2204
                    // This NSEC claims the requested record type DOES exist at ns1.example.
2205
                    (
2206
                        &Name::from_ascii("ns1.example.")?,
2207
                        &rdataNSEC::new(Name::from_ascii("ns2.example.")?, [A, NSEC, RRSIG, MX],),
2208
                    ),
2209
                ],
2210
            ),
2211
            Proof::Bogus
2212
        );
2213
2214
        assert_eq!(
2215
            verify_nsec(
2216
                &Query::query(Name::from_ascii("ns1.example.")?, MX),
2217
                Some(&Name::from_ascii("example.")?),
2218
                ResponseCode::NoError,
2219
                &[],
2220
                &[
2221
                    // In this case, the response indicates *some* record exists at ns1.example., just not an
2222
                    // MX record. This NSEC claims ns1.example. does not exist at all.
2223
                    (
2224
                        &Name::from_ascii("ml.example.")?,
2225
                        &rdataNSEC::new(Name::from_ascii("ns2.example.")?, [A, NSEC, RRSIG],),
2226
                    ),
2227
                ],
2228
            ),
2229
            Proof::Bogus
2230
        );
2231
2232
        assert_eq!(
2233
            verify_nsec(
2234
                &Query::query(Name::from_ascii("ns1.example.")?, MX),
2235
                Some(&Name::from_ascii("example.")?),
2236
                ResponseCode::NoError,
2237
                &[],
2238
                &[
2239
                    // This NSEC claims nothing exists from the SOA to ns2.example.
2240
                    (
2241
                        &Name::from_ascii("example.")?,
2242
                        &rdataNSEC::new(Name::from_ascii("ns2.example.")?, [A, NSEC, RRSIG],),
2243
                    ),
2244
                ],
2245
            ),
2246
            Proof::Bogus
2247
        );
2248
2249
        Ok(())
2250
    }
2251
2252
    // Ensure that positive answers expanded from wildcards pass validation
2253
    #[test]
2254
    fn nsec_wildcard_expansion() -> Result<(), ProtoError> {
2255
        subscribe();
2256
2257
        let input = SigInput {
2258
            type_covered: MX,
2259
            algorithm: Algorithm::ED25519,
2260
            num_labels: 2,
2261
            original_ttl: 3600,
2262
            sig_expiration: SerialNumber(0),
2263
            sig_inception: SerialNumber(0),
2264
            key_tag: 0,
2265
            signer_name: Name::root(),
2266
        };
2267
2268
        let rrsig = rdataRRSIG::from_sig(input, vec![]);
2269
        let mut rrsig_record = Record::from_rdata(
2270
            Name::from_ascii("a.z.w.example.")?,
2271
            3600,
2272
            RData::DNSSEC(DNSSECRData::RRSIG(rrsig)),
2273
        );
2274
        rrsig_record.set_proof(Proof::Secure);
2275
2276
        let answers = [
2277
            Record::from_rdata(
2278
                Name::from_ascii("a.z.w.example.")?,
2279
                3600,
2280
                RData::MX(rdata::MX::new(10, Name::from_ascii("a.z.w.example.")?)),
2281
            ),
2282
            rrsig_record,
2283
        ];
2284
2285
        // Based on RFC 4035 B.6 - Wildcard Expansion
2286
        assert_eq!(
2287
            verify_nsec(
2288
                &Query::query(Name::from_ascii("a.z.w.example.")?, MX),
2289
                None,
2290
                ResponseCode::NoError,
2291
                &answers,
2292
                &[
2293
                    // This NSEC encloses the query name and proves that no closer wildcard match
2294
                    // exists in the zone.
2295
                    (
2296
                        &Name::from_ascii("x.y.w.example.")?,
2297
                        &rdataNSEC::new(Name::from_ascii("xx.example.")?, [MX, NSEC, RRSIG],),
2298
                    ),
2299
                ],
2300
            ),
2301
            Proof::Secure
2302
        );
2303
2304
        // This response could not have been synthesized from the query name (z.example can't be expanded from *.w.example
2305
        assert_eq!(
2306
            verify_nsec(
2307
                &Query::query(Name::from_ascii("z.example.")?, MX),
2308
                Some(&Name::from_ascii("example.")?),
2309
                ResponseCode::NoError,
2310
                &answers,
2311
                &[
2312
                    // This NSEC encloses the query name and proves that z.example. does not exist.
2313
                    (
2314
                        &Name::from_ascii("y.example.")?,
2315
                        &rdataNSEC::new(Name::from_ascii("example.")?, [A, NSEC, RRSIG],),
2316
                    ),
2317
                    // This NSEC proves *.example. exists and contains an MX record.
2318
                    (
2319
                        &Name::from_ascii("example.")?,
2320
                        &rdataNSEC::new(
2321
                            Name::from_ascii("a.example.")?,
2322
                            [MX, NS, NSEC, RRSIG, SOA],
2323
                        ),
2324
                    ),
2325
                ],
2326
            ),
2327
            Proof::Bogus
2328
        );
2329
2330
        Ok(())
2331
    }
2332
2333
    // Ensure that defective wildcard expansion positive answer scenarios fail validation
2334
    #[test]
2335
    fn nsec_invalid_wildcard_expansion() -> Result<(), ProtoError> {
2336
        subscribe();
2337
2338
        let input = SigInput {
2339
            type_covered: MX,
2340
            algorithm: Algorithm::ED25519,
2341
            num_labels: 2,
2342
            original_ttl: 0,
2343
            sig_expiration: SerialNumber(0),
2344
            sig_inception: SerialNumber(0),
2345
            key_tag: 0,
2346
            signer_name: Name::root(),
2347
        };
2348
2349
        let rrsig = rdataRRSIG::from_sig(input, vec![]);
2350
        let mut rrsig_record = Record::from_rdata(
2351
            Name::from_ascii("a.z.w.example.")?,
2352
            3600,
2353
            RData::DNSSEC(DNSSECRData::RRSIG(rrsig)),
2354
        );
2355
        rrsig_record.set_proof(Proof::Secure);
2356
2357
        let answers = [
2358
            Record::from_rdata(
2359
                Name::from_ascii("a.z.w.example.")?,
2360
                3600,
2361
                RData::MX(rdata::MX::new(10, Name::from_ascii("a.z.w.example.")?)),
2362
            ),
2363
            rrsig_record,
2364
        ];
2365
2366
        assert_eq!(
2367
            verify_nsec(
2368
                &Query::query(Name::from_ascii("a.z.w.example.")?, MX),
2369
                None,
2370
                ResponseCode::NoError,
2371
                &answers,
2372
                &[
2373
                    // This NSEC does not prove the non-existence of *.z.w.example.
2374
                    (
2375
                        &Name::from_ascii("x.y.w.example.")?,
2376
                        &rdataNSEC::new(Name::from_ascii("z.w.example.")?, [MX, NSEC, RRSIG],),
2377
                    ),
2378
                ],
2379
            ),
2380
            Proof::Bogus
2381
        );
2382
2383
        assert_eq!(
2384
            verify_nsec(
2385
                &Query::query(Name::from_ascii("a.z.w.example.")?, MX),
2386
                None,
2387
                ResponseCode::NoError,
2388
                &answers,
2389
                &[],
2390
            ),
2391
            Proof::Bogus
2392
        );
2393
2394
        Ok(())
2395
    }
2396
2397
    #[test]
2398
    fn nsec_wildcard_no_data_error() -> Result<(), ProtoError> {
2399
        subscribe();
2400
2401
        // Based on RFC 4035 B.7 - Wildcard No Data Error
2402
        assert_eq!(
2403
            verify_nsec(
2404
                &Query::query(Name::from_ascii("a.z.w.example.")?, AAAA),
2405
                Some(&Name::from_ascii("example.")?),
2406
                ResponseCode::NoError,
2407
                &[],
2408
                &[
2409
                    // This NSEC encloses the query name and proves that no closer wildcard match
2410
                    // exists in the zone.
2411
                    (
2412
                        &Name::from_ascii("x.y.w.example.")?,
2413
                        &rdataNSEC::new(Name::from_ascii("xx.example.")?, [MX, NSEC, RRSIG],),
2414
                    ),
2415
                    // This NSEC proves the requested record type does not exist at the wildcard
2416
                    (
2417
                        &Name::from_ascii("*.w.example.")?,
2418
                        &rdataNSEC::new(Name::from_ascii("xw.example.")?, [MX, NSEC, RRSIG],),
2419
                    ),
2420
                ],
2421
            ),
2422
            Proof::Secure
2423
        );
2424
2425
        assert_eq!(
2426
            verify_nsec(
2427
                &Query::query(Name::from_ascii("zzzzzz.hickory-dns.testing.")?, TXT),
2428
                Some(&Name::from_ascii("hickory-dns.testing.")?),
2429
                ResponseCode::NoError,
2430
                &[],
2431
                &[
2432
                    // This NSEC proves zzzzzz.hickory-dns.testing. does not exist.
2433
                    (
2434
                        &Name::from_ascii("record.hickory-dns.testing.")?,
2435
                        &rdataNSEC::new(
2436
                            Name::from_ascii("hickory-dns.testing.")?,
2437
                            [A, NSEC, RRSIG],
2438
                        ),
2439
                    ),
2440
                    // This NSEC proves a wildcard does exist at *.hickory-dns.testing. but does not contain the
2441
                    // requested record type.
2442
                    (
2443
                        &Name::from_ascii("*.hickory-dns.testing.")?,
2444
                        &rdataNSEC::new(
2445
                            Name::from_ascii("primary0.hickory-dns.testing.")?,
2446
                            [A, NSEC, RRSIG],
2447
                        ),
2448
                    ),
2449
                ],
2450
            ),
2451
            Proof::Secure
2452
        );
2453
2454
        Ok(())
2455
    }
2456
2457
    #[test]
2458
    fn nsec_invalid_wildcard_no_data_error() -> Result<(), ProtoError> {
2459
        subscribe();
2460
2461
        assert_eq!(
2462
            verify_nsec(
2463
                &Query::query(Name::from_ascii("a.z.w.example.")?, AAAA),
2464
                Some(&Name::from_ascii("example.")?),
2465
                ResponseCode::NoError,
2466
                &[],
2467
                &[
2468
                    // This NSEC doesn't prove the non-existence of the query name
2469
                    (
2470
                        &Name::from_ascii("x.y.w.example.")?,
2471
                        &rdataNSEC::new(Name::from_ascii("z.w.example.")?, [MX, NSEC, RRSIG],),
2472
                    ),
2473
                    // This NSEC proves the wildcard does not contain the requested record type
2474
                    (
2475
                        &Name::from_ascii("*.w.example.")?,
2476
                        &rdataNSEC::new(Name::from_ascii("x.y.w.example.")?, [MX, NSEC, RRSIG],),
2477
                    ),
2478
                ],
2479
            ),
2480
            Proof::Bogus
2481
        );
2482
2483
        assert_eq!(
2484
            verify_nsec(
2485
                &Query::query(Name::from_ascii("a.z.w.example.")?, AAAA),
2486
                Some(&Name::from_ascii("example.")?),
2487
                ResponseCode::NoError,
2488
                &[],
2489
                &[
2490
                    // This NSEC proves the query name does not exist
2491
                    (
2492
                        &Name::from_ascii("x.y.w.example.")?,
2493
                        &rdataNSEC::new(Name::from_ascii("xx.example.")?, [MX, NSEC, RRSIG],),
2494
                    ),
2495
                    // This NSEC proves the requested record type exists at the wildcard
2496
                    (
2497
                        &Name::from_ascii("*.w.example.")?,
2498
                        &rdataNSEC::new(Name::from_ascii("xw.example.")?, [AAAA, MX, NSEC, RRSIG],),
2499
                    ),
2500
                ],
2501
            ),
2502
            Proof::Bogus
2503
        );
2504
2505
        assert_eq!(
2506
            verify_nsec(
2507
                &Query::query(Name::from_ascii("r.hickory-dns.testing.")?, TXT),
2508
                Some(&Name::from_ascii("hickory-dns.testing.")?),
2509
                ResponseCode::NoError,
2510
                &[],
2511
                &[
2512
                    // There is no NSEC proving the non-existence of r.hickory-dns.testing.
2513
2514
                    // This NSEC proves a wildcard does exist at *.hickory-dns.testing. but does not contain the
2515
                    // requested record type.
2516
                    (
2517
                        &Name::from_ascii("*.hickory-dns.testing.")?,
2518
                        &rdataNSEC::new(
2519
                            Name::from_ascii("primary0.hickory-dns.testing.")?,
2520
                            [A, NSEC, RRSIG],
2521
                        ),
2522
                    ),
2523
                ],
2524
            ),
2525
            Proof::Bogus
2526
        );
2527
2528
        Ok(())
2529
    }
2530
}