Commit 72042a06 authored by Matthijs Mekking's avatar Matthijs Mekking 🏡

dnssec-settime: Allow manipulating state files

Introduce a new option '-s' for dnssec-settime that when manipulating
timing metadata, it also updates the key state file.

For testing purposes, add options to dnssec-settime to set key
states and when they last changed.

The dst code adds ways to write and read the new key states and
timing metadata. It updates the parsing code for private key files
to not parse the newly introduced metadata (these are for state
files only).

Introduce key goal (the state the key wants to be in).
parent c55625b0
......@@ -88,6 +88,15 @@ usage(void) {
fprintf(stderr, " -i <interval>: prepublication interval for "
"successor key "
"(default: 30 days)\n");
fprintf(stderr, "Key state options:\n");
fprintf(stderr, " -s: update key state file (default no)\n");
fprintf(stderr, " -g state: set the goal state for this key\n");
fprintf(stderr, " -d state date/[+-]offset: set the DS state\n");
fprintf(stderr, " -k state date/[+-]offset: set the DNSKEY state\n");
fprintf(stderr, " -r state date/[+-]offset: set the RRSIG (KSK) "
"state\n");
fprintf(stderr, " -z state date/[+-]offset: set the RRSIG (ZSK) "
"state\n");
fprintf(stderr, "Printing options:\n");
fprintf(stderr, " -p C/P/Psync/A/R/I/D/Dsync/all: print a "
"particular time value or values\n");
......@@ -123,29 +132,87 @@ printtime(dst_key_t *key, int type, const char *tag, bool epoch,
}
}
static void
writekey(dst_key_t *key, const char *directory, bool write_state)
{
char newname[1024];
char keystr[DST_KEY_FORMATSIZE];
isc_buffer_t buf;
isc_result_t result;
int options = DST_TYPE_PUBLIC|DST_TYPE_PRIVATE;
if (write_state) {
options |= DST_TYPE_STATE;
}
isc_buffer_init(&buf, newname, sizeof(newname));
result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, &buf);
if (result != ISC_R_SUCCESS) {
fatal("Failed to build public key filename: %s",
isc_result_totext(result));
}
result = dst_key_tofile(key, options, directory);
if (result != ISC_R_SUCCESS) {
dst_key_format(key, keystr, sizeof(keystr));
fatal("Failed to write key %s: %s", keystr,
isc_result_totext(result));
}
printf("%s\n", newname);
isc_buffer_clear(&buf);
result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, &buf);
if (result != ISC_R_SUCCESS) {
fatal("Failed to build private key filename: %s",
isc_result_totext(result));
}
printf("%s\n", newname);
if (write_state) {
isc_buffer_clear(&buf);
result = dst_key_buildfilename(key, DST_TYPE_STATE, directory,
&buf);
if (result != ISC_R_SUCCESS) {
fatal("Failed to build key state filename: %s",
isc_result_totext(result));
}
printf("%s\n", newname);
}
}
int
main(int argc, char **argv) {
isc_result_t result;
const char *engine = NULL;
const char *filename = NULL;
char *directory = NULL;
char newname[1024];
char keystr[DST_KEY_FORMATSIZE];
char *endp, *p;
int ch;
const char *predecessor = NULL;
dst_key_t *prevkey = NULL;
dst_key_t *key = NULL;
isc_buffer_t buf;
dns_name_t *name = NULL;
dns_secalg_t alg = 0;
unsigned int size = 0;
uint16_t flags = 0;
int prepub = -1;
int options;
dns_ttl_t ttl = 0;
isc_stdtime_t now;
isc_stdtime_t dstime = 0, dnskeytime = 0;
isc_stdtime_t krrsigtime = 0, zrrsigtime = 0;
isc_stdtime_t pub = 0, act = 0, rev = 0, inact = 0, del = 0;
isc_stdtime_t prevact = 0, previnact = 0, prevdel = 0;
dst_key_state_t goal = DST_KEY_STATE_NA;
dst_key_state_t ds = DST_KEY_STATE_NA;
dst_key_state_t dnskey = DST_KEY_STATE_NA;
dst_key_state_t krrsig = DST_KEY_STATE_NA;
dst_key_state_t zrrsig = DST_KEY_STATE_NA;
bool setgoal = false, setds = false, setdnskey = false;
bool setkrrsig = false, setzrrsig = false;
bool setdstime = false, setdnskeytime = false;
bool setkrrsigtime = false, setzrrsigtime = false;
bool setpub = false, setact = false;
bool setrev = false, setinact = false;
bool setdel = false, setttl = false;
......@@ -156,14 +223,17 @@ main(int argc, char **argv) {
bool printact = false, printrev = false;
bool printinact = false, printdel = false;
bool force = false;
bool epoch = false;
bool changed = false;
bool epoch = false;
bool changed = false;
bool write_state = false;
isc_log_t *log = NULL;
isc_stdtime_t syncadd = 0, syncdel = 0;
bool unsetsyncadd = false, setsyncadd = false;
bool unsetsyncdel = false, setsyncdel = false;
bool printsyncadd = false, printsyncdel = false;
options = DST_TYPE_PUBLIC|DST_TYPE_PRIVATE|DST_TYPE_STATE;
if (argc == 1)
usage();
......@@ -180,7 +250,7 @@ main(int argc, char **argv) {
isc_stdtime_get(&now);
#define CMDLINE_FLAGS "A:D:E:fhI:i:K:L:P:p:R:S:uv:V"
#define CMDLINE_FLAGS "A:D:d:E:fg:hI:i:K:k:L:P:p:R:r:S:suv:Vz:"
while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) {
switch (ch) {
case 'E':
......@@ -339,6 +409,70 @@ main(int argc, char **argv) {
case 'i':
prepub = strtottl(isc_commandline_argument);
break;
case 's':
write_state = true;
break;
case 'g':
if (setgoal) {
fatal("-g specified more than once");
}
goal = strtokeystate(isc_commandline_argument);
if (goal != DST_KEY_STATE_NA &&
goal != DST_KEY_STATE_HIDDEN &&
goal != DST_KEY_STATE_OMNIPRESENT) {
fatal("-g must be either none, hidden, or "
"omnipresent");
}
setgoal = true;
break;
case 'd':
if (setds) {
fatal("-d specified more than once");
}
ds = strtokeystate(isc_commandline_argument);
setds = true;
/* time */
(void)isoptarg(isc_commandline_argument, argv, usage);
dstime = strtotime(isc_commandline_argument,
now, now, &setdstime);
break;
case 'k':
if (setdnskey) {
fatal("-k specified more than once");
}
dnskey = strtokeystate(isc_commandline_argument);
setdnskey = true;
/* time */
(void)isoptarg(isc_commandline_argument, argv, usage);
dnskeytime = strtotime(isc_commandline_argument,
now, now, &setdnskeytime);
break;
case 'r':
if (setkrrsig) {
fatal("-r specified more than once");
}
krrsig = strtokeystate(isc_commandline_argument);
setkrrsig = true;
/* time */
(void)isoptarg(isc_commandline_argument, argv, usage);
krrsigtime = strtotime(isc_commandline_argument,
now, now, &setkrrsigtime);
break;
case 'z':
if (setzrrsig) {
fatal("-z specified more than once");
}
zrrsig = strtokeystate(isc_commandline_argument);
setzrrsig = true;
(void)isoptarg(isc_commandline_argument, argv, usage);
zrrsigtime = strtotime(isc_commandline_argument,
now, now, &setzrrsigtime);
break;
case '?':
if (isc_commandline_option != '?')
fprintf(stderr, "%s: invalid argument -%c\n",
......@@ -365,6 +499,12 @@ main(int argc, char **argv) {
if (argc > isc_commandline_index + 1)
fatal("Extraneous arguments");
if ((setgoal || setds || setdnskey || setkrrsig || setzrrsig) &&
!write_state)
{
fatal("Options -g, -d, -k, -r and -z require -s to be set");
}
result = dst_lib_init(mctx, engine);
if (result != ISC_R_SUCCESS)
fatal("Could not initialize dst: %s",
......@@ -381,9 +521,7 @@ main(int argc, char **argv) {
if (setact || unsetact)
fatal("-S and -A cannot be used together");
result = dst_key_fromnamedfile(predecessor, directory,
DST_TYPE_PUBLIC |
DST_TYPE_PRIVATE,
result = dst_key_fromnamedfile(predecessor, directory, options,
mctx, &prevkey);
if (result != ISC_R_SUCCESS)
fatal("Invalid keyfile %s: %s",
......@@ -475,9 +613,8 @@ main(int argc, char **argv) {
isc_result_totext(result));
}
result = dst_key_fromnamedfile(filename, directory,
DST_TYPE_PUBLIC | DST_TYPE_PRIVATE,
mctx, &key);
result = dst_key_fromnamedfile(filename, directory, options, mctx,
&key);
if (result != ISC_R_SUCCESS)
fatal("Invalid keyfile %s: %s",
filename, isc_result_totext(result));
......@@ -588,6 +725,63 @@ main(int argc, char **argv) {
changed = true;
}
/*
* Make sure the key state goals are written.
*/
if (write_state) {
if (setgoal) {
if (goal == DST_KEY_STATE_NA) {
dst_key_unsetstate(key, DST_KEY_GOAL);
} else {
dst_key_setstate(key, DST_KEY_GOAL, goal);
}
changed = true;
}
if (setds) {
if (ds == DST_KEY_STATE_NA) {
dst_key_unsetstate(key, DST_KEY_DS);
dst_key_unsettime(key, DST_TIME_DS);
} else {
dst_key_setstate(key, DST_KEY_DS, ds);
dst_key_settime(key, DST_TIME_DS, dstime);
}
changed = true;
}
if (setdnskey) {
if (dnskey == DST_KEY_STATE_NA) {
dst_key_unsetstate(key, DST_KEY_DNSKEY);
dst_key_unsettime(key, DST_TIME_DNSKEY);
} else {
dst_key_setstate(key, DST_KEY_DNSKEY, dnskey);
dst_key_settime(key, DST_TIME_DNSKEY,
dnskeytime);
}
changed = true;
}
if (setkrrsig) {
if (krrsig == DST_KEY_STATE_NA) {
dst_key_unsetstate(key, DST_KEY_KRRSIG);
dst_key_unsettime(key, DST_TIME_KRRSIG);
} else {
dst_key_setstate(key, DST_KEY_KRRSIG, krrsig);
dst_key_settime(key, DST_TIME_KRRSIG,
krrsigtime);
}
changed = true;
}
if (setzrrsig) {
if (zrrsig == DST_KEY_STATE_NA) {
dst_key_unsetstate(key, DST_KEY_ZRRSIG);
dst_key_unsettime(key, DST_TIME_ZRRSIG);
} else {
dst_key_setstate(key, DST_KEY_ZRRSIG, zrrsig);
dst_key_settime(key, DST_TIME_ZRRSIG,
zrrsigtime);
}
changed = true;
}
}
if (!changed && setttl)
changed = true;
......@@ -621,32 +815,7 @@ main(int argc, char **argv) {
epoch, stdout);
if (changed) {
isc_buffer_init(&buf, newname, sizeof(newname));
result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory,
&buf);
if (result != ISC_R_SUCCESS) {
fatal("Failed to build public key filename: %s",
isc_result_totext(result));
}
result = dst_key_tofile(key, DST_TYPE_PUBLIC|DST_TYPE_PRIVATE,
directory);
if (result != ISC_R_SUCCESS) {
dst_key_format(key, keystr, sizeof(keystr));
fatal("Failed to write key %s: %s", keystr,
isc_result_totext(result));
}
printf("%s\n", newname);
isc_buffer_clear(&buf);
result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory,
&buf);
if (result != ISC_R_SUCCESS) {
fatal("Failed to build private key filename: %s",
isc_result_totext(result));
}
printf("%s\n", newname);
writekey(key, directory, write_state);
}
if (prevkey != NULL)
......
......@@ -64,6 +64,12 @@
<arg choice="opt" rep="norepeat"><option>-V</option></arg>
<arg choice="opt" rep="norepeat"><option>-v <replaceable class="parameter">level</replaceable></option></arg>
<arg choice="opt" rep="norepeat"><option>-E <replaceable class="parameter">engine</replaceable></option></arg>
<arg choice="opt" rep="norepeat"><option>-s</option></arg>
<arg choice="opt" rep="norepeat"><option>-g <replaceable class="parameter">state</replaceable></option></arg>
<arg choice="opt" rep="norepeat"><option>-d <replaceable class="parameter">state</replaceable> <replaceable class="parameter">date/offset</replaceable></option></arg>
<arg choice="opt" rep="norepeat"><option>-k <replaceable class="parameter">state</replaceable> <replaceable class="parameter">date/offset</replaceable></option></arg>
<arg choice="opt" rep="norepeat"><option>-r <replaceable class="parameter">state</replaceable> <replaceable class="parameter">date/offset</replaceable></option></arg>
<arg choice="opt" rep="norepeat"><option>-z <replaceable class="parameter">state</replaceable> <replaceable class="parameter">date/offset</replaceable></option></arg>
<arg choice="req" rep="norepeat">keyfile</arg>
</cmdsynopsis>
</refsynopsisdiv>
......@@ -88,11 +94,30 @@
When key metadata fields are changed, both files of a key
pair (<filename>Knnnn.+aaa+iiiii.key</filename> and
<filename>Knnnn.+aaa+iiiii.private</filename>) are regenerated.
</para>
<para>
Metadata fields are stored in the private file. A human-readable
description of the metadata is also placed in comments in the key
file. The private file's permissions are always set to be
inaccessible to anyone other than the owner (mode 0600).
</para>
<para>
When working with state files, it is possible to update the timing
metadata in those files as well with <option>-s</option>. If this
option is used you can also update key states with <option>-d</option>
(DS), <option>-k</option> (DNSKEY), <option>-r</option> (RRSIG of KSK),
or <option>-z</option> (RRSIG of ZSK). Allowed states are HIDDEN,
RUMOURED, OMNIPRESENT, and UNRETENTIVE.
</para>
<para>
You can also set the goal state of the key with <option>-g</option>.
This should be either HIDDEN or OMNIPRESENT (representing whether the
key should be removed from the zone, or published).
</para>
<para>
It is NOT RECOMMENDED to manipulate state files manually except for
testing purposes.
</para>
</refsection>
<refsection><info><title>OPTIONS</title></info>
......@@ -319,6 +344,74 @@
</variablelist>
</refsection>
<refsection><info><title>KEY STATE OPTIONS</title></info>
<para>
Known key states are HIDDEN, RUMOURED, OMNIPRESENT and UNRETENTIVE.
These should not be set manually except for testing purposes.
</para>
<variablelist>
<varlistentry>
<term>-s</term>
<listitem>
<para>
When setting key timing data, also update the state file.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-g</term>
<listitem>
<para>
Set the goal state for this key. Must be HIDDEN or OMNIPRESENT.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-d</term>
<listitem>
<para>
Set the DS state for this key, and when it was last changed.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-k</term>
<listitem>
<para>
Set the DNSKEY state for this key, and when it was last changed.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-r</term>
<listitem>
<para>
Set the RRSIG (KSK) state for this key, and when it was last
changed.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-z</term>
<listitem>
<para>
Set the RRSIG (ZSK) state for this key, and when it was last
changed.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsection>
<refsection><info><title>PRINTING OPTIONS</title></info>
<para>
......
......@@ -57,6 +57,11 @@
#include "dnssectool.h"
#define KEYSTATES_NVALUES 4
static const char *keystates[KEYSTATES_NVALUES] = {
"hidden", "rumoured", "omnipresent", "unretentive",
};
int verbose = 0;
bool quiet = false;
uint8_t dtype[8];
......@@ -244,6 +249,21 @@ strtottl(const char *str) {
return (ttl);
}
dst_key_state_t
strtokeystate(const char *str) {
if (isnone(str)) {
return (DST_KEY_STATE_NA);
}
for (int i = 0; i < KEYSTATES_NVALUES; i++) {
if (keystates[i] != NULL &&
strcasecmp(str, keystates[i]) == 0) {
return (dst_key_state_t) i;
}
}
fatal("unknown key state");
}
isc_stdtime_t
strtotime(const char *str, int64_t now, int64_t base,
bool *setp)
......
......@@ -71,6 +71,8 @@ cleanup_logging(isc_log_t **logp);
dns_ttl_t strtottl(const char *str);
dst_key_state_t strtokeystate(const char *str);
isc_stdtime_t
strtotime(const char *str, int64_t now, int64_t base,
bool *setp);
......
......@@ -17,7 +17,6 @@ set -e
status=0
n=0
log=1
################################################################################
# Utilities #
......@@ -35,9 +34,14 @@ get_keyids() {
ls ${start}*${end} | sed "s/$dir\/K${zone}.+${algorithm}+\([0-9]\{5\}\)${end}/\1/"
}
# By default log errors and don't quit immediately.
_log=1
_continue=1
log_error() {
test $log -eq 1 && echo_i "error: $1"
test $_log -eq 1 && echo_i "error: $1"
ret=$((ret+1))
test $_continue -eq 1 || exit 1
}
# Check the created key in directory $1 for zone $2.
......@@ -115,18 +119,6 @@ check_created_key() {
#
# dnssec-keygen
#
n=$((n+1))
echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)"
ret=0
$KEYGEN -k _default kasp > keygen.out._default.test$n 2>/dev/null || ret=1
lines=$(cat keygen.out._default.test$n | wc -l)
test "$lines" -eq 1 || log_error "wrong number of keys created for policy _default"
KEY_ID=$(get_keyids "." "kasp" "13")
echo_i "check key $KEY_ID..."
check_created_key "." "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "3600" "0"
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
n=$((n+1))
echo_i "check that 'dnssec-keygen -k' (configured policy) creates valid files ($n)"
ret=0
......@@ -138,7 +130,7 @@ KEY_ID=$(get_keyids "keys" "kasp" "13")
echo_i "check key $KEY_ID..."
check_created_key "keys" "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "200" "31536000"
# Temporarily don't log errors because we are searching multiple files.
log=0
_log=0
# Check the other algorithm.
KEY_IDS=$(get_keyids "keys" "kasp" "8")
for KEY_ID in $KEY_IDS; do
......@@ -151,10 +143,21 @@ for KEY_ID in $KEY_IDS; do
# If ret is non-zero, non of the files matched.
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
done
# Turn error logs on again.
log=1
_log=1
n=$((n+1))
echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)"
ret=0
$KEYGEN -k _default kasp > keygen.out._default.test$n 2>/dev/null || ret=1
lines=$(cat keygen.out._default.test$n | wc -l)
test "$lines" -eq 1 || log_error "wrong number of keys created for policy _default"
KEY_ID=$(get_keyids "." "kasp" "13")
echo_i "check key $KEY_ID..."
check_created_key "." "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "3600" "0"
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
n=$((n+1))
echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)"
......@@ -171,6 +174,45 @@ status=$((status+ret))
#
# dnssec-settime
#
BASE_FILE="K${zone}.+${alg_numpad}+${key_idpad}"
KEY_FILE="${BASE_FILE}.key"
PRIVATE_FILE="${BASE_FILE}.private"
STATE_FILE="${BASE_FILE}.state"
CMP_FILE="${BASE_FILE}.cmp"
n=$((n+1))
echo_i "check that 'dnssec-settime' by default does not edit key state file ($n)"
ret=0
cp $STATE_FILE $CMP_FILE
$SETTIME -P +3600 $BASE_FILE >/dev/null || log_error "settime failed"
grep "; Publish: " $KEY_FILE > /dev/null || log_error "mismatch published in $KEY_FILE"
grep "Publish: " $PRIVATE_FILE > /dev/null || log_error "mismatch published in $PRIVATE_FILE"
$DIFF $CMP_FILE $STATE_FILE || log_error "unexpected file change in $STATE_FILE"
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
n=$((n+1))
echo_i "check that 'dnssec-settime -s' also sets time metadata in key state file ($n)"
ret=0
cp $STATE_FILE $CMP_FILE
now=$(date +%Y%m%d%H%M%S)
$SETTIME -s -P $now $BASE_FILE >/dev/null || log_error "settime failed"
grep "; Publish: $now" $KEY_FILE > /dev/null || log_error "mismatch published in $KEY_FILE"
grep "Publish: $now" $PRIVATE_FILE > /dev/null || log_error "mismatch published in $PRIVATE_FILE"
grep "Published: $now" $STATE_FILE > /dev/null || log_error "mismatch published in $STATE_FILE"
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
n=$((n+1))
echo_i "check that 'dnssec-settime -s' also unsets time metadata in key state file ($n)"
ret=0
cp $STATE_FILE $CMP_FILE
$SETTIME -s -P none $BASE_FILE >/dev/null || log_error "settime failed"
grep "; Publish:" $KEY_FILE > /dev/null && log_error "unexpected published in $KEY_FILE"
grep "Publish:" $PRIVATE_FILE > /dev/null && log_error "unexpected published in $PRIVATE_FILE"
grep "Published:" $STATE_FILE > /dev/null && log_error "unexpected published in $STATE_FILE"
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
#
# named
......
......@@ -70,20 +70,21 @@
goto cleanup; \
}
#define NEXTTOKEN_OR_EOF(lex, opt, token) { \
ret = isc_lex_gettoken(lex, opt, token); \
if (ret == ISC_R_EOF) \
break; \
if (ret != ISC_R_SUCCESS) \
goto cleanup; \
}
#define NEXTTOKEN_OR_EOF(lex, opt, token) \
do { \
ret = isc_lex_gettoken(lex, opt, token); \
if (ret == ISC_R_EOF) \
break; \
if (ret != ISC_R_SUCCESS) \
goto cleanup; \
} while ((*token).type == isc_tokentype_eol); \
#define READLINE(lex, opt, token) \
do { \
ret = isc_lex_gettoken(lex, opt, token); \
if (ret == ISC_R_EOF) \
break; \
else if (ret != ISC_R_SUCCESS) \
if (ret != ISC_R_SUCCESS) \
goto cleanup; \
} while ((*token).type != isc_tokentype_eol)
......@@ -94,6 +95,10 @@