hickory_proto/rr/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#![allow(clippy::use_self)]
20
21#[cfg(feature = "serde-config")]
22use serde::{Deserialize, Serialize};
23
24#[cfg(any(feature = "openssl", feature = "ring"))]
25use super::{Digest, DigestType};
26use crate::error::*;
27#[cfg(any(feature = "openssl", feature = "ring"))]
28use crate::rr::Name;
29#[cfg(any(feature = "openssl", feature = "ring"))]
30use crate::serialize::binary::{BinEncodable, BinEncoder};
31
32/// ```text
33/// RFC 5155                         NSEC3                        March 2008
34///
35/// 11.  IANA Considerations
36///
37///    Although the NSEC3 and NSEC3PARAM RR formats include a hash algorithm
38///    parameter, this document does not define a particular mechanism for
39///    safely transitioning from one NSEC3 hash algorithm to another.  When
40///    specifying a new hash algorithm for use with NSEC3, a transition
41///    mechanism MUST also be defined.
42///
43///    This document updates the IANA registry "DOMAIN NAME SYSTEM
44///    PARAMETERS" (https://www.iana.org/assignments/dns-parameters) in sub-
45///    registry "TYPES", by defining two new types.  Section 3 defines the
46///    NSEC3 RR type 50.  Section 4 defines the NSEC3PARAM RR type 51.
47///
48///    This document updates the IANA registry "DNS SECURITY ALGORITHM
49///    NUMBERS -- per [RFC4035]"
50///    (https://www.iana.org/assignments/dns-sec-alg-numbers).  Section 2
51///    defines the aliases DSA-NSEC3-SHA1 (6) and RSASHA1-NSEC3-SHA1 (7) for
52///    respectively existing registrations DSA and RSASHA1 in combination
53///    with NSEC3 hash algorithm SHA1.
54///
55///    Since these algorithm numbers are aliases for existing DNSKEY
56///    algorithm numbers, the flags that exist for the original algorithm
57///    are valid for the alias algorithm.
58///
59///    This document creates a new IANA registry for NSEC3 flags.  This
60///    registry is named "DNSSEC NSEC3 Flags".  The initial contents of this
61///    registry are:
62///
63///      0   1   2   3   4   5   6   7
64///    +---+---+---+---+---+---+---+---+
65///    |   |   |   |   |   |   |   |Opt|
66///    |   |   |   |   |   |   |   |Out|
67///    +---+---+---+---+---+---+---+---+
68///
69///       bit 7 is the Opt-Out flag.
70///
71///       bits 0 - 6 are available for assignment.
72///
73///    Assignment of additional NSEC3 Flags in this registry requires IETF
74///    Standards Action [RFC2434].
75///
76///    This document creates a new IANA registry for NSEC3PARAM flags.  This
77///    registry is named "DNSSEC NSEC3PARAM Flags".  The initial contents of
78///    this registry are:
79///
80///      0   1   2   3   4   5   6   7
81///    +---+---+---+---+---+---+---+---+
82///    |   |   |   |   |   |   |   | 0 |
83///    +---+---+---+---+---+---+---+---+
84///
85///       bit 7 is reserved and must be 0.
86///
87///       bits 0 - 6 are available for assignment.
88///
89///    Assignment of additional NSEC3PARAM Flags in this registry requires
90///    IETF Standards Action [RFC2434].
91///
92///    Finally, this document creates a new IANA registry for NSEC3 hash
93///    algorithms.  This registry is named "DNSSEC NSEC3 Hash Algorithms".
94///    The initial contents of this registry are:
95///
96///       0 is Reserved.
97///
98///       1 is SHA-1.
99///
100///       2-255 Available for assignment.
101///
102///    Assignment of additional NSEC3 hash algorithms in this registry
103///    requires IETF Standards Action [RFC2434].
104/// ```
105#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
106#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
107pub enum Nsec3HashAlgorithm {
108    /// Hash for the Nsec3 records
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    #[cfg(any(feature = "openssl", feature = "ring"))]
154    #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))]
155    pub fn hash(self, salt: &[u8], name: &Name, iterations: u16) -> ProtoResult<Digest> {
156        match self {
157            // if there ever is more than just SHA1 support, this should be a genericized method
158            Self::SHA1 => {
159                let mut buf: Vec<u8> = Vec::new();
160                {
161                    let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut buf);
162                    encoder.set_canonical_names(true);
163                    name.emit(&mut encoder).expect("could not encode Name");
164                }
165
166                Self::sha1_recursive_hash(salt, buf, iterations)
167            }
168        }
169    }
170
171    /// until there is another supported algorithm, just hardcoded to this.
172    #[cfg(any(feature = "openssl", feature = "ring"))]
173    fn sha1_recursive_hash(salt: &[u8], bytes: Vec<u8>, iterations: u16) -> ProtoResult<Digest> {
174        let digested: Digest;
175        let to_digest = if iterations > 0 {
176            digested = Self::sha1_recursive_hash(salt, bytes, iterations - 1)?;
177            digested.as_ref()
178        } else {
179            &bytes
180        };
181        DigestType::SHA1.digest_all(&[to_digest, salt])
182    }
183}
184
185impl From<Nsec3HashAlgorithm> for u8 {
186    fn from(a: Nsec3HashAlgorithm) -> Self {
187        match a {
188            Nsec3HashAlgorithm::SHA1 => 1,
189        }
190    }
191}
192
193#[test]
194#[cfg(any(feature = "openssl", feature = "ring"))]
195fn test_hash() {
196    use std::str::FromStr;
197
198    let name = Name::from_str("www.example.com").unwrap();
199    let salt: Vec<u8> = vec![1, 2, 3, 4];
200
201    assert_eq!(
202        Nsec3HashAlgorithm::SHA1
203            .hash(&salt, &name, 0)
204            .unwrap()
205            .as_ref()
206            .len(),
207        20
208    );
209    assert_eq!(
210        Nsec3HashAlgorithm::SHA1
211            .hash(&salt, &name, 1)
212            .unwrap()
213            .as_ref()
214            .len(),
215        20
216    );
217    assert_eq!(
218        Nsec3HashAlgorithm::SHA1
219            .hash(&salt, &name, 3)
220            .unwrap()
221            .as_ref()
222            .len(),
223        20
224    );
225}
226
227#[test]
228#[cfg(any(feature = "openssl", feature = "ring"))]
229fn test_known_hashes() {
230    // H(example)       = 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom
231    assert_eq!(
232        hash_with_base32("example"),
233        "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom"
234    );
235
236    // H(a.example)     = 35mthgpgcu1qg68fab165klnsnk3dpvl
237    assert_eq!(
238        hash_with_base32("a.example"),
239        "35mthgpgcu1qg68fab165klnsnk3dpvl"
240    );
241
242    // H(ai.example)    = gjeqe526plbf1g8mklp59enfd789njgi
243    assert_eq!(
244        hash_with_base32("ai.example"),
245        "gjeqe526plbf1g8mklp59enfd789njgi"
246    );
247
248    // H(ns1.example)   = 2t7b4g4vsa5smi47k61mv5bv1a22bojr
249    assert_eq!(
250        hash_with_base32("ns1.example"),
251        "2t7b4g4vsa5smi47k61mv5bv1a22bojr"
252    );
253
254    // H(ns2.example)   = q04jkcevqvmu85r014c7dkba38o0ji5r
255    assert_eq!(
256        hash_with_base32("ns2.example"),
257        "q04jkcevqvmu85r014c7dkba38o0ji5r"
258    );
259
260    // H(w.example)     = k8udemvp1j2f7eg6jebps17vp3n8i58h
261    assert_eq!(
262        hash_with_base32("w.example"),
263        "k8udemvp1j2f7eg6jebps17vp3n8i58h"
264    );
265
266    // H(*.w.example)   = r53bq7cc2uvmubfu5ocmm6pers9tk9en
267    assert_eq!(
268        hash_with_base32("*.w.example"),
269        "r53bq7cc2uvmubfu5ocmm6pers9tk9en"
270    );
271
272    // H(x.w.example)   = b4um86eghhds6nea196smvmlo4ors995
273    assert_eq!(
274        hash_with_base32("x.w.example"),
275        "b4um86eghhds6nea196smvmlo4ors995"
276    );
277
278    // H(y.w.example)   = ji6neoaepv8b5o6k4ev33abha8ht9fgc
279    assert_eq!(
280        hash_with_base32("y.w.example"),
281        "ji6neoaepv8b5o6k4ev33abha8ht9fgc"
282    );
283
284    // H(x.y.w.example) = 2vptu5timamqttgl4luu9kg21e0aor3s
285    assert_eq!(
286        hash_with_base32("x.y.w.example"),
287        "2vptu5timamqttgl4luu9kg21e0aor3s"
288    );
289
290    // H(xx.example)    = t644ebqk9bibcna874givr6joj62mlhv
291    assert_eq!(
292        hash_with_base32("xx.example"),
293        "t644ebqk9bibcna874givr6joj62mlhv"
294    );
295}
296
297#[cfg(test)]
298#[cfg(any(feature = "openssl", feature = "ring"))]
299fn hash_with_base32(name: &str) -> String {
300    use data_encoding::BASE32_DNSSEC;
301
302    // NSEC3PARAM 1 0 12 aabbccdd
303    let known_name = Name::parse(name, Some(&Name::new())).unwrap();
304    let known_salt = [0xAAu8, 0xBBu8, 0xCCu8, 0xDDu8];
305    let hash = Nsec3HashAlgorithm::SHA1
306        .hash(&known_salt, &known_name, 12)
307        .unwrap();
308    BASE32_DNSSEC.encode(hash.as_ref())
309}