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