hickory_proto/rr/dnssec/rdata/ds.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//! pointer record from parent zone to child zone for dnskey proof
9
10use std::fmt::{self, Display, Formatter};
11
12#[cfg(feature = "serde-config")]
13use serde::{Deserialize, Serialize};
14
15use crate::{
16 error::{ProtoError, ProtoResult},
17 rr::{
18 dnssec::{rdata::DNSKEY, Algorithm, DigestType},
19 Name, RData, RecordData, RecordDataDecodable, RecordType,
20 },
21 serialize::binary::*,
22};
23
24use super::DNSSECRData;
25
26/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5)
27///
28/// ```text
29/// 5.1. DS RDATA Wire Format
30///
31/// The RDATA for a DS RR consists of a 2 octet Key Tag field, a 1 octet
32/// Algorithm field, a 1 octet Digest Type field, and a Digest field.
33///
34/// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
35/// 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
36/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37/// | Key Tag | Algorithm | Digest Type |
38/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
39/// / /
40/// / Digest /
41/// / /
42/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
43///
44/// 5.2. Processing of DS RRs When Validating Responses
45///
46/// The DS RR links the authentication chain across zone boundaries, so
47/// the DS RR requires extra care in processing. The DNSKEY RR referred
48/// to in the DS RR MUST be a DNSSEC zone key. The DNSKEY RR Flags MUST
49/// have Flags bit 7 set. If the DNSKEY flags do not indicate a DNSSEC
50/// zone key, the DS RR (and the DNSKEY RR it references) MUST NOT be
51/// used in the validation process.
52///
53/// 5.3. The DS RR Presentation Format
54///
55/// The presentation format of the RDATA portion is as follows:
56///
57/// The Key Tag field MUST be represented as an unsigned decimal integer.
58///
59/// The Algorithm field MUST be represented either as an unsigned decimal
60/// integer or as an algorithm mnemonic specified in Appendix A.1.
61///
62/// The Digest Type field MUST be represented as an unsigned decimal
63/// integer.
64///
65/// The Digest MUST be represented as a sequence of case-insensitive
66/// hexadecimal digits. Whitespace is allowed within the hexadecimal
67/// text.
68/// ```
69#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
70#[derive(Debug, PartialEq, Eq, Hash, Clone)]
71pub struct DS {
72 key_tag: u16,
73 algorithm: Algorithm,
74 digest_type: DigestType,
75 digest: Vec<u8>,
76}
77
78impl DS {
79 /// Constructs a new DS RData
80 ///
81 /// # Arguments
82 ///
83 /// * `key_tag` - the key_tag associated to the DNSKEY
84 /// * `algorithm` - algorithm as specified in the DNSKEY
85 /// * `digest_type` - hash algorithm used to validate the DNSKEY
86 /// * `digest` - hash of the DNSKEY
87 ///
88 /// # Returns
89 ///
90 /// the DS RDATA for use in a Resource Record
91 pub fn new(
92 key_tag: u16,
93 algorithm: Algorithm,
94 digest_type: DigestType,
95 digest: Vec<u8>,
96 ) -> Self {
97 Self {
98 key_tag,
99 algorithm,
100 digest_type,
101 digest,
102 }
103 }
104
105 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
106 ///
107 /// ```text
108 /// 5.1.1. The Key Tag Field
109 ///
110 /// The Key Tag field lists the key tag of the DNSKEY RR referred to by
111 /// the DS record, in network byte order.
112 ///
113 /// The Key Tag used by the DS RR is identical to the Key Tag used by
114 /// RRSIG RRs. Appendix B describes how to compute a Key Tag.
115 /// ```
116 pub fn key_tag(&self) -> u16 {
117 self.key_tag
118 }
119
120 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
121 ///
122 /// ```text
123 /// 5.1.2. The Algorithm Field
124 ///
125 /// The Algorithm field lists the algorithm number of the DNSKEY RR
126 /// referred to by the DS record.
127 ///
128 /// The algorithm number used by the DS RR is identical to the algorithm
129 /// number used by RRSIG and DNSKEY RRs. Appendix A.1 lists the
130 /// algorithm number types.
131 /// ```
132 pub fn algorithm(&self) -> Algorithm {
133 self.algorithm
134 }
135
136 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
137 ///
138 /// ```text
139 /// 5.1.3. The Digest Type Field
140 ///
141 /// The DS RR refers to a DNSKEY RR by including a digest of that DNSKEY
142 /// RR. The Digest Type field identifies the algorithm used to construct
143 /// the digest. Appendix A.2 lists the possible digest algorithm types.
144 /// ```
145 pub fn digest_type(&self) -> DigestType {
146 self.digest_type
147 }
148
149 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
150 ///
151 /// ```text
152 /// 5.1.4. The Digest Field
153 ///
154 /// The DS record refers to a DNSKEY RR by including a digest of that
155 /// DNSKEY RR.
156 ///
157 /// The digest is calculated by concatenating the canonical form of the
158 /// fully qualified owner name of the DNSKEY RR with the DNSKEY RDATA,
159 /// and then applying the digest algorithm.
160 ///
161 /// digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA);
162 ///
163 /// "|" denotes concatenation
164 ///
165 /// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.
166 ///
167 /// The size of the digest may vary depending on the digest algorithm and
168 /// DNSKEY RR size. As of the time of this writing, the only defined
169 /// digest algorithm is SHA-1, which produces a 20 octet digest.
170 /// ```
171 pub fn digest(&self) -> &[u8] {
172 &self.digest
173 }
174
175 /// Validates that a given DNSKEY is covered by the DS record.
176 ///
177 /// # Return
178 ///
179 /// true if and only if the DNSKEY is covered by the DS record.
180 #[cfg(any(feature = "openssl", feature = "ring"))]
181 #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))]
182 pub fn covers(&self, name: &Name, key: &DNSKEY) -> ProtoResult<bool> {
183 key.to_digest(name, self.digest_type())
184 .map(|hash| hash.as_ref() == self.digest())
185 }
186
187 /// This will always return an error unless the Ring or OpenSSL features are enabled
188 #[cfg(not(any(feature = "openssl", feature = "ring")))]
189 #[cfg_attr(docsrs, doc(cfg(not(any(feature = "openssl", feature = "ring")))))]
190 pub fn covers(&self, _: &Name, _: &DNSKEY) -> ProtoResult<bool> {
191 Err("Ring or OpenSSL must be enabled for this feature".into())
192 }
193}
194
195impl BinEncodable for DS {
196 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
197 encoder.emit_u16(self.key_tag())?;
198 self.algorithm().emit(encoder)?; // always 3 for now
199 encoder.emit(self.digest_type().into())?;
200 encoder.emit_vec(self.digest())?;
201
202 Ok(())
203 }
204}
205
206impl<'r> RecordDataDecodable<'r> for DS {
207 fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<Self> {
208 let start_idx = decoder.index();
209
210 let key_tag: u16 = decoder.read_u16()?.unverified(/*key_tag is valid as any u16*/);
211 let algorithm: Algorithm = Algorithm::read(decoder)?;
212 let digest_type: DigestType =
213 DigestType::from_u8(decoder.read_u8()?.unverified(/*DigestType is verified as safe*/))?;
214
215 let bytes_read = decoder.index() - start_idx;
216 let left: usize = length
217 .map(|u| u as usize)
218 .checked_sub(bytes_read)
219 .map_err(|_| ProtoError::from("invalid rdata length in DS"))?
220 .unverified(/*used only as length safely*/);
221 let digest =
222 decoder.read_vec(left)?.unverified(/*the byte array will fail in usage if invalid*/);
223
224 Ok(Self::new(key_tag, algorithm, digest_type, digest))
225 }
226}
227
228impl RecordData for DS {
229 fn try_from_rdata(data: RData) -> Result<Self, RData> {
230 match data {
231 RData::DNSSEC(DNSSECRData::DS(csync)) => Ok(csync),
232 _ => Err(data),
233 }
234 }
235
236 fn try_borrow(data: &RData) -> Option<&Self> {
237 match data {
238 RData::DNSSEC(DNSSECRData::DS(csync)) => Some(csync),
239 _ => None,
240 }
241 }
242
243 fn record_type(&self) -> RecordType {
244 RecordType::DS
245 }
246
247 fn into_rdata(self) -> RData {
248 RData::DNSSEC(DNSSECRData::DS(self))
249 }
250}
251
252/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.3)
253///
254/// ```text
255/// 5.3. The DS RR Presentation Format
256///
257/// The presentation format of the RDATA portion is as follows:
258///
259/// The Key Tag field MUST be represented as an unsigned decimal integer.
260///
261/// The Algorithm field MUST be represented either as an unsigned decimal
262/// integer or as an algorithm mnemonic specified in Appendix A.1.
263///
264/// The Digest Type field MUST be represented as an unsigned decimal
265/// integer.
266///
267/// The Digest MUST be represented as a sequence of case-insensitive
268/// hexadecimal digits. Whitespace is allowed within the hexadecimal
269/// text.
270///
271/// 5.4. DS RR Example
272///
273/// The following example shows a DNSKEY RR and its corresponding DS RR.
274///
275/// dskey.example.com. 86400 IN DNSKEY 256 3 5 ( AQOeiiR0GOMYkDshWoSKz9Xz
276/// fwJr1AYtsmx3TGkJaNXVbfi/
277/// 2pHm822aJ5iI9BMzNXxeYCmZ
278/// DRD99WYwYqUSdjMmmAphXdvx
279/// egXd/M5+X7OrzKBaMbCVdFLU
280/// Uh6DhweJBjEVv5f2wwjM9Xzc
281/// nOf+EPbtG9DMBmADjFDc2w/r
282/// ljwvFw==
283/// ) ; key id = 60485
284///
285/// dskey.example.com. 86400 IN DS 60485 5 1 ( 2BB183AF5F22588179A53B0A
286/// 98631FAD1A292118 )
287///
288/// The first four text fields specify the name, TTL, Class, and RR type
289/// (DS). Value 60485 is the key tag for the corresponding
290/// "dskey.example.com." DNSKEY RR, and value 5 denotes the algorithm
291/// used by this "dskey.example.com." DNSKEY RR. The value 1 is the
292/// algorithm used to construct the digest, and the rest of the RDATA
293/// text is the digest in hexadecimal.
294/// ```
295impl Display for DS {
296 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
297 write!(
298 f,
299 "{tag} {alg} {ty} {digest}",
300 tag = self.key_tag,
301 alg = u8::from(self.algorithm),
302 ty = u8::from(self.digest_type),
303 digest = data_encoding::HEXUPPER_PERMISSIVE.encode(&self.digest)
304 )
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 #![allow(clippy::dbg_macro, clippy::print_stdout)]
311
312 use super::*;
313
314 #[test]
315 fn test() {
316 let rdata = DS::new(
317 0xF00F,
318 Algorithm::RSASHA256,
319 DigestType::SHA256,
320 vec![5, 6, 7, 8],
321 );
322
323 let mut bytes = Vec::new();
324 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
325 assert!(rdata.emit(&mut encoder).is_ok());
326 let bytes = encoder.into_bytes();
327
328 println!("bytes: {bytes:?}");
329
330 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
331 let restrict = Restrict::new(bytes.len() as u16);
332 let read_rdata = DS::read_data(&mut decoder, restrict).expect("Decoding error");
333 assert_eq!(rdata, read_rdata);
334 }
335
336 #[test]
337 #[cfg(any(feature = "openssl", feature = "ring"))]
338 pub(crate) fn test_covers() {
339 use crate::rr::dnssec::rdata::DNSKEY;
340
341 let name = Name::parse("www.example.com.", None).unwrap();
342
343 let dnskey_rdata = DNSKEY::new(true, true, false, Algorithm::RSASHA256, vec![1, 2, 3, 4]);
344 let ds_rdata = DS::new(
345 0,
346 Algorithm::RSASHA256,
347 DigestType::SHA256,
348 dnskey_rdata
349 .to_digest(&name, DigestType::SHA256)
350 .unwrap()
351 .as_ref()
352 .to_owned(),
353 );
354
355 assert!(ds_rdata.covers(&name, &dnskey_rdata).unwrap());
356 }
357}