Skip to main content

hickory_proto/dnssec/
proof.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//! DNSSEC related Proof of record authenticity
9
10use core::{fmt, ops::BitOr};
11
12use bitflags::bitflags;
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16/// Represents the status of a DNSSEC verified record.
17///
18/// see [RFC 4035, DNSSEC Protocol Modifications, March 2005](https://datatracker.ietf.org/doc/html/rfc4035#section-4.3)
19/// ```text
20/// 4.3.  Determining Security Status of Data
21///
22///   A security-aware resolver MUST be able to determine whether it should
23///   expect a particular RRset to be signed.  More precisely, a
24///   security-aware resolver must be able to distinguish between four
25///   cases:
26/// ```
27#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
28#[must_use = "Proof is a flag on Record data, it should be interrogated before using a record"]
29#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
30#[repr(u8)]
31pub enum Proof {
32    /// An RRset for which the resolver is able to build a chain of
33    ///   signed DNSKEY and DS RRs from a trusted security anchor to the
34    ///   RRset.  In this case, the RRset should be signed and is subject to
35    ///   signature validation, as described above.
36    Secure = 3,
37
38    /// An RRset for which the resolver knows that it has no chain
39    ///   of signed DNSKEY and DS RRs from any trusted starting point to the
40    ///   RRset.  This can occur when the target RRset lies in an unsigned
41    ///   zone or in a descendent of an unsigned zone.  In this case, the
42    ///   RRset may or may not be signed, but the resolver will not be able
43    ///   to verify the signature.
44    Insecure = 2,
45
46    /// An RRset for which the resolver believes that it ought to be
47    ///   able to establish a chain of trust but for which it is unable to
48    ///   do so, either due to signatures that for some reason fail to
49    ///   validate or due to missing data that the relevant DNSSEC RRs
50    ///   indicate should be present.  This case may indicate an attack but
51    ///   may also indicate a configuration error or some form of data
52    ///   corruption.
53    Bogus = 1,
54
55    /// An RRset for which the resolver is not able to
56    ///   determine whether the RRset should be signed, as the resolver is
57    ///   not able to obtain the necessary DNSSEC RRs.  This can occur when
58    ///   the security-aware resolver is not able to contact security-aware
59    ///   name servers for the relevant zones.
60    #[default]
61    Indeterminate = 0,
62}
63
64impl Proof {
65    /// Returns true if this Proof represents a validated DNSSEC record
66    #[inline]
67    pub fn is_secure(&self) -> bool {
68        *self == Self::Secure
69    }
70
71    /// Returns true if this Proof represents a validated to be insecure DNSSEC record,
72    ///   meaning the zone is known to be not signed
73    #[inline]
74    pub fn is_insecure(&self) -> bool {
75        *self == Self::Insecure
76    }
77
78    /// Returns true if this Proof represents a DNSSEC record that failed validation,
79    ///   meaning that the DNSSEC is bad, or other DNSSEC records are incorrect
80    #[inline]
81    pub fn is_bogus(&self) -> bool {
82        *self == Self::Bogus
83    }
84
85    /// Either the record has not been verified or there were network issues fetching DNSSEC records
86    #[inline]
87    pub fn is_indeterminate(&self) -> bool {
88        *self == Self::Indeterminate
89    }
90}
91
92impl fmt::Display for Proof {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        let s = match self {
95            Self::Secure => "Secure",
96            Self::Insecure => "Insecure",
97            Self::Bogus => "Bogus",
98            Self::Indeterminate => "Indeterminate",
99        };
100
101        f.write_str(s)
102    }
103}
104
105impl core::error::Error for Proof {}
106
107#[test]
108fn test_order() {
109    assert!(Proof::Secure > Proof::Insecure);
110    assert!(Proof::Insecure > Proof::Bogus);
111    assert!(Proof::Bogus > Proof::Indeterminate);
112}
113
114bitflags! {
115    /// Represents a set of flags.
116    pub struct ProofFlags: u32 {
117        /// Represents Proof::Secure
118        const SECURE = 1 << Proof::Secure as u8;
119        /// Represents Proof::Insecure
120        const INSECURE = 1 << Proof::Insecure as u8;
121        /// Represents Proof::Bogus
122        const BOGUS = 1 << Proof::Bogus as u8;
123        /// Represents Proof::Indeterminate
124        const INDETERMINATE = 1 << Proof::Indeterminate as u8;
125    }
126}
127
128impl From<Proof> for ProofFlags {
129    fn from(proof: Proof) -> Self {
130        match proof {
131            Proof::Secure => Self::SECURE,
132            Proof::Insecure => Self::INSECURE,
133            Proof::Bogus => Self::BOGUS,
134            Proof::Indeterminate => Self::INDETERMINATE,
135        }
136    }
137}
138
139impl BitOr for Proof {
140    type Output = ProofFlags;
141
142    // rhs is the "right-hand side" of the expression `a | b`
143    fn bitor(self, rhs: Self) -> Self::Output {
144        ProofFlags::from(self) | ProofFlags::from(rhs)
145    }
146}
147
148/// A wrapper type to ensure that the state of a DNSSEC proof is evaluated before use
149#[derive(Debug, Clone, Eq, PartialEq)]
150pub struct Proven<T> {
151    proof: Proof,
152    value: T,
153}
154
155impl<T> Proven<T> {
156    /// Wrap the value with the given proof
157    pub fn new(proof: Proof, value: T) -> Self {
158        Self { proof, value }
159    }
160
161    /// Get the associated proof
162    pub fn proof(&self) -> Proof {
163        self.proof
164    }
165
166    /// Attempts to borrow the value only if it matches flags, returning the associated proof on failure
167    ///
168    /// ```
169    /// use hickory_proto::dnssec::{Proof, Proven};
170    ///
171    /// let proven = Proven::new(Proof::Bogus, 42u32);
172    ///
173    /// assert_eq!(*proven.require_as_ref(Proof::Bogus).unwrap(), 42_u32);
174    /// assert_eq!(*proven.require_as_ref(Proof::Bogus | Proof::Indeterminate).unwrap(), 42_u32);
175    /// assert_eq!(proven.require_as_ref(Proof::Secure | Proof::Insecure).unwrap_err(), Proof::Bogus);
176    /// ```
177    pub fn require_as_ref<I: Into<ProofFlags>>(&self, flags: I) -> Result<&T, Proof> {
178        if flags.into().contains(ProofFlags::from(self.proof)) {
179            Ok(&self.value)
180        } else {
181            Err(self.proof)
182        }
183    }
184
185    /// Attempts to take the value only if it matches flags, returning the associated proof on failure
186    ///
187    /// ```
188    /// use hickory_proto::dnssec::{Proof, Proven};
189    ///
190    /// let proven = Proven::new(Proof::Bogus, 42u32);
191    ///
192    /// assert_eq!(proven.clone().require(Proof::Bogus).unwrap(), 42_u32);
193    /// assert_eq!(proven.clone().require(Proof::Bogus | Proof::Indeterminate).unwrap(), 42_u32);
194    /// assert!(proven.require(Proof::Secure | Proof::Insecure).is_err());
195    /// ```
196    pub fn require<I: Into<ProofFlags>>(self, flags: I) -> Result<T, Self> {
197        if flags.into().contains(ProofFlags::from(self.proof)) {
198            Ok(self.value)
199        } else {
200            Err(self)
201        }
202    }
203
204    /// Map the value with the associated function, carrying forward the proof
205    pub fn map<U, F>(self, f: F) -> Proven<U>
206    where
207        F: FnOnce(T) -> U,
208    {
209        Proven {
210            proof: self.proof,
211            value: f(self.value),
212        }
213    }
214
215    /// Unwraps the Proven type into it's parts
216    pub fn into_parts(self) -> (Proof, T) {
217        let Self { proof, value } = self;
218
219        (proof, value)
220    }
221}
222
223impl<T> Proven<Option<T>> {
224    /// If the inner type is an Option this will transpose them so that it's an option wrapped Proven
225    pub fn transpose(self) -> Option<Proven<T>> {
226        Some(Proven {
227            proof: self.proof,
228            value: self.value?,
229        })
230    }
231}