Skip to content
This repository has been archived by the owner on Jun 7, 2024. It is now read-only.

Commit

Permalink
Add a non-existance test using NSEC3
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdrz committed May 18, 2024
1 parent fe3961f commit 42d55e2
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/conformance-tests/src/resolver/dnssec.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! DNSSEC functionality
mod rfc4035;
mod rfc5155;
mod scenarios;
181 changes: 181 additions & 0 deletions packages/conformance-tests/src/resolver/dnssec/rfc5155.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use std::net::Ipv4Addr;

use dns_test::{
client::{Client, DigSettings},
name_server::{Graph, NameServer, Sign},
record::{Record, RecordType, NSEC3},
Network, Resolver, Result, FQDN,
};

/// Find the index of the element immediately previous to `needle` in `haystack`.
fn find_prev(needle: &str, haystack: &Vec<&str>) -> usize {
let (Ok(index) | Err(index)) = haystack.binary_search(&needle);
match index {
0 => haystack.len() - 1,
index => index - 1,
}
}

/// Find the index of the element immediately next to `needle` in `haystack`.
fn find_next(needle: &str, haystack: &Vec<&str>) -> usize {
let (Ok(index) | Err(index)) = haystack.binary_search(&needle);
(index + 1) % haystack.len()
}

/// Return `true` if `record` convers `hash`. This is, if `hash` falls in between the owner of
/// `record` and the next hashed owner name of `record`.
fn covers(record: &NSEC3, hash: &str) -> bool {
record.next_hashed_owner_name.as_str() > hash
&& record.fqdn.labels().next().unwrap().to_uppercase().as_str() < hash
}

