Skip to content

Commit 2014166

Browse files
committed
Sign BIND to test DNSSEC
1 parent 14de3e3 commit 2014166

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+454
-315
lines changed

.clang-tidy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Checks: >
77
modernize-*,
88
-modernize-use-trailing-return-type,
99
-modernize-avoid-c-arrays,
10+
-modernize-raw-string-literal,
1011
performance-*,
1112
-performance-enum-size,
1213
portability-*,

src/main.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <cstdlib>
22
#include <memory>
3+
#include <optional>
34
#include <print>
45
#include <string>
56
#include "dns.hh"
@@ -85,9 +86,14 @@ int main(int argc, char **argv) {
8586
return EXIT_FAILURE;
8687
}
8788

89+
std::optional<NameserverConfig> nameserver = std::nullopt;
90+
if (result.contains("server")) {
91+
nameserver = NameserverConfig{.address = result["server"].as<std::string>()};
92+
}
93+
8894
Resolver resolver{{
8995
.timeout_ms = result["timeout"].as<uint64_t>() * 1000,
90-
.nameserver = result["server"].as_optional<std::string>(),
96+
.nameserver = nameserver,
9197
.use_root_nameservers = result["use-root"].as<bool>(),
9298
.use_resolve_config = result["use-config"].as<bool>(),
9399
.port = result["port"].as<uint16_t>(),

src/resolve.cc

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ struct Nameserver {
2626
};
2727

2828
struct Zone {
29-
// Do not use the zone whose nameserver is being resolved.
3029
bool is_being_resolved{false};
3130
std::string domain;
3231
bool enable_edns;
@@ -195,11 +194,11 @@ std::shared_ptr<Zone> Resolver::SafetyBelt::next() {
195194
std::queue<std::shared_ptr<Zone>> Resolver::init_safety_belt(const ResolverConfig &config) const {
196195
std::queue<std::shared_ptr<Zone>> zones;
197196

198-
if (dnssec != FeatureState::Require) {
199-
// Only the root zone can be used with DNSSEC because it is the only trust anchor.
200-
if (config.nameserver.has_value()) zones.push(new_zone_from_nameserver(config.nameserver.value()));
201-
if (config.use_resolve_config) zones.push(load_resolve_config());
197+
if (config.nameserver.has_value()) {
198+
auto zone = new_zone_from_config(config.nameserver.value());
199+
if (zone->enable_dnssec || dnssec != FeatureState::Require) zones.push(std::move(zone));
202200
}
201+
if (config.use_resolve_config && dnssec != FeatureState::Require) zones.push(load_resolve_config());
203202
if (config.use_root_nameservers) zones.push(new_root_zone());
204203

205204
if (zones.empty()) throw std::runtime_error("No nameserver is specified");
@@ -255,13 +254,18 @@ std::shared_ptr<Zone> Resolver::load_resolve_config() const {
255254
return zone;
256255
}
257256

258-
std::shared_ptr<Zone> Resolver::new_zone_from_nameserver(const std::string &address_or_domain) const {
259-
auto zone = new_zone(".", false);
257+
std::shared_ptr<Zone> Resolver::new_zone_from_config(const NameserverConfig &config) const {
258+
auto zone_domain = config.zone_domain.has_value() ? fully_qualify_domain(config.zone_domain.value()) : ".";
259+
bool enable_dnssec = config.zone_domain.has_value() && (!config.dss.empty() || !config.dnskeys.empty());
260+
auto zone = new_zone(zone_domain, enable_dnssec);
261+
zone->dss = config.dss;
262+
zone->dnskeys = config.dnskeys;
263+
260264
in_addr_t ip_address;
261-
if (inet_pton(AF_INET, address_or_domain.c_str(), &ip_address) == 1) {
265+
if (inet_pton(AF_INET, config.address.c_str(), &ip_address) == 1) {
262266
zone->add_nameserver(ip_address);
263267
} else {
264-
zone->add_nameserver(address_or_domain);
268+
zone->add_nameserver(fully_qualify_domain(config.address));
265269
}
266270
return zone;
267271
}
@@ -343,17 +347,17 @@ bool Resolver::authenticate_rrset(const std::vector<RR> &rrset, RRType rr_type,
343347
const Zone &zone) const {
344348
if (rrset.empty()) return true;
345349

346-
if (rr_type != RRType::DNSKEY) {
347-
return dnssec::authenticate_rrset(rrset, rrsigs, zone.dnskeys, nsec3_rrset, nsec_rrset, zone.domain);
348-
}
350+
if (rr_type == RRType::DNSKEY && zone.dnskeys.empty()) {
351+
if (!zone.dss.empty()) {
352+
return dnssec::authenticate_delegation(rrset, zone.dss, rrsigs, nsec3_rrset, nsec_rrset, zone.domain);
353+
}
349354

350-
if (!zone.dss.empty()) {
351-
return dnssec::authenticate_delegation(rrset, zone.dss, rrsigs, nsec3_rrset, nsec_rrset, zone.domain);
355+
// There is no secure delegation, so just verify that the RRSIG was signed with one of these DNSKEYs.
356+
auto dnskeys = rrset_to_data<DNSKEY>(rrset);
357+
return dnssec::authenticate_rrset(rrset, rrsigs, dnskeys, nsec3_rrset, nsec_rrset, zone.domain);
352358
}
353359

354-
// There is no secure delegation, so just verify that the RRSIG was signed with one of these DNSKEYs.
355-
auto dnskeys = rrset_to_data<DNSKEY>(rrset);
356-
return dnssec::authenticate_rrset(rrset, rrsigs, dnskeys, nsec3_rrset, nsec_rrset, zone.domain);
360+
return dnssec::authenticate_rrset(rrset, rrsigs, zone.dnskeys, nsec3_rrset, nsec_rrset, zone.domain);
357361
}
358362

359363
std::vector<RR> Resolver::get_unauthenticated_rrset(std::vector<RR> &rrset, RRType rr_type) {
@@ -472,8 +476,11 @@ std::optional<std::vector<RR>> Resolver::resolve_rec(const std::string &domain,
472476
next_zone = nullptr;
473477

474478
// Get zone's DNSKEYs.
475-
if (zone->enable_dnssec && zone->dnskeys.empty() && rr_type != RRType::DNSKEY) {
479+
if (zone->enable_dnssec && zone->dnskeys.empty() && !zone->is_being_resolved) {
480+
zone->is_being_resolved = true;
476481
auto dnskey_rrset = resolve_rec(zone->domain, RRType::DNSKEY, depth + 1, zone);
482+
zone->is_being_resolved = false;
483+
477484
if (dnskey_rrset.has_value() && !dnskey_rrset->empty()) {
478485
zone->dnskeys = rrset_to_data<DNSKEY>(std::move(dnskey_rrset.value()));
479486
} else {
@@ -633,7 +640,7 @@ std::optional<std::vector<RR>> Resolver::resolve_rec(const std::string &domain,
633640
}
634641
referral_zone = new_zone(ns_rr.domain);
635642
} else if (ns_rr.domain != referral_zone->domain) {
636-
throw std::runtime_error(std::format("Authority contains multiple referrals: {} and {}",
643+
throw std::runtime_error(std::format("Authority contains multiple referrals: \"{}\" and \"{}\"",
637644
ns_rr.domain, referral_zone->domain));
638645
}
639646

@@ -693,9 +700,8 @@ std::optional<std::vector<RR>> Resolver::resolve_rec(const std::string &domain,
693700
// Retry the same nameserver with the new server cookie once.
694701
i--;
695702
nameserver->sent_bad_cookie = true;
696-
} else {
697-
// Try a different nameserver.
698703
}
704+
// Try a different nameserver.
699705
} catch (const std::exception &e) {
700706
// Nameserver error, try asking the different nameserver if there are any left.
701707
if (verbose) std::println(stderr, "Failed to resolve the domain: {}.", e.what());

src/resolve.hh

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,16 @@ struct Zone;
3232

3333
enum class FeatureState { Disable, Enable, Require };
3434

35+
struct NameserverConfig {
36+
std::string address;
37+
std::optional<std::string> zone_domain{std::nullopt};
38+
std::vector<DS> dss{};
39+
std::vector<DNSKEY> dnskeys{};
40+
};
41+
3542
struct ResolverConfig {
3643
uint64_t timeout_ms{5000};
37-
std::optional<std::string> nameserver{std::nullopt};
44+
std::optional<NameserverConfig> nameserver{std::nullopt};
3845
bool use_root_nameservers{true};
3946
bool use_resolve_config{true};
4047
uint16_t port{DNS_PORT};
@@ -79,7 +86,7 @@ private:
7986
std::shared_ptr<Zone> new_zone(const std::string &domain, bool enable_dnssec = true) const;
8087
std::shared_ptr<Zone> new_root_zone() const;
8188
std::shared_ptr<Zone> load_resolve_config() const;
82-
std::shared_ptr<Zone> new_zone_from_nameserver(const std::string &address_or_domain) const;
89+
std::shared_ptr<Zone> new_zone_from_config(const NameserverConfig &config) const;
8390
std::shared_ptr<Zone> find_zone(const std::string &domain) const;
8491
void zone_disable_dnssec(Zone &zone) const;
8592

tests/bind/test.zone.base renamed to tests/bind/base.db

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
$TTL 0
2-
@ SOA @ @ 0 1d 1h 1w 0
1+
$TTL 1d
2+
@ SOA mname rname 100 200 300 400 500
33

44
; Nameservers
55
@ NS ns1
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
; This is a key-signing key, keyid 22197, for signed.com.
2+
; Created: 20250810141814 (Sun Aug 10 10:18:14 2025)
3+
; Publish: 20250810141814 (Sun Aug 10 10:18:14 2025)
4+
; Activate: 20250810141814 (Sun Aug 10 10:18:14 2025)
5+
signed.com. IN DNSKEY 257 3 13 co1i7ik/vkvjVF57saB+qrWubkFKliRH7TCypWOlFn/z9btaFk4toSyn csa4Lay0p6xe2OckNWXs7KMMtZQWjw==
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Private-key-format: v1.3
2+
Algorithm: 13 (ECDSAP256SHA256)
3+
PrivateKey: 9kPRAu6eIPpyrcz2JLdqtHjuY29BK2VYv0ZSzHPV0jA=
4+
Created: 20250810141814
5+
Publish: 20250810141814
6+
Activate: 20250810141814

tests/bind/named.conf

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,36 @@
11
options {
22
listen-on { 127.0.0.1; };
33
port 1053;
4+
45
directory "./build/named";
6+
key-directory "keys";
57
pid-file "named.pid";
8+
9+
max-cache-size 20%;
10+
session-keyfile none;
11+
recursion no;
612
};
713

8-
zone "test.com" {
14+
# Disable rndc
15+
controls {};
16+
17+
zone "unsigned.com" {
918
type primary;
10-
file "test.zone";
19+
file "unsigned.com.db";
1120
notify no;
1221
};
22+
23+
zone "signed.com" {
24+
type primary;
25+
file "signed.com.db";
26+
notify no;
27+
dnssec-policy "nsec-policy";
28+
inline-signing yes;
29+
};
30+
31+
dnssec-policy "nsec-policy" {
32+
keys {
33+
ksk lifetime unlimited algorithm ECDSAP256SHA256;
34+
zsk lifetime 60d algorithm ECDSAP256SHA256;
35+
};
36+
};

tests/cases/bind/a.cc

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,25 @@
22
#include "config.hh"
33
#include "resolve.hh"
44

5-
int main() {
6-
/// a A 1.2.3.4
7-
Resolver resolver{TEST_RESOLVER_CONFIG};
8-
auto opt_rrset = resolver.resolve("a." TEST_DOMAIN, RRType::A);
9-
ASSERT(opt_rrset.has_value());
5+
/// a A 1.2.3.4
6+
7+
namespace {
8+
void check_response(const std::optional<std::vector<RR>> &response) {
9+
ASSERT(response.has_value());
1010

11-
auto &rrset = opt_rrset.value();
11+
const auto &rrset = response.value();
1212
ASSERT(rrset.size() == 1);
1313

14-
auto &rr = rrset[0];
14+
const auto &rr = rrset[0];
1515
ASSERT(rr.type == RRType::A);
1616
ASSERT(std::get<A>(rr.data).address == get_ip4("1.2.3.4"));
17+
}
18+
} // namespace
1719

20+
int main() {
21+
Resolver unsigned_resolver{UNSIGNED_RESOLVER_CONFIG};
22+
Resolver signed_resolver{SIGNED_RESOLVER_CONFIG};
23+
check_response(unsigned_resolver.resolve("a." UNSIGNED_DOMAIN, RRType::A));
24+
check_response(signed_resolver.resolve("a." SIGNED_DOMAIN, RRType::A));
1825
return EXIT_SUCCESS;
1926
}

tests/cases/bind/aaaa.cc

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,25 @@
22
#include "config.hh"
33
#include "resolve.hh"
44

5-
int main() {
6-
/// aaaa AAAA 1:2:3:4::
7-
Resolver resolver{TEST_RESOLVER_CONFIG};
8-
auto opt_rrset = resolver.resolve("aaaa." TEST_DOMAIN, RRType::AAAA);
9-
ASSERT(opt_rrset.has_value());
5+
/// aaaa AAAA 1:2:3:4::
6+
7+
namespace {
8+
void check_response(const std::optional<std::vector<RR>> &response) {
9+
ASSERT(response.has_value());
1010

11-
auto &rrset = opt_rrset.value();
11+
const auto &rrset = response.value();
1212
ASSERT(rrset.size() == 1);
1313

14-
auto &rr = rrset[0];
14+
const auto &rr = rrset[0];
1515
ASSERT(rr.type == RRType::AAAA);
1616
ASSERT(ip6_equals(std::get<AAAA>(rr.data).address, "1:2:3:4::"));
17+
}
18+
} // namespace
1719

20+
int main() {
21+
Resolver unsigned_resolver{UNSIGNED_RESOLVER_CONFIG};
22+
Resolver signed_resolver{SIGNED_RESOLVER_CONFIG};
23+
check_response(unsigned_resolver.resolve("aaaa." UNSIGNED_DOMAIN, RRType::AAAA));
24+
check_response(signed_resolver.resolve("aaaa." SIGNED_DOMAIN, RRType::AAAA));
1825
return EXIT_SUCCESS;
1926
}

0 commit comments

Comments
 (0)