hickory_proto/dnssec/rdata/nsec3param.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//! parameters used for the nsec3 hash method
9
10use alloc::{string::ToString, vec::Vec};
11use core::fmt;
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16use crate::{
17 dnssec::Nsec3HashAlgorithm,
18 error::{ProtoError, ProtoErrorKind, ProtoResult},
19 rr::{RData, RecordData, RecordType},
20 serialize::binary::{BinDecodable, BinDecoder, BinEncodable, BinEncoder},
21};
22
23use super::DNSSECRData;
24
25/// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4), NSEC3, March 2008
26///
27/// ```text
28/// 4. The NSEC3PARAM Resource Record
29///
30/// The NSEC3PARAM RR contains the NSEC3 parameters (hash algorithm,
31/// flags, iterations, and salt) needed by authoritative servers to
32/// calculate hashed owner names. The presence of an NSEC3PARAM RR at a
33/// zone apex indicates that the specified parameters may be used by
34/// authoritative servers to choose an appropriate set of NSEC3 RRs for
35/// negative responses. The NSEC3PARAM RR is not used by validators or
36/// resolvers.
37///
38/// If an NSEC3PARAM RR is present at the apex of a zone with a Flags
39/// field value of zero, then there MUST be an NSEC3 RR using the same
40/// hash algorithm, iterations, and salt parameters present at every
41/// hashed owner name in the zone. That is, the zone MUST contain a
42/// complete set of NSEC3 RRs with the same hash algorithm, iterations,
43/// and salt parameters.
44///
45/// The owner name for the NSEC3PARAM RR is the name of the zone apex.
46///
47/// The type value for the NSEC3PARAM RR is 51.
48///
49/// The NSEC3PARAM RR RDATA format is class independent and is described
50/// below.
51///
52/// The class MUST be the same as the NSEC3 RRs to which this RR refers.
53///
54/// 4.2. NSEC3PARAM RDATA Wire Format
55///
56/// The RDATA of the NSEC3PARAM RR is as shown below:
57///
58/// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
59/// 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
60/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
61/// | Hash Alg. | Flags | Iterations |
62/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
63/// | Salt Length | Salt /
64/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
65///
66/// Hash Algorithm is a single octet.
67///
68/// Flags field is a single octet.
69///
70/// Iterations is represented as a 16-bit unsigned integer, with the most
71/// significant bit first.
72///
73/// Salt Length is represented as an unsigned octet. Salt Length
74/// represents the length of the following Salt field in octets. If the
75/// value is zero, the Salt field is omitted.
76///
77/// Salt, if present, is encoded as a sequence of binary octets. The
78/// length of this field is determined by the preceding Salt Length
79/// field.
80/// ```
81#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
82#[derive(Debug, PartialEq, Eq, Hash, Clone)]
83pub struct NSEC3PARAM {
84 hash_algorithm: Nsec3HashAlgorithm,
85 opt_out: bool,
86 iterations: u16,
87 salt: Vec<u8>,
88}
89
90impl NSEC3PARAM {
91 /// Constructs a new NSEC3PARAM RData for use in a Resource Record
92 pub fn new(
93 hash_algorithm: Nsec3HashAlgorithm,
94 opt_out: bool,
95 iterations: u16,
96 salt: Vec<u8>,
97 ) -> Self {
98 Self {
99 hash_algorithm,
100 opt_out,
101 iterations,
102 salt,
103 }
104 }
105
106 /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4.1.1), NSEC3, March 2008
107 ///
108 /// ```text
109 /// 4.1.1. Hash Algorithm
110 ///
111 /// The Hash Algorithm field identifies the cryptographic hash algorithm
112 /// used to construct the hash-value.
113 ///
114 /// The acceptable values are the same as the corresponding field in the
115 /// NSEC3 RR.
116 /// ```
117 pub fn hash_algorithm(&self) -> Nsec3HashAlgorithm {
118 self.hash_algorithm
119 }
120
121 /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4.1.2), NSEC3, March 2008
122 ///
123 /// ```text
124 /// 4.1.2. Flag Fields
125 ///
126 /// The Opt-Out flag is not used and is set to zero.
127 ///
128 /// All other flags are reserved for future use, and must be zero.
129 ///
130 /// NSEC3PARAM RRs with a Flags field value other than zero MUST be
131 /// ignored.
132 /// ```
133 pub fn opt_out(&self) -> bool {
134 self.opt_out
135 }
136
137 /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4.1.3), NSEC3, March 2008
138 ///
139 /// ```text
140 /// 4.1.3. Iterations
141 ///
142 /// The Iterations field defines the number of additional times the hash
143 /// is performed.
144 ///
145 /// Its acceptable values are the same as the corresponding field in the
146 /// NSEC3 RR.
147 /// ```
148 pub fn iterations(&self) -> u16 {
149 self.iterations
150 }
151
152 /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4.1.5), NSEC3, March 2008
153 ///
154 /// ```text
155 /// 4.1.5. Salt
156 ///
157 /// The Salt field is appended to the original owner name before hashing.
158 /// ```
159 pub fn salt(&self) -> &[u8] {
160 &self.salt
161 }
162
163 /// flags for encoding
164 pub fn flags(&self) -> u8 {
165 let mut flags: u8 = 0;
166 if self.opt_out {
167 flags |= 0b0000_0001
168 };
169 flags
170 }
171}
172
173impl BinEncodable for NSEC3PARAM {
174 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
175 encoder.emit(self.hash_algorithm().into())?;
176 encoder.emit(self.flags())?;
177 encoder.emit_u16(self.iterations())?;
178 encoder.emit(self.salt().len() as u8)?;
179 encoder.emit_vec(self.salt())?;
180
181 Ok(())
182 }
183}
184
185impl<'r> BinDecodable<'r> for NSEC3PARAM {
186 fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult<Self> {
187 let hash_algorithm = Nsec3HashAlgorithm::from_u8(
188 decoder.read_u8()?.unverified(/*Algorithm verified as safe*/),
189 )?;
190 let flags: u8 = decoder
191 .read_u8()?
192 .verify_unwrap(|flags| flags & 0b1111_1110 == 0)
193 .map_err(|flags| ProtoError::from(ProtoErrorKind::UnrecognizedNsec3Flags(flags)))?;
194
195 let opt_out: bool = flags & 0b0000_0001 == 0b0000_0001;
196 let iterations: u16 = decoder.read_u16()?.unverified(/*valid as any u16*/);
197 let salt_len: usize = decoder
198 .read_u8()?
199 .map(|u| u as usize)
200 .verify_unwrap(|salt_len| *salt_len <= decoder.len())
201 .map_err(|_| ProtoError::from("salt_len exceeds buffer length"))?;
202 let salt: Vec<u8> = decoder.read_vec(salt_len)?.unverified(/*valid as any array of u8*/);
203
204 Ok(Self::new(hash_algorithm, opt_out, iterations, salt))
205 }
206}
207
208impl RecordData for NSEC3PARAM {
209 fn try_from_rdata(data: RData) -> Result<Self, RData> {
210 match data {
211 RData::DNSSEC(DNSSECRData::NSEC3PARAM(csync)) => Ok(csync),
212 _ => Err(data),
213 }
214 }
215
216 fn try_borrow(data: &RData) -> Option<&Self> {
217 match data {
218 RData::DNSSEC(DNSSECRData::NSEC3PARAM(csync)) => Some(csync),
219 _ => None,
220 }
221 }
222
223 fn record_type(&self) -> RecordType {
224 RecordType::NSEC3PARAM
225 }
226
227 fn into_rdata(self) -> RData {
228 RData::DNSSEC(DNSSECRData::NSEC3PARAM(self))
229 }
230}
231
232/// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4), NSEC3, March 2008
233///
234/// ```text
235/// 4.3. Presentation Format
236///
237/// The presentation format of the RDATA portion is as follows:
238///
239/// o The Hash Algorithm field is represented as an unsigned decimal
240/// integer. The value has a maximum of 255.
241///
242/// o The Flags field is represented as an unsigned decimal integer.
243/// The value has a maximum value of 255.
244///
245/// o The Iterations field is represented as an unsigned decimal
246/// integer. The value is between 0 and 65535, inclusive.
247///
248/// o The Salt Length field is not represented.
249///
250/// o The Salt field is represented as a sequence of case-insensitive
251/// hexadecimal digits. Whitespace is not allowed within the
252/// sequence. This field is represented as "-" (without the quotes)
253/// when the Salt Length field is zero.
254/// ```
255impl fmt::Display for NSEC3PARAM {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
257 let salt = if self.salt.is_empty() {
258 "-".to_string()
259 } else {
260 data_encoding::HEXUPPER_PERMISSIVE.encode(&self.salt)
261 };
262
263 write!(
264 f,
265 "{alg} {flags} {iterations} {salt}",
266 alg = u8::from(self.hash_algorithm),
267 flags = self.flags(),
268 iterations = self.iterations,
269 salt = salt
270 )
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 #![allow(clippy::dbg_macro, clippy::print_stdout)]
277
278 use std::println;
279
280 use super::*;
281
282 #[test]
283 fn test() {
284 let rdata = NSEC3PARAM::new(Nsec3HashAlgorithm::SHA1, true, 2, vec![1, 2, 3, 4, 5]);
285
286 let mut bytes = Vec::new();
287 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
288 assert!(rdata.emit(&mut encoder).is_ok());
289 let bytes = encoder.into_bytes();
290
291 println!("bytes: {bytes:?}");
292
293 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
294 let read_rdata = NSEC3PARAM::read(&mut decoder).expect("Decoding error");
295 assert_eq!(rdata, read_rdata);
296 }
297}