Add mixed DS test to dnssec_py

Rewrite dnssec_unsupported_ds/tests_mixed_ds.py as
dnssec_py/tests_mixed_ds.py using the isctest.zone helpers for zone
setup.

The test verifies that a zone whose DS RRset contains only an
unsupported algorithm DS and a bogus DS record is treated as insecure
by a validating resolver, resulting in SERVFAIL for queries to that
zone. The DS set for child.mixed-ds. is deliberately corrupted after
signing to contain a DS record with an unsupported algorithm (12) and
a DS record with an invalid digest, exercising the mixed-DS insecurity
proof path.

Assisted-by: Claude:claude-opus-4-8
This commit is contained in:
Nicki Křížek 2026-06-08 15:19:22 +00:00
parent 02c6ef5ba2
commit cdafea5f12
13 changed files with 72 additions and 311 deletions

View file

@ -0,0 +1,72 @@
# 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 re import compile as Re
from dnssec_py.common import DNSSEC_PY_MARK
from isctest.template import NS2, NS3, zones
from isctest.vars.algorithms import Algorithm
from isctest.zone import Zone, configure_root
import isctest
pytestmark = DNSSEC_PY_MARK
def modify_dsset():
with open("ns3/dsset-child.mixed-ds.", encoding="utf-8") as dsset_file:
ds_orig = dsset_file.readline()
alg = Algorithm.default().number
alg_re = Re(rf"\s+{alg}\s+")
ds_unsupported = alg_re.sub(" 12 ", ds_orig)
digest_re = Re(rf"\s+{alg}\s+2\s+.*")
ds_bogus = digest_re.sub(
f" {alg} 2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
ds_orig,
)
with open("ns3/dsset-child.mixed-ds.", "w", encoding="utf-8") as dsset_file:
dsset_file.writelines([ds_unsupported, ds_bogus])
def bootstrap():
child = Zone("child.mixed-ds", NS3, signed=True)
child.configure()
isctest.log.info(
"child.mixed-ds: modify DS set to have unsupported and bogus DS records"
)
modify_dsset()
mixed_ds = Zone("mixed-ds", NS2, signed=True)
mixed_ds.delegations = [child]
mixed_ds.configure()
root = configure_root([mixed_ds])
return {
"trust_anchors": root.trust_anchors(),
"zones": zones([root, mixed_ds, child]),
}
def test_mixed_ds(ns9):
msg = isctest.query.create("child.mixed-ds.", "DNSKEY")
with ns9.watch_log_from_here() as watcher:
res = isctest.query.tcp(msg, ns9.ip)
watcher.wait_for_line("child.mixed-ds/DNSKEY: insecurity proof failed")
isctest.check.servfail(res)
msg = isctest.query.create("a.child.mixed-ds.", "A")
res = isctest.query.tcp(msg, ns9.ip)
isctest.check.servfail(res)

View file

@ -1,25 +0,0 @@
// NS1
options {
query-source address 10.53.0.1;
notify-source 10.53.0.1;
transfer-source 10.53.0.1;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.1; };
listen-on-v6 { none; };
minimal-any no;
minimal-responses no;
recursion no;
notify yes;
dnssec-validation yes;
/* test that we can turn off trust-anchor-telemetry */
trust-anchor-telemetry no;
};
zone "." {
type primary;
file "zones/root.db.signed";
};
include "trusted.conf";

View file

@ -1,37 +0,0 @@
#!/bin/sh -e
# 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.
# shellcheck source=conf.sh
. ../../conf.sh
set -e
echo_i "ns1/sign.sh"
zone=.
mkdir -p keys
ksk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -f KSK "${zone}")
zsk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "${zone}")
cat "zones/root.db.in" "keys/$ksk.key" "keys/$zsk.key" ../ns2/dsset-example. >"zones/root.db"
"$SIGNER" -S -K "keys" \
-o . \
-f "zones/root.db.signed" \
"zones/root.db" >/dev/null 2>&1
keyfile_to_static_ds "keys/$ksk" >trusted.conf
cp trusted.conf ../ns2/trusted.conf
cp trusted.conf ../ns3/trusted.conf
cp trusted.conf ../ns4/trusted.conf

View file

@ -1,5 +0,0 @@
. 300 IN SOA gson.nominum.com. a.root.servers.nil. 2000042100 600 600 1200 600
ns2.example. 300 IN A 10.53.0.2
example. 300 IN NS ns2.example.
a.root-servers.nil. 300 IN A 10.53.0.1
. 300 IN NS a.root-servers.nil.

View file

