Add Proof to NSEC results

This commit is contained in:
Benjamin Fry 2023-10-19 18:08:13 -07:00
parent 552fa36dc3
commit bc044e89f3
4 changed files with 61 additions and 13 deletions

View File

@ -24,7 +24,7 @@ use tracing::debug;
use crate::op::{Header, Query, ResponseCode};
#[cfg(feature = "dnssec")]
use crate::rr::dnssec::rdata::tsig::TsigAlgorithm;
use crate::rr::dnssec::{rdata::tsig::TsigAlgorithm, Proof};
use crate::rr::rdata::SOA;
use crate::rr::resource::RecordRef;
use crate::rr::{Name, Record, RecordType};
@ -99,6 +99,16 @@ pub enum ProtoErrorKind {
other: usize,
},
/// No Records and there is a corresponding DNSSEC Proof for NSEC
#[cfg(feature = "dnssec")]
#[error("DNSSEC Negative Record Response for {query}, {proof}")]
Nsec {
/// Query for which the NSEC was returned
query: Query,
/// DNSSEC proof of the record
proof: Proof,
},
/// DNS protocol version doesn't have the expected version 3
#[error("dns key value unknown, must be 3: {0}")]
DnsKeyProtocolNot3(u8),
@ -629,6 +639,11 @@ impl Clone for ProtoErrorKind {
trusted,
},
RequestRefused => RequestRefused,
#[cfg(feature = "dnssec")]
Nsec { ref query, proof } => Nsec {
query: query.clone(),
proof,
},
RrsigsNotPresent {
ref name,
ref record_type,

View File

@ -29,6 +29,7 @@ mod verifier;
pub use self::algorithm::Algorithm;
pub use self::digest_type::DigestType;
pub use self::nsec3::Nsec3HashAlgorithm;
pub use self::proof::Proof;
pub use self::public_key::PublicKey;
pub use self::public_key::PublicKeyBuf;
pub use self::public_key::PublicKeyEnum;

View File

@ -5,6 +5,8 @@
// https://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::fmt;
/// Represents the status of a DNSSEC verified record.
///
/// see [RFC 4035, DNSSEC Protocol Modifications, March 2005](https://datatracker.ietf.org/doc/html/rfc4035#section-4.3)
@ -48,3 +50,22 @@ pub enum Proof {
/// name servers for the relevant zones.
Indeterminate,
}
impl Proof {
pub fn is_secure(&self) -> bool {
*self == Self::Secure
}
}
impl fmt::Display for Proof {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Secure => "Secure",
Self::Insecure => "Insecure",
Self::Bogus => "Bogus",
Self::Indeterminate => "Indeterminate",
};
f.write_str(s)
}
}

View File

@ -21,7 +21,7 @@ use crate::{
rr::{
dnssec::{
rdata::{DNSSECRData, DNSKEY, RRSIG},
Algorithm, SupportedAlgorithms, TrustAnchor,
Algorithm, Proof, SupportedAlgorithms, TrustAnchor,
},
rdata::opt::EdnsOption,
DNSClass, Name, RData, Record, RecordData, RecordType,
@ -208,11 +208,13 @@ where
.filter(|rr| is_dnssec(rr, RecordType::NSEC))
.collect::<Vec<_>>();
if !verify_nsec(&query, soa_name, nsecs.as_slice()) {
let nsec_proof = verify_nsec(&query, soa_name, nsecs.as_slice());
if !nsec_proof.is_secure() {
// TODO change this to remove the NSECs, like we do for the others?
return future::err(ProtoError::from(
"could not validate negative response with NSEC",
));
return future::err(ProtoError::from(ProtoErrorKind::Nsec {
query: query.clone(),
proof: nsec_proof,
}));
}
}
@ -856,7 +858,7 @@ fn verify_rrset_with_dnskey(_: &DNSKEY, _: &RRSIG, _: &Rrset) -> ProtoResult<()>
/// ```
#[allow(clippy::blocks_in_conditions)]
#[doc(hidden)]
pub fn verify_nsec(query: &Query, soa_name: &Name, nsecs: &[&Record]) -> bool {
pub fn verify_nsec(query: &Query, soa_name: &Name, nsecs: &[&Record]) -> Proof {
// TODO: consider converting this to Result, and giving explicit reason for the failure
// first look for a record with the same name
@ -864,14 +866,19 @@ pub fn verify_nsec(query: &Query, soa_name: &Name, nsecs: &[&Record]) -> bool {
// if we got an NSEC record of the same name, but it is listed in the NSEC types,
// WTF? is that bad server, bad record
if let Some(nsec) = nsecs.iter().find(|nsec| query.name() == nsec.name()) {
return nsec
if nsec
.data()
.and_then(RData::as_dnssec)
.and_then(DNSSECRData::as_nsec)
.map_or(false, |rdata| {
// this should not be in the covered list
!rdata.type_bit_maps().contains(&query.query_type())
});
})
{
return Proof::Secure;
} else {
return Proof::Bogus;
}
}
let verify_nsec_coverage = |name: &Name| -> bool {
@ -890,9 +897,9 @@ pub fn verify_nsec(query: &Query, soa_name: &Name, nsecs: &[&Record]) -> bool {
})
};
// continue to validate there is no wildcard
if !verify_nsec_coverage(query.name()) {
// continue to validate there is no wildcard
return false;
return Proof::Bogus;
}
// validate ANY or *.domain record existence
@ -908,10 +915,14 @@ pub fn verify_nsec(query: &Query, soa_name: &Name, nsecs: &[&Record]) -> bool {
// don't need to validate the same name again
if wildcard == *query.name() {
// this was validated by the nsec coverage over the query.name()
true
Proof::Secure
} else {
// this is the final check, return it's value
// if there is wildcard coverage, we're good.
verify_nsec_coverage(&wildcard)
if verify_nsec_coverage(&wildcard) {
Proof::Secure
} else {
Proof::Bogus
}
}
}