hickory_proto/rr/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
9use std::fmt;
10
11#[cfg(feature = "serde-config")]
12use serde::{Deserialize, Serialize};
13
14use crate::error::*;
15use crate::rr::type_bit_map::{decode_type_bit_maps, encode_type_bit_maps};
16use crate::rr::{Name, RData, RecordData, RecordDataDecodable, RecordType};
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-config", derive(Deserialize, Serialize))]
46#[derive(Debug, PartialEq, Eq, Hash, Clone)]
47pub struct NSEC {
48 next_domain_name: Name,
49 type_bit_maps: Vec<RecordType>,
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(next_domain_name: Name, type_bit_maps: Vec<RecordType>) -> Self {
65 Self {
66 next_domain_name,
67 type_bit_maps,
68 }
69 }
70
71 /// Constructs a new NSEC RData, this will add the NSEC itself as covered, generally
72 /// correct for NSEC records generated at their own name
73 ///
74 /// # Arguments
75 ///
76 /// * `next_domain_name` - the name labels of the next ordered name in the zone
77 /// * `type_bit_maps` - a bit map of the types that exist at this name
78 ///
79 /// # Returns
80 ///
81 /// An NSEC RData for use in a Resource Record
82 pub fn new_cover_self(next_domain_name: Name, mut type_bit_maps: Vec<RecordType>) -> Self {
83 type_bit_maps.push(RecordType::NSEC);
84
85 Self::new(next_domain_name, type_bit_maps)
86 }
87
88 /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-4.1.1), DNSSEC Resource Records, March 2005
89 ///
90 /// ```text
91 /// 4.1.1. The Next Domain Name Field
92 ///
93 /// The Next Domain field contains the next owner name (in the canonical
94 /// ordering of the zone) that has authoritative data or contains a
95 /// delegation point NS RRset; see Section 6.1 for an explanation of
96 /// canonical ordering. The value of the Next Domain Name field in the
97 /// last NSEC record in the zone is the name of the zone apex (the owner
98 /// name of the zone's SOA RR). This indicates that the owner name of
99 /// the NSEC RR is the last name in the canonical ordering of the zone.
100 ///
101 /// A sender MUST NOT use DNS name compression on the Next Domain Name
102 /// field when transmitting an NSEC RR.
103 ///
104 /// Owner names of RRsets for which the given zone is not authoritative
105 /// (such as glue records) MUST NOT be listed in the Next Domain Name
106 /// unless at least one authoritative RRset exists at the same owner
107 /// name.
108 /// ```
109 pub fn next_domain_name(&self) -> &Name {
110 &self.next_domain_name
111 }
112
113 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-4.1.2)
114 ///
115 /// ```text
116 /// 4.1.2. The Type Bit Maps Field
117 ///
118 /// The Type Bit Maps field identifies the RRset types that exist at the
119 /// NSEC RR's owner name.
120 ///
121 /// A zone MUST NOT include an NSEC RR for any domain name that only
122 /// holds glue records.
123 /// ```
124 pub fn type_bit_maps(&self) -> &[RecordType] {
125 &self.type_bit_maps
126 }
127}
128
129impl BinEncodable for NSEC {
130 /// [RFC 6840](https://tools.ietf.org/html/rfc6840#section-6)
131 ///
132 /// ```text
133 /// 5.1. Errors in Canonical Form Type Code List
134 ///
135 /// When canonicalizing DNS names (for both ordering and signing), DNS
136 /// names in the RDATA section of NSEC resource records are not converted
137 /// to lowercase. DNS names in the RDATA section of RRSIG resource
138 /// records are converted to lowercase.
139 /// ```
140 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
141 encoder.with_canonical_names(|encoder| {
142 self.next_domain_name().emit(encoder)?;
143 encode_type_bit_maps(encoder, self.type_bit_maps())
144 })
145 }
146}
147
148impl<'r> RecordDataDecodable<'r> for NSEC {
149 fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<Self> {
150 let start_idx = decoder.index();
151
152 let next_domain_name = Name::read(decoder)?;
153
154 let bit_map_len = length
155 .map(|u| u as usize)
156 .checked_sub(decoder.index() - start_idx)
157 .map_err(|_| ProtoError::from("invalid rdata length in NSEC"))?;
158 let record_types = decode_type_bit_maps(decoder, bit_map_len)?;
159
160 Ok(Self::new(next_domain_name, record_types))
161 }
162}
163
164impl RecordData for NSEC {
165 fn try_from_rdata(data: RData) -> Result<Self, RData> {
166 match data {
167 RData::DNSSEC(DNSSECRData::NSEC(csync)) => Ok(csync),
168 _ => Err(data),
169 }
170 }
171
172 fn try_borrow(data: &RData) -> Option<&Self> {
173 match data {
174 RData::DNSSEC(DNSSECRData::NSEC(csync)) => Some(csync),
175 _ => None,
176 }
177 }
178
179 fn record_type(&self) -> RecordType {
180 RecordType::NSEC
181 }
182
183 fn into_rdata(self) -> RData {
184 RData::DNSSEC(DNSSECRData::NSEC(self))
185 }
186}
187
188/// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-4.2), DNSSEC Resource Records, March 2005
189///
190/// ```text
191/// 4.2. The NSEC RR Presentation Format
192///
193/// The presentation format of the RDATA portion is as follows:
194///
195/// The Next Domain Name field is represented as a domain name.
196///
197/// The Type Bit Maps field is represented as a sequence of RR type
198/// mnemonics. When the mnemonic is not known, the TYPE representation
199/// described in [RFC3597], Section 5, MUST be used.
200///
201/// 4.3. NSEC RR Example
202///
203/// The following NSEC RR identifies the RRsets associated with
204/// alfa.example.com. and identifies the next authoritative name after
205/// alfa.example.com.
206///
207/// alfa.example.com. 86400 IN NSEC host.example.com. (
208/// A MX RRSIG NSEC TYPE1234 )
209///
210/// The first four text fields specify the name, TTL, Class, and RR type
211/// (NSEC). The entry host.example.com. is the next authoritative name
212/// after alfa.example.com. in canonical order. The A, MX, RRSIG, NSEC,
213/// and TYPE1234 mnemonics indicate that there are A, MX, RRSIG, NSEC,
214/// and TYPE1234 RRsets associated with the name alfa.example.com.
215///
216/// Assuming that the validator can authenticate this NSEC record, it
217/// could be used to prove that beta.example.com does not exist, or to
218/// prove that there is no AAAA record associated with alfa.example.com.
219/// Authenticated denial of existence is discussed in [RFC4035].
220/// ```
221impl fmt::Display for NSEC {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
223 write!(f, "{}", self.next_domain_name)?;
224
225 for ty in &self.type_bit_maps {
226 write!(f, " {ty}")?;
227 }
228
229 Ok(())
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 #![allow(clippy::dbg_macro, clippy::print_stdout)]
236
237 use super::*;
238
239 #[test]
240 fn test() {
241 use crate::rr::RecordType;
242 use std::str::FromStr;
243
244 let rdata = NSEC::new(
245 Name::from_str("www.example.com").unwrap(),
246 vec![
247 RecordType::A,
248 RecordType::AAAA,
249 RecordType::DS,
250 RecordType::RRSIG,
251 ],
252 );
253
254 let mut bytes = Vec::new();
255 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
256 assert!(rdata.emit(&mut encoder).is_ok());
257 let bytes = encoder.into_bytes();
258
259 println!("bytes: {bytes:?}");
260
261 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
262 let restrict = Restrict::new(bytes.len() as u16);
263 let read_rdata = NSEC::read_data(&mut decoder, restrict).expect("Decoding error");
264 assert_eq!(rdata, read_rdata);
265 }
266}