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}