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