@ -1,34 +0,0 @@
// NS2
options {
query-source address 10.53.0.2;
notify-source 10.53.0.2;
transfer-source 10.53.0.2;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.2; };
listen-on-v6 { none; };
recursion no;
dnssec-validation yes;
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
zone "." {
type hint;
file "../../_common/root.hint";
};
zone "example." {
type primary;
file "zones/example.db.signed";
};
include "trusted.conf";

View file

@ -1,35 +0,0 @@
#!/bin/sh -e
# 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.
# shellcheck source=conf.sh
. ../../conf.sh
set -e
echo_i "ns2/sign.sh"
zone=example.
mkdir -p keys
ksk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -f KSK "${zone}")
zsk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "${zone}")
cat "zones/${zone}db.in" "keys/$ksk.key" "keys/$zsk.key" >"zones/${zone}db"
<"../ns3/dsset-child.${zone}" sed -E "s/[[:space:]]+$DEFAULT_ALGORITHM_NUMBER[[:space:]]+/ 12 /" >>"zones/${zone}db"
<"../ns3/dsset-child.${zone}" sed -E "s/[[:space:]]+$DEFAULT_ALGORITHM_NUMBER[[:space:]]+2[[:space:]]+.*/ $DEFAULT_ALGORITHM_NUMBER 2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/" >>"zones/${zone}db"
"$SIGNER" -S -K "keys" \
-o "${zone}" \
-f "zones/${zone}db.signed" \
"zones/${zone}db" >/dev/null 2>&1

View file

@ -1,5 +0,0 @@
example. 300 IN SOA ns1.example. admin.example. 2026021901 3600 900 86400 300
ns1.example. 300 IN A 10.53.0.2
ns1.child.example. 300 IN A 10.53.0.3
child.example. 300 IN NS ns1.child.example.
example. 300 IN NS ns1.example.

View file

@ -1,34 +0,0 @@
// NS3
options {
query-source address 10.53.0.3;
notify-source 10.53.0.3;
transfer-source 10.53.0.3;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.3; };
listen-on-v6 { none; };
recursion no;
dnssec-validation yes;
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
zone "." {
type hint;
file "../../_common/root.hint";
};
zone "child.example." {
type primary;
file "zones/child.example.db.signed";
};
include "trusted.conf";

View file

@ -1,32 +0,0 @@
#!/bin/sh -e
# 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.
# shellcheck source=conf.sh
. ../../conf.sh
set -e
echo_i "ns3/sign.sh"
zone=child.example.
mkdir -p keys
ksk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -f KSK "${zone}")
zsk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "${zone}")
cat "zones/${zone}db.in" "keys/$ksk.key" "keys/$zsk.key" >"zones/${zone}db"
"$SIGNER" -S -K "keys" \
-o "${zone}" \
-f "zones/${zone}db.signed" \
"zones/${zone}db" >/dev/null 2>&1

View file

@ -1,7 +0,0 @@
child.example. 300 IN SOA ns1.child.example. admin.child.example. 2026021901 3600 900 86400 300
api.child.example. 300 IN A 192.0.2.102
child.example. 300 IN MX 10 mail.child.example.
mail.child.example. 300 IN A 192.0.2.101
www.child.example. 300 IN A 192.0.2.100
ns1.child.example. 300 IN A 10.53.0.3
child.example. 300 IN NS ns1.child.example.

View file

@ -1,29 +0,0 @@
// NS4
options {
query-source address 10.53.0.4;
notify-source 10.53.0.4;
transfer-source 10.53.0.4;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.4; };
listen-on-v6 { none; };
recursion yes;
dnssec-validation yes;
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
zone "." {
type hint;
file "../../_common/root.hint";
};
include "trusted.conf";

View file

@ -1,32 +0,0 @@
#!/bin/sh -e
# 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.
# shellcheck source=conf.sh
. ../conf.sh
set -e
(
cd ns3
$SHELL sign.sh
)
(
cd ns2
$SHELL sign.sh
)
(
cd ns1
$SHELL sign.sh
)

View file

@ -1,36 +0,0 @@
# 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.
import pytest
import isctest
pytestmark = pytest.mark.extra_artifacts(
[
"ns*/dsset-*",
"ns*/keys",
"ns*/keys/*.key",
"ns*/keys/*.private",
"ns*/trusted.conf",
"ns*/zones/*.db",
"ns*/zones/*.db.signed",
]
)
def test_mixed_ds():
msg = isctest.query.create("child.example.", "DNSKEY")
res = isctest.query.tcp(msg, "10.53.0.4")
isctest.check.servfail(res)
msg = isctest.query.create("child.example.", "A")
res = isctest.query.tcp(msg, "10.53.0.4")
isctest.check.servfail(res)