mirror of
https://github.com/isc-projects/bind9.git
synced 2026-06-10 13:10:00 -04:00
Adds a static system test that fails to load an NSEC3 record with an invalid next part length. Additionally, introduces a dynamic test using a crafted authoritative DNS proxy to inject invalid NSEC3 records on the fly to test runtime behavior.
163 lines
4.9 KiB
Python
163 lines
4.9 KiB
Python
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
#
|
|
# SPDX-License-Identifier: MPL-2.0
|
|
#
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
#
|
|
# See the COPYRIGHT file distributed with this work for additional
|
|
# information regarding copyright ownership.
|
|
|
|
from datetime import timedelta
|
|
|
|
import dns.rcode
|
|
import dns.rdataclass
|
|
import dns.rdatatype
|
|
import pytest
|
|
|
|
import isctest
|
|
|
|
NSEC3_MARK = pytest.mark.extra_artifacts(
|
|
[
|
|
"*.axfr",
|
|
"*.created",
|
|
"dig.out.*",
|
|
"rndc.reload.*",
|
|
"rndc.signing.*",
|
|
"update.out.*",
|
|
"verify.out.*",
|
|
"ns*/dsset-**",
|
|
"ns*/K*",
|
|
"ns*/settime.out.*",
|
|
"ns*/*.db",
|
|
"ns*/*.jbk",
|
|
"ns*/*.jnl",
|
|
"ns*/*.signed",
|
|
"ns*/keygen.out.*",
|
|
"ns3/named-*.conf",
|
|
"ans*/ans.run",
|
|
]
|
|
)
|
|
|
|
default_config = {
|
|
"dnskey-ttl": timedelta(hours=1),
|
|
"ds-ttl": timedelta(days=1),
|
|
"max-zone-ttl": timedelta(days=1),
|
|
"parent-propagation-delay": timedelta(hours=1),
|
|
"publish-safety": timedelta(hours=1),
|
|
"retire-safety": timedelta(hours=1),
|
|
"signatures-refresh": timedelta(days=5),
|
|
"signatures-validity": timedelta(days=14),
|
|
"zone-propagation-delay": timedelta(minutes=5),
|
|
}
|
|
|
|
|
|
def check_auth_nsec(response):
|
|
rrs = []
|
|
for rrset in response.authority:
|
|
if rrset.match(dns.rdataclass.IN, dns.rdatatype.NSEC, dns.rdatatype.NONE):
|
|
rrs.append(rrset)
|
|
assert not rrset.match(
|
|
dns.rdataclass.IN, dns.rdatatype.NSEC3, dns.rdatatype.NONE
|
|
)
|
|
assert len(rrs) != 0, "no NSEC records found in authority section"
|
|
|
|
|
|
def check_auth_nsec3(response, iterations=0, optout=0, salt="-"):
|
|
match = f"IN NSEC3 1 {optout} {iterations} {salt}"
|
|
rrs = []
|
|
|
|
for rrset in response.authority:
|
|
if rrset.match(dns.rdataclass.IN, dns.rdatatype.NSEC3, dns.rdatatype.NONE):
|
|
assert match in rrset.to_text()
|
|
rrs.append(rrset)
|
|
assert not rrset.match(
|
|
dns.rdataclass.IN, dns.rdatatype.NSEC, dns.rdatatype.NONE
|
|
)
|
|
|
|
assert len(rrs) != 0, "no NSEC3 records found in authority section"
|
|
|
|
|
|
def check_nsec3param(response, match, saltlen):
|
|
rrs = []
|
|
salt = "-"
|
|
|
|
for rrset in response.answer:
|
|
if rrset.match(dns.rdataclass.IN, dns.rdatatype.NSEC3PARAM, dns.rdatatype.NONE):
|
|
assert match in rrset.to_text()
|
|
if saltlen == 0:
|
|
assert f"{match} -" in rrset.to_text()
|
|
else:
|
|
assert not f"{match} -" in rrset.to_text()
|
|
salt = rrset.to_text().split()[7]
|
|
|
|
rrs.append(rrset)
|
|
else:
|
|
assert rrset.match(
|
|
dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC3PARAM
|
|
)
|
|
|
|
assert len(rrs) != 0
|
|
|
|
return salt
|
|
|
|
|
|
def check_nsec3_case(server, params, nsec3=True):
|
|
# Get test parameters.
|
|
zone = params["zone"]
|
|
fqdn = f"{zone}."
|
|
policy = params["policy"]
|
|
keydir = server.identifier
|
|
config = default_config
|
|
ttl = int(config["dnskey-ttl"].total_seconds())
|
|
expected = isctest.kasp.policy_to_properties(ttl=ttl, keys=params["key-properties"])
|
|
|
|
# Test case.
|
|
isctest.log.info(f"check nsec case zone {zone} policy {policy}")
|
|
|
|
# Key files.
|
|
keys = isctest.kasp.keydir_to_keylist(zone, keydir)
|
|
if "external-keys" in params:
|
|
expected2 = isctest.kasp.policy_to_properties(ttl, keys=params["external-keys"])
|
|
for ek in expected2:
|
|
ek.private = False
|
|
ek.legacy = True
|
|
expected = expected + expected2
|
|
assert "external-keydir" in params
|
|
extkeys = isctest.kasp.keydir_to_keylist(zone, params["external-keydir"])
|
|
keys = keys + extkeys
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
|
isctest.kasp.check_apex(server, zone, keys, [])
|
|
|
|
query = isctest.query.create(fqdn, dns.rdatatype.NSEC3PARAM)
|
|
nsec3param_response = isctest.query.tcp(query, server.ip)
|
|
assert nsec3param_response.rcode() == dns.rcode.NOERROR
|
|
|
|
query = isctest.query.create(f"nosuchname.{fqdn}", dns.rdatatype.A)
|
|
response = isctest.query.tcp(query, server.ip)
|
|
assert response.rcode() == dns.rcode.NXDOMAIN
|
|
|
|
if nsec3:
|
|
# NSEC3
|
|
minimum = params.get("soa-minimum", 3600)
|
|
iterations = 0
|
|
optout = 0
|
|
saltlen = 0
|
|
if "nsec3param" in params:
|
|
optout = params["nsec3param"].get("optout", 0)
|
|
saltlen = params["nsec3param"].get("salt-length", 0)
|
|
|
|
match = f"{fqdn} {minimum} IN NSEC3PARAM 1 0 {iterations}"
|
|
|
|
salt = check_nsec3param(nsec3param_response, match, saltlen)
|
|
|
|
check_auth_nsec3(response, iterations, optout, salt)
|
|
else:
|
|
# NSEC
|
|
assert len(nsec3param_response.answer) == 0
|
|
check_auth_nsec(nsec3param_response)
|
|
|
|
check_auth_nsec(response)
|