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}