hickory_proto/dnssec/rdata/
cds.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//! CDS type and related implementations
9
10use alloc::vec::Vec;
11use core::fmt;
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16use crate::{
17    ProtoError,
18    dnssec::{Algorithm, DigestType},
19    error::ProtoResult,
20    rr::{RData, RecordData, RecordDataDecodable, RecordType},
21    serialize::binary::{BinDecoder, BinEncodable, BinEncoder, Restrict, RestrictedMath},
22};
23
24use super::DNSSECRData;
25
26/// Child DS. See RFC 8078.
27#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
28#[derive(Debug, PartialEq, Eq, Hash, Clone)]
29pub struct CDS {
30    key_tag: u16,
31    /// The algorithm of the desired DS record if requesting an update, or `None` if requesting
32    /// deletion.
33    algorithm: Option<Algorithm>,
34    digest_type: DigestType,
35    digest: Vec<u8>,
36}
37
38impl CDS {
39    /// Constructs a new CDS RData
40    ///
41    /// # Arguments
42    ///
43    /// * `key_tag` - the key tag associated to the DNSKEY
44    /// * `algorithm` - algorithm as specified in the DNSKEY, or None to request DS RRset deletion
45    /// * `digest_type` - hash algorithm used to validate the DNSKEY
46    /// * `digest` - hash of the DNSKEY
47    ///
48    /// # Returns
49    ///
50    /// the CDS RDATA for use in a Resource Record
51    pub fn new(
52        key_tag: u16,
53        algorithm: Option<Algorithm>,
54        digest_type: DigestType,
55        digest: Vec<u8>,
56    ) -> Self {
57        Self {
58            key_tag,
59            algorithm,
60            digest_type,
61            digest,
62        }
63    }
64
65    /// Returns the Key Tag field
66    pub fn key_tag(&self) -> u16 {
67        self.key_tag
68    }
69
70    /// Returns the Algorithm field. This is `None` if deletion is requested, or the key's algorithm
71    /// if an update is requested.
72    pub fn algorithm(&self) -> Option<Algorithm> {
73        self.algorithm
74    }
75
76    /// Returns whether this record is requesting deletion of the DS RRset.
77    pub fn is_delete(&self) -> bool {
78        self.algorithm.is_none()
79    }
80
81    /// Returns the Digest Type field.
82    pub fn digest_type(&self) -> DigestType {
83        self.digest_type
84    }
85
86    /// Returns the Digest field.
87    pub fn digest(&self) -> &[u8] {
88        &self.digest
89    }
90}
91
92impl From<CDS> for RData {
93    fn from(value: CDS) -> Self {
94        Self::DNSSEC(DNSSECRData::CDS(value))
95    }
96}
97
98impl BinEncodable for CDS {
99    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
100        encoder.emit_u16(self.key_tag())?;
101        match self.algorithm() {
102            Some(algorithm) => algorithm.emit(encoder)?,
103            None => encoder.emit_u8(0)?,
104        }
105        encoder.emit(self.digest_type().into())?;
106        encoder.emit_vec(self.digest())?;
107
108        Ok(())
109    }
110}
111
112impl<'r> RecordDataDecodable<'r> for CDS {
113    fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<Self> {
114        let start_idx = decoder.index();
115
116        let key_tag = decoder.read_u16()?.unverified(/* any u16 is a valid key_tag */);
117
118        let algorithm_value = decoder.read_u8()?.unverified(/* no further validation required */);
119        let algorithm = match algorithm_value {
120            0 => None,
121            _ => Some(Algorithm::from_u8(algorithm_value)),
122        };
123
124        let digest_type =
125            DigestType::from(decoder.read_u8()?.unverified(/* DigestType is verified as safe */));
126
127        let bytes_read = decoder.index() - start_idx;
128        let left = length
129            .map(|u| u as usize)
130            .checked_sub(bytes_read)
131            .map_err(|_| ProtoError::from("invalid rdata length in CDS"))?
132            .unverified(/* used only as length safely */);
133        let digest =
134            decoder.read_vec(left)?.unverified(/* this is only compared with other digests */);
135
136        Ok(Self::new(key_tag, algorithm, digest_type, digest))
137    }
138}
139
140impl RecordData for CDS {
141    fn try_from_rdata(data: RData) -> Result<Self, RData> {
142        match data {
143            RData::DNSSEC(DNSSECRData::CDS(cds)) => Ok(cds),
144            _ => Err(data),
145        }
146    }
147
148    fn try_borrow(data: &RData) -> Option<&Self> {
149        match data {
150            RData::DNSSEC(DNSSECRData::CDS(cds)) => Some(cds),
151            _ => None,
152        }
153    }
154
155    fn record_type(&self) -> RecordType {
156        RecordType::CDS
157    }
158
159    fn into_rdata(self) -> RData {
160        RData::DNSSEC(DNSSECRData::CDS(self))
161    }
162}
163
164impl fmt::Display for CDS {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
166        write!(
167            f,
168            "{tag} {alg} {ty} {digest}",
169            tag = self.key_tag,
170            alg = self.algorithm.map(u8::from).unwrap_or(0),
171            ty = u8::from(self.digest_type),
172            digest = data_encoding::HEXUPPER_PERMISSIVE.encode(&self.digest)
173        )
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    #![allow(clippy::dbg_macro, clippy::print_stdout)]
180
181    use alloc::vec::Vec;
182    use std::println;
183
184    use crate::{
185        dnssec::{Algorithm, DigestType},
186        rr::RecordDataDecodable,
187        serialize::binary::{BinDecoder, BinEncodable, BinEncoder, Restrict},
188    };
189
190    use super::CDS;
191
192    #[test]
193    fn test() {
194        let rdata = CDS::new(
195            0xF00F,
196            Some(Algorithm::RSASHA256),
197            DigestType::SHA256,
198            vec![5, 6, 7, 8],
199        );
200
201        let mut bytes = Vec::new();
202        let mut encoder = BinEncoder::new(&mut bytes);
203        rdata.emit(&mut encoder).expect("error encoding");
204        let bytes = encoder.into_bytes();
205
206        println!("bytes: {bytes:?}");
207
208        let mut decoder = BinDecoder::new(bytes);
209        let read_rdata = CDS::read_data(&mut decoder, Restrict::new(bytes.len() as u16))
210            .expect("error decoding");
211        assert_eq!(rdata, read_rdata);
212    }
213
214    #[test]
215    fn test_delete() {
216        let rdata = CDS::new(0, None, DigestType::Unknown(0), vec![0]);
217
218        let mut bytes = Vec::new();
219        let mut encoder = BinEncoder::new(&mut bytes);
220        rdata.emit(&mut encoder).expect("error encoding");
221        let bytes = encoder.into_bytes();
222
223        println!("bytes: {bytes:?}");
224
225        let mut decoder = BinDecoder::new(bytes);
226        let read_rdata = CDS::read_data(&mut decoder, Restrict::new(bytes.len() as u16))
227            .expect("error decoding");
228        assert_eq!(rdata, read_rdata);
229    }
230}