use std::collections::VecDeque;
use std::str::{FromStr, Utf8Error};
use std::sync::Arc;
use std::vec::Vec;
use bytes::Bytes;
use moka::future::Cache;
use crate::base::iana::{ExtendedErrorCode, Nsec3HashAlgorithm};
use crate::base::name::{Label, ToName};
use crate::base::opt::ExtendedError;
use crate::base::{Name, ParsedName, Rtype};
use crate::dep::octseq::Octets;
use crate::dnssec::common::nsec3_hash;
use crate::rdata::nsec3::{Nsec3Salt, OwnerHash};
use crate::rdata::{AllRecordData, Nsec, Nsec3};
use super::context::{Config, ValidationState};
use super::group::ValidatedGroup;
use super::utilities::{make_ede, star_closest_encloser};
#[derive(Debug)]
pub enum NsecState {
NoData,
Nothing,
}
pub fn nsec_for_nodata(
target: &Name<Bytes>,
groups: &mut [ValidatedGroup],
rtype: Rtype,
signer_name: &Name<Bytes>,
) -> (NsecState, Option<ExtendedError<Vec<u8>>>) {
let mut ede = None;
for g in groups.iter() {
let (opt_nsec, new_ede) = get_checked_nsec(g, signer_name);
let nsec = if let Some(nsec) = opt_nsec {
nsec
} else {
if ede.is_none() {
ede = new_ede;
}
continue;
};
let owner = g.owner();
if target.name_eq(&owner) {
let types = nsec.types();
if types.contains(rtype) || types.contains(Rtype::CNAME) {
let ede = make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"NSEC for NODATA proves requested Rtype or CNAME",
);
return (NsecState::Nothing, ede);
}
if rtype == Rtype::DS && *target != Name::<Vec<u8>>::root() {
if types.contains(Rtype::NS) && types.contains(Rtype::SOA) {
let ede = make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"NSEC from apex for DS",
);
return (NsecState::Nothing, ede);
}
} else if types.contains(Rtype::NS) && !types.contains(Rtype::SOA)
{
let ede = make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"NSEC from parent for non-DS rtype",
);
return (NsecState::Nothing, ede);
}
return (NsecState::NoData, None);
}
if nsec_in_range(target, &owner, &nsec.next_name())
&& nsec.next_name().ends_with(target)
{
return (NsecState::NoData, None);
}
}
(NsecState::Nothing, ede)
}
pub fn nsec_for_nodata_wildcard(
target: &Name<Bytes>,
groups: &mut [ValidatedGroup],
rtype: Rtype,
signer_name: &Name<Bytes>,
) -> (NsecState, Option<ExtendedError<Vec<u8>>>) {
let (state, ede) = nsec_for_not_exists(target, groups, signer_name);
let ce = match state {
NsecNXState::DoesNotExist(ce) => ce,
NsecNXState::Nothing => return (NsecState::Nothing, ede),
NsecNXState::Exists => {
return (NsecState::Nothing, ede);
}
};
let star_name = match star_closest_encloser(&ce) {
Ok(name) => name,
Err(_) => {
let ede = make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"cannot create wildcard record",
);
return (NsecState::Nothing, ede);
}
};
nsec_for_nodata(&star_name, groups, rtype, signer_name)
}
#[derive(Debug)]
pub enum NsecNXState {
Exists,
DoesNotExist(Name<Bytes>),
Nothing,
}
pub fn nsec_for_not_exists(
target: &Name<Bytes>,
groups: &mut [ValidatedGroup],
signer_name: &Name<Bytes>,
) -> (NsecNXState, Option<ExtendedError<Vec<u8>>>) {
let mut ede = None;
for g in groups.iter() {
let (opt_nsec, new_ede) = get_checked_nsec(g, signer_name);
let nsec = if let Some(nsec) = opt_nsec {
nsec
} else {
if ede.is_none() {
ede = new_ede;
}
continue;
};
let owner = g.owner();
if target.name_eq(&owner) {
return (
NsecNXState::Exists,
make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"Found matching NSEC while trying to proof non-existance",
),
);
}
if !nsec_in_range(target, &owner, nsec.next_name()) {
continue;
}
if nsec.next_name().ends_with(target) {
return (
NsecNXState::Exists,
make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"Found ENT NSEC while trying to proof non-existance",
),
);
}
if target.ends_with(&owner) {
let types = nsec.types();
if types.contains(Rtype::DNAME)
|| (types.contains(Rtype::NS) && !types.contains(Rtype::SOA))
{
return (
NsecNXState::Exists,
make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"Found NSEC with DNAME or delegation while trying to proof non-existance",
),
);
}
}
let ce = nsec_closest_encloser(target, &owner, &nsec);
return (NsecNXState::DoesNotExist(ce), None);
}
(NsecNXState::Nothing, ede)
}
pub fn nsec_for_nxdomain(
target: &Name<Bytes>,
groups: &mut [ValidatedGroup],
signer_name: &Name<Bytes>,
) -> (NsecNXState, Option<ExtendedError<Vec<u8>>>) {
let (state, ede) = nsec_for_not_exists(target, groups, signer_name);
let ce = match state {
NsecNXState::Exists => {
return (NsecNXState::Nothing, ede);
}
NsecNXState::DoesNotExist(ce) => ce,
NsecNXState::Nothing => return (NsecNXState::Nothing, ede),
};
let star_name = match star_closest_encloser(&ce) {
Ok(name) => name,
Err(_) => {
let ede = make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"cannot create wildcard record",
);
return (NsecNXState::Nothing, ede);
}
};
nsec_for_not_exists(&star_name, groups, signer_name)
}
pub fn nsec_in_range<TN>(
target: &Name<Bytes>,
owner: &Name<Bytes>,
next_name: &TN,
) -> bool
where
TN: ToName,
{
if owner < next_name {
target > owner && target < next_name
} else {
target > owner
}
}
type BytesNsec = Nsec<Bytes, ParsedName<Bytes>>;
fn get_checked_nsec(
group: &ValidatedGroup,
signer_name: &Name<Bytes>,
) -> (Option<BytesNsec>, Option<ExtendedError<Vec<u8>>>) {
if group.rtype() != Rtype::NSEC {
return (None, None);
}
let owner = group.owner();
let rrs = group.rr_set();
if rrs.len() != 1 {
return (None, None);
}
let AllRecordData::Nsec(nsec) = rrs[0].data() else {
panic!("NSEC expected");
};
if let ValidationState::Secure = group.state() {
} else {
return (None, None);
};
if group.signer_name() != signer_name {
return (None, None);
}
let opt_closest_encloser = group.closest_encloser();
if let Some(closest_encloser) = opt_closest_encloser {
let star_name = match star_closest_encloser(&closest_encloser) {
Ok(name) => name,
Err(_) => {
return (None, None);
}
};
if owner != star_name {
let ede = make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"NSEC is expanded from wildcard",
);
return (None, ede);
}
}
(Some(nsec.clone()), None)
}
fn nsec_closest_encloser(
target: &Name<Bytes>,
nsec_owner: &Name<Bytes>,
nsec: &Nsec<Bytes, ParsedName<Bytes>>,
) -> Name<Bytes> {
let mut owner_encloser = Name::root(); for n in nsec_owner.iter_suffixes() {
if target.ends_with(&n) {
owner_encloser = n;
break;
}
}
let mut next_encloser: Name<Bytes> = Name::root(); for n in nsec.next_name().iter_suffixes() {
if target.ends_with(&n) {
next_encloser = n.to_name();
break;
}
}
if owner_encloser.label_count() > next_encloser.label_count() {
owner_encloser
} else {
next_encloser
}
}
pub async fn nsec3_for_nodata(
target: &Name<Bytes>,
groups: &mut [ValidatedGroup],
rtype: Rtype,
signer_name: &Name<Bytes>,
nsec3_cache: &Nsec3Cache,
config: &Config,
) -> (Nsec3State, Option<ExtendedError<Vec<u8>>>) {
if rtype == Rtype::DS {
let (state, ede) = nsec3_for_not_exists(
target,
groups,
signer_name,
nsec3_cache,
config,
)
.await;
match state {
Nsec3NXState::DoesNotExist(_) => {
return (Nsec3State::Nothing, ede);
}
Nsec3NXState::DoesNotExistInsecure(_) => {
return (Nsec3State::NoDataInsecure, ede);
}
Nsec3NXState::Insecure => {
return (Nsec3State::NoDataInsecure, ede);
}
Nsec3NXState::Bogus => {
return (Nsec3State::Bogus, ede);
}
Nsec3NXState::Nothing => (), }
}
for g in groups.iter() {
let res_opt_nsec3_hash = get_checked_nsec3(g, signer_name, config);
let (nsec3, ownerhash) = match res_opt_nsec3_hash {
Ok(opt_nsec3_hash) => {
if let Some(nsec3_hash) = opt_nsec3_hash {
nsec3_hash
} else {
continue;
}
}
Err((state, ede)) => {
match state {
ValidationState::Bogus =>
return (Nsec3State::Bogus, ede),
ValidationState::Insecure =>
return (Nsec3State::NoDataInsecure, ede),
ValidationState::Secure
| ValidationState::Indeterminate =>
panic!("get_checked_nsec3 should only return Bogus or Insecure"),
}
}
};
let hash = cached_nsec3_hash(
target,
nsec3.hash_algorithm(),
nsec3.iterations(),
nsec3.salt(),
nsec3_cache,
)
.await;
if ownerhash == hash.as_ref() {
let types = nsec3.types();
if types.contains(rtype) || types.contains(Rtype::CNAME) {
let ede = make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"NSEC3 for NODATA proves requested Rtype or CNAME",
);
return (Nsec3State::Nothing, ede);
}
if rtype == Rtype::DS {
if types.contains(Rtype::NS) && types.contains(Rtype::SOA) {
let ede = make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"NSEC3 from apex for DS",
);
return (Nsec3State::Nothing, ede);
}
} else if types.contains(Rtype::NS) && !types.contains(Rtype::SOA)
{
let ede = make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"NSEC3 from parent for non-DS rtype",
);
return (Nsec3State::Nothing, ede);
}
return (Nsec3State::NoData, None);
}
}
(Nsec3State::Nothing, None)
}
#[derive(Debug)]
pub enum Nsec3State {
NoData,
NoDataInsecure,
Bogus,
Nothing,
}
pub async fn nsec3_for_nodata_wildcard(
target: &Name<Bytes>,
groups: &mut [ValidatedGroup],
rtype: Rtype,
signer_name: &Name<Bytes>,
nsec3_cache: &Nsec3Cache,
config: &Config,
) -> (Nsec3State, Option<ExtendedError<Vec<u8>>>) {
let (state, mut ede) = nsec3_for_not_exists(
target,
groups,
signer_name,
nsec3_cache,
config,
)
.await;
let (ce, secure) = match state {
Nsec3NXState::DoesNotExist(ce) => (ce, true),
Nsec3NXState::DoesNotExistInsecure(ce) => (ce, false),
Nsec3NXState::Bogus => return (Nsec3State::Bogus, ede),
Nsec3NXState::Insecure => return (Nsec3State::NoDataInsecure, ede),
Nsec3NXState::Nothing => return (Nsec3State::Nothing, ede),
};
let star_name = match star_closest_encloser(&ce) {
Ok(name) => name,
Err(_) => {
let ede = make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"cannot create wildcard record",
);
return (Nsec3State::Bogus, ede);
}
};
let (state, nodata_ede) = nsec3_for_nodata(
&star_name,
groups,
rtype,
signer_name,
nsec3_cache,
config,
)
.await;
if ede.is_none() {
ede = nodata_ede;
}
match state {
Nsec3State::NoData => {
if secure {
(Nsec3State::NoData, ede)
} else {
(Nsec3State::NoDataInsecure, ede)
}
}
Nsec3State::Nothing
| Nsec3State::Bogus
| Nsec3State::NoDataInsecure => (state, ede),
}
}
#[derive(Debug)]
pub enum Nsec3NXState {
DoesNotExist(Name<Bytes>),
DoesNotExistInsecure(Name<Bytes>),
Bogus,
Insecure,
Nothing,
}
pub async fn nsec3_for_not_exists(
target: &Name<Bytes>,
groups: &mut [ValidatedGroup],
signer_name: &Name<Bytes>,
nsec3_cache: &Nsec3Cache,
config: &Config,
) -> (Nsec3NXState, Option<ExtendedError<Vec<u8>>>) {
let mut names = VecDeque::new();
for n in target.iter_suffixes() {
if !n.ends_with(signer_name) {
break;
}
names.push_front(n);
}
let mut maybe_ce = signer_name.clone();
let mut maybe_ce_exists = false;
'next_name: for n in names {
if n == signer_name {
maybe_ce = n;
maybe_ce_exists = true;
continue;
}
for g in groups.iter() {
let res_opt_nsec3_hash =
get_checked_nsec3(g, signer_name, config);
let (nsec3, ownerhash) = match res_opt_nsec3_hash {
Ok(opt_nsec3_hash) => {
if let Some(nsec3_hash) = opt_nsec3_hash {
nsec3_hash
} else {
continue;
}
}
Err((ValidationState::Bogus, ede)) => {
return (Nsec3NXState::Bogus, ede)
}
Err((ValidationState::Insecure, ede)) => {
return (Nsec3NXState::Insecure, ede)
}
Err(_) => panic!(
"get_checked_nsec3 should on return Bogus or Insecure"
),
};
let hash = cached_nsec3_hash(
&n,
nsec3.hash_algorithm(),
nsec3.iterations(),
nsec3.salt(),
nsec3_cache,
)
.await;
if ownerhash == hash.as_ref() {
let types = nsec3.types();
if types.contains(Rtype::DNAME)
|| (types.contains(Rtype::NS)
&& !types.contains(Rtype::SOA))
{
return (
Nsec3NXState::Nothing,
make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"Found NSEC3 with DNAME or delegation while trying to proof non-existance",
),
);
}
maybe_ce = n;
maybe_ce_exists = true;
continue 'next_name;
}
if nsec3_in_range(hash.as_ref(), &ownerhash, nsec3.next_owner()) {
if maybe_ce_exists {
if nsec3.opt_out() {
return (
Nsec3NXState::DoesNotExistInsecure(maybe_ce),
make_ede(
ExtendedErrorCode::OTHER,
"NSEC3 with Opt-Out",
),
);
}
return (Nsec3NXState::DoesNotExist(maybe_ce), None);
}
maybe_ce_exists = false;
continue 'next_name;
}
}
maybe_ce_exists = false;
}
(
Nsec3NXState::Nothing,
make_ede(ExtendedErrorCode::OTHER, "No NSEC3 proves non-existance"),
)
}
#[derive(Debug)]
pub enum Nsec3NXStateNoCE {
DoesNotExist,
DoesNotExistInsecure,
Nothing,
Bogus,
}
pub async fn nsec3_for_not_exists_no_ce(
target: &Name<Bytes>,
groups: &mut [ValidatedGroup],
signer_name: &Name<Bytes>,
nsec3_cache: &Nsec3Cache,
config: &Config,
) -> (Nsec3NXStateNoCE, Option<ExtendedError<Vec<u8>>>) {
for g in groups.iter() {
let res_opt_nsec3_hash = get_checked_nsec3(g, signer_name, config);
let (nsec3, ownerhash) = match res_opt_nsec3_hash {
Ok(opt_nsec3_hash) => {
if let Some(nsec3_hash) = opt_nsec3_hash {
nsec3_hash
} else {
continue;
}
}
Err((state, ede)) => {
match state {
ValidationState::Bogus =>
return (Nsec3NXStateNoCE::Bogus, ede),
ValidationState::Insecure =>
return (Nsec3NXStateNoCE::DoesNotExistInsecure, ede),
ValidationState::Secure
| ValidationState::Indeterminate =>
panic!("get_checked_nsec3 should only return Bogus or Insecure"),
}
}
};
let hash = cached_nsec3_hash(
target,
nsec3.hash_algorithm(),
nsec3.iterations(),
nsec3.salt(),
nsec3_cache,
)
.await;
if nsec3_in_range(hash.as_ref(), &ownerhash, nsec3.next_owner()) {
if nsec3.opt_out() {
return (Nsec3NXStateNoCE::DoesNotExistInsecure, None);
}
return (Nsec3NXStateNoCE::DoesNotExist, None);
}
}
(Nsec3NXStateNoCE::Nothing, None)
}
pub async fn nsec3_for_nxdomain(
target: &Name<Bytes>,
groups: &mut [ValidatedGroup],
signer_name: &Name<Bytes>,
nsec3_cache: &Nsec3Cache,
config: &Config,
) -> (Nsec3NXState, Option<ExtendedError<Vec<u8>>>) {
let (state, mut ede) = nsec3_for_not_exists(
target,
groups,
signer_name,
nsec3_cache,
config,
)
.await;
let (ce, secure) = match state {
Nsec3NXState::DoesNotExist(ce) => (ce, true),
Nsec3NXState::DoesNotExistInsecure(ce) => (ce, false),
Nsec3NXState::Bogus
| Nsec3NXState::Insecure
| Nsec3NXState::Nothing => return (state, ede),
};
let star_name = match star_closest_encloser(&ce) {
Ok(name) => name,
Err(_) => {
let ede = make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"cannot create wildcard record",
);
return (Nsec3NXState::Bogus, ede);
}
};
let (state, new_ede) = nsec3_for_not_exists_no_ce(
&star_name,
groups,
signer_name,
nsec3_cache,
config,
)
.await;
if ede.is_none() {
ede = new_ede;
}
match state {
Nsec3NXStateNoCE::DoesNotExist => {
if secure {
(Nsec3NXState::DoesNotExist(ce), None)
} else {
(Nsec3NXState::DoesNotExistInsecure(ce), ede)
}
}
Nsec3NXStateNoCE::DoesNotExistInsecure => {
(Nsec3NXState::DoesNotExistInsecure(ce), ede)
}
Nsec3NXStateNoCE::Bogus => (Nsec3NXState::Bogus, ede),
Nsec3NXStateNoCE::Nothing => (Nsec3NXState::Nothing, ede),
}
}
#[derive(Eq, Hash, PartialEq)]
struct Nsec3CacheKey(Name<Bytes>, Nsec3HashAlgorithm, u16, Nsec3Salt<Bytes>);
pub struct Nsec3Cache {
cache: Cache<Nsec3CacheKey, Arc<OwnerHash<Vec<u8>>>>,
}
impl Nsec3Cache {
pub fn new(size: u64) -> Self {
Self {
cache: Cache::new(size),
}
}
}
pub fn supported_nsec3_hash(h: Nsec3HashAlgorithm) -> bool {
h == Nsec3HashAlgorithm::SHA1
}
pub async fn cached_nsec3_hash(
owner: &Name<Bytes>,
algorithm: Nsec3HashAlgorithm,
iterations: u16,
salt: &Nsec3Salt<Bytes>,
cache: &Nsec3Cache,
) -> Arc<OwnerHash<Vec<u8>>> {
let key =
Nsec3CacheKey(owner.clone(), algorithm, iterations, salt.clone());
if let Some(ce) = cache.cache.get(&key).await {
return ce;
}
let hash = nsec3_hash(owner, algorithm, iterations, salt).unwrap();
let hash = Arc::new(hash);
cache.cache.insert(key, hash.clone()).await;
hash
}
pub fn nsec3_label_to_hash(
label: &Label,
) -> Result<OwnerHash<Vec<u8>>, Utf8Error> {
let label_str = std::str::from_utf8(label.as_ref())?;
Ok(OwnerHash::<Vec<u8>>::from_str(label_str).expect("should not fail"))
}
pub fn nsec3_in_range<O1, O2, O3>(
targethash: &OwnerHash<O1>,
ownerhash: &OwnerHash<O2>,
nexthash: &OwnerHash<O3>,
) -> bool
where
O1: Octets,
O2: Octets,
O3: Octets,
{
if *nexthash > ownerhash {
ownerhash < targethash && targethash < nexthash
} else {
ownerhash < targethash || targethash < nexthash
}
}
#[allow(clippy::type_complexity)]
fn get_checked_nsec3(
group: &ValidatedGroup,
signer_name: &Name<Bytes>,
config: &Config,
) -> Result<
Option<(Nsec3<Bytes>, OwnerHash<Vec<u8>>)>,
(ValidationState, Option<ExtendedError<Vec<u8>>>),
> {
let rrs = group.rr_set();
if rrs.len() != 1 {
return Ok(None);
}
let AllRecordData::Nsec3(nsec3) = rrs[0].data() else {
return Ok(None);
};
if let ValidationState::Secure = group.state() {
} else {
return Ok(None);
};
if group.signer_name() != signer_name {
return Ok(None);
}
if !supported_nsec3_hash(nsec3.hash_algorithm()) {
return Ok(None);
}
let iterations = nsec3.iterations();
if iterations > config.nsec3_iter_insecure()
|| iterations > config.nsec3_iter_bogus()
{
if iterations > config.nsec3_iter_bogus() {
return Err((
ValidationState::Bogus,
make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"NSEC3 with too high iteration count",
),
));
}
return Err((
ValidationState::Insecure,
make_ede(
ExtendedErrorCode::OTHER,
"NSEC3 with too high iteration count",
),
));
}
let ownerhash = match nsec3_label_to_hash(group.owner().first()) {
Ok(hash) => hash,
Err(_) => {
return Err((
ValidationState::Bogus,
make_ede(
ExtendedErrorCode::DNSSEC_BOGUS,
"NSEC3 with bad owner hash",
),
))
}
};
if ownerhash.as_slice().len() != nsec3.next_owner().as_slice().len() {
return Ok(None);
}
Ok(Some((nsec3.clone(), ownerhash)))
}