]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Implement 'rndc dnssec -checkds'
authorMatthijs Mekking <matthijs@isc.org>
Fri, 31 Jul 2020 06:37:51 +0000 (08:37 +0200)
committerMatthijs Mekking <matthijs@isc.org>
Fri, 7 Aug 2020 09:26:09 +0000 (11:26 +0200)
Add a new 'rndc' command 'dnssec -checkds' that allows the user to
signal named that a new DS record has been seen published in the
parent, or that an existing DS record has been withdrawn from the
parent.

Upon the 'checkds' request, 'named' will write out the new state for
the key, updating the 'DSPublish' or 'DSRemoved' timing metadata.

This replaces the "parent-registration-delay" configuration option,
this was unreliable because it was purely time based (if the user
did not actually submit the new DS to the parent for example, this
could result in an invalid DNSSEC state).

Because we cannot rely on the parent registration delay for state
transition, we need to replace it with a different guard. Instead,
if a key wants its DS state to be moved to RUMOURED, the "DSPublish"
time must be set and must not be in the future. If a key wants its
DS state to be moved to UNRETENTIVE, the "DSRemoved" time must be set
and must not be in the future.

By default, with '-checkds' you set the time that the DS has been
published or withdrawn to now, but you can set a different time with
'-when'. If there is only one KSK for the zone, that key has its
DS state moved to RUMOURED. If there are multiple keys for the zone,
specify the right key with '-key'.

CHANGES
bin/named/server.c
bin/rndc/rndc.c
bin/rndc/rndc.rst
doc/man/rndc.8in
lib/dns/dst_api.c
lib/dns/include/dns/keymgr.h
lib/dns/include/dst/dst.h
lib/dns/keymgr.c
lib/dns/win32/libdns.def.in

diff --git a/CHANGES b/CHANGES
index b01a399729a8fbfdab68843b74abfd87985e554d..6aa1d6be823ee4863f1f3ae38b9d34197cb6780d 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,7 @@
+5486.  [func]          Add 'rndc dnssec -checkds' command to tell named
+                       that the DS record has been published in the parent.
+                       [GL #1613]
+
 5485.  [placeholder]
 
 5484.  [func]          Expire the 0 TTL RRSet quickly rather using them for
index 88726bb4c7d3fbc8cb156977ce34d004bdce6e36..89f8eaa29b9c9c3de819ab50f371459ff91d1ba5 100644 (file)
@@ -89,6 +89,7 @@
 #include <dns/secalg.h>
 #include <dns/soa.h>
 #include <dns/stats.h>
+#include <dns/time.h>
 #include <dns/tkey.h>
 #include <dns/tsig.h>
 #include <dns/ttl.h>
@@ -14497,6 +14498,23 @@ cleanup:
        return (result);
 }
 
