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(|_| DecodeError::InsufficientBytes)?;
387 let mac =
388 decoder.read_vec(mac_size as usize)?.unverified(/*valid as any vec of the right size*/);
389 let oid = decoder.read_u16()?.unverified(/*valid as any u16*/);
390 let error = match decoder.read_u16()?.unverified(/*valid as any u16*/) {
391 0 => None,
392 code => Some(TsigError::from(code)),
393 };
394 let other_len = decoder
395 .read_u16()?
396 .verify_unwrap(|&size| decoder.index() + size as usize == end_idx)
397 .map_err(|_| DecodeError::InsufficientBytes)?;
398 let other = decoder.read_vec(other_len as usize)?.unverified(/*valid as any vec of the right size*/);
399
400 Ok(Self {
401 algorithm,
402 time,
403 fudge,
404 mac,
405 oid,
406 error,
407 other,
408 })
409 }
410}
411
412impl RecordData for TSIG {
413 fn try_borrow(data: &RData) -> Option<&Self> {
414 match data {
415 RData::TSIG(csync) => Some(csync),
416 _ => None,
417 }
418 }
419
420 fn record_type(&self) -> RecordType {
421 RecordType::TSIG
422 }
423
424 fn into_rdata(self) -> RData {
425 RData::TSIG(self)
426 }
427}
428
429// Does not appear to have a normalized text representation
430impl fmt::Display for TSIG {
431 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
432 write!(
433 f,
434 "{algorithm} {time} {fudge} {mac} {oid} {error} {other}",
435 algorithm = self.algorithm,
436 time = self.time,
437 fudge = self.fudge,
438 mac = sshfp::HEX.encode(&self.mac),
439 oid = self.oid,
440 error = self.error.map(Into::into).unwrap_or(0),
441 other = sshfp::HEX.encode(&self.other),
442 )
443 }
444}
445
446/// Algorithm used to authenticate communication
447///
448/// [RFC8945 Secret Key Transaction Authentication for DNS](https://tools.ietf.org/html/rfc8945#section-6)
449/// ```text
450/// +==========================+================+=================+
451/// | Algorithm Name | Implementation | Use |
452/// +==========================+================+=================+
453/// | HMAC-MD5.SIG-ALG.REG.INT | MAY | MUST NOT |
454/// +--------------------------+----------------+-----------------+
455/// | gss-tsig | MAY | MAY |
456/// +--------------------------+----------------+-----------------+
457/// | hmac-sha1 | MUST | NOT RECOMMENDED |
458/// +--------------------------+----------------+-----------------+
459/// | hmac-sha224 | MAY | MAY |
460/// +--------------------------+----------------+-----------------+
461/// | hmac-sha256 | MUST | RECOMMENDED |
462/// +--------------------------+----------------+-----------------+
463/// | hmac-sha256-128 | MAY | MAY |
464/// +--------------------------+----------------+-----------------+
465/// | hmac-sha384 | MAY | MAY |
466/// +--------------------------+----------------+-----------------+
467/// | hmac-sha384-192 | MAY | MAY |
468/// +--------------------------+----------------+-----------------+
469/// | hmac-sha512 | MAY | MAY |
470/// +--------------------------+----------------+-----------------+
471/// | hmac-sha512-256 | MAY | MAY |
472/// +--------------------------+----------------+-----------------+
473/// ```
474#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
475#[derive(Debug, PartialEq, Eq, Hash, Clone)]
476pub enum TsigAlgorithm {
477 /// HMAC-MD5.SIG-ALG.REG.INT (not supported for cryptographic operations)
478 #[cfg_attr(feature = "serde", serde(rename = "HMAC-MD5.SIG-ALG.REG.INT"))]
479 HmacMd5,
480 /// gss-tsig (not supported for cryptographic operations)
481 #[cfg_attr(feature = "serde", serde(rename = "gss-tsig"))]
482 Gss,
483 /// hmac-sha1 (not supported for cryptographic operations)
484 #[cfg_attr(feature = "serde", serde(rename = "hmac-sha1"))]
485 HmacSha1,
486 /// hmac-sha224 (not supported for cryptographic operations)
487 #[cfg_attr(feature = "serde", serde(rename = "hmac-sha224"))]
488 HmacSha224,
489 /// hmac-sha256
490 #[cfg_attr(feature = "serde", serde(rename = "hmac-sha256"))]
491 HmacSha256,
492 /// hmac-sha256-128 (not supported for cryptographic operations)
493 #[cfg_attr(feature = "serde", serde(rename = "hmac-sha256-128"))]
494 HmacSha256_128,
495 /// hmac-sha384
496 #[cfg_attr(feature = "serde", serde(rename = "hmac-sha384"))]
497 HmacSha384,
498 /// hmac-sha384-192 (not supported for cryptographic operations)
499 #[cfg_attr(feature = "serde", serde(rename = "hmac-sha384-192"))]
500 HmacSha384_192,
501 /// hmac-sha512
502 #[cfg_attr(feature = "serde", serde(rename = "hmac-sha512"))]
503 HmacSha512,
504 /// hmac-sha512-256 (not supported for cryptographic operations)
505 #[cfg_attr(feature = "serde", serde(rename = "hmac-sha512-256"))]
506 HmacSha512_256,
507 /// Unknown algorithm
508 Unknown(Name),
509}
510
511impl TsigAlgorithm {
512 /// Return DNS name for the algorithm
513 pub fn to_name(&self) -> Name {
514 use TsigAlgorithm::*;
515 match self {
516 HmacMd5 => Name::from_ascii("HMAC-MD5.SIG-ALG.REG.INT"),
517 Gss => Name::from_ascii("gss-tsig"),
518 HmacSha1 => Name::from_ascii("hmac-sha1"),
519 HmacSha224 => Name::from_ascii("hmac-sha224"),
520 HmacSha256 => Name::from_ascii("hmac-sha256"),
521 HmacSha256_128 => Name::from_ascii("hmac-sha256-128"),
522 HmacSha384 => Name::from_ascii("hmac-sha384"),
523 HmacSha384_192 => Name::from_ascii("hmac-sha384-192"),
524 HmacSha512 => Name::from_ascii("hmac-sha512"),
525 HmacSha512_256 => Name::from_ascii("hmac-sha512-256"),
526 Unknown(name) => Ok(name.clone()),
527 }.unwrap(/* should not fail with static strings*/)
528 }
529
530 /// Convert a DNS name to an Algorithm
531 pub fn from_name(name: Name) -> Self {
532 use TsigAlgorithm::*;
533 match name.to_ascii().as_str() {
534 "HMAC-MD5.SIG-ALG.REG.INT" => HmacMd5,
535 "gss-tsig" => Gss,
536 "hmac-sha1" => HmacSha1,
537 "hmac-sha224" => HmacSha224,
538 "hmac-sha256" => HmacSha256,
539 "hmac-sha256-128" => HmacSha256_128,
540 "hmac-sha384" => HmacSha384,
541 "hmac-sha384-192" => HmacSha384_192,
542 "hmac-sha512" => HmacSha512,
543 "hmac-sha512-256" => HmacSha512_256,
544 _ => Unknown(name),
545 }
546 }
547
548 /// Compute the Message Authentication Code using key and algorithm
549 ///
550 /// Supported algorithm are HmacSha256, HmacSha384, HmacSha512 and HmacSha512_256
551 /// Other algorithm return an error.
552 #[cfg(feature = "__dnssec")]
553 pub fn mac_data(&self, key: &[u8], message: &[u8]) -> Result<Vec<u8>, DnsSecError> {
554 use TsigAlgorithm::*;
555
556 let key = match self {
557 HmacSha256 => hmac::Key::new(hmac::HMAC_SHA256, key),
558 HmacSha384 => hmac::Key::new(hmac::HMAC_SHA384, key),
559 HmacSha512 => hmac::Key::new(hmac::HMAC_SHA512, key),
560 _ => return Err(DnsSecError::TsigUnsupportedMacAlgorithm(self.clone())),
561 };
562
563 let mac = hmac::sign(&key, message);
564 let res = mac.as_ref().to_vec();
565
566 Ok(res)
567 }
568
569 /// Verifies the hmac tag against the given key and this algorithm.
570 ///
571 /// This is both faster than independently creating the MAC and also constant time preventing timing attacks
572 #[cfg(feature = "__dnssec")]
573 pub fn verify_mac(&self, key: &[u8], message: &[u8], tag: &[u8]) -> Result<(), DnsSecError> {
574 use TsigAlgorithm::*;
575
576 let key = match self {
577 HmacSha256 => hmac::Key::new(hmac::HMAC_SHA256, key),
578 HmacSha384 => hmac::Key::new(hmac::HMAC_SHA384, key),
579 HmacSha512 => hmac::Key::new(hmac::HMAC_SHA512, key),
580 _ => return Err(DnsSecError::TsigUnsupportedMacAlgorithm(self.clone())),
581 };
582
583 hmac::verify(&key, message, tag).map_err(|_| DnsSecError::HmacInvalid)
584 }
585
586 /// Return `true` if cryptographic operations needed for using this algorithm are supported,
587 /// `false` otherwise
588 ///
589 /// ## Supported
590 ///
591 /// - HmacSha256
592 /// - HmacSha384
593 /// - HmacSha512
594 /// - HmacSha512_256
595 pub fn supported(&self) -> bool {
596 use TsigAlgorithm::*;
597 matches!(self, HmacSha256 | HmacSha384 | HmacSha512)
598 }
599
600 /// Return length in bytes of the algorithms output
601 #[cfg(feature = "__dnssec")]
602 pub(crate) fn output_len(&self) -> Result<usize, DnsSecError> {
603 use TsigAlgorithm::*;
604
605 let len = match self {
606 HmacSha256 => hmac::HMAC_SHA256.digest_algorithm().output_len(),
607 HmacSha384 => hmac::HMAC_SHA384.digest_algorithm().output_len(),
608 HmacSha512 => hmac::HMAC_SHA512.digest_algorithm().output_len(),
609 _ => return Err(DnsSecError::TsigUnsupportedMacAlgorithm(self.clone())),
610 };
611
612 Ok(len)
613 }
614}
615
616impl fmt::Display for TsigAlgorithm {
617 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
618 write!(f, "{}", self.to_name())
619 }
620}
621
622impl BinEncodable for TsigAlgorithm {
623 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
624 self.to_name().emit(encoder)
625 }
626}
627
628impl BinDecodable<'_> for TsigAlgorithm {
629 fn read(decoder: &mut BinDecoder<'_>) -> Result<Self, DecodeError> {
630 let mut name = Name::read(decoder)?;
631 name.set_fqdn(false);
632 Ok(Self::from_name(name))
633 }
634}
635
636/// A TSIG RR error rcode
637///
638/// See <https://www.rfc-editor.org/rfc/rfc8945.html#section-3>
639#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
640#[derive(Debug, Eq, PartialEq, PartialOrd, Copy, Clone, Hash)]
641pub enum TsigError {
642 /// Bad signature
643 BadSig,
644 /// Bad key
645 BadKey,
646 /// Bad signature time
647 BadTime,
648 /// Bad truncated request MAC
649 BadTrunc,
650 /// An unknown error
651 Unknown(u16),
652}
653
654impl From<u16> for TsigError {
655 fn from(value: u16) -> Self {
656 match value {
657 16 => Self::BadSig,
658 17 => Self::BadKey,
659 18 => Self::BadTime,
660 22 => Self::BadTrunc,
661 code => Self::Unknown(code),
662 }
663 }
664}
665
666impl From<TsigError> for u16 {
667 fn from(value: TsigError) -> Self {
668 match value {
669 TsigError::BadSig => 16,
670 TsigError::BadKey => 17,
671 TsigError::BadTime => 18,
672 TsigError::BadTrunc => 22,
673 TsigError::Unknown(code) => code,
674 }
675 }
676}
677
678/// Return the to-be-signed data for authenticating the message with TSIG.
679///
680/// # Arguments
681///
682/// * `message` - the message to authenticate. Should not be modified after calling this function
683/// except to add the final TSIG record
684/// * `pre_tsig` - TSIG rrdata, possibly with missing MAC. Should not be modified in any other way
685/// after calling this function.
686/// * `key_name` - the name of the TSIG key, should be the same as the name known by the remote
687/// peer.
688pub fn message_tbs<M: BinEncodable>(
689 message: &M,
690 pre_tsig: &TSIG,
691 key_name: &Name,
692) -> ProtoResult<Vec<u8>> {
693 let mut buf = Vec::with_capacity(512);
694 let mut encoder = BinEncoder::new(&mut buf);
695 message.emit(&mut encoder)?;
696 pre_tsig.emit_tsig_for_mac(&mut encoder, key_name)?;
697 Ok(buf)
698}
699
700/// Return the byte-message that would have been used to generate a TSIG
701///
702/// # Arguments
703///
704/// * `previous_hash` - hash of previous message in case of message chaining, or of query in case
705/// of response. Should be None for query
706/// * `message` - the byte-message to authenticate, with included TSIG
707/// * `first_message` - whether to emit the tsig pseudo-record for a first message
708#[cfg(feature = "__dnssec")]
709pub fn signed_bitmessage_to_buf(
710 message: &[u8],
711 previous_hash: Option<&[u8]>,
712 first_message: bool,
713) -> ProtoResult<(Vec<u8>, Box<Record<TSIG>>)> {
714 let mut decoder = BinDecoder::new(message);
715 let Header {
716 mut metadata,
717 mut counts,
718 } = Header::read(&mut decoder)?;
719
720 // Adjust the header additional count down by one - this separates out the final
721 // additional data TSIG record.
722 if counts.additionals > 0 {
723 counts.additionals -= 1;
724 } else {
725 return Err(ProtoError::from(
726 "missing tsig from response that must be authenticated",
727 ));
728 }
729
730 // Note the position of the decoder in the message, past the header, before reading any data.
731 let start_data = message.len() - decoder.len();
732
733 // Advance past the queries.
734 let count = counts.queries;
735 for _ in 0..count {
736 Query::read(&mut decoder)?;
737 }
738
739 // Advance past answer and authority records together.
740 let answer_authority_count = (counts.answers + counts.authorities) as usize;
741 let (_, _, sig) = Message::read_records(
742 &mut decoder,
743 answer_authority_count,
744 false,
745 metadata.op_code,
746 )?;
747 debug_assert!(sig.is_none());
748
749 // Advance past additional records, up to the final TSIG record.
750 let (_, _, sig) = Message::read_records(
751 &mut decoder,
752 counts.additionals as usize,
753 true,
754 metadata.op_code,
755 )?;
756 debug_assert!(sig.is_none());
757 // Note the position of the decoder ahead of the final additional data TSIG record.
758 let end_data = message.len() - decoder.len();
759
760 // Read the TSIG signature record.
761 let (_, _, sig) = Message::read_records(&mut decoder, 1, true, metadata.op_code)?;
762 let Some(tsig_rr) = sig else {
763 return Err(ProtoError::from("TSIG signature record not found"));
764 };
765
766 let tsig = &tsig_rr.data;
767 metadata.id = tsig.oid;
768
769 // Construct the TBS data.
770 let mut buf = Vec::with_capacity(message.len());
771 let mut encoder = BinEncoder::new(&mut buf);
772
773 // Prepend the previous hash if provided.
774 if let Some(previous_hash) = previous_hash {
775 encoder.emit_u16(previous_hash.len() as u16)?;
776 encoder.emit_vec(previous_hash)?;
777 }
778
779 // Emit the header we modified to remove the TSIG additional record.
780 Header { metadata, counts }.emit(&mut encoder)?;
781
782 // Emit all the message data between the header and the TSIG record.
783 encoder.emit_vec(&message[start_data..end_data])?;
784
785 if first_message {
786 // Emit the TSIG pseudo-record when this is the first message.
787 tsig.emit_tsig_for_mac(&mut encoder, &tsig_rr.name)?;
788 } else {
789 // Emit only time and fudge data for later messages.
790 encoder.emit_u16((tsig.time >> 32) as u16)?;
791 encoder.emit_u32(tsig.time as u32)?;
792 encoder.emit_u16(tsig.fudge)?;
793 }
794
795 Ok((buf, tsig_rr))
796}
797
798/// Helper function to make a TSIG record from the name of the key, and the TSIG RData
799pub fn make_tsig_record(name: Name, rdata: TSIG) -> Record<TSIG> {
800 // https://tools.ietf.org/html/rfc8945#section-4.2
801
802 let mut tsig = Record::from_rdata(
803 name, // NAME: The name of the key used, in domain name syntax
804 0, // TTL: This MUST be 0.
805 rdata, // TYPE: This MUST be TSIG (250: Transaction SIGnature).
806 );
807
808 // CLASS: This MUST be ANY.
809 tsig.dns_class = DNSClass::ANY;
810 tsig
811}
812
813#[cfg(test)]
814mod tests {
815 #![allow(clippy::dbg_macro, clippy::print_stdout)]
816
817 use std::println;
818
819 use super::*;
820 #[cfg(feature = "__dnssec")]
821 use crate::rr::Record;
822
823 fn test_encode_decode(rdata: TSIG) {
824 let mut bytes = Vec::new();
825 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
826 rdata.emit(&mut encoder).expect("failed to emit tsig");
827 let bytes = encoder.into_bytes();
828
829 println!("bytes: {bytes:?}");
830
831 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
832 let read_rdata = TSIG::read_data(&mut decoder, Restrict::new(bytes.len() as u16))
833 .expect("failed to read back");
834 assert_eq!(rdata, read_rdata);
835 }
836
837 #[test]
838 fn test_encode_decode_tsig() {
839 test_encode_decode(TSIG::new(
840 TsigAlgorithm::HmacSha256,
841 0,
842 300,
843 vec![0, 1, 2, 3],
844 0,
845 None,
846 vec![4, 5, 6, 7],
847 ));
848 test_encode_decode(TSIG::new(
849 TsigAlgorithm::HmacSha384,
850 123456789,
851 60,
852 vec![9, 8, 7, 6, 5, 4],
853 1,
854 Some(TsigError::BadKey),
855 vec![],
856 ));
857 test_encode_decode(TSIG::new(
858 TsigAlgorithm::Unknown(Name::from_ascii("unknown_algorithm").unwrap()),
859 123456789,
860 60,
861 vec![],
862 1,
863 Some(TsigError::BadTime),
864 vec![0, 1, 2, 3, 4, 5, 6],
865 ));
866 test_encode_decode(TSIG::new(
867 TsigAlgorithm::Unknown(Name::from_ascii("unknown_algorithm").unwrap()),
868 123456789,
869 60,
870 vec![],
871 1,
872 Some(TsigError::Unknown(420)),
873 vec![0, 1, 2, 3, 4, 5, 6],
874 ));
875 }
876
877 #[test]
878 #[cfg(feature = "__dnssec")]
879 fn test_sign_encode() {
880 let mut message = Message::query();
881 message.add_answer(Record::stub());
882
883 let key_name = Name::from_ascii("some.name").unwrap();
884
885 let pre_tsig = TSIG::new(
886 TsigAlgorithm::HmacSha256,
887 12345,
888 60,
889 vec![],
890 message.id,
891 None,
892 vec![],
893 );
894
895 let tbs = message_tbs(&message, &pre_tsig, &key_name).unwrap();
896
897 let pre_tsig = pre_tsig.set_mac(b"some signature".to_vec());
898
899 message.set_signature(Box::new(make_tsig_record(key_name, pre_tsig)));
900
901 let message_byte = message.to_bytes().unwrap();
902
903 let tbv = signed_bitmessage_to_buf(&message_byte, None, true)
904 .unwrap()
905 .0;
906
907 assert_eq!(tbs, tbv);
908 }
909
910 #[test]
911 #[cfg(feature = "__dnssec")]
912 fn test_sign_encode_id_changed() {
913 let mut message = Message::query();
914 message.metadata.id = 123;
915 message.answers.push(Record::stub());
916
917 let key_name = Name::from_ascii("some.name").unwrap();
918
919 let pre_tsig = TSIG::new(
920 TsigAlgorithm::HmacSha256,
921 12345,
922 60,
923 vec![],
924 message.id,
925 None,
926 vec![],
927 );
928
929 let tbs = message_tbs(&message, &pre_tsig, &key_name).unwrap();
930
931 let pre_tsig = pre_tsig.set_mac(b"some signature".to_vec());
932
933 message.set_signature(Box::new(make_tsig_record(key_name, pre_tsig)));
934
935 let message_byte = message.to_bytes().unwrap();
936 let mut message = Message::from_bytes(&message_byte).unwrap();
937
938 message.metadata.id = 456; // simulate the request id being changed due to request forwarding
939
940 let message_byte = message.to_bytes().unwrap();
941
942 let tbv = signed_bitmessage_to_buf(&message_byte, None, true)
943 .unwrap()
944 .0;
945
946 assert_eq!(tbs, tbv);
947
948 // sign and verify
949 let key = &[0, 1, 2, 3, 4];
950
951 let tag = TsigAlgorithm::HmacSha256.mac_data(key, &tbv).unwrap();
952
953 TsigAlgorithm::HmacSha256
954 .verify_mac(key, &tbv, &tag)
955 .expect("did not verify")
956 }
957}