]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Fix and test migration to dnssec-policy
authorMatthijs Mekking <matthijs@isc.org>
Fri, 27 Mar 2020 09:28:22 +0000 (10:28 +0100)
committerMatthijs Mekking <matthijs@isc.org>
Fri, 3 Apr 2020 06:29:22 +0000 (08:29 +0200)
Migrating from 'auto-dnssec maintain;' to dnssec-policy did not
work properly, mainly because the legacy keys were initialized
badly. Several adjustments in the keymgr are required to get it right:

- Set published time on keys when we calculate prepublication time.
  This is not strictly necessary, but it is weird to have an active
  key without the published time set.

- Initalize key states also before matching keys. Determine the
  target state by looking at existing time metadata: If the time
  data is set and is in the past, it is a hint that the key and
  its corresponding records have been published in the zone already,
  and the state is initialized to RUMOURED. Otherwise, initialize it
  as HIDDEN. This fixes migration to dnssec-policy from existing
  keys.

- Initialize key goal on keys that match key policy to OMNIPRESENT.
  These may be existing legacy keys that are being migrated.

- A key that has its goal to OMNIPRESENT *or* an active key can
  match a kasp key.  The code was changed with CHANGE 5354 that
  was a bugfix to prevent creating new KSK keys for zones in the
  initial stage of signing.  However, this caused problems for
  restarts when rollovers are in progress, because an outroducing
  key can still be an active key.

The test for this introduces a new KEY property 'legacy'.  This is
used to skip tests related to .state files.

bin/tests/system/kasp/ns6/named.conf.in
bin/tests/system/kasp/ns6/named2.conf.in
bin/tests/system/kasp/ns6/policies/kasp.conf
bin/tests/system/kasp/ns6/setup.sh
bin/tests/system/kasp/tests.sh
lib/dns/keymgr.c