+static inline bool
+argcheck(char *cmd, const char *full) {
+       size_t l;
+
+       if (cmd == NULL || cmd[0] != '-') {
+               return (false);
+       }
+
+       cmd++;
+       l = strlen(cmd);
+       if (l > strlen(full) || strncasecmp(cmd, full, l) != 0) {
+               return (false);
+       }
+
+       return (true);
+}
+
 isc_result_t
 named_server_dnssec(named_server_t *server, isc_lex_t *lex,
                    isc_buffer_t **text) {
@@ -14505,11 +14523,16 @@ named_server_dnssec(named_server_t *server, isc_lex_t *lex,
        dns_kasp_t *kasp = NULL;
        dns_dnsseckeylist_t keys;
        dns_dnsseckey_t *key;
-       const char *ptr;
+       char *ptr;
+       const char *msg = NULL;
+       /* variables for -checkds */
+       bool checkds = false, dspublish = false, use_keyid = false;
+       dns_keytag_t keyid = 0;
        /* variables for -status */
+       bool status = false;
        char output[4096];
-       isc_stdtime_t now;
-       isc_time_t timenow;
+       isc_stdtime_t now, when;
+       isc_time_t timenow, timewhen;
        const char *dir;
 
        /* Skip the command name. */
@@ -14524,43 +14547,163 @@ named_server_dnssec(named_server_t *server, isc_lex_t *lex,
                return (ISC_R_UNEXPECTEDEND);
        }
 
-       if (strcasecmp(ptr, "-status") != 0) {
-               return (DNS_R_SYNTAX);
-       }
+       /* Initialize current time and key list. */
+       TIME_NOW(&timenow);
+       now = isc_time_seconds(&timenow);
+       when = now;
 
        ISC_LIST_INIT(keys);
 
+       if (strcasecmp(ptr, "-status") == 0) {
+               status = true;
+       } else if (strcasecmp(ptr, "-checkds") == 0) {
+               checkds = true;
+
+               /* Check for options */
+               for (;;) {
+                       ptr = next_token(lex, text);
+                       if (ptr == NULL) {
+                               msg = "Bad format";
+                               CHECK(ISC_R_UNEXPECTEDEND);
+                       }
+                       if (argcheck(ptr, "key")) {
+                               uint16_t id;
+                               ptr = next_token(lex, text);
+                               if (ptr == NULL) {
+                                       msg = "No key identifier specified";
+                                       CHECK(ISC_R_UNEXPECTEDEND);
+                               }
+                               CHECK(isc_parse_uint16(&id, ptr, 10));
+                               keyid = (dns_keytag_t)id;
+                               use_keyid = true;
+                               continue;
+                       } else if (argcheck(ptr, "when")) {
+                               uint32_t tw;
+                               ptr = next_token(lex, text);
+                               if (ptr == NULL) {
+                                       msg = "No time specified";
+                                       CHECK(ISC_R_UNEXPECTEDEND);
+                               }
+                               CHECK(dns_time32_fromtext(ptr, &tw));
+                               when = (isc_stdtime_t)tw;
+                               continue;
+                       } else if (ptr[0] == '-') {
+                               msg = "Unknown option";
+                               CHECK(DNS_R_SYNTAX);
+                       } else {
+                               /*
+                                * No arguments provided, so we must be
+                                * parsing "published|withdrawn".
+                                */
+                               if (strcasecmp(ptr, "publish") == 0) {
+                                       dspublish = true;
+                               } else if (strcasecmp(ptr, "withdraw") != 0) {
+                                       CHECK(DNS_R_SYNTAX);
+                               }
+                       }
+                       break;
+               }
+       } else {
+               CHECK(DNS_R_SYNTAX);
+       }
+
+       /* Get zone. */
        CHECK(zone_from_args(server, lex, NULL, &zone, NULL, text, false));
        if (zone == NULL) {
+               msg = "Zone not found";
                CHECK(ISC_R_UNEXPECTEDEND);
        }
 
+       /* Trailing garbage? */
+       ptr = next_token(lex, text);
+       if (ptr != NULL) {
+               msg = "Too many arguments";
+               CHECK(DNS_R_SYNTAX);
+       }
+
+       /* Get dnssec-policy. */
        kasp = dns_zone_getkasp(zone);
        if (kasp == NULL) {
-               CHECK(putstr(text, "zone does not have dnssec-policy"));
-               CHECK(putnull(text));
+               msg = "Zone does not have dnssec-policy";
                goto cleanup;
        }
 
-       /* -status */
-       TIME_NOW(&timenow);
-       now = isc_time_seconds(&timenow);
+       /* Get DNSSEC keys. */
        dir = dns_zone_getkeydirectory(zone);
        LOCK(&kasp->lock);
        result = dns_dnssec_findmatchingkeys(dns_zone_getorigin(zone), dir, now,
                                             dns_zone_getmctx(zone), &keys);
        UNLOCK(&kasp->lock);
-
        if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
                goto cleanup;
        }
-       LOCK(&kasp->lock);
-       dns_keymgr_status(kasp, &keys, now, &output[0], sizeof(output));
-       UNLOCK(&kasp->lock);
-       CHECK(putstr(text, output));
+
+       if (status) {
+               /*
+                * Output the DNSSEC status of the key and signing policy.
+                */
+               LOCK(&kasp->lock);
+               dns_keymgr_status(kasp, &keys, now, &output[0], sizeof(output));
+               UNLOCK(&kasp->lock);
+               CHECK(putstr(text, output));
+       } else if (checkds) {
+               /*
+                * Mark DS record has been seen, so it may move to the
+                * rumoured state.
+                */
+               char whenbuf[80];
+               isc_time_set(&timewhen, when, 0);
+               isc_time_formattimestamp(&timewhen, whenbuf, sizeof(whenbuf));
+
+               LOCK(&kasp->lock);
+               if (use_keyid) {
+                       result = dns_keymgr_checkds_id(kasp, &keys, dir, when,
+                                                      dspublish, keyid);
+               } else {
+                       result = dns_keymgr_checkds(kasp, &keys, dir, when,
+                                                   dspublish);
+               }
+               UNLOCK(&kasp->lock);
+
+               switch (result) {
+               case ISC_R_SUCCESS:
+                       if (use_keyid) {
+                               char tagbuf[6];
+                               snprintf(tagbuf, sizeof(tagbuf), "%u", keyid);
+                               CHECK(putstr(text, "KSK "));
+                               CHECK(putstr(text, tagbuf));
+                               CHECK(putstr(text, ": "));
+                       }
+                       CHECK(putstr(text, "Marked DS as "));
+                       if (dspublish) {
+                               CHECK(putstr(text, "published "));
+                       } else {
+                               CHECK(putstr(text, "withdrawn "));
+                       }
+                       CHECK(putstr(text, "since "));
+                       CHECK(putstr(text, whenbuf));
+                       break;
+               case ISC_R_NOTFOUND:
+                       CHECK(putstr(text, "No matching KSK found"));
+                       break;
+               case ISC_R_FAILURE:
+                       CHECK(putstr(text,
+                                    "Error: multiple possible KSKs found, "
+                                    "retry command with -key id"));
+                       break;
+               default:
+                       CHECK(putstr(text, "Error executing checkds command"));
+                       break;
+               }
+       }
        CHECK(putnull(text));
 
 cleanup:
+       if (msg != NULL) {
+               (void)putstr(text, msg);
+               (void)putnull(text);
+       }
+
        while (!ISC_LIST_EMPTY(keys)) {
                key = ISC_LIST_HEAD(keys);
                ISC_LIST_UNLINK(keys, key, link);
@@ -14905,23 +15048,6 @@ cleanup:
        return (result);
 }
 
-static inline bool
-argcheck(char *cmd, const char *full) {
-       size_t l;
-
-       if (cmd == NULL || cmd[0] != '-') {
-               return (false);
-       }
-
-       cmd++;
-       l = strlen(cmd);
-       if (l > strlen(full) || strncasecmp(cmd, full, l) != 0) {
-               return (false);
-       }
-
-       return (true);
-}
-
 isc_result_t
 named_server_nta(named_server_t *server, isc_lex_t *lex, bool readonly,
                 isc_buffer_t **text) {
index ef6b31c53176c0aab13820191e3adc3a5bd3c5f5..e68c2e2244f5e2a61989f769bd9401d9250624d1 100644 (file)
@@ -108,6 +108,11 @@ command is one of the following:\n\
                Add zone to given view. Requires allow-new-zones option.\n\
   delzone [-clean] zone [class [view]]\n\
                Removes zone from given view.\n\
+  dnssec -checkds [-key id] [-when time] (published|withdrawn) zone [class [view]]\n\
+               Mark the DS record for the KSK of the given zone as seen\n\
+               in the parent.  If the zone has multiple KSKs, select a\n\
+               specific key by providing the keytag with -key id.\n\
+               Requires the zone to have a dnssec-policy.\n\
   dnssec -status zone [class [view]]\n\
                Show the DNSSEC signing state for the specified zone.\n\
                Requires the zone to have a dnssec-policy.\n\
index 163786571de32e5c55b94790647e65f880430138..72e42384cb1df479a109aaa3bfd0be185dc0ac71 100644 (file)
@@ -161,9 +161,20 @@ Currently supported commands are:
 
    See also ``rndc addzone`` and ``rndc modzone``.
 
-``dnssec`` [**-status** *zone* [*class* [*view*]]
-   Show the DNSSEC signing state for the specified zone.  Requires the
-   zone to have a "dnssec-policy".
+``dnssec`` ( **-status** | **-checkds** [**-key** *id*] [**-when** *time*] ( *published* | *withdrawn* )) *zone* [*class* [*view*]]
+   This command allows you to interact with the "dnssec-policy" of a given
+   zone.
+
+   ``rndc dnssec -status`` show the DNSSEC signing state for the specified
+   zone.
+
+   ``rndc dnssec -checkds`` will let ``named`` know that the DS for the given
+   key has been seen published into or withdrawn from the parent.  This is
+   required in order to complete a KSK rollover.  If the ``-key id`` argument
+   is specified, look for the key with the given identifier, otherwise if there
+   is only one key acting as a KSK in the zone, assume the DS of that key.
+   The time that the DS has been published or withdrawn is set to now, unless
+   otherwise specified with the argument ``-when time``.
 
 ``dnstap`` ( **-reopen** | **-roll** [*number*] )
    This command closes and re-opens DNSTAP output files. ``rndc dnstap -reopen`` allows
index c1988be64b7f07ac09580eaddcde02fabd541a75..81fd8f642b44ea1f026909747539531bdd74f8b5 100644 (file)
@@ -161,9 +161,20 @@ recreated. To remove it permanently, it must also be removed from
 .sp
 See also \fBrndc addzone\fP and \fBrndc modzone\fP\&.
 .TP
-\fBdnssec\fP [\fB\-status\fP \fIzone\fP [\fIclass\fP [\fIview\fP]]
-Show the DNSSEC signing state for the specified zone.  Requires the
-zone to have a "dnssec\-policy".
+\fBdnssec\fP ( \fB\-status\fP | \fB\-checkds\fP [\fB\-key\fP \fIid\fP] [\fB\-when\fP \fItime\fP] ( \fIpublished\fP | \fIwithdrawn\fP )) \fIzone\fP [\fIclass\fP [\fIview\fP]]
+This command allows you to interact with the "dnssec\-policy" of a given
+zone.
+.sp
+\fBrndc dnssec \-status\fP show the DNSSEC signing state for the specified
+zone.
+.sp
+\fBrndc dnssec \-checkds\fP will let \fBnamed\fP know that the DS for the given
+key has been seen published into or withdrawn from the parent.  This is
+required in order to complete a KSK rollover.  If the \fB\-key id\fP argument
+is specified, look for the key with the given identifier, otherwise if there
+is only one key acting as a KSK in the zone, assume the DS of that key.
+The time that the DS has been published or withdrawn is set to now, unless
+otherwise specified with the argument \fB\-when time\fP\&.
 .TP
 \fBdnstap\fP ( \fB\-reopen\fP | \fB\-roll\fP [\fInumber\fP] )
 This command closes and re\-opens DNSTAP output files. \fBrndc dnstap \-reopen\fP allows
index 7d7940a85159f7f867f7dfd559323da18387f6bf..f9cf22ec5d9fa2ef8d49b2e4034dd99d5c9f664f 100644 (file)
@@ -110,7 +110,9 @@ static const char *timingtags[TIMING_NTAGS] = {
 
        "DSPublish:",    "SyncPublish:",  "SyncDelete:",
 
-       "DNSKEYChange:", "ZRRSIGChange:", "KRRSIGChange:", "DSChange:"
+       "DNSKEYChange:", "ZRRSIGChange:", "KRRSIGChange:", "DSChange:",
+
+       "DSRemoved:"
 };
 
 #define KEYSTATES_NTAGS (DST_MAX_KEYSTATES + 1)
@@ -2009,6 +2011,8 @@ write_key_state(const dst_key_t *key, int type, const char *directory) {
                printtime(key, DST_TIME_INACTIVE, "Retired", fp);
                printtime(key, DST_TIME_REVOKE, "Revoked", fp);
                printtime(key, DST_TIME_DELETE, "Removed", fp);
+               printtime(key, DST_TIME_DSPUBLISH, "DSPublish", fp);
+               printtime(key, DST_TIME_DSDELETE, "DSRemoved", fp);
                printtime(key, DST_TIME_SYNCPUBLISH, "PublishCDS", fp);
                printtime(key, DST_TIME_SYNCDELETE, "DeleteCDS", fp);
 
index df48b792bf3c76fc018c1fecd6fe1efec7e2c280..3e8ad6af307bfa5fcab502230887edb27ba38c04 100644 (file)
@@ -51,6 +51,31 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
  *\li          On error, keypool is unchanged
  */
 
+isc_result_t
+dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+                  const char *directory, isc_stdtime_t now, bool dspublish);
+isc_result_t
+dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+                     const char *directory, isc_stdtime_t now, bool dspublish,
+                     dns_keytag_t id);
+/*%<
+ * Check DS for one key in 'keyring'. The key must have the KSK role.
+ * If 'dspublish' is set to true, set the DS Publish time to 'now'.
+ * If 'dspublish' is set to false, set the DS Removed time to 'now'.
+ * If a specific key 'id' is given it must match the keytag.
+ * The result is stored in the key state file.
+ *
+ *     Requires:
+ *\li          'kasp' is not NULL.
+ *\li          'keyring' is not NULL.
+ *
+ *     Returns:
+ *\li          #ISC_R_SUCCESS (No error).
+ *\li          #ISC_R_FAILURE (More than one matching KSK found).
+ *\li          #ISC_R_NOTFOUND (No matching KSK found).
+ *
+ */
+
 void
 dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
                  isc_stdtime_t now, char *out, size_t out_len);
index e4ac3737098d57d8fb5f279f95c5bb799fbe79b5..bdf95522f6754f7374b6032640f2e22772192b08 100644 (file)
@@ -133,7 +133,8 @@ typedef enum dst_key_state {
 #define DST_TIME_ZRRSIG             10
 #define DST_TIME_KRRSIG             11
 #define DST_TIME_DS         12
-#define DST_MAX_TIMES       12
+#define DST_TIME_DSDELETE    13
+#define DST_MAX_TIMES       13
 
 /* Numeric metadata definitions */
 #define DST_NUM_PREDECESSOR 0
index 23dc33d74c2c0105a4cb9b4531678505e41d6bc1..c8a5badb7835b74d271096b5d8c20159a5e525f0 100644 (file)
@@ -128,9 +128,6 @@ keymgr_settime_remove(dns_dnsseckey_t *key, dns_kasp_t *kasp) {
                             dns_kasp_parentpropagationdelay(kasp) +
                             dns_kasp_retiresafety(kasp);
        }
-       if (zsk && ksk) {
-               ksk_remove += dns_kasp_parentregistrationdelay(kasp);
-       }
 
        remove = ksk_remove > zsk_remove ? ksk_remove : zsk_remove;
        dst_key_settime(key->key, DST_TIME_DELETE, remove);
@@ -263,12 +260,6 @@ keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
         * so ignore the result code.
         */
        (void)dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
-       if (!zsk && ksk) {
-               /*
-                * Include registration delay in prepublication time.
-                */
-               prepub += dns_kasp_parentregistrationdelay(kasp);
-       }
 
        ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
        if (ret != ISC_R_SUCCESS) {
@@ -965,10 +956,14 @@ keymgr_have_rrsig(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type,
  *    - First introduce the DNSKEY record, as well as the KRRSIG records.
  *    - Only if the DNSKEY record is OMNIPRESENT, suggest to introduce the DS.
  *
+ * Also check the DS Publish or Delete times, to see if the DS record
+ * already reached the parent.
  */
 static bool
 keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
-                      int type, dst_key_state_t next) {
+                      int type, dst_key_state_t next, isc_stdtime_t now) {
+       isc_result_t ret;
+       isc_stdtime_t dstime;
        dst_key_state_t dnskeystate = HIDDEN;
        dst_key_state_t ksk_present[4] = { OMNIPRESENT, NA, OMNIPRESENT,
                                           OMNIPRESENT };
@@ -980,10 +975,10 @@ keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
        dst_key_state_t ksk_retired[4] = { UNRETENTIVE, NA, NA, OMNIPRESENT };
        dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */
 
-       if (next != RUMOURED) {
+       if (next != RUMOURED && next != UNRETENTIVE) {
                /*
                 * Local policy only adds an extra barrier on transitions to
-                * the RUMOURED state.
+                * the RUMOURED and UNRETENTIVE states.
                 */
                return (true);
        }
@@ -993,6 +988,9 @@ keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
                /* No restrictions. */
                return (true);
        case DST_KEY_ZRRSIG:
+               if (next != RUMOURED) {
+                       return (true);
+               }
                /* Make sure the DNSKEY record is OMNIPRESENT. */
                (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
                if (dnskeystate == OMNIPRESENT) {
@@ -1013,13 +1011,35 @@ keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
                                  keyring, key, type, next, ksk_retired,
                                  ksk_rumoured, true, true)));
        case DST_KEY_KRRSIG:
+               if (next != RUMOURED) {
+                       return (true);
+               }
                /* Only introduce if the DNSKEY is also introduced. */
                (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
                return (dnskeystate != HIDDEN);
        case DST_KEY_DS:
-               /* Make sure the DNSKEY record is OMNIPRESENT. */
-               (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
-               return (dnskeystate == OMNIPRESENT);
+               if (next == RUMOURED) {
+                       /* Make sure the DNSKEY record is OMNIPRESENT. */
+                       (void)dst_key_getstate(key->key, DST_KEY_DNSKEY,
+                                              &dnskeystate);
+                       if (dnskeystate != OMNIPRESENT) {
+                               return (false);
+                       }
+                       /* Make sure DS has been seen in the parent. */
+                       ret = dst_key_gettime(key->key, DST_TIME_DSPUBLISH,
+                                             &dstime);
+                       if (ret != ISC_R_SUCCESS || dstime > now) {
+                               return (false);
+                       }
+               } else if (next == UNRETENTIVE) {
+                       /* Make sure DS has been withdrawn from the parent. */
+                       ret = dst_key_gettime(key->key, DST_TIME_DSDELETE,
+                                             &dstime);
+                       if (ret != ISC_R_SUCCESS || dstime > now) {
+                               return (false);
+                       }
+               }
+               return (true);
        default:
                return (false);
        }
@@ -1203,16 +1223,13 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type,
                         *
                         *      Iret = DprpP + TTLds
                         *
-                        * So we need to wait Dreg + Iret before the DS becomes
-                        * OMNIPRESENT. This translates to:
+                        * This translates to:
                         *
-                        *      parent-registration-delay +
                         *      parent-propagation-delay + parent-ds-ttl.
                         *
                         * We will also add the retire-safety interval.
                         */
                        nexttime = lastchange + dns_kasp_dsttl(kasp) +
-                                  dns_kasp_parentregistrationdelay(kasp) +
                                   dns_kasp_parentpropagationdelay(kasp) +
                                   dns_kasp_retiresafety(kasp);
                        break;
@@ -1302,7 +1319,7 @@ transition:
 
                        /* Is the transition allowed according to policy? */
                        if (!keymgr_policy_approval(keyring, dkey, i,
-                                                   next_state)) {
+                                                   next_state, now)) {
                                /* No, please respect rollover methods. */
                                isc_log_write(
                                        dns_lctx, DNS_LOGCATEGORY_DNSSEC,
@@ -1433,7 +1450,6 @@ keymgr_key_init(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now) {
        ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub);
        if (syncpub <= now && ret == ISC_R_SUCCESS) {
                dns_ttl_t ds_ttl = dns_kasp_dsttl(kasp);
-               ds_ttl += dns_kasp_parentregistrationdelay(kasp);
                ds_ttl += dns_kasp_parentpropagationdelay(kasp);
                if ((syncpub + ds_ttl) <= now) {
                        ds_state = OMNIPRESENT;
@@ -1854,6 +1870,83 @@ failure:
        return (result);
 }
 
+static isc_result_t
+keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+              const char *directory, isc_stdtime_t now, bool dspublish,
+              dns_keytag_t id, bool check_id) {
+       int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE);
+       isc_dir_t dir;
+       isc_result_t result;
+       dns_dnsseckey_t *ksk_key = NULL;
+
+       REQUIRE(DNS_KASP_VALID(kasp));
+       REQUIRE(keyring != NULL);
+
+       for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+            dkey = ISC_LIST_NEXT(dkey, link))
+       {
+               isc_result_t ret;
+               bool ksk = false;
+
+               ret = dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk);
+               if (ret == ISC_R_SUCCESS && ksk) {
+                       if (check_id && dst_key_id(dkey->key) != id) {
+                               continue;
+                       }
+
+                       if (ksk_key != NULL) {
+                               /*
+                                * Only checkds for one key at a time.
+                                */
+                               return (ISC_R_FAILURE);
+                       }
+
+                       ksk_key = dkey;
+               }
+       }
+
+       if (ksk_key == NULL) {
+               return (ISC_R_NOTFOUND);
+       }
+
+       if (dspublish) {
+               dst_key_settime(ksk_key->key, DST_TIME_DSPUBLISH, now);
+       } else {
+               dst_key_settime(ksk_key->key, DST_TIME_DSDELETE, now);
+       }
+
+       /* Store key state and update hints. */
+       isc_dir_init(&dir);
+       if (directory == NULL) {
+               directory = ".";
+       }
+       result = isc_dir_open(&dir, directory);
+       if (result != ISC_R_SUCCESS) {
+               return result;
+       }
+
+       dns_dnssec_get_hints(ksk_key, now);
+       result = dst_key_tofile(ksk_key->key, options, directory);
+       isc_dir_close(&dir);
+
+       return (result);
+}
+
+isc_result_t
+dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+                  const char *directory, isc_stdtime_t now, bool dspublish) {
+       return (keymgr_checkds(kasp, keyring, directory, now, dspublish, 0,
+                              false));
+}
+
+isc_result_t
+dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+                     const char *directory, isc_stdtime_t now, bool dspublish,
+                     dns_keytag_t id) {
+       return (keymgr_checkds(kasp, keyring, directory, now, dspublish, id,
+                              true));
+}
+
 static void
 keytime_status(dst_key_t *key, isc_stdtime_t now, isc_buffer_t *buf,
               const char *pre, int ks, int kt) {
index be5f94c7f5f308e7578964de13c3c110a9c59100..e08904ff8cf5a82507772d98bf84cbf35b651843 100644 (file)
@@ -470,6 +470,8 @@ dns_kasplist_find
 dns_keydata_fromdnskey
 dns_keydata_todnskey
 dns_keyflags_fromtext
+dns_keymgr_checkds
+dns_keymgr_checkds_id
 dns_keymgr_run
 dns_keymgr_status
 dns_keynode_dsset