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