hickory_proto/dnssec/rdata/
tsig.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//! TSIG for secret key authentication of transaction
9#![allow(clippy::use_self)]
10
11use alloc::vec::Vec;
12use core::{convert::TryInto, fmt};
13
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17use super::DNSSECRData;
18use crate::{
19    dnssec::{DnsSecError, DnsSecErrorKind, ring_like::hmac},
20    error::{ProtoError, ProtoResult},
21    op::{Header, Message, Query},
22    rr::{
23        Name, Record, RecordData, RecordDataDecodable, dns_class::DNSClass, rdata::sshfp,
24        record_data::RData, record_type::RecordType,
25    },
26    serialize::binary::{
27        BinDecodable, BinDecoder, BinEncodable, BinEncoder, EncodeMode, Restrict, RestrictedMath,
28    },
29};
30
31/// [RFC 8945, Secret Key Transaction Authentication for DNS](https://tools.ietf.org/html/rfc8945#section-4.2)
32///
33/// ```text
34///   4.2.  TSIG Record Format
35///
36///   The fields of the TSIG RR are described below.  All multi-octet
37///   integers in the record are sent in network byte order (see
38///   Section 2.3.2 of [RFC1035]).
39///
40///   NAME:  The name of the key used, in domain name syntax.  The name
41///      should reflect the names of the hosts and uniquely identify the
42///      key among a set of keys these two hosts may share at any given
43///      time.  For example, if hosts A.site.example and B.example.net
44///      share a key, possibilities for the key name include
45///      <id>.A.site.example, <id>.B.example.net, and
46///      <id>.A.site.example.B.example.net.  It should be possible for more
47///      than one key to be in simultaneous use among a set of interacting
48///      hosts.  This allows for periodic key rotation as per best
49///      operational practices, as well as algorithm agility as indicated
50///      by [RFC7696].
51///
52///      The name may be used as a local index to the key involved, but it
53///      is recommended that it be globally unique.  Where a key is just
54///      shared between two hosts, its name actually need only be
55///      meaningful to them, but it is recommended that the key name be
56///      mnemonic and incorporate the names of participating agents or
57///      resources as suggested above.
58///
59///   TYPE:  This MUST be TSIG (250: Transaction SIGnature).
60///
61///   CLASS:  This MUST be ANY.
62///
63///   TTL:  This MUST be 0.
64///
65///   RDLENGTH:  (variable)
66///
67///   RDATA:  The RDATA for a TSIG RR consists of a number of fields,
68///      described below:
69///
70///                            1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
71///        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
72///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
73///       /                         Algorithm Name                        /
74///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
75///       |                                                               |
76///       |          Time Signed          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
77///       |                               |            Fudge              |
78///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79///       |          MAC Size             |                               /
80///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+             MAC               /
81///       /                                                               /
82///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
83///       |          Original ID          |            Error              |
84///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
85///       |          Other Len            |                               /
86///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+           Other Data          /
87///       /                                                               /
88///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
89///
90///   The contents of the RDATA fields are:
91///
92///   Algorithm Name:
93///      an octet sequence identifying the TSIG algorithm in the domain
94///      name syntax.  (Allowed names are listed in Table 3.)  The name is
95///      stored in the DNS name wire format as described in [RFC1034].  As
96///      per [RFC3597], this name MUST NOT be compressed.
97///
98///   Time Signed:
99///      an unsigned 48-bit integer containing the time the message was
100///      signed as seconds since 00:00 on 1970-01-01 UTC, ignoring leap
101///      seconds.
102///
103///   Fudge:
104///      an unsigned 16-bit integer specifying the allowed time difference
105///      in seconds permitted in the Time Signed field.
106///
107///   MAC Size:
108///      an unsigned 16-bit integer giving the length of the MAC field in
109///      octets.  Truncation is indicated by a MAC Size less than the size
110///      of the keyed hash produced by the algorithm specified by the
111///      Algorithm Name.
112///
113///   MAC:
114///      a sequence of octets whose contents are defined by the TSIG
115///      algorithm used, possibly truncated as specified by the MAC Size.
116///      The length of this field is given by the MAC Size.  Calculation of
117///      the MAC is detailed in Section 4.3.
118///
119///   Original ID:
120///      an unsigned 16-bit integer holding the message ID of the original
121///      request message.  For a TSIG RR on a request, it is set equal to
122///      the DNS message ID.  In a TSIG attached to a response -- or in
123///      cases such as the forwarding of a dynamic update request -- the
124///      field contains the ID of the original DNS request.
125///
126///   Error:
127///      in responses, an unsigned 16-bit integer containing the extended
128///      RCODE covering TSIG processing.  In requests, this MUST be zero.
129///
130///   Other Len:
131///      an unsigned 16-bit integer specifying the length of the Other Data
132///      field in octets.
133///
134///   Other Data:
135///      additional data relevant to the TSIG record.  In responses, this
136///      will be empty (i.e., Other Len will be zero) unless the content of
137///      the Error field is BADTIME, in which case it will be a 48-bit
138///      unsigned integer containing the server's current time as the
139///      number of seconds since 00:00 on 1970-01-01 UTC, ignoring leap
140///      seconds (see Section 5.2.3).  This document assigns no meaning to
141///      its contents in requests.
142/// ```
143#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
144#[derive(Debug, PartialEq, Eq, Hash, Clone)]
145pub struct TSIG {
146    algorithm: TsigAlgorithm,
147    time: u64,
148    fudge: u16,
149    mac: Vec<u8>,
150    oid: u16,
151    error: u16,
152    other: Vec<u8>,
153}
154
155/// Algorithm used to authenticate communication
156///
157/// [RFC8945 Secret Key Transaction Authentication for DNS](https://tools.ietf.org/html/rfc8945#section-6)
158/// ```text
159///      +==========================+================+=================+
160///      | Algorithm Name           | Implementation | Use             |
161///      +==========================+================+=================+
162///      | HMAC-MD5.SIG-ALG.REG.INT | MAY            | MUST NOT        |
163///      +--------------------------+----------------+-----------------+
164///      | gss-tsig                 | MAY            | MAY             |
165///      +--------------------------+----------------+-----------------+
166///      | hmac-sha1                | MUST           | NOT RECOMMENDED |
167///      +--------------------------+----------------+-----------------+
168///      | hmac-sha224              | MAY            | MAY             |
169///      +--------------------------+----------------+-----------------+
170///      | hmac-sha256              | MUST           | RECOMMENDED     |
171///      +--------------------------+----------------+-----------------+
172///      | hmac-sha256-128          | MAY            | MAY             |
173///      +--------------------------+----------------+-----------------+
174///      | hmac-sha384              | MAY            | MAY             |
175///      +--------------------------+----------------+-----------------+
176///      | hmac-sha384-192          | MAY            | MAY             |
177///      +--------------------------+----------------+-----------------+
178///      | hmac-sha512              | MAY            | MAY             |
179///      +--------------------------+----------------+-----------------+
180///      | hmac-sha512-256          | MAY            | MAY             |
181///      +--------------------------+----------------+-----------------+
182/// ```
183#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
184#[derive(Debug, PartialEq, Eq, Hash, Clone)]
185pub enum TsigAlgorithm {
186    /// HMAC-MD5.SIG-ALG.REG.INT (not supported for cryptographic operations)
187    #[cfg_attr(feature = "serde", serde(rename = "HMAC-MD5.SIG-ALG.REG.INT"))]
188    HmacMd5,
189    /// gss-tsig (not supported for cryptographic operations)
190    #[cfg_attr(feature = "serde", serde(rename = "gss-tsig"))]
191    Gss,
192    /// hmac-sha1 (not supported for cryptographic operations)
193    #[cfg_attr(feature = "serde", serde(rename = "hmac-sha1"))]
194    HmacSha1,
195    /// hmac-sha224 (not supported for cryptographic operations)
196    #[cfg_attr(feature = "serde", serde(rename = "hmac-sha224"))]
197    HmacSha224,
198    /// hmac-sha256
199    #[cfg_attr(feature = "serde", serde(rename = "hmac-sha256"))]
200    HmacSha256,
201    /// hmac-sha256-128 (not supported for cryptographic operations)
202    #[cfg_attr(feature = "serde", serde(rename = "hmac-sha256-128"))]
203    HmacSha256_128,
204    /// hmac-sha384
205    #[cfg_attr(feature = "serde", serde(rename = "hmac-sha384"))]
206    HmacSha384,
207    /// hmac-sha384-192 (not supported for cryptographic operations)
208    #[cfg_attr(feature = "serde", serde(rename = "hmac-sha384-192"))]
209    HmacSha384_192,
210    /// hmac-sha512
211    #[cfg_attr(feature = "serde", serde(rename = "hmac-sha512"))]
212    HmacSha512,
213    /// hmac-sha512-256 (not supported for cryptographic operations)
214    #[cfg_attr(feature = "serde", serde(rename = "hmac-sha512-256"))]
215    HmacSha512_256,
216    /// Unknown algorithm
217    Unknown(Name),
218}
219
220impl TSIG {
221    /// Constructs a new TSIG
222    ///
223    /// [RFC 8945, Secret Key Transaction Authentication for DNS](https://tools.ietf.org/html/rfc8945#section-4.1)
224    ///
225    /// ```text
226    /// 4.1.  TSIG RR Type
227    ///
228    ///   To provide secret key authentication, we use an RR type whose
229    ///   mnemonic is TSIG and whose type code is 250.  TSIG is a meta-RR and
230    ///   MUST NOT be cached.  TSIG RRs are used for authentication between DNS
231    ///   entities that have established a shared secret key.  TSIG RRs are
232    ///   dynamically computed to cover a particular DNS transaction and are
233    ///   not DNS RRs in the usual sense.
234    ///
235    ///   As the TSIG RRs are related to one DNS request/response, there is no
236    ///   value in storing or retransmitting them; thus, the TSIG RR is
237    ///   discarded once it has been used to authenticate a DNS message.
238    /// ```
239    pub fn new(
240        algorithm: TsigAlgorithm,
241        time: u64,
242        fudge: u16,
243        mac: Vec<u8>,
244        oid: u16,
245        error: u16,
246        other: Vec<u8>,
247    ) -> Self {
248        Self {
249            algorithm,
250            time,
251            fudge,
252            mac,
253            oid,
254            error,
255            other,
256        }
257    }
258
259    /// Returns the Mac in this TSIG
260    pub fn mac(&self) -> &[u8] {
261        &self.mac
262    }
263
264    /// Returns the time this TSIG was generated at
265    pub fn time(&self) -> u64 {
266        self.time
267    }
268
269    /// Returns the max delta from `time` for remote to accept the signature
270    pub fn fudge(&self) -> u16 {
271        self.fudge
272    }
273
274    /// Returns the algorithm used for the authentication code
275    pub fn algorithm(&self) -> &TsigAlgorithm {
276        &self.algorithm
277    }
278
279    /// Emit TSIG RR and RDATA as used for computing MAC
280    ///
281    /// ```text
282    /// 4.3.3.  TSIG Variables
283    ///
284    ///    Also included in the digest is certain information present in the
285    ///    TSIG RR.  Adding this data provides further protection against an
286    ///    attempt to interfere with the message.
287    ///
288    ///    +============+================+====================================+
289    ///    | Source     | Field Name     | Notes                              |
290    ///    +============+================+====================================+
291    ///    | TSIG RR    | NAME           | Key name, in canonical wire format |
292    ///    +------------+----------------+------------------------------------+
293    ///    | TSIG RR    | CLASS          | MUST be ANY                        |
294    ///    +------------+----------------+------------------------------------+
295    ///    | TSIG RR    | TTL            | MUST be 0                          |
296    ///    +------------+----------------+------------------------------------+
297    ///    | TSIG RDATA | Algorithm Name | in canonical wire format           |
298    ///    +------------+----------------+------------------------------------+
299    ///    | TSIG RDATA | Time Signed    | in network byte order              |
300    ///    +------------+----------------+------------------------------------+
301    ///    | TSIG RDATA | Fudge          | in network byte order              |
302    ///    +------------+----------------+------------------------------------+
303    ///    | TSIG RDATA | Error          | in network byte order              |
304    ///    +------------+----------------+------------------------------------+
305    ///    | TSIG RDATA | Other Len      | in network byte order              |
306    ///    +------------+----------------+------------------------------------+
307    ///    | TSIG RDATA | Other Data     | exactly as transmitted             |
308    ///    +------------+----------------+------------------------------------+
309    /// ```
310    pub fn emit_tsig_for_mac(
311        &self,
312        encoder: &mut BinEncoder<'_>,
313        key_name: &Name,
314    ) -> ProtoResult<()> {
315        key_name.emit_as_canonical(encoder, true)?;
316        DNSClass::ANY.emit(encoder)?;
317        encoder.emit_u32(0)?; // TTL
318        self.algorithm.emit(encoder)?;
319        encoder.emit_u16((self.time >> 32) as u16)?;
320        encoder.emit_u32(self.time as u32)?;
321        encoder.emit_u16(self.fudge)?;
322        encoder.emit_u16(self.error)?;
323        encoder.emit_u16(self.other.len() as u16)?;
324        encoder.emit_vec(&self.other)?;
325        Ok(())
326    }
327
328    /// Add actual MAC value to existing TSIG record data.
329    ///
330    /// # Arguments
331    ///
332    /// * `mac` - mac to be stored in this record.
333    pub fn set_mac(self, mac: Vec<u8>) -> Self {
334        Self { mac, ..self }
335    }
336}
337
338impl BinEncodable for TSIG {
339    /// Write the RData from the given Encoder
340    ///
341    /// ```text
342    ///                       1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
343    ///   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
344    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
345    ///  /                         Algorithm Name                        /
346    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
347    ///  |                                                               |
348    ///  |          Time Signed          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
349    ///  |                               |            Fudge              |
350    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
351    ///  |          MAC Size             |                               /
352    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+             MAC               /
353    ///  /                                                               /
354    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
355    ///  |          Original ID          |            Error              |
356    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
357    ///  |          Other Len            |                               /
358    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+           Other Data          /
359    ///  /                                                               /
360    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
361    /// ```
362    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
363        self.algorithm.emit(encoder)?;
364        encoder.emit_u16(
365            (self.time >> 32)
366                .try_into()
367                .map_err(|_| ProtoError::from("invalid time, overflow 48 bit counter in TSIG"))?,
368        )?;
369        encoder.emit_u32(self.time as u32)?; // this cast is supposed to truncate
370        encoder.emit_u16(self.fudge)?;
371        encoder.emit_u16(
372            self.mac
373                .len()
374                .try_into()
375                .map_err(|_| ProtoError::from("invalid mac, longer than 65535 B in TSIG"))?,
376        )?;
377        encoder.emit_vec(&self.mac)?;
378        encoder.emit_u16(self.oid)?;
379        encoder.emit_u16(self.error)?;
380        encoder.emit_u16(self.other.len().try_into().map_err(|_| {
381            ProtoError::from("invalid other_buffer, longer than 65535 B in TSIG")
382        })?)?;
383        encoder.emit_vec(&self.other)?;
384        Ok(())
385    }
386}
387
388impl<'r> RecordDataDecodable<'r> for TSIG {
389    /// Read the RData from the given Decoder
390    ///
391    /// ```text
392    ///                       1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
393    ///   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
394    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
395    ///  /                         Algorithm Name                        /
396    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
397    ///  |                                                               |
398    ///  |          Time Signed          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
399    ///  |                               |            Fudge              |
400    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
401    ///  |          MAC Size             |                               /
402    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+             MAC               /
403    ///  /                                                               /
404    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
405    ///  |          Original ID          |            Error              |
406    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
407    ///  |          Other Len            |                               /
408    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+           Other Data          /
409    ///  /                                                               /
410    ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
411    /// ```
412    fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<Self> {
413        let end_idx = length.map(|rdl| rdl as usize)
414        .checked_add(decoder.index())
415        .map_err(|_| ProtoError::from("rdata end position overflow"))? // no legal message is long enough to trigger that
416        .unverified(/*used only as length safely*/);
417
418        let algorithm = TsigAlgorithm::read(decoder)?;
419        let time_high = decoder.read_u16()?.unverified(/*valid as any u16*/) as u64;
420        let time_low = decoder.read_u32()?.unverified(/*valid as any u32*/) as u64;
421        let time = (time_high << 32) | time_low;
422        let fudge = decoder.read_u16()?.unverified(/*valid as any u16*/);
423        let mac_size = decoder
424            .read_u16()?
425            .verify_unwrap(|&size| decoder.index() + size as usize + 6 /* 3 u16 */ <= end_idx)
426            .map_err(|_| ProtoError::from("invalid mac length in TSIG"))?;
427        let mac =
428            decoder.read_vec(mac_size as usize)?.unverified(/*valid as any vec of the right size*/);
429        let oid = decoder.read_u16()?.unverified(/*valid as any u16*/);
430        let error = decoder.read_u16()?.unverified(/*valid as any u16*/);
431        let other_len = decoder
432            .read_u16()?
433            .verify_unwrap(|&size| decoder.index() + size as usize == end_idx)
434            .map_err(|_| ProtoError::from("invalid other length in TSIG"))?;
435        let other = decoder.read_vec(other_len as usize)?.unverified(/*valid as any vec of the right size*/);
436
437        Ok(Self {
438            algorithm,
439            time,
440            fudge,
441            mac,
442            oid,
443            error,
444            other,
445        })
446    }
447}
448
449impl RecordData for TSIG {
450    fn try_from_rdata(data: RData) -> Result<Self, RData> {
451        match data {
452            RData::DNSSEC(DNSSECRData::TSIG(csync)) => Ok(csync),
453            _ => Err(data),
454        }
455    }
456
457    fn try_borrow(data: &RData) -> Option<&Self> {
458        match data {
459            RData::DNSSEC(DNSSECRData::TSIG(csync)) => Some(csync),
460            _ => None,
461        }
462    }
463
464    fn record_type(&self) -> RecordType {
465        RecordType::TSIG
466    }
467
468    fn into_rdata(self) -> RData {
469        RData::DNSSEC(DNSSECRData::TSIG(self))
470    }
471}
472
473// Does not appear to have a normalized text representation
474impl fmt::Display for TSIG {
475    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
476        write!(
477            f,
478            "{algorithm} {time} {fudge} {mac} {oid} {error} {other}",
479            algorithm = self.algorithm,
480            time = self.time,
481            fudge = self.fudge,
482            mac = sshfp::HEX.encode(&self.mac),
483            oid = self.oid,
484            error = self.error,
485            other = sshfp::HEX.encode(&self.other),
486        )
487    }
488}
489
490impl TsigAlgorithm {
491    /// Return DNS name for the algorithm
492    pub fn to_name(&self) -> Name {
493        use TsigAlgorithm::*;
494        match self {
495            HmacMd5 => Name::from_ascii("HMAC-MD5.SIG-ALG.REG.INT"),
496            Gss => Name::from_ascii("gss-tsig"),
497            HmacSha1 => Name::from_ascii("hmac-sha1"),
498            HmacSha224 => Name::from_ascii("hmac-sha224"),
499            HmacSha256 => Name::from_ascii("hmac-sha256"),
500            HmacSha256_128 => Name::from_ascii("hmac-sha256-128"),
501            HmacSha384 => Name::from_ascii("hmac-sha384"),
502            HmacSha384_192 => Name::from_ascii("hmac-sha384-192"),
503            HmacSha512 => Name::from_ascii("hmac-sha512"),
504            HmacSha512_256 => Name::from_ascii("hmac-sha512-256"),
505            Unknown(name) => Ok(name.clone()),
506        }.unwrap(/* should not fail with static strings*/)
507    }
508
509    /// Write the Algorithm to the given encoder
510    pub fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
511        self.to_name().emit_as_canonical(encoder, true)?;
512        Ok(())
513    }
514
515    /// Read the Algorithm from the given Encoder
516    pub fn read(decoder: &mut BinDecoder<'_>) -> ProtoResult<Self> {
517        let mut name = Name::read(decoder)?;
518        name.set_fqdn(false);
519        Ok(Self::from_name(name))
520    }
521
522    /// Convert a DNS name to an Algorithm
523    pub fn from_name(name: Name) -> Self {
524        use TsigAlgorithm::*;
525        match name.to_ascii().as_str() {
526            "HMAC-MD5.SIG-ALG.REG.INT" => HmacMd5,
527            "gss-tsig" => Gss,
528            "hmac-sha1" => HmacSha1,
529            "hmac-sha224" => HmacSha224,
530            "hmac-sha256" => HmacSha256,
531            "hmac-sha256-128" => HmacSha256_128,
532            "hmac-sha384" => HmacSha384,
533            "hmac-sha384-192" => HmacSha384_192,
534            "hmac-sha512" => HmacSha512,
535            "hmac-sha512-256" => HmacSha512_256,
536            _ => Unknown(name),
537        }
538    }
539
540    /// Compute the Message Authentication Code using key and algorithm
541    ///
542    /// Supported algorithm are HmacSha256, HmacSha384, HmacSha512 and HmacSha512_256
543    /// Other algorithm return an error.
544    pub fn mac_data(&self, key: &[u8], message: &[u8]) -> Result<Vec<u8>, DnsSecError> {
545        use TsigAlgorithm::*;
546
547        let key = match self {
548            HmacSha256 => hmac::Key::new(hmac::HMAC_SHA256, key),
549            HmacSha384 => hmac::Key::new(hmac::HMAC_SHA384, key),
550            HmacSha512 => hmac::Key::new(hmac::HMAC_SHA512, key),
551            _ => return Err(DnsSecErrorKind::TsigUnsupportedMacAlgorithm(self.clone()).into()),
552        };
553
554        let mac = hmac::sign(&key, message);
555        let res = mac.as_ref().to_vec();
556
557        Ok(res)
558    }
559
560    /// Verifies the hmac tag against the given key and this algorithm.
561    ///
562    /// This is both faster than independently creating the MAC and also constant time preventing timing attacks
563    pub fn verify_mac(&self, key: &[u8], message: &[u8], tag: &[u8]) -> Result<(), DnsSecError> {
564        use TsigAlgorithm::*;
565
566        let key = match self {
567            HmacSha256 => hmac::Key::new(hmac::HMAC_SHA256, key),
568            HmacSha384 => hmac::Key::new(hmac::HMAC_SHA384, key),
569            HmacSha512 => hmac::Key::new(hmac::HMAC_SHA512, key),
570            _ => return Err(DnsSecErrorKind::TsigUnsupportedMacAlgorithm(self.clone()).into()),
571        };
572
573        hmac::verify(&key, message, tag).map_err(|_| DnsSecErrorKind::HmacInvalid.into())
574    }
575
576    /// Return length in bytes of the algorithms output
577    pub fn output_len(&self) -> Result<usize, DnsSecError> {
578        use TsigAlgorithm::*;
579
580        let len = match self {
581            HmacSha256 => hmac::HMAC_SHA256.digest_algorithm().output_len(),
582            HmacSha384 => hmac::HMAC_SHA384.digest_algorithm().output_len(),
583            HmacSha512 => hmac::HMAC_SHA512.digest_algorithm().output_len(),
584            _ => return Err(DnsSecErrorKind::TsigUnsupportedMacAlgorithm(self.clone()).into()),
585        };
586
587        Ok(len)
588    }
589
590    /// Return `true` if cryptographic operations needed for using this algorithm are supported,
591    /// `false` otherwise
592    ///
593    /// ## Supported
594    ///
595    /// - HmacSha256
596    /// - HmacSha384
597    /// - HmacSha512
598    /// - HmacSha512_256
599    pub fn supported(&self) -> bool {
600        use TsigAlgorithm::*;
601        matches!(self, HmacSha256 | HmacSha384 | HmacSha512)
602    }
603}
604
605impl fmt::Display for TsigAlgorithm {
606    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
607        write!(f, "{}", self.to_name())
608    }
609}
610
611/// Return the byte-message to be authenticated with a TSIG
612///
613/// # Arguments
614///
615/// * `previous_hash` - hash of previous message in case of message chaining, or of query in case
616///   of response. Should be None for query
617/// * `message` - the message to authenticate. Should not be modified after calling message_tbs
618///   except for adding the TSIG record
619/// * `pre_tsig` - TSIG rrdata, possibly with missing mac. Should not be modified in any other way
620///   after calling message_tbs
621/// * `key_name` - name of they key, should be the same as the name known by the remove
622///   server/client
623pub fn message_tbs<M: BinEncodable>(
624    previous_hash: Option<&[u8]>,
625    message: &M,
626    pre_tsig: &TSIG,
627    key_name: &Name,
628) -> ProtoResult<Vec<u8>> {
629    let mut buf: Vec<u8> = Vec::with_capacity(512);
630    let mut encoder: BinEncoder<'_> = BinEncoder::with_mode(&mut buf, EncodeMode::Normal);
631
632    if let Some(previous_hash) = previous_hash {
633        encoder.emit_u16(previous_hash.len() as u16)?;
634        encoder.emit_vec(previous_hash)?;
635    };
636    message.emit(&mut encoder)?;
637    pre_tsig.emit_tsig_for_mac(&mut encoder, key_name)?;
638    Ok(buf)
639}
640
641/// Return the byte-message that would have been used to generate a TSIG
642///
643/// # Arguments
644///
645/// * `previous_hash` - hash of previous message in case of message chaining, or of query in case
646///   of response. Should be None for query
647/// * `message` - the byte-message to authenticate, with included TSIG
648pub fn signed_bitmessage_to_buf(
649    previous_hash: Option<&[u8]>,
650    message: &[u8],
651    first_message: bool,
652) -> ProtoResult<(Vec<u8>, Record)> {
653    let mut decoder = BinDecoder::new(message);
654
655    // remove the tsig from Additional count
656    let mut header = Header::read(&mut decoder)?;
657    let adc = header.additional_count();
658    if adc > 0 {
659        header.set_additional_count(adc - 1);
660    } else {
661        return Err(ProtoError::from(
662            "missing tsig from response that must be authenticated",
663        ));
664    }
665
666    // keep position of data start
667    let start_data = message.len() - decoder.len();
668
669    let count = header.query_count();
670    for _ in 0..count {
671        Query::read(&mut decoder)?;
672    }
673
674    // read all records except for the last one (tsig)
675    let record_count = header.answer_count() as usize
676        + header.name_server_count() as usize
677        + header.additional_count() as usize;
678    Message::read_records(&mut decoder, record_count, false)?;
679
680    // keep position of data end
681    let end_data = message.len() - decoder.len();
682
683    // parse a tsig record
684    let sig = Record::read(&mut decoder)?;
685    let tsig = if let (RecordType::TSIG, RData::DNSSEC(DNSSECRData::TSIG(tsig_data))) =
686        (sig.record_type(), sig.data())
687    {
688        tsig_data
689    } else {
690        return Err(ProtoError::from("signature is not tsig"));
691    };
692    header.set_id(tsig.oid);
693
694    let mut buf = Vec::with_capacity(message.len());
695    let mut encoder = BinEncoder::new(&mut buf);
696
697    // prepend previous Mac if it exists
698    if let Some(previous_hash) = previous_hash {
699        encoder.emit_u16(previous_hash.len() as u16)?;
700        encoder.emit_vec(previous_hash)?;
701    }
702
703    // emit header without tsig
704    header.emit(&mut encoder)?;
705    // copy all records verbatim, without decompressing it
706    encoder.emit_vec(&message[start_data..end_data])?;
707    if first_message {
708        // emit the tsig pseudo-record for first message
709        tsig.emit_tsig_for_mac(&mut encoder, sig.name())?;
710    } else {
711        // emit only time and fudge for followings
712        encoder.emit_u16((tsig.time >> 32) as u16)?;
713        encoder.emit_u32(tsig.time as u32)?;
714        encoder.emit_u16(tsig.fudge)?;
715    }
716
717    Ok((buf, sig))
718}
719
720/// Helper function to make a TSIG record from the name of the key, and the TSIG RData
721pub fn make_tsig_record(name: Name, rdata: TSIG) -> Record {
722    // https://tools.ietf.org/html/rfc8945#section-4.2
723
724    let mut tsig = Record::from_rdata(
725        //   NAME:  The name of the key used, in domain name syntax
726        name,
727        //   TTL:  This MUST be 0.
728        0,
729        //   TYPE:  This MUST be TSIG (250: Transaction SIGnature).
730        DNSSECRData::TSIG(rdata).into(),
731    );
732
733    //   CLASS:  This MUST be ANY.
734    tsig.set_dns_class(DNSClass::ANY);
735    tsig
736}
737
738#[cfg(test)]
739mod tests {
740    #![allow(clippy::dbg_macro, clippy::print_stdout)]
741
742    use std::println;
743
744    use super::*;
745    use crate::rr::Record;
746
747    fn test_encode_decode(rdata: TSIG) {
748        let mut bytes = Vec::new();
749        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
750        rdata.emit(&mut encoder).expect("failed to emit tsig");
751        let bytes = encoder.into_bytes();
752
753        println!("bytes: {bytes:?}");
754
755        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
756        let read_rdata = TSIG::read_data(&mut decoder, Restrict::new(bytes.len() as u16))
757            .expect("failed to read back");
758        assert_eq!(rdata, read_rdata);
759    }
760
761    #[test]
762    fn test_encode_decode_tsig() {
763        test_encode_decode(TSIG::new(
764            TsigAlgorithm::HmacSha256,
765            0,
766            300,
767            vec![0, 1, 2, 3],
768            0,
769            0,
770            vec![4, 5, 6, 7],
771        ));
772        test_encode_decode(TSIG::new(
773            TsigAlgorithm::HmacSha384,
774            123456789,
775            60,
776            vec![9, 8, 7, 6, 5, 4],
777            1,
778            2,
779            vec![],
780        ));
781        test_encode_decode(TSIG::new(
782            TsigAlgorithm::Unknown(Name::from_ascii("unknown_algorithm").unwrap()),
783            123456789,
784            60,
785            vec![],
786            1,
787            2,
788            vec![0, 1, 2, 3, 4, 5, 6],
789        ));
790    }
791
792    #[test]
793    fn test_sign_encode() {
794        let mut message = Message::new();
795        message.add_answer(Record::stub());
796
797        let key_name = Name::from_ascii("some.name").unwrap();
798
799        let pre_tsig = TSIG::new(
800            TsigAlgorithm::HmacSha256,
801            12345,
802            60,
803            vec![],
804            message.id(),
805            0,
806            vec![],
807        );
808
809        let tbs = message_tbs(None, &message, &pre_tsig, &key_name).unwrap();
810
811        let pre_tsig = pre_tsig.set_mac(b"some signature".to_vec());
812
813        let tsig = make_tsig_record(key_name, pre_tsig);
814
815        message.add_tsig(tsig);
816
817        let message_byte = message.to_bytes().unwrap();
818
819        let tbv = signed_bitmessage_to_buf(None, &message_byte, true)
820            .unwrap()
821            .0;
822
823        assert_eq!(tbs, tbv);
824    }
825
826    #[test]
827    fn test_sign_encode_id_changed() {
828        let mut message = Message::new();
829        message.set_id(123).add_answer(Record::stub());
830
831        let key_name = Name::from_ascii("some.name").unwrap();
832
833        let pre_tsig = TSIG::new(
834            TsigAlgorithm::HmacSha256,
835            12345,
836            60,
837            vec![],
838            message.id(),
839            0,
840            vec![],
841        );
842
843        let tbs = message_tbs(None, &message, &pre_tsig, &key_name).unwrap();
844
845        let pre_tsig = pre_tsig.set_mac(b"some signature".to_vec());
846
847        let tsig = make_tsig_record(key_name, pre_tsig);
848
849        message.add_tsig(tsig);
850
851        let message_byte = message.to_bytes().unwrap();
852        let mut message = Message::from_bytes(&message_byte).unwrap();
853
854        message.set_id(456); // simulate the request id being changed due to request forwarding
855
856        let message_byte = message.to_bytes().unwrap();
857
858        let tbv = signed_bitmessage_to_buf(None, &message_byte, true)
859            .unwrap()
860            .0;
861
862        assert_eq!(tbs, tbv);
863
864        // sign and verify
865        let key = &[0, 1, 2, 3, 4];
866
867        let tag = TsigAlgorithm::HmacSha256.mac_data(key, &tbv).unwrap();
868
869        TsigAlgorithm::HmacSha256
870            .verify_mac(key, &tbv, &tag)
871            .expect("did not verify")
872    }
873}