index 5a6ca042c42d60d0a4bd7ecf5a421f615e360e8a..41c1157f56639478b6bc9ac943b595595d45dbb9 100644 (file)
@@ -24,6 +24,7 @@ options {
        listen-on-v6 { none; };
        allow-transfer { any; };
        recursion no;
+       key-directory ".";
 };
 
 key rndc_key {
@@ -35,6 +36,17 @@ controls {
        inet 10.53.0.6 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
 };
 
+/* This is a zone that migrates to dnssec-policy. */
+zone "migrate.kasp" {
+       type master;
+       file "migrate.kasp.db";
+       auto-dnssec maintain;
+       allow-update { any; };
+       dnssec-dnskey-kskonly yes;
+       update-check-ksk yes;
+};
+
+/* These are alorithm rollover test zones. */
 zone "step1.algorithm-roll.kasp" {
        type master;
        file "step1.algorithm-roll.kasp.db";
index 52660c608461892178dcf382cf96d5f2ae69b404..6c5a1f3001ac1dc6534e7c6b3f7db87b97a71d52 100644 (file)
@@ -35,6 +35,17 @@ controls {
        inet 10.53.0.6 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
 };
 
+/* This is a zone that migrates to dnssec-policy. */
+zone "migrate.kasp" {
+       type master;
+       file "migrate.kasp.db";
+       allow-update { any; };
+       dnssec-policy "migrate";
+};
+
+/*
+ * Zones for testing KSK/ZSK algorithm roll.
+ */
 zone "step1.algorithm-roll.kasp" {
        type master;
        file "step1.algorithm-roll.kasp.db";
index ad7028ed0cd501c7dfb8fb7cf63103d435f29116..a9424ec05076eeffed8685d216b070a0d8b3b2a5 100644 (file)
@@ -48,3 +48,12 @@ dnssec-policy "ecdsa256" {
        parent-propagation-delay pt1h;
        parent-ds-ttl 7200;
 };
+
+dnssec-policy "migrate" {
+       dnskey-ttl 300;
+
+       keys {
+               ksk key-directory lifetime unlimited algorithm ECDSAP256SHA256;
+               zsk key-directory lifetime P60D algorithm ECDSAP256SHA256;
+       };
+};
index cae347553527ef35e657d8730902d4c34ca07a72..b22d36d21ea7c1a2dbb9babfedc399ce51694d9b 100644 (file)
@@ -39,6 +39,18 @@ R="RUMOURED"
 O="OMNIPRESENT"
 U="UNRETENTIVE"
 
+# Set up a zone with auto-dnssec maintain to migrate to dnssec-policy.
+setup migrate.kasp
+echo "$zone" >> zones
+KSK=$($KEYGEN -a ECDSAP256SHA256 -f KSK -L 300 $zone 2> keygen.out.$zone.1)
+ZSK=$($KEYGEN -a ECDSAP256SHA256 -L 300 $zone 2> keygen.out.$zone.2)
+$SETTIME -P now -P sync now -A now "$KSK" > settime.out.$zone.1 2>&1
+$SETTIME -P now -A now "$ZSK" > settime.out.$zone.2 2>&1
+cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile"
+private_type_record $zone 5 "$KSK" >> "$infile"
+private_type_record $zone 5 "$ZSK" >> "$infile"
+$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
+
 #
 # The zones at algorithm-roll.kasp represent the various steps of a ZSK/KSK
 # algorithm rollover.
@@ -47,6 +59,7 @@ U="UNRETENTIVE"
 # Step 1:
 # Introduce the first key. This will immediately be active.
 setup step1.algorithm-roll.kasp
+echo "$zone" >> zones
 KSK=$($KEYGEN -a RSASHA1 -f KSK -L 3600 $zone 2> keygen.out.$zone.1)
 ZSK=$($KEYGEN -a RSASHA1 -L 3600 $zone 2> keygen.out.$zone.2)
 TactN="now"
index 896731e5b5681daf834758a1d1e7602b69630de9..8e79abf91cf99fde367ee8e53e15c6589b64e41e 100644 (file)
@@ -55,6 +55,7 @@ VIEW2="4xILSZQnuO1UKubXHkYUsvBRPu8="
 # STATE_DS=18
 # EXPECT_ZRRSIG=19
 # EXPECT_KRRSIG=20
+# LEGACY=21
 
 key_key() {
        echo "${1}__${2}"
@@ -93,6 +94,7 @@ key_clear() {
        key_set "$1" "STATE_DS" 'none'
        key_set "$1" "EXPECT_ZRRSIG" 'no'
        key_set "$1" "EXPECT_KRRSIG" 'no'
+       key_set "$1" "LEGACY" 'no'
 }
 
 # Start clear.
@@ -238,6 +240,7 @@ check_key() {
        _length=$(key_get "$1" "ALG_LEN")
        _dnskey_ttl="$DNSKEY_TTL"
        _lifetime=$(key_get "$1" LIFETIME)
+       _legacy=$(key_get "$1" LEGACY)
 
        _published=$(key_get "$1" PUBLISHED)
        _active=$(key_get "$1" ACTIVE)
@@ -277,7 +280,10 @@ check_key() {
        # Check file existence.
        [ -s "$KEY_FILE" ] || ret=1
        [ -s "$PRIVATE_FILE" ] || ret=1
-       [ -s "$STATE_FILE" ] || ret=1
+       if [ "$_legacy" == "no" ]; then
+               [ -s "$STATE_FILE" ] || ret=1
+       fi
+       [ "$ret" -eq 0 ] || log_error "${BASE_FILE} files missing"
        [ "$ret" -eq 0 ] || return
 
        test $_log -eq 1 && echo_i "check key $BASE_FILE"
@@ -289,106 +295,130 @@ check_key() {
        grep "Private-key-format: v1.3" "$PRIVATE_FILE" > /dev/null || log_error "mismatch private key format in $PRIVATE_FILE"
        grep "Algorithm: ${_alg_num} (${_alg_string})" "$PRIVATE_FILE" > /dev/null || log_error "mismatch algorithm in $PRIVATE_FILE"
        # Now check the key state file.
-       grep "This is the state of key ${_key_id}, for ${_zone}." "$STATE_FILE" > /dev/null || log_error "mismatch top comment in $STATE_FILE"
-       grep "Lifetime: ${_lifetime}" "$STATE_FILE" > /dev/null || log_error "mismatch lifetime in $STATE_FILE"
-       grep "Algorithm: ${_alg_num}" "$STATE_FILE" > /dev/null || log_error "mismatch algorithm in $STATE_FILE"
-       grep "Length: ${_length}" "$STATE_FILE" > /dev/null || log_error "mismatch length in $STATE_FILE"
-       grep "KSK: ${_ksk}" "$STATE_FILE" > /dev/null || log_error "mismatch ksk in $STATE_FILE"
-       grep "ZSK: ${_zsk}" "$STATE_FILE" > /dev/null || log_error "mismatch zsk in $STATE_FILE"
-
-       # Check key states.
-       if [ "$_goal" = "none" ]; then
-               grep "GoalState: " "$STATE_FILE" > /dev/null && log_error "unexpected goal state in $STATE_FILE"
-       else
-               grep "GoalState: ${_goal}" "$STATE_FILE" > /dev/null || log_error "mismatch goal state in $STATE_FILE"
-       fi
+       if [ "$_legacy" == "no" ]; then
+               grep "This is the state of key ${_key_id}, for ${_zone}." "$STATE_FILE" > /dev/null || log_error "mismatch top comment in $STATE_FILE"
+               grep "Lifetime: ${_lifetime}" "$STATE_FILE" > /dev/null || log_error "mismatch lifetime in $STATE_FILE"
+               grep "Algorithm: ${_alg_num}" "$STATE_FILE" > /dev/null || log_error "mismatch algorithm in $STATE_FILE"
+               grep "Length: ${_length}" "$STATE_FILE" > /dev/null || log_error "mismatch length in $STATE_FILE"
+               grep "KSK: ${_ksk}" "$STATE_FILE" > /dev/null || log_error "mismatch ksk in $STATE_FILE"
+               grep "ZSK: ${_zsk}" "$STATE_FILE" > /dev/null || log_error "mismatch zsk in $STATE_FILE"
+
+               # Check key states.
+               if [ "$_goal" = "none" ]; then
+                       grep "GoalState: " "$STATE_FILE" > /dev/null && log_error "unexpected goal state in $STATE_FILE"
+               else
+                       grep "GoalState: ${_goal}" "$STATE_FILE" > /dev/null || log_error "mismatch goal state in $STATE_FILE"
+               fi
 
-       if [ "$_state_dnskey" = "none" ]; then
-               grep "DNSKEYState: " "$STATE_FILE" > /dev/null && log_error "unexpected dnskey state in $STATE_FILE"
-               grep "DNSKEYChange: " "$STATE_FILE" > /dev/null && log_error "unexpected dnskey change in $STATE_FILE"
-       else
-               grep "DNSKEYState: ${_state_dnskey}" "$STATE_FILE" > /dev/null || log_error "mismatch dnskey state in $STATE_FILE"
-               grep "DNSKEYChange: " "$STATE_FILE" > /dev/null || log_error "mismatch dnskey change in $STATE_FILE"
-       fi
+               if [ "$_state_dnskey" = "none" ]; then
+                       grep "DNSKEYState: " "$STATE_FILE" > /dev/null && log_error "unexpected dnskey state in $STATE_FILE"
+                       grep "DNSKEYChange: " "$STATE_FILE" > /dev/null && log_error "unexpected dnskey change in $STATE_FILE"
+               else
+                       grep "DNSKEYState: ${_state_dnskey}" "$STATE_FILE" > /dev/null || log_error "mismatch dnskey state in $STATE_FILE"
+                       grep "DNSKEYChange: " "$STATE_FILE" > /dev/null || log_error "mismatch dnskey change in $STATE_FILE"
+               fi
 
-       if [ "$_state_zrrsig" = "none" ]; then
-               grep "ZRRSIGState: " "$STATE_FILE" > /dev/null && log_error "unexpected zrrsig state in $STATE_FILE"
-               grep "ZRRSIGChange: " "$STATE_FILE" > /dev/null && log_error "unexpected zrrsig change in $STATE_FILE"
-       else
-               grep "ZRRSIGState: ${_state_zrrsig}" "$STATE_FILE" > /dev/null || log_error "mismatch zrrsig state in $STATE_FILE"
-               grep "ZRRSIGChange: " "$STATE_FILE" > /dev/null || log_error "mismatch zrrsig change in $STATE_FILE"
-       fi
+               if [ "$_state_zrrsig" = "none" ]; then
+                       grep "ZRRSIGState: " "$STATE_FILE" > /dev/null && log_error "unexpected zrrsig state in $STATE_FILE"
+                       grep "ZRRSIGChange: " "$STATE_FILE" > /dev/null && log_error "unexpected zrrsig change in $STATE_FILE"
+               else
+                       grep "ZRRSIGState: ${_state_zrrsig}" "$STATE_FILE" > /dev/null || log_error "mismatch zrrsig state in $STATE_FILE"
+                       grep "ZRRSIGChange: " "$STATE_FILE" > /dev/null || log_error "mismatch zrrsig change in $STATE_FILE"
+               fi
 
-       if [ "$_state_krrsig" = "none" ]; then
-               grep "KRRSIGState: " "$STATE_FILE" > /dev/null && log_error "unexpected krrsig state in $STATE_FILE"
-               grep "KRRSIGChange: " "$STATE_FILE" > /dev/null && log_error "unexpected krrsig change in $STATE_FILE"
-       else
-               grep "KRRSIGState: ${_state_krrsig}" "$STATE_FILE" > /dev/null || log_error "mismatch krrsig state in $STATE_FILE"
-               grep "KRRSIGChange: " "$STATE_FILE" > /dev/null || log_error "mismatch krrsig change in $STATE_FILE"
-       fi
+               if [ "$_state_krrsig" = "none" ]; then
+                       grep "KRRSIGState: " "$STATE_FILE" > /dev/null && log_error "unexpected krrsig state in $STATE_FILE"
+                       grep "KRRSIGChange: " "$STATE_FILE" > /dev/null && log_error "unexpected krrsig change in $STATE_FILE"
+               else
+                       grep "KRRSIGState: ${_state_krrsig}" "$STATE_FILE" > /dev/null || log_error "mismatch krrsig state in $STATE_FILE"
+                       grep "KRRSIGChange: " "$STATE_FILE" > /dev/null || log_error "mismatch krrsig change in $STATE_FILE"
+               fi
 
-       if [ "$_state_ds" = "none" ]; then
-               grep "DSState: " "$STATE_FILE" > /dev/null && log_error "unexpected ds state in $STATE_FILE"
-               grep "DSChange: " "$STATE_FILE" > /dev/null && log_error "unexpected ds change in $STATE_FILE"
-       else
-               grep "DSState: ${_state_ds}" "$STATE_FILE" > /dev/null || log_error "mismatch ds state in $STATE_FILE"
-               grep "DSChange: " "$STATE_FILE" > /dev/null || log_error "mismatch ds change in $STATE_FILE"
+               if [ "$_state_ds" = "none" ]; then
+                       grep "DSState: " "$STATE_FILE" > /dev/null && log_error "unexpected ds state in $STATE_FILE"
+                       grep "DSChange: " "$STATE_FILE" > /dev/null && log_error "unexpected ds change in $STATE_FILE"
+               else
+                       grep "DSState: ${_state_ds}" "$STATE_FILE" > /dev/null || log_error "mismatch ds state in $STATE_FILE"
+                       grep "DSChange: " "$STATE_FILE" > /dev/null || log_error "mismatch ds change in $STATE_FILE"
+               fi
        fi
 
        # Check timing metadata.
        if [ "$_published" = "none" ]; then
                grep "; Publish:" "$KEY_FILE" > /dev/null && log_error "unexpected publish comment in $KEY_FILE"
                grep "Publish:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected publish in $PRIVATE_FILE"
-               grep "Published: " "$STATE_FILE" > /dev/null && log_error "unexpected publish in $STATE_FILE"
+               if [ "$_legacy" == "no" ]; then
+                       grep "Published: " "$STATE_FILE" > /dev/null && log_error "unexpected publish in $STATE_FILE"
+               fi
        else
-               grep "; Publish:" "$KEY_FILE" > /dev/null || log_error "mismatch publish comment in $KEY_FILE ($KEY_PUBLISHED)"
-               grep "Publish:" "$PRIVATE_FILE" > /dev/null || log_error "mismatch publish in $PRIVATE_FILE ($KEY_PUBLISHED)"
-               grep "Published:" "$STATE_FILE" > /dev/null || log_error "mismatch publish in $STATE_FILE ($KEY_PUBLISHED)"
+               grep "; Publish:" "$KEY_FILE" > /dev/null || log_error "mismatch publish comment in $KEY_FILE"
+               grep "Publish:" "$PRIVATE_FILE" > /dev/null || log_error "mismatch publish in $PRIVATE_FILE"
+               if [ "$_legacy" == "no" ]; then
+                       grep "Published:" "$STATE_FILE" > /dev/null || log_error "mismatch publish in $STATE_FILE"
+               fi
        fi
 
        if [ "$_active" = "none" ]; then
                grep "; Activate:" "$KEY_FILE" > /dev/null && log_error "unexpected active comment in $KEY_FILE"
                grep "Activate:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected active in $PRIVATE_FILE"
-               grep "Active: " "$STATE_FILE" > /dev/null && log_error "unexpected active in $STATE_FILE"
+               if [ "$_legacy" == "no" ]; then
+                       grep "Active: " "$STATE_FILE" > /dev/null && log_error "unexpected active in $STATE_FILE"
+               fi
        else
                grep "; Activate:" "$KEY_FILE" > /dev/null || log_error "mismatch active comment in $KEY_FILE"
                grep "Activate:" "$PRIVATE_FILE" > /dev/null || log_error "mismatch active in $PRIVATE_FILE"
-               grep "Active: " "$STATE_FILE" > /dev/null || log_error "mismatch active in $STATE_FILE"
+               if [ "$_legacy" == "no" ]; then
+                       grep "Active: " "$STATE_FILE" > /dev/null || log_error "mismatch active in $STATE_FILE"
+               fi
        fi
 
        if [ "$_retired" = "none" ]; then
                grep "; Inactive:" "$KEY_FILE" > /dev/null && log_error "unexpected retired comment in $KEY_FILE"
                grep "Inactive:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected retired in $PRIVATE_FILE"
-               grep "Retired: " "$STATE_FILE" > /dev/null && log_error "unexpected retired in $STATE_FILE"
+               if [ "$_legacy" == "no" ]; then
+                       grep "Retired: " "$STATE_FILE" > /dev/null && log_error "unexpected retired in $STATE_FILE"
+               fi
        else
                grep "; Inactive:" "$KEY_FILE" > /dev/null || log_error "mismatch retired comment in $KEY_FILE"
                grep "Inactive:" "$PRIVATE_FILE" > /dev/null || log_error "mismatch retired in $PRIVATE_FILE"
-               grep "Retired: " "$STATE_FILE" > /dev/null || log_error "mismatch retired in $STATE_FILE"
+               if [ "$_legacy" == "no" ]; then
+                       grep "Retired: " "$STATE_FILE" > /dev/null || log_error "mismatch retired in $STATE_FILE"
+               fi
        fi
 
        if [ "$_revoked" = "none" ]; then
                grep "; Revoke:" "$KEY_FILE" > /dev/null && log_error "unexpected revoked comment in $KEY_FILE"
                grep "Revoke:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected revoked in $PRIVATE_FILE"
-               grep "Revoked: " "$STATE_FILE" > /dev/null && log_error "unexpected revoked in $STATE_FILE"
+               if [ "$_legacy" == "no" ]; then
+                       grep "Revoked: " "$STATE_FILE" > /dev/null && log_error "unexpected revoked in $STATE_FILE"
+               fi
        else
                grep "; Revoke:" "$KEY_FILE" > /dev/null || log_error "mismatch revoked comment in $KEY_FILE"
                grep "Revoke:" "$PRIVATE_FILE" > /dev/null || log_error "mismatch revoked in $PRIVATE_FILE"
-               grep "Revoked: " "$STATE_FILE" > /dev/null || log_error "mismatch revoked in $STATE_FILE"
+               if [ "$_legacy" == "no" ]; then
+                       grep "Revoked: " "$STATE_FILE" > /dev/null || log_error "mismatch revoked in $STATE_FILE"
+               fi
        fi
 
        if [ "$_removed" = "none" ]; then
                grep "; Delete:" "$KEY_FILE" > /dev/null && log_error "unexpected removed comment in $KEY_FILE"
                grep "Delete:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected removed in $PRIVATE_FILE"
-               grep "Removed: " "$STATE_FILE" > /dev/null && log_error "unexpected removed in $STATE_FILE"
+               if [ "$_legacy" == "no" ]; then
+                       grep "Removed: " "$STATE_FILE" > /dev/null && log_error "unexpected removed in $STATE_FILE"
+               fi
        else
                grep "; Delete:" "$KEY_FILE" > /dev/null || log_error "mismatch removed comment in $KEY_FILE"
                grep "Delete:" "$PRIVATE_FILE" > /dev/null || log_error "mismatch removed in $PRIVATE_FILE"
-               grep "Removed: " "$STATE_FILE" > /dev/null || log_error "mismatch removed in $STATE_FILE"
+               if [ "$_legacy" == "no" ]; then
+                       grep "Removed: " "$STATE_FILE" > /dev/null || log_error "mismatch removed in $STATE_FILE"
+               fi
        fi
 
        grep "; Created:" "$KEY_FILE" > /dev/null || log_error "mismatch created comment in $KEY_FILE"
        grep "Created:" "$PRIVATE_FILE" > /dev/null || log_error "mismatch created in $PRIVATE_FILE"
-       grep "Generated: " "$STATE_FILE" > /dev/null || log_error "mismatch generated in $STATE_FILE"
+       if [ "$_legacy" == "no" ]; then
+               grep "Generated: " "$STATE_FILE" > /dev/null || log_error "mismatch generated in $STATE_FILE"
+       fi
 }
 
 # Check the key with key id $1 and see if it is unused.
@@ -424,20 +454,24 @@ key_unused() {
 
        # Check timing metadata.
        grep "; Publish:" "$KEY_FILE" > /dev/null && log_error "unexpected publish comment in $KEY_FILE"
-       grep "Publish:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected publish in $PRIVATE_FILE"
-       grep "Published: " "$STATE_FILE" > /dev/null && log_error "unexpected publish in $STATE_FILE"
        grep "; Activate:" "$KEY_FILE" > /dev/null && log_error "unexpected active comment in $KEY_FILE"
-       grep "Activate:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected active in $PRIVATE_FILE"
-       grep "Active: " "$STATE_FILE" > /dev/null && log_error "unexpected active in $STATE_FILE"
        grep "; Inactive:" "$KEY_FILE" > /dev/null && log_error "unexpected retired comment in $KEY_FILE"
-       grep "Inactive:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected retired in $PRIVATE_FILE"
-       grep "Retired: " "$STATE_FILE" > /dev/null && log_error "unexpected retired in $STATE_FILE"
        grep "; Revoke:" "$KEY_FILE" > /dev/null && log_error "unexpected revoked comment in $KEY_FILE"
-       grep "Revoke:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected revoked in $PRIVATE_FILE"
-       grep "Revoked: " "$STATE_FILE" > /dev/null && log_error "unexpected revoked in $STATE_FILE"
        grep "; Delete:" "$KEY_FILE" > /dev/null && log_error "unexpected removed comment in $KEY_FILE"
+
+       grep "Publish:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected publish in $PRIVATE_FILE"
+       grep "Activate:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected active in $PRIVATE_FILE"
+       grep "Inactive:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected retired in $PRIVATE_FILE"
+       grep "Revoke:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected revoked in $PRIVATE_FILE"
        grep "Delete:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected removed in $PRIVATE_FILE"
-       grep "Removed: " "$STATE_FILE" > /dev/null && log_error "unexpected removed in $STATE_FILE"
+
+       if [ "$_legacy" == "no" ]; then
+               grep "Published: " "$STATE_FILE" > /dev/null && log_error "unexpected publish in $STATE_FILE"
+               grep "Active: " "$STATE_FILE" > /dev/null && log_error "unexpected active in $STATE_FILE"
+               grep "Retired: " "$STATE_FILE" > /dev/null && log_error "unexpected retired in $STATE_FILE"
+               grep "Revoked: " "$STATE_FILE" > /dev/null && log_error "unexpected revoked in $STATE_FILE"
+               grep "Removed: " "$STATE_FILE" > /dev/null && log_error "unexpected removed in $STATE_FILE"
+       fi
 }
 
 # Test: dnssec-verify zone $1.
@@ -2821,7 +2855,62 @@ dnssec_verify
 # interval.
 check_next_key_event 3600
 
-# Reconfig dnssec-policy (triggering algorithm roll).
+#
+# Testing good migration.
+#
+set_zone "migrate.kasp"
+set_policy "none" "2" "300"
+set_server "ns6" "10.53.0.6"
+
+init_migration_match() {
+       key_clear        "KEY1"
+       key_set          "KEY1" "LEGACY" "yes"
+       set_keyrole      "KEY1" "ksk"
+       set_keylifetime  "KEY1" "0"
+       set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
+       set_keysigning   "KEY1" "yes"
+       set_zonesigning  "KEY1" "no"
+
+       key_clear        "KEY2"
+       key_set          "KEY2" "LEGACY" "yes"
+       set_keyrole      "KEY2" "zsk"
+       set_keylifetime  "KEY2" "5184000"
+       set_keyalgorithm "KEY2" "13" "ECDSAP256SHA256" "256"
+       set_keysigning   "KEY2" "no"
+       set_zonesigning  "KEY2" "yes"
+
+       key_clear        "KEY3"
+       key_clear        "KEY4"
+
+       set_keytime  "KEY1" "PUBLISHED"    "yes"
+       set_keytime  "KEY1" "ACTIVE"       "yes"
+       set_keytime  "KEY1" "RETIRED"      "none"
+       set_keystate "KEY1" "GOAL"         "omnipresent"
+       set_keystate "KEY1" "STATE_DNSKEY" "rumoured"
+       set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
+       set_keystate "KEY1" "STATE_DS"     "rumoured"
+
+       set_keytime  "KEY2" "PUBLISHED"    "yes"
+       set_keytime  "KEY2" "ACTIVE"       "yes"
+       set_keytime  "KEY2" "RETIRED"      "none"
+       set_keystate "KEY2" "GOAL"         "omnipresent"
+       set_keystate "KEY2" "STATE_DNSKEY" "rumoured"
+       set_keystate "KEY2" "STATE_ZRRSIG" "rumoured"
+}
+init_migration_match
+
+# Make sure the zone is signed with legacy keys.
+check_keys
+check_apex
+check_subdomain
+dnssec_verify
+
+# Remember legacy key tags.
+_migrate_ksk=$(key_get KEY1 ID)
+_migrate_zsk=$(key_get KEY2 ID)
+
+# Reconfig dnssec-policy (triggering algorithm roll and other dnssec-policy
+# changes).
 echo_i "reconfig dnssec-policy to trigger algorithm rollover"
 copy_setports ns6/named2.conf.in ns6/named.conf
 rndc_reconfig ns6 10.53.0.6
@@ -2854,6 +2943,35 @@ status=$((status+ret))
 
 next_key_event_threshold=$((next_key_event_threshold+i))
 
+#
+# Testing migration.
+#
+set_zone "migrate.kasp"
+set_policy "migrate" "2" "300"
+set_server "ns6" "10.53.0.6"
+
+# Key properties, timings and metadata should be the same as legacy keys above.
+# However, because the zsk has a lifetime, kasp will set the retired time.
+init_migration_match
+
+key_set     "KEY1" "LEGACY"  "no"
+
+key_set     "KEY2" "LEGACY"  "no"
+set_keytime "KEY2" "RETIRED" "yes"
+
+check_keys
+check_apex
+check_subdomain
+dnssec_verify
+
+# Check key tags, should be the same.
+n=$((n+1))
+echo_i "check that of zone ${ZONE} migration to dnssec-policy uses the same keys ($n)"
+ret=0
+[ $_migrate_ksk == $(key_get KEY1 ID) ] || log_error "mismatch ksk tag"
+[ $_migrate_zsk == $(key_get KEY2 ID) ] || log_error "mismatch zsk tag"
+status=$((status+ret))
+
 #
 # Testing KSK/ZSK algorithm rollover.
 #
index 8a54ea977132eb3a857a66440091fb165ea6431f..e5d7f08eddce2383b7b2e4f76c3a92a56b87f7c9 100644 (file)
  * Set key state to HIDDEN and change last changed to now,
  * only if key state has not been set before.
  */
-#define INITIALIZE_STATE(key, state, time)                                    \
+#define INITIALIZE_STATE(key, state, time, target)                            \
        do {                                                                  \
                dst_key_state_t s;                                            \
                if (dst_key_getstate((key), (state), &s) == ISC_R_NOTFOUND) { \
                        isc_stdtime_t t;                                      \
                        dst_key_gettime((key), DST_TIME_CREATED, &t);         \
-                       dst_key_setstate((key), (state), HIDDEN);             \
+                       dst_key_setstate((key), (state), target);             \
                        dst_key_settime((key), (time), t);                    \
                }                                                             \
        } while (0)
@@ -105,7 +105,7 @@ static isc_stdtime_t
 keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
                           uint32_t lifetime, isc_stdtime_t now) {
        isc_result_t ret;
-       isc_stdtime_t active, retire, prepub;
+       isc_stdtime_t active, retire, pub, prepub;
        bool ksk = false;
 
        REQUIRE(key != NULL);
@@ -125,7 +125,8 @@ keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
        if (ret != ISC_R_SUCCESS) {
                uint32_t klifetime = 0;
                /*
-                * An active key must have an activate timing metadata.
+                * An active key must have publish and activate timing
+                * metadata.
                 */
                ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active);
                if (ret != ISC_R_SUCCESS) {
@@ -133,6 +134,12 @@ keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
                        dst_key_settime(key->key, DST_TIME_ACTIVATE, now);
                        active = now;
                }
+               ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &pub);
+               if (ret != ISC_R_SUCCESS) {
+                       /* Super weird, but if it happens, set it to now. */
+                       dst_key_settime(key->key, DST_TIME_PUBLISH, now);
+                       pub = now;
+               }
 
                ret = dst_key_getnum(key->key, DST_NUM_LIFETIME, &klifetime);
                if (ret != ISC_R_SUCCESS) {
@@ -1214,19 +1221,25 @@ transition:
 }
 
 /*
- * See if this key needs to be initialized with a role.  A key created and
- * derived from a dnssec-policy will have the required metadata available,
- * otherwise these may be missing and need to be initialized.
+ * See if this key needs to be initialized with properties.  A key created
+ * and derived from a dnssec-policy will have the required metadata available,
+ * otherwise these may be missing and need to be initialized.  The key states
+ * will be initialized according to existing timing metadata.
  *
  */
 static void
-keymgr_key_init_role(dns_dnsseckey_t *key) {
+keymgr_key_init(dns_dnsseckey_t *key, isc_stdtime_t now) {
        bool ksk, zsk;
        isc_result_t ret;
+       isc_stdtime_t active = 0, pub = 0, syncpub = 0;
+       dst_key_state_t dnskey_state = HIDDEN;
+       dst_key_state_t ds_state = HIDDEN;
+       dst_key_state_t zrrsig_state = HIDDEN;
 
        REQUIRE(key != NULL);
        REQUIRE(key->key != NULL);
 
+       /* Initialize role. */
        ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
        if (ret != ISC_R_SUCCESS) {
                ksk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) != 0);
@@ -1237,6 +1250,33 @@ keymgr_key_init_role(dns_dnsseckey_t *key) {
                zsk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) == 0);
                dst_key_setbool(key->key, DST_BOOL_ZSK, zsk);
        }
+
+       /* Get time metadata. */
+       ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active);
+       if (active <= now && ret == ISC_R_SUCCESS) {
+               dnskey_state = RUMOURED;
+       }
+       ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &pub);
+       if (pub <= now && ret == ISC_R_SUCCESS) {
+               zrrsig_state = RUMOURED;
+       }
+       ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub);
+       if (syncpub <= now && ret == ISC_R_SUCCESS) {
+               ds_state = RUMOURED;
+       }
+
+       /* Set key states for all keys that do not have them. */
+       INITIALIZE_STATE(key->key, DST_KEY_DNSKEY, DST_TIME_DNSKEY,
+                        dnskey_state);
+       if (ksk) {
+               INITIALIZE_STATE(key->key, DST_KEY_KRRSIG, DST_TIME_KRRSIG,
+                                dnskey_state);
+               INITIALIZE_STATE(key->key, DST_KEY_DS, DST_TIME_DS, ds_state);
+       }
+       if (zsk) {
+               INITIALIZE_STATE(key->key, DST_KEY_ZRRSIG, DST_TIME_ZRRSIG,
+                                zrrsig_state);
+       }
 }
 
 /*
@@ -1302,23 +1342,13 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
        {
                bool found_match = false;
 
-               /* Make sure this key knows about roles. */
-               keymgr_key_init_role(dkey);
+               keymgr_key_init(dkey, now);
 
                for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
                     kkey = ISC_LIST_NEXT(kkey, link))
                {
                        if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) {
                                found_match = true;
-                               dst_key_format(dkey->key, keystr,
-                                              sizeof(keystr));
-                               isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
-                                             DNS_LOGMODULE_DNSSEC,
-                                             ISC_LOG_DEBUG(1),
-                                             "keymgr: DNSKEY %s (%s) matches "
-                                             "policy %s",
-                                             keystr, keymgr_keyrole(dkey->key),
-                                             dns_kasp_getname(kasp));
                                break;
                        }
                }
