hickory_proto/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 alloc::borrow::ToOwned;
11use alloc::vec::Vec;
12use core::fmt::{self, Display, Formatter};
13
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17use super::DNSSECRData;
18use crate::{
19 dnssec::{Algorithm, DigestType, DnsSecError, PublicKey, rdata::DNSKEY},
20 error::{ProtoError, ProtoResult},
21 rr::{Name, RData, RecordData, RecordDataDecodable, RecordType},
22 serialize::binary::{
23 BinDecodable, BinDecoder, BinEncodable, BinEncoder, Restrict, RestrictedMath,
24 },
25};
26
27/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5)
28///
29/// ```text
30/// 5.1. DS RDATA Wire Format
31///
32/// The RDATA for a DS RR consists of a 2 octet Key Tag field, a 1 octet
33/// Algorithm field, a 1 octet Digest Type field, and a Digest field.
34///
35/// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
36/// 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
37/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
38/// | Key Tag | Algorithm | Digest Type |
39/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40/// / /
41/// / Digest /
42/// / /
43/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
44///
45/// 5.2. Processing of DS RRs When Validating Responses
46///
47/// The DS RR links the authentication chain across zone boundaries, so
48/// the DS RR requires extra care in processing. The DNSKEY RR referred
49/// to in the DS RR MUST be a DNSSEC zone key. The DNSKEY RR Flags MUST
50/// have Flags bit 7 set. If the DNSKEY flags do not indicate a DNSSEC
51/// zone key, the DS RR (and the DNSKEY RR it references) MUST NOT be
52/// used in the validation process.
53///
54/// 5.3. The DS RR Presentation Format
55///
56/// The presentation format of the RDATA portion is as follows:
57///
58/// The Key Tag field MUST be represented as an unsigned decimal integer.
59///
60/// The Algorithm field MUST be represented either as an unsigned decimal
61/// integer or as an algorithm mnemonic specified in Appendix A.1.
62///
63/// The Digest Type field MUST be represented as an unsigned decimal
64/// integer.
65///
66/// The Digest MUST be represented as a sequence of case-insensitive
67/// hexadecimal digits. Whitespace is allowed within the hexadecimal
68/// text.
69/// ```
70#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
71#[derive(Debug, PartialEq, Eq, Hash, Clone)]
72pub struct DS {
73 key_tag: u16,
74 algorithm: Algorithm,
75 digest_type: DigestType,
76 digest: Vec<u8>,
77}
78
79impl DS {
80 /// Creates a [`DS`] record for the given `public_key` and `name`.
81 ///
82 /// # Arguments
83 ///
84 /// * `public_key` - the public key to create the DS record for
85 /// * `name` - name of the DNSKEY record covered by the new DS record
86 /// * `algorithm` - the algorithm of the DNSKEY
87 /// * `digest_type` - the digest_type used to
88 pub fn from_key(
89 public_key: &dyn PublicKey,
90 name: &Name,
91 digest_type: DigestType,
92 ) -> Result<Self, DnsSecError> {
93 let tag = key_tag(public_key.public_bytes());
94 let dnskey = DNSKEY::from_key(public_key);
95 Ok(Self::new(
96 tag,
97 public_key.algorithm(),
98 digest_type,
99 dnskey.to_digest(name, digest_type)?.as_ref().to_owned(),
100 ))
101 }
102
103 /// Constructs a new DS RData
104 ///
105 /// # Arguments
106 ///
107 /// * `key_tag` - the key_tag associated to the DNSKEY
108 /// * `algorithm` - algorithm as specified in the DNSKEY
109 /// * `digest_type` - hash algorithm used to validate the DNSKEY
110 /// * `digest` - hash of the DNSKEY
111 ///
112 /// # Returns
113 ///
114 /// the DS RDATA for use in a Resource Record
115 pub fn new(
116 key_tag: u16,
117 algorithm: Algorithm,
118 digest_type: DigestType,
119 digest: Vec<u8>,
120 ) -> Self {
121 Self {
122 key_tag,
123 algorithm,
124 digest_type,
125 digest,
126 }
127 }
128
129 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
130 ///
131 /// ```text
132 /// 5.1.1. The Key Tag Field
133 ///
134 /// The Key Tag field lists the key tag of the DNSKEY RR referred to by
135 /// the DS record, in network byte order.
136 ///
137 /// The Key Tag used by the DS RR is identical to the Key Tag used by
138 /// RRSIG RRs. Appendix B describes how to compute a Key Tag.
139 /// ```
140 pub fn key_tag(&self) -> u16 {
141 self.key_tag
142 }
143
144 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
145 ///
146 /// ```text
147 /// 5.1.2. The Algorithm Field
148 ///
149 /// The Algorithm field lists the algorithm number of the DNSKEY RR
150 /// referred to by the DS record.
151 ///
152 /// The algorithm number used by the DS RR is identical to the algorithm
153 /// number used by RRSIG and DNSKEY RRs. Appendix A.1 lists the
154 /// algorithm number types.
155 /// ```
156 pub fn algorithm(&self) -> Algorithm {
157 self.algorithm
158 }
159
160 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
161 ///
162 /// ```text
163 /// 5.1.3. The Digest Type Field
164 ///
165 /// The DS RR refers to a DNSKEY RR by including a digest of that DNSKEY
166 /// RR. The Digest Type field identifies the algorithm used to construct
167 /// the digest. Appendix A.2 lists the possible digest algorithm types.
168 /// ```
169 pub fn digest_type(&self) -> DigestType {
170 self.digest_type
171 }
172
173 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
174 ///
175 /// ```text
176 /// 5.1.4. The Digest Field
177 ///
178 /// The DS record refers to a DNSKEY RR by including a digest of that
179 /// DNSKEY RR.
180 ///
181 /// The digest is calculated by concatenating the canonical form of the
182 /// fully qualified owner name of the DNSKEY RR with the DNSKEY RDATA,
183 /// and then applying the digest algorithm.
184 ///
185 /// digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA);
186 ///
187 /// "|" denotes concatenation
188 ///
189 /// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.
190 ///
191 /// The size of the digest may vary depending on the digest algorithm and
192 /// DNSKEY RR size. As of the time of this writing, the only defined
193 /// digest algorithm is SHA-1, which produces a 20 octet digest.
194 /// ```
195 pub fn digest(&self) -> &[u8] {
196 &self.digest
197 }
198
199 /// Validates that a given DNSKEY is covered by the DS record.
200 ///
201 /// # Return
202 ///
203 /// true if and only if the DNSKEY is covered by the DS record.
204 pub fn covers(&self, name: &Name, key: &DNSKEY) -> ProtoResult<bool> {
205 key.to_digest(name, self.digest_type())
206 .map(|hash| key.zone_key() && hash.as_ref() == self.digest())
207 }
208}
209
210impl BinEncodable for DS {
211 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
212 encoder.emit_u16(self.key_tag())?;
213 self.algorithm().emit(encoder)?;
214 encoder.emit(self.digest_type().into())?;
215 encoder.emit_vec(self.digest())?;
216
217 Ok(())
218 }
219}
220
221impl<'r> RecordDataDecodable<'r> for DS {
222 fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<Self> {
223 let start_idx = decoder.index();
224
225 let key_tag: u16 = decoder.read_u16()?.unverified(/*key_tag is valid as any u16*/);
226 let algorithm: Algorithm = Algorithm::read(decoder)?;
227 let digest_type =
228 DigestType::from(decoder.read_u8()?.unverified(/*DigestType is verified as safe*/));
229
230 let bytes_read = decoder.index() - start_idx;
231 let left: usize = length
232 .map(|u| u as usize)
233 .checked_sub(bytes_read)
234 .map_err(|_| ProtoError::from("invalid rdata length in DS"))?
235 .unverified(/*used only as length safely*/);
236 let digest =
237 decoder.read_vec(left)?.unverified(/*the byte array will fail in usage if invalid*/);
238
239 Ok(Self::new(key_tag, algorithm, digest_type, digest))
240 }
241}
242
243impl RecordData for DS {
244 fn try_from_rdata(data: RData) -> Result<Self, RData> {
245 match data {
246 RData::DNSSEC(DNSSECRData::DS(csync)) => Ok(csync),
247 _ => Err(data),
248 }
249 }
250
251 fn try_borrow(data: &RData) -> Option<&Self> {
252 match data {
253 RData::DNSSEC(DNSSECRData::DS(csync)) => Some(csync),
254 _ => None,
255 }
256 }
257
258 fn record_type(&self) -> RecordType {
259 RecordType::DS
260 }
261
262 fn into_rdata(self) -> RData {
263 RData::DNSSEC(DNSSECRData::DS(self))
264 }
265}
266
267/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.3)
268///
269/// ```text
270/// 5.3. The DS RR Presentation Format
271///
272/// The presentation format of the RDATA portion is as follows:
273///
274/// The Key Tag field MUST be represented as an unsigned decimal integer.
275///
276/// The Algorithm field MUST be represented either as an unsigned decimal
277/// integer or as an algorithm mnemonic specified in Appendix A.1.
278///
279/// The Digest Type field MUST be represented as an unsigned decimal
280/// integer.
281///
282/// The Digest MUST be represented as a sequence of case-insensitive
283/// hexadecimal digits. Whitespace is allowed within the hexadecimal
284/// text.
285///
286/// 5.4. DS RR Example
287///
288/// The following example shows a DNSKEY RR and its corresponding DS RR.
289///
290/// dskey.example.com. 86400 IN DNSKEY 256 3 5 ( AQOeiiR0GOMYkDshWoSKz9Xz
291/// fwJr1AYtsmx3TGkJaNXVbfi/
292/// 2pHm822aJ5iI9BMzNXxeYCmZ
293/// DRD99WYwYqUSdjMmmAphXdvx
294/// egXd/M5+X7OrzKBaMbCVdFLU
295/// Uh6DhweJBjEVv5f2wwjM9Xzc
296/// nOf+EPbtG9DMBmADjFDc2w/r
297/// ljwvFw==
298/// ) ; key id = 60485
299///
300/// dskey.example.com. 86400 IN DS 60485 5 1 ( 2BB183AF5F22588179A53B0A
301/// 98631FAD1A292118 )
302///
303/// The first four text fields specify the name, TTL, Class, and RR type
304/// (DS). Value 60485 is the key tag for the corresponding
305/// "dskey.example.com." DNSKEY RR, and value 5 denotes the algorithm
306/// used by this "dskey.example.com." DNSKEY RR. The value 1 is the
307/// algorithm used to construct the digest, and the rest of the RDATA
308/// text is the digest in hexadecimal.
309/// ```
310impl Display for DS {
311 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
312 write!(
313 f,
314 "{tag} {alg} {ty} {digest}",
315 tag = self.key_tag,
316 alg = u8::from(self.algorithm),
317 ty = u8::from(self.digest_type),
318 digest = data_encoding::HEXUPPER_PERMISSIVE.encode(&self.digest)
319 )
320 }
321}
322
323/// The key tag is calculated as a hash to more quickly lookup a DNSKEY.
324///
325/// ```text
326/// RFC 2535 DNS Security Extensions March 1999
327///
328/// 4.1.6 Key Tag Field
329///
330/// The "key Tag" is a two octet quantity that is used to efficiently
331/// select between multiple keys which may be applicable and thus check
332/// that a public key about to be used for the computationally expensive
333/// effort to check the signature is possibly valid. For algorithm 1
334/// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two
335/// octets of the public key modulus needed to decode the signature
336/// field. That is to say, the most significant 16 of the least
337/// significant 24 bits of the modulus in network (big endian) order. For
338/// all other algorithms, including private algorithms, it is calculated
339/// as a simple checksum of the KEY RR as described in Appendix C.
340///
341/// Appendix C: Key Tag Calculation
342///
343/// The key tag field in the SIG RR is just a means of more efficiently
344/// selecting the correct KEY RR to use when there is more than one KEY
345/// RR candidate available, for example, in verifying a signature. It is
346/// possible for more than one candidate key to have the same tag, in
347/// which case each must be tried until one works or all fail. The
348/// following reference implementation of how to calculate the Key Tag,
349/// for all algorithms other than algorithm 1, is in ANSI C. It is coded
350/// for clarity, not efficiency. (See section 4.1.6 for how to determine
351/// the Key Tag of an algorithm 1 key.)
352///
353/// /* assumes int is at least 16 bits
354/// first byte of the key tag is the most significant byte of return
355/// value
356/// second byte of the key tag is the least significant byte of
357/// return value
358/// */
359///
360/// int keytag (
361///
362/// unsigned char key[], /* the RDATA part of the KEY RR */
363/// unsigned int keysize, /* the RDLENGTH */
364/// )
365/// {
366/// long int ac; /* assumed to be 32 bits or larger */
367///
368/// for ( ac = 0, i = 0; i < keysize; ++i )
369/// ac += (i&1) ? key[i] : key[i]<<8;
370/// ac += (ac>>16) & 0xFFFF;
371/// return ac & 0xFFFF;
372/// }
373/// ```
374fn key_tag(public_key: &[u8]) -> u16 {
375 let mut ac = 0;
376
377 for (i, k) in public_key.iter().enumerate() {
378 ac += if i & 0x0001 == 0x0001 {
379 *k as usize
380 } else {
381 (*k as usize) << 8
382 };
383 }
384
385 ac += (ac >> 16) & 0xFFFF;
386 (ac & 0xFFFF) as u16 // this is unnecessary, no?
387}
388
389#[cfg(test)]
390mod tests {
391 #![allow(clippy::dbg_macro, clippy::print_stdout)]
392
393 #[cfg(feature = "std")]
394 use std::println;
395
396 use super::*;
397 use crate::dnssec::{PublicKeyBuf, SigningKey, crypto::EcdsaSigningKey, rdata::DNSKEY};
398
399 #[test]
400 fn test() {
401 let rdata = DS::new(
402 0xF00F,
403 Algorithm::RSASHA256,
404 DigestType::SHA256,
405 vec![5, 6, 7, 8],
406 );
407
408 let mut bytes = Vec::new();
409 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
410 assert!(rdata.emit(&mut encoder).is_ok());
411 let bytes = encoder.into_bytes();
412
413 #[cfg(feature = "std")]
414 println!("bytes: {bytes:?}");
415
416 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
417 let restrict = Restrict::new(bytes.len() as u16);
418 let read_rdata = DS::read_data(&mut decoder, restrict).expect("Decoding error");
419 assert_eq!(rdata, read_rdata);
420 }
421
422 #[test]
423 fn test_covers() {
424 let algorithm = Algorithm::ECDSAP256SHA256;
425 let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
426 let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
427
428 let dnskey_rdata = DNSKEY::new(
429 true,
430 true,
431 false,
432 PublicKeyBuf::new(
433 signing_key
434 .to_public_key()
435 .unwrap()
436 .public_bytes()
437 .to_owned(),
438 algorithm,
439 ),
440 );
441
442 let name = Name::parse("www.example.com.", None).unwrap();
443 let ds_rdata = DS::new(
444 0,
445 algorithm,
446 DigestType::SHA256,
447 dnskey_rdata
448 .to_digest(&name, DigestType::SHA256)
449 .unwrap()
450 .as_ref()
451 .to_owned(),
452 );
453
454 assert!(ds_rdata.covers(&name, &dnskey_rdata).unwrap());
455 }
456
457 #[test]
458 fn test_covers_fails_with_non_zone_key() {
459 let algorithm = Algorithm::ECDSAP256SHA256;
460 let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
461 let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
462
463 let dnskey_rdata = DNSKEY::new(
464 false,
465 true,
466 false,
467 PublicKeyBuf::new(
468 signing_key
469 .to_public_key()
470 .unwrap()
471 .public_bytes()
472 .to_owned(),
473 algorithm,
474 ),
475 );
476
477 let name = Name::parse("www.example.com.", None).unwrap();
478 let ds_rdata = DS::new(
479 0,
480 algorithm,
481 DigestType::SHA256,
482 dnskey_rdata
483 .to_digest(&name, DigestType::SHA256)
484 .unwrap()
485 .as_ref()
486 .to_owned(),
487 );
488
489 assert!(!ds_rdata.covers(&name, &dnskey_rdata).unwrap());
490 }
491
492 #[test]
493 fn test_covers_uppercase() {
494 let algorithm = Algorithm::ECDSAP256SHA256;
495 let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
496 let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
497
498 let dnskey_rdata = DNSKEY::new(
499 true,
500 true,
501 false,
502 PublicKeyBuf::new(
503 signing_key
504 .to_public_key()
505 .unwrap()
506 .public_bytes()
507 .to_owned(),
508 algorithm,
509 ),
510 );
511
512 let name = Name::parse("www.example.com.", None).unwrap();
513 let ds_rdata = DS::new(
514 0,
515 algorithm,
516 DigestType::SHA256,
517 dnskey_rdata
518 .to_digest(&name, DigestType::SHA256)
519 .unwrap()
520 .as_ref()
521 .to_owned(),
522 );
523
524 let uppercase_name = Name::from_ascii("WWW.EXAMPLE.COM.").unwrap();
525 assert!(ds_rdata.covers(&uppercase_name, &dnskey_rdata).unwrap());
526 }
527}