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