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