Skip to main content

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}