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