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}