Merge branch '1612-rndc-dnssec-status' into 'main'

Resolve "Get current state of DNSSEC keys (kasp) via rndc"

Closes #1612

See merge request isc-projects/bind9!3717
This commit is contained in:
Matthijs Mekking 2020-06-30 09:12:48 +00:00
commit 3970e6e832
22 changed files with 537 additions and 13 deletions

View file

@ -1,3 +1,5 @@
5451. [func] Add 'rndc dnssec -status' command. [GL #1612]
5450. [placeholder]
5449. [bug] Fix a socket shutdown race in netmgr udp. [GL #1938]

View file

@ -209,6 +209,8 @@ named_control_docommand(isccc_sexpr_t *message, bool readonly,
result = named_server_changezone(named_g_server, cmdline, text);
} else if (command_compare(command, NAMED_COMMAND_DELZONE)) {
result = named_server_delzone(named_g_server, lex, text);
} else if (command_compare(command, NAMED_COMMAND_DNSSEC)) {
result = named_server_dnssec(named_g_server, lex, text);
} else if (command_compare(command, NAMED_COMMAND_DNSTAP) ||
command_compare(command, NAMED_COMMAND_DNSTAPREOPEN))
{

View file

@ -61,6 +61,7 @@
#define NAMED_COMMAND_SHOWZONE "showzone"
#define NAMED_COMMAND_SYNC "sync"
#define NAMED_COMMAND_SIGNING "signing"
#define NAMED_COMMAND_DNSSEC "dnssec"
#define NAMED_COMMAND_ZONESTATUS "zonestatus"
#define NAMED_COMMAND_NTA "nta"
#define NAMED_COMMAND_TESTGEN "testgen"

View file

@ -333,6 +333,13 @@ isc_result_t
named_server_signing(named_server_t *server, isc_lex_t *lex,
isc_buffer_t **text);
/*%
* Lists the DNSSEC status for a given zone.
*/
isc_result_t
named_server_dnssec(named_server_t *server, isc_lex_t *lex,
isc_buffer_t **text);
/*%
* Lists status information for a given zone (e.g., name, type, files,
* load time, expiry, etc).

View file

@ -59,6 +59,7 @@
#include <dns/dlz.h>
#include <dns/dns64.h>
#include <dns/dnsrps.h>
#include <dns/dnssec.h>
#include <dns/dyndb.h>
#include <dns/events.h>
#include <dns/fixedname.h>
@ -66,6 +67,7 @@
#include <dns/geoip.h>
#include <dns/journal.h>
#include <dns/kasp.h>
#include <dns/keymgr.h>
#include <dns/keytable.h>
#include <dns/keyvalues.h>
#include <dns/lib.h>
@ -14463,6 +14465,83 @@ cleanup:
return (result);
}
isc_result_t
named_server_dnssec(named_server_t *server, isc_lex_t *lex,
isc_buffer_t **text) {
isc_result_t result = ISC_R_SUCCESS;
dns_zone_t *zone = NULL;
dns_kasp_t *kasp = NULL;
dns_dnsseckeylist_t keys;
dns_dnsseckey_t *key;
const char *ptr;
/* variables for -status */
char output[BUFSIZ];
isc_stdtime_t now;
isc_time_t timenow;
const char *dir;
/* Skip the command name. */
ptr = next_token(lex, text);
if (ptr == NULL) {
return (ISC_R_UNEXPECTEDEND);
}
/* Find out what we are to do. */
ptr = next_token(lex, text);
if (ptr == NULL) {
return (ISC_R_UNEXPECTEDEND);
}
if (strcasecmp(ptr, "-status") != 0) {
return (DNS_R_SYNTAX);
}
ISC_LIST_INIT(keys);
CHECK(zone_from_args(server, lex, NULL, &zone, NULL, text, false));
if (zone == NULL) {
CHECK(ISC_R_UNEXPECTEDEND);
}
kasp = dns_zone_getkasp(zone);
if (kasp == NULL) {
CHECK(putstr(text, "zone does not have dnssec-policy"));
CHECK(putnull(text));
goto cleanup;
}
/* -status */
TIME_NOW(&timenow);
now = isc_time_seconds(&timenow);
dir = dns_zone_getkeydirectory(zone);
LOCK(&kasp->lock);
result = dns_dnssec_findmatchingkeys(dns_zone_getorigin(zone), dir, now,
dns_zone_getmctx(zone), &keys);
UNLOCK(&kasp->lock);
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
goto cleanup;
}
LOCK(&kasp->lock);
dns_keymgr_status(kasp, &keys, now, &output[0], sizeof(output));
UNLOCK(&kasp->lock);
CHECK(putstr(text, output));
CHECK(putnull(text));
cleanup:
while (!ISC_LIST_EMPTY(keys)) {
key = ISC_LIST_HEAD(keys);
ISC_LIST_UNLINK(keys, key, link);
dns_dnsseckey_destroy(dns_zone_getmctx(zone), &key);
}
if (zone != NULL) {
dns_zone_detach(&zone);
}
return (result);
}
static isc_result_t
putmem(isc_buffer_t **b, const char *str, size_t len) {
isc_result_t result;

View file

@ -105,6 +105,9 @@ command is one of the following:\n\
Add zone to given view. Requires allow-new-zones option.\n\
delzone [-clean] zone [class [view]]\n\
Removes zone from given view.\n\
dnssec -status zone [class [view]]\n\
Show the DNSSEC signing state for the specified zone.\n\
Requires the zone to have a dnssec-policy.\n\
dnstap -reopen\n\
Close, truncate and re-open the DNSTAP output file.\n\
dnstap -roll count\n\

View file

@ -162,6 +162,10 @@ Currently supported commands are:
See also ``rndc addzone`` and ``rndc modzone``.
``dnssec`` [**-status** *zone* [*class* [*view*]]
Show the DNSSEC signing state for the specified zone. Requires the
zone to have a "dnssec-policy".
``dnstap`` ( **-reopen** | **-roll** [*number*] )
Close and re-open DNSTAP output files. ``rndc dnstap -reopen`` allows
the output file to be renamed externally, so that :manpage:`named(8)` can

View file

@ -24,4 +24,5 @@ rm -f ns*/managed-keys.bind
rm -f ns*/*.mkeys
rm -f ns*/zones ns*/*.db.infile
rm -f *.created published.test* retired.test*
rm -f rndc.dnssec.status.out.*
rm -f python.out.*

View file

@ -11,6 +11,15 @@
// NS4
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
key "sha1" {
algorithm "hmac-sha1";
secret "FrSt77yPTFx6hTs4i2tKLB9LmE0=";

View file

@ -11,6 +11,15 @@
// NS5
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.5 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
key "sha1" {
algorithm "hmac-sha1";
secret "FrSt77yPTFx6hTs4i2tKLB9LmE0=";

View file

@ -919,6 +919,46 @@ check_keys()
status=$((status+ret))
}
# Call rndc dnssec -status on server $1 for zone $2 and check output.
# This is a loose verification, it just tests if the right policy
# name is returned, and if all expected keys are listed. The rndc
# dnssec -status output also lists whether a key is published,
# used for signing, is retired, or is removed, and if not when
# it is scheduled to do so, and it shows the states for the various
# DNSSEC records.
check_dnssecstatus() {
_server=$1
_zone=$2
_view=$3
n=$((n+1))
echo_i "check rndc dnssec -status output for ${_zone} ($n)"
ret=0
rndccmd $_server dnssec -status $_zone in $_view > rndc.dnssec.status.out.$_zone.$n || log_error "rndc dnssec -status zone ${_zone} failed"
if [ "$POLICY" = "none" ]; then
grep "zone does not have dnssec-policy" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "bad dnssec status for zone ${_zone}"
else
grep "dnssec-policy: ${POLICY}" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "bad dnssec status for zone ${_zone}"
if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
grep "key: $(key_get KEY1 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "missing key $(key_get KEY1 ID) from dnssec status"
fi
if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
grep "key: $(key_get KEY2 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "missing key $(key_get KEY2 ID) from dnssec status"
fi
if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
grep "key: $(key_get KEY3 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "missing key $(key_get KEY3 ID) from dnssec status"
fi
if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
grep "key: $(key_get KEY4 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "missing key $(key_get KEY4 ID) from dnssec status"
fi
fi
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
}
# Check if RRset of type $1 in file $2 is signed with the right keys.
# The right keys are the ones that expect a signature and matches the role $3.
check_signatures() {
@ -1165,6 +1205,7 @@ set_keystate "KEY1" "STATE_ZRRSIG" "rumoured"
set_keystate "KEY1" "STATE_DS" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -1206,6 +1247,7 @@ set_policy "default" "1" "3600"
set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -1238,6 +1280,7 @@ set_policy "default" "1" "3600"
set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -1265,6 +1308,7 @@ set_policy "default" "1" "3600"
set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -1390,6 +1434,7 @@ set_keystate "KEY3" "STATE_ZRRSIG" "rumoured"
key_clear "KEY4"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy
check_keytimes
check_apex
@ -1409,6 +1454,7 @@ key_clear "KEY3"
key_clear "KEY4"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
check_apex
check_subdomain
@ -1432,6 +1478,7 @@ set_keystate "KEY1" "STATE_ZRRSIG" "rumoured"
set_keystate "KEY1" "STATE_DS" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -1484,6 +1531,7 @@ set_keystate "KEY3" "STATE_ZRRSIG" "rumoured"
key_clear "KEY4"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy
check_keytimes
check_apex
@ -1499,6 +1547,7 @@ set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy
check_keytimes
check_apex
@ -1514,6 +1563,7 @@ set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy "pregenerated"
check_keytimes
check_apex
@ -1529,6 +1579,7 @@ set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy
check_keytimes
check_apex
@ -1546,6 +1597,7 @@ set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy "pregenerated"
check_keytimes
check_apex
@ -1562,6 +1614,7 @@ set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy
# Activation date is a day later.
set_addkeytime "KEY1" "ACTIVE" $(key_get KEY1 ACTIVE) 86400
@ -1587,6 +1640,7 @@ set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy
check_keytimes
check_apex
@ -1635,6 +1689,7 @@ set_keyalgorithm "KEY3" "7" "NSEC3RSASHA1" "2000"
# Key timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy
check_keytimes
check_apex
@ -1654,6 +1709,7 @@ set_keyalgorithm "KEY3" "8" "RSASHA256" "2000"
# Key timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy
check_keytimes
check_apex
@ -1673,6 +1729,7 @@ set_keyalgorithm "KEY3" "10" "RSASHA512" "2000"
# Key timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy
check_keytimes
check_apex
@ -1692,6 +1749,7 @@ set_keyalgorithm "KEY3" "13" "ECDSAP256SHA256" "256"
# Key timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy
check_keytimes
check_apex
@ -1711,6 +1769,7 @@ set_keyalgorithm "KEY3" "14" "ECDSAP384SHA384" "384"
# Key timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_algorithm_policy
check_keytimes
check_apex
@ -1787,6 +1846,7 @@ key_clear "KEY3"
key_clear "KEY4"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_autosign_policy
check_keytimes
check_apex
@ -1844,6 +1904,7 @@ set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_autosign_policy
check_keytimes
check_apex
@ -1901,6 +1962,7 @@ set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_autosign_policy
check_keytimes
check_apex
@ -1941,6 +2003,7 @@ set_keystate "KEY3" "STATE_DNSKEY" "rumoured"
set_keystate "KEY3" "STATE_ZRRSIG" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_autosign_policy
# The old ZSK is retired.
@ -1995,6 +2058,7 @@ set_policy "none" "0" "0"
set_server "ns2" "10.53.0.2"
TSIG=""
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
check_apex
check_subdomain
@ -2003,6 +2067,7 @@ set_policy "none" "0" "0"
set_server "ns4" "10.53.0.4"
TSIG="hmac-sha1:sha1:$SHA1"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
check_apex
check_subdomain
@ -2011,6 +2076,7 @@ set_policy "none" "0" "0"
set_server "ns4" "10.53.0.4"
TSIG="hmac-sha224:sha224:$SHA224"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
check_apex
check_subdomain
@ -2019,6 +2085,7 @@ set_policy "none" "0" "0"
set_server "ns4" "10.53.0.4"
TSIG="hmac-sha256:sha256:$SHA256"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
check_apex
check_subdomain
@ -2027,6 +2094,7 @@ set_policy "none" "0" "0"
set_server "ns4" "10.53.0.4"
TSIG="hmac-sha256:sha256:$SHA256"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
check_apex
check_subdomain
@ -2035,6 +2103,7 @@ set_policy "none" "0" "0"
set_server "ns5" "10.53.0.5"
TSIG="hmac-sha1:sha1:$SHA1"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
check_apex
check_subdomain
@ -2043,6 +2112,7 @@ set_policy "none" "0" "0"
set_server "ns5" "10.53.0.5"
TSIG="hmac-sha1:sha1:$SHA1"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
check_apex
check_subdomain
@ -2051,6 +2121,7 @@ set_policy "none" "0" "0"
set_server "ns5" "10.53.0.5"
TSIG="hmac-sha224:sha224:$SHA224"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
check_apex
check_subdomain
@ -2059,6 +2130,7 @@ set_policy "none" "0" "0"
set_server "ns5" "10.53.0.5"
TSIG="hmac-sha256:sha256:$SHA256"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
check_apex
check_subdomain
@ -2067,6 +2139,7 @@ set_policy "none" "0" "0"
set_server "ns5" "10.53.0.5"
TSIG="hmac-sha256:sha256:$SHA256"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
check_apex
check_subdomain
@ -2093,6 +2166,7 @@ set_policy "default" "1" "3600"
set_server "ns2" "10.53.0.2"
TSIG=""
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -2104,6 +2178,7 @@ set_policy "default" "1" "3600"
set_server "ns4" "10.53.0.4"
TSIG="hmac-sha1:sha1:$SHA1"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -2115,6 +2190,7 @@ set_policy "default" "1" "3600"
set_server "ns4" "10.53.0.4"
TSIG="hmac-sha224:sha224:$SHA224"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -2126,6 +2202,7 @@ set_policy "default" "1" "3600"
set_server "ns5" "10.53.0.5"
TSIG="hmac-sha1:sha1:$SHA1"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -2137,6 +2214,7 @@ set_policy "default" "1" "3600"
set_server "ns5" "10.53.0.5"
TSIG="hmac-sha224:sha224:$SHA224"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -2162,6 +2240,7 @@ set_server "ns4" "10.53.0.4"
TSIG="hmac-sha1:sha1:$SHA1"
wait_for_nsec
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -2174,6 +2253,7 @@ set_server "ns4" "10.53.0.4"
TSIG="hmac-sha224:sha224:$SHA224"
wait_for_nsec
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -2186,6 +2266,7 @@ set_server "ns4" "10.53.0.4"
TSIG="hmac-sha256:sha256:$SHA256"
wait_for_nsec
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -2198,6 +2279,7 @@ set_server "ns5" "10.53.0.5"
TSIG="hmac-sha224:sha224:$SHA224"
wait_for_nsec
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -2210,6 +2292,7 @@ set_server "ns5" "10.53.0.5"
TSIG="hmac-sha256:sha256:$SHA256"
wait_for_nsec
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -2221,6 +2304,7 @@ set_server "ns4" "10.53.0.4"
TSIG="hmac-sha1:keyforview1:$VIEW1"
wait_for_nsec
check_keys
check_dnssecstatus "$SERVER" "$ZONE" "example1"
set_keytimes_csk_policy
check_keytimes
check_apex
@ -2239,6 +2323,7 @@ status=$((status+ret))
TSIG="hmac-sha1:keyforview2:$VIEW2"
wait_for_nsec
check_keys
check_dnssecstatus "$SERVER" "$ZONE" "example2"
check_apex
dnssec_verify
n=$((n+1))
@ -2284,6 +2369,7 @@ key_clear "KEY3"
key_clear "KEY4"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The first key is immediately published and activated.
created=$(key_get KEY1 CREATED)
@ -2340,6 +2426,7 @@ set_keystate "KEY1" "STATE_DNSKEY" "omnipresent"
set_keystate "KEY1" "STATE_KRRSIG" "omnipresent"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The key was published and activated 900 seconds ago (with settime).
created=$(key_get KEY1 CREATED)
@ -2368,6 +2455,7 @@ set_keystate "KEY1" "STATE_ZRRSIG" "omnipresent"
set_keystate "KEY1" "STATE_DS" "rumoured"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The key was published and activated 44700 seconds ago (with settime).
created=$(key_get KEY1 CREATED)
@ -2395,6 +2483,7 @@ set_server "ns3" "10.53.0.3"
set_keystate "KEY1" "STATE_DS" "omnipresent"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The key was published and activated 143100 seconds ago (with settime).
created=$(key_get KEY1 CREATED)
@ -2487,6 +2576,7 @@ key_clear "KEY3"
key_clear "KEY4"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# These keys are immediately published and activated.
rollover_predecessor_keytimes 0
@ -2521,6 +2611,7 @@ set_keystate "KEY3" "STATE_DNSKEY" "rumoured"
set_keystate "KEY3" "STATE_ZRRSIG" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys were activated 694 hours ago (2498400 seconds).
rollover_predecessor_keytimes -2498400
@ -2558,6 +2649,7 @@ set_keystate "KEY3" "STATE_DNSKEY" "omnipresent"
set_keystate "KEY3" "STATE_ZRRSIG" "rumoured"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys are activated 30 days ago (2592000 seconds).
rollover_predecessor_keytimes -2592000
@ -2599,6 +2691,7 @@ set_keystate "KEY2" "STATE_ZRRSIG" "hidden"
set_keystate "KEY3" "STATE_ZRRSIG" "omnipresent"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys are activated 961 hours ago (3459600 seconds).
rollover_predecessor_keytimes -3459600
@ -2628,6 +2721,7 @@ set_server "ns3" "10.53.0.3"
set_keystate "KEY2" "STATE_DNSKEY" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys are activated 962 hours ago (3463200 seconds).
rollover_predecessor_keytimes -3463200
@ -2697,6 +2791,7 @@ key_clear "KEY3"
key_clear "KEY4"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# These keys are immediately published and activated.
rollover_predecessor_keytimes 0
@ -2733,6 +2828,7 @@ set_keystate "KEY3" "STATE_KRRSIG" "rumoured"
set_keystate "KEY3" "STATE_DS" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys were activated 1413 hours ago (5086800 seconds).
rollover_predecessor_keytimes -5086800
@ -2776,6 +2872,7 @@ set_keystate "KEY3" "STATE_KRRSIG" "omnipresent"
set_keystate "KEY3" "STATE_DS" "rumoured"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys were activated 59 days ago (5097600 seconds).
rollover_predecessor_keytimes -5097600
@ -2817,6 +2914,7 @@ set_keystate "KEY1" "STATE_DS" "hidden"
set_keystate "KEY3" "STATE_DS" "omnipresent"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys were activated 1490 hours ago (5364000 seconds).
rollover_predecessor_keytimes -5364000
@ -2849,6 +2947,7 @@ set_keystate "KEY1" "STATE_DNSKEY" "hidden"
set_keystate "KEY1" "STATE_KRRSIG" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The old KSK is activated 1492 hours ago (5371200 seconds).
rollover_predecessor_keytimes -5371200
@ -2922,6 +3021,7 @@ key_clear "KEY3"
key_clear "KEY4"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key is immediately published and activated.
csk_rollover_predecessor_keytimes 0 0
@ -2958,6 +3058,7 @@ set_keystate "KEY2" "STATE_ZRRSIG" "hidden"
set_keystate "KEY2" "STATE_DS" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key was activated 4437 hours ago (15973200 seconds)
# and started signing 4461 hours ago (16059600 seconds).
@ -3001,6 +3102,7 @@ set_keystate "KEY2" "STATE_ZRRSIG" "rumoured"
set_keystate "KEY2" "STATE_DS" "rumoured"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key was activated 185 days ago (15984000 seconds)
# and started signing 186 days ago (16070400 seconds).
@ -3049,6 +3151,7 @@ set_keystate "KEY1" "STATE_DS" "hidden"
set_keystate "KEY2" "STATE_DS" "omnipresent"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key was activated 4468 hours ago (16084800 seconds)
# and started signing 4492 hours ago (16171200 seconds).
@ -3080,6 +3183,7 @@ set_server "ns3" "10.53.0.3"
set_keystate "KEY1" "STATE_KRRSIG" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key was activated 4470 hours ago (16092000 seconds)
# and started signing 4494 hours ago (16178400 seconds).
@ -3117,6 +3221,7 @@ set_keystate "KEY1" "STATE_ZRRSIG" "hidden"
set_keystate "KEY2" "STATE_ZRRSIG" "omnipresent"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key was activated 5067 hours ago (18241200 seconds)
# and started signing 5091 hours ago (18327600 seconds).
@ -3148,6 +3253,7 @@ set_server "ns3" "10.53.0.3"
set_keystate "KEY1" "STATE_DNSKEY" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key was activated 5069 hours ago (18248400 seconds)
# and started signing 5093 hours ago (18334800 seconds).
@ -3214,6 +3320,7 @@ key_clear "KEY3"
key_clear "KEY4"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key is immediately published and activated.
csk_rollover_predecessor_keytimes 0 0
@ -3250,6 +3357,7 @@ set_keystate "KEY2" "STATE_ZRRSIG" "hidden"
set_keystate "KEY2" "STATE_DS" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key was activated 4293 hours ago (15454800 seconds)
# and started signing 4461 hours ago (16059600 seconds).
@ -3292,6 +3400,7 @@ set_keystate "KEY2" "STATE_ZRRSIG" "rumoured"
set_keystate "KEY2" "STATE_DS" "rumoured"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key was activated 179 days ago (15465600 seconds)
# and started signing 186 days ago (16070400 seconds).
@ -3337,6 +3446,7 @@ set_keystate "KEY1" "STATE_ZRRSIG" "hidden"
set_keystate "KEY2" "STATE_ZRRSIG" "omnipresent"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key was activated 4334 hours ago (15602400 seconds)
# and started signing 4502 hours ago (16207200 seconds).
@ -3377,6 +3487,7 @@ set_keystate "KEY1" "STATE_DS" "hidden"
set_keystate "KEY2" "STATE_DS" "omnipresent"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key was activated 4467 hours ago (16081200 seconds)
# and started signing 4635 hours ago (16686000 seconds).
@ -3409,6 +3520,7 @@ set_keystate "KEY1" "STATE_DNSKEY" "hidden"
set_keystate "KEY1" "STATE_KRRSIG" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key was activated 4469 hours ago (16088400 seconds)
# and started signing 4637 hours ago (16693200 seconds).
@ -3469,6 +3581,7 @@ set_keystate "KEY2" "STATE_DNSKEY" "omnipresent"
set_keystate "KEY2" "STATE_ZRRSIG" "omnipresent"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# These keys are immediately published and activated.
Lksk=0
@ -3510,6 +3623,7 @@ set_keystate "KEY1" "STATE_ZRRSIG" "omnipresent"
set_keystate "KEY1" "STATE_DS" "omnipresent"
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# This key is immediately published and activated.
Lcsk=0
@ -3565,6 +3679,7 @@ init_migration_match
# Make sure the zone is signed with legacy keys.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# These keys are immediately published and activated.
rollover_predecessor_keytimes 0
@ -3615,6 +3730,7 @@ init_migration_nomatch_algnum
# Make sure the zone is signed with legacy keys.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The KSK is immediately published and activated.
# -P : now-3900s
@ -3677,6 +3793,7 @@ init_migration_nomatch_alglen
# Make sure the zone is signed with legacy keys.
check_keys
check_dnssecstatus "$SERVER" "$ZONE"
# The KSK is immediately published and activated.
# -P : now-3900s
@ -3765,6 +3882,7 @@ key_set "KEY2" "LEGACY" "no"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
rollover_predecessor_keytimes 0
# Key now has lifetime of 60 days (5184000 seconds).
@ -3831,6 +3949,7 @@ set_keystate "KEY4" "STATE_ZRRSIG" "rumoured"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# KSK must be retired since it no longer matches the policy.
# -P : now-3900s
@ -3947,6 +4066,7 @@ set_keystate "KEY4" "STATE_ZRRSIG" "hidden"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# KSK must be retired since it no longer matches the policy.
# -P : now-3900s
@ -4088,6 +4208,7 @@ set_keystate "KEY4" "STATE_ZRRSIG" "rumoured"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys are published and activated.
rollover_predecessor_keytimes 0
@ -4167,6 +4288,7 @@ set_keystate "KEY4" "STATE_DNSKEY" "omnipresent"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys were activated three hours ago (10800 seconds).
rollover_predecessor_keytimes -10800
@ -4222,6 +4344,7 @@ set_keystate "KEY4" "STATE_ZRRSIG" "omnipresent"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys were activated 9 hours ago (32400 seconds)
# and retired 6 hours ago (21600 seconds).
@ -4279,6 +4402,7 @@ set_keystate "KEY3" "STATE_DS" "omnipresent"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys were activated 38 hours ago (136800 seconds)
# and retired 35 hours ago (126000 seconds).
@ -4327,6 +4451,7 @@ set_keystate "KEY2" "STATE_DNSKEY" "hidden"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys were activated 40 hours ago (144000 seconds)
# and retired 35 hours ago (133200 seconds).
@ -4378,6 +4503,7 @@ set_keystate "KEY2" "STATE_ZRRSIG" "hidden"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys were activated 47 hours ago (169200 seconds)
# and retired 34 hours ago (158400 seconds).
@ -4458,6 +4584,7 @@ set_keystate "KEY2" "STATE_DS" "hidden"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# CSK must be retired since it no longer matches the policy.
csk_rollover_predecessor_keytimes 0 0
@ -4515,6 +4642,7 @@ set_keystate "KEY2" "STATE_KRRSIG" "omnipresent"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# The old key was activated three hours ago (10800 seconds).
csk_rollover_predecessor_keytimes -10800 -10800
@ -4561,6 +4689,7 @@ set_keystate "KEY2" "STATE_DS" "rumoured"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# The old key was activated 9 hours ago (10800 seconds)
# and retired 6 hours ago (21600 seconds).
@ -4605,6 +4734,7 @@ set_keystate "KEY2" "STATE_DS" "omnipresent"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# The old key was activated 38 hours ago (136800 seconds)
# and retired 35 hours ago (126000 seconds).
@ -4642,6 +4772,7 @@ set_keystate "KEY1" "STATE_KRRSIG" "hidden"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# The old key was activated 40 hours ago (144000 seconds)
# and retired 37 hours ago (133200 seconds).
@ -4683,6 +4814,7 @@ set_keystate "KEY1" "STATE_ZRRSIG" "hidden"
check_keys
wait_for_done_signing
check_dnssecstatus "$SERVER" "$ZONE"
# The old keys were activated 47 hours ago (169200 seconds)
# and retired 44 hours ago (158400 seconds).

View file

@ -162,6 +162,10 @@ back. To remove it permanently, it must also be removed from
.sp
See also \fBrndc addzone\fP and \fBrndc modzone\fP\&.
.TP
\fBdnssec\fP [\fB\-status\fP \fIzone\fP [\fIclass\fP [\fIview\fP]]
Show the DNSSEC signing state for the specified zone. Requires the
zone to have a "dnssec\-policy".
.TP
\fBdnstap\fP ( \fB\-reopen\fP | \fB\-roll\fP [\fInumber\fP] )
Close and re\-open DNSTAP output files. \fBrndc dnstap \-reopen\fP allows
the output file to be renamed externally, so that \fBnamed(8)\fP can

View file

@ -29,6 +29,10 @@ New Features
Feature Changes
~~~~~~~~~~~~~~~
- New ``rndc`` command ``rndc dnssec -status`` that shows the current
DNSSEC policy and keys in use, the key states and rollover status.
[GL #1612]
- Disable and disallow static linking of BIND 9 binaries and libraries
as BIND 9 modules require ``dlopen()`` support and static linking also
prevents using security features like read-only relocations (RELRO) or

View file

@ -1906,7 +1906,6 @@ printtime(const dst_key_t *key, int type, const char *tag, FILE *stream) {
isc_result_t result;
char output[26]; /* Minimum buffer as per ctime_r() specification. */
isc_stdtime_t when;
time_t t;
char utc[sizeof("YYYYMMDDHHSSMM")];
isc_buffer_t b;
isc_region_t r;
@ -1916,18 +1915,7 @@ printtime(const dst_key_t *key, int type, const char *tag, FILE *stream) {
return;
}
/* time_t and isc_stdtime_t might be different sizes */
t = when;
#ifdef WIN32
if (ctime_s(output, sizeof(output), &t) != 0) {
goto error;
}
#else /* ifdef WIN32 */
if (ctime_r(&t, output) == NULL) {
goto error;
}
#endif /* ifdef WIN32 */
isc_stdtime_tostring(when, output, sizeof(output));
isc_buffer_init(&b, utc, sizeof(utc));
result = dns_time32_totext(when, &b);
if (result != ISC_R_SUCCESS) {

View file

@ -51,6 +51,23 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
*\li On error, keypool is unchanged
*/
void
dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
isc_stdtime_t now, char *out, size_t out_len);
/*%<
* Retrieve the status of given 'kasp' policy and keys in the
* 'keyring' and store the printable output in the 'out' buffer.
*
* Requires:
*\li 'kasp' is not NULL.
*\li 'keyring' is not NULL.
*\li 'out' is not NULL.
*
* Returns:
*\li Printable status in 'out'.
*
*/
ISC_LANG_ENDDECLS
#endif /* DNS_KEYMGR_H */

View file

@ -1841,3 +1841,204 @@ failure:
return (result);
}
static void
keytime_status(dst_key_t *key, isc_stdtime_t now, isc_buffer_t *buf,
const char *pre, int ks, int kt) {
char timestr[26]; /* Minimal buf as per ctime_r() spec. */
isc_result_t ret;
isc_stdtime_t when = 0;
dst_key_state_t state;
isc_buffer_printf(buf, "%s", pre);
(void)dst_key_getstate(key, ks, &state);
ret = dst_key_gettime(key, kt, &when);
if (state == RUMOURED || state == OMNIPRESENT) {
isc_buffer_printf(buf, "yes - since ");
} else if (now < when) {
isc_buffer_printf(buf, "no - scheduled ");
} else {
isc_buffer_printf(buf, "no\n");
return;
}
if (ret == ISC_R_SUCCESS) {
isc_stdtime_tostring(when, timestr, sizeof(timestr));
isc_buffer_printf(buf, "%s\n", timestr);
}
}
static void
rollover_status(dns_dnsseckey_t *dkey, dns_kasp_t *kasp, isc_stdtime_t now,
isc_buffer_t *buf, bool zsk) {
char timestr[26]; /* Minimal buf as per ctime_r() spec. */
isc_result_t ret = ISC_R_SUCCESS;
isc_stdtime_t active_time = 0;
dst_key_state_t state = NA, goal = NA;
int rrsig, active, retire;
dst_key_t *key = dkey->key;
if (zsk) {
rrsig = DST_KEY_ZRRSIG;
active = DST_TIME_ACTIVATE;
retire = DST_TIME_INACTIVE;
} else {
rrsig = DST_KEY_KRRSIG;
active = DST_TIME_PUBLISH;
retire = DST_TIME_DELETE;
}
isc_buffer_printf(buf, "\n");
(void)dst_key_getstate(key, DST_KEY_GOAL, &goal);
(void)dst_key_getstate(key, rrsig, &state);
(void)dst_key_gettime(key, active, &active_time);
if (active_time == 0) {
// only interested in keys that were once active.
return;
}
if (goal == HIDDEN && (state == UNRETENTIVE || state == HIDDEN)) {
isc_stdtime_t remove_time = 0;
// is the key removed yet?
state = NA;
(void)dst_key_getstate(key, DST_KEY_DNSKEY, &state);
if (state == RUMOURED || state == OMNIPRESENT) {
ret = dst_key_gettime(key, DST_TIME_DELETE,
&remove_time);
if (ret == ISC_R_SUCCESS) {
isc_buffer_printf(buf, " Key is retired, will "
"be removed on ");
isc_stdtime_tostring(remove_time, timestr,
sizeof(timestr));
isc_buffer_printf(buf, "%s", timestr);
}
} else {
isc_buffer_printf(
buf, " Key has been removed from the zone");
}
} else {
isc_stdtime_t retire_time = 0;
uint32_t lifetime = 0;
(void)dst_key_getnum(key, DST_NUM_LIFETIME, &lifetime);
ret = dst_key_gettime(key, retire, &retire_time);
if (ret == ISC_R_SUCCESS) {
if (now < retire_time) {
if (goal == OMNIPRESENT) {
isc_buffer_printf(buf,
" Next rollover "
"scheduled on ");
retire_time = keymgr_prepublication_time(
dkey, kasp, lifetime, now);
} else {
isc_buffer_printf(
buf, " Key will retire on ");
}
} else {
isc_buffer_printf(buf,
" Rollover is due since ");
}
isc_stdtime_tostring(retire_time, timestr,
sizeof(timestr));
isc_buffer_printf(buf, "%s", timestr);
} else {
isc_buffer_printf(buf, " No rollover scheduled");
}
}
isc_buffer_printf(buf, "\n");
}
static void
keystate_status(dst_key_t *key, isc_buffer_t *buf, const char *pre, int ks) {
dst_key_state_t state = NA;
(void)dst_key_getstate(key, ks, &state);
switch (state) {
case HIDDEN:
isc_buffer_printf(buf, " - %shidden\n", pre);
break;
case RUMOURED:
isc_buffer_printf(buf, " - %srumoured\n", pre);
break;
case OMNIPRESENT:
isc_buffer_printf(buf, " - %somnipresent\n", pre);
break;
case UNRETENTIVE:
isc_buffer_printf(buf, " - %sunretentive\n", pre);
break;
case NA:
default:
/* print nothing */
break;
}
}
void
dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
isc_stdtime_t now, char *out, size_t out_len) {
isc_buffer_t buf;
char timestr[26]; /* Minimal buf as per ctime_r() spec. */
REQUIRE(DNS_KASP_VALID(kasp));
REQUIRE(keyring != NULL);
REQUIRE(out != NULL);
isc_buffer_init(&buf, out, out_len);
// policy name
isc_buffer_printf(&buf, "dnssec-policy: %s\n", dns_kasp_getname(kasp));
isc_buffer_printf(&buf, "current time: ");
isc_stdtime_tostring(now, timestr, sizeof(timestr));
isc_buffer_printf(&buf, "%s\n", timestr);
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
dkey = ISC_LIST_NEXT(dkey, link))
{
char algstr[DNS_NAME_FORMATSIZE];
bool ksk = false, zsk = false;
if (dst_key_is_unused(dkey->key)) {
continue;
}
// key data
dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk);
dst_key_getbool(dkey->key, DST_BOOL_ZSK, &zsk);
dns_secalg_format((dns_secalg_t)dst_key_alg(dkey->key), algstr,
sizeof(algstr));
isc_buffer_printf(&buf, "\nkey: %d (%s), %s\n",
dst_key_id(dkey->key), algstr,
keymgr_keyrole(dkey->key));
// publish status
keytime_status(dkey->key, now, &buf,
" published: ", DST_KEY_DNSKEY,
DST_TIME_PUBLISH);
// signing status
if (ksk) {
keytime_status(dkey->key, now, &buf,
" key signing: ", DST_KEY_KRRSIG,
DST_TIME_PUBLISH);
}
if (zsk) {
keytime_status(dkey->key, now, &buf,
" zone signing: ", DST_KEY_ZRRSIG,
DST_TIME_ACTIVATE);
}
// rollover status
rollover_status(dkey, kasp, now, &buf, zsk);
// key states
keystate_status(dkey->key, &buf,
"goal: ", DST_KEY_GOAL);
keystate_status(dkey->key, &buf,
"dnskey: ", DST_KEY_DNSKEY);
keystate_status(dkey->key, &buf,
"ds: ", DST_KEY_DS);
keystate_status(dkey->key, &buf,
"zone rrsig: ", DST_KEY_ZRRSIG);
keystate_status(dkey->key, &buf,
"key rrsig: ", DST_KEY_KRRSIG);
}
}

