diff --git a/bin/tests/system/dnssec_malformed_dnskey/ns2/named.conf.j2 b/bin/tests/system/dnssec_malformed_dnskey/ns2/named.conf.j2 deleted file mode 100644 index 75173e22f6..0000000000 --- a/bin/tests/system/dnssec_malformed_dnskey/ns2/named.conf.j2 +++ /dev/null @@ -1,29 +0,0 @@ -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; }; - allow-transfer { any; }; - recursion no; - dnssec-validation yes; - - /* Keep the order of RRSIGs in the response static. */ - rrset-order { - name "example." order none; - }; -}; - -zone example. { - type primary; - file "example.db.signed.malformed"; -}; - -zone truncated.selfsigned. { - type primary; - file "truncated.selfsigned.db.signed"; -}; - -include "trusted.conf"; diff --git a/bin/tests/system/dnssec_malformed_dnskey/ns2/trusted.conf.j2 b/bin/tests/system/dnssec_malformed_dnskey/ns2/trusted.conf.j2 deleted file mode 100644 index 1a0f3c959e..0000000000 --- a/bin/tests/system/dnssec_malformed_dnskey/ns2/trusted.conf.j2 +++ /dev/null @@ -1,14 +0,0 @@ -trust-anchors { - example. static-key 257 3 14 "@ksk_public_key@"; - - /* - * The key tag in the trust anchor must match that of the revoked - * truncated self-signed key in the truncated.selfsigned. zone. - * - * The DNSKEY contents are intentionally different here, because the - * key doesn't have the revoked bit here and that flag is part of the - * key tag. The following decodes to key tag 33167, which is the same - * as the revoked truncated key in the zone file. - */ - truncated.selfsigned. static-key 257 3 14 "fYA="; -}; diff --git a/bin/tests/system/dnssec_malformed_dnskey/ns3/named.conf.j2 b/bin/tests/system/dnssec_malformed_dnskey/ns3/named.conf.j2 deleted file mode 100644 index 939c7b8c35..0000000000 --- a/bin/tests/system/dnssec_malformed_dnskey/ns3/named.conf.j2 +++ /dev/null @@ -1,26 +0,0 @@ -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; }; - allow-transfer { any; }; - dnssec-validation yes; - - /* This is the default, but the test relies on it. */ - max-validation-failures-per-fetch 1; -}; - -zone "example." { - type static-stub; - server-addresses { 10.53.0.2; }; -}; - -zone "truncated.selfsigned." { - type static-stub; - server-addresses { 10.53.0.2; }; -}; - -include "trusted.conf"; diff --git a/bin/tests/system/dnssec_malformed_dnskey/ns3/trusted.conf.j2 b/bin/tests/system/dnssec_malformed_dnskey/ns3/trusted.conf.j2 deleted file mode 120000 index e14af83a92..0000000000 --- a/bin/tests/system/dnssec_malformed_dnskey/ns3/trusted.conf.j2 +++ /dev/null @@ -1 +0,0 @@ -../ns2/trusted.conf.j2 \ No newline at end of file diff --git a/bin/tests/system/dnssec_py/common.py b/bin/tests/system/dnssec_py/common.py index 56a30f0c41..8a9fbca4d2 100644 --- a/bin/tests/system/dnssec_py/common.py +++ b/bin/tests/system/dnssec_py/common.py @@ -15,5 +15,7 @@ DNSSEC_PY_MARK = pytest.mark.extra_artifacts( [ "ns*/dsset-*", "ns*/trusted.conf", + "ns*/zones/*.db", + "ns*/zones/*.db.signed", ] ) diff --git a/bin/tests/system/dnssec_malformed_dnskey/ns2/example.db.in b/bin/tests/system/dnssec_py/ns2/zones/dnskey-malformed.db.j2.manual similarity index 98% rename from bin/tests/system/dnssec_malformed_dnskey/ns2/example.db.in rename to bin/tests/system/dnssec_py/ns2/zones/dnskey-malformed.db.j2.manual index 70614af0c3..5925b9701a 100644 --- a/bin/tests/system/dnssec_malformed_dnskey/ns2/example.db.in +++ b/bin/tests/system/dnssec_py/ns2/zones/dnskey-malformed.db.j2.manual @@ -1,18 +1,10 @@ -$TTL 300 -@ IN SOA mname1. . ( - 1 ; serial - 600 ; refresh - 600 ; retry - 1200 ; expire - 600 ; minimum - ) - -@ NS @ -@ A 10.53.0.2 +{% include '_common/zones/soa.partial.db.j2' %} +{% include '_common/zones/ns.partial.db.j2' %} ; All of the following DNSKEYs are malformed and have the same key tag - 20071. ; The keys use invalid parameters for the ECDSA curve. +{% raw %} @ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF1z4ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3YPgZjMZtl1Wd/fvtHF/3sU3HoOfwFg5Y9Ytl2+URx5Or0NNksES2iAAwmRfEEnH/hzk+8xF @ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF1z4ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3YPgZjMZtl1Wd/fvtHF/3sU3HoOfjFg5Y9Ytl2+UR1JO/UNNksES2iAAwmRfEEnH/hzk+8v3 @ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF1z4ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3YPgZjMZtl1Wd/fvtHF/3sW2HoOfwFg5Y1ctl2+URx5O/UNNksES2iAAwmRfEEnH/hzk+8v3 @@ -112,6 +104,7 @@ $TTL 300 @ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF534ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3YPgZjMZtl1Wd/fvtHF/3sU3HoOfwFg5Y9Ytl2+URx5O/UNNksES2h+/wmRfEEnH/hzk+8v3 @ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF1z4ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3QngZjMZtl1Wd/fvtHF/3sU3HoOfwFg5ZFAtl2+URx5O/UNNksES2iAAwmRfEEnH/hzk+8v3 @ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF1z4ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3YPgZjMZtl1Wd/fvtHF/3sU3HoOfwFg5Y9Ytl2+URx5O/UNNksES2iAAwmRfEEnH/hzk+8v3 +{% endraw %} -malformed-dnskey A 10.53.0.2 +invalid-rrsig A 10.53.0.2 multiple-rrsigs A 10.53.0.2 diff --git a/bin/tests/system/dnssec_malformed_dnskey/ns2/truncated.selfsigned.db.signed b/bin/tests/system/dnssec_py/ns2/zones/truncated.selfsigned.db.signed.j2 similarity index 98% rename from bin/tests/system/dnssec_malformed_dnskey/ns2/truncated.selfsigned.db.signed rename to bin/tests/system/dnssec_py/ns2/zones/truncated.selfsigned.db.signed.j2 index 1a74fd566f..533dbca091 100644 --- a/bin/tests/system/dnssec_malformed_dnskey/ns2/truncated.selfsigned.db.signed +++ b/bin/tests/system/dnssec_py/ns2/zones/truncated.selfsigned.db.signed.j2 @@ -1,3 +1,4 @@ +{% raw %} $TTL 300 @ IN SOA mname1. . ( @@ -27,3 +28,4 @@ a A 10.53.0.2 a RRSIG A 14 3 86400 20950926153053 20251013153053 33167 @ xxxxv31CNatB9xzj3AfTMlwiO0OqxbpJ cWrHN8zjj1ScXpqrHITfG/CZpoECDLWF wkXshDB/QMxHrnXkPKEcR2c9o5tcQT5R nHvtr7HT4Ob5PcY5DnItf3OWhE+bocmW a NSEC @ A RRSIG NSEC a RRSIG NSEC 14 3 0 20950926153053 20251013153053 33167 @ xxxxwMWbUxb3ScBKEVheQ2wFqujc6cyt 28GVCU0wPrBpK72HSsgdYme7IG8ZXGfa IWSU1Kf/om5+El7Tf2vDs7aI1yI7e7YG D5IxMejQg5v3/wtP7AJZXP5K9ICjq/ph +{% endraw %} diff --git a/bin/tests/system/dnssec_malformed_dnskey/tests_malformed_dnskey.py b/bin/tests/system/dnssec_py/tests_dnskey_malformed.py similarity index 65% rename from bin/tests/system/dnssec_malformed_dnskey/tests_malformed_dnskey.py rename to bin/tests/system/dnssec_py/tests_dnskey_malformed.py index cdb8932e7c..2972441b67 100644 --- a/bin/tests/system/dnssec_malformed_dnskey/tests_malformed_dnskey.py +++ b/bin/tests/system/dnssec_py/tests_dnskey_malformed.py @@ -9,6 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +from pathlib import Path from re import compile as Re import base64 @@ -25,8 +26,14 @@ import dns.rdtypes.ANY.RRSIG import dns.zone import pytest +from dnssec_py.common import DNSSEC_PY_MARK +from isctest.template import NS2, TrustAnchor, zones +from isctest.zone import Zone, configure_root + import isctest +pytestmark = DNSSEC_PY_MARK + def generate_key(): algorithm = dns.dnssec.Algorithm.ECDSAP384SHA384 @@ -65,20 +72,28 @@ def create_malformed_rr(rr, n=0): def bootstrap(): - zone = dns.zone.from_file("ns2/example.db.in", origin="example.") + zone = Zone("dnskey-malformed", NS2, signed=True) lifetime = 300 - # geneate KSK, avoid key tag collision with ZSKs + # generate KSK, avoid key tag collision with ZSKs while True: ksk_private_key, ksk_dnskey = generate_key() if dns.dnssec.key_id(ksk_dnskey) != MALFORMED_ZSK_KEY_TAG: break keys = [(ksk_private_key, ksk_dnskey)] + # render unsigned zone file + zone.render() + + # read the rendered zone + unsigned_path = str(Path(zone.ns.name) / zone.filepath_unsigned) + signed_path = str(Path(zone.ns.name) / zone.filepath_signed) + zoneobj = dns.zone.from_file(unsigned_path, origin="dnskey-malformed.") + # sign the zone (including the malformed ZSKs) with KSK - with zone.writer() as txn: + with zoneobj.writer() as txn: dns.dnssec.sign_zone( - zone=zone, + zone=zoneobj, txn=txn, keys=keys, lifetime=lifetime, @@ -86,44 +101,51 @@ def bootstrap(): deterministic=False, # for OpenSSL<3.2.0 compat ) - # force use of the malformed ZSKs for dnssec verification - # malformed-dnskey.example. has only one invalid RRSIG and is only signed - # with malformed ZSKs - malformed_rrset = zone.get_rdataset("malformed-dnskey", "RRSIG", "A") - rr = malformed_rrset.pop() - malformed_rrset.add(create_malformed_rr(rr)) + # force use of the malformed ZSKs for invalid-rrsig.dnskey-malformed; + # the record only has one invalid RRSIG signed with a malformed ZSK + invalid_rrset = zoneobj.get_rdataset("invalid-rrsig", "RRSIG", "A") + rr = invalid_rrset.pop() + invalid_rrset.add(create_malformed_rr(rr)) - # multiple-rrsigs.example. contains a lot of RRSIGS with the same invalid - # signature using malformed RRSIG, and one valid RRSIG - multiple_rrset = zone.get_rdataset("multiple-rrsigs", "RRSIG", "A") + # multiple-rrsigs.dnskey-malformed contains a lot of RRSIGs with the same + # invalid signature using a malformed key, and one valid RRSIG + multiple_rrset = zoneobj.get_rdataset("multiple-rrsigs", "RRSIG", "A") rr = multiple_rrset.pop() for i in range(99): multiple_rrset.add(create_malformed_rr(rr, i)) multiple_rrset.add(rr) - zone.to_file("ns2/example.db.signed.malformed") + zoneobj.to_file(signed_path) + + root = configure_root([zone]) + ksk_key_b64 = base64.b64encode(ksk_dnskey.key).decode() + ksk_ta = TrustAnchor("dnskey-malformed", "static-key", f'257 3 14 "{ksk_key_b64}"') return { - "ksk_public_key": base64.b64encode(ksk_dnskey.key).decode(), + "rrset_order_none": ["dnskey-malformed"], + "trust_anchors": [*root.trust_anchors(), ksk_ta], + "zones": zones([root, zone]), } -def test_malformed_ecdsa(ns3): - log_validation_failed = Re(r"malformed-dnskey\.example/A\): validation failed") +def test_malformed_ecdsa(ns9): + log_validation_failed = Re( + r"invalid-rrsig\.dnskey-malformed/A\): validation failed" + ) log_openssl_failure = Re("EVP_PKEY_fromdata.*failed") log_openssl_version = Re("linked to OpenSSL version: OpenSSL ([0-9]+)") - msg = isctest.query.create("malformed-dnskey.example", "A") + msg = isctest.query.create("invalid-rrsig.dnskey-malformed", "A") - openssl_vers = ns3.log.grep(log_openssl_version) + openssl_vers = ns9.log.grep(log_openssl_version) if ( openssl_vers and int(openssl_vers[0].group(1)) >= 3 and os.getenv("FEATURE_QUERYTRACE") == "1" ): # extra check for OpenSSL 3.0.0+ - with ns3.watch_log_from_here() as watcher: - res = isctest.query.tcp(msg, "10.53.0.3") + with ns9.watch_log_from_here() as watcher: + res = isctest.query.tcp(msg, ns9.ip) # check the OpenSSL-specific log message appears just once matches = watcher.wait_for_all( @@ -134,25 +156,27 @@ def test_malformed_ecdsa(ns3): ) assert len([m for m in matches if m.re == log_openssl_failure]) == 1 else: - res = isctest.query.tcp(msg, "10.53.0.3") + res = isctest.query.tcp(msg, ns9.ip) isctest.check.servfail(res) -def test_multiple_rrsigs(ns3): - log_validation_failed = Re(r"multiple-rrsigs\.example/A\): validation failed") +def test_multiple_rrsigs(ns2, ns9): + log_validation_failed = Re( + r"multiple-rrsigs\.dnskey-malformed/A\): validation failed" + ) log_openssl_failure = Re("EVP_PKEY_fromdata.*failed") log_openssl_version = Re("linked to OpenSSL version: OpenSSL ([0-9]+)") - msg = isctest.query.create("multiple-rrsigs.example", "A") + msg = isctest.query.create("multiple-rrsigs.dnskey-malformed", "A") # Check the order of returned RRSIGs from auth. Due to rrset-order none; # this should remain constant for the remainder of the test. # Ensure the first two RRSIGs are malformed, otherwise skip the test. - res = isctest.query.tcp(msg, "10.53.0.2") + res = isctest.query.tcp(msg, ns2.ip) rrsigs = res.get_rrset( res.answer, - dns.name.from_text("multiple-rrsigs.example."), + dns.name.from_text("multiple-rrsigs.dnskey-malformed."), dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.A, @@ -164,15 +188,15 @@ def test_multiple_rrsigs(ns3): ): pytest.skip("valid RRSIG listed first in response, re-run test") - openssl_vers = ns3.log.grep(log_openssl_version) + openssl_vers = ns9.log.grep(log_openssl_version) if ( openssl_vers and int(openssl_vers[0].group(1)) >= 3 and os.getenv("FEATURE_QUERYTRACE") == "1" ): # extra check for OpenSSL 3.0.0+ - with ns3.watch_log_from_here() as watcher: - res = isctest.query.tcp(msg, "10.53.0.3") + with ns9.watch_log_from_here() as watcher: + res = isctest.query.tcp(msg, ns9.ip) # check the OpenSSL-specific log message appears exactly twice: # one failure is allowed by setting max-validation-failures-per-fetch 1; @@ -184,12 +208,6 @@ def test_multiple_rrsigs(ns3): ) assert len([m for m in matches if m.re == log_openssl_failure]) == 2 else: - res = isctest.query.tcp(msg, "10.53.0.3") + res = isctest.query.tcp(msg, ns9.ip) isctest.check.servfail(res) - - -def test_truncated_dnskey(): - msg = isctest.query.create("a.truncated.selfsigned.", "A") - res = isctest.query.tcp(msg, "10.53.0.3") - isctest.check.servfail(res) diff --git a/bin/tests/system/dnssec_py/tests_dnskey_truncated_selfsigned.py b/bin/tests/system/dnssec_py/tests_dnskey_truncated_selfsigned.py new file mode 100644 index 0000000000..fa6ceea85f --- /dev/null +++ b/bin/tests/system/dnssec_py/tests_dnskey_truncated_selfsigned.py @@ -0,0 +1,45 @@ +# 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, TrustAnchor, zones +from isctest.zone import Zone, configure_root + +import isctest + +pytestmark = DNSSEC_PY_MARK + + +def bootstrap(): + zone = Zone("truncated.selfsigned", NS2, signed=True) + + root = configure_root([zone], signed=False) # just delegation, TA is added directly + + # The trust anchor key tag must match the revoked truncated self-signed key + # in the zone (key tag 33167). The flags differ here (257 vs 385) because + # the revoked bit is not part of the trust anchor, but it is part of the key + # tag calculation. + zone_ta = TrustAnchor("truncated.selfsigned", "static-key", '257 3 14 "fYA="') + + return { + "trust_anchors": [zone_ta], + "zones": zones([root, zone]), + } + + +def test_truncated_dnskey(ns9): + msg = isctest.query.create("a.truncated.selfsigned.", "A") + with ns9.watch_log_from_here() as watcher: + res = isctest.query.tcp(msg, ns9.ip) + watcher.wait_for_line(Re("a.truncated.selfsigned/A.*broken trust chain")) + isctest.check.servfail(res)