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}