View file

@ -470,6 +470,7 @@ dns_keydata_fromdnskey
dns_keydata_todnskey
dns_keyflags_fromtext
dns_keymgr_run
dns_keymgr_status
dns_keynode_dsset
dns_keynode_initial
dns_keynode_managed

View file

@ -15,6 +15,7 @@
/*! \file */
#include <inttypes.h>
#include <stdlib.h>
#include <isc/lang.h>
@ -37,6 +38,20 @@ isc_stdtime_get(isc_stdtime_t *t);
*\li 't' is a valid pointer.
*/
void
isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen);
/*
* Convert 't' into a null-terminated string of the form
* "Wed Jun 30 21:49:08 1993". Store the string in the 'out'
* buffer.
*
* Requires:
*
* 't' is a valid time.
* 'out' is a valid pointer.
* 'outlen' is at least 26.
*/
#define isc_stdtime_convert32(t, t32p) (*(t32p) = t)
/*
* Convert the standard time to its 32-bit version.

View file

@ -49,3 +49,18 @@ isc_stdtime_get(isc_stdtime_t *t) {
*t = (isc_stdtime_t)ts.tv_sec;
}
void
isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen) {
time_t when;
REQUIRE(out != NULL);
REQUIRE(outlen >= 26);
UNUSED(outlen);
/* time_t and isc_stdtime_t might be different sizes */
when = t;
INSIST((ctime_r(&when, out) != NULL));
*(out + strlen(out) - 1) = '\0';
}

