]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add tests for CDS/CDNSKEY publication
authorMatthijs Mekking <matthijs@isc.org>
Mon, 4 Nov 2019 10:20:00 +0000 (11:20 +0100)
committerMatthijs Mekking <matthijs@isc.org>
Wed, 6 Nov 2019 21:36:21 +0000 (22:36 +0100)
The kasp system tests are updated with 'check_cds' calls that will
verify that the correct CDS and CDNSKEY records are published during
a rollover and that they are signed with the correct KSK.

This requires a change in 'dnssec.c' to check the kasp key states
whether the CDS/CDNSKEY of a key should be published or not.  If no
kasp state exist, fall back to key timings.

bin/tests/system/kasp/tests.sh
lib/dns/dnssec.c

index 42f2eef2ab408d720b5b042bbbd5f95b687c6fed..a79a871cc6b0cfcf02fc83a6e9498a5a2dda329b 100644 (file)
@@ -700,7 +700,7 @@ grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch st
 grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${qtype}.*257.*.3.*${KEY1[$ALG_NUM]}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${qtype} record in response"
 lines=$(get_keys_which_signed $qtype dig.out.$DIR.test$n | wc -l)
 test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response"
-get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with ${KEY_ID}"
+get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with key ${KEY_ID}"
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
 
@@ -714,7 +714,7 @@ grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch st
 grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*${qtype}.*mname1\..*\." dig.out.$DIR.test$n > /dev/null || log_error "missing ${qtype} record in response"
 lines=$(get_keys_which_signed $qtype dig.out.$DIR.test$n | wc -l)
 test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response"
-get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with ${KEY_ID}"
+get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with key ${KEY_ID}"
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
 
@@ -735,14 +735,14 @@ do
        grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.11" dig.out.$DIR.test$n.a > /dev/null || log_error "missing a.${ZONE} A record in response"
        lines=$(get_keys_which_signed A dig.out.$DIR.test$n.a | wc -l)
        test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response"
-       get_keys_which_signed A dig.out.$DIR.test$n.a | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with ${KEY_ID}"
+       get_keys_which_signed A dig.out.$DIR.test$n.a | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with key ${KEY_ID}"
 
        dig_with_opts "d.${ZONE}" @10.53.0.3 A > dig.out.$DIR.test$n.d || log_error "dig d.${ZONE} A failed"
        grep "status: NOERROR" dig.out.$DIR.test$n.d > /dev/null || log_error "mismatch status in DNS response"
        grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.4" dig.out.$DIR.test$n.d > /dev/null || log_error "missing d.${ZONE} A record in response"
        lines=$(get_keys_which_signed A dig.out.$DIR.test$n.d | wc -l)
        test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response"
-       get_keys_which_signed A dig.out.$DIR.test$n.d | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with ${KEY_ID}"
+       get_keys_which_signed A dig.out.$DIR.test$n.d | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with key ${KEY_ID}"
 
        i=`expr $i + 1`
        if [ $ret = 0 ]; then break; fi
@@ -870,22 +870,59 @@ check_signatures() {
        if [ "${KEY1[$_expect_type]}" == "yes" ] && [ "${KEY1[$_role]}" == "yes" ]; then
                get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with key ${KEY1[$ID]}"
        elif [ "${KEY1[$EXPECT]}" == "yes" ]; then
-               get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY1[$ID]}"
+               get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key ${KEY1[$ID]}"
        fi
 
        if [ "${KEY2[$_expect_type]}" == "yes" ] && [ "${KEY2[$_role]}" == "yes" ]; then
-               get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with ${KEY2[$ID]}"
+               get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with key ${KEY2[$ID]}"
        elif [ "${KEY2[$EXPECT]}" == "yes" ]; then
-               get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY2[$ID]}"
+               get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key ${KEY2[$ID]}"
        fi
 
        if [ "${KEY3[$_expect_type]}" == "yes" ] && [ "${KEY3[$_role]}" == "yes" ]; then
-               get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with ${KEY3[$ID]}"
+               get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with key ${KEY3[$ID]}"
        elif [ "${KEY3[$EXPECT]}" == "yes" ]; then
-               get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY3[$ID]}"
+               get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key ${KEY3[$ID]}"
        fi
 }
 
