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 alloc::string::String;
11use core::{fmt, ops::BitOr};
12
13use bitflags::bitflags;
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16use thiserror::Error;
17
18use super::{Algorithm, DnsSecError};
19use crate::{
20 error::ProtoError,
21 op::Query,
22 rr::{Name, RecordType},
23};
24
25/// Represents the status of a DNSSEC verified record.
26///
27/// see [RFC 4035, DNSSEC Protocol Modifications, March 2005](https://datatracker.ietf.org/doc/html/rfc4035#section-4.3)
28/// ```text
29/// 4.3. Determining Security Status of Data
30///
31/// A security-aware resolver MUST be able to determine whether it should
32/// expect a particular RRset to be signed. More precisely, a
33/// security-aware resolver must be able to distinguish between four
34/// cases:
35/// ```
36#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
37#[must_use = "Proof is a flag on Record data, it should be interrogated before using a record"]
38#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
39#[repr(u8)]
40pub enum Proof {
41 /// An RRset for which the resolver is able to build a chain of
42 /// signed DNSKEY and DS RRs from a trusted security anchor to the
43 /// RRset. In this case, the RRset should be signed and is subject to
44 /// signature validation, as described above.
45 Secure = 3,
46
47 /// An RRset for which the resolver knows that it has no chain
48 /// of signed DNSKEY and DS RRs from any trusted starting point to the
49 /// RRset. This can occur when the target RRset lies in an unsigned
50 /// zone or in a descendent of an unsigned zone. In this case, the
51 /// RRset may or may not be signed, but the resolver will not be able
52 /// to verify the signature.
53 Insecure = 2,
54
55 /// An RRset for which the resolver believes that it ought to be
56 /// able to establish a chain of trust but for which it is unable to
57 /// do so, either due to signatures that for some reason fail to
58 /// validate or due to missing data that the relevant DNSSEC RRs
59 /// indicate should be present. This case may indicate an attack but
60 /// may also indicate a configuration error or some form of data
61 /// corruption.
62 Bogus = 1,
63
64 /// An RRset for which the resolver is not able to
65 /// determine whether the RRset should be signed, as the resolver is
66 /// not able to obtain the necessary DNSSEC RRs. This can occur when
67 /// the security-aware resolver is not able to contact security-aware
68 /// name servers for the relevant zones.
69 #[default]
70 Indeterminate = 0,
71}
72
73impl Proof {
74 /// Returns true if this Proof represents a validated DNSSEC record
75 #[inline]
76 pub fn is_secure(&self) -> bool {
77 *self == Self::Secure
78 }
79
80 /// Returns true if this Proof represents a validated to be insecure DNSSEC record,
81 /// meaning the zone is known to be not signed
82 #[inline]
83 pub fn is_insecure(&self) -> bool {
84 *self == Self::Insecure
85 }
86
87 /// Returns true if this Proof represents a DNSSEC record that failed validation,
88 /// meaning that the DNSSEC is bad, or other DNSSEC records are incorrect
89 #[inline]
90 pub fn is_bogus(&self) -> bool {
91 *self == Self::Bogus
92 }
93
94 /// Either the record has not been verified or there were network issues fetching DNSSEC records
95 #[inline]
96 pub fn is_indeterminate(&self) -> bool {
97 *self == Self::Indeterminate
98 }
99}
100
101impl fmt::Display for Proof {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 let s = match self {
104 Self::Secure => "Secure",
105 Self::Insecure => "Insecure",
106 Self::Bogus => "Bogus",
107 Self::Indeterminate => "Indeterminate",
108 };
109
110 f.write_str(s)
111 }
112}
113
114impl std::error::Error for Proof {}
115
116#[test]
117fn test_order() {
118 assert!(Proof::Secure > Proof::Insecure);
119 assert!(Proof::Insecure > Proof::Bogus);
120 assert!(Proof::Bogus > Proof::Indeterminate);
121}
122
123bitflags! {
124 /// Represents a set of flags.
125 pub struct ProofFlags: u32 {
126 /// Represents Proof::Secure
127 const SECURE = 1 << Proof::Secure as u8;
128 /// Represents Proof::Insecure
129 const INSECURE = 1 << Proof::Insecure as u8;
130 /// Represents Proof::Bogus
131 const BOGUS = 1 << Proof::Bogus as u8;
132 /// Represents Proof::Indeterminate
133 const INDETERMINATE = 1 << Proof::Indeterminate as u8;
134 }
135}
136
137impl From<Proof> for ProofFlags {
138 fn from(proof: Proof) -> Self {
139 match proof {
140 Proof::Secure => Self::SECURE,
141 Proof::Insecure => Self::INSECURE,
142 Proof::Bogus => Self::BOGUS,
143 Proof::Indeterminate => Self::INDETERMINATE,
144 }
145 }
146}
147
148impl BitOr for Proof {
149 type Output = ProofFlags;
150
151 // rhs is the "right-hand side" of the expression `a | b`
152 fn bitor(self, rhs: Self) -> Self::Output {
153 ProofFlags::from(self) | ProofFlags::from(rhs)
154 }
155}
156
157/// The error kind for dnssec errors that get returned in the crate
158#[allow(unreachable_pub)]
159#[derive(Debug, Error, Clone)]
160#[non_exhaustive]
161pub enum ProofErrorKind {
162 /// An error with an arbitrary message, referenced as &'static str
163 #[error("{0}")]
164 Message(&'static str),
165
166 /// An error with an arbitrary message, stored as String
167 #[error("{0}")]
168 Msg(String),
169
170 /// Algorithm mismatch between rrsig and dnskey
171 #[error("algorithm mismatch rrsig: {rrsig} dnskey: {dnskey}")]
172 AlgorithmMismatch {
173 /// Algorithm specified in the RRSIG
174 rrsig: Algorithm,
175 /// Algorithm supported in the DNSKEY
176 dnskey: Algorithm,
177 },
178
179 /// A DNSSEC validation error, occurred
180 #[error("ssl error: {0}")]
181 DnsSecError(#[from] DnsSecError),
182
183 /// A DnsKey verification of rrset and rrsig failed
184 #[error("dnskey and rrset failed to verify: {name} key_tag: {key_tag}")]
185 DnsKeyVerifyRrsig {
186 /// The name/label of the DNSKEY
187 name: Name,
188 /// The key tag derived from the DNSKEY
189 key_tag: u16,
190 /// The Error that occurred during validation
191 error: ProtoError,
192 },
193
194 /// There was no DNSKEY found for verifying the DNSSEC of the zone
195 #[error("no dnskey was found: {name}")]
196 DnskeyNotFound {
197 /// The name of the missing DNSKEY
198 name: Name,
199 },
200
201 /// A DnsKey was revoked and could not be used for validation
202 #[error("dnskey revoked: {name}, key_tag: {key_tag}")]
203 DnsKeyRevoked {
204 /// The name of the DNSKEY that was revoked
205 name: Name,
206 /// The key tag derived from the DNSKEY
207 key_tag: u16,
208 },
209
210 /// The DNSKEY is not covered by a DS record
211 #[error("dnskey has no ds: {name}")]
212 DnsKeyHasNoDs {
213 /// The name of the DNSKEY
214 name: Name,
215 },
216
217 /// No DNSSEC records returned with for the DS record
218 #[error("ds has no dnssec proof: {name}")]
219 DsHasNoDnssecProof {
220 /// DS record name
221 name: Name,
222 },
223
224 /// DS record exists but not a DNSKEY that matches
225 #[error("ds record exists, but no dnskey: {name}")]
226 DsRecordsButNoDnskey {
227 /// Name of the missing DNSKEY
228 name: Name,
229 },
230
231 /// DS record parent exists, but child does not
232 #[error("ds record should exist: {name}")]
233 DsRecordShouldExist {
234 /// Name fo the missing DS key
235 name: Name,
236 },
237
238 /// The DS response was empty
239 #[error("ds response empty: {name}")]
240 DsResponseEmpty {
241 /// No records for the DS query were returned
242 name: Name,
243 },
244
245 /// DS record does not exist, and this was proven with an NSEC
246 #[error("ds record does not exist: {name}")]
247 DsResponseNsec {
248 /// The name of the DS record
249 name: Name,
250 },
251
252 /// An error ocurred while calculating the DNSKEY key tag
253 #[error("internal error computing the key tag for: {name}")]
254 ErrorComputingKeyTag {
255 /// The name of the DNSKEY record
256 name: Name,
257 },
258
259 /// The DNSKEY used was not verified as secure
260 #[error("dnskey insecure: {name}, key_tag: {key_tag}")]
261 InsecureDnsKey {
262 /// The name of the DNSKEY
263 name: Name,
264 /// The key tag derived from the DNSKEY
265 key_tag: u16,
266 },
267
268 /// The DnsKey is not marked as a zone key
269 #[error("not a zone signing key: {name} key_tag: {key_tag}")]
270 NotZoneDnsKey {
271 /// Name of the DNSKEY
272 name: Name,
273 /// The key tag derived from the DNSKEY
274 key_tag: u16,
275 },
276
277 /// There was a protocol error when looking up DNSSEC records
278 #[error("communication failure for query: {query}: {proto}")]
279 Proto {
280 /// Query that failed
281 query: Query,
282 /// Reasons fo the failure
283 proto: ProtoError,
284 },
285
286 /// The RRSIGs for the rrset are not present.
287 /// It's indeterminate if DS records can't be found
288 /// It's bogus if the DS records are present
289 #[error("rrsigs are not present for: {name} record_type: {record_type}")]
290 RrsigsNotPresent {
291 /// Name that RRSIGS are missing for
292 name: Name,
293 /// The record type in question
294 record_type: RecordType,
295 },
296
297 /// The RRSIGs could not be verified or failed validation
298 #[error("rrsigs were not able to be verified: {name}, type: {record_type}")]
299 RrsigsUnverified {
300 /// Name that RRSIGS failed for
301 name: Name,
302 /// The record type in question
303 record_type: RecordType,
304 },
305
306 /// The self-signed dnskey is invalid
307 #[error("self-signed dnskey is invalid: {name}")]
308 SelfSignedKeyInvalid {
309 /// Name of the DNSKEY
310 name: Name,
311 },
312
313 /// Unknown or reserved key algorithm
314 #[error("unknown or reserved key algorithm")]
315 UnknownKeyAlgorithm,
316
317 /// Unsupported key algorithms
318 #[error("unsupported key algorithms")]
319 UnsupportedKeyAlgorithm,
320}
321
322/// The error type for dnssec errors that get returned in the crate
323#[derive(Debug, Clone, Error)]
324pub struct ProofError {
325 /// The proof derived from the failed state
326 pub proof: Proof,
327 /// The kind of error
328 pub kind: ProofErrorKind,
329}
330
331impl ProofError {
332 /// Create an error with the given Proof and Associated Error
333 pub fn new(proof: Proof, kind: ProofErrorKind) -> Self {
334 Self { proof, kind }
335 }
336
337 /// Get the kind of the error
338 pub fn kind(&self) -> &ProofErrorKind {
339 &self.kind
340 }
341}
342
343impl fmt::Display for ProofError {
344 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345 write!(f, "{}: {}", self.proof, self.kind)
346 }
347}
348
349/// A wrapper type to ensure that the state of a DNSSEC proof is evaluated before use
350#[derive(Debug, Clone, Eq, PartialEq)]
351pub struct Proven<T> {
352 proof: Proof,
353 value: T,
354}
355
356impl<T> Proven<T> {
357 /// Wrap the value with the given proof
358 pub fn new(proof: Proof, value: T) -> Self {
359 Self { proof, value }
360 }
361
362 /// Get the associated proof
363 pub fn proof(&self) -> Proof {
364 self.proof
365 }
366
367 /// Attempts to borrow the value only if it matches flags, returning the associated proof on failure
368 ///
369 /// ```
370 /// use hickory_proto::dnssec::{Proof, Proven};
371 ///
372 /// let proven = Proven::new(Proof::Bogus, 42u32);
373 ///
374 /// assert_eq!(*proven.require_as_ref(Proof::Bogus).unwrap(), 42_u32);
375 /// assert_eq!(*proven.require_as_ref(Proof::Bogus | Proof::Indeterminate).unwrap(), 42_u32);
376 /// assert_eq!(proven.require_as_ref(Proof::Secure | Proof::Insecure).unwrap_err(), Proof::Bogus);
377 /// ```
378 pub fn require_as_ref<I: Into<ProofFlags>>(&self, flags: I) -> Result<&T, Proof> {
379 if flags.into().contains(ProofFlags::from(self.proof)) {
380 Ok(&self.value)
381 } else {
382 Err(self.proof)
383 }
384 }
385
386 /// Attempts to take the value only if it matches flags, returning the associated proof on failure
387 ///
388 /// ```
389 /// use hickory_proto::dnssec::{Proof, Proven};
390 ///
391 /// let proven = Proven::new(Proof::Bogus, 42u32);
392 ///
393 /// assert_eq!(proven.clone().require(Proof::Bogus).unwrap(), 42_u32);
394 /// assert_eq!(proven.clone().require(Proof::Bogus | Proof::Indeterminate).unwrap(), 42_u32);
395 /// assert!(proven.require(Proof::Secure | Proof::Insecure).is_err());
396 /// ```
397 pub fn require<I: Into<ProofFlags>>(self, flags: I) -> Result<T, Self> {
398 if flags.into().contains(ProofFlags::from(self.proof)) {
399 Ok(self.value)
400 } else {
401 Err(self)
402 }
403 }
404
405 /// Map the value with the associated function, carrying forward the proof
406 pub fn map<U, F>(self, f: F) -> Proven<U>
407 where
408 F: FnOnce(T) -> U,
409 {
410 Proven {
411 proof: self.proof,
412 value: f(self.value),
413 }
414 }
415
416 /// Unwraps the Proven type into it's parts
417 pub fn into_parts(self) -> (Proof, T) {
418 let Self { proof, value } = self;
419
420 (proof, value)
421 }
422}
423
424impl<T> Proven<Option<T>> {
425 /// If the inner type is an Option this will transpose them so that it's an option wrapped Proven
426 pub fn transpose(self) -> Option<Proven<T>> {
427 if let Some(value) = self.value {
428 Some(Proven {
429 proof: self.proof,
430 value,
431 })
432 } else {
433 None
434 }
435 }
436}