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/// [RFC 1035](https://tools.ietf.org/html/rfc1035), DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987
326///
327/// ```text
328/// RFC 2535                DNS Security Extensions               March 1999
329///
330/// 4.1.6 Key Tag Field
331///
332///  The "key Tag" is a two octet quantity that is used to efficiently
333///  select between multiple keys which may be applicable and thus check
334///  that a public key about to be used for the computationally expensive
335///  effort to check the signature is possibly valid.  For algorithm 1
336///  (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two
337///  octets of the public key modulus needed to decode the signature
338///  field.  That is to say, the most significant 16 of the least
339///  significant 24 bits of the modulus in network (big endian) order. For
340///  all other algorithms, including private algorithms, it is calculated
341///  as a simple checksum of the KEY RR as described in Appendix C.
342///
343/// Appendix C: Key Tag Calculation
344///
345///  The key tag field in the SIG RR is just a means of more efficiently
346///  selecting the correct KEY RR to use when there is more than one KEY
347///  RR candidate available, for example, in verifying a signature.  It is
348///  possible for more than one candidate key to have the same tag, in
349///  which case each must be tried until one works or all fail.  The
350///  following reference implementation of how to calculate the Key Tag,
351///  for all algorithms other than algorithm 1, is in ANSI C.  It is coded
352///  for clarity, not efficiency.  (See section 4.1.6 for how to determine
353///  the Key Tag of an algorithm 1 key.)
354///
355///  /* assumes int is at least 16 bits
356///     first byte of the key tag is the most significant byte of return
357///     value
358///     second byte of the key tag is the least significant byte of
359///     return value
360///     */
361///
362///  int keytag (
363///
364///          unsigned char key[],  /* the RDATA part of the KEY RR */
365///          unsigned int keysize, /* the RDLENGTH */
366///          )
367///  {
368///  long int    ac;    /* assumed to be 32 bits or larger */
369///
370///  for ( ac = 0, i = 0; i < keysize; ++i )
371///      ac += (i&1) ? key[i] : key[i]<<8;
372///  ac += (ac>>16) & 0xFFFF;
373///  return ac & 0xFFFF;
374///  }
375/// ```
376fn key_tag(public_key: &[u8]) -> u16 {
377    let mut ac = 0;
378
379    for (i, k) in public_key.iter().enumerate() {
380        ac += if i & 0x0001 == 0x0001 {
381            *k as usize
382        } else {
383            (*k as usize) << 8
384        };
385    }
386
387    ac += (ac >> 16) & 0xFFFF;
388    (ac & 0xFFFF) as u16 // this is unnecessary, no?
389}
390
391#[cfg(test)]
392mod tests {
393    #![allow(clippy::dbg_macro, clippy::print_stdout)]
394
395    #[cfg(feature = "std")]
396    use std::println;
397
398    use super::*;
399    use crate::dnssec::{PublicKeyBuf, SigningKey, crypto::EcdsaSigningKey, rdata::DNSKEY};
400
401    #[test]
402    fn test() {
403        let rdata = DS::new(
404            0xF00F,
405            Algorithm::RSASHA256,
406            DigestType::SHA256,
407            vec![5, 6, 7, 8],
408        );
409
410        let mut bytes = Vec::new();
411        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
412        assert!(rdata.emit(&mut encoder).is_ok());
413        let bytes = encoder.into_bytes();
414
415        #[cfg(feature = "std")]
416        println!("bytes: {bytes:?}");
417
418        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
419        let restrict = Restrict::new(bytes.len() as u16);
420        let read_rdata = DS::read_data(&mut decoder, restrict).expect("Decoding error");
421        assert_eq!(rdata, read_rdata);
422    }
423
424    #[test]
425    fn test_covers() {
426        let algorithm = Algorithm::ECDSAP256SHA256;
427        let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
428        let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
429
430        let dnskey_rdata = DNSKEY::new(
431            true,
432            true,
433            false,
434            PublicKeyBuf::new(
435                signing_key
436                    .to_public_key()
437                    .unwrap()
438                    .public_bytes()
439                    .to_owned(),
440                algorithm,
441            ),
442        );
443
444        let name = Name::parse("www.example.com.", None).unwrap();
445        let ds_rdata = DS::new(
446            0,
447            algorithm,
448            DigestType::SHA256,
449            dnskey_rdata
450                .to_digest(&name, DigestType::SHA256)
451                .unwrap()
452                .as_ref()
453                .to_owned(),
454        );
455
456        assert!(ds_rdata.covers(&name, &dnskey_rdata).unwrap());
457    }
458
459    #[test]
460    fn test_covers_fails_with_non_zone_key() {
461        let algorithm = Algorithm::ECDSAP256SHA256;
462        let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
463        let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
464
465        let dnskey_rdata = DNSKEY::new(
466            false,
467            true,
468            false,
469            PublicKeyBuf::new(
470                signing_key
471                    .to_public_key()
472                    .unwrap()
473                    .public_bytes()
474                    .to_owned(),
475                algorithm,
476            ),
477        );
478
479        let name = Name::parse("www.example.com.", None).unwrap();
480        let ds_rdata = DS::new(
481            0,
482            algorithm,
483            DigestType::SHA256,
484            dnskey_rdata
485                .to_digest(&name, DigestType::SHA256)
486                .unwrap()
487                .as_ref()
488                .to_owned(),
489        );
490
491        assert!(!ds_rdata.covers(&name, &dnskey_rdata).unwrap());
492    }
493
494    #[test]
495    fn test_covers_uppercase() {
496        let algorithm = Algorithm::ECDSAP256SHA256;
497        let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
498        let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
499
500        let dnskey_rdata = DNSKEY::new(
501            true,
502            true,
503            false,
504            PublicKeyBuf::new(
505                signing_key
506                    .to_public_key()
507                    .unwrap()
508                    .public_bytes()
509                    .to_owned(),
510                algorithm,
511            ),
512        );
513
514        let name = Name::parse("www.example.com.", None).unwrap();
515        let ds_rdata = DS::new(
516            0,
517            algorithm,
518            DigestType::SHA256,
519            dnskey_rdata
520                .to_digest(&name, DigestType::SHA256)
521                .unwrap()
522                .as_ref()
523                .to_owned(),
524        );
525
526        let uppercase_name = Name::from_ascii("WWW.EXAMPLE.COM.").unwrap();
527        assert!(ds_rdata.covers(&uppercase_name, &dnskey_rdata).unwrap());
528    }
529}