hickory_proto/dnssec/rdata/nsec3.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 alloc::{fmt, string::ToString, vec::Vec};
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Deserializer, Serialize};
14
15use crate::{
16 dnssec::Nsec3HashAlgorithm,
17 error::{ProtoError, ProtoErrorKind, ProtoResult},
18 rr::{RData, RecordData, RecordDataDecodable, RecordType, RecordTypeSet, domain::Label},
19 serialize::binary::*,
20};
21
22use super::DNSSECRData;
23
24/// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3), NSEC3, March 2008
25///
26/// ```text
27/// 3. The NSEC3 Resource Record
28///
29/// The NSEC3 Resource Record (RR) provides authenticated denial of
30/// existence for DNS Resource Record Sets.
31///
32/// The NSEC3 RR lists RR types present at the original owner name of the
33/// NSEC3 RR. It includes the next hashed owner name in the hash order
34/// of the zone. The complete set of NSEC3 RRs in a zone indicates which
35/// RRSets exist for the original owner name of the RR and form a chain
36/// of hashed owner names in the zone. This information is used to
37/// provide authenticated denial of existence for DNS data. To provide
38/// protection against zone enumeration, the owner names used in the
39/// NSEC3 RR are cryptographic hashes of the original owner name
40/// prepended as a single label to the name of the zone. The NSEC3 RR
41/// indicates which hash function is used to construct the hash, which
42/// salt is used, and how many iterations of the hash function are
43/// performed over the original owner name. The hashing technique is
44/// described fully in Section 5.
45///
46/// Hashed owner names of unsigned delegations may be excluded from the
47/// chain. An NSEC3 RR whose span covers the hash of an owner name or
48/// "next closer" name of an unsigned delegation is referred to as an
49/// Opt-Out NSEC3 RR and is indicated by the presence of a flag.
50///
51/// The owner name for the NSEC3 RR is the base32 encoding of the hashed
52/// owner name prepended as a single label to the name of the zone.
53///
54/// The type value for the NSEC3 RR is 50.
55///
56/// The NSEC3 RR RDATA format is class independent and is described
57/// below.
58///
59/// The class MUST be the same as the class of the original owner name.
60///
61/// The NSEC3 RR SHOULD have the same TTL value as the SOA minimum TTL
62/// field. This is in the spirit of negative caching [RFC2308].
63///
64/// 3.2. NSEC3 RDATA Wire Format
65///
66/// The RDATA of the NSEC3 RR is as shown below:
67///
68/// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
69/// 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
70/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
71/// | Hash Alg. | Flags | Iterations |
72/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
73/// | Salt Length | Salt /
74/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
75/// | Hash Length | Next Hashed Owner Name /
76/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
77/// / Type Bit Maps /
78/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79///
80/// Hash Algorithm is a single octet.
81///
82/// Flags field is a single octet, the Opt-Out flag is the least
83/// significant bit, as shown below:
84///
85/// 0 1 2 3 4 5 6 7
86/// +-+-+-+-+-+-+-+-+
87/// | |O|
88/// +-+-+-+-+-+-+-+-+
89///
90/// Iterations is represented as a 16-bit unsigned integer, with the most
91/// significant bit first.
92///
93/// Salt Length is represented as an unsigned octet. Salt Length
94/// represents the length of the Salt field in octets. If the value is
95/// zero, the following Salt field is omitted.
96///
97/// Salt, if present, is encoded as a sequence of binary octets. The
98/// length of this field is determined by the preceding Salt Length
99/// field.
100///
101/// Hash Length is represented as an unsigned octet. Hash Length
102/// represents the length of the Next Hashed Owner Name field in octets.
103///
104/// The next hashed owner name is not base32 encoded, unlike the owner
105/// name of the NSEC3 RR. It is the unmodified binary hash value. It
106/// does not include the name of the containing zone. The length of this
107/// field is determined by the preceding Hash Length field.
108/// ```
109#[cfg_attr(feature = "serde", derive(Serialize))]
110#[derive(Debug, PartialEq, Eq, Hash, Clone)]
111pub struct NSEC3 {
112 hash_algorithm: Nsec3HashAlgorithm,
113 opt_out: bool,
114 iterations: u16,
115 salt: Vec<u8>,
116 next_hashed_owner_name: Vec<u8>,
117 /// The next hashed owner name, in base32-encoded form. If the next hashed owner name field is
118 /// too long, this may be `None` instead.
119 #[cfg_attr(feature = "serde", serde(skip_serializing))]
120 next_hashed_owner_name_base32: Option<Label>,
121 type_bit_maps: RecordTypeSet,
122}
123
124impl NSEC3 {
125 /// Constructs a new NSEC3 record
126 pub fn new(
127 hash_algorithm: Nsec3HashAlgorithm,
128 opt_out: bool,
129 iterations: u16,
130 salt: Vec<u8>,
131 next_hashed_owner_name: Vec<u8>,
132 type_bit_maps: impl IntoIterator<Item = RecordType>,
133 ) -> Self {
134 Self::with_record_type_set(
135 hash_algorithm,
136 opt_out,
137 iterations,
138 salt,
139 next_hashed_owner_name,
140 RecordTypeSet::new(type_bit_maps),
141 )
142 }
143
144 fn with_record_type_set(
145 hash_algorithm: Nsec3HashAlgorithm,
146 opt_out: bool,
147 iterations: u16,
148 salt: Vec<u8>,
149 next_hashed_owner_name: Vec<u8>,
150 type_bit_maps: RecordTypeSet,
151 ) -> Self {
152 let next_hashed_owner_name_base32 =
153 Label::from_ascii(&data_encoding::BASE32_DNSSEC.encode(&next_hashed_owner_name)).ok();
154 Self {
155 hash_algorithm,
156 opt_out,
157 iterations,
158 salt,
159 next_hashed_owner_name,
160 next_hashed_owner_name_base32,
161 type_bit_maps,
162 }
163 }
164
165 /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.1), NSEC3, March 2008
166 ///
167 /// ```text
168 /// 3.1.1. Hash Algorithm
169 ///
170 /// The Hash Algorithm field identifies the cryptographic hash algorithm
171 /// used to construct the hash-value.
172 ///
173 /// The values for this field are defined in the NSEC3 hash algorithm
174 /// registry defined in Section 11.
175 /// ```
176 pub fn hash_algorithm(&self) -> Nsec3HashAlgorithm {
177 self.hash_algorithm
178 }
179
180 /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.2), NSEC3, March 2008
181 ///
182 /// ```text
183 /// 3.1.2. Flags
184 ///
185 /// The Flags field contains 8 one-bit flags that can be used to indicate
186 /// different processing. All undefined flags must be zero. The only
187 /// flag defined by this specification is the Opt-Out flag.
188 ///
189 /// 3.1.2.1. Opt-Out Flag
190 ///
191 /// If the Opt-Out flag is set, the NSEC3 record covers zero or more
192 /// unsigned delegations.
193 ///
194 /// If the Opt-Out flag is clear, the NSEC3 record covers zero unsigned
195 /// delegations.
196 ///
197 /// The Opt-Out Flag indicates whether this NSEC3 RR may cover unsigned
198 /// delegations. It is the least significant bit in the Flags field.
199 /// See Section 6 for details about the use of this flag.
200 /// ```
201 pub fn opt_out(&self) -> bool {
202 self.opt_out
203 }
204
205 /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.3), NSEC3, March 2008
206 ///
207 /// ```text
208 /// 3.1.3. Iterations
209 ///
210 /// The Iterations field defines the number of additional times the hash
211 /// function has been performed. More iterations result in greater
212 /// resiliency of the hash value against dictionary attacks, but at a
213 /// higher computational cost for both the server and resolver. See
214 /// Section 5 for details of the use of this field, and Section 10.3 for
215 /// limitations on the value.
216 /// ```
217 pub fn iterations(&self) -> u16 {
218 self.iterations
219 }
220
221 /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.5), NSEC3, March 2008
222 ///
223 /// ```text
224 /// 3.1.5. Salt
225 ///
226 /// The Salt field is appended to the original owner name before hashing
227 /// in order to defend against pre-calculated dictionary attacks. See
228 /// Section 5 for details on how the salt is used.
229 /// ```
230 pub fn salt(&self) -> &[u8] {
231 &self.salt
232 }
233
234 /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.7), NSEC3, March 2008
235 ///
236 /// ```text
237 /// 3.1.7. Next Hashed Owner Name
238 ///
239 /// The Next Hashed Owner Name field contains the next hashed owner name
240 /// in hash order. This value is in binary format. Given the ordered
241 /// set of all hashed owner names, the Next Hashed Owner Name field
242 /// contains the hash of an owner name that immediately follows the owner
243 /// name of the given NSEC3 RR. The value of the Next Hashed Owner Name
244 /// field in the last NSEC3 RR in the zone is the same as the hashed
245 /// owner name of the first NSEC3 RR in the zone in hash order. Note
246 /// that, unlike the owner name of the NSEC3 RR, the value of this field
247 /// does not contain the appended zone name.
248 /// ```
249 pub fn next_hashed_owner_name(&self) -> &[u8] {
250 &self.next_hashed_owner_name
251 }
252
253 /// Returns the base32-encoded form of the next hashed owner name.
254 ///
255 /// This may return `None` if the next hashed owner name is too long.
256 pub fn next_hashed_owner_name_base32(&self) -> Option<&Label> {
257 self.next_hashed_owner_name_base32.as_ref()
258 }
259
260 /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.8), NSEC3, March 2008
261 ///
262 /// ```text
263 /// 3.1.8. Type Bit Maps
264 ///
265 /// The Type Bit Maps field identifies the RRSet types that exist at the
266 /// original owner name of the NSEC3 RR.
267 /// ```
268 pub fn type_bit_maps(&self) -> impl Iterator<Item = RecordType> + '_ {
269 self.type_bit_maps.iter()
270 }
271
272 pub(crate) fn type_set(&self) -> &RecordTypeSet {
273 &self.type_bit_maps
274 }
275
276 /// Flags for encoding
277 pub fn flags(&self) -> u8 {
278 let mut flags: u8 = 0;
279 if self.opt_out {
280 flags |= 0b0000_0001
281 };
282 flags
283 }
284}
285
286impl BinEncodable for NSEC3 {
287 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
288 encoder.emit(self.hash_algorithm().into())?;
289 encoder.emit(self.flags())?;
290 encoder.emit_u16(self.iterations())?;
291 encoder.emit(self.salt().len() as u8)?;
292 encoder.emit_vec(self.salt())?;
293 encoder.emit(self.next_hashed_owner_name().len() as u8)?;
294 encoder.emit_vec(self.next_hashed_owner_name())?;
295 self.type_bit_maps.emit(encoder)?;
296
297 Ok(())
298 }
299}
300
301impl<'r> RecordDataDecodable<'r> for NSEC3 {
302 fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<Self> {
303 let start_idx = decoder.index();
304
305 let hash_algorithm = Nsec3HashAlgorithm::from_u8(
306 decoder.read_u8()?.unverified(/*Algorithm verified as safe*/),
307 )?;
308 let flags: u8 = decoder
309 .read_u8()?
310 .verify_unwrap(|flags| flags & 0b1111_1110 == 0)
311 .map_err(|flags| ProtoError::from(ProtoErrorKind::UnrecognizedNsec3Flags(flags)))?;
312
313 let opt_out: bool = flags & 0b0000_0001 == 0b0000_0001;
314 let iterations: u16 = decoder.read_u16()?.unverified(/*valid as any u16*/);
315
316 // read the salt
317 let salt_len = decoder.read_u8()?.map(|u| u as usize);
318 let salt_len_max = length
319 .map(|u| u as usize)
320 .checked_sub(decoder.index() - start_idx)
321 .map_err(|_| "invalid rdata for salt_len_max")?;
322 let salt_len = salt_len
323 .verify_unwrap(|salt_len| {
324 *salt_len <= salt_len_max.unverified(/*safe in comparison usage*/)
325 })
326 .map_err(|_| ProtoError::from("salt_len exceeds buffer length"))?;
327 let salt: Vec<u8> =
328 decoder.read_vec(salt_len)?.unverified(/*salt is any valid array of bytes*/);
329
330 // read the hashed_owner_name
331 let hash_len = decoder.read_u8()?.map(|u| u as usize);
332 let hash_len_max = length
333 .map(|u| u as usize)
334 .checked_sub(decoder.index() - start_idx)
335 .map_err(|_| "invalid rdata for hash_len_max")?;
336 let hash_len = hash_len
337 .verify_unwrap(|hash_len| {
338 *hash_len <= hash_len_max.unverified(/*safe in comparison usage*/)
339 })
340 .map_err(|_| ProtoError::from("hash_len exceeds buffer length"))?;
341 let next_hashed_owner_name: Vec<u8> =
342 decoder.read_vec(hash_len)?.unverified(/*will fail in usage if invalid*/);
343
344 // read the bitmap
345 let offset = u16::try_from(decoder.index() - start_idx)
346 .map_err(|_| ProtoError::from("decoding offset too large in NSEC3"))?;
347 let bit_map_len = length
348 .checked_sub(offset)
349 .map_err(|_| "invalid rdata length in NSEC3")?;
350 let record_types = RecordTypeSet::read_data(decoder, bit_map_len)?;
351
352 Ok(Self::with_record_type_set(
353 hash_algorithm,
354 opt_out,
355 iterations,
356 salt,
357 next_hashed_owner_name,
358 record_types,
359 ))
360 }
361}
362
363impl RecordData for NSEC3 {
364 fn try_from_rdata(data: RData) -> Result<Self, RData> {
365 match data {
366 RData::DNSSEC(DNSSECRData::NSEC3(csync)) => Ok(csync),
367 _ => Err(data),
368 }
369 }
370
371 fn try_borrow(data: &RData) -> Option<&Self> {
372 match data {
373 RData::DNSSEC(DNSSECRData::NSEC3(csync)) => Some(csync),
374 _ => None,
375 }
376 }
377
378 fn record_type(&self) -> RecordType {
379 RecordType::NSEC3
380 }
381
382 fn into_rdata(self) -> RData {
383 RData::DNSSEC(DNSSECRData::NSEC3(self))
384 }
385}
386
387/// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.3), NSEC3, March 2008
388///
389/// ```text
390/// 3.3. Presentation Format
391///
392/// The presentation format of the RDATA portion is as follows:
393///
394/// o The Hash Algorithm field is represented as an unsigned decimal
395/// integer. The value has a maximum of 255.
396///
397/// o The Flags field is represented as an unsigned decimal integer.
398/// The value has a maximum of 255.
399///
400/// o The Iterations field is represented as an unsigned decimal
401/// integer. The value is between 0 and 65535, inclusive.
402///
403/// o The Salt Length field is not represented.
404///
405/// o The Salt field is represented as a sequence of case-insensitive
406/// hexadecimal digits. Whitespace is not allowed within the
407/// sequence. The Salt field is represented as "-" (without the
408/// quotes) when the Salt Length field has a value of 0.
409///
410/// o The Hash Length field is not represented.
411///
412/// o The Next Hashed Owner Name field is represented as an unpadded
413/// sequence of case-insensitive base32 digits, without whitespace.
414///
415/// o The Type Bit Maps field is represented as a sequence of RR type
416/// mnemonics. When the mnemonic is not known, the TYPE
417/// representation as described in Section 5 of [RFC3597] MUST be
418/// used.
419/// ```
420impl fmt::Display for NSEC3 {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
422 let salt = if self.salt.is_empty() {
423 "-".to_string()
424 } else {
425 data_encoding::HEXUPPER_PERMISSIVE.encode(&self.salt)
426 };
427
428 write!(
429 f,
430 "{alg} {flags} {iterations} {salt} {owner}",
431 alg = u8::from(self.hash_algorithm),
432 flags = self.flags(),
433 iterations = self.iterations,
434 salt = salt,
435 owner = data_encoding::BASE32_DNSSEC.encode(&self.next_hashed_owner_name)
436 )?;
437
438 for ty in self.type_bit_maps.iter() {
439 write!(f, " {ty}")?;
440 }
441
442 Ok(())
443 }
444}
445
446/// Helper struct used in deserialization of [`NSEC3`].
447///
448/// This struct excludes the `next_hashed_owner_name_base32` field, which is calculated from
449/// `next_hashed_owner_name`. Deserialization of `NSEC3` first uses the automatically generated
450/// `Deserialize` implementation provided by this struct, and then uses the [`NSEC3::new`]
451/// constructor. Deriving `Deserialize` on `NSEC3` directly would have broken the invariant
452/// relating `next_hashed_owner_name` and `next_hashed_owner_name_base32`.
453#[cfg(feature = "serde")]
454#[derive(Deserialize)]
455#[serde(rename = "NSEC3")]
456struct NSEC3Serde {
457 hash_algorithm: Nsec3HashAlgorithm,
458 opt_out: bool,
459 iterations: u16,
460 salt: Vec<u8>,
461 next_hashed_owner_name: Vec<u8>,
462 type_bit_maps: RecordTypeSet,
463}
464
465#[cfg(feature = "serde")]
466impl<'de> Deserialize<'de> for NSEC3 {
467 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
468 where
469 D: Deserializer<'de>,
470 {
471 let NSEC3Serde {
472 hash_algorithm,
473 opt_out,
474 iterations,
475 salt,
476 next_hashed_owner_name,
477 type_bit_maps,
478 } = NSEC3Serde::deserialize(deserializer)?;
479 Ok(Self::with_record_type_set(
480 hash_algorithm,
481 opt_out,
482 iterations,
483 salt,
484 next_hashed_owner_name,
485 type_bit_maps,
486 ))
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 #![allow(clippy::dbg_macro, clippy::print_stdout)]
493
494 use std::println;
495
496 use super::*;
497 use crate::dnssec::rdata::RecordType;
498
499 #[test]
500 fn test() {
501 let rdata = NSEC3::new(
502 Nsec3HashAlgorithm::SHA1,
503 true,
504 2,
505 vec![1, 2, 3, 4, 5],
506 vec![6, 7, 8, 9, 0],
507 [
508 RecordType::A,
509 RecordType::AAAA,
510 RecordType::DS,
511 RecordType::RRSIG,
512 ],
513 );
514
515 let mut bytes = Vec::new();
516 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
517 assert!(rdata.emit(&mut encoder).is_ok());
518 let bytes = encoder.into_bytes();
519
520 println!("bytes: {bytes:?}");
521
522 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
523 let restrict = Restrict::new(bytes.len() as u16);
524 let read_rdata = NSEC3::read_data(&mut decoder, restrict).expect("Decoding error");
525 assert_eq!(rdata, read_rdata);
526 }
527
528 #[test]
529 fn test_dups() {
530 let rdata_with_dups = NSEC3::new(
531 Nsec3HashAlgorithm::SHA1,
532 true,
533 2,
534 vec![1, 2, 3, 4, 5],
535 vec![6, 7, 8, 9, 0],
536 [
537 RecordType::A,
538 RecordType::AAAA,
539 RecordType::DS,
540 RecordType::AAAA,
541 RecordType::RRSIG,
542 ],
543 );
544
545 let rdata_wo = NSEC3::new(
546 Nsec3HashAlgorithm::SHA1,
547 true,
548 2,
549 vec![1, 2, 3, 4, 5],
550 vec![6, 7, 8, 9, 0],
551 [
552 RecordType::A,
553 RecordType::AAAA,
554 RecordType::DS,
555 RecordType::RRSIG,
556 ],
557 );
558
559 let mut bytes = Vec::new();
560 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
561 assert!(rdata_with_dups.emit(&mut encoder).is_ok());
562 let bytes = encoder.into_bytes();
563
564 println!("bytes: {bytes:?}");
565
566 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
567 let restrict = Restrict::new(bytes.len() as u16);
568 let read_rdata = NSEC3::read_data(&mut decoder, restrict).expect("Decoding error");
569 assert_eq!(rdata_wo, read_rdata);
570 }
571}