@@ -1343,8 +1373,17 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
                {
                        if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) {
                                /* Found a match. */
+                               dst_key_format(dkey->key, keystr,
+                                              sizeof(keystr));
+                               isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+                                             DNS_LOGMODULE_DNSSEC,
+                                             ISC_LOG_DEBUG(1),
+                                             "keymgr: DNSKEY %s (%s) matches "
+                                             "policy %s",
+                                             keystr, keymgr_keyrole(dkey->key),
+                                             dns_kasp_getname(kasp));
 
-                               /* Initialize lifetime. */
+                               /* Initialize lifetime and goal, if not set. */
                                uint32_t l;
                                if (dst_key_getnum(dkey->key, DST_NUM_LIFETIME,
                                                   &l) != ISC_R_SUCCESS) {
@@ -1353,18 +1392,44 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
                                                       lifetime);
                                }
 
-                               if (dst_key_goal(dkey->key) == OMNIPRESENT) {
-                                       if (active_key != NULL) {
+                               dst_key_state_t goal;
+                               if (dst_key_getstate(dkey->key, DST_KEY_GOAL,
+                                                    &goal) != ISC_R_SUCCESS) {
+                                       dst_key_setstate(dkey->key,
+                                                        DST_KEY_GOAL,
+                                                        OMNIPRESENT);
+                               }
+
+                               if (active_key) {
+                                       /* We already have an active key that
+                                        * matches the kasp policy.
+                                        */
+                                       if (!dst_key_is_unused(dkey->key) &&
+                                           (dst_key_goal(dkey->key) ==
+                                            OMNIPRESENT) &&
+                                           !keymgr_key_is_successor(
+                                                   dkey->key,
+                                                   active_key->key) &&
+                                           !keymgr_key_is_successor(
+                                                   active_key->key, dkey->key))
+                                       {
                                                /*
                                                 * Multiple signing keys match
                                                 * the kasp key configuration.
-                                                * Retire excess keys.
+                                                * Retire excess keys in use.
                                                 */
                                                keymgr_key_retire(dkey, now);
-                                       } else {
-                                               /* Save the matched key. */
-                                               active_key = dkey;
                                        }
+                                       continue;
+                               }
+
+                               /*
+                                * Save the matched key only if it is active
+                                * or desires to be active.
+                                */
+                               if (dst_key_goal(dkey->key) == OMNIPRESENT ||
+                                   dst_key_is_active(dkey->key, now)) {
+                                       active_key = dkey;
                                }
                        }
                }
