hickory_proto/dnssec/rdata/
nsec.rs

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//! NSEC record types
9
10use core::fmt;
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15use crate::error::*;
16use crate::rr::{Name, RData, RecordData, RecordDataDecodable, RecordType, RecordTypeSet};
17use crate::serialize::binary::*;
18
19use super::DNSSECRData;
20
21/// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-4), DNSSEC Resource Records, March 2005
22///
23/// ```text
24/// 4.1.  NSEC RDATA Wire Format
25///
26///    The RDATA of the NSEC RR is as shown below:
27///
28///                         1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
29///     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
30///    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
31///    /                      Next Domain Name                         /
32///    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33///    /                       Type Bit Maps                           /
34///    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
35///
36/// 4.1.3.  Inclusion of Wildcard Names in NSEC RDATA
37///
38///    If a wildcard owner name appears in a zone, the wildcard label ("*")
39///    is treated as a literal symbol and is treated the same as any other
40///    owner name for the purposes of generating NSEC RRs.  Wildcard owner
41///    names appear in the Next Domain Name field without any wildcard
42///    expansion.  [RFC4035] describes the impact of wildcards on
43///    authenticated denial of existence.
44/// ```
45#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
46#[derive(Debug, PartialEq, Eq, Hash, Clone)]
47pub struct NSEC {
48    next_domain_name: Name,
49    type_bit_maps: RecordTypeSet,
50}
51
52impl NSEC {
53    /// Constructs a new NSEC RData, warning this won't guarantee that the NSEC covers itself
54    ///  which it should at it's own name.
55    ///
56    /// # Arguments
57    ///
58    /// * `next_domain_name` - the name labels of the next ordered name in the zone
59    /// * `type_bit_maps` - a bit map of the types that exist at this name
60    ///
61    /// # Returns
62    ///
63    /// An NSEC RData for use in a Resource Record
64    pub fn new(
65        next_domain_name: Name,
66        type_bit_maps: impl IntoIterator<Item = RecordType>,
67    ) -> Self {
68        Self {
69            next_domain_name,
70            type_bit_maps: RecordTypeSet::new(type_bit_maps),
71        }
72    }
73
74    /// Constructs a new NSEC RData, this will add the NSEC itself as covered, generally
75    ///   correct for NSEC records generated at their own name
76    ///
77    /// # Arguments
78    ///
79    /// * `next_domain_name` - the name labels of the next ordered name in the zone
80    /// * `type_bit_maps` - a bit map of the types that exist at this name
81    ///
82    /// # Returns
83    ///
84    /// An NSEC RData for use in a Resource Record
85    pub fn new_cover_self(
86        next_domain_name: Name,
87        type_bit_maps: impl IntoIterator<Item = RecordType>,
88    ) -> Self {
89        Self::new(
90            next_domain_name,
91            type_bit_maps.into_iter().chain([RecordType::NSEC]),
92        )
93    }
94
95    /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-4.1.1), DNSSEC Resource Records, March 2005
96    ///
97    /// ```text
98    /// 4.1.1.  The Next Domain Name Field
99    ///
100    ///    The Next Domain field contains the next owner name (in the canonical
101    ///    ordering of the zone) that has authoritative data or contains a
102    ///    delegation point NS RRset; see Section 6.1 for an explanation of
103    ///    canonical ordering.  The value of the Next Domain Name field in the
104    ///    last NSEC record in the zone is the name of the zone apex (the owner
105    ///    name of the zone's SOA RR).  This indicates that the owner name of
106    ///    the NSEC RR is the last name in the canonical ordering of the zone.
107    ///
108    ///    A sender MUST NOT use DNS name compression on the Next Domain Name
109    ///    field when transmitting an NSEC RR.
110    ///
111    ///    Owner names of RRsets for which the given zone is not authoritative
112    ///    (such as glue records) MUST NOT be listed in the Next Domain Name
113    ///    unless at least one authoritative RRset exists at the same owner
114    ///    name.
115    /// ```
116    pub fn next_domain_name(&self) -> &Name {
117        &self.next_domain_name
118    }
119
120    /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-4.1.2)
121    ///
122    /// ```text
123    /// 4.1.2.  The Type Bit Maps Field
124    ///
125    ///    The Type Bit Maps field identifies the RRset types that exist at the
126    ///    NSEC RR's owner name.
127    ///
128    ///    A zone MUST NOT include an NSEC RR for any domain name that only
129    ///    holds glue records.
130    /// ```
131    pub fn type_bit_maps(&self) -> impl Iterator<Item = RecordType> + '_ {
132        self.type_bit_maps.iter()
133    }
134
135    pub(crate) fn type_set(&self) -> &RecordTypeSet {
136        &self.type_bit_maps
137    }
138}
139
140impl BinEncodable for NSEC {
141    /// [RFC 6840](https://tools.ietf.org/html/rfc6840#section-6)
142    ///
143    /// ```text
144    /// 5.1.  Errors in Canonical Form Type Code List
145    ///
146    ///   When canonicalizing DNS names (for both ordering and signing), DNS
147    ///   names in the RDATA section of NSEC resource records are not converted
148    ///   to lowercase.  DNS names in the RDATA section of RRSIG resource
149    ///   records are converted to lowercase.
150    /// ```
151    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
152        encoder.with_canonical_names(|encoder| {
153            self.next_domain_name().emit(encoder)?;
154            self.type_bit_maps.emit(encoder)?;
155
156            Ok(())
157        })
158    }
159}
160
161impl<'r> RecordDataDecodable<'r> for NSEC {
162    fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<Self> {
163        let start_idx = decoder.index();
164
165        let next_domain_name = Name::read(decoder)?;
166
167        let offset = u16::try_from(decoder.index() - start_idx)
168            .map_err(|_| ProtoError::from("decoding offset too large in NSEC"))?;
169        let bit_map_len = length
170            .checked_sub(offset)
171            .map_err(|_| ProtoError::from("invalid rdata length in NSEC"))?;
172        let type_bit_maps = RecordTypeSet::read_data(decoder, bit_map_len)?;
173
174        Ok(Self {
175            next_domain_name,
176            type_bit_maps,
177        })
178    }
179}
180
181impl RecordData for NSEC {
182    fn try_from_rdata(data: RData) -> Result<Self, RData> {
183        match data {
184            RData::DNSSEC(DNSSECRData::NSEC(csync)) => Ok(csync),
185            _ => Err(data),
186        }
187    }
188
189    fn try_borrow(data: &RData) -> Option<&Self> {
190        match data {
191            RData::DNSSEC(DNSSECRData::NSEC(csync)) => Some(csync),
192            _ => None,
193        }
194    }
195
196    fn record_type(&self) -> RecordType {
197        RecordType::NSEC
198    }
199
200    fn into_rdata(self) -> RData {
201        RData::DNSSEC(DNSSECRData::NSEC(self))
202    }
203}
204
205/// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-4.2), DNSSEC Resource Records, March 2005
206///
207/// ```text
208/// 4.2.  The NSEC RR Presentation Format
209///
210///    The presentation format of the RDATA portion is as follows:
211///
212///    The Next Domain Name field is represented as a domain name.
213///
214///    The Type Bit Maps field is represented as a sequence of RR type
215///    mnemonics.  When the mnemonic is not known, the TYPE representation
216///    described in [RFC3597], Section 5, MUST be used.
217///
218/// 4.3.  NSEC RR Example
219///
220///    The following NSEC RR identifies the RRsets associated with
221///    alfa.example.com. and identifies the next authoritative name after
222///    alfa.example.com.
223///
224///    alfa.example.com. 86400 IN NSEC host.example.com. (
225///                                    A MX RRSIG NSEC TYPE1234 )
226///
227///    The first four text fields specify the name, TTL, Class, and RR type
228///    (NSEC).  The entry host.example.com. is the next authoritative name
229///    after alfa.example.com. in canonical order.  The A, MX, RRSIG, NSEC,
230///    and TYPE1234 mnemonics indicate that there are A, MX, RRSIG, NSEC,
231///    and TYPE1234 RRsets associated with the name alfa.example.com.
232///
233///    Assuming that the validator can authenticate this NSEC record, it
234///    could be used to prove that beta.example.com does not exist, or to
235///    prove that there is no AAAA record associated with alfa.example.com.
236///    Authenticated denial of existence is discussed in [RFC4035].
237/// ```
238impl fmt::Display for NSEC {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
240        write!(f, "{}", self.next_domain_name)?;
241
242        for ty in self.type_bit_maps.iter() {
243            write!(f, " {ty}")?;
244        }
245
246        Ok(())
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    #![allow(clippy::dbg_macro, clippy::print_stdout)]
253
254    use std::println;
255
256    use alloc::vec::Vec;
257
258    use super::*;
259
260    #[test]
261    fn test() {
262        use crate::rr::RecordType;
263        use core::str::FromStr;
264
265        let rdata = NSEC::new(
266            Name::from_str("www.example.com.").unwrap(),
267            [
268                RecordType::A,
269                RecordType::AAAA,
270                RecordType::DS,
271                RecordType::RRSIG,
272            ],
273        );
274
275        let mut bytes = Vec::new();
276        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
277        assert!(rdata.emit(&mut encoder).is_ok());
278        let bytes = encoder.into_bytes();
279
280        println!("bytes: {bytes:?}");
281
282        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
283        let restrict = Restrict::new(bytes.len() as u16);
284        let read_rdata = NSEC::read_data(&mut decoder, restrict).expect("Decoding error");
285        assert_eq!(rdata, read_rdata);
286    }
287
288    #[test]
289    fn rfc4034_example_rdata() {
290        // From section 4.3 of RFC 4034
291        let bytes = b"\x04host\
292        \x07example\
293        \x03com\x00\
294        \x00\x06\x40\x01\x00\x00\x00\x03\
295        \x04\x1b\x00\x00\x00\x00\x00\x00\
296        \x00\x00\x00\x00\x00\x00\x00\x00\
297        \x00\x00\x00\x00\x00\x00\x00\x00\
298        \x00\x00\x00\x00\x20";
299        let rdata = NSEC::new(
300            Name::parse("host.example.com.", None).unwrap(),
301            [
302                RecordType::A,
303                RecordType::MX,
304                RecordType::RRSIG,
305                RecordType::NSEC,
306                RecordType::Unknown(1234),
307            ],
308        );
309
310        let mut buffer = Vec::new();
311        let mut encoder = BinEncoder::new(&mut buffer);
312        rdata.emit(&mut encoder).expect("Encoding error");
313        assert_eq!(encoder.into_bytes(), bytes);
314
315        let mut decoder = BinDecoder::new(bytes);
316        let decoded = NSEC::read_data(&mut decoder, Restrict::new(bytes.len() as u16))
317            .expect("Decoding error");
318        assert_eq!(decoded, rdata);
319    }
320}