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}