[master] tag initializing keys

4798.	[func]		Keys specified in "managed-keys" statements
			are tagged as "initializing" until they have been
			updated by a key refresh query. If initialization
			fails it will be visible from "rndc secroots".
			[RT #46267]
This commit is contained in:
Evan Hunt 2017-10-27 15:45:18 -07:00
parent 1d57d460d4
commit c9f8165a06
24 changed files with 664 additions and 121 deletions

View file

@ -1,3 +1,9 @@
4798. [func] Keys specified in "managed-keys" statements
are tagged as "initializing" until they have been
updated by a key refresh query. If initialization
fails it will be visible from "rndc secroots".
[RT #46267]
4797. [func] Removed "isc-hmac-fixup", as the versions of BIND that
had the bug it worked around are long past end of
life. [RT #46411]

View file

@ -805,6 +805,11 @@ dstkey_fromconfig(const cfg_obj_t *vconfig, const cfg_obj_t *key,
return (result);
}
/*
* Load keys from configuration into key table. If 'keyname' is specified,
* only load keys matching that name. If 'managed' is true, load the key as
* an initializing key.
*/
static isc_result_t
load_view_keys(const cfg_obj_t *keys, const cfg_obj_t *vconfig,
dns_view_t *view, isc_boolean_t managed,
@ -820,12 +825,14 @@ load_view_keys(const cfg_obj_t *keys, const cfg_obj_t *vconfig,
for (elt = cfg_list_first(keys);
elt != NULL;
elt = cfg_list_next(elt)) {
elt = cfg_list_next(elt))
{
keylist = cfg_listelt_value(elt);
for (elt2 = cfg_list_first(keylist);
elt2 != NULL;
elt2 = cfg_list_next(elt2)) {
elt2 = cfg_list_next(elt2))
{
key = cfg_listelt_value(elt2);
result = dstkey_fromconfig(vconfig, key, managed,
&dstkey, mctx);
@ -833,8 +840,9 @@ load_view_keys(const cfg_obj_t *keys, const cfg_obj_t *vconfig,
result = ISC_R_SUCCESS;
continue;
}
if (result != ISC_R_SUCCESS)
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
/*
* If keyname was specified, we only add that key.
@ -846,17 +854,27 @@ load_view_keys(const cfg_obj_t *keys, const cfg_obj_t *vconfig,
continue;
}
CHECK(dns_keytable_add(secroots, managed, &dstkey));
/*
* This key is taken from the configuration, so
* if it's a managed key then it's an
* initializing key; that's why 'managed'
* is duplicated below.
*/
CHECK(dns_keytable_add2(secroots, managed,
managed, &dstkey));
}
}
cleanup:
if (dstkey != NULL)
if (dstkey != NULL) {
dst_key_free(&dstkey);
if (secroots != NULL)
}
if (secroots != NULL) {
dns_keytable_detach(&secroots);
if (result == DST_R_NOCRYPTO)
}
if (result == DST_R_NOCRYPTO) {
result = ISC_R_SUCCESS;
}
return (result);
}
@ -1026,7 +1044,7 @@ configure_view_dnsseckeys(dns_view_t *view, const cfg_obj_t *vconfig,
}
/*
* Add key zone for managed-keys.
* Add key zone for managed keys.
*/
obj = NULL;
(void)named_config_get(maps, "managed-keys-directory", &obj);
@ -1050,6 +1068,7 @@ configure_view_dnsseckeys(dns_view_t *view, const cfg_obj_t *vconfig,
goto cleanup;
}
}
CHECK(add_keydata_zone(view, directory, named_g_mctx));
cleanup:
@ -6443,16 +6462,19 @@ dotat(dns_keytable_t *keytable, dns_keynode_t *keynode, void *arg) {
}
nextnode = NULL;
(void)dns_keytable_nextkeynode(keytable, keynode, &nextnode);
if (keynode != firstnode)
if (keynode != firstnode) {
dns_keytable_detachkeynode(keytable, &keynode);
}
keynode = nextnode;
} while (keynode != NULL);
if (n == 0)
if (n == 0) {
return;
}
if (n > 1)
if (n > 1) {
qsort(ids, n, sizeof(ids[0]), cid);
}
/*
* Encoded as "_ta-xxxx\(-xxxx\)*" where xxxx is the hex version of
@ -6460,22 +6482,25 @@ dotat(dns_keytable_t *keytable, dns_keynode_t *keynode, void *arg) {
*/
label[0] = 0;
r.base = label;
r.length = sizeof(label);;
r.length = sizeof(label);
m = snprintf(r.base, r.length, "_ta");
if (m < 0 || (unsigned)m > r.length)
if (m < 0 || (unsigned)m > r.length) {
return;
}
isc_textregion_consume(&r, m);
for (i = 0; i < n; i++) {
m = snprintf(r.base, r.length, "-%04x", ids[i]);
if (m < 0 || (unsigned)m > r.length)
if (m < 0 || (unsigned)m > r.length) {
return;
}
isc_textregion_consume(&r, m);
}
dns_fixedname_init(&fixed);
tatname = dns_fixedname_name(&fixed);
result = dns_name_fromstring2(tatname, label, name, 0, NULL);
if (result != ISC_R_SUCCESS)
if (result != ISC_R_SUCCESS) {
return;
}
dns_name_format(tatname, namebuf, sizeof(namebuf));
isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL,
@ -6484,8 +6509,9 @@ dotat(dns_keytable_t *keytable, dns_keynode_t *keynode, void *arg) {
view->name, namebuf);
tat = isc_mem_get(dotat_arg->view->mctx, sizeof(*tat));
if (tat == NULL)
if (tat == NULL) {
return;
}
tat->mctx = NULL;
tat->task = NULL;

View file

@ -16,16 +16,8 @@ is used so it will send TAT queries once per second.
ns3 is a validator with a broken key in managed-keys.
Tests TODO:
ns4 is a validator with a deliberately broken managed-keys.bind and
managed-keys.jnl, causing RFC 5011 initialization to fail.
- initial working KSK
TODO: test using delv with new trusted key too
- introduce a REVOKE bit
- later remove a signature
- corrupt a signature
TODO: also same things with dlv auto updates of trust anchor
ns5 is a validator which is prevented from getting a response from the
root server, causing key refresh queries to fail.

View file

@ -10,8 +10,10 @@ rm -f */K* */*.signed */trusted.conf */*.jnl */*.bk
rm -f dsset-. ns1/dsset-.
rm -f ns*/named.lock
rm -f */managed-keys.bind* */named.secroots
rm -f */managed.conf ns1/managed.key ns1/managed.key.id
rm -f */managed*.conf ns1/managed.key ns1/managed.key.id
rm -f */named.memstats */named.run
rm -f dig.out* delv.out* rndc.out* signer.out*
rm -f ns1/named.secroots ns1/root.db.signed* ns1/root.db.tmp
rm -f ns1/named.conf
rm -rf ns4/nope
rm -f ns5/named.args

View file

@ -10,6 +10,11 @@
controls { /* empty */ };
acl allowed {
! 10.53.0.5;
any;
};
options {
query-source address 10.53.0.1;
notify-source 10.53.0.1;
@ -22,6 +27,7 @@ options {
notify no;
dnssec-enable yes;
dnssec-validation yes;
allow-query { allowed; };
};
key rndc_key {

View file

@ -10,6 +10,11 @@
controls { /* empty */ };
acl allowed {
! 10.53.0.5;
any;
};
options {
query-source address 10.53.0.1;
notify-source 10.53.0.1;
@ -22,6 +27,7 @@ options {
notify no;
dnssec-enable yes;
dnssec-validation yes;
allow-query { allowed; };
};
key rndc_key {

View file

@ -0,0 +1,39 @@
/*
* Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
*
* 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 http://mozilla.org/MPL/2.0/.
*/
// NS1
controls { /* empty */ };
options {
query-source address 10.53.0.1;
notify-source 10.53.0.1;
transfer-source 10.53.0.1;
port 5300;
pid-file "named.pid";
listen-on { 10.53.0.1; };
listen-on-v6 { none; };
recursion no;
notify no;
dnssec-enable yes;
dnssec-validation yes;
};
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.1 port 9953 allow { any; } keys { rndc_key; };
};
zone "." {
type master;
file "root.db.signed";
};

View file

@ -28,6 +28,8 @@ managed-keys {
EOF
' > managed.conf
cp managed.conf ../ns2/managed.conf
cp managed.conf ../ns4/managed.conf
cp managed.conf ../ns5/managed.conf
# Configure a trusted key statement (used by delve)
cat $keyname.key | grep -v '^; ' | $PERL -n -e '

View file

@ -1 +1 @@
-m record,size,mctx -T clienttest -c named.conf -d 99 -X named.lock -g -T mkeytimers=2/20/40
-m record,size,mctx -T clienttest -c named.conf -d 99 -X named.lock -g -T mkeytimers=2/20/40 -T tat=1

View file

@ -0,0 +1,41 @@
/*
* Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
*
* 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 http://mozilla.org/MPL/2.0/.
*/
// NS4
controls { /* empty */ };
options {
query-source address 10.53.0.4;
notify-source 10.53.0.4;
transfer-source 10.53.0.4;
port 5300;
pid-file "named.pid";
listen-on { 10.53.0.4; };
listen-on-v6 { none; };
recursion yes;
notify no;
dnssec-enable yes;
dnssec-validation auto;
bindkeys-file "managed.conf";
managed-keys-directory "nope";
};
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.4 port 9953 allow { any; } keys { rndc_key; };
};
zone "." {
type hint;
file "../../common/root.hint";
};

View file

@ -0,0 +1,38 @@
/*
* Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
*
* 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 http://mozilla.org/MPL/2.0/.
*/
// NS5
options {
query-source address 10.53.0.5;
notify-source 10.53.0.5;
transfer-source 10.53.0.5;
port 5300;
pid-file "named.pid";
listen-on { 10.53.0.5; };
listen-on-v6 { none; };
recursion yes;
notify no;
dnssec-enable yes;
dnssec-validation auto;
bindkeys-file "managed.conf";
};
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.5 port 9953 allow { any; } keys { rndc_key; };
};
zone "." {
type hint;
file "../../common/root.hint";
};

View file

@ -0,0 +1 @@
-m record,size,mctx -T clienttest -c named.conf -d 99 -X named.lock -g

View file

@ -0,0 +1 @@
-m record,size,mctx -T clienttest -c named.conf -d 99 -X named.lock -g -T mkeytimers=2/20/40

View file

@ -14,5 +14,14 @@ $SHELL clean.sh
test -r $RANDFILE || $GENRANDOM 800 $RANDFILE
cp ns1/named1.conf ns1/named.conf
cp ns5/named1.args ns5/named.args
cd ns1 && $SHELL sign.sh
( cd ns1 && $SHELL sign.sh )
cp ns2/managed.conf ns2/managed1.conf
cd ns4
mkdir nope
touch nope/managed-keys.bind
touch nope/managed.keys.bind.jnl
chmod 444 nope/*

View file

@ -215,9 +215,36 @@ t2=`grep "trust pending" ns2/managed-keys.bind`
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I: reinitialize trust anchors"
echo "I: reinitialize trust anchors, add second key to bind.keys"
$PERL $SYSTEMTESTTOP/stop.pl --use-rndc . ns2
rm -f ns2/managed-keys.bind*
cat ns1/$standby1.key | grep -v '^; ' | $PERL -n -e '
local ($dn, $class, $type, $flags, $proto, $alg, @rest) = split;
local $key = join("", @rest);
local $originalkey = `grep initial-key ns2/managed1.conf`;
print <<EOF
managed-keys {
$originalkey
"$dn" initial-key $flags $proto $alg "$key";
};
EOF
' > ns2/managed.conf
$PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns2
n=`expr $n + 1`
echo "I: check that no key from bind.keys is marked as an initializing key ($n)"
ret=0
sleep 3
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 secroots | sed 's/^/I: ns2 /'
sleep 1
grep '; initializing' ns2/named.secroots > /dev/null 2>&1 && ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I: reinitialize trust anchors, revert to one key in bind.keys"
$PERL $SYSTEMTESTTOP/stop.pl --use-rndc . ns2
rm -f ns2/managed-keys.bind*
mv ns2/managed1.conf ns2/managed.conf
$PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns2
n=`expr $n + 1`
@ -446,7 +473,6 @@ rm -f ${revoked}.key ${revoked}.private
$SETTIME -D none -R none -K ns1 `cat ns1/managed.key` > /dev/null
$SETTIME -D now -K ns1 $standby1 > /dev/null
$SETTIME -D now -K ns1 $standby2 > /dev/null
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 flush | sed 's/^/I: ns1 /'
sleep 1
$SIGNER -Sg -K ns1 -N unixtime -r $RANDFILE -o . ns1/root.db > /dev/null 2>&-
$RNDC -c ../common/rndc.conf -s 10.53.0.1 -p 9953 reload . | sed 's/^/I: ns1 /'
@ -454,6 +480,7 @@ sleep 3
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 managed-keys refresh | sed 's/^/I: ns2 /'
sleep 1
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 managed-keys status > rndc.out.$n 2>&1
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 flush | sed 's/^/I: ns1 /'
$DIG $DIGOPTS +noauth example. @10.53.0.2 txt > dig.out.ns2.test$n || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns2.test$n > /dev/null || ret=1
grep "example..*.RRSIG..*TXT" dig.out.ns2.test$n > /dev/null || ret=1
@ -537,14 +564,14 @@ status=`expr $status + $ret`
n=`expr $n + 1`
echo "I: check that trust-anchor-telemetry queries are logged ($n)"
ret=0
grep "sending trust-anchor-telemetry query '_ta-[0-9a-f]*/NULL" ns3/named.run > /dev/null || ret=1
grep "sending trust-anchor-telemetry query '_ta-[0-9a-f]*/NULL" ns2/named.run > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo "I: check that trust-anchor-telemetry queries are received ($n)"
ret=0
grep "query '_ta-[0-9a-f]*/NULL/IN' approved" ns1/named.run > /dev/null || ret=1
grep "query '_ta-[0-9a-f][0-9a-f]*/NULL/IN' approved" ns1/named.run > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
@ -562,5 +589,82 @@ grep "name: \." rndc.out.$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo "I: check that trust-anchor-telemetry queries contain the correct key ($n)"
ret=0
# convert the hexadecimal key from the TAT query into decimal and
# compare against the known key.
tathex=`grep "query '_ta-[0-9a-f][0-9a-f]*/NULL/IN' approved" ns1/named.run | awk '{print $6; exit 0}' | sed -e 's/(_ta-\([0-9a-f][0-9a-f]*\)):/\1/'`
tatkey=`$PERL -e 'printf("%d\n", hex(@ARGV[0]));' $tathex`
realkey=`$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 secroots - | grep '; managed' | sed 's#.*SHA256/\([0-9][0-9]*\) ; managed.*#\1#'`
[ "$tatkey" -eq "$realkey" ] || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo "I: check initialization fails if managed-keys can't be created ($n)"
ret=0
$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 secroots | sed 's/^/I: ns4 /'
grep '; initializing managed' ns4/named.secroots > /dev/null 2>&1 || ret=1
grep '; managed' ns4/named.secroots > /dev/null 2>&1 && ret=1
grep '; trusted' ns4/named.secroots > /dev/null 2>&1 && ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo "I: check failure to contact root servers does not prevent key refreshes after restart ($n)"
ret=0
# By the time we get here, ns5 should have attempted refreshing its managed
# keys. These attempts should fail as ns1 is configured to REFUSE all queries
# from ns5. Note that named1.args does not contain "-T mkeytimers"; this is to
# ensure key refresh retry will be scheduled to one actual hour after the first
# key refresh failure instead of just a few seconds, in order to prevent races
# between the next scheduled key refresh time and startup time of restarted ns5.
$PERL $SYSTEMTESTTOP/stop.pl --use-rndc . ns5
$PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns5
sleep 2
# ns5/named.run will contain logs from both the old instance and the new
# instance. In order for the test to pass, both must attempt a fetch.
count=`grep -c "Creating key fetch" ns5/named.run`
[ $count -lt 2 ] && ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo "I: check key refreshes are resumed after root servers become available ($n)"
ret=0
$PERL $SYSTEMTESTTOP/stop.pl --use-rndc . ns5
# Prevent previous check from affecting this one
rm -f ns2/managed-keys.bind*
# named2.args adds "-T mkeytimers=2/20/40" to named1.args as we need to wait for
# an "hour" until keys are refreshed again after initial failure
cp ns5/named2.args ns5/named.args
$PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns5
sleep 2
$RNDC -c ../common/rndc.conf -s 10.53.0.5 -p 9953 secroots | sed 's/^/I: ns4 /'
sleep 1
grep '; initializing managed' ns5/named.secroots > /dev/null 2>&1 || ret=1
# ns1 should still REFUSE queries from ns5, so resolving should be impossible
$DIG $DIGOPTS +noauth example. @10.53.0.5 txt > dig.out.ns5.a.test$n || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns5.a.test$n > /dev/null && ret=1
grep "example..*.RRSIG..*TXT" dig.out.ns5.a.test$n > /dev/null && ret=1
grep "status: SERVFAIL" dig.out.ns5.a.test$n > /dev/null || ret=1
# Allow queries from ns5 to ns1
cp ns1/named3.conf ns1/named.conf
rm -f ns1/root.db.signed.jnl
$RNDC -c ../common/rndc.conf -s 10.53.0.1 -p 9953 reconfig
sleep 3
$RNDC -c ../common/rndc.conf -s 10.53.0.5 -p 9953 secroots | sed 's/^/I: ns4 /'
sleep 1
grep '; managed' ns5/named.secroots > /dev/null 2>&1 || ret=1
# ns1 should not longer REFUSE queries from ns5, so managed keys should be
# correctly refreshed and resolving should succeed
$DIG $DIGOPTS +noauth example. @10.53.0.5 txt > dig.out.ns5.b.test$n || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns5.b.test$n > /dev/null || ret=1
grep "example..*.RRSIG..*TXT" dig.out.ns5.b.test$n > /dev/null || ret=1
grep "status: NOERROR" dig.out.ns5.b.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:exit status: $status"
[ $status -eq 0 ] || exit 1

View file

@ -566,6 +566,16 @@
are not writable by the effective user ID. [RT #46077]
</para>
</listitem>
<listitem>
<para>
Initializing keys specified in a <command>managed-keys</command>
statement or by <command>dnssec-validation auto;</command> are
now tagged as "initializing", until they have been updated by a
key refresh query. If key maintenance fails to initialize,
this will be visible when running <command>rndc secroots</command>.
[RT #46267]
</para>
</listitem>
<listitem>
<para>
Previously, <command>update-policy local;</command> accepted

View file

@ -1546,7 +1546,7 @@ dns_client_addtrustedkey(dns_client_t *client, dns_rdataclass_t rdclass,
if (result != ISC_R_SUCCESS)
goto cleanup;
result = dns_keytable_add(secroots, ISC_FALSE, &dstkey);
result = dns_keytable_add2(secroots, ISC_FALSE, ISC_FALSE, &dstkey);
cleanup:
if (dstkey != NULL)

View file

@ -102,10 +102,18 @@ dns_keytable_detach(dns_keytable_t **keytablep);
isc_result_t
dns_keytable_add(dns_keytable_t *keytable, isc_boolean_t managed,
dst_key_t **keyp);
dst_key_t **keyp) ISC_DEPRECATED;
isc_result_t
dns_keytable_add2(dns_keytable_t *keytable, isc_boolean_t managed,
isc_boolean_t initial, dst_key_t **keyp);
/*%<
* Add '*keyp' to 'keytable' (using the name in '*keyp').
* The value of keynode->managed is set to 'managed'
* The value of keynode->managed is set to 'managed', and the
* value of keynode->initial is set to 'initial'. (Note: 'initial'
* should only be used when adding managed-keys from configuration.
* This indicates the key is in "initializing" state, and has not yet
* been confirmed with a key refresh query. Once a key refresh query
* has validated, we update the keynode with inital == ISC_FALSE.)
*
* Notes:
*
@ -117,6 +125,8 @@ dns_keytable_add(dns_keytable_t *keytable, isc_boolean_t managed,
*
*\li 'keytable' points to a valid keytable.
*
*\li if 'initial' is true then 'managed' must also be true.
*
*\li keyp != NULL && *keyp is a valid dst_key_t *.
*
* Ensures:
@ -402,6 +412,19 @@ dns_keynode_managed(dns_keynode_t *keynode);
* Is this flagged as a managed key?
*/
isc_boolean_t
dns_keynode_initial(dns_keynode_t *keynode);
/*%<
* Is this flagged as an initializing key?
*/
void
dns_keynode_trust(dns_keynode_t *keynode);
/*%<
* Sets keynode->initial to ISC_FALSE in order to mark the key as
* trusted: no longer an initializing key.
*/
isc_result_t
dns_keynode_create(isc_mem_t *mctx, dns_keynode_t **target);
/*%<

View file

@ -614,6 +614,12 @@ dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries);
unsigned int
dns_resolver_getoptions(dns_resolver_t *resolver);
/*%<
* Get the resolver options.
*
* Requires:
* \li resolver to be valid.
*/
void
dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name,

View file

@ -47,6 +47,7 @@ struct dns_keynode {
isc_refcount_t refcount;
dst_key_t * key;
isc_boolean_t managed;
isc_boolean_t initial;
struct dns_keynode * next;
};
@ -164,83 +165,169 @@ dns_keytable_detach(dns_keytable_t **keytablep) {
*keytablep = NULL;
}
/*%
* Search "node" for either a null key node or a key node for the exact same
* key as the one supplied in "keyp" and, if found, update it accordingly.
*/
static isc_result_t
insert(dns_keytable_t *keytable, isc_boolean_t managed,
const dns_name_t *keyname, dst_key_t **keyp)
update_keynode(dst_key_t **keyp, dns_rbtnode_t *node, isc_boolean_t initial) {
dns_keynode_t *knode;
REQUIRE(keyp != NULL && *keyp != NULL);
REQUIRE(node != NULL);
for (knode = node->data; knode != NULL; knode = knode->next) {
if (knode->key == NULL) {
/*
* Null key node found. Attach the supplied key to it,
* making it a non-null key node and transferring key
* ownership to the keytable.
*/
knode->key = *keyp;
*keyp = NULL;
return (ISC_R_SUCCESS);
} else if (dst_key_compare(knode->key, *keyp)) {
/*
* Key node found for the supplied key. Free the
* supplied copy of the key and update the found key
* node's flags if necessary.
*/
dst_key_free(keyp);
if (!initial) {
dns_keynode_trust(knode);
}
return (ISC_R_SUCCESS);
}
}
return (ISC_R_NOTFOUND);
}
/*%
* Create a key node for "keyp" (or a null key node if "keyp" is NULL), set
* "managed" and "initial" as requested and make the created key node the first
* one attached to "node" in "keytable".
*/
static isc_result_t
prepend_keynode(dst_key_t **keyp, dns_rbtnode_t *node,
dns_keytable_t *keytable, isc_boolean_t managed,
isc_boolean_t initial)
{
isc_result_t result;
dns_keynode_t *knode = NULL;
dns_rbtnode_t *node;
isc_result_t result;
REQUIRE(keyp == NULL || *keyp != NULL);
REQUIRE(VALID_KEYTABLE(keytable));
REQUIRE(!initial || managed);
result = dns_keynode_create(keytable->mctx, &knode);
if (result != ISC_R_SUCCESS)
if (result != ISC_R_SUCCESS) {
return (result);
}
/*
* If a key was supplied, transfer its ownership to the keytable.
*/
if (keyp) {
knode->key = *keyp;
*keyp = NULL;
}
knode->managed = managed;
knode->initial = initial;
knode->next = node->data;
node->data = knode;
return (ISC_R_SUCCESS);
}
/*%
* Add key "keyp" at "keyname" in "keytable". If the key already exists at the
* requested name, update its flags. If "keyp" is NULL, add a null key to
* indicate that "keyname" should be treated as a secure domain without
* supplying key data which would allow the domain to be validated.
*/
static isc_result_t
insert(dns_keytable_t *keytable, isc_boolean_t managed, isc_boolean_t initial,
const dns_name_t *keyname, dst_key_t **keyp)
{
dns_rbtnode_t *node = NULL;
isc_result_t result;
REQUIRE(VALID_KEYTABLE(keytable));
RWLOCK(&keytable->rwlock, isc_rwlocktype_write);
node = NULL;
result = dns_rbt_addnode(keytable->table, keyname, &node);
if (keyp != NULL) {
if (result == ISC_R_EXISTS) {
/* Key already in table? */
dns_keynode_t *k;
for (k = node->data; k != NULL; k = k->next) {
if (k->key == NULL) {
k->key = *keyp;
*keyp = NULL; /* transfer ownership */
break;
}
if (dst_key_compare(k->key, *keyp) == ISC_TRUE)
break;
}
if (k == NULL)
result = ISC_R_SUCCESS;
else if (*keyp != NULL)
dst_key_free(keyp);
}
if (result == ISC_R_SUCCESS) {
knode->key = *keyp;
knode->next = node->data;
*keyp = NULL;
}
}
if (result == ISC_R_SUCCESS) {
node->data = knode;
knode = NULL;
/*
* There was no node for "keyname" in "keytable" yet, so one
* was created. Create a new key node for the supplied key (or
* a null key node if "keyp" is NULL) and attach it to the
* created node.
*/
result = prepend_keynode(keyp, node, keytable, managed,
initial);
} else if (result == ISC_R_EXISTS) {
/*
* A node already exists for "keyname" in "keytable".
*/
if (keyp == NULL) {
/*
* We were told to add a null key at "keyname", which
* means there is nothing left to do as there is either
* a null key at this node already or there is a
* non-null key node which would not be affected.
* Reset result to reflect the fact that the node for
* "keyname" is already marked as secure.
*/
result = ISC_R_SUCCESS;
} else {
/*
* We were told to add the key supplied in "keyp" at
* "keyname". Try to find an already existing key node
* we could reuse for the supplied key (i.e. a null key
* node or a key node for the exact same key) and, if
* found, update it accordingly.
*/
result = update_keynode(keyp, node, initial);
if (result == ISC_R_NOTFOUND) {
/*
* The node for "keyname" only contains key
* nodes for keys different than the supplied
* one. Create a new key node for the supplied
* key and prepend it before the others.
*/
result = prepend_keynode(keyp, node, keytable,
managed, initial);
}
}
}
/* Key was already there? That's the same as a success */
if (result == ISC_R_EXISTS)
result = ISC_R_SUCCESS;
RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write);
if (knode != NULL)
dns_keynode_detach(keytable->mctx, &knode);
return (result);
}
isc_result_t
dns_keytable_add(dns_keytable_t *keytable, isc_boolean_t managed,
dst_key_t **keyp)
{
return (dns_keytable_add2(keytable, managed, ISC_FALSE, keyp));
}
isc_result_t
dns_keytable_add2(dns_keytable_t *keytable, isc_boolean_t managed,
isc_boolean_t initial, dst_key_t **keyp)
{
REQUIRE(keyp != NULL && *keyp != NULL);
return (insert(keytable, managed, dst_key_name(*keyp), keyp));
REQUIRE(!initial || managed);
return (insert(keytable, managed, initial, dst_key_name(*keyp), keyp));
}
isc_result_t
dns_keytable_marksecure(dns_keytable_t *keytable, const dns_name_t *name) {
return (insert(keytable, ISC_TRUE, name, NULL));
return (insert(keytable, ISC_TRUE, ISC_FALSE, name, NULL));
}
isc_result_t
@ -644,8 +731,9 @@ dns_keytable_totext(dns_keytable_t *keytable, isc_buffer_t **text) {
if (knode->key == NULL)
continue;
dst_key_format(knode->key, pbuf, sizeof(pbuf));
snprintf(obuf, sizeof(obuf), "%s ; %s\n", pbuf,
knode->managed ? "managed" : "trusted");
snprintf(obuf, sizeof(obuf), "%s ; %s%s\n", pbuf,
knode->initial ? "initializing " : "",
knode->managed ? "managed" : "trusted");
result = putstr(text, obuf);
if (result != ISC_R_SUCCESS)
break;
@ -703,11 +791,6 @@ dns_keytable_forall(dns_keytable_t *keytable,
dst_key_t *
dns_keynode_key(dns_keynode_t *keynode) {
/*
* Get the DST key associated with keynode.
*/
REQUIRE(VALID_KEYNODE(keynode));
return (keynode->key);
@ -715,14 +798,25 @@ dns_keynode_key(dns_keynode_t *keynode) {
isc_boolean_t
dns_keynode_managed(dns_keynode_t *keynode) {
/*
* Is this a managed key?
*/
REQUIRE(VALID_KEYNODE(keynode));
return (keynode->managed);
}
isc_boolean_t
dns_keynode_initial(dns_keynode_t *keynode) {
REQUIRE(VALID_KEYNODE(keynode));
return (keynode->initial);
}
void
dns_keynode_trust(dns_keynode_t *keynode) {
REQUIRE(VALID_KEYNODE(keynode));
keynode->initial = ISC_FALSE;
}
isc_result_t
dns_keynode_create(isc_mem_t *mctx, dns_keynode_t **target) {
isc_result_t result;
@ -736,6 +830,7 @@ dns_keynode_create(isc_mem_t *mctx, dns_keynode_t **target) {
knode->magic = KEYNODE_MAGIC;
knode->managed = ISC_FALSE;
knode->initial = ISC_FALSE;
knode->key = NULL;
knode->next = NULL;

View file

@ -10595,8 +10595,8 @@ dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name,
if (!fuzzing_resolver)
#endif
{
(void) dns_badcache_add(resolver->badcache, name, type,
ISC_FALSE, 0, expire);
dns_badcache_add(resolver->badcache, name, type,
ISC_FALSE, 0, expire);
}
}

View file

@ -126,7 +126,12 @@ create_tables() {
/* Add a normal key */
create_key(257, 3, 5, "example.com", keystr1, &key);
ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key),
ATF_REQUIRE_EQ(dns_keytable_add2(keytable, ISC_FALSE, ISC_FALSE, &key),
ISC_R_SUCCESS);
/* Add an initializing managed key */
create_key(257, 3, 5, "managed.com", keystr1, &key);
ATF_REQUIRE_EQ(dns_keytable_add2(keytable, ISC_TRUE, ISC_TRUE, &key),
ISC_R_SUCCESS);
/* Add a null key */
@ -185,7 +190,7 @@ ATF_TC_BODY(add, tc) {
* nextkeynode() should still return NOTFOUND.
*/
create_key(257, 3, 5, "example.com", keystr1, &key);
ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key),
ATF_REQUIRE_EQ(dns_keytable_add2(keytable, ISC_FALSE, ISC_FALSE, &key),
ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
&next_keynode), ISC_R_NOTFOUND);
@ -193,23 +198,129 @@ ATF_TC_BODY(add, tc) {
/* Add another key (different keydata) */
dns_keytable_detachkeynode(keytable, &keynode);
create_key(257, 3, 5, "example.com", keystr2, &key);
ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key),
ATF_REQUIRE_EQ(dns_keytable_add2(keytable, ISC_FALSE, ISC_FALSE, &key),
ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"),
&keynode), ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
&next_keynode), ISC_R_SUCCESS);
dns_keytable_detachkeynode(keytable, &next_keynode);
dns_keytable_detachkeynode(keytable, &keynode);
/*
* Get the keynode for the managed.com key. There's no other key for
* the name, so nextkeynode() should return NOTFOUND. Ensure the
* retrieved key is an initializing key, then mark it as trusted using
* dns_keynode_trust() and ensure the latter works as expected.
*/
ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("managed.com"),
&keynode), ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
&next_keynode), ISC_R_NOTFOUND);
ATF_REQUIRE_EQ(dns_keynode_initial(keynode), ISC_TRUE);
dns_keynode_trust(keynode);
ATF_REQUIRE_EQ(dns_keynode_initial(keynode), ISC_FALSE);
dns_keytable_detachkeynode(keytable, &keynode);
/*
* Add a different managed key for managed.com, marking it as an
* initializing key. Ensure nextkeynode() no longer returns
* ISC_R_NOTFOUND and that the added key is an initializing key.
*/
create_key(257, 3, 5, "managed.com", keystr2, &key);
ATF_REQUIRE_EQ(dns_keytable_add2(keytable, ISC_TRUE, ISC_TRUE, &key),
ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("managed.com"),
&keynode), ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
&next_keynode), ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keynode_initial(keynode), ISC_TRUE);
dns_keytable_detachkeynode(keytable, &next_keynode);
dns_keytable_detachkeynode(keytable, &keynode);
/*
* Add the same managed key again, but this time mark it as a
* non-initializing key. Ensure the previously added key is upgraded
* to a non-initializing key and make sure there are still two key
* nodes for managed.com, both containing non-initializing keys.
*/
create_key(257, 3, 5, "managed.com", keystr2, &key);
ATF_REQUIRE_EQ(dns_keytable_add2(keytable, ISC_TRUE, ISC_FALSE, &key),
ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("managed.com"),
&keynode), ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keynode_initial(keynode), ISC_FALSE);
ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
&next_keynode), ISC_R_SUCCESS);
dns_keytable_detachkeynode(keytable, &keynode);
keynode = next_keynode;
next_keynode = NULL;
ATF_REQUIRE_EQ(dns_keynode_initial(keynode), ISC_FALSE);
ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
&next_keynode), ISC_R_NOTFOUND);
dns_keytable_detachkeynode(keytable, &keynode);
/*
* Add a managed key at a new node, two.com, marking it as an
* initializing key. Ensure nextkeynode() returns ISC_R_NOTFOUND and
* that the added key is an initializing key.
*/
create_key(257, 3, 5, "two.com", keystr1, &key);
ATF_REQUIRE_EQ(dns_keytable_add2(keytable, ISC_TRUE, ISC_TRUE, &key),
ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("two.com"),
&keynode), ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
&next_keynode), ISC_R_NOTFOUND);
ATF_REQUIRE_EQ(dns_keynode_initial(keynode), ISC_TRUE);
dns_keytable_detachkeynode(keytable, &keynode);
/*
* Add a different managed key for two.com, marking it as a
* non-initializing key. Ensure nextkeynode() no longer returns
* ISC_R_NOTFOUND and that the added key is not an initializing key.
*/
create_key(257, 3, 5, "two.com", keystr2, &key);
ATF_REQUIRE_EQ(dns_keytable_add2(keytable, ISC_TRUE, ISC_FALSE, &key),
ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("two.com"),
&keynode), ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
&next_keynode), ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keynode_initial(keynode), ISC_FALSE);
dns_keytable_detachkeynode(keytable, &next_keynode);
dns_keytable_detachkeynode(keytable, &keynode);
/*
* Add the first managed key again, but this time mark it as a
* non-initializing key. Ensure the previously added key is upgraded
* to a non-initializing key and make sure there are still two key
* nodes for two.com, both containing non-initializing keys.
*/
create_key(257, 3, 5, "two.com", keystr1, &key);
ATF_REQUIRE_EQ(dns_keytable_add2(keytable, ISC_TRUE, ISC_FALSE, &key),
ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("two.com"),
&keynode), ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keynode_initial(keynode), ISC_FALSE);
ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
&next_keynode), ISC_R_SUCCESS);
dns_keytable_detachkeynode(keytable, &keynode);
keynode = next_keynode;
next_keynode = NULL;
ATF_REQUIRE_EQ(dns_keynode_initial(keynode), ISC_FALSE);
ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
&next_keynode), ISC_R_NOTFOUND);
dns_keytable_detachkeynode(keytable, &keynode);
/*
* Add a normal key to a name that has a null key. The null key node
* will be updated with the normal key.
*/
dns_keytable_detachkeynode(keytable, &keynode);
ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"),
&null_keynode), ISC_R_SUCCESS);
create_key(257, 3, 5, "null.example", keystr2, &key);
ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key),
ATF_REQUIRE_EQ(dns_keytable_add2(keytable, ISC_FALSE, ISC_FALSE, &key),
ISC_R_SUCCESS);
ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"),
&keynode), ISC_R_SUCCESS);
@ -523,7 +634,7 @@ ATF_TC_BODY(nta, tc) {
ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
create_key(257, 3, 5, "example", keystr1, &key);
result = dns_keytable_add(keytable, ISC_FALSE, &key);
result = dns_keytable_add2(keytable, ISC_FALSE, ISC_FALSE, &key);
ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
isc_stdtime_get(&now);

View file

@ -431,10 +431,13 @@ dns_keynode_attach
dns_keynode_create
dns_keynode_detach
dns_keynode_detachall
dns_keynode_initial
dns_keynode_key
dns_keynode_managed
dns_keynode_trust
dns_keyring_restore
dns_keytable_add
dns_keytable_add2
dns_keytable_attach
dns_keytable_attachkeynode
dns_keytable_create

View file

@ -3891,7 +3891,8 @@ compute_tag(dns_name_t *name, dns_rdata_dnskey_t *dnskey, isc_mem_t *mctx,
*/
static void
trust_key(dns_zone_t *zone, dns_name_t *keyname,
dns_rdata_dnskey_t *dnskey, isc_mem_t *mctx)
dns_rdata_dnskey_t *dnskey, isc_boolean_t initial,
isc_mem_t *mctx)
{
isc_result_t result;
dns_rdata_t rdata = DNS_RDATA_INIT;
@ -3910,7 +3911,7 @@ trust_key(dns_zone_t *zone, dns_name_t *keyname,
goto failure;
CHECK(dns_dnssec_keyfromrdata(keyname, &rdata, mctx, &dstkey));
CHECK(dns_keytable_add(sr, ISC_TRUE, &dstkey));
CHECK(dns_keytable_add2(sr, ISC_TRUE, initial, &dstkey));
dns_keytable_detach(&sr);
failure:
@ -3996,7 +3997,8 @@ load_secroots(dns_zone_t *zone, dns_name_t *name, dns_rdataset_t *rdataset) {
/* Add to keytables. */
trusted++;
trust_key(zone, name, &dnskey, mctx);
trust_key(zone, name, &dnskey,
ISC_TF(keydata.addhd == 0), mctx);
}
if (trusted == 0 && pending != 0) {
@ -4731,8 +4733,9 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime,
case dns_zone_key:
result = sync_keyzone(zone, db);
if (result != ISC_R_SUCCESS)
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
break;
default:
@ -4882,9 +4885,17 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime,
return (result);
cleanup:
if (zone->type == dns_zone_key && result != ISC_R_SUCCESS) {
dns_zone_log(zone, ISC_LOG_ERROR,
"failed to initialize managed-keys (%s): "
"DNSSEC validation is at risk",
isc_result_totext(result));
}
for (inc = ISC_LIST_HEAD(zone->newincludes);
inc != NULL;
inc = ISC_LIST_HEAD(zone->newincludes)) {
inc = ISC_LIST_HEAD(zone->newincludes))
{
ISC_LIST_UNLINK(zone->newincludes, inc, link);
isc_mem_free(zone->mctx, inc->name);
isc_mem_put(zone->mctx, inc, sizeof(*inc));
@ -9045,7 +9056,7 @@ keyfetch_done(isc_task_t *task, isc_event_t *event) {
dst_key_t *dstkey;
isc_stdtime_t now;
int pending = 0;
isc_boolean_t secure = ISC_FALSE;
isc_boolean_t secure = ISC_FALSE, initial = ISC_FALSE;
isc_boolean_t free_needed;
UNUSED(task);
@ -9122,7 +9133,8 @@ keyfetch_done(isc_task_t *task, isc_event_t *event) {
*/
for (result = dns_rdataset_first(&kfetch->dnskeysigset);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(&kfetch->dnskeysigset)) {
result = dns_rdataset_next(&kfetch->dnskeysigset))
{
dns_keynode_t *keynode = NULL;
dns_rdata_reset(&sigrr);
@ -9141,7 +9153,8 @@ keyfetch_done(isc_task_t *task, isc_event_t *event) {
break;
if (dst_key_alg(dstkey) == sig.algorithm &&
dst_key_id(dstkey) == sig.keyid) {
dst_key_id(dstkey) == sig.keyid)
{
result = dns_dnssec_verify2(keyname,
&kfetch->dnskeyset,
dstkey, ISC_FALSE,
@ -9159,6 +9172,9 @@ keyfetch_done(isc_task_t *task, isc_event_t *event) {
dns_trust_secure;
kfetch->dnskeysigset.trust =
dns_trust_secure;
secure = ISC_TRUE;
initial = dns_keynode_initial(keynode);
dns_keynode_trust(keynode);
break;
}
}
@ -9169,11 +9185,11 @@ keyfetch_done(isc_task_t *task, isc_event_t *event) {
keynode = nextnode;
}
if (keynode != NULL)
if (keynode != NULL) {
dns_keytable_detachkeynode(secroots, &keynode);
}
if (kfetch->dnskeyset.trust == dns_trust_secure) {
secure = ISC_TRUE;
if (secure) {
break;
}
}
@ -9182,7 +9198,6 @@ keyfetch_done(isc_task_t *task, isc_event_t *event) {
* If we were not able to verify the answer using the current
* trusted keys then all we can do is look at any revoked keys.
*/
if (!secure) {
dns_zone_log(zone, ISC_LOG_DEBUG(3),
"DNSKEY set for zone '%s' could not be verified "
@ -9422,10 +9437,13 @@ keyfetch_done(isc_task_t *task, isc_event_t *event) {
trustkey = ISC_TRUE;
dns_zone_log(zone, ISC_LOG_INFO,
"Key %d for zone %s "
"acceptance timer "
"complete: "
"key now trusted",
keytag, namebuf);
"is now trusted (%s)",
keytag, namebuf,
initial
? "initializing key "
"verified"
: "acceptance timer "
"complete");
}
} else if (keydata.addhd > now) {
/*
@ -9524,7 +9542,7 @@ keyfetch_done(isc_task_t *task, isc_event_t *event) {
/* Trust this key. */
result = dns_rdata_tostruct(&dnskeyrr, &dnskey, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
trust_key(zone, keyname, &dnskey, mctx);
trust_key(zone, keyname, &dnskey, ISC_FALSE, mctx);
}
if (secure && !deletekey) {
@ -9546,7 +9564,6 @@ keyfetch_done(isc_task_t *task, isc_event_t *event) {
fail_secure(zone, keyname);
done:
if (!ISC_LIST_EMPTY(diff.tuples)) {
/* Write changes to journal file. */
CHECK(update_soa_serial(kfetch->db, ver, &diff, mctx,
@ -9559,7 +9576,12 @@ keyfetch_done(isc_task_t *task, isc_event_t *event) {
}
failure:
if (result != ISC_R_SUCCESS) {
dns_zone_log(zone, ISC_LOG_ERROR,
"error during managed-keys processing (%s): "
"DNSSEC validation may be at risk",
isc_result_totext(result));
}
dns_diff_clear(&diff);
if (ver != NULL)
dns_db_closeversion(kfetch->db, &ver, commit);
@ -9669,7 +9691,7 @@ zone_refreshkeys(dns_zone_t *zone) {
}
/* Acceptance timer expired? */
if (kd.addhd != 0 && kd.addhd < now)
if (kd.addhd < now)
timer = kd.addhd;
/* Or do we just need to refresh the keyset? */