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}