#[ignore]
#[test]
fn proof_of_non_existence_with_nsec3_records() -> Result<()> {
let peer = dns_test::peer();
let network = Network::new()?;

let alice_fqdn = FQDN("alice.nameservers.com.")?;
let bob_fqdn = FQDN("bob.nameservers.com.")?;
let charlie_fqdn = FQDN("charlie.nameservers.com.")?;

let bob_hash = "9AU9KOU2HVABPTPB7D3AQBH57QPLNDI6"; /* bob.namesevers.com. */
let wildcard_hash = "M417220KKVJDM7CHD6QVUV4TGHDU2N2K"; /* *.nameservers.com */
let nameservers_hash = "7M2FCI51VUC2E5RIBDPTVJ6S08EMMR3O"; /* nameservers.com. */

let mut leaf_ns = NameServer::new(&peer, FQDN::NAMESERVERS, &network)?;
leaf_ns
.add(Record::a(alice_fqdn.clone(), Ipv4Addr::new(1, 2, 3, 4)))
.add(Record::a(charlie_fqdn.clone(), Ipv4Addr::new(1, 2, 3, 5)));

let Graph {
nameservers,
root,
trust_anchor,
} = Graph::build(leaf_ns, Sign::Yes)?;

// This is the sorted list of hashes that can be proven to exist by the name servers.
let hashes = {
// These are the hashes that we statically know they exist.
let mut hashes = vec![
nameservers_hash,
"8C538GR0B1FT11G01UI8THM4IPM64NUC", /* charlie.nameservers.com. */
"PQVTTO5UIDVCHKP34DDQ3LIIH7TQED20", /* alice.nameservers.com. */
];

// Include the hashes of the nameservers dynamically as they change between executions.
for ns in &nameservers {
let hash = match ns.fqdn().as_str() {
"primary0.nameservers.com." => "E05P5R80N590NS9PP24QOOFHRT605T8A",
"primary1.nameservers.com." => "C1JIVO7U1IH8JFK6BMU60V65S5FVEFT2",
"primary2.nameservers.com." => "NJ1OLIA8A6HTNBMC20ATDDIDTA42AI8V",
"primary3.nameservers.com." => "9JMUC5ADM6MUKUN4NTBMR19C1030SRM0",
"primary4.nameservers.com." => "0RM17SJJI0C51PADDIFG9LI8K2S04EE9",
"primary5.nameservers.com." => "546PPSKSPN8DOKTTA9MASB0TM06I72GD",
"primary6.nameservers.com." => "40PTL9S01ERIF3E05RERHM419K0465GB",
ns => panic!("Unexpected nameserver: {ns}"),
};

hashes.push(hash);
}

// Sort the hashes
hashes.sort();
hashes
};

let trust_anchor = &trust_anchor.unwrap();
let resolver = Resolver::new(&network, root)
.trust_anchor(trust_anchor)
.start(&dns_test::subject())?;
let resolver_addr = resolver.ipv4_addr();

let client = Client::new(&network)?;
let settings = *DigSettings::default().recurse().authentic_data().dnssec();

let output = client.dig(settings, resolver_addr, RecordType::MX, &bob_fqdn)?;

assert!(output.status.is_nxdomain());

let nsec3_rrs = output
.authority
.into_iter()
.filter_map(|record| {
if let Record::NSEC3(r) = record {
Some(r)
} else {
None
}
})
.collect::<Vec<_>>();

// Closest encloser RR: Must match the closest encloser of bob.nameservers.com.
//
// The closest encloser must be nameservers.com. as it is the closest existing ancestor of
// bob.nameservers.com.
let closest_encloser_fqdn = FQDN(nameservers_hash.to_lowercase() + ".nameservers.com.")?;
let closest_encloser_rr = nsec3_rrs
.iter()
.find(|record| record.fqdn == closest_encloser_fqdn)
.expect("Closest encloser RR was not found");

// Check that the next hashed owner name of the record is the hash immediately next to the hash
// of nameservers.com.
let expected = hashes[find_next(nameservers_hash, &hashes)];
let found = &closest_encloser_rr.next_hashed_owner_name;
assert_eq!(expected, found);

// Next closer name RR: Must cover the next closer name of bob.nameservers.com.
//
// The next closer name of bob.nameservers.com. is bob.nameservers.com. as it is the name one
// label longer than nameservers.com.
let next_closer_name_rr = nsec3_rrs
.iter()
.find(|record| covers(record, bob_hash))
.expect("Closest encloser RR was not found");

let index = find_prev(bob_hash, &hashes);

// Check that the owner hash of record is the hash immediately previous to the hash of
// bob.nameservers.com.
let expected = hashes[index];
let found = next_closer_name_rr
.fqdn
.labels()
.next()
.unwrap()
.to_uppercase();
assert_eq!(expected, found);

// Check that the next hashed owner name of the record is the hash immediately next to the
// owner hash.
let expected = hashes[(index + 1) % hashes.len()];
let found = &next_closer_name_rr.next_hashed_owner_name;
assert_eq!(expected, found);

// Wildcard at the closet encloser RR: Must cover the wildcard at the closest encloser of
// bob.nameservers.com.
//
// The wildcard at the closest encloser of bob.nameservers.com. is *.nameservers.com. as it is
// the wildcard at nameservers.com.
let wildcard_rr = nsec3_rrs
.iter()
.find(|record| covers(record, wildcard_hash))
.expect("Wildcard RR was not found");

let index = find_prev(wildcard_hash, &hashes);

// Check that the owner hash of record is the hash immediately previous to the hash of
// *.nameservers.com.
let expected = hashes[index];
let found = wildcard_rr.fqdn.labels().next().unwrap().to_uppercase();
assert_eq!(expected, found);

// Check that the next hashed owner name of the record is the hash immediately next to the
// owner hash.
let expected = hashes[(index + 1) % hashes.len()];
let found = &wildcard_rr.next_hashed_owner_name;
assert_eq!(expected, found);

Ok(())
}
4 changes: 4 additions & 0 deletions packages/dns-test/src/fqdn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ impl FQDN {
.filter(|label| !label.is_empty())
.count()
}

pub fn labels(&self) -> impl Iterator<Item = &str> {
self.inner.split('.')
}
}

impl FromStr for FQDN {
Expand Down

0 comments on commit 42d55e2

Please sign in to comment.