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