Skip to main content

hickory_proto/dnssec/
tbs.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//! hash functions for DNSSEC operations
9
10use alloc::{borrow::ToOwned, vec::Vec};
11
12use super::rdata::sig::SigInput;
13use crate::{
14    error::{ProtoError, ProtoResult},
15    rr::{DNSClass, Name, Record},
16    serialize::binary::{BinEncodable, BinEncoder, NameEncoding},
17};
18
19/// Data To Be Signed.
20pub struct TBS(Vec<u8>);
21
22impl TBS {
23    /// Returns the to-be-signed serialization of the given record set using the information
24    /// provided from the SIG record.
25    ///
26    /// # Arguments
27    ///
28    /// * `name` - labels of the record to sign
29    /// * `dns_class` - DNSClass of the RRSet, i.e. IN
30    /// * `input` - `SigInput` data used to create the signature
31    /// * `records` - RRSet records to sign with the information in the `rrsig`
32    ///
33    /// # Return
34    ///
35    /// binary hash of the RRSet with the information from the RRSIG record
36    pub fn from_input<'a>(
37        name: &Name,
38        dns_class: DNSClass,
39        input: &SigInput,
40        records: impl Iterator<Item = &'a Record>,
41    ) -> ProtoResult<Self> {
42        Self::new(name, dns_class, input, records)
43    }
44
45    /// Returns the to-be-signed serialization of the given record set.
46    ///
47    /// # Arguments
48    ///
49    /// * `name` - RRset record name
50    /// * `dns_class` - DNSClass, i.e. IN, of the records
51    /// * `input` - the input data used to create the signature
52    /// * `records` - RRSet to hash
53    ///
54    /// # Returns
55    ///
56    /// the binary hash of the specified RRSet and associated information
57    #[allow(clippy::too_many_arguments)]
58    fn new<'a>(
59        name: &Name,
60        dns_class: DNSClass,
61        input: &SigInput,
62        records: impl Iterator<Item = &'a Record>,
63    ) -> ProtoResult<Self> {
64        // TODO: change this to a BTreeSet so that it's preordered, no sort necessary
65        let mut rrset = Vec::new();
66
67        // collect only the records for this rrset
68        for record in records {
69            if dns_class == record.dns_class
70                && input.type_covered == record.record_type()
71                && name == &record.name
72            {
73                rrset.push(record);
74            }
75        }
76
77        // put records in canonical order
78        rrset.sort();
79
80        let name = determine_name(name, input.num_labels)?;
81
82        // TODO: rather than buffering here, use the Signer/Verifier? might mean fewer allocations...
83        let mut buf = Vec::new();
84        let mut encoder = BinEncoder::new(&mut buf);
85        // Encode records using DNSSEC canonical form. This affects how names inside RDATA are
86        // encoded.
87        encoder.set_canonical_form(true);
88        // Disable name compression. Encoding of other fields may switch to use lowercase names
89        // as well.
90        encoder.set_name_encoding(NameEncoding::Uncompressed);
91
92        //          signed_data = RRSIG_RDATA | RR(1) | RR(2)...  where
93        //
94        //             "|" denotes concatenation
95        //
96        //             RRSIG_RDATA is the wire format of the RRSIG RDATA fields
97        //                with the Signature field excluded and the Signer's Name
98        //                in canonical form.
99        input.emit(&mut encoder)?;
100
101        // construct the rrset signing data
102        for record in rrset {
103            //             RR(i) = name | type | class | OrigTTL | RDATA length | RDATA
104            //
105            //                name is calculated according to the function in the RFC 4035
106            {
107                let mut encoder_name =
108                    encoder.with_name_encoding(NameEncoding::UncompressedLowercase);
109                name.emit(&mut encoder_name)?;
110            }
111            //
112            //                type is the RRset type and all RRs in the class
113            input.type_covered.emit(&mut encoder)?;
114            //
115            //                class is the RRset's class
116            dns_class.emit(&mut encoder)?;
117            //
118            //                OrigTTL is the value from the RRSIG Original TTL field
119            encoder.emit_u32(input.original_ttl)?;
120            //
121            //                RDATA length
122            let rdata_length_place = encoder.place::<u16>()?;
123            //
124            //                All names in the RDATA field are in canonical form (set above)
125            record.data.emit(&mut encoder)?;
126
127            let length = u16::try_from(encoder.len_since_place(&rdata_length_place))
128                .map_err(|_| ProtoError::from("RDATA length exceeds u16::MAX"))?;
129            rdata_length_place.replace(&mut encoder, length)?;
130        }
131
132        Ok(Self(buf))
133    }
134}
135
136impl<'a> From<&'a [u8]> for TBS {
137    fn from(slice: &'a [u8]) -> Self {
138        Self(slice.to_owned())
139    }
140}
141
142impl AsRef<[u8]> for TBS {
143    fn as_ref(&self) -> &[u8] {
144        self.0.as_ref()
145    }
146}
147
148/// [RFC 4035](https://tools.ietf.org/html/rfc4035), DNSSEC Protocol Modifications, March 2005
149///
150/// ```text
151///
152/// 5.3.2.  Reconstructing the Signed Data
153///             ...
154///             To calculate the name:
155///                let rrsig_labels = the value of the RRSIG Labels field
156///
157///                let fqdn = RRset's fully qualified domain name in
158///                                canonical form
159///
160///                let fqdn_labels = Label count of the fqdn above.
161///
162///                if rrsig_labels = fqdn_labels,
163///                    name = fqdn
164///
165///                if rrsig_labels < fqdn_labels,
166///                   name = "*." | the rightmost rrsig_label labels of the
167///                                 fqdn
168///
169///                if rrsig_labels > fqdn_labels
170///                   the RRSIG RR did not pass the necessary validation
171///                   checks and MUST NOT be used to authenticate this
172///                   RRset.
173///
174///    The canonical forms for names and RRsets are defined in [RFC4034].
175/// ```
176fn determine_name(name: &Name, num_labels: u8) -> Result<Name, ProtoError> {
177    //             To calculate the name:
178    //                let rrsig_labels = the value of the RRSIG Labels field
179    //
180    //                let fqdn = RRset's fully qualified domain name in
181    //                                canonical form
182    //
183    //                let fqdn_labels = Label count of the fqdn above.
184    let fqdn_labels = name.num_labels();
185    //                if rrsig_labels = fqdn_labels,
186    //                    name = fqdn
187
188    if fqdn_labels == num_labels {
189        return Ok(name.clone());
190    }
191    //                if rrsig_labels < fqdn_labels,
192    //                   name = "*." | the rightmost rrsig_label labels of the
193    //                                 fqdn
194    if num_labels < fqdn_labels {
195        let mut star_name: Name = Name::from_labels(vec![b"*" as &[u8]]).unwrap();
196        let rightmost = name.trim_to(num_labels as usize);
197        if !rightmost.is_root() {
198            star_name = star_name.append_name(&rightmost)?;
199            return Ok(star_name);
200        }
201        return Ok(star_name);
202    }
203    //
204    //                if rrsig_labels > fqdn_labels
205    //                   the RRSIG RR did not pass the necessary validation
206    //                   checks and MUST NOT be used to authenticate this
207    //                   RRset.
208
209    Err(format!("could not determine name from {name}").into())
210}