Compare commits
23 Commits
main
...
matthijs-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55d3cd8da6 | ||
|
|
871f765178 | ||
|
|
78c1e003ef | ||
|
|
f06ce16463 | ||
|
|
2d305bfb72 | ||
|
|
ec79078beb | ||
|
|
d46274014a | ||
|
|
486130652c | ||
|
|
bbba4a9fd1 | ||
|
|
b46d937db5 | ||
|
|
a528db84dd | ||
|
|
5aa1d9edbf | ||
|
|
6fc904d8d9 | ||
|
|
bbaa9ac808 | ||
|
|
2b11debbae | ||
|
|
05c9d4218f | ||
|
|
e5eb84f3ce | ||
|
|
e09516a5e7 | ||
|
|
092beb55b1 | ||
|
|
d8aa8db8bd | ||
|
|
ffb78c8f85 | ||
|
|
42c1eae444 | ||
|
|
aecf92dcf0 |
@@ -9,6 +9,7 @@
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
import difflib
|
||||
import shutil
|
||||
from typing import Optional
|
||||
|
||||
@@ -97,6 +98,28 @@ def zones_equal(
|
||||
assert found_rdataset.ttl == rdataset.ttl
|
||||
|
||||
|
||||
def zone_contains(
|
||||
zone: dns.zone.Zone, rrset: dns.rrset.RRset, compare_ttl=False
|
||||
) -> bool:
|
||||
"""Check if a zone contains RRset"""
|
||||
|
||||
def compare_rrs(rr1, rrset):
|
||||
rr2 = next((other_rr for other_rr in rrset if rr1 == other_rr), None)
|
||||
if rr2 is None:
|
||||
return False
|
||||
if compare_ttl:
|
||||
return rr1.ttl == rr2.ttl
|
||||
return True
|
||||
|
||||
for _, node in zone.nodes.items():
|
||||
for rdataset in node:
|
||||
for rr in rdataset:
|
||||
if compare_rrs(rr, rrset):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def is_executable(cmd: str, errmsg: str) -> None:
|
||||
executable = shutil.which(cmd)
|
||||
assert executable is not None, errmsg
|
||||
@@ -128,3 +151,32 @@ def is_response_to(response: dns.message.Message, query: dns.message.Message) ->
|
||||
single_question(response)
|
||||
single_question(query)
|
||||
assert query.is_response(response), str(response)
|
||||
|
||||
|
||||
def file_contents_contain(file, substr):
|
||||
with open(file, "r", encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
if f"{substr}" in line:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def file_contents_equal(file1, file2):
|
||||
def normalize_line(line):
|
||||
# remove trailing&leading whitespace and replace multiple whitespaces
|
||||
return " ".join(line.split())
|
||||
|
||||
def read_lines(file_path):
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
return [normalize_line(line) for line in file.readlines()]
|
||||
|
||||
lines1 = read_lines(file1)
|
||||
lines2 = read_lines(file2)
|
||||
|
||||
differ = difflib.Differ()
|
||||
diff = differ.compare(lines1, lines2)
|
||||
|
||||
for line in diff:
|
||||
assert not line.startswith("+ ") and not line.startswith(
|
||||
"- "
|
||||
), f'file contents of "{file1}" and "{file2}" differ'
|
||||
|
||||
@@ -10,24 +10,34 @@
|
||||
# information regarding copyright ownership.
|
||||
|
||||
from functools import total_ordering
|
||||
import glob
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Optional, Union
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import dns
|
||||
import dns.tsig
|
||||
import isctest.log
|
||||
import isctest.query
|
||||
|
||||
DEFAULT_TTL = 300
|
||||
|
||||
NEXT_KEY_EVENT_THRESHOLD = 100
|
||||
|
||||
def _query(server, qname, qtype):
|
||||
|
||||
def _query(server, qname, qtype, tsig=None):
|
||||
query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True)
|
||||
|
||||
if tsig is not None:
|
||||
tsigkey = tsig.split(":")
|
||||
keyring = dns.tsig.Key(tsigkey[1], tsigkey[2], tsigkey[0])
|
||||
query.use_tsig(keyring)
|
||||
|
||||
try:
|
||||
response = isctest.query.tcp(query, server.ip, server.ports.dns, timeout=3)
|
||||
except dns.exception.Timeout:
|
||||
@@ -37,6 +47,158 @@ def _query(server, qname, qtype):
|
||||
return response
|
||||
|
||||
|
||||
class KeyProperties:
|
||||
"""
|
||||
Represent the (expected) properties a key should have.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, properties: dict, metadata: dict, timing: dict):
|
||||
self.name = name
|
||||
self.key = None
|
||||
self.properties = properties
|
||||
self.metadata = metadata
|
||||
self.timing = timing
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
@staticmethod
|
||||
def default(with_state=True) -> "KeyProperties":
|
||||
result = KeyProperties.__new__(KeyProperties)
|
||||
result.name = "DEFAULT"
|
||||
result.key = None
|
||||
result.timing = {}
|
||||
result.properties = {
|
||||
"expect": True,
|
||||
"private": True,
|
||||
"legacy": False,
|
||||
"role": "csk",
|
||||
"role_full": "key-signing",
|
||||
"dnskey_ttl": 3600,
|
||||
"flags": 257,
|
||||
}
|
||||
result.metadata = {
|
||||
"Algorithm": 13, # ECDSAP256SHA256
|
||||
"Length": 256,
|
||||
"Lifetime": 0,
|
||||
"KSK": "yes",
|
||||
"ZSK": "yes",
|
||||
}
|
||||
if with_state:
|
||||
result.metadata["GoalState"] = "omnipresent"
|
||||
result.metadata["DNSKEYState"] = "rumoured"
|
||||
result.metadata["KRRSIGState"] = "rumoured"
|
||||
result.metadata["ZRRSIGState"] = "rumoured"
|
||||
result.metadata["DSState"] = "hidden"
|
||||
|
||||
return result
|
||||
|
||||
def Ipub(self, config):
|
||||
ipub = timedelta(0)
|
||||
|
||||
if self.key.get_metadata("Predecessor", must_exist=False) != "undefined":
|
||||
# Ipub = Dprp + TTLkey
|
||||
ipub = (
|
||||
config["dnskey-ttl"]
|
||||
+ config["zone-propagation-delay"]
|
||||
+ config["publish-safety"]
|
||||
)
|
||||
|
||||
self.timing["Active"] = self.timing["Published"] + ipub
|
||||
|
||||
def IpubC(self, config):
|
||||
if not self.key.is_ksk():
|
||||
return
|
||||
|
||||
ttl1 = config["dnskey-ttl"] + config["publish-safety"]
|
||||
ttl2 = timedelta(0)
|
||||
|
||||
if self.key.get_metadata("Predecessor", must_exist=False) == "undefined":
|
||||
# If this is the first key, we also need to wait until the zone
|
||||
# signatures are omnipresent. Use max-zone-ttl instead of
|
||||
# dnskey-ttl, and no publish-safety (because we are looking at
|
||||
# signatures here, not the public key).
|
||||
ttl2 = config["max-zone-ttl"]
|
||||
|
||||
# IpubC = DprpC + TTLkey
|
||||
ipubc = config["zone-propagation-delay"] + max(ttl1, ttl2)
|
||||
|
||||
self.timing["PublishCDS"] = self.timing["Published"] + ipubc
|
||||
|
||||
if self.metadata["Lifetime"] != 0:
|
||||
self.timing["DeleteCDS"] = self.timing["PublishCDS"] + int(
|
||||
self.metadata["Lifetime"]
|
||||
)
|
||||
|
||||
def Iret(self, config):
|
||||
if self.metadata["Lifetime"] == 0:
|
||||
return
|
||||
|
||||
sign_delay = config["signatures-validity"] - config["signatures-refresh"]
|
||||
safety_interval = config["retire-safety"]
|
||||
|
||||
iretKSK = timedelta(0)
|
||||
iretZSK = timedelta(0)
|
||||
if self.key.is_ksk():
|
||||
# Iret = DprpP + TTLds
|
||||
iretKSK = (
|
||||
config["parent-propagation-delay"] + config["ds-ttl"] + safety_interval
|
||||
)
|
||||
if self.key.is_zsk():
|
||||
# Iret = Dsgn + Dprp + TTLsig
|
||||
iretZSK = (
|
||||
sign_delay
|
||||
+ config["zone-propagation-delay"]
|
||||
+ config["max-zone-ttl"]
|
||||
+ safety_interval
|
||||
)
|
||||
|
||||
self.timing["Removed"] = self.timing["Retired"] + max(iretKSK, iretZSK)
|
||||
|
||||
def set_expected_keytimes(self, config, offset=None, pregenerated=False):
|
||||
if self.key is None:
|
||||
raise ValueError("KeyProperties must be attached to a Key")
|
||||
|
||||
if self.properties["legacy"]:
|
||||
return
|
||||
|
||||
if offset is None:
|
||||
offset = self.properties["offset"]
|
||||
|
||||
self.timing["Generated"] = self.key.get_timing("Created")
|
||||
|
||||
self.timing["Published"] = self.timing["Generated"]
|
||||
if pregenerated:
|
||||
self.timing["Published"] = self.key.get_timing("Publish")
|
||||
self.timing["Published"] = self.timing["Published"] + offset
|
||||
self.Ipub(config)
|
||||
|
||||
# Set Retired timing metadata if key has lifetime.
|
||||
if self.metadata["Lifetime"] != 0:
|
||||
self.timing["Retired"] = self.timing["Active"] + int(
|
||||
self.metadata["Lifetime"]
|
||||
)
|
||||
|
||||
self.IpubC(config)
|
||||
self.Iret(config)
|
||||
|
||||
# Key state change times must exist, but since we cannot reliably tell
|
||||
# when named made the actual state change, we don't care what the
|
||||
# value is. Set it to None will verify that the metadata exists, but
|
||||
# without actual checking the value.
|
||||
self.timing["DNSKEYChange"] = None
|
||||
|
||||
if self.key.is_ksk():
|
||||
self.timing["DSChange"] = None
|
||||
self.timing["KRRSIGChange"] = None
|
||||
|
||||
if self.key.is_zsk():
|
||||
self.timing["ZRRSIGChange"] = None
|
||||
|
||||
|
||||
@total_ordering
|
||||
class KeyTimingMetadata:
|
||||
"""
|
||||
@@ -117,6 +279,7 @@ class Key:
|
||||
else:
|
||||
self.keydir = Path(keydir)
|
||||
self.path = str(self.keydir / name)
|
||||
self.privatefile = f"{self.path}.private"
|
||||
self.keyfile = f"{self.path}.key"
|
||||
self.statefile = f"{self.path}.state"
|
||||
self.tag = int(self.name[-5:])
|
||||
@@ -139,21 +302,43 @@ class Key:
|
||||
)
|
||||
return None
|
||||
|
||||
def get_metadata(self, metadata: str, must_exist=True) -> str:
|
||||
def get_metadata(
|
||||
self, metadata: str, file=None, comment=False, must_exist=True
|
||||
) -> str:
|
||||
if file is None:
|
||||
file = self.statefile
|
||||
value = "undefined"
|
||||
regex = rf"{metadata}:\s+(.*)"
|
||||
with open(self.statefile, "r", encoding="utf-8") as file:
|
||||
for line in file:
|
||||
regex = rf"{metadata}:\s+(\S+).*"
|
||||
if comment:
|
||||
# The expected metadata is prefixed with a ';'.
|
||||
regex = rf";\s+{metadata}:\s+(\S+).*"
|
||||
with open(file, "r", encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
match = re.match(regex, line)
|
||||
if match is not None:
|
||||
value = match.group(1)
|
||||
break
|
||||
if must_exist and value == "undefined":
|
||||
raise ValueError(
|
||||
'state metadata "{metadata}" for key "{self.name}" undefined'
|
||||
f'metadata "{metadata}" for key "{self.name}" in file "{file}" undefined'
|
||||
)
|
||||
return value
|
||||
|
||||
def ttl(self) -> int:
|
||||
with open(self.keyfile, "r", encoding="utf-8") as file:
|
||||
for line in file:
|
||||
if line.startswith(";"):
|
||||
continue
|
||||
return int(line.split()[1])
|
||||
return 0
|
||||
|
||||
def dnskey(self):
|
||||
with open(self.keyfile, "r", encoding="utf-8") as file:
|
||||
for line in file:
|
||||
if "DNSKEY" in line:
|
||||
return line.strip()
|
||||
return "undefined"
|
||||
|
||||
def is_ksk(self) -> bool:
|
||||
return self.get_metadata("KSK") == "yes"
|
||||
|
||||
@@ -187,7 +372,7 @@ class Key:
|
||||
dsfromkey_command = [
|
||||
os.environ.get("DSFROMKEY"),
|
||||
"-T",
|
||||
"3600",
|
||||
str(self.ttl()),
|
||||
"-a",
|
||||
alg,
|
||||
"-C",
|
||||
@@ -216,6 +401,152 @@ class Key:
|
||||
|
||||
return digest_fromfile == digest_fromwire
|
||||
|
||||
def has_metadata(self, key, metadata):
|
||||
# If 'key' exists in 'metadata' then it must also exist in the state
|
||||
# meta data. Otherwise, it must not exist in the state meta data.
|
||||
if key in metadata:
|
||||
return self.get_metadata(key) != "undefined"
|
||||
|
||||
value = self.get_metadata(key, must_exist=False)
|
||||
if value != "undefined":
|
||||
isctest.log.debug(f"{self.name} {key} METADATA UNEXPECTED: {value}")
|
||||
return value == "undefined"
|
||||
|
||||
def match_metadata(self, key, metadata):
|
||||
# If 'key' exists in 'metadata' then it must match the value in the
|
||||
# state meta data. Otherwise, it must also not exist in the state meta
|
||||
# data.
|
||||
if key in metadata:
|
||||
value = self.get_metadata(key)
|
||||
if value != f"{metadata[key]}":
|
||||
isctest.log.debug(
|
||||
f"{self.name} {key} METADATA MISMATCH: {value} - {metadata[key]}"
|
||||
)
|
||||
return value == f"{metadata[key]}"
|
||||
|
||||
value = self.get_metadata(key, must_exist=False)
|
||||
if value != "undefined":
|
||||
isctest.log.debug(f"{self.name} {key} METADATA UNEXPECTED: {value}")
|
||||
return value == "undefined"
|
||||
|
||||
def match_timing(self, key, timing, file, comment=False):
|
||||
# If 'key' exists in 'timing' then it must match the value in the
|
||||
# state timing data. Otherwise, it must also not exist in the state timing
|
||||
# data.
|
||||
if key in timing:
|
||||
value = self.get_metadata(key, file=file, comment=comment)
|
||||
if value != str(timing[key]):
|
||||
isctest.log.debug(
|
||||
f"{self.name} {key} TIMING MISMATCH: {value} - {timing[key]}"
|
||||
)
|
||||
return value == str(timing[key])
|
||||
|
||||
value = self.get_metadata(key, file=file, comment=comment, must_exist=False)
|
||||
if value != "undefined":
|
||||
isctest.log.debug(f"{self.name} {key} TIMING UNEXPECTED: {value}")
|
||||
return value == "undefined"
|
||||
|
||||
def match_properties(self, zone, properties):
|
||||
# Check the key with given properties.
|
||||
if not properties.properties["expect"]:
|
||||
return False
|
||||
|
||||
# Check file existence.
|
||||
# Noop. If file is missing then the get_metadata calls will fail.
|
||||
|
||||
# Check the public key file.
|
||||
role = properties.properties["role_full"]
|
||||
comment = f"This is a {role} key, keyid {self.tag}, for {zone}."
|
||||
if not isctest.check.file_contents_contain(self.keyfile, comment):
|
||||
isctest.log.debug(f"{self.name} COMMENT MISMATCH: expected '{comment}'")
|
||||
return False
|
||||
|
||||
ttl = properties.properties["dnskey_ttl"]
|
||||
flags = properties.properties["flags"]
|
||||
alg = properties.metadata["Algorithm"]
|
||||
dnskey = f"{zone}. {ttl} IN DNSKEY {flags} 3 {alg}"
|
||||
if not isctest.check.file_contents_contain(self.keyfile, dnskey):
|
||||
isctest.log.debug(f"{self.name} DNSKEY MISMATCH: expected '{dnskey}'")
|
||||
return False
|
||||
|
||||
# Now check the private key file.
|
||||
if properties.properties["private"]:
|
||||
# Retrieve creation date.
|
||||
created = self.get_metadata("Generated")
|
||||
|
||||
pval = self.get_metadata("Created", file=self.privatefile)
|
||||
if pval != created:
|
||||
isctest.log.debug(
|
||||
f"{self.name} Created METADATA MISMATCH: {pval} - {created}"
|
||||
)
|
||||
return False
|
||||
pval = self.get_metadata("Private-key-format", file=self.privatefile)
|
||||
if pval != "v1.3":
|
||||
isctest.log.debug(
|
||||
f"{self.name} Private-key-format METADATA MISMATCH: {pval} - v1.3"
|
||||
)
|
||||
return False
|
||||
pval = self.get_metadata("Algorithm", file=self.privatefile)
|
||||
if pval != f"{alg}":
|
||||
isctest.log.debug(
|
||||
f"{self.name} Algorithm METADATA MISMATCH: {pval} - {alg}"
|
||||
)
|
||||
return False
|
||||
|
||||
# Now check the key state file.
|
||||
if properties.properties["legacy"]:
|
||||
return True
|
||||
|
||||
comment = f"This is the state of key {self.tag}, for {zone}."
|
||||
if not isctest.check.file_contents_contain(self.statefile, comment):
|
||||
isctest.log.debug(f"{self.name} COMMENT MISMATCH: expected '{comment}'")
|
||||
return False
|
||||
|
||||
attributes = [
|
||||
"Lifetime",
|
||||
"Algorithm",
|
||||
"Length",
|
||||
"KSK",
|
||||
"ZSK",
|
||||
"GoalState",
|
||||
"DNSKEYState",
|
||||
"KRRSIGState",
|
||||
"ZRRSIGState",
|
||||
"DSState",
|
||||
]
|
||||
for key in attributes:
|
||||
if not self.match_metadata(key, properties.metadata):
|
||||
return False
|
||||
|
||||
# A match is found.
|
||||
return True
|
||||
|
||||
def match_timingmetadata(self, timings, file=None, comment=False):
|
||||
if file is None:
|
||||
file = self.statefile
|
||||
|
||||
attributes = [
|
||||
"Generated",
|
||||
"Created",
|
||||
"Published",
|
||||
"Publish",
|
||||
"PublishCDS",
|
||||
"SyncPublish",
|
||||
"Active",
|
||||
"Activate",
|
||||
"Retired",
|
||||
"Inactive",
|
||||
"Revoked",
|
||||
"Removed",
|
||||
"Delete",
|
||||
]
|
||||
for key in attributes:
|
||||
if not self.match_timing(key, timings, file, comment=comment):
|
||||
isctest.log.debug(f"{self.name} TIMING METADATA MISMATCH: {key}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __lt__(self, other: "Key"):
|
||||
return self.name < other.name
|
||||
|
||||
@@ -226,14 +557,14 @@ class Key:
|
||||
return self.path
|
||||
|
||||
|
||||
def check_zone_is_signed(server, zone):
|
||||
def check_zone_is_signed(server, zone, tsig=None):
|
||||
addr = server.ip
|
||||
fqdn = f"{zone}."
|
||||
|
||||
# wait until zone is fully signed
|
||||
signed = False
|
||||
for _ in range(10):
|
||||
response = _query(server, fqdn, dns.rdatatype.NSEC)
|
||||
response = _query(server, fqdn, dns.rdatatype.NSEC, tsig=tsig)
|
||||
if not isinstance(response, dns.message.Message):
|
||||
isctest.log.debug(f"no response for {fqdn} NSEC from {addr}")
|
||||
elif response.rcode() != dns.rcode.NOERROR:
|
||||
@@ -277,13 +608,111 @@ def check_zone_is_signed(server, zone):
|
||||
assert signed
|
||||
|
||||
|
||||
def check_dnssec_verify(server, zone):
|
||||
def check_keys(zone, keys, expected):
|
||||
# Checks keys for a configured zone. This verifies:
|
||||
# 1. The expected number of keys exist in 'keys'.
|
||||
# 2. The keys match the expected properties.
|
||||
|
||||
def _check_keys():
|
||||
# check number of keys matches expected.
|
||||
if len(keys) != len(expected):
|
||||
return False
|
||||
|
||||
if len(keys) == 0:
|
||||
return True
|
||||
|
||||
for expect in expected:
|
||||
expect.key = None
|
||||
|
||||
for key in keys:
|
||||
found = False
|
||||
i = 0
|
||||
while not found and i < len(expected):
|
||||
if expected[i].key is None:
|
||||
found = key.match_properties(zone, expected[i])
|
||||
if found:
|
||||
key.external = expected[i].properties["legacy"]
|
||||
expected[i].key = key
|
||||
i += 1
|
||||
if not found:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
isctest.run.retry_with_timeout(_check_keys, timeout=10)
|
||||
|
||||
|
||||
def check_keytimes(keys, expected):
|
||||
# Check the key timing metadata for all keys in 'keys'.
|
||||
assert len(keys) == len(expected)
|
||||
|
||||
if len(keys) == 0:
|
||||
return
|
||||
|
||||
for key in keys:
|
||||
for expect in expected:
|
||||
if expect.properties["legacy"]:
|
||||
continue
|
||||
|
||||
if not key is expect.key:
|
||||
continue
|
||||
|
||||
synonyms = {}
|
||||
if "Generated" in expect.timing:
|
||||
synonyms["Created"] = expect.timing["Generated"]
|
||||
if "Published" in expect.timing:
|
||||
synonyms["Publish"] = expect.timing["Published"]
|
||||
if "PublishCDS" in expect.timing:
|
||||
synonyms["SyncPublish"] = expect.timing["PublishCDS"]
|
||||
if "Active" in expect.timing:
|
||||
synonyms["Activate"] = expect.timing["Active"]
|
||||
if "Retired" in expect.timing:
|
||||
synonyms["Inactive"] = expect.timing["Retired"]
|
||||
if "DeleteCDS" in expect.timing:
|
||||
synonyms["SyncDelete"] = expect.timing["DeleteCDS"]
|
||||
if "Revoked" in expect.timing:
|
||||
synonyms["Revoked"] = expect.timing["Revoked"]
|
||||
if "Removed" in expect.timing:
|
||||
synonyms["Delete"] = expect.timing["Removed"]
|
||||
|
||||
assert key.match_timingmetadata(synonyms, file=key.keyfile, comment=True)
|
||||
if expect.properties["private"]:
|
||||
assert key.match_timingmetadata(synonyms, file=key.privatefile)
|
||||
if not expect.properties["legacy"]:
|
||||
assert key.match_timingmetadata(expect.timing)
|
||||
|
||||
state_changes = [
|
||||
"DNSKEYChange",
|
||||
"KRRSIGChange",
|
||||
"ZRRSIGChange",
|
||||
"DSChange",
|
||||
]
|
||||
for change in state_changes:
|
||||
assert key.has_metadata(change, expect.timing)
|
||||
|
||||
|
||||
def check_keyrelationships(keys, expected):
|
||||
# Check the key relationships (Successor and Predecessor metadata).
|
||||
for key in keys:
|
||||
for expect in expected:
|
||||
if expect.properties["legacy"]:
|
||||
continue
|
||||
|
||||
if not key is expect.key:
|
||||
continue
|
||||
|
||||
relationship_status = ["Predecessor", "Successor"]
|
||||
for status in relationship_status:
|
||||
assert key.match_metadata(status, expect.metadata)
|
||||
|
||||
|
||||
def check_dnssec_verify(server, zone, tsig=None):
|
||||
# Check if zone if DNSSEC valid with dnssec-verify.
|
||||
fqdn = f"{zone}."
|
||||
|
||||
verified = False
|
||||
for _ in range(10):
|
||||
transfer = _query(server, fqdn, dns.rdatatype.AXFR)
|
||||
transfer = _query(server, fqdn, dns.rdatatype.AXFR, tsig=tsig)
|
||||
if not isinstance(transfer, dns.message.Message):
|
||||
isctest.log.debug(f"no response for {fqdn} AXFR from {server.ip}")
|
||||
elif transfer.rcode() != dns.rcode.NOERROR:
|
||||
@@ -330,7 +759,9 @@ def check_dnssecstatus(server, zone, keys, policy=None, view=None):
|
||||
assert f"key: {key.tag}" in response
|
||||
|
||||
|
||||
def _check_signatures(signatures, covers, fqdn, keys):
|
||||
def _check_signatures(
|
||||
signatures, covers, fqdn, keys, offline_ksk=False, zsk_missing=False
|
||||
):
|
||||
now = KeyTimingMetadata.now()
|
||||
numsigs = 0
|
||||
zrrsig = True
|
||||
@@ -345,17 +776,31 @@ def _check_signatures(signatures, covers, fqdn, keys):
|
||||
active = now >= activate
|
||||
retired = inactive is not None and inactive <= now
|
||||
signing = active and not retired
|
||||
|
||||
krrsigstate = key.get_metadata("KRRSIGState", must_exist=False)
|
||||
ksigning = krrsigstate in ["rumoured", "omnipresent"]
|
||||
zrrsigstate = key.get_metadata("ZRRSIGState", must_exist=False)
|
||||
zsigning = zrrsigstate in ["rumoured", "omnipresent"]
|
||||
|
||||
if ksigning:
|
||||
assert key.is_ksk()
|
||||
if zsigning:
|
||||
assert key.is_zsk()
|
||||
|
||||
if zsk_missing:
|
||||
zsigning = not zsigning
|
||||
|
||||
if offline_ksk and signing and key.is_zsk():
|
||||
assert zsigning
|
||||
if offline_ksk and signing and key.is_ksk():
|
||||
ksigning = signing
|
||||
|
||||
alg = key.get_metadata("Algorithm")
|
||||
rtype = dns.rdatatype.to_text(covers)
|
||||
|
||||
expect = rf"IN RRSIG {rtype} {alg} (\d) (\d+) (\d+) (\d+) {key.tag} {fqdn}"
|
||||
|
||||
if not signing:
|
||||
for rrsig in signatures:
|
||||
assert re.search(expect, rrsig) is None
|
||||
continue
|
||||
|
||||
if zrrsig and key.is_zsk():
|
||||
if zrrsig and zsigning:
|
||||
has_rrsig = False
|
||||
for rrsig in signatures:
|
||||
if re.search(expect, rrsig) is not None:
|
||||
@@ -364,11 +809,11 @@ def _check_signatures(signatures, covers, fqdn, keys):
|
||||
assert has_rrsig, f"Expected signature but not found: {expect}"
|
||||
numsigs += 1
|
||||
|
||||
if zrrsig and not key.is_zsk():
|
||||
if zrrsig and not zsigning:
|
||||
for rrsig in signatures:
|
||||
assert re.search(expect, rrsig) is None
|
||||
|
||||
if krrsig and key.is_ksk():
|
||||
if krrsig and ksigning:
|
||||
has_rrsig = False
|
||||
for rrsig in signatures:
|
||||
if re.search(expect, rrsig) is not None:
|
||||
@@ -377,14 +822,16 @@ def _check_signatures(signatures, covers, fqdn, keys):
|
||||
assert has_rrsig, f"Expected signature but not found: {expect}"
|
||||
numsigs += 1
|
||||
|
||||
if krrsig and not key.is_ksk():
|
||||
if krrsig and not ksigning:
|
||||
for rrsig in signatures:
|
||||
assert re.search(expect, rrsig) is None
|
||||
|
||||
return numsigs
|
||||
|
||||
|
||||
def check_signatures(rrset, covers, fqdn, ksks, zsks):
|
||||
def check_signatures(
|
||||
rrset, covers, fqdn, ksks, zsks, offline_ksk=False, zsk_missing=False
|
||||
):
|
||||
# Check if signatures with covering type are signed with the right keys.
|
||||
# The right keys are the ones that expect a signature and have the
|
||||
# correct role.
|
||||
@@ -398,8 +845,12 @@ def check_signatures(rrset, covers, fqdn, ksks, zsks):
|
||||
rrsig = f"{rr.name} {rr.ttl} {rdclass} {rdtype} {rdata}"
|
||||
signatures.append(rrsig)
|
||||
|
||||
numsigs += _check_signatures(signatures, covers, fqdn, ksks)
|
||||
numsigs += _check_signatures(signatures, covers, fqdn, zsks)
|
||||
numsigs += _check_signatures(
|
||||
signatures, covers, fqdn, ksks, offline_ksk=offline_ksk, zsk_missing=zsk_missing
|
||||
)
|
||||
numsigs += _check_signatures(
|
||||
signatures, covers, fqdn, zsks, offline_ksk=offline_ksk, zsk_missing=zsk_missing
|
||||
)
|
||||
|
||||
assert numsigs == len(signatures)
|
||||
|
||||
@@ -415,9 +866,9 @@ def _check_dnskeys(dnskeys, keys, cdnskey=False):
|
||||
delete_md = f"Sync{delete_md}"
|
||||
|
||||
for key in keys:
|
||||
publish = key.get_timing(publish_md)
|
||||
publish = key.get_timing(publish_md, must_exist=False)
|
||||
delete = key.get_timing(delete_md, must_exist=False)
|
||||
published = now >= publish
|
||||
published = publish is not None and now >= publish
|
||||
removed = delete is not None and delete <= now
|
||||
|
||||
if not published or removed:
|
||||
@@ -502,8 +953,8 @@ def check_cds(rrset, keys):
|
||||
assert numcds == len(cdss)
|
||||
|
||||
|
||||
def _query_rrset(server, fqdn, qtype):
|
||||
response = _query(server, fqdn, qtype)
|
||||
def _query_rrset(server, fqdn, qtype, tsig=None):
|
||||
response = _query(server, fqdn, qtype, tsig=tsig)
|
||||
assert response.rcode() == dns.rcode.NOERROR
|
||||
|
||||
rrs = []
|
||||
@@ -523,46 +974,59 @@ def _query_rrset(server, fqdn, qtype):
|
||||
return rrs, rrsigs
|
||||
|
||||
|
||||
def check_apex(server, zone, ksks, zsks):
|
||||
def check_apex(
|
||||
server, zone, ksks, zsks, offline_ksk=False, zsk_missing=False, tsig=None
|
||||
):
|
||||
# Test the apex of a zone. This checks that the SOA and DNSKEY RRsets
|
||||
# are signed correctly and with the appropriate keys.
|
||||
fqdn = f"{zone}."
|
||||
|
||||
# test dnskey query
|
||||
dnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.DNSKEY)
|
||||
assert len(dnskeys) > 0
|
||||
dnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.DNSKEY, tsig=tsig)
|
||||
check_dnskeys(dnskeys, ksks, zsks)
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.DNSKEY, fqdn, ksks, zsks)
|
||||
check_signatures(
|
||||
rrsigs, dns.rdatatype.DNSKEY, fqdn, ksks, zsks, offline_ksk=offline_ksk
|
||||
)
|
||||
|
||||
# test soa query
|
||||
soa, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.SOA)
|
||||
soa, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.SOA, tsig=tsig)
|
||||
assert len(soa) == 1
|
||||
assert f"{zone}. {DEFAULT_TTL} IN SOA" in soa[0].to_text()
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.SOA, fqdn, ksks, zsks)
|
||||
check_signatures(
|
||||
rrsigs,
|
||||
dns.rdatatype.SOA,
|
||||
fqdn,
|
||||
ksks,
|
||||
zsks,
|
||||
offline_ksk=offline_ksk,
|
||||
zsk_missing=zsk_missing,
|
||||
)
|
||||
|
||||
# test cdnskey query
|
||||
cdnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDNSKEY)
|
||||
cdnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDNSKEY, tsig=tsig)
|
||||
check_dnskeys(cdnskeys, ksks, zsks, cdnskey=True)
|
||||
if len(cdnskeys) > 0:
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.CDNSKEY, fqdn, ksks, zsks)
|
||||
check_signatures(
|
||||
rrsigs, dns.rdatatype.CDNSKEY, fqdn, ksks, zsks, offline_ksk=offline_ksk
|
||||
)
|
||||
|
||||
# test cds query
|
||||
cds, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDS)
|
||||
cds, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDS, tsig=tsig)
|
||||
check_cds(cds, ksks)
|
||||
if len(cds) > 0:
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.CDS, fqdn, ksks, zsks)
|
||||
check_signatures(
|
||||
rrsigs, dns.rdatatype.CDS, fqdn, ksks, zsks, offline_ksk=offline_ksk
|
||||
)
|
||||
|
||||
|
||||
def check_subdomain(server, zone, ksks, zsks):
|
||||
def check_subdomain(server, zone, ksks, zsks, offline_ksk=False, tsig=None):
|
||||
# Test an RRset below the apex and verify it is signed correctly.
|
||||
fqdn = f"{zone}."
|
||||
qname = f"a.{zone}."
|
||||
qtype = dns.rdatatype.A
|
||||
response = _query(server, qname, qtype)
|
||||
response = _query(server, qname, qtype, tsig=tsig)
|
||||
assert response.rcode() == dns.rcode.NOERROR
|
||||
|
||||
match = f"{qname} {DEFAULT_TTL} IN A 10.0.0.1"
|
||||
@@ -575,5 +1039,271 @@ def check_subdomain(server, zone, ksks, zsks):
|
||||
else:
|
||||
assert match in rrset.to_text()
|
||||
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, qtype, fqdn, ksks, zsks, offline_ksk=offline_ksk)
|
||||
|
||||
|
||||
def check_update_is_signed(server, fqdn, qname, qtype, rdata, ksks, zsks, tsig=None):
|
||||
# Test an RRset below the apex and verify it is updated and signed correctly.
|
||||
response = _query(server, qname, qtype, tsig=tsig)
|
||||
|
||||
if response.rcode() != dns.rcode.NOERROR:
|
||||
return False
|
||||
|
||||
rrtype = dns.rdatatype.to_text(qtype)
|
||||
match = f"{qname} {DEFAULT_TTL} IN {rrtype} {rdata}"
|
||||
rrsigs = []
|
||||
for rrset in response.answer:
|
||||
if rrset.match(
|
||||
dns.name.from_text(qname), dns.rdataclass.IN, dns.rdatatype.RRSIG, qtype
|
||||
):
|
||||
rrsigs.append(rrset)
|
||||
elif not match in rrset.to_text():
|
||||
return False
|
||||
|
||||
if len(rrsigs) == 0:
|
||||
return False
|
||||
|
||||
# Zone is updated, ready to verify the signatures.
|
||||
check_signatures(rrsigs, qtype, fqdn, ksks, zsks)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def check_rrsig_is_refreshed(
|
||||
server, fqdn, zonefile, qname, qtype, ksks, zsks, tsig=None
|
||||
):
|
||||
# Verify signature for RRset has been refreshed.
|
||||
response = _query(server, qname, qtype, tsig=tsig)
|
||||
|
||||
if response.rcode() != dns.rcode.NOERROR:
|
||||
return False
|
||||
|
||||
rrtype = dns.rdatatype.to_text(qtype)
|
||||
match = f"{qname}. {DEFAULT_TTL} IN {rrtype}"
|
||||
rrsigs = []
|
||||
for rrset in response.answer:
|
||||
if rrset.match(
|
||||
dns.name.from_text(qname), dns.rdataclass.IN, dns.rdatatype.RRSIG, qtype
|
||||
):
|
||||
rrsigs.append(rrset)
|
||||
elif not match in rrset.to_text():
|
||||
return False
|
||||
|
||||
if len(rrsigs) == 0:
|
||||
return False
|
||||
|
||||
tmp_zonefile = f"{zonefile}.tmp"
|
||||
isctest.run.cmd(
|
||||
[
|
||||
os.environ["CHECKZONE"],
|
||||
"-D",
|
||||
"-q",
|
||||
"-o",
|
||||
tmp_zonefile,
|
||||
"-f",
|
||||
"raw",
|
||||
fqdn,
|
||||
zonefile,
|
||||
],
|
||||
)
|
||||
|
||||
zone = dns.zone.from_file(tmp_zonefile, fqdn)
|
||||
for rrsig in rrsigs:
|
||||
if isctest.check.zone_contains(zone, rrsig):
|
||||
return False
|
||||
|
||||
# Zone is updated, ready to verify the signatures.
|
||||
check_signatures(rrsigs, qtype, fqdn, ksks, zsks)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def check_rrsig_is_reused(server, fqdn, zonefile, qname, qtype, ksks, zsks, tsig=None):
|
||||
# Verify signature for RRset has been reused.
|
||||
response = _query(server, qname, qtype, tsig=tsig)
|
||||
|
||||
assert response.rcode() == dns.rcode.NOERROR
|
||||
|
||||
rrtype = dns.rdatatype.to_text(qtype)
|
||||
match = f"{qname}. {DEFAULT_TTL} IN {rrtype}"
|
||||
rrsigs = []
|
||||
for rrset in response.answer:
|
||||
if rrset.match(
|
||||
dns.name.from_text(qname), dns.rdataclass.IN, dns.rdatatype.RRSIG, qtype
|
||||
):
|
||||
rrsigs.append(rrset)
|
||||
else:
|
||||
assert match in rrset.to_text()
|
||||
|
||||
tmp_zonefile = f"{zonefile}.tmp"
|
||||
isctest.run.cmd(
|
||||
[
|
||||
os.environ["CHECKZONE"],
|
||||
"-D",
|
||||
"-q",
|
||||
"-o",
|
||||
tmp_zonefile,
|
||||
"-f",
|
||||
"raw",
|
||||
fqdn,
|
||||
zonefile,
|
||||
],
|
||||
)
|
||||
|
||||
zone = dns.zone.from_file(tmp_zonefile, dns.name.from_text(fqdn), relativize=False)
|
||||
for rrsig in rrsigs:
|
||||
assert isctest.check.zone_contains(zone, rrsig)
|
||||
|
||||
check_signatures(rrsigs, qtype, fqdn, ksks, zsks)
|
||||
|
||||
|
||||
def check_next_key_event(server, zone, next_event):
|
||||
if next_event is None:
|
||||
# No next key event check.
|
||||
return True
|
||||
|
||||
val = int(next_event.total_seconds())
|
||||
if val == 3600:
|
||||
waitfor = rf".*zone {zone}.*: next key event in (.*) seconds"
|
||||
else:
|
||||
# Don't want default loadkeys interval.
|
||||
waitfor = rf".*zone {zone}.*: next key event in (?!3600$)(.*) seconds"
|
||||
|
||||
with server.watch_log_from_start() as watcher:
|
||||
watcher.wait_for_line(re.compile(waitfor))
|
||||
|
||||
next_found = False
|
||||
minval = val - NEXT_KEY_EVENT_THRESHOLD
|
||||
maxval = val + NEXT_KEY_EVENT_THRESHOLD
|
||||
with open(f"{server.identifier}/named.run", "r", encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
match = re.match(waitfor, line)
|
||||
if match is not None:
|
||||
nextval = int(match.group(1))
|
||||
if minval <= nextval <= maxval:
|
||||
next_found = True
|
||||
break
|
||||
|
||||
isctest.log.debug(
|
||||
f"check next key event: expected {val} in: {line.strip()}"
|
||||
)
|
||||
|
||||
return next_found
|
||||
|
||||
|
||||
def keydir_to_keylist(
|
||||
zone: str, keydir: Optional[str] = None, in_use: Optional[bool] = False
|
||||
) -> List[Key]:
|
||||
# Retrieve all keys from the key files in a directory. If 'zone' is None,
|
||||
# retrieve all keys in the directory, otherwise only those matching the
|
||||
# zone name. If 'keydir' is None, search the current directory.
|
||||
if zone is None:
|
||||
zone = ""
|
||||
|
||||
all_keys = []
|
||||
if keydir is None:
|
||||
regex = rf"(K{zone}\.\+.*\+.*)\.key"
|
||||
for filename in glob.glob(f"K{zone}.+*+*.key"):
|
||||
match = re.match(regex, filename)
|
||||
if match is not None:
|
||||
all_keys.append(Key(match.group(1)))
|
||||
else:
|
||||
regex = rf"{keydir}/(K{zone}\.\+.*\+.*)\.key"
|
||||
for filename in glob.glob(f"{keydir}/K{zone}.+*+*.key"):
|
||||
match = re.match(regex, filename)
|
||||
if match is not None:
|
||||
all_keys.append(Key(match.group(1), keydir))
|
||||
|
||||
states = ["GoalState", "DNSKEYState", "KRRSIGState", "ZRRSIGState", "DSState"]
|
||||
|
||||
def used(kk):
|
||||
if not in_use:
|
||||
return True
|
||||
|
||||
for state in states:
|
||||
val = kk.get_metadata(state, must_exist=False)
|
||||
if val not in ["undefined", "hidden"]:
|
||||
isctest.log.debug(f"key {kk} in use")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
return [k for k in all_keys if used(k)]
|
||||
|
||||
|
||||
def keystr_to_keylist(keystr: str, keydir: Optional[str] = None) -> List[Key]:
|
||||
return [Key(name, keydir) for name in keystr.split()]
|
||||
|
||||
|
||||
def policy_to_properties(ttl, keys: List[str]) -> List[KeyProperties]:
|
||||
# Get the policies from a list of specially formatted strings.
|
||||
# The splitted line should result in the following items:
|
||||
# line[0]: Role
|
||||
# line[1]: Lifetime
|
||||
# line[2]: Algorithm
|
||||
# line[3]: Length
|
||||
# Then, optional data for specific tests may follow:
|
||||
# - "goal", "dnskey", "krrsig", "zrrsig", "ds", followed by a value,
|
||||
# sets the given state to the specific value
|
||||
# - "missing", set if the private key file for this key is not available.
|
||||
# - "offset", an offset for testing key rollover timings
|
||||
proplist = []
|
||||
count = 0
|
||||
for key in keys:
|
||||
count += 1
|
||||
line = key.split()
|
||||
keyprop = KeyProperties(f"KEY{count}", {}, {}, {})
|
||||
keyprop.properties["expect"] = True
|
||||
keyprop.properties["private"] = True
|
||||
keyprop.properties["legacy"] = False
|
||||
keyprop.properties["offset"] = timedelta(0)
|
||||
keyprop.properties["role"] = line[0]
|
||||
if line[0] == "zsk":
|
||||
keyprop.properties["role_full"] = "zone-signing"
|
||||
keyprop.properties["flags"] = 256
|
||||
keyprop.metadata["ZSK"] = "yes"
|
||||
keyprop.metadata["KSK"] = "no"
|
||||
else:
|
||||
keyprop.properties["role_full"] = "key-signing"
|
||||
keyprop.properties["flags"] = 257
|
||||
keyprop.metadata["ZSK"] = "yes" if line[0] == "csk" else "no"
|
||||
keyprop.metadata["KSK"] = "yes"
|
||||
|
||||
keyprop.properties["dnskey_ttl"] = ttl
|
||||
keyprop.metadata["Algorithm"] = line[2]
|
||||
keyprop.metadata["Length"] = line[3]
|
||||
keyprop.metadata["Lifetime"] = 0
|
||||
if line[1] != "unlimited":
|
||||
keyprop.metadata["Lifetime"] = int(line[1])
|
||||
|
||||
if len(line) > 4:
|
||||
i = 4
|
||||
while i < len(line):
|
||||
if line[i].startswith("goal:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["GoalState"] = keyval[1]
|
||||
elif line[i].startswith("dnskey:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["DNSKEYState"] = keyval[1]
|
||||
elif line[i].startswith("krrsig:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["KRRSIGState"] = keyval[1]
|
||||
elif line[i].startswith("zrrsig:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["ZRRSIGState"] = keyval[1]
|
||||
elif line[i].startswith("ds:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["DSState"] = keyval[1]
|
||||
elif line[i].startswith("offset:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.properties["offset"] = timedelta(seconds=int(keyval[1]))
|
||||
elif line[i] == "missing":
|
||||
keyprop.properties["private"] = False
|
||||
else:
|
||||
assert False, f"undefined optional data {line[i]}"
|
||||
|
||||
i += 1
|
||||
|
||||
proplist.append(keyprop)
|
||||
|
||||
return proplist
|
||||
|
||||
@@ -130,7 +130,7 @@ $KEYGEN -G -k rsasha256 -l policies/kasp.conf $zone >keygen.out.$zone.2 2>&1
|
||||
zone="multisigner-model2.kasp"
|
||||
echo_i "setting up zone: $zone"
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -f KSK -L 3600 -M 32768:65535 $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zone -M 32768:65535 2>keygen.out.$zone.2)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -M 32768:65535 $zone 2>keygen.out.$zone.2)
|
||||
cat "${KSK}.key" | grep -v ";.*" >>"${zone}.db"
|
||||
cat "${ZSK}.key" | grep -v ";.*" >>"${zone}.db"
|
||||
# Import the ZSK sets of the other providers into their DNSKEY RRset.
|
||||
@@ -200,13 +200,14 @@ $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1
|
||||
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
|
||||
$SIGNER -PS -z -x -s now-2w -e now-1mi -o $zone -f "${zonefile}" $infile >signer.out.$zone.1 2>&1
|
||||
|
||||
# Treat the next zones as if they were signed six months ago.
|
||||
T="now-6mo"
|
||||
keytimes="-P $T -A $T"
|
||||
|
||||
# These signatures are set to expire long in the past, update immediately.
|
||||
setup expired-sigs.autosign
|
||||
T="now-6mo"
|
||||
ksktimes="-P $T -A $T -P sync $T"
|
||||
zsktimes="-P $T -A $T"
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2)
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2)
|
||||
$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1
|
||||
$SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1
|
||||
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
|
||||
@@ -217,19 +218,18 @@ $SIGNER -PS -x -s now-2mo -e now-1mo -o $zone -O raw -f "${zonefile}.signed" $in
|
||||
|
||||
# The DNSKEY's TTLs do not match the policy.
|
||||
setup dnskey-ttl-mismatch.autosign
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 $zsktimes $zone 2>keygen.out.$zone.2)
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 $keytimes $zone 2>keygen.out.$zone.2)
|
||||
$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1
|
||||
$SETTIME -s -g $O -k $O $T -z $O $T "$ZSK " >settime.out.$zone.2 2>&1
|
||||
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
|
||||
cp $infile $zonefile
|
||||
$SIGNER -PS -x -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
|
||||
|
||||
# These signatures are still good, and can be reused.
|
||||
setup fresh-sigs.autosign
|
||||
T="now-6mo"
|
||||
ksktimes="-P $T -A $T -P sync $T"
|
||||
zsktimes="-P $T -A $T"
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2)
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2)
|
||||
$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1
|
||||
$SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1
|
||||
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
|
||||
@@ -240,11 +240,8 @@ $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infil
|
||||
|
||||
# These signatures are still good, but not fresh enough, update immediately.
|
||||
setup unfresh-sigs.autosign
|
||||
T="now-6mo"
|
||||
ksktimes="-P $T -A $T -P sync $T"
|
||||
zsktimes="-P $T -A $T"
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2)
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2)
|
||||
$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1
|
||||
$SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1
|
||||
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
|
||||
@@ -255,11 +252,13 @@ $SIGNER -S -x -s now-1w -e now+1w -o $zone -O raw -f "${zonefile}.signed" $infil
|
||||
|
||||
# These signatures are still good, but the private KSK is missing.
|
||||
setup ksk-missing.autosign
|
||||
T="now-6mo"
|
||||
ksktimes="-P $T -A $T -P sync $T"
|
||||
zsktimes="-P $T -A $T"
|
||||
# KSK file will be gone missing, so we set expected times during setup.
|
||||
TI="now+550d" # Lifetime of 2 years minus 6 months equals 550 days
|
||||
TD="now+13226h" # 550 days plus retire time of 1 day 2 hours equals 13226 hours
|
||||
TS="now-257755mi" # 6 months minus 1 day, 5 minutes equals 257695 minutes
|
||||
ksktimes="$keytimes -P sync $TS -I $TI -D $TD"
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2)
|
||||
$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1
|
||||
$SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1
|
||||
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
|
||||
@@ -274,10 +273,11 @@ rm -f "${KSK}".private
|
||||
|
||||
# These signatures are still good, but the private ZSK is missing.
|
||||
setup zsk-missing.autosign
|
||||
T="now-6mo"
|
||||
ksktimes="-P $T -A $T -P sync $T"
|
||||
zsktimes="-P $T -A $T"
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
|
||||
# ZSK file will be gone missing, so we set expected times during setup.
|
||||
TI="now+185d" # Lifetime of 1 year minus 6 months equals 185 days
|
||||
TD="now+277985mi" # 185 days plus retire time (sign delay, retire safety, propagation, zone TTL)
|
||||
zsktimes="$keytimes -I $TI -D $TD"
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2)
|
||||
$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1
|
||||
$SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1
|
||||
@@ -294,11 +294,8 @@ rm -f "${ZSK}".private
|
||||
# These signatures are still good, but the key files will be removed
|
||||
# before a second run of reconfiguring keys.
|
||||
setup keyfiles-missing.autosign
|
||||
T="now-6mo"
|
||||
ksktimes="-P $T -A $T -P sync $T"
|
||||
zsktimes="-P $T -A $T"
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2)
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
|
||||
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2)
|
||||
$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1
|
||||
$SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1
|
||||
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
|
||||
@@ -309,7 +306,6 @@ $SIGNER -S -x -s now-1w -e now+1w -o $zone -O raw -f "${zonefile}.signed" $infil
|
||||
|
||||
# These signatures are already expired, and the private ZSK is retired.
|
||||
setup zsk-retired.autosign
|
||||
T="now-6mo"
|
||||
ksktimes="-P $T -A $T -P sync $T"
|
||||
zsktimes="-P $T -A $T -I now"
|
||||
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
|
||||
@@ -350,10 +346,9 @@ setup step2.enable-dnssec.autosign
|
||||
TpubN="now-900s"
|
||||
# RRSIG TTL: 12 hour (43200 seconds)
|
||||
# zone-propagation-delay: 5 minutes (300 seconds)
|
||||
# retire-safety: 20 minutes (1200 seconds)
|
||||
# Already passed time: -900 seconds
|
||||
# Total: 43800 seconds
|
||||
TsbmN="now+43800s"
|
||||
# Total: 42600 seconds
|
||||
TsbmN="now+42600s"
|
||||
keytimes="-P ${TpubN} -P sync ${TsbmN} -A ${TpubN}"
|
||||
CSK=$($KEYGEN -k enable-dnssec -l policies/autosign.conf $keytimes $zone 2>keygen.out.$zone.1)
|
||||
$SETTIME -s -g $O -k $R $TpubN -r $R $TpubN -d $H $TpubN -z $R $TpubN "$CSK" >settime.out.$zone.1 2>&1
|
||||
@@ -365,10 +360,10 @@ $SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O raw -f "${zonefile}.signed" $i
|
||||
# Step 3:
|
||||
# The zone signatures have been published long enough to become OMNIPRESENT.
|
||||
setup step3.enable-dnssec.autosign
|
||||
# Passed time since publications: 43800 + 900 = 44700 seconds.
|
||||
TpubN="now-44700s"
|
||||
# Passed time since publications: 42600 + 900 = 43500 seconds.
|
||||
TpubN="now-43500s"
|
||||
# The key is secure for using in chain of trust when the DNSKEY is OMNIPRESENT.
|
||||
TcotN="now-43800s"
|
||||
TcotN="now-42600s"
|
||||
# We can submit the DS now.
|
||||
TsbmN="now"
|
||||
keytimes="-P ${TpubN} -P sync ${TsbmN} -A ${TpubN}"
|
||||
|
||||
@@ -127,9 +127,9 @@ setup step2.algorithm-roll.kasp
|
||||
# The time passed since the new algorithm keys have been introduced is 3 hours.
|
||||
TactN="now-3h"
|
||||
TpubN1="now-3h"
|
||||
# Tsbm(N+1) = TpubN1 + Ipub = now + TTLsig + Dprp + publish-safety =
|
||||
# now - 3h + 6h + 1h + 1h = now + 5h
|
||||
TsbmN1="now+5h"
|
||||
# Tsbm(N+1) = TpubN1 + Ipub = now + TTLsig + Dprp =
|
||||
# now - 3h + 6h + 1h = now + 4h
|
||||
TsbmN1="now+4h"
|
||||
ksk1times="-P ${TactN} -A ${TactN} -P sync ${TactN} -I now"
|
||||
zsk1times="-P ${TactN} -A ${TactN} -I now"
|
||||
ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
|
||||
@@ -156,11 +156,11 @@ $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infil
|
||||
# Step 3:
|
||||
# The zone signatures are also OMNIPRESENT.
|
||||
setup step3.algorithm-roll.kasp
|
||||
# The time passed since the new algorithm keys have been introduced is 9 hours.
|
||||
TactN="now-9h"
|
||||
TretN="now-6h"
|
||||
TpubN1="now-9h"
|
||||
TsbmN1="now-1h"
|
||||
# The time passed since the new algorithm keys have been introduced is 7 hours.
|
||||
TactN="now-7h"
|
||||
TretN="now-3h"
|
||||
TpubN1="now-7h"
|
||||
TsbmN1="now"
|
||||
ksk1times="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
zsk1times="-P ${TactN} -A ${TactN} -I ${TretN}"
|
||||
ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
|
||||
@@ -188,11 +188,11 @@ $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infil
|
||||
# The DS is swapped and can become OMNIPRESENT.
|
||||
setup step4.algorithm-roll.kasp
|
||||
# The time passed since the DS has been swapped is 29 hours.
|
||||
TactN="now-38h"
|
||||
TretN="now-35h"
|
||||
TpubN1="now-38h"
|
||||
TsbmN1="now-30h"
|
||||
TactN1="now-29h"
|
||||
TactN="now-36h"
|
||||
TretN="now-33h"
|
||||
TpubN1="now-36h"
|
||||
TsbmN1="now-29h"
|
||||
TactN1="now-27h"
|
||||
ksk1times="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
zsk1times="-P ${TactN} -A ${TactN} -I ${TretN}"
|
||||
ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
|
||||
@@ -220,12 +220,12 @@ $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infil
|
||||
# The DNSKEY is removed long enough to be HIDDEN.
|
||||
setup step5.algorithm-roll.kasp
|
||||
# The time passed since the DNSKEY has been removed is 2 hours.
|
||||
TactN="now-40h"
|
||||
TretN="now-37h"
|
||||
TactN="now-38h"
|
||||
TretN="now-35h"
|
||||
TremN="now-2h"
|
||||
TpubN1="now-40h"
|
||||
TsbmN1="now-32h"
|
||||
TactN1="now-31h"
|
||||
TpubN1="now-38h"
|
||||
TsbmN1="now-31h"
|
||||
TactN1="now-29h"
|
||||
ksk1times="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
zsk1times="-P ${TactN} -A ${TactN} -I ${TretN}"
|
||||
ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
|
||||
@@ -253,13 +253,13 @@ $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infil
|
||||
# The RRSIGs have been removed long enough to be HIDDEN.
|
||||
setup step6.algorithm-roll.kasp
|
||||
# Additional time passed: 7h.
|
||||
TactN="now-47h"
|
||||
TretN="now-44h"
|
||||
TactN="now-45h"
|
||||
TretN="now-42h"
|
||||
TremN="now-7h"
|
||||
TpubN1="now-47h"
|
||||
TsbmN1="now-39h"
|
||||
TactN1="now-38h"
|
||||
TdeaN="now-9h"
|
||||
TpubN1="now-45h"
|
||||
TsbmN1="now-38h"
|
||||
TactN1="now-36h"
|
||||
TdeaN="now-7h"
|
||||
ksk1times="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
zsk1times="-P ${TactN} -A ${TactN} -I ${TretN}"
|
||||
ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
|
||||
@@ -324,11 +324,11 @@ $SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $in
|
||||
# Step 3:
|
||||
# The zone signatures are also OMNIPRESENT.
|
||||
setup step3.csk-algorithm-roll.kasp
|
||||
# The time passed since the new algorithm keys have been introduced is 9 hours.
|
||||
TactN="now-9h"
|
||||
TretN="now-6h"
|
||||
TpubN1="now-9h"
|
||||
TactN1="now-6h"
|
||||
# The time passed since the new algorithm keys have been introduced is 7 hours.
|
||||
TactN="now-7h"
|
||||
TretN="now-3h"
|
||||
TpubN1="now-7h"
|
||||
TactN1="now-3h"
|
||||
csktimes="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
newtimes="-P ${TpubN1} -A ${TpubN1}"
|
||||
CSK1=$($KEYGEN -k csk-algoroll -l policies/csk1.conf $csktimes $zone 2>keygen.out.$zone.1)
|
||||
@@ -347,10 +347,10 @@ $SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $in
|
||||
# The DS is swapped and can become OMNIPRESENT.
|
||||
setup step4.csk-algorithm-roll.kasp
|
||||
# The time passed since the DS has been swapped is 29 hours.
|
||||
TactN="now-38h"
|
||||
TretN="now-35h"
|
||||
TpubN1="now-38h"
|
||||
TactN1="now-35h"
|
||||
TactN="now-36h"
|
||||
TretN="now-33h"
|
||||
TpubN1="now-36h"
|
||||
TactN1="now-33h"
|
||||
TsubN1="now-29h"
|
||||
csktimes="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
newtimes="-P ${TpubN1} -A ${TpubN1}"
|
||||
@@ -370,11 +370,11 @@ $SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $in
|
||||
# The DNSKEY is removed long enough to be HIDDEN.
|
||||
setup step5.csk-algorithm-roll.kasp
|
||||
# The time passed since the DNSKEY has been removed is 2 hours.
|
||||
TactN="now-40h"
|
||||
TretN="now-37h"
|
||||
TactN="now-38h"
|
||||
TretN="now-35h"
|
||||
TremN="now-2h"
|
||||
TpubN1="now-40h"
|
||||
TactN1="now-37h"
|
||||
TpubN1="now-38h"
|
||||
TactN1="now-35h"
|
||||
TsubN1="now-31h"
|
||||
csktimes="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
newtimes="-P ${TpubN1} -A ${TpubN1}"
|
||||
@@ -394,12 +394,12 @@ $SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $in
|
||||
# The RRSIGs have been removed long enough to be HIDDEN.
|
||||
setup step6.csk-algorithm-roll.kasp
|
||||
# Additional time passed: 7h.
|
||||
TactN="now-47h"
|
||||
TretN="now-44h"
|
||||
TactN="now-45h"
|
||||
TretN="now-42h"
|
||||
TdeaN="now-9h"
|
||||
TremN="now-7h"
|
||||
TpubN1="now-47h"
|
||||
TactN1="now-44h"
|
||||
TpubN1="now-45h"
|
||||
TactN1="now-42h"
|
||||
TsubN1="now-38h"
|
||||
csktimes="-P ${TactN} -A ${TactN} -P sync ${TactN} -I ${TretN}"
|
||||
newtimes="-P ${TpubN1} -A ${TpubN1}"
|
||||
|
||||
28
bin/tests/system/kasp/prereq.sh
Normal file
28
bin/tests/system/kasp/prereq.sh
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 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.
|
||||
|
||||
. ../conf.sh
|
||||
|
||||
if test -n "$PYTHON"; then
|
||||
if $PYTHON -c "from dns.update import UpdateMessage" 2>/dev/null; then
|
||||
:
|
||||
else
|
||||
echo_i "This test requires the dnspython >= 2.0.0 module." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo_i "This test requires Python and the dnspython module." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
File diff suppressed because it is too large
Load Diff
1054
bin/tests/system/kasp/tests_kasp.py
Normal file
1054
bin/tests/system/kasp/tests_kasp.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,19 +10,14 @@
|
||||
# information regarding copyright ownership.
|
||||
|
||||
from datetime import timedelta
|
||||
import difflib
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
from typing import List, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
import isctest
|
||||
from isctest.kasp import (
|
||||
Key,
|
||||
KeyTimingMetadata,
|
||||
)
|
||||
from isctest.kasp import KeyTimingMetadata
|
||||
|
||||
pytestmark = pytest.mark.extra_artifacts(
|
||||
[
|
||||
@@ -89,31 +84,6 @@ def between(value, start, end):
|
||||
return start < value < end
|
||||
|
||||
|
||||
def check_file_contents_equal(file1, file2):
|
||||
def normalize_line(line):
|
||||
# remove trailing&leading whitespace and replace multiple whitespaces
|
||||
return " ".join(line.split())
|
||||
|
||||
def read_lines(file_path):
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
return [normalize_line(line) for line in file.readlines()]
|
||||
|
||||
lines1 = read_lines(file1)
|
||||
lines2 = read_lines(file2)
|
||||
|
||||
differ = difflib.Differ()
|
||||
diff = differ.compare(lines1, lines2)
|
||||
|
||||
for line in diff:
|
||||
assert not line.startswith("+ ") and not line.startswith(
|
||||
"- "
|
||||
), f'file contents of "{file1}" and "{file2}" differ'
|
||||
|
||||
|
||||
def keystr_to_keylist(keystr: str, keydir: Optional[str] = None) -> List[Key]:
|
||||
return [Key(name, keydir) for name in keystr.split()]
|
||||
|
||||
|
||||
def ksr(zone, policy, action, options="", raise_on_exception=True):
|
||||
ksr_command = [
|
||||
os.environ.get("KSR"),
|
||||
@@ -515,14 +485,14 @@ def test_ksr_common(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None)
|
||||
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
out, _ = ksr(zone, policy, "keygen", options="-i now -e +1y")
|
||||
zsks = keystr_to_keylist(out)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out)
|
||||
assert len(zsks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -532,7 +502,7 @@ def test_ksr_common(servers):
|
||||
# in the given key directory
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -575,18 +545,22 @@ def test_ksr_common(servers):
|
||||
# check that 'dnssec-ksr keygen' selects pregenerated keys for
|
||||
# the same time bundle
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +1y")
|
||||
selected_zsks = keystr_to_keylist(out, zskdir)
|
||||
selected_zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(selected_zsks) == 2
|
||||
for index, key in enumerate(selected_zsks):
|
||||
assert zsks[index] == key
|
||||
check_file_contents_equal(f"{key.path}.private", f"{key.path}.private.backup")
|
||||
check_file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
|
||||
check_file_contents_equal(f"{key.path}.state", f"{key.path}.state.backup")
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.private", f"{key.path}.private.backup"
|
||||
)
|
||||
isctest.check.file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.state", f"{key.path}.state.backup"
|
||||
)
|
||||
|
||||
# check that 'dnssec-ksr keygen' generates only necessary keys for
|
||||
# overlapping time bundle
|
||||
out, err = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +2y -v 1")
|
||||
overlapping_zsks = keystr_to_keylist(out, zskdir)
|
||||
overlapping_zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(overlapping_zsks) == 4
|
||||
|
||||
verbose = err.split()
|
||||
@@ -602,15 +576,19 @@ def test_ksr_common(servers):
|
||||
for index, key in enumerate(overlapping_zsks):
|
||||
if index < 2:
|
||||
assert zsks[index] == key
|
||||
check_file_contents_equal(
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.private", f"{key.path}.private.backup"
|
||||
)
|
||||
check_file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
|
||||
check_file_contents_equal(f"{key.path}.state", f"{key.path}.state.backup")
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.key", f"{key.path}.key.backup"
|
||||
)
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.state", f"{key.path}.state.backup"
|
||||
)
|
||||
|
||||
# run 'dnssec-ksr keygen' again with verbosity 0
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +2y")
|
||||
overlapping_zsks2 = keystr_to_keylist(out, zskdir)
|
||||
overlapping_zsks2 = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(overlapping_zsks2) == 4
|
||||
check_keys(overlapping_zsks2, lifetime)
|
||||
for index, key in enumerate(overlapping_zsks2):
|
||||
@@ -691,9 +669,9 @@ def test_ksr_common(servers):
|
||||
# - check keys
|
||||
check_keys(overlapping_zsks, lifetime, with_state=True)
|
||||
# - check apex
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, overlapping_zsks)
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, overlapping_zsks, offline_ksk=True)
|
||||
# - check subdomain
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, overlapping_zsks)
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, overlapping_zsks, offline_ksk=True)
|
||||
|
||||
|
||||
def test_ksr_lastbundle(servers):
|
||||
@@ -705,7 +683,7 @@ def test_ksr_lastbundle(servers):
|
||||
kskdir = "ns1/offline"
|
||||
offset = -timedelta(days=365)
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i -1y -e +1d -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None, offset=offset)
|
||||
@@ -713,7 +691,7 @@ def test_ksr_lastbundle(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i -1y -e +1d")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -766,9 +744,9 @@ def test_ksr_lastbundle(servers):
|
||||
# - check keys
|
||||
check_keys(zsks, lifetime, offset=offset, with_state=True)
|
||||
# - check apex
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, zsks)
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True)
|
||||
# - check subdomain
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, zsks)
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True)
|
||||
|
||||
# check that last bundle warning is logged
|
||||
warning = "last bundle in skr, please import new skr file"
|
||||
@@ -784,7 +762,7 @@ def test_ksr_inthemiddle(servers):
|
||||
kskdir = "ns1/offline"
|
||||
offset = -timedelta(days=365)
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i -1y -e +1y -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None, offset=offset)
|
||||
@@ -792,7 +770,7 @@ def test_ksr_inthemiddle(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i -1y -e +1y")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 4
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -846,9 +824,9 @@ def test_ksr_inthemiddle(servers):
|
||||
# - check keys
|
||||
check_keys(zsks, lifetime, offset=offset, with_state=True)
|
||||
# - check apex
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, zsks)
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True)
|
||||
# - check subdomain
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, zsks)
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True)
|
||||
|
||||
# check that no last bundle warning is logged
|
||||
warning = "last bundle in skr, please import new skr file"
|
||||
@@ -864,13 +842,13 @@ def check_ksr_rekey_logs_error(server, zone, policy, offset, end):
|
||||
then = now + offset
|
||||
until = now + end
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i {then} -e {until} -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
# key generation
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {then} -e {until}")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 2
|
||||
|
||||
# create request
|
||||
@@ -937,7 +915,7 @@ def test_ksr_unlimited(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +2y -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None)
|
||||
@@ -945,7 +923,7 @@ def test_ksr_unlimited(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +2y")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 1
|
||||
|
||||
lifetime = None
|
||||
@@ -1041,9 +1019,9 @@ def test_ksr_unlimited(servers):
|
||||
# - check keys
|
||||
check_keys(zsks, lifetime, with_state=True)
|
||||
# - check apex
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, zsks)
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True)
|
||||
# - check subdomain
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, zsks)
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True)
|
||||
|
||||
|
||||
def test_ksr_twotone(servers):
|
||||
@@ -1054,7 +1032,7 @@ def test_ksr_twotone(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 2
|
||||
|
||||
ksks_defalg = []
|
||||
@@ -1078,7 +1056,7 @@ def test_ksr_twotone(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
# First algorithm keys have a lifetime of 3 months, so there should
|
||||
# be 4 created keys. Second algorithm keys have a lifetime of 5
|
||||
# months, so there should be 3 created keys. While only two time
|
||||
@@ -1159,9 +1137,9 @@ def test_ksr_twotone(servers):
|
||||
lifetime = timedelta(days=31 * 5)
|
||||
check_keys(zsks_altalg, lifetime, alg, size, with_state=True)
|
||||
# - check apex
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, zsks)
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True)
|
||||
# - check subdomain
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, zsks)
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True)
|
||||
|
||||
|
||||
def test_ksr_kskroll(servers):
|
||||
@@ -1172,7 +1150,7 @@ def test_ksr_kskroll(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -1181,7 +1159,7 @@ def test_ksr_kskroll(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 1
|
||||
|
||||
check_keys(zsks, None)
|
||||
@@ -1233,6 +1211,6 @@ def test_ksr_kskroll(servers):
|
||||
# - check keys
|
||||
check_keys(zsks, None, with_state=True)
|
||||
# - check apex
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, zsks)
|
||||
isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True)
|
||||
# - check subdomain
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, zsks)
|
||||
isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True)
|
||||
|
||||
109
lib/dns/keymgr.c
109
lib/dns/keymgr.c
@@ -189,13 +189,19 @@ dns_keymgr_settime_syncpublish(dst_key_t *key, dns_kasp_t *kasp, bool first) {
|
||||
isc_stdtime_t zrrsig_present;
|
||||
dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true);
|
||||
zrrsig_present = published + ttlsig +
|
||||
dns_kasp_zonepropagationdelay(kasp) +
|
||||
dns_kasp_publishsafety(kasp);
|
||||
dns_kasp_zonepropagationdelay(kasp);
|
||||
if (zrrsig_present > syncpublish) {
|
||||
syncpublish = zrrsig_present;
|
||||
}
|
||||
}
|
||||
dst_key_settime(key, DST_TIME_SYNCPUBLISH, syncpublish);
|
||||
|
||||
uint32_t lifetime = 0;
|
||||
ret = dst_key_getnum(key, DST_NUM_LIFETIME, &lifetime);
|
||||
if (ret == ISC_R_SUCCESS && lifetime > 0) {
|
||||
dst_key_settime(key, DST_TIME_SYNCDELETE,
|
||||
(syncpublish + lifetime));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -243,6 +249,17 @@ keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
|
||||
pub = now;
|
||||
}
|
||||
|
||||
/*
|
||||
* To calculate phase out times ("Retired", "Removed", ...),
|
||||
* the key lifetime is required.
|
||||
*/
|
||||
uint32_t klifetime = 0;
|
||||
ret = dst_key_getnum(key->key, DST_NUM_LIFETIME, &klifetime);
|
||||
if (ret != ISC_R_SUCCESS) {
|
||||
dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime);
|
||||
klifetime = lifetime;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate prepublication time.
|
||||
*/
|
||||
@@ -272,13 +289,16 @@ keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
|
||||
dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp,
|
||||
true);
|
||||
syncpub2 = pub + ttlsig +
|
||||
dns_kasp_publishsafety(kasp) +
|
||||
dns_kasp_zonepropagationdelay(kasp);
|
||||
}
|
||||
|
||||
syncpub = ISC_MAX(syncpub1, syncpub2);
|
||||
dst_key_settime(key->key, DST_TIME_SYNCPUBLISH,
|
||||
syncpub);
|
||||
if (klifetime > 0) {
|
||||
dst_key_settime(key->key, DST_TIME_SYNCDELETE,
|
||||
(syncpub + klifetime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,13 +311,6 @@ keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
|
||||
|
||||
ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
|
||||
if (ret != ISC_R_SUCCESS) {
|
||||
uint32_t klifetime = 0;
|
||||
|
||||
ret = dst_key_getnum(key->key, DST_NUM_LIFETIME, &klifetime);
|
||||
if (ret != ISC_R_SUCCESS) {
|
||||
dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime);
|
||||
klifetime = lifetime;
|
||||
}
|
||||
if (klifetime == 0) {
|
||||
/*
|
||||
* No inactive time and no lifetime,
|
||||
@@ -398,7 +411,7 @@ keymgr_key_update_lifetime(dns_dnsseckey_t *key, dns_kasp_t *kasp,
|
||||
/* Initialize lifetime. */
|
||||
if (r != ISC_R_SUCCESS) {
|
||||
dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime);
|
||||
return;
|
||||
l = lifetime - 1;
|
||||
}
|
||||
/* Skip keys that are still hidden or already retiring. */
|
||||
if (g != OMNIPRESENT) {
|
||||
@@ -420,6 +433,7 @@ keymgr_key_update_lifetime(dns_dnsseckey_t *key, dns_kasp_t *kasp,
|
||||
} else {
|
||||
dst_key_unsettime(key->key, DST_TIME_INACTIVE);
|
||||
dst_key_unsettime(key->key, DST_TIME_DELETE);
|
||||
dst_key_unsettime(key->key, DST_TIME_SYNCDELETE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1286,6 +1300,7 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
||||
isc_result_t ret;
|
||||
isc_stdtime_t lastchange, dstime, nexttime = now;
|
||||
dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true);
|
||||
uint32_t dsstate;
|
||||
|
||||
/*
|
||||
* No need to wait if we move things into an uncertain state.
|
||||
@@ -1355,15 +1370,12 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
||||
* records. This translates to:
|
||||
*
|
||||
* Dsgn + zone-propagation-delay + max-zone-ttl.
|
||||
*
|
||||
* We will also add the retire-safety interval.
|
||||
*/
|
||||
nexttime = lastchange + ttlsig +
|
||||
dns_kasp_zonepropagationdelay(kasp) +
|
||||
dns_kasp_retiresafety(kasp);
|
||||
dns_kasp_zonepropagationdelay(kasp);
|
||||
/*
|
||||
* Only add the sign delay Dsgn if there is an actual
|
||||
* predecessor or successor key.
|
||||
* Only add the sign delay Dsgn and retire-safety if
|
||||
* there is an actual predecessor or successor key.
|
||||
*/
|
||||
uint32_t tag;
|
||||
ret = dst_key_getnum(key->key, DST_NUM_PREDECESSOR,
|
||||
@@ -1373,7 +1385,8 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
||||
DST_NUM_SUCCESSOR, &tag);
|
||||
}
|
||||
if (ret == ISC_R_SUCCESS) {
|
||||
nexttime += dns_kasp_signdelay(kasp);
|
||||
nexttime += dns_kasp_signdelay(kasp) +
|
||||
dns_kasp_retiresafety(kasp);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1399,35 +1412,36 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
||||
* This translates to:
|
||||
*
|
||||
* parent-propagation-delay + parent-ds-ttl.
|
||||
*
|
||||
* We will also add the retire-safety interval.
|
||||
*/
|
||||
case OMNIPRESENT:
|
||||
/* Make sure DS has been seen in the parent. */
|
||||
ret = dst_key_gettime(key->key, DST_TIME_DSPUBLISH,
|
||||
&dstime);
|
||||
if (ret != ISC_R_SUCCESS || dstime > now) {
|
||||
/* Not yet, try again in an hour. */
|
||||
nexttime = now + 3600;
|
||||
} else {
|
||||
nexttime =
|
||||
dstime + dns_kasp_dsttl(kasp) +
|
||||
dns_kasp_parentpropagationdelay(kasp) +
|
||||
dns_kasp_retiresafety(kasp);
|
||||
}
|
||||
break;
|
||||
case HIDDEN:
|
||||
/* Make sure DS has been withdrawn from the parent. */
|
||||
ret = dst_key_gettime(key->key, DST_TIME_DSDELETE,
|
||||
&dstime);
|
||||
/* Make sure DS has been seen in/withdrawn from the
|
||||
* parent. */
|
||||
dsstate = next_state == HIDDEN ? DST_TIME_DSDELETE
|
||||
: DST_TIME_DSPUBLISH;
|
||||
ret = dst_key_gettime(key->key, dsstate, &dstime);
|
||||
if (ret != ISC_R_SUCCESS || dstime > now) {
|
||||
/* Not yet, try again in an hour. */
|
||||
nexttime = now + 3600;
|
||||
} else {
|
||||
nexttime =
|
||||
dstime + dns_kasp_dsttl(kasp) +
|
||||
dns_kasp_parentpropagationdelay(kasp) +
|
||||
dns_kasp_retiresafety(kasp);
|
||||
dns_kasp_parentpropagationdelay(kasp);
|
||||
/*
|
||||
* Only add the retire-safety if there is an
|
||||
* actual predecessor or successor key.
|
||||
*/
|
||||
uint32_t tag;
|
||||
ret = dst_key_getnum(key->key,
|
||||
DST_NUM_PREDECESSOR, &tag);
|
||||
if (ret != ISC_R_SUCCESS) {
|
||||
ret = dst_key_getnum(key->key,
|
||||
DST_NUM_SUCCESSOR,
|
||||
&tag);
|
||||
}
|
||||
if (ret == ISC_R_SUCCESS) {
|
||||
nexttime += dns_kasp_retiresafety(kasp);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1763,7 +1777,9 @@ keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key,
|
||||
if (prepub == 0 || prepub > now) {
|
||||
/* No need to start rollover now. */
|
||||
if (*nexttime == 0 || prepub < *nexttime) {
|
||||
*nexttime = prepub;
|
||||
if (prepub > 0) {
|
||||
*nexttime = prepub;
|
||||
}
|
||||
}
|
||||
return ISC_R_SUCCESS;
|
||||
}
|
||||
@@ -2022,6 +2038,20 @@ keymgr_purge_keyfile(dst_key_t *key, int type) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
dst_key_doublematch(dns_dnsseckey_t *key, dns_kasp_t *kasp) {
|
||||
int matches = 0;
|
||||
|
||||
for (dns_kasp_key_t *kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp));
|
||||
kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link))
|
||||
{
|
||||
if (dns_kasp_key_match(kkey, key)) {
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
return matches > 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Examine 'keys' and match 'kasp' policy.
|
||||
*
|
||||
@@ -2161,6 +2191,7 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
|
||||
* matches the kasp policy.
|
||||
*/
|
||||
if (!dst_key_is_unused(dkey->key) &&
|
||||
!dst_key_doublematch(dkey, kasp) &&
|
||||
(dst_key_goal(dkey->key) ==
|
||||
OMNIPRESENT) &&
|
||||
!keymgr_dep(dkey->key, keyring,
|
||||
|
||||
Reference in New Issue
Block a user