@@ -1377,7 +1442,7 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
                                isc_log_write(
                                        dns_lctx, DNS_LOGCATEGORY_DNSSEC,
                                        DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
-                                       "keymgr: DNSKEY %s (%s) matches "
+                                       "keymgr: DNSKEY %s (%s) is active in "
                                        "policy %s",
                                        keystr, keymgr_keyrole(active_key->key),
                                        dns_kasp_getname(kasp));
@@ -1446,6 +1511,7 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
                        dst_key_setttl(dst_key, dns_kasp_dnskeyttl(kasp));
                        dst_key_settime(dst_key, DST_TIME_CREATED, now);
                        RETERR(dns_dnsseckey_create(mctx, &dst_key, &newkey));
+                       keymgr_key_init(newkey, now);
                } else {
                        newkey = candidate;
                        dst_key_setnum(newkey->key, DST_NUM_LIFETIME, lifetime);
@@ -1512,27 +1578,6 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
                ISC_LIST_APPENDLIST(*keyring, newkeys, link);
        }
 
-       /* Initialize key states (for keys that don't have them yet). */
-       for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
-            dkey = ISC_LIST_NEXT(dkey, link))
-       {
-               bool ksk = false, zsk = false;
-
-               /* Set key states for all keys that do not have them. */
-               INITIALIZE_STATE(dkey->key, DST_KEY_DNSKEY, DST_TIME_DNSKEY);
-               (void)dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk);
-               if (ksk) {
-                       INITIALIZE_STATE(dkey->key, DST_KEY_KRRSIG,
-                                        DST_TIME_KRRSIG);
-                       INITIALIZE_STATE(dkey->key, DST_KEY_DS, DST_TIME_DS);
-               }
-               (void)dst_key_getbool(dkey->key, DST_BOOL_ZSK, &zsk);
-               if (zsk) {
-                       INITIALIZE_STATE(dkey->key, DST_KEY_ZRRSIG,
-                                        DST_TIME_ZRRSIG);
-               }
-       }
-
        /* Read to update key states. */
        keymgr_update(keyring, kasp, now, nexttime);