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};
11use time::OffsetDateTime;
12
13use super::Algorithm;
14use crate::{
15    error::{ProtoError, ProtoResult},
16    rr::{DNSClass, Name, Record, RecordSet, RecordType, SerialNumber},
17    serialize::binary::{BinEncodable, BinEncoder, EncodeMode},
18};
19
20use super::{
21    SigSigner,
22    rdata::{RRSIG, SIG, sig},
23};
24
25/// Data To Be Signed.
26pub struct TBS(Vec<u8>);
27
28impl TBS {
29    /// Returns the to-be-signed serialization of the given record set using the information
30    /// provided from the RRSIG record.
31    ///
32    /// # Arguments
33    ///
34    /// * `rrsig` - SIG or RRSIG record, which was produced from the RRSet
35    /// * `records` - RRSet records to sign with the information in the `rrsig`
36    ///
37    /// # Return
38    ///
39    /// binary hash of the RRSet with the information from the RRSIG record
40    pub fn from_rrsig<'a>(
41        rrsig: &Record<RRSIG>,
42        records: impl Iterator<Item = &'a Record>,
43    ) -> ProtoResult<Self> {
44        Self::from_sig(rrsig.name(), rrsig.dns_class(), rrsig.data(), records)
45    }
46
47    /// Returns the to-be-signed serialization of the given record set using the information
48    /// provided from the SIG record.
49    ///
50    /// # Arguments
51    ///
52    /// * `name` - labels of the record to sign
53    /// * `dns_class` - DNSClass of the RRSet, i.e. IN
54    /// * `sig` - SIG or RRSIG record, which was produced from the RRSet
55    /// * `records` - RRSet records to sign with the information in the `rrsig`
56    ///
57    /// # Return
58    ///
59    /// binary hash of the RRSet with the information from the RRSIG record
60    pub fn from_sig<'a>(
61        name: &Name,
62        dns_class: DNSClass,
63        sig: &SIG,
64        records: impl Iterator<Item = &'a Record>,
65    ) -> ProtoResult<Self> {
66        Self::new(
67            name,
68            dns_class,
69            sig.num_labels(),
70            sig.type_covered(),
71            sig.algorithm(),
72            sig.original_ttl(),
73            sig.sig_expiration(),
74            sig.sig_inception(),
75            sig.key_tag(),
76            sig.signer_name(),
77            records,
78        )
79    }
80
81    /// Returns the to-be-signed serialization of the given record set.
82    ///
83    /// # Arguments
84    ///
85    /// * `rr_set` - RRSet to sign
86    /// * `zone_class` - DNSClass, i.e. IN, of the records
87    /// * `inception` - the date/time when this hashed signature will become valid
88    /// * `expiration` - the date/time when this hashed signature will expire
89    /// * `signer` - the signer to use for signing the RRSet
90    ///
91    /// # Returns
92    ///
93    /// the binary hash of the specified RRSet and associated information
94    pub fn from_rrset(
95        rr_set: &RecordSet,
96        zone_class: DNSClass,
97        inception: OffsetDateTime,
98        expiration: OffsetDateTime,
99        signer: &SigSigner,
100    ) -> ProtoResult<Self> {
101        Self::new(
102            rr_set.name(),
103            zone_class,
104            rr_set.name().num_labels(),
105            rr_set.record_type(),
106            signer.key().algorithm(),
107            rr_set.ttl(),
108            SerialNumber(expiration.unix_timestamp() as u32),
109            SerialNumber(inception.unix_timestamp() as u32),
110            signer.calculate_key_tag()?,
111            signer.signer_name(),
112            rr_set.records_without_rrsigs(),
113        )
114    }
115
116    /// Returns the to-be-signed serialization of the given record set.
117    ///
118    /// # Arguments
119    ///
120    /// * `name` - RRset record name
121    /// * `dns_class` - DNSClass, i.e. IN, of the records
122    /// * `num_labels` - number of labels in the name, needed to deal with `*.example.com`
123    /// * `type_covered` - RecordType of the RRSet being hashed
124    /// * `algorithm` - The Algorithm type used for the hashing
125    /// * `original_ttl` - Original TTL is the TTL as specified in the SOA zones RRSet associated record
126    /// * `sig_expiration` - the epoch seconds of when this hashed signature will expire
127    /// * `key_inception` - the epoch seconds of when this hashed signature will be valid
128    /// * `signer_name` - label of the entity responsible for signing this hash
129    /// * `records` - RRSet to hash
130    ///
131    /// # Returns
132    ///
133    /// the binary hash of the specified RRSet and associated information
134    // FIXME: OMG, there are a ton of asserts in here...
135    #[allow(clippy::too_many_arguments)]
136    fn new<'a>(
137        name: &Name,
138        dns_class: DNSClass,
139        num_labels: u8,
140        type_covered: RecordType,
141        algorithm: Algorithm,
142        original_ttl: u32,
143        sig_expiration: SerialNumber,
144        sig_inception: SerialNumber,
145        key_tag: u16,
146        signer_name: &Name,
147        records: impl Iterator<Item = &'a Record>,
148    ) -> ProtoResult<Self> {
149        // TODO: change this to a BTreeSet so that it's preordered, no sort necessary
150        let mut rrset: Vec<&Record> = Vec::new();
151
152        // collect only the records for this rrset
153        for record in records {
154            if dns_class == record.dns_class()
155                && type_covered == record.record_type()
156                && name == record.name()
157            {
158                rrset.push(record);
159            }
160        }
161
162        // put records in canonical order
163        rrset.sort();
164
165        let name = determine_name(name, num_labels)?;
166
167        // TODO: rather than buffering here, use the Signer/Verifier? might mean fewer allocations...
168        let mut buf: Vec<u8> = Vec::new();
169
170        {
171            let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut buf);
172            encoder.set_canonical_names(true);
173
174            //          signed_data = RRSIG_RDATA | RR(1) | RR(2)...  where
175            //
176            //             "|" denotes concatenation
177            //
178            //             RRSIG_RDATA is the wire format of the RRSIG RDATA fields
179            //                with the Signature field excluded and the Signer's Name
180            //                in canonical form.
181            assert!(
182                sig::emit_pre_sig(
183                    &mut encoder,
184                    type_covered,
185                    algorithm,
186                    name.num_labels(),
187                    original_ttl,
188                    sig_expiration,
189                    sig_inception,
190                    key_tag,
191                    signer_name,
192                )
193                .is_ok()
194            );
195
196            // construct the rrset signing data
197            for record in rrset {
198                //             RR(i) = name | type | class | OrigTTL | RDATA length | RDATA
199                //
200                //                name is calculated according to the function in the RFC 4035
201                assert!(
202                    name.to_lowercase()
203                        .emit_as_canonical(&mut encoder, true)
204                        .is_ok()
205                );
206                //
207                //                type is the RRset type and all RRs in the class
208                assert!(type_covered.emit(&mut encoder).is_ok());
209                //
210                //                class is the RRset's class
211                assert!(dns_class.emit(&mut encoder).is_ok());
212                //
213                //                OrigTTL is the value from the RRSIG Original TTL field
214                assert!(encoder.emit_u32(original_ttl).is_ok());
215                //
216                //                RDATA length
217                // TODO: add support to the encoder to set a marker to go back and write the length
218                let mut rdata_buf = Vec::new();
219                {
220                    let mut rdata_encoder = BinEncoder::new(&mut rdata_buf);
221                    rdata_encoder.set_canonical_names(true);
222                    assert!(record.data().emit(&mut rdata_encoder).is_ok());
223                }
224                assert!(encoder.emit_u16(rdata_buf.len() as u16).is_ok());
225                //
226                //                All names in the RDATA field are in canonical form (set above)
227                assert!(encoder.emit_vec(&rdata_buf).is_ok());
228            }
229        }
230
231        Ok(Self(buf))
232    }
233}
234
235impl<'a> From<&'a [u8]> for TBS {
236    fn from(slice: &'a [u8]) -> Self {
237        Self(slice.to_owned())
238    }
239}
240
241impl AsRef<[u8]> for TBS {
242    fn as_ref(&self) -> &[u8] {
243        self.0.as_ref()
244    }
245}
246
247/// Returns the to-be-signed serialization of the given message.
248pub fn message_tbs<M: BinEncodable>(message: &M, pre_sig0: &SIG) -> ProtoResult<TBS> {
249    // TODO: should perform the serialization and sign block by block to reduce the max memory
250    //  usage, though at 4k max, this is probably unnecessary... For AXFR and large zones, it's
251    //  more important
252    let mut buf: Vec<u8> = Vec::with_capacity(512);
253    let mut buf2: Vec<u8> = Vec::with_capacity(512);
254
255    {
256        let mut encoder: BinEncoder<'_> = BinEncoder::with_mode(&mut buf, EncodeMode::Normal);
257        assert!(
258            sig::emit_pre_sig(
259                &mut encoder,
260                pre_sig0.type_covered(),
261                pre_sig0.algorithm(),
262                pre_sig0.num_labels(),
263                pre_sig0.original_ttl(),
264                pre_sig0.sig_expiration(),
265                pre_sig0.sig_inception(),
266                pre_sig0.key_tag(),
267                pre_sig0.signer_name(),
268            )
269            .is_ok()
270        );
271        // need a separate encoder here, as the encoding references absolute positions
272        // inside the buffer. If the buffer already contains the sig0 RDATA, offsets
273        // are wrong and the signature won't match.
274        let mut encoder2: BinEncoder<'_> = BinEncoder::with_mode(&mut buf2, EncodeMode::Signing);
275        message.emit(&mut encoder2).unwrap(); // coding error if this panics (i think?)
276    }
277
278    buf.append(&mut buf2);
279
280    Ok(TBS(buf))
281}
282
283/// [RFC 4035](https://tools.ietf.org/html/rfc4035), DNSSEC Protocol Modifications, March 2005
284///
285/// ```text
286///
287/// 5.3.2.  Reconstructing the Signed Data
288///             ...
289///             To calculate the name:
290///                let rrsig_labels = the value of the RRSIG Labels field
291///
292///                let fqdn = RRset's fully qualified domain name in
293///                                canonical form
294///
295///                let fqdn_labels = Label count of the fqdn above.
296///
297///                if rrsig_labels = fqdn_labels,
298///                    name = fqdn
299///
300///                if rrsig_labels < fqdn_labels,
301///                   name = "*." | the rightmost rrsig_label labels of the
302///                                 fqdn
303///
304///                if rrsig_labels > fqdn_labels
305///                   the RRSIG RR did not pass the necessary validation
306///                   checks and MUST NOT be used to authenticate this
307///                   RRset.
308///
309///    The canonical forms for names and RRsets are defined in [RFC4034].
310/// ```
311pub fn determine_name(name: &Name, num_labels: u8) -> Result<Name, ProtoError> {
312    //             To calculate the name:
313    //                let rrsig_labels = the value of the RRSIG Labels field
314    //
315    //                let fqdn = RRset's fully qualified domain name in
316    //                                canonical form
317    //
318    //                let fqdn_labels = Label count of the fqdn above.
319    let fqdn_labels = name.num_labels();
320    //                if rrsig_labels = fqdn_labels,
321    //                    name = fqdn
322
323    if fqdn_labels == num_labels {
324        return Ok(name.clone());
325    }
326    //                if rrsig_labels < fqdn_labels,
327    //                   name = "*." | the rightmost rrsig_label labels of the
328    //                                 fqdn
329    if num_labels < fqdn_labels {
330        let mut star_name: Name = Name::from_labels(vec![b"*" as &[u8]]).unwrap();
331        let rightmost = name.trim_to(num_labels as usize);
332        if !rightmost.is_root() {
333            star_name = star_name.append_name(&rightmost)?;
334            return Ok(star_name);
335        }
336        return Ok(star_name);
337    }
338    //
339    //                if rrsig_labels > fqdn_labels
340    //                   the RRSIG RR did not pass the necessary validation
341    //                   checks and MUST NOT be used to authenticate this
342    //                   RRset.
343
344    Err(format!("could not determine name from {name}").into())
345}