+# Test CDS and CDNSKEY publication.
+check_cds() {
+
+       _qtype="CDS"
+       _key_algnum="${KEY1[$ALG_NUM]}"
+
+       n=$((n+1))
+       echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)"
+       ret=0
+       dig_with_opts $ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed"
+       grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response"
+
+       if [ "${KEY1[$STATE_DS]}" == "rumoured" ] || [ "${KEY1[$STATE_DS]}" == "omnipresent" ]; then
+               grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY1[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY1[$ID]}"
+               check_signatures $_qtype dig.out.$DIR.test$n $KSK
+       elif [ "${KEY1[$EXPECT]}" == "yes" ]; then
+               grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY1[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY1[$ID]}"
+       fi
+
+       if [ "${KEY2[$STATE_DS]}" == "rumoured" ] || [ "${KEY2[$STATE_DS]}" == "omnipresent" ]; then
+               grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY2[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY2[$ID]}"
+               check_signatures $_qtype dig.out.$DIR.test$n $KSK
+       elif [ "${KEY2[$EXPECT]}" == "yes" ]; then
+               grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY2[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY2[$ID]}"
+       fi
+
+       if [ "${KEY3[$STATE_DS]}" == "rumoured" ] || [ "${KEY3[$STATE_DS]}" == "omnipresent" ]; then
+               grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY3[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY3[$ID]}"
+               check_signatures $_qtype dig.out.$DIR.test$n $KSK
+       elif [ "${KEY3[$EXPECT]}" == "yes" ]; then
+               grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY3[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY3[$ID]}"
+       fi
+
+       test "$ret" -eq 0 || echo_i "failed"
+       status=$((status+ret))
+}
+
 # Test the apex of a configured zone. This checks that the SOA and DNSKEY
 # RRsets are signed correctly and with the appropriate keys.
 check_apex() {
@@ -916,6 +953,9 @@ check_apex() {
        check_signatures $_qtype dig.out.$DIR.test$n $ZSK
        test "$ret" -eq 0 || echo_i "failed"
        status=$((status+ret))
+
+       # Test CDS publication.
+       check_cds
 }
 
 # Test an RRset below the apex and verify it is signed correctly.
index 388689144b575feca750773d43ab472cb2f16224..9e21cc35b1cdb3fca940a46b3b8a17691b8ecc36 100644 (file)
@@ -635,7 +635,10 @@ dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) {
  * Indicate whether a key is scheduled to to have CDS/CDNSKEY records
  * published now.
  *
- * Returns true iff.
+ * Returns true if.
+ *  - kasp says the DS record should be published (e.g. the DS state is in
+ *    RUMOURED or OMNIPRESENT state).
+ * Or:
  *  - SyncPublish is set and in the past, AND
  *  - SyncDelete is unset or in the future
  */
@@ -643,6 +646,7 @@ static bool
 syncpublish(dst_key_t *key, isc_stdtime_t now) {
        isc_result_t result;
        isc_stdtime_t when;
+       dst_key_state_t state;
        int major, minor;
 
        /*
@@ -654,18 +658,29 @@ syncpublish(dst_key_t *key, isc_stdtime_t now) {
        /*
         * Smart signing started with key format 1.3
         */
-       if (major == 1 && minor <= 2)
+       if (major == 1 && minor <= 2) {
                return (false);
+       }
+
+       /* Check kasp state first. */
+       result = dst_key_getstate(key, DST_KEY_DS, &state);
+       if (result == ISC_R_SUCCESS) {
+               return (state == DST_KEY_STATE_RUMOURED ||
+                       state == DST_KEY_STATE_OMNIPRESENT);
+       }
 
+       /* If no kasp state, check timings. */
        result = dst_key_gettime(key, DST_TIME_SYNCPUBLISH, &when);
-       if (result != ISC_R_SUCCESS)
+       if (result != ISC_R_SUCCESS) {
                return (false);
-
+       }
        result = dst_key_gettime(key, DST_TIME_SYNCDELETE, &when);
-       if (result != ISC_R_SUCCESS)
+       if (result != ISC_R_SUCCESS) {
                return (true);
-       if (when <= now)
+       }
+       if (when <= now) {
                return (false);
+       }
        return (true);
 }
 
@@ -673,12 +688,17 @@ syncpublish(dst_key_t *key, isc_stdtime_t now) {
  * Indicate whether a key is scheduled to to have CDS/CDNSKEY records
  * deleted now.
  *
- * Returns true iff. SyncDelete is set and in the past.
+ * Returns true if:
+ *  - kasp says the DS record should be unpublished (e.g. the DS state is in
+ *    UNRETENTIVE or HIDDEN state).
+ * Or:
+ * - SyncDelete is set and in the past.
  */
 static bool
 syncdelete(dst_key_t *key, isc_stdtime_t now) {
        isc_result_t result;
        isc_stdtime_t when;
+       dst_key_state_t state;
        int major, minor;
 
        /*
@@ -690,14 +710,25 @@ syncdelete(dst_key_t *key, isc_stdtime_t now) {
        /*
         * Smart signing started with key format 1.3.
         */
-       if (major == 1 && minor <= 2)
+       if (major == 1 && minor <= 2) {
                return (false);
+       }
+
+       /* Check kasp state first. */
+       result = dst_key_getstate(key, DST_KEY_DS, &state);
+       if (result == ISC_R_SUCCESS) {
+               return (state == DST_KEY_STATE_UNRETENTIVE ||
+                       state == DST_KEY_STATE_HIDDEN);
+       }
 
+       /* If no kasp state, check timings. */
        result = dst_key_gettime(key, DST_TIME_SYNCDELETE, &when);
-       if (result != ISC_R_SUCCESS)
+       if (result != ISC_R_SUCCESS) {
                return (false);
-       if (when <= now)
+       }
+       if (when <= now) {
                return (true);
+       }
        return (false);
 }
 
@@ -2135,6 +2166,9 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys,
                /* Printable version of key2 (the old key, if any) */
                dst_key_format(key2->key, keystr2, sizeof(keystr2));
 
+               /* Copy key metadata. */
+               dst_key_copy_metadata(key2->key, key1->key);
+
                /* Match found: remove or update it as needed */
                if (key1->hint_remove) {
                        RETERR(remove_key(diff, key2, origin, ttl, mctx,