--- /dev/null
+/*
+ * 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.
+ */
+
+options {
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.2; };
+ listen-on-v6 { none; };
+ allow-transfer { any; };
+ recursion no;
+ dnssec-validation no;
+ ixfr-from-differences yes;
+ sig-signing-nodes 900;
+ sig-signing-signatures 900;
+};
+
+include "controls.conf";
+
+dnssec-policy "optout" {
+ keys {
+ csk lifetime unlimited algorithm ecdsa256;
+ };
+ nsec3param iterations 0 optout yes salt-length 0;
+};
+
+zone "test" {
+ type primary;
+ file "test.db";
+ dnssec-policy "optout";
+ inline-signing yes;
+};
--- /dev/null
+; 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.
+
+$TTL 3600
+@ IN SOA ns2.test. hostmaster.test. 1 7200 3600 24796800 3600
+ IN NS ns2
+
+ns2 IN A 10.53.0.2
+
+a IN A 127.0.0.1
+
+$GENERATE 1-50000 child$ IN NS ns.example.
+
+child303 IN DS 7250 13 2 A30B3F78B6DDE9A4A9A2AD0C805518B4F49EC62E7D3F4531D33DE697 CDA01CB2
--- /dev/null
+#!/usr/bin/python3
+
+# 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 os
+import re
+import sys
+
+import isctest
+import pytest
+
+pytest.importorskip("dns", minversion="2.0.0")
+import dns.exception
+import dns.message
+import dns.name
+import dns.query
+import dns.rcode
+import dns.rdataclass
+import dns.rdatatype
+
+
+pytestmark = [
+ pytest.mark.skipif(
+ sys.version_info < (3, 7), reason="Python >= 3.7 required [GL #3001]"
+ ),
+ pytest.mark.extra_artifacts(
+ [
+ "*.out",
+ "ns2/*.infile",
+ "ns2/*.signed",
+ "ns2/*.jnl",
+ "ns2/*.jbk",
+ "ns2/controls.conf",
+ "ns2/dsset-*",
+ "ns2/K*",
+ ]
+ ),
+]
+
+
+def has_nsec3param(zone, response):
+ match = rf"{re.escape(zone)}\.\s+\d+\s+IN\s+NSEC3PARAM\s+1\s+0\s+0\s+-"
+
+ for rr in response.answer:
+ if re.search(match, rr.to_text()):
+ return True
+
+ return False
+
+
+def do_query(server, qname, qtype, tcp=False):
+ msg = isctest.query.create(qname, qtype)
+ query_func = isctest.query.tcp if tcp else isctest.query.udp
+ response = query_func(msg, server.ip, expected_rcode=dns.rcode.NOERROR)
+ return response
+
+
+def do_xfr(server, qname):
+ xfr = dns.zone.Zone(origin=f"{qname}.", relativize=False)
+ dns.query.inbound_xfr(
+ where=server.ip, txn_manager=xfr, port=int(os.environ["PORT"])
+ )
+ return xfr
+
+
+def verify_zone(zone, transfer):
+ verify = os.getenv("VERIFY")
+ assert verify is not None
+
+ filename = f"{zone}.out"
+ with open(filename, "w", encoding="utf-8") as file:
+ file.write(transfer.to_text())
+
+ # dnssec-verify command with default arguments.
+ verify_cmd = [verify, "-z", "-o", zone, filename]
+
+ verifier = isctest.run.cmd(verify_cmd)
+
+ if verifier.rc != 0:
+ isctest.log.error(f"dnssec-verify {zone} failed")
+
+ return verifier.rc == 0
+
+
+def test_optout(ns2):
+ zone = "test"
+
+ # Wait until the provided zone is signed and then verify its DNSSEC data.
+ def check_nsec3param():
+ response = do_query(ns2, zone, "NSEC3PARAM")
+ return has_nsec3param(zone, response)
+
+ # check zone is fully signed.
+ isctest.run.retry_with_timeout(check_nsec3param, timeout=300)
+
+ # check if zone if DNSSEC valid.
+ transfer = do_xfr(ns2, zone)
+ assert verify_zone(zone, transfer)