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}