add DS proof validation
This commit is contained in:
parent
98933eeb85
commit
c0f93de61e
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -966,6 +966,7 @@ dependencies = [
|
||||
name = "hickory-proto"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
@ -86,6 +86,7 @@ name = "hickory_proto"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
async-recursion.workspace = true
|
||||
async-trait.workspace = true
|
||||
backtrace = { workspace = true, optional = true }
|
||||
bytes = { workspace = true, optional = true }
|
||||
|
@ -171,9 +171,25 @@ pub enum ProofErrorKind {
|
||||
},
|
||||
|
||||
/// A DnsKey was revoked and could not be used for validation
|
||||
#[error("dnskey revoked: {name} key_tag: {key_tag}")]
|
||||
#[error("dnskey revoked: {name}, key_tag: {key_tag}")]
|
||||
DnsKeyRevoked { name: Name, key_tag: u16 },
|
||||
|
||||
/// No DNSSEC records returned with for the DS record
|
||||
#[error("ds has no dnssec proof: {name}")]
|
||||
DsHasNoDnssecProof { name: Name },
|
||||
|
||||
/// DS record parent exists, but child does not
|
||||
#[error("ds record is bogus, should exist: {name}")]
|
||||
DsRecordShouldExist { name: Name },
|
||||
|
||||
/// The DS response was empty
|
||||
#[error("ds response empty: {name}")]
|
||||
DsResponseEmpty { name: Name },
|
||||
|
||||
/// DS record doesnot exist, and this was proven with an NSEC
|
||||
#[error("ds record does not exist: {name}")]
|
||||
DsResponseNsec { name: Name },
|
||||
|
||||
/// The DnsKey is not marked as a zone key
|
||||
#[error("not a zone signing key: {name} key_tag: {key_tag}")]
|
||||
NotZoneDnsKey { name: Name, key_tag: u16 },
|
||||
|
@ -15,6 +15,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use async_recursion::async_recursion;
|
||||
use futures_util::{
|
||||
future::{self, Future, FutureExt, TryFutureExt},
|
||||
stream::{self, Stream, TryStreamExt},
|
||||
@ -343,33 +344,14 @@ where
|
||||
rrset_proofs.insert((name, record_type), proof);
|
||||
}
|
||||
|
||||
// set the proofs of all the records
|
||||
// set the proofs of all the records, all records are returned, it's up to downstream users to check for correctness
|
||||
let mut records = records;
|
||||
|
||||
let mut i = 0;
|
||||
while i < records.len() {
|
||||
let record = &mut records[i];
|
||||
let proof = rrset_proofs.get(&(record.name().clone(), record.record_type()));
|
||||
|
||||
match proof {
|
||||
Some(Proof::Secure) => {
|
||||
record.set_proof(Proof::Secure);
|
||||
i += 1;
|
||||
}
|
||||
// no proof or not secure, this is the old/current behavior to remove the record
|
||||
_ => {
|
||||
records.remove(i);
|
||||
}
|
||||
}
|
||||
for record in &mut records {
|
||||
rrset_proofs
|
||||
.get(&(record.name().clone(), record.record_type()))
|
||||
.map(|proof| record.set_proof(*proof));
|
||||
}
|
||||
|
||||
// TODO: future code, always return the records...
|
||||
// for record in &mut records {
|
||||
// rrset_proofs
|
||||
// .get(&(record.name().clone(), record.record_type()))
|
||||
// .map(|proof| record.set_proof(*proof));
|
||||
// }
|
||||
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
@ -426,48 +408,45 @@ where
|
||||
);
|
||||
|
||||
// check the DNSKEYS against the trust_anchor, if it's approved allow it.
|
||||
// this includes the root keys
|
||||
{
|
||||
let anchored_keys = rrset
|
||||
let anchored_keys: Vec<&DNSKEY> = rrset
|
||||
.records
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, rr)| rr.data().map(|d| (i, d)))
|
||||
.filter_map(|(i, data)| DNSKEY::try_borrow(data).map(|d| (i, d)))
|
||||
.filter_map(|(i, rdata)| {
|
||||
.filter_map(|r| r.data())
|
||||
.filter_map(DNSKEY::try_borrow)
|
||||
.filter(|dnskey| {
|
||||
if handle
|
||||
.trust_anchor
|
||||
.contains_dnskey_bytes(rdata.public_key())
|
||||
.contains_dnskey_bytes(dnskey.public_key())
|
||||
{
|
||||
debug!(
|
||||
"validated dnskey with trust_anchor: {}, {}",
|
||||
rrset.name, rdata
|
||||
rrset.name, dnskey
|
||||
);
|
||||
|
||||
Some(i)
|
||||
true
|
||||
} else {
|
||||
None
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect::<Vec<usize>>();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !anchored_keys.is_empty() {
|
||||
//let mut rrset = rrset;
|
||||
//preserve(&mut rrset.records, anchored_keys);
|
||||
return Ok(Proof::Secure);
|
||||
}
|
||||
}
|
||||
|
||||
// need to get DS records for each DNSKEY
|
||||
let ds_message = handle
|
||||
.lookup(Query::query(rrset.name.clone(), RecordType::DS), options)
|
||||
.first_answer()
|
||||
.await?;
|
||||
|
||||
// if DS record query is NSEC then this is
|
||||
Proof::Insecure;
|
||||
// if DS record query is NXdomain or no records found then
|
||||
Proof::Indeterminate;
|
||||
// otherwise, if DS record is Proof::Secure, then continue
|
||||
// there will be a DS record for everything under the root keys
|
||||
let ds_records = match find_ds_records(handle, rrset.name.clone(), options).await {
|
||||
Ok(records) => records,
|
||||
Err(err) => {
|
||||
return Err(ProtoError::from(ProtoErrorKind::Msg(format!(
|
||||
"No valid DS records: {err}"
|
||||
))))
|
||||
}
|
||||
};
|
||||
|
||||
let valid_keys = rrset
|
||||
.records
|
||||
@ -476,11 +455,9 @@ where
|
||||
.filter_map(|(i, rr)| rr.data().map(|d| (i, d)))
|
||||
.filter_map(|(i, data)| DNSKEY::try_borrow(data).map(|d| (i, d)))
|
||||
.filter(|&(_, key_rdata)| {
|
||||
ds_message
|
||||
.answers()
|
||||
ds_records
|
||||
.iter()
|
||||
.filter_map(|r| r.data().map(|d| (d, r.name())))
|
||||
.filter_map(|(ds, n)| DS::try_borrow(ds).map(|ds| (ds, n)))
|
||||
// must be covered by at least one DS record
|
||||
.any(|(ds_rdata, ds_name)| {
|
||||
if ds_rdata.covers(&rrset.name, key_rdata).unwrap_or(false) {
|
||||
@ -508,6 +485,74 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
async fn find_ds_records<H>(
|
||||
handle: DnssecDnsHandle<H>,
|
||||
zone: Name,
|
||||
options: DnsRequestOptions,
|
||||
) -> Result<Vec<Record<DS>>, ProofError>
|
||||
where
|
||||
H: DnsHandle + Sync + Unpin,
|
||||
{
|
||||
// need to get DS records for each DNSKEY
|
||||
// there will be a DS record for everything under the root keys
|
||||
let ds_message = handle
|
||||
.lookup(Query::query(zone.clone(), RecordType::DS), options)
|
||||
.first_answer()
|
||||
.await;
|
||||
|
||||
let error: ProtoError = match ds_message {
|
||||
Ok(mut ds_message)
|
||||
if ds_message
|
||||
.answers()
|
||||
.iter()
|
||||
.filter(|r| r.record_type() == RecordType::DS)
|
||||
.any(|r| r.proof().is_secure()) =>
|
||||
{
|
||||
// this is a secure DS record, perfect
|
||||
let ds_records = ds_message
|
||||
.take_answers()
|
||||
.into_iter()
|
||||
.filter_map(|r| Record::<DS>::try_from(r).ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
return Ok(ds_records);
|
||||
}
|
||||
Ok(_) => ProtoError::from(ProtoErrorKind::NoError),
|
||||
Err(error) => error,
|
||||
};
|
||||
|
||||
// if the DS record was an NSEC then we have an insecure zone
|
||||
if let Some((query, proof)) = error
|
||||
.kind()
|
||||
.as_nsec()
|
||||
.filter(|(_query, proof)| proof.is_secure())
|
||||
{
|
||||
return Err(ProofError::new(
|
||||
Proof::Insecure,
|
||||
ProofErrorKind::DsResponseNsec {
|
||||
name: query.name().to_owned(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
// otherwise we need to recursively discover the status of DS up the chain,
|
||||
// if we find a valid DS, then we're in a Bogus state,
|
||||
// if we find no records, then we are Indeterminate
|
||||
// if we get ProofError, our result is the same
|
||||
match find_ds_records(handle, zone.base_name(), options).await {
|
||||
Ok(ds_records) if !ds_records.is_empty() => Err(ProofError::new(
|
||||
Proof::Bogus,
|
||||
ProofErrorKind::DsRecordShouldExist { name: zone },
|
||||
)),
|
||||
Ok(ds_records) if ds_records.is_empty() => Err(ProofError::new(
|
||||
Proof::Indeterminate,
|
||||
ProofErrorKind::DsHasNoDnssecProof { name: zone },
|
||||
)),
|
||||
err => err,
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies that a given RRSET is validly signed by any of the specified RRSIGs.
|
||||
///
|
||||
/// Invalid RRSIGs will be ignored. RRSIGs will only be validated against DNSKEYs which can
|
||||
|
Loading…
Reference in New Issue
Block a user