domain/rdata/
tsig.rs

1//! Record data from [RFC 2845]: TSIG records.
2//!
3//! This RFC defines the TSIG record type used for signing DNS messages.
4//!
5//! [RFC 2845]: https://tools.ietf.org/html/rfc2845
6
7use core::cmp::Ordering;
8use core::{fmt, hash};
9
10#[cfg(all(feature = "std", not(test)))]
11use std::time::SystemTime;
12
13#[cfg(all(feature = "std", test))]
14use mock_instant::thread_local::SystemTime;
15use octseq::builder::OctetsBuilder;
16use octseq::octets::{Octets, OctetsFrom, OctetsInto};
17use octseq::parse::Parser;
18
19use crate::base::cmp::CanonicalOrd;
20use crate::base::iana::{Rtype, TsigRcode};
21use crate::base::name::{FlattenInto, ParsedName, ToName};
22use crate::base::rdata::{
23    ComposeRecordData, LongRecordData, ParseRecordData, RecordData,
24};
25use crate::base::wire::{Compose, Composer, Parse, ParseError};
26use crate::utils::base64;
27
28//------------ Tsig ----------------------------------------------------------
29
30#[derive(Clone)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Tsig<Octs, Name> {
33    /// The signature algorithm as a domain name.
34    algorithm: Name,
35
36    /// The Unix epoch time at which the signature was created.
37    ///
38    /// Note that this is an unsigned 48 bit value in wire format.
39    time_signed: Time48,
40
41    /// Seconds of error permitted in time signed.
42    fudge: u16,
43
44    /// MAC.
45    ///
46    /// In wire format, consists of a unsigned 16 bit integer containing the
47    /// length followed by that many octets of actual MAC.
48    #[cfg_attr(
49        feature = "serde",
50        serde(
51            serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
52            deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
53            bound(
54                serialize = "Octs: octseq::serde::SerializeOctets",
55                deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
56            )
57        )
58    )]
59    mac: Octs,
60
61    /// Original message ID.
62    original_id: u16,
63
64    /// TSIG response code.
65    error: TsigRcode,
66
67    /// Other.
68    ///
69    /// This is normally empty unless a BADTIME error happened. In wire
70    /// format, it is encoded as a unsigned 16 bit integer followed by that
71    /// many octets.
72    #[cfg_attr(
73        feature = "serde",
74        serde(
75            serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
76            deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
77            bound(
78                serialize = "Octs: octseq::serde::SerializeOctets",
79                deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
80            )
81        )
82    )]
83    other: Octs,
84}
85
86impl Tsig<(), ()> {
87    /// The rtype of this record data type.
88    pub(crate) const RTYPE: Rtype = Rtype::TSIG;
89}
90
91impl<O, N> Tsig<O, N> {
92    /// Creates new TSIG record data from its components.
93    ///
94    /// See the access methods for an explanation of these components. The
95    /// function will return an error if the wire format length of the record
96    /// would exceed 65,535 octets.
97    pub fn new(
98        algorithm: N,
99        time_signed: Time48,
100        fudge: u16,
101        mac: O,
102        original_id: u16,
103        error: TsigRcode,
104        other: O,
105    ) -> Result<Self, LongRecordData>
106    where O: AsRef<[u8]>, N: ToName {
107        LongRecordData::check_len(
108            6 // time_signed
109            + 2 // fudge
110            + 2 // MAC length
111            + 2 // original ID
112            + 2 // error
113            + 2 // other length
114            + usize::from(algorithm.compose_len()).checked_add(
115                mac.as_ref().len()
116            ).expect("long MAC").checked_add(
117                other.as_ref().len()
118            ).expect("long TSIG")
119        )?;
120        Ok(unsafe {
121            Tsig::new_unchecked(
122                algorithm, time_signed, fudge, mac, original_id, error, other,
123            )
124        })
125    }
126
127    /// Creates new TSIG record data without checking.
128    ///
129    /// # Safety
130    ///
131    /// The caller needs to ensure that the wire format length of the
132    /// created record will not exceed 65,535 octets.
133    pub unsafe fn new_unchecked(
134        algorithm: N,
135        time_signed: Time48,
136        fudge: u16,
137        mac: O,
138        original_id: u16,
139        error: TsigRcode,
140        other: O,
141    ) -> Self {
142        Tsig {
143            algorithm,
144            time_signed,
145            fudge,
146            mac,
147            original_id,
148            error,
149            other,
150        }
151    }
152
153    /// Returns a reference to the algorithm name.
154    ///
155    /// TSIG encodes the algorithm used for keys and signatures as a domain
156    /// name. It does, however, only use the format. No structure is used at
157    /// all.
158    pub fn algorithm(&self) -> &N {
159        &self.algorithm
160    }
161
162    /// Returns the Unix time when the signature is created.
163    ///
164    /// Despite its type, this is actually a 48 bit number. The upper 16 bits
165    /// will never be set.
166    pub fn time_signed(&self) -> Time48 {
167        self.time_signed
168    }
169
170    /// Return the number of seconds of offset from signing time permitted.
171    ///
172    /// When a signature is checked, the local system time needs to be within
173    /// this many seconds from `time_signed` to be accepted.
174    pub fn fudge(&self) -> u16 {
175        self.fudge
176    }
177
178    /// Returns a reference to the bytes value containing the MAC.
179    pub fn mac(&self) -> &O {
180        &self.mac
181    }
182
183    /// Returns an octet slice containing the MAC.
184    pub fn mac_slice(&self) -> &[u8]
185    where
186        O: AsRef<[u8]>,
187    {
188        self.mac.as_ref()
189    }
190
191    /// Converts the record data into the MAC.
192    pub fn into_mac(self) -> O {
193        self.mac
194    }
195
196    /// Returns the original message ID.
197    ///
198    /// Since the message ID is part of the signature generation but may be
199    /// changed for a forwarded message, it is included in the TSIG record.
200    pub fn original_id(&self) -> u16 {
201        self.original_id
202    }
203
204    /// Returns the TSIG error.
205    pub fn error(&self) -> TsigRcode {
206        self.error
207    }
208
209    /// Returns a reference to the other bytes.
210    ///
211    /// This field is only used for BADTIME errors to return the server time.
212    /// Otherwise it is empty.
213    pub fn other(&self) -> &O {
214        &self.other
215    }
216
217    /// Returns the other bytes as the server time.
218    ///
219    /// If the other bytes field is exactly 6 bytes long, this methods
220    /// returns it as a `u64` representation of the Unix time contained.
221    pub fn other_time(&self) -> Option<Time48>
222    where
223        O: AsRef<[u8]>,
224    {
225        if self.other.as_ref().len() == 6 {
226            Some(Time48::from_slice(self.other.as_ref()))
227        } else {
228            None
229        }
230    }
231
232    /// Returns whether the record is valid at the given time.
233    ///
234    /// The method checks whether the given time is within [`fudge`]
235    /// seconds of the [`time_signed`].
236    ///
237    /// [`fudge`]: #method.fudge
238    /// [`time_signed`]: #method.time_signed
239    pub fn is_valid_at(&self, now: Time48) -> bool {
240        now.eq_fudged(self.time_signed, self.fudge.into())
241    }
242
243    /// Returns whether the record is valid right now.
244    ///
245    /// The method checks whether the current system time is within [`fudge`]
246    /// seconds of the [`time_signed`].
247    ///
248    /// [`fudge`]: #method.fudge
249    /// [`time_signed`]: #method.time_signed
250    #[cfg(feature = "std")]
251    pub fn is_valid_now(&self) -> bool {
252        self.is_valid_at(Time48::now())
253    }
254
255    pub(super) fn convert_octets<TOcts, TName>(
256        self,
257    ) -> Result<Tsig<TOcts, TName>, TOcts::Error>
258    where
259        TOcts: OctetsFrom<O>,
260        TName: OctetsFrom<N, Error = TOcts::Error>,
261    {
262        Ok(unsafe {
263            Tsig::new_unchecked(
264                self.algorithm.try_octets_into()?,
265                self.time_signed,
266                self.fudge,
267                self.mac.try_octets_into()?,
268                self.original_id,
269                self.error,
270                self.other.try_octets_into()?,
271            )
272        })
273    }
274
275    pub(super) fn flatten<TOcts, TName>(
276        self,
277    ) -> Result<Tsig<TOcts, TName>, TOcts::Error>
278    where
279        TOcts: OctetsFrom<O>,
280        N: FlattenInto<TName, AppendError = TOcts::Error>,
281    {
282        Ok(unsafe {
283            Tsig::new_unchecked(
284                self.algorithm.try_flatten_into()?,
285                self.time_signed,
286                self.fudge,
287                self.mac.try_octets_into()?,
288                self.original_id,
289                self.error,
290                self.other.try_octets_into()?,
291            )
292        })
293    }
294}
295
296impl<Octs> Tsig<Octs, ParsedName<Octs>> {
297    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
298        parser: &mut Parser<'a, Src>,
299    ) -> Result<Self, ParseError> {
300        let algorithm = ParsedName::parse(parser)?;
301        let time_signed = Time48::parse(parser)?;
302        let fudge = u16::parse(parser)?;
303        let mac_size = u16::parse(parser)?;
304        let mac = parser.parse_octets(mac_size as usize)?;
305        let original_id = u16::parse(parser)?;
306        let error = TsigRcode::parse(parser)?;
307        let other_len = u16::parse(parser)?;
308        let other = parser.parse_octets(other_len as usize)?;
309        Ok(unsafe {
310            Tsig::new_unchecked(
311                algorithm, time_signed, fudge, mac, original_id, error, other,
312            )
313        })
314    }
315}
316
317//--- OctetsFrom and FlattenInto
318
319impl<Octs, SrcOctets, Name, SrcName> OctetsFrom<Tsig<SrcOctets, SrcName>>
320    for Tsig<Octs, Name>
321where
322    Octs: OctetsFrom<SrcOctets>,
323    Name: OctetsFrom<SrcName>,
324    Octs::Error: From<Name::Error>,
325{
326    type Error = Octs::Error;
327
328    fn try_octets_from(
329        source: Tsig<SrcOctets, SrcName>,
330    ) -> Result<Self, Self::Error> {
331        Ok(unsafe {
332            Tsig::new_unchecked(
333                Name::try_octets_from(source.algorithm)?,
334                source.time_signed,
335                source.fudge,
336                Octs::try_octets_from(source.mac)?,
337                source.original_id,
338                source.error,
339                Octs::try_octets_from(source.other)?,
340            )
341        })
342    }
343}
344
345impl<Octs, TOcts, Name, TName> FlattenInto<Tsig<TOcts, TName>>
346    for Tsig<Octs, Name>
347where
348    TOcts: OctetsFrom<Octs>,
349    Name: FlattenInto<TName, AppendError = TOcts::Error>
350{
351    type AppendError = TOcts::Error;
352
353    fn try_flatten_into(
354        self
355    ) -> Result<Tsig<TOcts, TName>, Self::AppendError > {
356        self.flatten()
357    }
358}
359
360
361//--- PartialEq and Eq
362
363impl<O, OO, N, NN> PartialEq<Tsig<OO, NN>> for Tsig<O, N>
364where
365    O: AsRef<[u8]>,
366    OO: AsRef<[u8]>,
367    N: ToName,
368    NN: ToName,
369{
370    fn eq(&self, other: &Tsig<OO, NN>) -> bool {
371        self.algorithm.name_eq(&other.algorithm)
372            && self.time_signed == other.time_signed
373            && self.fudge == other.fudge
374            && self.mac.as_ref().eq(other.mac.as_ref())
375            && self.original_id == other.original_id
376            && self.error == other.error
377            && self.other.as_ref().eq(other.other.as_ref())
378    }
379}
380
381impl<O: AsRef<[u8]>, N: ToName> Eq for Tsig<O, N> {}
382
383//--- PartialOrd, Ord, and CanonicalOrd
384
385impl<O, OO, N, NN> PartialOrd<Tsig<OO, NN>> for Tsig<O, N>
386where
387    O: AsRef<[u8]>,
388    OO: AsRef<[u8]>,
389    N: ToName,
390    NN: ToName,
391{
392    fn partial_cmp(&self, other: &Tsig<OO, NN>) -> Option<Ordering> {
393        match self.algorithm.name_cmp(&other.algorithm) {
394            Ordering::Equal => {}
395            other => return Some(other),
396        }
397        match self.time_signed.partial_cmp(&other.time_signed) {
398            Some(Ordering::Equal) => {}
399            other => return other,
400        }
401        match self.fudge.partial_cmp(&other.fudge) {
402            Some(Ordering::Equal) => {}
403            other => return other,
404        }
405        match self.mac.as_ref().partial_cmp(other.mac.as_ref()) {
406            Some(Ordering::Equal) => {}
407            other => return other,
408        }
409        match self.original_id.partial_cmp(&other.original_id) {
410            Some(Ordering::Equal) => {}
411            other => return other,
412        }
413        match self.error.partial_cmp(&other.error) {
414            Some(Ordering::Equal) => {}
415            other => return other,
416        }
417        self.other.as_ref().partial_cmp(other.other.as_ref())
418    }
419}
420
421impl<O: AsRef<[u8]>, N: ToName> Ord for Tsig<O, N> {
422    fn cmp(&self, other: &Self) -> Ordering {
423        match self.algorithm.name_cmp(&other.algorithm) {
424            Ordering::Equal => {}
425            other => return other,
426        }
427        match self.time_signed.cmp(&other.time_signed) {
428            Ordering::Equal => {}
429            other => return other,
430        }
431        match self.fudge.cmp(&other.fudge) {
432            Ordering::Equal => {}
433            other => return other,
434        }
435        match self.mac.as_ref().cmp(other.mac.as_ref()) {
436            Ordering::Equal => {}
437            other => return other,
438        }
439        match self.original_id.cmp(&other.original_id) {
440            Ordering::Equal => {}
441            other => return other,
442        }
443        match self.error.cmp(&other.error) {
444            Ordering::Equal => {}
445            other => return other,
446        }
447        self.other.as_ref().cmp(other.other.as_ref())
448    }
449}
450
451impl<O, OO, N, NN> CanonicalOrd<Tsig<OO, NN>> for Tsig<O, N>
452where
453    O: AsRef<[u8]>,
454    OO: AsRef<[u8]>,
455    N: ToName,
456    NN: ToName,
457{
458    fn canonical_cmp(&self, other: &Tsig<OO, NN>) -> Ordering {
459        match self.algorithm.composed_cmp(&other.algorithm) {
460            Ordering::Equal => {}
461            other => return other,
462        }
463        match self.time_signed.cmp(&other.time_signed) {
464            Ordering::Equal => {}
465            other => return other,
466        }
467        match self.fudge.cmp(&other.fudge) {
468            Ordering::Equal => {}
469            other => return other,
470        }
471        match self.mac.as_ref().len().cmp(&other.mac.as_ref().len()) {
472            Ordering::Equal => {}
473            other => return other,
474        }
475        match self.mac.as_ref().cmp(other.mac.as_ref()) {
476            Ordering::Equal => {}
477            other => return other,
478        }
479        match self.original_id.cmp(&other.original_id) {
480            Ordering::Equal => {}
481            other => return other,
482        }
483        match self.error.cmp(&other.error) {
484            Ordering::Equal => {}
485            other => return other,
486        }
487        match self.other.as_ref().len().cmp(&other.other.as_ref().len()) {
488            Ordering::Equal => {}
489            other => return other,
490        }
491        self.other.as_ref().cmp(other.other.as_ref())
492    }
493}
494
495//--- Hash
496
497impl<O: AsRef<[u8]>, N: hash::Hash> hash::Hash for Tsig<O, N> {
498    fn hash<H: hash::Hasher>(&self, state: &mut H) {
499        self.algorithm.hash(state);
500        self.time_signed.hash(state);
501        self.fudge.hash(state);
502        self.mac.as_ref().hash(state);
503        self.original_id.hash(state);
504        self.error.hash(state);
505        self.other.as_ref().hash(state);
506    }
507}
508
509//--- RecordData, ParseRecordData, ComposeRecordData
510
511impl<O, N> RecordData for Tsig<O, N> {
512    fn rtype(&self) -> Rtype {
513        Tsig::RTYPE
514    }
515}
516
517impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
518    for Tsig<Octs::Range<'a>, ParsedName<Octs::Range<'a>>>
519{
520    fn parse_rdata(
521        rtype: Rtype,
522        parser: &mut Parser<'a, Octs>,
523    ) -> Result<Option<Self>, ParseError> {
524        if rtype == Tsig::RTYPE {
525            Self::parse(parser).map(Some)
526        } else {
527            Ok(None)
528        }
529    }
530}
531
532impl<Octs: AsRef<[u8]>, Name: ToName> ComposeRecordData
533    for Tsig<Octs, Name>
534{
535    fn rdlen(&self, _compress: bool) -> Option<u16> {
536        Some(
537            6 // time_signed
538            + 2 // fudge
539            + 2 // MAC length
540            + 2 // original ID
541            + 2 // error
542            + 2 // other length
543            + self.algorithm.compose_len().checked_add(
544                u16::try_from(self.mac.as_ref().len()).expect("long MAC")
545            ).expect("long MAC").checked_add(
546                u16::try_from(self.other.as_ref().len()).expect("long TSIG")
547            ).expect("long TSIG"),
548        )
549    }
550
551    fn compose_rdata<Target: Composer + ?Sized>(
552        &self,
553        target: &mut Target,
554    ) -> Result<(), Target::AppendError> {
555        self.algorithm.compose(target)?;
556        self.time_signed.compose(target)?;
557        self.fudge.compose(target)?;
558        u16::try_from(self.mac.as_ref().len())
559            .expect("long MAC")
560            .compose(target)?;
561        target.append_slice(self.mac.as_ref())?;
562        self.original_id.compose(target)?;
563        self.error.compose(target)?;
564        u16::try_from(self.other.as_ref().len())
565            .expect("long MAC")
566            .compose(target)?;
567        target.append_slice(self.other.as_ref())
568    }
569
570    fn compose_canonical_rdata<Target: Composer + ?Sized>(
571        &self,
572        target: &mut Target,
573    ) -> Result<(), Target::AppendError> {
574        self.compose_rdata(target)
575    }
576}
577
578//--- Display and Debug
579
580impl<O: AsRef<[u8]>, N: fmt::Display> fmt::Display for Tsig<O, N> {
581    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
582        write!(
583            f,
584            "{}. {} {} ",
585            self.algorithm, self.time_signed, self.fudge
586        )?;
587        base64::display(&self.mac, f)?;
588        write!(f, " {} {} \"", self.original_id, self.error)?;
589        base64::display(&self.other, f)?;
590        write!(f, "\"")
591    }
592}
593
594impl<O: AsRef<[u8]>, N: fmt::Debug> fmt::Debug for Tsig<O, N> {
595    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
596        f.debug_struct("Tsig")
597            .field("algorithm", &self.algorithm)
598            .field("time_signed", &self.time_signed)
599            .field("fudge", &self.fudge)
600            .field("mac", &self.mac.as_ref())
601            .field("original_id", &self.original_id)
602            .field("error", &self.error)
603            .field("other", &self.other.as_ref())
604            .finish()
605    }
606}
607
608//------------ Time48 --------------------------------------------------------
609
610/// A 48-bit Unix timestamp.
611#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
612#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
613pub struct Time48(u64);
614
615impl Time48 {
616    /// Returns the timestamp of the current moment.
617    ///
618    /// The funtion will panic if for whatever reason the current moment is
619    /// too far in the future to fit into this type. For a correctly set
620    /// clock, this will happen in December 8,921,556, so should be fine.
621    #[cfg(feature = "std")]
622    #[must_use]
623    pub fn now() -> Time48 {
624        Self::from_u64(
625            SystemTime::now()
626                .duration_since(SystemTime::UNIX_EPOCH)
627                .expect("system time before Unix epoch")
628                .as_secs(),
629        )
630    }
631
632    /// Creates a value from a 64 bit integer.
633    ///
634    /// The upper 16 bits of the arument must be zero or else this function
635    /// panics. This is also why we don’t implement `From`.
636    #[must_use]
637    pub fn from_u64(value: u64) -> Self {
638        assert!(value & 0xFFFF_0000_0000_0000 == 0);
639        Time48(value)
640    }
641
642    /// Creates a value from an octet slice.
643    ///
644    /// This slice should contain the octets of the value in network byte
645    /// order.
646    ///
647    /// # Panics
648    ///
649    /// The function panics if the slice is shorter than 6 octets.
650    fn from_slice(slice: &[u8]) -> Self {
651        Time48(
652            (u64::from(slice[0]) << 40)
653                | (u64::from(slice[1]) << 32)
654                | (u64::from(slice[2]) << 24)
655                | (u64::from(slice[3]) << 16)
656                | (u64::from(slice[4]) << 8)
657                | (u64::from(slice[5])),
658        )
659    }
660
661    /// Converts a value into its wire format.
662    ///
663    /// Returns the octets of the encoded value in network byte order.
664    #[must_use]
665    pub fn into_octets(self) -> [u8; 6] {
666        let mut res = [0u8; 6];
667        res[0] = (self.0 >> 40) as u8;
668        res[1] = (self.0 >> 32) as u8;
669        res[2] = (self.0 >> 24) as u8;
670        res[3] = (self.0 >> 16) as u8;
671        res[4] = (self.0 >> 8) as u8;
672        res[5] = self.0 as u8;
673        res
674    }
675
676    /// Returns whether the time is within a given period.
677    ///
678    /// Returns `true` iff `other` is at most `fudge` seconds before or after
679    /// this value’s time.
680    #[must_use]
681    pub fn eq_fudged(self, other: Self, fudge: u64) -> bool {
682        self.0.saturating_sub(fudge) <= other.0
683            && self.0.saturating_add(fudge) >= other.0
684    }
685
686    pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
687        parser: &mut Parser<Octs>,
688    ) -> Result<Self, ParseError> {
689        let mut buf = [0u8; 6];
690        parser.parse_buf(&mut buf)?;
691        Ok(Time48::from_slice(&buf))
692    }
693
694    pub fn compose<Target: OctetsBuilder + ?Sized>(
695        &self,
696        target: &mut Target,
697    ) -> Result<(), Target::AppendError> {
698        target.append_slice(&self.into_octets())
699    }
700}
701
702//--- From
703
704impl From<Time48> for u64 {
705    fn from(value: Time48) -> u64 {
706        value.0
707    }
708}
709
710//--- Display
711
712impl fmt::Display for Time48 {
713    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
714        self.0.fmt(f)
715    }
716}
717
718//============ Testing =======================================================
719
720#[cfg(test)]
721#[cfg(all(feature = "std", feature = "bytes"))]
722mod test {
723    use super::*;
724    use crate::base::name::Name;
725    use crate::base::rdata::test::{test_compose_parse, test_rdlen};
726    use core::str::FromStr;
727    use std::vec::Vec;
728
729    #[test]
730    #[allow(clippy::redundant_closure)] // lifetimes ...
731    fn tsig_compose_parse_scan() {
732        let rdata = Tsig::new(
733            Name::<Vec<u8>>::from_str("key.example.com.").unwrap(),
734            Time48::now(),
735            12,
736            "foo",
737            13,
738            TsigRcode::BADCOOKIE,
739            "",
740        ).unwrap();
741        test_rdlen(&rdata);
742        test_compose_parse(&rdata, |parser| Tsig::parse(parser));
743    }
744}