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

resolver: add CD bit handling tests #56

Merged
merged 2 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 fixtures;
mod rfc4035;
mod scenarios;
50 changes: 50 additions & 0 deletions packages/conformance-tests/src/resolver/dnssec/fixtures.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::net::Ipv4Addr;

use base64::prelude::*;
use dns_test::{
name_server::{Graph, NameServer, Sign},
record::Record,
Network, Resolver, Result, FQDN,
};

pub fn bad_signature_in_leaf_nameserver(
leaf_fqdn: &FQDN,
leaf_ipv4_addr: Ipv4Addr,
) -> Result<(Resolver, Graph)> {
assert_eq!(Some(FQDN::NAMESERVERS), leaf_fqdn.parent());

let network = Network::new()?;

let mut leaf_ns = NameServer::new(&dns_test::PEER, FQDN::NAMESERVERS, &network)?;
leaf_ns.add(Record::a(leaf_fqdn.clone(), leaf_ipv4_addr));

let graph = Graph::build(
leaf_ns,
Sign::AndAmend(&|zone, records| {
if zone == &FQDN::NAMESERVERS {
let mut modified = 0;
for record in records {
if let Record::RRSIG(rrsig) = record {
if rrsig.fqdn == *leaf_fqdn {
let mut signature = BASE64_STANDARD.decode(&rrsig.signature).unwrap();
let last = signature.last_mut().expect("empty signature");
*last = !*last;

rrsig.signature = BASE64_STANDARD.encode(&signature);
modified += 1;
}
}
}

assert_eq!(modified, 1, "sanity check");
}
}),
)?;

let trust_anchor = graph.trust_anchor.as_ref().unwrap();
let resolver = Resolver::new(&network, graph.root.clone())
.trust_anchor(trust_anchor)
.start(&dns_test::SUBJECT)?;

Ok((resolver, graph))
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod section_3_2_2;

use dns_test::{
client::{Client, DigSettings},
name_server::NameServer,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::net::Ipv4Addr;

use dns_test::{
client::{Client, DigSettings},
name_server::NameServer,
record::RecordType,
zone_file::Root,
Network, Resolver, Result, FQDN,
};

use crate::resolver::dnssec::fixtures;

#[test]
fn copies_cd_bit_from_query_to_response() -> Result<()> {
let network = &Network::new()?;
let ns = NameServer::new(&dns_test::PEER, FQDN::ROOT, network)?.start()?;
let resolver = Resolver::new(network, Root::new(ns.fqdn().clone(), ns.ipv4_addr()))
.start(&dns_test::SUBJECT)?;

let client = Client::new(network)?;
let settings = *DigSettings::default().checking_disabled().recurse();
let ans = client.dig(settings, resolver.ipv4_addr(), RecordType::SOA, &FQDN::ROOT)?;

assert!(ans.flags.checking_disabled);

Ok(())
}

#[test]
fn if_cd_bit_is_set_then_respond_with_data_that_fails_authentication() -> Result<()> {
let needle_fqdn = FQDN("example.nameservers.com.")?;
let needle_ipv4_addr = Ipv4Addr::new(1, 2, 3, 4);

let (resolver, _graph) =
fixtures::bad_signature_in_leaf_nameserver(&needle_fqdn, needle_ipv4_addr)?;

let resolver_addr = resolver.ipv4_addr();

let client = Client::new(resolver.network())?;

let settings = *DigSettings::default()
.recurse()
.authentic_data()
.checking_disabled();
let output = client.dig(settings, resolver_addr, RecordType::A, &needle_fqdn)?;

assert!(output.status.is_noerror());
assert!(!output.flags.authenticated_data);

let [record] = output.answer.try_into().unwrap();
let record = record.try_into_a().unwrap();

assert_eq!(needle_fqdn, record.fqdn);
assert_eq!(needle_ipv4_addr, record.ipv4_addr);

Ok(())
}
66 changes: 11 additions & 55 deletions packages/conformance-tests/src/resolver/dnssec/scenarios/bogus.rs
Original file line number Diff line number Diff line change
@@ -1,73 +1,29 @@
use std::net::Ipv4Addr;

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

use crate::resolver::dnssec::fixtures;

// TODO find out which RFC section states this
#[ignore]
#[test]
fn bad_signature_in_leaf_nameserver() -> Result<()> {
let expected_ipv4_addr = Ipv4Addr::new(1, 2, 3, 4);
fn if_cd_bit_is_clear_and_data_is_not_authentic_then_respond_with_servfail() -> Result<()> {
let needle_fqdn = FQDN("example.nameservers.com.")?;
let needle_ipv4_addr = Ipv4Addr::new(1, 2, 3, 4);

let network = Network::new()?;

let mut leaf_ns = NameServer::new(&dns_test::PEER, FQDN::NAMESERVERS, &network)?;
leaf_ns.add(Record::a(needle_fqdn.clone(), expected_ipv4_addr));

let Graph {
nameservers: _nameservers,
root,
trust_anchor,
} = Graph::build(
leaf_ns,
Sign::AndAmend(&|zone, records| {
if zone == &FQDN::NAMESERVERS {
let mut modified = 0;
for record in records {
if let Record::RRSIG(rrsig) = record {
if rrsig.fqdn == needle_fqdn {
let mut signature = BASE64_STANDARD.decode(&rrsig.signature).unwrap();
let last = signature.last_mut().expect("empty signature");
*last = !*last;

rrsig.signature = BASE64_STANDARD.encode(&signature);
modified += 1;
}
}
}

assert_eq!(modified, 1, "sanity check");
}
}),
)?;
let (resolver, _graph) =
fixtures::bad_signature_in_leaf_nameserver(&needle_fqdn, needle_ipv4_addr)?;

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 client = Client::new(resolver.network())?;

let mut settings = *DigSettings::default().recurse().authentic_data();
let settings = *DigSettings::default().recurse().authentic_data();
let output = client.dig(settings, resolver_addr, RecordType::A, &needle_fqdn)?;

// the resolver will try to validate the chain of trust; the validation fails so it responds
// with SERVFAIL
assert!(output.status.is_servfail());

// avoids a SERVFAIL response
settings.checking_disabled();

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

// when the CD (Checking Disabled) bit is set the server won't respond with SERVFAIL on
// validation errors. the outcome of the validation process is reported in the AD bit
assert!(output.status.is_noerror());
assert!(!output.flags.authenticated_data);

Ok(())
}
4 changes: 4 additions & 0 deletions packages/dns-test/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ impl Resolver {
self.container.eavesdrop()
}

pub fn network(&self) -> &Network {
self.container.network()
}

pub fn container_id(&self) -> &str {
self.container.id()
}
Expand Down
1 change: 1 addition & 0 deletions packages/dns-test/src/zone_file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ impl FromStr for ZoneFile {
}

/// A root (server) hint
#[derive(Clone)]
pub struct Root {
pub ipv4_addr: Ipv4Addr,
pub ns: FQDN,
Expand Down