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