hickory_proto/dnssec/
nsec3.rs

1/*
2 * Copyright (C) 2015 Benjamin Fry <benjaminfry@me.com>
3 * Copyright (C) 2017 Google LLC.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18//! NSEC3 related record types
19
20use alloc::vec::Vec;
21
22#[cfg(feature = "serde")]
23use serde::{Deserialize, Serialize};
24
25use super::{DigestType, crypto::Digest};
26use crate::error::*;
27use crate::rr::Name;
28use crate::serialize::binary::{BinEncodable, BinEncoder};
29
30/// ```text
31/// RFC 5155                         NSEC3                        March 2008
32///
33/// 11.  IANA Considerations
34///
35///    Although the NSEC3 and NSEC3PARAM RR formats include a hash algorithm
36///    parameter, this document does not define a particular mechanism for
37///    safely transitioning from one NSEC3 hash algorithm to another.  When
38///    specifying a new hash algorithm for use with NSEC3, a transition
39///    mechanism MUST also be defined.
40///
41///    This document updates the IANA registry "DOMAIN NAME SYSTEM
42///    PARAMETERS" (https://www.iana.org/assignments/dns-parameters) in sub-
43///    registry "TYPES", by defining two new types.  Section 3 defines the
44///    NSEC3 RR type 50.  Section 4 defines the NSEC3PARAM RR type 51.
45///
46///    This document updates the IANA registry "DNS SECURITY ALGORITHM
47///    NUMBERS -- per [RFC4035]"
48///    (https://www.iana.org/assignments/dns-sec-alg-numbers).  Section 2
49///    defines the aliases DSA-NSEC3-SHA1 (6) and RSASHA1-NSEC3-SHA1 (7) for
50///    respectively existing registrations DSA and RSASHA1 in combination
51///    with NSEC3 hash algorithm SHA1.
52///
53///    Since these algorithm numbers are aliases for existing DNSKEY
54///    algorithm numbers, the flags that exist for the original algorithm
55///    are valid for the alias algorithm.
56///
57///    This document creates a new IANA registry for NSEC3 flags.  This
58///    registry is named "DNSSEC NSEC3 Flags".  The initial contents of this
59///    registry are:
60///
61///      0   1   2   3   4   5   6   7
62///    +---+---+---+---+---+---+---+---+
63///    |   |   |   |   |   |   |   |Opt|
64///    |   |   |   |   |   |   |   |Out|
65///    +---+---+---+---+---+---+---+---+
66///
67///       bit 7 is the Opt-Out flag.
68///
69///       bits 0 - 6 are available for assignment.
70///
71///    Assignment of additional NSEC3 Flags in this registry requires IETF
72///    Standards Action [RFC2434].
73///
74///    This document creates a new IANA registry for NSEC3PARAM flags.  This
75///    registry is named "DNSSEC NSEC3PARAM Flags".  The initial contents of
76///    this registry are:
77///
78///      0   1   2   3   4   5   6   7
79///    +---+---+---+---+---+---+---+---+
80///    |   |   |   |   |   |   |   | 0 |
81///    +---+---+---+---+---+---+---+---+
82///
83///       bit 7 is reserved and must be 0.
84///
85///       bits 0 - 6 are available for assignment.
86///
87///    Assignment of additional NSEC3PARAM Flags in this registry requires
88///    IETF Standards Action [RFC2434].
89///
90///    Finally, this document creates a new IANA registry for NSEC3 hash
91///    algorithms.  This registry is named "DNSSEC NSEC3 Hash Algorithms".
92///    The initial contents of this registry are:
93///
94///       0 is Reserved.
95///
96///       1 is SHA-1.
97///
98///       2-255 Available for assignment.
99///
100///    Assignment of additional NSEC3 hash algorithms in this registry
101///    requires IETF Standards Action [RFC2434].
102/// ```
103#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
104#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]
105pub enum Nsec3HashAlgorithm {
106    /// Hash for the Nsec3 records
107    #[default]
108    #[cfg_attr(feature = "serde", serde(rename = "SHA-1"))]
109    SHA1,
110}
111
112impl Nsec3HashAlgorithm {
113    /// <https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml>
114    pub fn from_u8(value: u8) -> ProtoResult<Self> {
115        match value {
116            1 => Ok(Self::SHA1),
117            // TODO: where/when is SHA2?
118            _ => Err(ProtoErrorKind::UnknownAlgorithmTypeValue(value).into()),
119        }
120    }
121
122    /// ```text
123    /// Laurie, et al.              Standards Track                    [Page 14]
124    ///
125    /// RFC 5155                         NSEC3                        March 2008
126    ///
127    /// Define H(x) to be the hash of x using the Hash Algorithm selected by
128    ///    the NSEC3 RR, k to be the number of Iterations, and || to indicate
129    ///    concatenation.  Then define:
130    ///
131    ///       IH(salt, x, 0) = H(x || salt), and
132    ///
133    ///       IH(salt, x, k) = H(IH(salt, x, k-1) || salt), if k > 0
134    ///
135    ///    Then the calculated hash of an owner name is
136    ///
137    ///       IH(salt, owner name, iterations),
138    ///
139    ///    where the owner name is in the canonical form, defined as:
140    ///
141    ///    The wire format of the owner name where:
142    ///
143    ///    1.  The owner name is fully expanded (no DNS name compression) and
144    ///        fully qualified;
145    ///
146    ///    2.  All uppercase US-ASCII letters are replaced by the corresponding
147    ///        lowercase US-ASCII letters;
148    ///
149    ///    3.  If the owner name is a wildcard name, the owner name is in its
150    ///        original unexpanded form, including the "*" label (no wildcard
151    ///        substitution);
152    /// ```
153    pub fn hash(self, salt: &[u8], name: &Name, iterations: u16) -> ProtoResult<Digest> {
154        match self {
155            // if there ever is more than just SHA1 support, this should be a genericized method
156            Self::SHA1 => {
157                let mut buf: Vec<u8> = Vec::new();
158                {
159                    let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut buf);
160                    encoder.set_canonical_names(true);
161                    name.to_lowercase().emit(&mut encoder)?;
162                }
163
164                Digest::iterated(salt, &buf, DigestType::SHA1, iterations)
165            }
166        }
167    }
168}
169
170impl From<Nsec3HashAlgorithm> for u8 {
171    fn from(a: Nsec3HashAlgorithm) -> Self {
172        match a {
173            Nsec3HashAlgorithm::SHA1 => 1,
174        }
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use alloc::string::String;
181    use core::str::FromStr;
182
183    use super::*;
184
185    #[test]
186    fn test_hash() {
187        let name = Name::from_str("www.example.com").unwrap();
188        let salt: Vec<u8> = vec![1, 2, 3, 4];
189
190        assert_eq!(
191            Nsec3HashAlgorithm::SHA1
192                .hash(&salt, &name, 0)
193                .unwrap()
194                .as_ref()
195                .len(),
196            20
197        );
198        assert_eq!(
199            Nsec3HashAlgorithm::SHA1
200                .hash(&salt, &name, 1)
201                .unwrap()
202                .as_ref()
203                .len(),
204            20
205        );
206        assert_eq!(
207            Nsec3HashAlgorithm::SHA1
208                .hash(&salt, &name, 3)
209                .unwrap()
210                .as_ref()
211                .len(),
212            20
213        );
214    }
215
216    #[test]
217    fn test_known_hashes() {
218        // H(example)       = 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom
219        assert_eq!(
220            hash_with_base32("example"),
221            "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom"
222        );
223        assert_eq!(
224            hash_with_base32("EXAMPLE"),
225            "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom"
226        );
227
228        // H(a.example)     = 35mthgpgcu1qg68fab165klnsnk3dpvl
229        assert_eq!(
230            hash_with_base32("a.example"),
231            "35mthgpgcu1qg68fab165klnsnk3dpvl"
232        );
233
234        // H(ai.example)    = gjeqe526plbf1g8mklp59enfd789njgi
235        assert_eq!(
236            hash_with_base32("ai.example"),
237            "gjeqe526plbf1g8mklp59enfd789njgi"
238        );
239
240        // H(ns1.example)   = 2t7b4g4vsa5smi47k61mv5bv1a22bojr
241        assert_eq!(
242            hash_with_base32("ns1.example"),
243            "2t7b4g4vsa5smi47k61mv5bv1a22bojr"
244        );
245
246        // H(ns2.example)   = q04jkcevqvmu85r014c7dkba38o0ji5r
247        assert_eq!(
248            hash_with_base32("ns2.example"),
249            "q04jkcevqvmu85r014c7dkba38o0ji5r"
250        );
251
252        // H(w.example)     = k8udemvp1j2f7eg6jebps17vp3n8i58h
253        assert_eq!(
254            hash_with_base32("w.example"),
255            "k8udemvp1j2f7eg6jebps17vp3n8i58h"
256        );
257
258        // H(*.w.example)   = r53bq7cc2uvmubfu5ocmm6pers9tk9en
259        assert_eq!(
260            hash_with_base32("*.w.example"),
261            "r53bq7cc2uvmubfu5ocmm6pers9tk9en"
262        );
263
264        // H(x.w.example)   = b4um86eghhds6nea196smvmlo4ors995
265        assert_eq!(
266            hash_with_base32("x.w.example"),
267            "b4um86eghhds6nea196smvmlo4ors995"
268        );
269
270        // H(y.w.example)   = ji6neoaepv8b5o6k4ev33abha8ht9fgc
271        assert_eq!(
272            hash_with_base32("y.w.example"),
273            "ji6neoaepv8b5o6k4ev33abha8ht9fgc"
274        );
275
276        // H(x.y.w.example) = 2vptu5timamqttgl4luu9kg21e0aor3s
277        assert_eq!(
278            hash_with_base32("x.y.w.example"),
279            "2vptu5timamqttgl4luu9kg21e0aor3s"
280        );
281
282        // H(xx.example)    = t644ebqk9bibcna874givr6joj62mlhv
283        assert_eq!(
284            hash_with_base32("xx.example"),
285            "t644ebqk9bibcna874givr6joj62mlhv"
286        );
287    }
288
289    #[cfg(test)]
290    fn hash_with_base32(name: &str) -> String {
291        use data_encoding::BASE32_DNSSEC;
292
293        // NSEC3PARAM 1 0 12 aabbccdd
294        let known_name = Name::from_ascii(name).unwrap();
295        let known_salt = [0xAAu8, 0xBBu8, 0xCCu8, 0xDDu8];
296        let hash = Nsec3HashAlgorithm::SHA1
297            .hash(&known_salt, &known_name, 12)
298            .unwrap();
299        BASE32_DNSSEC.encode(hash.as_ref())
300    }
301}