View file

@ -13,6 +13,7 @@
#define ISC_STDTIME_H 1
#include <inttypes.h>
#include <stdlib.h>
#include <isc/lang.h>
@ -35,6 +36,20 @@ isc_stdtime_get(isc_stdtime_t *t);
* 't' is a valid pointer.
*/
void
isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen);
/*
* Convert 't' into a null-terminated string of the form
* "Wed Jun 30 21:49:08 1993". Store the string in the 'out'
* buffer.
*
* Requires:
*
* 't' is a valid time.
* 'out' is a valid pointer.
* 'outlen' is at least 26.
*/
#define isc_stdtime_convert32(t, t32p) (*(t32p) = t)
/*
* Convert the standard time to its 32-bit version.

View file

@ -605,6 +605,7 @@ isc_stdio_sync
isc_stdio_tell
isc_stdio_write
isc_stdtime_get
isc_stdtime_tostring
isc_string_strerror_r
isc_symtab_count
isc_symtab_create

View file

@ -25,3 +25,17 @@ isc_stdtime_get(isc_stdtime_t *t) {
(void)_time32(t);
}
void
isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen) {
time_t when;
REQUIRE(out != NULL);
/* Minimum buffer as per ctime_r() specification. */
REQUIRE(outlen >= 26);
/* time_t and isc_stdtime_t might be different sizes */
when = t;
INSIST((ctime_s(out, outlen, &when) == 0));
*(out + strlen(out) - 1) = '\0';
}