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}