+ if res.is_empty() { return Err(ValidationError::Invalid) }
+ if latest_inception >= earliest_expiry { return Err(ValidationError::Invalid) }
+
+ // First sort the proofs we're looking for so that the retains below avoid shifting.
+ rrs_needing_non_existence_proofs.sort_unstable_by(nsec_ord_extra);
+ 'proof_search_loop: while let Some((name, zone, ty)) = rrs_needing_non_existence_proofs.pop() {
+ let nsec_search = res.iter()
+ .filter_map(|rr| if let RR::NSec(nsec) = rr { Some(nsec) } else { None })
+ .filter(|nsec| nsec.name.ends_with(zone.as_str()));
+ for nsec in nsec_search {
+ let name_matches = nsec.name.as_str() == name;
+ let name_contained = nsec_ord(&nsec.name, &name) != Ordering::Greater &&
+ nsec_ord(&nsec.next_name, name) == Ordering::Greater;
+ if (name_matches && !nsec.types.contains_type(ty)) || name_contained {
+ rrs_needing_non_existence_proofs
+ .retain(|(n, _, t)| *n != name || (name_matches && nsec.types.contains_type(*t)));
+ continue 'proof_search_loop;
+ }
+ }
+ let nsec3_search = res.iter()
+ .filter_map(|rr| if let RR::NSec3(nsec3) = rr { Some(nsec3) } else { None })
+ .filter(|nsec3| nsec3.name.ends_with(zone.as_str()));
+
+ // Because we will only ever have two entries, a Vec is simpler than a map here.
+ let mut nsec3params_to_name_hash = Vec::new();
+ for nsec3 in nsec3_search.clone() {
+ if nsec3.hash_iterations > 2500 {
+ // RFC 5115 places different limits on the iterations based on the signature key
+ // length, but we just use 2500 for all key types
+ continue;
+ }
+ if nsec3.hash_algo != 1 { continue; }
+ if nsec3params_to_name_hash.iter()
+ .any(|(iterations, salt, _)| *iterations == nsec3.hash_iterations && *salt == &nsec3.salt)
+ { continue; }
+
+ let mut hasher = crypto::hash::Hasher::sha1();
+ write_name(&mut hasher, &name);
+ hasher.update(&nsec3.salt);
+ for _ in 0..nsec3.hash_iterations {
+ let res = hasher.finish();
+ hasher = crypto::hash::Hasher::sha1();
+ hasher.update(res.as_ref());
+ hasher.update(&nsec3.salt);
+ }
+ nsec3params_to_name_hash.push((nsec3.hash_iterations, &nsec3.salt, hasher.finish()));
+
+ if nsec3params_to_name_hash.len() >= 2 {
+ // We only allow for up to two sets of hash_iterations/salt per zone. Beyond that
+ // we assume this is a malicious DoSing proof and give up.
+ break;
+ }
+ }
+ for nsec3 in nsec3_search {
+ if nsec3.flags != 0 {
+ // This is an opt-out NSEC3 (or has unknown flags set). Thus, we shouldn't rely on
+ // it as proof that some record doesn't exist.
+ continue;
+ }
+ if nsec3.hash_algo != 1 { continue; }
+ let name_hash = if let Some((_, _, hash)) =
+ nsec3params_to_name_hash.iter()
+ .find(|(iterations, salt, _)| *iterations == nsec3.hash_iterations && *salt == &nsec3.salt)
+ {
+ hash
+ } else { continue };
+
+ let (start_hash_base32, _) = nsec3.name.split_once(".")
+ .unwrap_or_else(|| { debug_assert!(false); ("", "")});
+ let start_hash = if let Ok(start_hash) = base32::decode(start_hash_base32) {
+ start_hash
+ } else { continue };
+ if start_hash.len() != 20 || nsec3.next_name_hash.len() != 20 { continue; }
+
+ let hash_matches = &start_hash[..] == name_hash.as_ref();
+ let hash_contained =
+ &start_hash[..] <= name_hash.as_ref() && &nsec3.next_name_hash[..] > name_hash.as_ref();
+ if (hash_matches && !nsec3.types.contains_type(ty)) || hash_contained {
+ rrs_needing_non_existence_proofs
+ .retain(|(n, _, t)| *n != name || (hash_matches && nsec3.types.contains_type(*t)));
+ continue 'proof_search_loop;
+ }
+ }
+ return Err(ValidationError::Invalid);