Skip to main content

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