///|
/// Complete bounded NSEC closest-encloser and wildcard denial.
/// Requires an explicit exact-owner closest-encloser proof (no inferred empty nonterminals).
pub fn nsec_nxdomain(
proofs : Array[AuthenticatedSet],
zone : Name,
name : Name,
now : UInt32,
limits : Limits,
) -> Result[Denial, DnsError] {
match limits.validate() {
Ok(_) => ()
Err(e) => return Err(e)
}
match valid_query(zone, name, 1) {
Ok(_) => ()
Err(e) => return Err(e)
}
if name == zone {
return Err(Bogus("zone apex exists"))
}
if name.label_count() - zone.label_count() > limits.max_chain {
return Err(ResourceLimit("NSEC ancestor depth"))
}
if proofs.is_empty() || proofs.length() > limits.max_proofs {
return Err(ResourceLimit("NSEC proof count"))
}
let decoded : Array[(Nsec, UInt32)] = []
for proof in proofs {
let rr = match ensure_proof(proof, zone, 47, now) {
Ok(r) => r
Err(e) => return Err(e)
}
let n = match Nsec::parse(rr) {
Ok(n) => n
Err(e) => return Err(e)
}
if !n.next.is_subdomain(zone) {
return Err(Bogus("NSEC successor outside signer zone"))
}
if n.owner == name {
return Err(Bogus("NXDOMAIN query owner exists"))
}
if name.is_subdomain(n.owner) &&
(
n.bitmap.contains(39) ||
n.bitmap.contains(5) ||
(n.bitmap.contains(2) && !n.bitmap.contains(6))
) {
return Err(Indeterminate("query is beneath alias or delegation boundary"))
}
let ttl = match proof.remaining(now) {
Ok(n) => n
Err(e) => return Err(e)
}
decoded.push((n, ttl))
}
let mut closest : Name? = None
let mut closest_ttl = 0U
let mut candidate = name.parent()
while candidate is Some(c) {
if !c.is_subdomain(zone) {
break
}
for entry in decoded {
if entry.0.owner == c {
closest = Some(c)
closest_ttl = entry.1
break
}
}
if closest is Some(_) {
break
}
candidate = c.parent()
}
let ce = match closest {
Some(c) => c
None => return Err(Indeterminate("missing closest-encloser NSEC"))
}
let next = match next_closer(name, ce) {
Ok(n) => n
Err(e) => return Err(e)
}
let wildcard = match ce.wildcard() {
Ok(n) => n
Err(e) => return Err(e)
}
let mut next_ttl : UInt32? = None
let mut wildcard_ttl : UInt32? = None
for entry in decoded {
if entry.0.owner == wildcard {
return Err(Bogus("closest-encloser wildcard exists"))
}
if entry.0.covers(next) {
next_ttl = Some(entry.1)
}
if entry.0.covers(wildcard) {
wildcard_ttl = Some(entry.1)
}
}
match (next_ttl, wildcard_ttl) {
(Some(a), Some(b)) =>
Ok({ kind: NxDomain, lifetime: min_ttl(closest_ttl, min_ttl(a, b)), })
_ =>
Err(Indeterminate("need both next-closer and wildcard interval coverage"))
}
}
///|
fn next_closer(name : Name, closest : Name) -> Result[Name, DnsError] {
if name == closest || !name.is_subdomain(closest) {
return Err(Bogus("invalid closest encloser"))
}
let mut next = name
while next.parent() is Some(p) {
if p == closest {
return Ok(next)
}
next = p
}
Err(Bogus("closest encloser not an ancestor"))
}
///|
fn min_ttl(a : UInt32, b : UInt32) -> UInt32 {
if a < b {
a
} else {
b
}
}