2025-03-14 12:19:36 +01:00
|
|
|
# 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 shutil
|
2025-03-14 13:08:44 +01:00
|
|
|
import time
|
2025-03-14 12:19:36 +01:00
|
|
|
|
|
|
|
from datetime import timedelta
|
|
|
|
|
2025-03-14 12:52:36 +01:00
|
|
|
import dns
|
2025-03-14 13:08:44 +01:00
|
|
|
import dns.update
|
2025-03-14 12:19:36 +01:00
|
|
|
import pytest
|
|
|
|
|
2025-03-18 10:06:29 +01:00
|
|
|
pytest.importorskip("dns", minversion="2.0.0")
|
2025-03-14 12:19:36 +01:00
|
|
|
import isctest
|
|
|
|
from isctest.kasp import (
|
|
|
|
KeyProperties,
|
|
|
|
KeyTimingMetadata,
|
|
|
|
)
|
|
|
|
|
|
|
|
pytestmark = pytest.mark.extra_artifacts(
|
|
|
|
[
|
|
|
|
"K*.private",
|
|
|
|
"K*.backup",
|
|
|
|
"K*.cmp",
|
|
|
|
"K*.key",
|
|
|
|
"K*.state",
|
2025-03-14 12:52:36 +01:00
|
|
|
"*.axfr",
|
2025-03-14 12:19:36 +01:00
|
|
|
"*.created",
|
|
|
|
"dig.out*",
|
|
|
|
"keyevent.out.*",
|
|
|
|
"keygen.out.*",
|
|
|
|
"keys",
|
|
|
|
"published.test*",
|
|
|
|
"python.out.*",
|
|
|
|
"retired.test*",
|
|
|
|
"rndc.dnssec.*.out.*",
|
|
|
|
"rndc.zonestatus.out.*",
|
|
|
|
"rrsig.out.*",
|
|
|
|
"created.key-*",
|
|
|
|
"unused.key-*",
|
|
|
|
"verify.out.*",
|
|
|
|
"zone.out.*",
|
|
|
|
"ns*/K*.key",
|
2025-03-14 12:52:36 +01:00
|
|
|
"ns*/K*.offline",
|
|
|
|
"ns*/K*.private",
|
2025-03-14 12:19:36 +01:00
|
|
|
"ns*/K*.state",
|
|
|
|
"ns*/*.db",
|
|
|
|
"ns*/*.db.infile",
|
|
|
|
"ns*/*.db.signed",
|
2025-03-14 12:52:36 +01:00
|
|
|
"ns*/*.db.signed.tmp",
|
2025-03-14 12:19:36 +01:00
|
|
|
"ns*/*.jbk",
|
|
|
|
"ns*/*.jnl",
|
2025-03-14 12:52:36 +01:00
|
|
|
"ns*/*.zsk1",
|
|
|
|
"ns*/*.zsk2",
|
2025-03-14 12:19:36 +01:00
|
|
|
"ns*/dsset-*",
|
|
|
|
"ns*/keygen.out.*",
|
|
|
|
"ns*/keys",
|
|
|
|
"ns*/ksk",
|
|
|
|
"ns*/ksk/K*",
|
|
|
|
"ns*/zsk",
|
|
|
|
"ns*/zsk",
|
|
|
|
"ns*/zsk/K*",
|
|
|
|
"ns*/named-fips.conf",
|
|
|
|
"ns*/settime.out.*",
|
|
|
|
"ns*/signer.out.*",
|
|
|
|
"ns*/zones",
|
|
|
|
"ns*/policies/*.conf",
|
|
|
|
"ns3/legacy-keys.*",
|
|
|
|
"ns3/dynamic-signed-inline-signing.kasp.db.signed.signed",
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2025-03-17 14:53:55 +01:00
|
|
|
def check_all(server, zone, policy, ksks, zsks, zsk_missing=False, tsig=None):
|
2025-03-14 12:52:36 +01:00
|
|
|
isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy)
|
2025-03-17 14:53:55 +01:00
|
|
|
isctest.kasp.check_apex(
|
|
|
|
server, zone, ksks, zsks, zsk_missing=zsk_missing, tsig=tsig
|
|
|
|
)
|
2025-03-14 12:52:36 +01:00
|
|
|
isctest.kasp.check_subdomain(server, zone, ksks, zsks, tsig=tsig)
|
2025-03-14 17:11:14 +01:00
|
|
|
isctest.kasp.check_dnssec_verify(server, zone, tsig=tsig)
|
2025-03-14 12:52:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
def set_keytimes_default_policy(kp):
|
|
|
|
# The first key is immediately published and activated.
|
|
|
|
kp.timing["Generated"] = kp.key.get_timing("Created")
|
|
|
|
kp.timing["Published"] = kp.timing["Generated"]
|
|
|
|
kp.timing["Active"] = kp.timing["Generated"]
|
|
|
|
# The DS can be published if the DNSKEY and RRSIG records are
|
|
|
|
# OMNIPRESENT. This happens after max-zone-ttl (1d) plus
|
|
|
|
# plus zone-propagation-delay (300s).
|
|
|
|
kp.timing["PublishCDS"] = kp.timing["Published"] + timedelta(days=1, seconds=300)
|
|
|
|
# Key lifetime is unlimited, so not setting 'Retired' nor 'Removed'.
|
|
|
|
kp.timing["DNSKEYChange"] = kp.timing["Published"]
|
|
|
|
kp.timing["DSChange"] = kp.timing["Published"]
|
|
|
|
kp.timing["KRRSIGChange"] = kp.timing["Active"]
|
|
|
|
kp.timing["ZRRSIGChange"] = kp.timing["Active"]
|
|
|
|
|
|
|
|
|
2025-03-14 17:11:14 +01:00
|
|
|
def test_kasp_cases(servers):
|
|
|
|
# Test many different configurations and expected keys and states after
|
|
|
|
# initial startup.
|
|
|
|
server = servers["ns3"]
|
|
|
|
keydir = server.identifier
|
|
|
|
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
|
|
|
|
size = os.environ["DEFAULT_BITS"]
|
|
|
|
|
|
|
|
kasp_config = {
|
|
|
|
"dnskey-ttl": timedelta(seconds=1234),
|
|
|
|
"ds-ttl": timedelta(days=1),
|
|
|
|
"key-directory": keydir,
|
|
|
|
"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),
|
|
|
|
}
|
|
|
|
|
|
|
|
autosign_config = {
|
|
|
|
"dnskey-ttl": timedelta(seconds=300),
|
|
|
|
"ds-ttl": timedelta(days=1),
|
|
|
|
"key-directory": keydir,
|
|
|
|
"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=7),
|
|
|
|
"signatures-validity": timedelta(days=14),
|
|
|
|
"zone-propagation-delay": timedelta(minutes=5),
|
|
|
|
}
|
|
|
|
|
|
|
|
lifetime = {
|
|
|
|
"P10Y": int(timedelta(days=10 * 365).total_seconds()),
|
|
|
|
"P5Y": int(timedelta(days=5 * 365).total_seconds()),
|
|
|
|
"P2Y": int(timedelta(days=2 * 365).total_seconds()),
|
|
|
|
"P1Y": int(timedelta(days=365).total_seconds()),
|
|
|
|
"P30D": int(timedelta(days=30).total_seconds()),
|
|
|
|
"P6M": int(timedelta(days=31 * 6).total_seconds()),
|
|
|
|
}
|
|
|
|
|
|
|
|
autosign_properties = [
|
|
|
|
f"ksk {lifetime['P2Y']} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent",
|
|
|
|
f"zsk {lifetime['P1Y']} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent",
|
|
|
|
]
|
|
|
|
|
|
|
|
def rsa1_properties(alg):
|
|
|
|
return [
|
|
|
|
f"ksk {lifetime['P10Y']} {alg} 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden",
|
|
|
|
f"zsk {lifetime['P5Y']} {alg} 2048 goal:omnipresent dnskey:rumoured zrrsig:rumoured",
|
|
|
|
f"zsk {lifetime['P1Y']} {alg} 2000 goal:omnipresent dnskey:rumoured zrrsig:rumoured",
|
|
|
|
]
|
|
|
|
|
|
|
|
def fips_properties(alg, bits=None):
|
|
|
|
sizes = [2048, 2048, 3072]
|
|
|
|
if bits is not None:
|
|
|
|
sizes = [bits, bits, bits]
|
|
|
|
|
|
|
|
return [
|
|
|
|
f"ksk {lifetime['P10Y']} {alg} {sizes[0]} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden",
|
|
|
|
f"zsk {lifetime['P5Y']} {alg} {sizes[1]} goal:omnipresent dnskey:rumoured zrrsig:rumoured",
|
|
|
|
f"zsk {lifetime['P1Y']} {alg} {sizes[2]} goal:omnipresent dnskey:rumoured zrrsig:rumoured",
|
|
|
|
]
|
|
|
|
|
2025-03-17 14:53:55 +01:00
|
|
|
# Additional test functions.
|
|
|
|
def test_ixfr_is_signed(
|
|
|
|
expected_updates, zone=None, policy=None, ksks=None, zsks=None
|
|
|
|
):
|
|
|
|
isctest.log.info(f"check that the zone {zone} is correctly signed after ixfr")
|
|
|
|
isctest.log.debug(
|
|
|
|
f"expected updates {expected_updates} policy {policy} ksks {ksks} zsks {zsks}"
|
|
|
|
)
|
|
|
|
|
|
|
|
shutil.copyfile(f"ns2/{zone}.db.in2", f"ns2/{zone}.db")
|
|
|
|
servers["ns2"].rndc(f"reload {zone}", log=False)
|
|
|
|
|
|
|
|
def update_is_signed():
|
|
|
|
parts = update.split()
|
|
|
|
qname = parts[0]
|
|
|
|
qtype = dns.rdatatype.from_text(parts[1])
|
|
|
|
rdata = parts[2]
|
|
|
|
return isctest.kasp.verify_update_is_signed(
|
|
|
|
server, zone, qname, qtype, rdata, ksks, zsks
|
|
|
|
)
|
|
|
|
|
|
|
|
for update in expected_updates:
|
|
|
|
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
|
|
|
|
|
|
|
def test_rrsig_refresh(zone=None, policy=None, ksks=None, zsks=None):
|
|
|
|
# pylint: disable=unused-argument
|
|
|
|
isctest.log.info(f"check that the zone {zone} refreshes expired signatures")
|
|
|
|
|
|
|
|
def rrsig_is_refreshed():
|
|
|
|
parts = query.split()
|
|
|
|
qname = parts[0]
|
|
|
|
qtype = dns.rdatatype.from_text(parts[1])
|
|
|
|
return isctest.kasp.check_rrsig_is_refreshed(
|
|
|
|
server, zone, f"ns3/{zone}.db.signed", qname, qtype, ksks, zsks
|
|
|
|
)
|
|
|
|
|
|
|
|
queries = [
|
|
|
|
f"{zone} DNSKEY",
|
|
|
|
f"{zone} SOA",
|
|
|
|
f"{zone} NS",
|
|
|
|
f"{zone} NSEC",
|
|
|
|
f"a.{zone} A",
|
|
|
|
f"a.{zone} NSEC",
|
|
|
|
f"b.{zone} A",
|
|
|
|
f"b.{zone} NSEC",
|
|
|
|
f"c.{zone} A",
|
|
|
|
f"c.{zone} NSEC",
|
|
|
|
f"ns3.{zone} A",
|
|
|
|
f"ns3.{zone} NSEC",
|
|
|
|
]
|
|
|
|
|
|
|
|
for query in queries:
|
|
|
|
isctest.run.retry_with_timeout(rrsig_is_refreshed, timeout=5)
|
|
|
|
|
|
|
|
def test_rrsig_reuse(zone=None, policy=None, ksks=None, zsks=None):
|
|
|
|
# pylint: disable=unused-argument
|
|
|
|
isctest.log.info(f"check that the zone {zone} reuses fresh signatures")
|
|
|
|
|
|
|
|
def rrsig_is_reused():
|
|
|
|
parts = query.split()
|
|
|
|
qname = parts[0]
|
|
|
|
qtype = dns.rdatatype.from_text(parts[1])
|
|
|
|
return isctest.kasp.check_rrsig_is_reused(
|
|
|
|
server, zone, f"{keydir}/{zone}.db.signed", qname, qtype, ksks, zsks
|
|
|
|
)
|
|
|
|
|
|
|
|
queries = [
|
|
|
|
f"{zone} NS",
|
|
|
|
f"{zone} NSEC",
|
|
|
|
f"a.{zone} A",
|
|
|
|
f"a.{zone} NSEC",
|
|
|
|
f"b.{zone} A",
|
|
|
|
f"b.{zone} NSEC",
|
|
|
|
f"c.{zone} A",
|
|
|
|
f"c.{zone} NSEC",
|
|
|
|
f"ns3.{zone} A",
|
|
|
|
f"ns3.{zone} NSEC",
|
|
|
|
]
|
|
|
|
|
|
|
|
for query in queries:
|
|
|
|
rrsig_is_reused()
|
|
|
|
|
|
|
|
def test_legacy_keys(zone=None, policy=None, ksks=None, zsks=None):
|
|
|
|
# pylint: disable=unused-argument
|
|
|
|
isctest.log.info(f"check that the zone {zone} uses correct legacy keys")
|
|
|
|
|
|
|
|
assert len(ksks) == 1
|
|
|
|
assert len(zsks) == 1
|
|
|
|
|
|
|
|
# This assumes the zone has a policy that dictates one KSK and one ZSK.
|
|
|
|
# The right keys to be used are stored in "{zone}.ksk" and "{zone}.zsk".
|
|
|
|
with open(f"{keydir}/{zone}.ksk", "r", encoding="utf-8") as file:
|
|
|
|
kskfile = file.read()
|
|
|
|
with open(f"{keydir}/{zone}.zsk", "r", encoding="utf-8") as file:
|
|
|
|
zskfile = file.read()
|
|
|
|
|
|
|
|
assert f"{keydir}/{kskfile}".strip() == ksks[0].path
|
|
|
|
assert f"{keydir}/{zskfile}".strip() == zsks[0].path
|
|
|
|
|
|
|
|
def test_remove_keyfiles(zone=None, policy=None, ksks=None, zsks=None):
|
|
|
|
# pylint: disable=unused-argument
|
|
|
|
isctest.log.info(
|
|
|
|
"check that removing key files does not create new keys to be generated"
|
|
|
|
)
|
|
|
|
|
|
|
|
for k in ksks + zsks:
|
|
|
|
os.remove(k.keyfile)
|
|
|
|
os.remove(k.privatefile)
|
|
|
|
os.remove(k.statefile)
|
|
|
|
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"loadkeys {zone}", log=False)
|
|
|
|
watcher.wait_for_line(
|
|
|
|
f"zone {zone}/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing"
|
|
|
|
)
|
|
|
|
|
|
|
|
# Check keys again, make sure no new keys are created.
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, keydir)
|
|
|
|
isctest.kasp.check_keys(zone, keys, [])
|
|
|
|
# Zone is still signed correctly.
|
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
|
|
|
|
2025-03-14 17:11:14 +01:00
|
|
|
# Test case function.
|
|
|
|
def test_case():
|
|
|
|
zone = test["zone"]
|
|
|
|
policy = test["policy"]
|
|
|
|
ttl = int(test["config"]["dnskey-ttl"].total_seconds())
|
2025-03-14 17:28:28 +01:00
|
|
|
pregenerated = False
|
|
|
|
if test.get("pregenerated"):
|
|
|
|
pregenerated = test["pregenerated"]
|
2025-03-17 14:53:55 +01:00
|
|
|
zsk_missing = zone == "zsk-missing.autosign"
|
2025-03-14 17:11:14 +01:00
|
|
|
|
|
|
|
isctest.log.info(f"check test case zone {zone} policy {policy}")
|
|
|
|
|
|
|
|
# Key properties.
|
|
|
|
expected = isctest.kasp.policy_to_properties(
|
|
|
|
ttl=ttl, keys=test["key-properties"]
|
|
|
|
)
|
|
|
|
# Key files.
|
2025-03-17 15:32:43 +01:00
|
|
|
if "key-directories" in test:
|
|
|
|
kdir = test["key-directories"][0]
|
|
|
|
ksks = isctest.kasp.keydir_to_keylist(zone, kdir, in_use=pregenerated)
|
|
|
|
kdir = test["key-directories"][1]
|
|
|
|
zsks = isctest.kasp.keydir_to_keylist(zone, kdir, in_use=pregenerated)
|
|
|
|
keys = ksks + zsks
|
|
|
|
else:
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(
|
|
|
|
zone, test["config"]["key-directory"], in_use=pregenerated
|
|
|
|
)
|
|
|
|
ksks = [k for k in keys if k.is_ksk()]
|
|
|
|
zsks = [k for k in keys if not k.is_ksk()]
|
2025-03-14 17:11:14 +01:00
|
|
|
|
|
|
|
isctest.kasp.check_zone_is_signed(server, zone)
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
|
|
|
|
offset = test["offset"] if "offset" in test else None
|
|
|
|
|
|
|
|
for kp in expected:
|
2025-03-14 17:28:28 +01:00
|
|
|
kp.set_expected_keytimes(
|
|
|
|
test["config"], offset=offset, pregenerated=pregenerated
|
|
|
|
)
|
2025-03-14 17:11:14 +01:00
|
|
|
|
2025-03-17 15:32:43 +01:00
|
|
|
if "rumoured" not in test:
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
2025-03-14 17:11:14 +01:00
|
|
|
|
2025-03-17 14:53:55 +01:00
|
|
|
check_all(server, zone, policy, ksks, zsks, zsk_missing=zsk_missing)
|
|
|
|
|
|
|
|
if "additional-tests" in test:
|
|
|
|
for additional_test in test["additional-tests"]:
|
|
|
|
callback = additional_test["callback"]
|
|
|
|
arguments = additional_test["arguments"]
|
|
|
|
callback(*arguments, zone=zone, policy=policy, ksks=ksks, zsks=zsks)
|
2025-03-14 17:11:14 +01:00
|
|
|
|
|
|
|
# Test cases.
|
|
|
|
rsa_cases = []
|
|
|
|
if os.environ["RSASHA1_SUPPORTED"] == 1:
|
|
|
|
rsa_cases = [
|
|
|
|
{
|
|
|
|
"zone": "rsasha1.kasp",
|
|
|
|
"policy": "rsasha1",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": rsa1_properties(5),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "rsasha1-nsec3.kasp",
|
|
|
|
"policy": "rsasha1",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": rsa1_properties(7),
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
fips_cases = [
|
|
|
|
{
|
|
|
|
"zone": "dnskey-ttl-mismatch.autosign",
|
|
|
|
"policy": "autosign",
|
|
|
|
"config": autosign_config,
|
|
|
|
"offset": -timedelta(days=30 * 6),
|
|
|
|
"key-properties": autosign_properties,
|
|
|
|
},
|
2025-03-17 14:53:55 +01:00
|
|
|
{
|
|
|
|
"zone": "expired-sigs.autosign",
|
|
|
|
"policy": "autosign",
|
|
|
|
"config": autosign_config,
|
|
|
|
"offset": -timedelta(days=30 * 6),
|
|
|
|
"key-properties": autosign_properties,
|
|
|
|
"additional-tests": [
|
|
|
|
{
|
|
|
|
"callback": test_rrsig_refresh,
|
|
|
|
"arguments": [],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "fresh-sigs.autosign",
|
|
|
|
"policy": "autosign",
|
|
|
|
"config": autosign_config,
|
|
|
|
"offset": -timedelta(days=30 * 6),
|
|
|
|
"key-properties": autosign_properties,
|
|
|
|
"additional-tests": [
|
|
|
|
{
|
|
|
|
"callback": test_rrsig_reuse,
|
|
|
|
"arguments": [],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "unfresh-sigs.autosign",
|
|
|
|
"policy": "autosign",
|
|
|
|
"config": autosign_config,
|
|
|
|
"offset": -timedelta(days=30 * 6),
|
|
|
|
"key-properties": autosign_properties,
|
|
|
|
"additional-tests": [
|
|
|
|
{
|
|
|
|
"callback": test_rrsig_refresh,
|
|
|
|
"arguments": [],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "keyfiles-missing.autosign",
|
|
|
|
"policy": "autosign",
|
|
|
|
"config": autosign_config,
|
|
|
|
"offset": -timedelta(days=30 * 6),
|
|
|
|
"key-properties": autosign_properties,
|
|
|
|
"additional-tests": [
|
|
|
|
{
|
|
|
|
"callback": test_remove_keyfiles,
|
|
|
|
"arguments": [],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "ksk-missing.autosign",
|
|
|
|
"policy": "autosign",
|
|
|
|
"config": autosign_config,
|
|
|
|
"offset": -timedelta(days=30 * 6),
|
|
|
|
"key-properties": [
|
|
|
|
f"ksk 63072000 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent missing",
|
|
|
|
f"zsk 31536000 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "zsk-missing.autosign",
|
|
|
|
"policy": "autosign",
|
|
|
|
"config": autosign_config,
|
|
|
|
"offset": -timedelta(days=30 * 6),
|
|
|
|
"key-properties": [
|
|
|
|
f"ksk 63072000 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent",
|
|
|
|
f"zsk 31536000 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent missing",
|
|
|
|
],
|
|
|
|
},
|
2025-03-14 17:11:14 +01:00
|
|
|
{
|
|
|
|
"zone": "dnssec-keygen.kasp",
|
|
|
|
"policy": "rsasha256",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": fips_properties(8),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "ecdsa256.kasp",
|
|
|
|
"policy": "ecdsa256",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": fips_properties(13, bits=256),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "ecdsa384.kasp",
|
|
|
|
"policy": "ecdsa384",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": fips_properties(14, bits=384),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "inherit.kasp",
|
|
|
|
"policy": "rsasha256",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": fips_properties(8),
|
|
|
|
},
|
2025-03-17 15:32:43 +01:00
|
|
|
{
|
|
|
|
"zone": "keystore.kasp",
|
|
|
|
"policy": "keystore",
|
|
|
|
"config": {
|
|
|
|
"dnskey-ttl": timedelta(seconds=303),
|
|
|
|
"ds-ttl": timedelta(days=1),
|
|
|
|
"key-directory": keydir,
|
|
|
|
"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),
|
|
|
|
},
|
|
|
|
"key-directories": [f"{keydir}/ksk", f"{keydir}/zsk"],
|
|
|
|
"key-properties": [
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden",
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:rumoured",
|
|
|
|
],
|
|
|
|
},
|
2025-03-17 14:53:55 +01:00
|
|
|
{
|
|
|
|
"zone": "legacy-keys.kasp",
|
|
|
|
"policy": "migrate-to-dnssec-policy",
|
|
|
|
"config": kasp_config,
|
|
|
|
"pregenerated": True,
|
|
|
|
"key-properties": [
|
|
|
|
"ksk 16070400 8 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden",
|
|
|
|
"zsk 16070400 8 2048 goal:omnipresent dnskey:rumoured zrrsig:rumoured",
|
|
|
|
],
|
|
|
|
"additional-tests": [
|
|
|
|
{
|
|
|
|
"callback": test_legacy_keys,
|
|
|
|
"arguments": [],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
2025-03-14 17:28:28 +01:00
|
|
|
{
|
|
|
|
"zone": "pregenerated.kasp",
|
|
|
|
"policy": "rsasha256",
|
|
|
|
"config": kasp_config,
|
|
|
|
"pregenerated": True,
|
|
|
|
"key-properties": fips_properties(8),
|
|
|
|
},
|
2025-03-14 17:11:14 +01:00
|
|
|
{
|
|
|
|
"zone": "rsasha256.kasp",
|
|
|
|
"policy": "rsasha256",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": fips_properties(8),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "rsasha512.kasp",
|
|
|
|
"policy": "rsasha512",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": fips_properties(10),
|
|
|
|
},
|
2025-03-17 15:32:43 +01:00
|
|
|
{
|
|
|
|
"zone": "rumoured.kasp",
|
|
|
|
"policy": "rsasha256",
|
|
|
|
"config": kasp_config,
|
|
|
|
"rumoured": True,
|
|
|
|
"key-properties": fips_properties(8),
|
|
|
|
},
|
2025-03-17 14:53:55 +01:00
|
|
|
{
|
|
|
|
"zone": "secondary.kasp",
|
|
|
|
"policy": "rsasha256",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": fips_properties(8),
|
|
|
|
"additional-tests": [
|
|
|
|
{
|
|
|
|
"callback": test_ixfr_is_signed,
|
|
|
|
"arguments": [
|
|
|
|
[
|
|
|
|
"a.secondary.kasp. A 10.0.0.11",
|
|
|
|
"d.secondary.kasp. A 10.0.0.4",
|
|
|
|
],
|
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
2025-03-14 17:28:28 +01:00
|
|
|
{
|
|
|
|
"zone": "some-keys.kasp",
|
|
|
|
"policy": "rsasha256",
|
|
|
|
"config": kasp_config,
|
|
|
|
"pregenerated": True,
|
|
|
|
"key-properties": fips_properties(8),
|
|
|
|
},
|
2025-03-14 17:11:14 +01:00
|
|
|
{
|
|
|
|
"zone": "unlimited.kasp",
|
|
|
|
"policy": "unlimited",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": [
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
if os.environ["ED25519_SUPPORTED"] == 1:
|
|
|
|
fips_cases.append(
|
|
|
|
{
|
|
|
|
"zone": "ed25519.kasp",
|
|
|
|
"policy": "ed25519",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": fips_properties(15, bits=256),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
if os.environ["ED448_SUPPORTED"] == 1:
|
|
|
|
fips_cases.append(
|
|
|
|
{
|
|
|
|
"zone": "ed448.kasp",
|
|
|
|
"policy": "ed448",
|
|
|
|
"config": kasp_config,
|
|
|
|
"key-properties": fips_properties(16, bits=456),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
test_cases = rsa_cases + fips_cases
|
|
|
|
for test in test_cases:
|
|
|
|
test_case()
|
|
|
|
|
|
|
|
|
2025-03-14 12:52:36 +01:00
|
|
|
def test_kasp_default(servers):
|
|
|
|
server = servers["ns3"]
|
|
|
|
|
|
|
|
# check the zone with default kasp policy has loaded and is signed.
|
|
|
|
isctest.log.info("check a zone with the default policy is signed")
|
|
|
|
zone = "default.kasp"
|
|
|
|
policy = "default"
|
|
|
|
|
|
|
|
# Key properties.
|
|
|
|
# DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait.
|
|
|
|
keyprops = [
|
|
|
|
"csk 0 13 256 goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden",
|
|
|
|
]
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
|
|
|
isctest.kasp.check_zone_is_signed(server, zone)
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
set_keytimes_default_policy(expected[0])
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
check_all(server, zone, policy, keys, [])
|
|
|
|
|
|
|
|
# Trigger a keymgr run. Make sure the key files are not touched if there
|
|
|
|
# are no modifications to the key metadata.
|
|
|
|
isctest.log.info(
|
|
|
|
"check that key files are untouched if there are no metadata changes"
|
|
|
|
)
|
|
|
|
key = keys[0]
|
|
|
|
privkey_stat = os.stat(key.privatefile)
|
|
|
|
pubkey_stat = os.stat(key.keyfile)
|
|
|
|
state_stat = os.stat(key.statefile)
|
|
|
|
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"loadkeys {zone}", log=False)
|
|
|
|
watcher.wait_for_line(f"keymgr: {zone} done")
|
|
|
|
|
|
|
|
assert privkey_stat.st_mtime == os.stat(key.privatefile).st_mtime
|
|
|
|
assert pubkey_stat.st_mtime == os.stat(key.keyfile).st_mtime
|
|
|
|
assert state_stat.st_mtime == os.stat(key.statefile).st_mtime
|
|
|
|
|
|
|
|
# again
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"loadkeys {zone}", log=False)
|
|
|
|
watcher.wait_for_line(f"keymgr: {zone} done")
|
|
|
|
|
|
|
|
assert privkey_stat.st_mtime == os.stat(key.privatefile).st_mtime
|
|
|
|
assert pubkey_stat.st_mtime == os.stat(key.keyfile).st_mtime
|
|
|
|
assert state_stat.st_mtime == os.stat(key.statefile).st_mtime
|
|
|
|
|
|
|
|
# modify unsigned zone file and check that new record is signed.
|
|
|
|
isctest.log.info("check that an updated zone signs the new record")
|
|
|
|
shutil.copyfile("ns3/template2.db.in", f"ns3/{zone}.db")
|
|
|
|
server.rndc(f"reload {zone}", log=False)
|
|
|
|
|
|
|
|
def update_is_signed():
|
|
|
|
parts = update.split()
|
|
|
|
qname = parts[0]
|
|
|
|
qtype = dns.rdatatype.from_text(parts[1])
|
|
|
|
rdata = parts[2]
|
|
|
|
return isctest.kasp.verify_update_is_signed(
|
|
|
|
server, zone, qname, qtype, rdata, keys, []
|
|
|
|
)
|
|
|
|
|
|
|
|
expected_updates = [f"a.{zone}. A 10.0.0.11", f"d.{zone}. A 10.0.0.44"]
|
|
|
|
for update in expected_updates:
|
|
|
|
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
|
|
|
|
|
|
|
# Move the private key file, a rekey event should not introduce
|
|
|
|
# replacement keys.
|
|
|
|
isctest.log.info("check that missing private key doesn't trigger rollover")
|
|
|
|
shutil.move(f"{key.privatefile}", f"{key.path}.offline")
|
|
|
|
expectmsg = "zone_rekey:zone_verifykeys failed: some key files are missing"
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"loadkeys {zone}", log=False)
|
|
|
|
watcher.wait_for_line(f"zone {zone}/IN (signed): {expectmsg}")
|
|
|
|
# Nothing has changed.
|
|
|
|
expected[0].properties["private"] = False
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
check_all(server, zone, policy, keys, [])
|
|
|
|
|
2025-03-14 13:08:44 +01:00
|
|
|
# A zone that uses inline-signing.
|
|
|
|
isctest.log.info("check an inline-signed zone with the default policy is signed")
|
|
|
|
zone = "inline-signing.kasp"
|
|
|
|
# Key properties.
|
|
|
|
key1 = KeyProperties.default()
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
|
|
|
expected = [key1]
|
|
|
|
isctest.kasp.check_zone_is_signed(server, zone)
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
set_keytimes_default_policy(key1)
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
check_all(server, zone, policy, keys, [])
|
|
|
|
|
|
|
|
|
|
|
|
def test_kasp_dynamic(servers):
|
|
|
|
# Dynamic update test cases.
|
|
|
|
server = servers["ns3"]
|
|
|
|
|
|
|
|
# Standard dynamic zone.
|
|
|
|
isctest.log.info("check dynamic zone is updated and signed after update")
|
|
|
|
zone = "dynamic.kasp"
|
|
|
|
policy = "default"
|
|
|
|
# Key properties.
|
|
|
|
key1 = KeyProperties.default()
|
|
|
|
expected = [key1]
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
|
|
|
isctest.kasp.check_zone_is_signed(server, zone)
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
set_keytimes_default_policy(key1)
|
|
|
|
expected = [key1]
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
check_all(server, zone, policy, keys, [])
|
|
|
|
|
|
|
|
# Update zone with nsupdate.
|
|
|
|
def nsupdate(updates):
|
|
|
|
message = dns.update.UpdateMessage(zone)
|
|
|
|
for update in updates:
|
|
|
|
if update[0] == "del":
|
|
|
|
message.delete(update[1], update[2], update[3])
|
|
|
|
else:
|
|
|
|
assert update[0] == "add"
|
|
|
|
message.add(update[1], update[2], update[3], update[4])
|
|
|
|
|
|
|
|
try:
|
|
|
|
response = isctest.query.udp(
|
|
|
|
message, server.ip, server.ports.dns, timeout=3
|
|
|
|
)
|
|
|
|
assert response.rcode() == dns.rcode.NOERROR
|
|
|
|
except dns.exception.Timeout:
|
|
|
|
assert False, f"update timeout for {zone}"
|
|
|
|
|
|
|
|
isctest.log.debug(f"update of zone {zone} to server {server.ip} successful")
|
|
|
|
|
|
|
|
def update_is_signed():
|
|
|
|
parts = update.split()
|
|
|
|
qname = parts[0]
|
|
|
|
qtype = dns.rdatatype.from_text(parts[1])
|
|
|
|
rdata = parts[2]
|
|
|
|
return isctest.kasp.verify_update_is_signed(
|
|
|
|
server, zone, qname, qtype, rdata, keys, []
|
|
|
|
)
|
|
|
|
|
|
|
|
updates = [
|
|
|
|
["del", f"a.{zone}.", "A", "10.0.0.1"],
|
|
|
|
["add", f"a.{zone}.", 300, "A", "10.0.0.101"],
|
|
|
|
["add", f"d.{zone}.", 300, "A", "10.0.0.4"],
|
|
|
|
]
|
|
|
|
nsupdate(updates)
|
|
|
|
|
|
|
|
expected_updates = [f"a.{zone}. A 10.0.0.101", f"d.{zone}. A 10.0.0.4"]
|
|
|
|
for update in expected_updates:
|
|
|
|
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
|
|
|
|
|
|
|
# Update zone with nsupdate (reverting the above change).
|
|
|
|
updates = [
|
|
|
|
["add", f"a.{zone}.", 300, "A", "10.0.0.1"],
|
|
|
|
["del", f"a.{zone}.", "A", "10.0.0.101"],
|
|
|
|
["del", f"d.{zone}.", "A", "10.0.0.4"],
|
|
|
|
]
|
|
|
|
nsupdate(updates)
|
|
|
|
|
|
|
|
update = f"a.{zone}. A 10.0.0.1"
|
|
|
|
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
|
|
|
|
|
|
|
# Update zone with freeze/thaw.
|
|
|
|
isctest.log.info("check dynamic zone is updated and signed after freeze and thaw")
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"freeze {zone}", log=False)
|
|
|
|
watcher.wait_for_line(f"freezing zone '{zone}/IN': success")
|
|
|
|
|
|
|
|
time.sleep(1)
|
|
|
|
with open(f"ns3/{zone}.db", "a", encoding="utf-8") as zonefile:
|
|
|
|
zonefile.write(f"d.{zone}. 300 A 10.0.0.44\n")
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"thaw {zone}", log=False)
|
|
|
|
watcher.wait_for_line(f"thawing zone '{zone}/IN': success")
|
|
|
|
|
|
|
|
expected_updates = [f"a.{zone}. A 10.0.0.1", f"d.{zone}. A 10.0.0.44"]
|
|
|
|
|
|
|
|
for update in expected_updates:
|
|
|
|
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
|
|
|
|
|
|
|
# Dynamic, and inline-signing.
|
|
|
|
zone = "dynamic-inline-signing.kasp"
|
|
|
|
# Key properties.
|
|
|
|
key1 = KeyProperties.default()
|
|
|
|
expected = [key1]
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
|
|
|
isctest.kasp.check_zone_is_signed(server, zone)
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
set_keytimes_default_policy(key1)
|
|
|
|
expected = [key1]
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
check_all(server, zone, policy, keys, [])
|
|
|
|
|
|
|
|
# Update zone with freeze/thaw.
|
|
|
|
isctest.log.info(
|
|
|
|
"check dynamic inline-signed zone is updated and signed after freeze and thaw"
|
|
|
|
)
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"freeze {zone}", log=False)
|
|
|
|
watcher.wait_for_line(f"freezing zone '{zone}/IN': success")
|
|
|
|
|
|
|
|
time.sleep(1)
|
|
|
|
shutil.copyfile("ns3/template2.db.in", f"ns3/{zone}.db")
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"thaw {zone}", log=False)
|
|
|
|
watcher.wait_for_line(f"thawing zone '{zone}/IN': success")
|
|
|
|
|
|
|
|
expected_updates = [f"a.{zone}. A 10.0.0.11", f"d.{zone}. A 10.0.0.44"]
|
|
|
|
for update in expected_updates:
|
|
|
|
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
|
|
|
|
|
|
|
# Dynamic, signed, and inline-signing.
|
|
|
|
isctest.log.info("check dynamic signed, and inline-signed zone")
|
|
|
|
zone = "dynamic-signed-inline-signing.kasp"
|
|
|
|
# Key properties.
|
|
|
|
key1 = KeyProperties.default()
|
|
|
|
# The ns3/setup.sh script sets all states to omnipresent.
|
|
|
|
key1.metadata["DNSKEYState"] = "omnipresent"
|
|
|
|
key1.metadata["KRRSIGState"] = "omnipresent"
|
|
|
|
key1.metadata["ZRRSIGState"] = "omnipresent"
|
|
|
|
key1.metadata["DSState"] = "omnipresent"
|
|
|
|
expected = [key1]
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, "ns3/keys")
|
|
|
|
isctest.kasp.check_zone_is_signed(server, zone)
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
check_all(server, zone, policy, keys, [])
|
|
|
|
# Ensure no zone_resigninc for the unsigned version of the zone is triggered.
|
|
|
|
assert f"zone_resigninc: zone {zone}/IN (unsigned): enter" not in "ns3/named.run"
|
|
|
|
|
2025-03-14 12:52:36 +01:00
|
|
|
|
2025-03-14 13:42:30 +01:00
|
|
|
def test_kasp_special_characters(servers):
|
|
|
|
server = servers["ns3"]
|
|
|
|
|
|
|
|
# A zone with special characters.
|
|
|
|
isctest.log.info("check special characters")
|
|
|
|
|
|
|
|
zone = r'i-am.":\;?&[]\@!\$*+,|=\.\(\)special.kasp'
|
|
|
|
# It is non-trivial to adapt the tests to deal with all possible different
|
|
|
|
# escaping characters, so we will just try to verify the zone.
|
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
|
|
|
|
|
|
|
|
|
|
|
def test_kasp_insecure(servers):
|
|
|
|
server = servers["ns3"]
|
|
|
|
|
|
|
|
# Insecure zones.
|
|
|
|
isctest.log.info("check insecure zones")
|
|
|
|
|
|
|
|
zone = "insecure.kasp"
|
|
|
|
expected = []
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
isctest.kasp.check_dnssecstatus(server, zone, keys, policy="insecure")
|
|
|
|
isctest.kasp.check_apex(server, zone, keys, [])
|
|
|
|
isctest.kasp.check_subdomain(server, zone, keys, [])
|
|
|
|
|
|
|
|
zone = "unsigned.kasp"
|
|
|
|
expected = []
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=None)
|
|
|
|
isctest.kasp.check_apex(server, zone, keys, [])
|
|
|
|
isctest.kasp.check_subdomain(server, zone, keys, [])
|
|
|
|
# Make sure the zone file is untouched.
|
|
|
|
isctest.check.file_contents_equal(f"ns3/{zone}.db.infile", f"ns3/{zone}.db")
|
|
|
|
|
|
|
|
|
|
|
|
def test_kasp_bad_maxzonettl(servers):
|
|
|
|
server = servers["ns3"]
|
|
|
|
|
|
|
|
# check that max-zone-ttl rejects zones with too high TTL.
|
|
|
|
isctest.log.info("check max-zone-ttl rejects zones with too high TTL")
|
|
|
|
zone = "max-zone-ttl.kasp"
|
|
|
|
assert f"loading from master file {zone}.db failed: out of range" in server.log
|
|
|
|
|
|
|
|
|
2025-03-14 12:19:36 +01:00
|
|
|
def test_kasp_dnssec_keygen():
|
|
|
|
def keygen(zone, policy, keydir=None):
|
|
|
|
if keydir is None:
|
|
|
|
keydir = "."
|
|
|
|
|
|
|
|
keygen_command = [
|
|
|
|
os.environ.get("KEYGEN"),
|
|
|
|
"-K",
|
|
|
|
keydir,
|
|
|
|
"-k",
|
|
|
|
policy,
|
|
|
|
"-l",
|
|
|
|
"kasp.conf",
|
|
|
|
zone,
|
|
|
|
]
|
|
|
|
|
|
|
|
return isctest.run.cmd(keygen_command, log_stdout=True).stdout.decode("utf-8")
|
|
|
|
|
|
|
|
# check that 'dnssec-keygen -k' (configured policy) creates valid files.
|
|
|
|
lifetime = {
|
|
|
|
"P1Y": int(timedelta(days=365).total_seconds()),
|
|
|
|
"P30D": int(timedelta(days=30).total_seconds()),
|
2025-03-14 12:52:36 +01:00
|
|
|
"P6M": int(timedelta(days=31 * 6).total_seconds()),
|
2025-03-14 12:19:36 +01:00
|
|
|
}
|
|
|
|
keyprops = [
|
|
|
|
f"csk {lifetime['P1Y']} 13 256",
|
|
|
|
f"ksk {lifetime['P1Y']} 8 2048",
|
|
|
|
f"zsk {lifetime['P30D']} 8 2048",
|
|
|
|
f"zsk {lifetime['P6M']} 8 3072",
|
|
|
|
]
|
2025-03-14 12:52:36 +01:00
|
|
|
keydir = "keys"
|
2025-03-14 12:19:36 +01:00
|
|
|
out = keygen("kasp", "kasp", keydir)
|
|
|
|
keys = isctest.kasp.keystr_to_keylist(out, keydir)
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl=200, keys=keyprops)
|
|
|
|
isctest.kasp.check_keys("kasp", keys, expected)
|
|
|
|
|
|
|
|
# check that 'dnssec-keygen -k' (default policy) creates valid files.
|
|
|
|
keyprops = ["csk 0 13 256"]
|
|
|
|
out = keygen("kasp", "default")
|
|
|
|
keys = isctest.kasp.keystr_to_keylist(out)
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
|
|
|
isctest.kasp.check_keys("kasp", keys, expected)
|
|
|
|
|
|
|
|
# check that 'dnssec-settime' by default does not edit key state file.
|
|
|
|
key = keys[0]
|
|
|
|
shutil.copyfile(key.privatefile, f"{key.privatefile}.backup")
|
|
|
|
shutil.copyfile(key.keyfile, f"{key.keyfile}.backup")
|
|
|
|
shutil.copyfile(key.statefile, f"{key.statefile}.backup")
|
|
|
|
|
|
|
|
created = key.get_timing("Created")
|
|
|
|
publish = key.get_timing("Publish") + timedelta(hours=1)
|
|
|
|
settime = [
|
|
|
|
os.environ.get("SETTIME"),
|
|
|
|
"-P",
|
|
|
|
str(publish),
|
|
|
|
key.path,
|
|
|
|
]
|
|
|
|
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
|
|
|
|
|
|
|
isctest.check.file_contents_equal(f"{key.statefile}", f"{key.statefile}.backup")
|
|
|
|
assert key.get_metadata("Publish", file=key.privatefile) == str(publish)
|
|
|
|
assert key.get_metadata("Publish", file=key.keyfile, comment=True) == str(publish)
|
|
|
|
|
|
|
|
# check that 'dnssec-settime -s' also sets publish time metadata and
|
|
|
|
# states in key state file.
|
|
|
|
now = KeyTimingMetadata.now()
|
|
|
|
goal = "omnipresent"
|
|
|
|
dnskey = "rumoured"
|
|
|
|
krrsig = "rumoured"
|
|
|
|
zrrsig = "omnipresent"
|
|
|
|
ds = "hidden"
|
|
|
|
keyprops = [
|
|
|
|
f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
|
|
|
|
]
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
|
|
|
expected[0].timing = {
|
|
|
|
"Generated": created,
|
|
|
|
"Published": now,
|
|
|
|
"Active": created,
|
|
|
|
"DNSKEYChange": now,
|
|
|
|
"KRRSIGChange": now,
|
|
|
|
"ZRRSIGChange": now,
|
|
|
|
"DSChange": now,
|
|
|
|
}
|
|
|
|
|
|
|
|
settime = [
|
|
|
|
os.environ.get("SETTIME"),
|
|
|
|
"-s",
|
|
|
|
"-P",
|
|
|
|
str(now),
|
|
|
|
"-g",
|
|
|
|
goal,
|
|
|
|
"-k",
|
|
|
|
dnskey,
|
|
|
|
str(now),
|
|
|
|
"-r",
|
|
|
|
krrsig,
|
|
|
|
str(now),
|
|
|
|
"-z",
|
|
|
|
zrrsig,
|
|
|
|
str(now),
|
|
|
|
"-d",
|
|
|
|
ds,
|
|
|
|
str(now),
|
|
|
|
key.path,
|
|
|
|
]
|
|
|
|
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
|
|
|
isctest.kasp.check_keys("kasp", keys, expected)
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
|
|
|
|
# check that 'dnssec-settime -s' also unsets publish time metadata and
|
|
|
|
# states in key state file.
|
|
|
|
now = KeyTimingMetadata.now()
|
|
|
|
keyprops = ["csk 0 13 256"]
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
|
|
|
expected[0].timing = {
|
|
|
|
"Generated": created,
|
|
|
|
"Active": created,
|
|
|
|
}
|
|
|
|
|
|
|
|
settime = [
|
|
|
|
os.environ.get("SETTIME"),
|
|
|
|
"-s",
|
|
|
|
"-P",
|
|
|
|
"none",
|
|
|
|
"-g",
|
|
|
|
"none",
|
|
|
|
"-k",
|
|
|
|
"none",
|
|
|
|
str(now),
|
|
|
|
"-z",
|
|
|
|
"none",
|
|
|
|
str(now),
|
|
|
|
"-r",
|
|
|
|
"none",
|
|
|
|
str(now),
|
|
|
|
"-d",
|
|
|
|
"none",
|
|
|
|
str(now),
|
|
|
|
key.path,
|
|
|
|
]
|
|
|
|
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
|
|
|
isctest.kasp.check_keys("kasp", keys, expected)
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
|
|
|
|
# check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase)
|
|
|
|
soon = now + timedelta(hours=2)
|
|
|
|
goal = "hidden"
|
|
|
|
dnskey = "unretentive"
|
|
|
|
krrsig = "omnipresent"
|
|
|
|
zrrsig = "unretentive"
|
|
|
|
ds = "omnipresent"
|
|
|
|
keyprops = [
|
|
|
|
f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
|
|
|
|
]
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
|
|
|
expected[0].timing = {
|
|
|
|
"Generated": created,
|
|
|
|
"Active": soon,
|
|
|
|
"DNSKEYChange": soon,
|
|
|
|
"KRRSIGChange": soon,
|
|
|
|
"ZRRSIGChange": soon,
|
|
|
|
"DSChange": soon,
|
|
|
|
}
|
|
|
|
|
|
|
|
settime = [
|
|
|
|
os.environ.get("SETTIME"),
|
|
|
|
"-s",
|
|
|
|
"-A",
|
|
|
|
str(soon),
|
|
|
|
"-g",
|
|
|
|
"HIDDEN",
|
|
|
|
"-k",
|
|
|
|
"UNRETENTIVE",
|
|
|
|
str(soon),
|
|
|
|
"-z",
|
|
|
|
"UNRETENTIVE",
|
|
|
|
str(soon),
|
|
|
|
"-r",
|
|
|
|
"OMNIPRESENT",
|
|
|
|
str(soon),
|
|
|
|
"-d",
|
|
|
|
"OMNIPRESENT",
|
|
|
|
str(soon),
|
|
|
|
key.path,
|
|
|
|
]
|
|
|
|
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
|
|
|
isctest.kasp.check_keys("kasp", keys, expected)
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|