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}