src/utils/keymgr/functions.c
src/utils/keymgr/functions.h
src/utils/keymgr/main.c
+src/utils/keymgr/offline_ksk.c
+src/utils/keymgr/offline_ksk.h
src/utils/khost/khost_main.c
src/utils/khost/khost_params.c
src/utils/khost/khost_params.h
\fBshare\fP \fIkey_ID\fP
Import a key (specified by full key ID) from another zone as shared. After this, the key is
owned by both zones equally.
+.UNINDENT
+.SS Commands related to Offline KSK feature
+.INDENT 0.0
+.TP
+\fBpregenerate\fP \fIperiod_secs\fP
+Pre\-generate ZSKs for use with offline KSK, for the specified period starting from now.
+.TP
+\fBpresign\fP \fIperiod_secs\fP
+Sign pre\-generated ZSKs, for the specified period starting from now.
+.TP
+\fBshow\-rrsig\fP \fItimestamp\fP
+Print a pre\-generated DNSKEY RRSIG for specified timestamp.
.TP
\fBdel\-all\-old\fP
Delete old keys that are in state \(aqremoved\(aq.
Import a key (specified by full key ID) from another zone as shared. After this, the key is
owned by both zones equally.
+Commands related to Offline KSK feature
+.......................................
+
+**pregenerate** *period_secs*
+ Pre-generate ZSKs for use with offline KSK, for the specified period starting from now.
+
+**presign** *period_secs*
+ Sign pre-generated ZSKs, for the specified period starting from now.
+
+**show-rrsig** *timestamp*
+ Print a pre-generated DNSKEY RRSIG for specified timestamp.
+
**del-all-old**
Delete old keys that are in state 'removed'.
char *kasp_zone_path;
bool rrsig_drop_existing;
+ bool keep_deleted_keys;
+ bool rollover_only_zsk;
} kdnssec_ctx_t;
/*!
#include "contrib/files.h"
#include "contrib/wire_ctx.h"
+#include "knot/journal/serialization.h"
struct kasp_db {
knot_db_t *keys_db;
};
typedef enum {
+ KASPDBKEY_OFFLINE_RRSIG = 0x0, // this MUST be always first, because we use LEQ operator
KASPDBKEY_PARAMS = 0x1,
KASPDBKEY_POLICYLAST = 0x2,
KASPDBKEY_NSEC3SALT = 0x3,
}
return (EMPTY_LIST(*dst) ? KNOT_ENOENT : KNOT_EOK);
}
+
+static void for_time2string(char str[21], knot_time_t t)
+{
+ snprintf(str, 21, "%020lu", t);
+}
+
+int kasp_db_store_offline_rrsig(kasp_db_t *db, knot_time_t for_time, const knot_rrset_t *rrsig)
+{
+ if (db == NULL || rrsig == NULL || rrsig->type != KNOT_RRTYPE_RRSIG) {
+ return KNOT_EINVAL;
+ }
+
+ char for_time_str[21];
+ for_time2string(for_time_str, for_time);
+ knot_db_val_t key = make_key(KASPDBKEY_OFFLINE_RRSIG, rrsig->owner, for_time_str), val;
+ val.len = rrset_serialized_size(rrsig);
+ val.data = malloc(val.len);
+ if (val.data == NULL) {
+ free_key(&key);
+ return KNOT_ENOMEM;
+ }
+ with_txn(KEYS_RW, NULL);
+ wire_ctx_t wire = wire_ctx_init(val.data, val.len);
+ ret = serialize_rrset(&wire, rrsig);
+ if (ret == KNOT_EOK) {
+ ret = db_api->insert(txn, &key, &val, 0);
+ }
+ free_key(&key);
+ with_txn_end(NULL);
+ return ret;
+}
+
+int kasp_db_load_offline_rrsig(kasp_db_t *db, const knot_dname_t *for_dname, knot_time_t for_time, knot_rrset_t *rrsig)
+{
+ if (db == NULL || rrsig == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ char for_time_str[21];
+ for_time2string(for_time_str, for_time);
+ with_txn(KEYS_RO, NULL);
+ knot_db_val_t key = make_key(KASPDBKEY_OFFLINE_RRSIG, for_dname, for_time_str), val;
+ ret = db_api->find(txn, &key, &val, KNOT_DB_LEQ);
+ if (ret == KNOT_EOK) {
+ wire_ctx_t wire = wire_ctx_init(val.data, val.len);
+ ret = deserialize_rrset(&wire, rrsig);
+ }
+ free_key(&key);
+ with_txn_end(NULL);
+ return ret;
+}
* \return KNOT_E*
*/
int kasp_db_list_zones(kasp_db_t *db, list_t *dst);
+
+/*!
+ * \brief Store pre-generated RRSIG for offline KSK usage.
+ *
+ * \param db KASP db.
+ * \param for_time Timestamp in future in which the RRSIG shall be used.
+ * \param rrsig The rrsig to be stored.
+ *
+ * \return KNOT_E*
+ */
+int kasp_db_store_offline_rrsig(kasp_db_t *db, knot_time_t for_time, const knot_rrset_t *rrsig);
+
+/*!
+ * \brief Load pregenerated RRSIG.
+ *
+ * \param db KASP db.
+ * \param for_dname Name of the related zone.
+ * \param for_time Now. Closest RRSIG (timestamp equals or is closest lower).
+ * \param rrsig Output: the RRSIG.
+ *
+ * \return KNOT_E*
+ */
+int kasp_db_load_offline_rrsig(kasp_db_t *db, const knot_dname_t *for_dname, knot_time_t for_time, knot_rrset_t *rrsig);
continue;
}
if (key->is_ksk) {
+ if (ctx->rollover_only_zsk) {
+ continue;
+ }
+
switch (get_key_state(key, ctx->now)) {
case DNSSEC_KEY_STATE_PRE_ACTIVE:
keytime = alg_publish_time(key->timing.pre_active, ctx);
keytime = alg_remove_time(key->timing.post_active, ctx);
restype = REMOVE;
break;
- case DNSSEC_KEY_STATE_RETIRED:
case DNSSEC_KEY_STATE_REMOVED:
// ad REMOVED state: normally this wouldn't happen
// (key in removed state is instantly deleted)
// but if imported keys, they can be in this state
+ if (ctx->keep_deleted_keys) {
+ break;
+ } // else FALLBACK
+ case DNSSEC_KEY_STATE_RETIRED:
keytime = knot_time_min(key->timing.retire, key->timing.remove);
keytime = ksk_remove_time(keytime, ctx);;
restype = REMOVE;
get_key_state(key, ctx->now) == DNSSEC_KEY_STATE_REMOVED);
key->timing.remove = ctx->now;
- return kdnssec_delete_key(ctx, key);
+ if (ctx->keep_deleted_keys) {
+ return KNOT_EOK;
+ } else {
+ return kdnssec_delete_key(ctx, key);
+ }
}
int knot_dnssec_key_rollover(kdnssec_ctx_t *ctx, zone_sign_roll_flags_t flags,
}
}
// algorithm rollover
- if (algorithm_present(ctx, ctx->policy->algorithm) == 0 &&
+ if (!ctx->rollover_only_zsk &&
+ algorithm_present(ctx, ctx->policy->algorithm) == 0 &&
!running_rollover(ctx) && allowed_general_roll && ret == KNOT_EOK) {
ret = generate_ksk(ctx, 0, true);
if (!ctx->policy->singe_type_signing && ret == KNOT_EOK) {
}
}
// scheme rollover
- if (!signing_scheme_present(ctx) && allowed_general_roll &&
+ if (!ctx->rollover_only_zsk &&
+ !signing_scheme_present(ctx) && allowed_general_roll &&
!running_rollover(ctx) && ret == KNOT_EOK) {
ret = generate_ksk(ctx, 0, false);
if (!ctx->policy->singe_type_signing && ret == KNOT_EOK) {
zone_key_t *key = &keyset->keys[i];
int r = dnssec_key_import_keystore(key->key, keystore, key->id);
- if (r != DNSSEC_EOK && r != DNSSEC_KEY_ALREADY_PRESENT) {
+ switch (r) {
+ case DNSSEC_EOK:
+ case DNSSEC_KEY_ALREADY_PRESENT:
+ break;
+ case DNSSEC_ENOENT: // we hope that this is just offline KSK
+ break;
+ default:
return r;
}
}
#include "libdnssec/key.h"
#include "libdnssec/keytag.h"
#include "libdnssec/sign.h"
+#include "knot/common/log.h"
#include "knot/dnssec/key-events.h"
#include "knot/dnssec/rrset-sign.h"
#include "knot/dnssec/zone-sign.h"
const knot_rrset_t *rrsigs,
const dnssec_key_t *key,
dnssec_sign_ctx_t *ctx,
- const kdnssec_ctx_t *dnssec_ctx)
+ const kdnssec_ctx_t *dnssec_ctx,
+ uint16_t *at)
{
assert(key);
if (knot_check_signature(covered, rrsigs, i, key, ctx,
dnssec_ctx) == KNOT_EOK) {
+ if (at != NULL) {
+ *at = i;
+ }
return true;
}
}
}
if (!valid_signature_exists(covered, rrsigs, key->key,
- key->ctx, dnssec_ctx)) {
+ key->ctx, dnssec_ctx, NULL)) {
return false;
}
}
return result;
}
+static bool can_have_offline_rrsig(const knot_rrset_t *rr, const knot_dname_t *zone_apex)
+{
+ return (rr->type == KNOT_RRTYPE_DNSKEY && knot_dname_cmp(rr->owner, zone_apex) == 0);
+}
+
+static int load_offline_rrsig(const knot_rrset_t *covered,
+ knot_rrset_t *rrsig,
+ const kdnssec_ctx_t *ctx)
+{
+ knot_rrset_init_empty(rrsig);
+
+ if (!can_have_offline_rrsig(covered, ctx->zone->dname)) {
+ return KNOT_EOK;
+ }
+
+ int ret = kasp_db_load_offline_rrsig(*ctx->kasp_db, covered->owner, ctx->now, rrsig);
+ if (ret == KNOT_ENOENT) {
+ ret = KNOT_EOK;
+ }
+ return ret;
+}
+
/*!
* \brief Add missing RRSIGs into the changeset for adding.
*
assert(zone_keys);
assert(changeset);
- int result = KNOT_EOK;
- knot_rrset_t to_add;
+ knot_rrset_t to_add, offline_rrsigs;
knot_rrset_init_empty(&to_add);
+ int result = load_offline_rrsig(covered, &offline_rrsigs, dnssec_ctx);
+ if (result != KNOT_EOK) {
+ log_zone_warning(dnssec_ctx->zone->dname, "DNSSEC, failed to load offline DNSKEY RRSIG (%s)",
+ knot_strerror(result));
+ }
for (int i = 0; i < zone_keys->count; i++) {
const zone_key_t *key = &zone_keys->keys[i];
continue;
}
- if (valid_signature_exists(covered, rrsigs, key->key, key->ctx, dnssec_ctx)) {
+ if (valid_signature_exists(covered, rrsigs, key->key, key->ctx, dnssec_ctx, NULL)) {
continue;
}
to_add = create_empty_rrsigs_for(covered);
}
+ uint16_t at_offline;
+ if (valid_signature_exists(covered, &offline_rrsigs, key->key, key->ctx, dnssec_ctx, &at_offline)) {
+ log_zone_info(dnssec_ctx->zone->dname, "DNSSEC, using offline DNSKEY RRSIG");
+ knot_rdata_t *offline_rd = knot_rdataset_at(&offline_rrsigs.rrs, at_offline);
+ result = knot_rrset_add_rdata(&to_add, offline_rd->data, offline_rd->len, NULL);
+ if (result != KNOT_EOK) {
+ break;
+ }
+ continue;
+ }
+
result = knot_sign_rrset(&to_add, covered, key->key, key->ctx,
dnssec_ctx, NULL, expires_at);
if (result != KNOT_EOK) {
}
knot_rdataset_clear(&to_add.rrs, NULL);
+ knot_rrset_clear(&offline_rrsigs, NULL);
return result;
}
/*- private API - DNSKEY handling --------------------------------------------*/
-static int rrset_add_zone_key(knot_rrset_t *rrset, zone_key_t *zone_key)
+int rrset_add_zone_key(knot_rrset_t *rrset, zone_key_t *zone_key)
{
assert(rrset);
assert(zone_key);
#include "knot/dnssec/context.h"
#include "knot/dnssec/zone-keys.h"
+int rrset_add_zone_key(knot_rrset_t *rrset, zone_key_t *zone_key);
+
/*!
* \brief Adds/removes DNSKEY (and CDNSKEY, CDS) records to zone according to zone keyset.
*
free(ctx);
}
-static int deserialize_rrset(wire_ctx_t *wire, knot_rrset_t *rrset, long *phase)
+static int _deserialize_rrset(wire_ctx_t *wire, knot_rrset_t *rrset, long *phase)
{
assert(wire != NULL && rrset != NULL && phase != NULL);
assert(*phase >= SERIALIZE_RRSET_INIT && *phase < SERIALIZE_RRSET_DONE);
{
long phase = SERIALIZE_RRSET_INIT;
while (1) {
- int ret = deserialize_rrset(wire, rrset, &phase);
+ int ret = _deserialize_rrset(wire, rrset, &phase);
if (ret != KNOT_EOK || phase == SERIALIZE_RRSET_DONE) {
return ret;
}
return wire.error;
}
+
+int serialize_rrset(wire_ctx_t *wire, const knot_rrset_t *rrset)
+{
+ assert(wire != NULL && rrset != NULL);
+
+ // write owner, type, class, rrcnt
+ int size = knot_dname_to_wire(wire->position, rrset->owner,
+ wire_ctx_available(wire));
+ if (size < 0 || wire_ctx_available(wire) < size + 3 * sizeof(uint16_t)) {
+ assert(0);
+ }
+ wire_ctx_skip(wire, size);
+ wire_ctx_write_u16(wire, rrset->type);
+ wire_ctx_write_u16(wire, rrset->rclass);
+ wire_ctx_write_u16(wire, rrset->rrs.count);
+
+ for (size_t phase = 0; phase < rrset->rrs.count; phase++) {
+ const knot_rdata_t *rr = knot_rdataset_at(&rrset->rrs, phase);
+ assert(rr);
+ uint16_t rdlen = rr->len;
+ if (wire_ctx_available(wire) < sizeof(uint32_t) + sizeof(uint16_t) + rdlen) {
+ assert(0);
+ }
+ wire_ctx_write_u32(wire, rrset->ttl);
+ wire_ctx_write_u16(wire, rdlen);
+ wire_ctx_write(wire, rr->data, rdlen);
+ }
+
+ return KNOT_EOK;
+}
+
+int deserialize_rrset(wire_ctx_t *wire, knot_rrset_t *rrset)
+{
+ assert(wire != NULL && rrset != NULL);
+
+ // Read owner, rtype, rclass and RR count.
+ int size = knot_dname_size(wire->position);
+ if (size < 0) {
+ assert(0);
+ }
+ knot_dname_t *owner = knot_dname_copy(wire->position, NULL);
+ if (owner == NULL || wire_ctx_available(wire) < size + 3 * sizeof(uint16_t)) {
+ return KNOT_EMALF;
+ }
+ wire_ctx_skip(wire, size);
+ uint16_t type = wire_ctx_read_u16(wire);
+ uint16_t rclass = wire_ctx_read_u16(wire);
+ uint16_t rrcount = wire_ctx_read_u16(wire);
+ if (wire->error != KNOT_EOK) {
+ return wire->error;
+ }
+ knot_rrset_init(rrset, owner, type, rclass, 0);
+
+ for (size_t phase = 0; phase < rrcount && wire_ctx_available(wire) > 0; phase++) {
+ uint32_t ttl = wire_ctx_read_u32(wire);
+ uint32_t rdata_size = wire_ctx_read_u16(wire);
+ if (phase == 0) {
+ rrset->ttl = ttl;
+ }
+ if (wire->error != KNOT_EOK ||
+ wire_ctx_available(wire) < rdata_size ||
+ knot_rrset_add_rdata(rrset, wire->position, rdata_size,
+ NULL) != KNOT_EOK) {
+ knot_rrset_clear(rrset, NULL);
+ return KNOT_EMALF;
+ }
+ wire_ctx_skip(wire, rdata_size);
+ }
+
+ return KNOT_EOK;
+}
+
+size_t rrset_serialized_size(const knot_rrset_t *rrset)
+{
+ if (rrset == NULL || rrset->rrs.count == 0) {
+ return 0;
+ }
+
+ // Owner size + type + class + RR count.
+ size_t size = knot_dname_size(rrset->owner) + 3 * sizeof(uint16_t);
+
+ for (uint16_t i = 0; i < rrset->rrs.count; i++) {
+ const knot_rdata_t *rr = knot_rdataset_at(&rrset->rrs, i);
+ assert(rr);
+ // TTL + RR size + RR.
+ size += sizeof(uint32_t) + sizeof(uint16_t) + rr->len;
+ }
+
+ return size;
+}
int deserialize_rrset_chunks(wire_ctx_t *wire, knot_rrset_t *rrset,
uint8_t *src_chunks[], const size_t *chunk_sizes,
size_t chunks_count, size_t *cur_chunk);
+
+/*!
+ * \brief Simply serialize RRset w/o any chunking.
+ *
+ * \param wire
+ * \param rrset
+ *
+ * \return KNOT_E*
+ */
+int serialize_rrset(wire_ctx_t *wire, const knot_rrset_t *rrset);
+
+/*!
+ * \brief Simply deserialize RRset w/o any chunking.
+ *
+ * \param wire
+ * \param rrset
+ *
+ * \return KNOT_E*
+ */
+int deserialize_rrset(wire_ctx_t *wire, knot_rrset_t *rrset);
+
+/*!
+ * \brief Space needed to serialize RRset.
+ *
+ * \param rrset RRset.
+ *
+ * \return RRset binary size.
+ */
+size_t rrset_serialized_size(const knot_rrset_t *rrset);
utils/keymgr/bind_privkey.h \
utils/keymgr/functions.c \
utils/keymgr/functions.h \
+ utils/keymgr/offline_ksk.c \
+ utils/keymgr/offline_ksk.h \
utils/keymgr/main.c
kjournalprint_SOURCES = \
free(name);
return KNOT_EOK;
}
-
-int keymgr_del_all_old(kdnssec_ctx_t *ctx)
-{
- for (size_t i = 0; i < ctx->zone->num_keys; i++) {
- knot_kasp_key_t *key = &ctx->zone->keys[i];
- if (knot_time_cmp(key->timing.remove, ctx->now) < 0) {
- int ret = kdnssec_delete_key(ctx, key);
- printf("- %s\n", knot_strerror(ret));
- }
- }
- return kdnssec_ctx_commit(ctx);
-}
int keymgr_generate_ds(const knot_dname_t *dname, const knot_kasp_key_t *key);
int keymgr_generate_dnskey(const knot_dname_t *dname, const knot_kasp_key_t *key);
-
-int keymgr_del_all_old(kdnssec_ctx_t *ctx);
#include "libknot/libknot.h"
#include "utils/common/params.h"
#include "utils/keymgr/functions.h"
+#include "utils/keymgr/offline_ksk.h"
#define PROGRAM_NAME "keymgr"
" (syntax: delete <key_spec>)\n"
" set Set existing key's timing attribute.\n"
" (syntax: set <key_spec> <attribute_name>=<value>...)\n"
+ "\n"
+ "Commands related to Offline KSK feature:\n"
+ " pregenerate Pre-generate ZSKs for later rollovers with offline KSK.\n"
+ " (syntax: pregenerate <period_secs>)\n"
+ " presign Pre-generate RRSIG signatures for pregenerated ZSKs.\n"
+ " (syntax: presign <period_secs>)\n"
+ " show-rrsig Print a pre-generated DNSKEY RRSIG for specified timestamp.\n"
+ " (syntax: show-rrsig <timestamp>)\n"
" del-all-old Delete old keys that are in state 'removed'.\n"
"\n"
"Key specification:\n"
if (ret == KNOT_EOK) {
ret = kdnssec_delete_key(&kctx, key2del);
}
+ } else if (strcmp(argv[1], "pregenerate") == 0) {
+ CHECK_MISSING_ARG("Period not specified");
+ ret = keymgr_pregenerate_zsks(&kctx, knot_time() + atol(argv[2]));
+ } else if (strcmp(argv[1], "presign") == 0) {
+ CHECK_MISSING_ARG("Timestamp not specified");
+ ret = keymgr_presign_zsks(&kctx, knot_time() + atol(argv[2]));
+ } else if (strcmp(argv[1], "show-rrsig") == 0) {
+ CHECK_MISSING_ARG("Timestamp not specified");
+ ret = keymgr_print_rrsig(&kctx, atol(argv[2]));
} else if (strcmp(argv[1], "del-all-old") == 0) {
ret = keymgr_del_all_old(&kctx);
} else {
--- /dev/null
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "utils/keymgr/offline_ksk.h"
+#include "knot/dnssec/kasp/policy.h"
+#include "knot/dnssec/key-events.h"
+#include "knot/dnssec/rrset-sign.h"
+#include "knot/dnssec/zone-events.h"
+#include "knot/dnssec/zone-keys.h"
+#include "knot/dnssec/zone-sign.h"
+#include "libzscanner/scanner.h"
+
+static int pregenerate_once(kdnssec_ctx_t *ctx, knot_time_t *next)
+{
+ zone_sign_reschedule_t resch = { 0 };
+
+ // generate ZSKs
+ int ret = knot_dnssec_key_rollover(ctx, KEY_ROLL_ALLOW_ZSK_ROLL, &resch);
+ if (ret != KNOT_EOK) {
+ printf("rollover failed\n");
+ return ret;
+ }
+ // we don't need to do anything explicitly with the generated ZSKs
+ // they're simply stored in KASP db
+
+ *next = resch.next_rollover;
+ return KNOT_EOK;
+}
+
+static void update_next_resign(knot_time_t *next, knot_time_t now, knot_time_t key_timer)
+{
+ if (knot_time_cmp(now, key_timer) < 0) {
+ *next = knot_time_min(*next, key_timer);
+ }
+}
+
+static int presign_once(kdnssec_ctx_t *ctx, knot_time_t *next)
+{
+ // prepare the DNSKEY rrset to be signed
+ zone_keyset_t keyset = { 0 };
+ int ret = load_zone_keys(ctx, &keyset, false);
+ if (ret != KNOT_EOK) {
+ printf("load keys failed\n");
+ return ret;
+ }
+ knot_rrset_t *dnskey = knot_rrset_new(ctx->zone->dname, KNOT_RRTYPE_DNSKEY, KNOT_CLASS_IN, ctx->policy->dnskey_ttl, NULL);
+ knot_rrset_t *rrsig = knot_rrset_new(ctx->zone->dname, KNOT_RRTYPE_RRSIG, KNOT_CLASS_IN, ctx->policy->dnskey_ttl, NULL);
+ if (dnskey == NULL || rrsig == NULL) {
+ ret = KNOT_ENOMEM;
+ goto done;
+ }
+ for (int i = 0; i < keyset.count; i++) {
+ zone_key_t *key = &keyset.keys[i];
+ if (key->is_public) {
+ ret = rrset_add_zone_key(dnskey, key);
+ if (ret != KNOT_EOK) {
+ printf("add zone key failed\n");
+ goto done;
+ }
+ }
+ }
+
+ // sign the DNSKEY rrset
+ for (int i = 0; i < keyset.count; i++) {
+ zone_key_t *key = &keyset.keys[i];
+ if (key->is_active && key->is_ksk) {
+ ret = knot_sign_rrset(rrsig, dnskey, key->key, key->ctx, ctx, NULL);
+ if (ret != KNOT_EOK) {
+ printf("sign rrset failed\n");
+ goto done;
+ }
+ }
+ }
+
+ // store it to KASP db
+ assert(!knot_rrset_empty(rrsig));
+ ret = kasp_db_store_offline_rrsig(*ctx->kasp_db, ctx->now, rrsig);
+ if (ret != KNOT_EOK) {
+ printf("store rrsig failed\n");
+ goto done;
+ }
+
+ // next re-sign when rrsig expire or dnskey rrset changes
+ *next = ctx->now + ctx->policy->rrsig_lifetime - ctx->policy->rrsig_refresh_before;
+ for (int i = 0; i < ctx->zone->num_keys; i++) {
+ update_next_resign(next, ctx->now, ctx->zone->keys[i].timing.publish);
+ update_next_resign(next, ctx->now, ctx->zone->keys[i].timing.retire_active);
+ update_next_resign(next, ctx->now, ctx->zone->keys[i].timing.remove);
+ }
+
+done:
+ knot_rrset_free(dnskey, NULL);
+ knot_rrset_free(rrsig, NULL);
+ free_zone_keys(&keyset);
+ return ret;
+}
+
+int keymgr_pregenerate_zsks(kdnssec_ctx_t *ctx, knot_time_t upto)
+{
+ knot_time_t next = ctx->now;
+ int ret = KNOT_EOK;
+
+ ctx->keep_deleted_keys = true;
+ ctx->rollover_only_zsk = true;
+ ctx->policy->manual = false;
+
+ while (ret == KNOT_EOK && knot_time_cmp(next, upto) <= 0) {
+ ctx->now = next;
+ printf("pregenerate %lu\n", ctx->now);
+ ret = pregenerate_once(ctx, &next);
+ }
+
+ return ret;
+}
+
+int keymgr_presign_zsks(kdnssec_ctx_t *ctx, knot_time_t upto)
+{
+ knot_time_t next = ctx->now;
+ int ret = KNOT_EOK;
+
+ ctx->keep_deleted_keys = true;
+ ctx->policy->manual = true;
+
+ while (ret == KNOT_EOK && knot_time_cmp(next, upto) <= 0) {
+ ctx->now = next;
+ printf("presign %lu\n", ctx->now);
+ ret = presign_once(ctx, &next);
+ }
+
+ return ret;
+}
+
+int keymgr_print_rrsig(kdnssec_ctx_t *ctx, knot_time_t when)
+{
+ knot_rrset_t rrsig = { 0 };
+ knot_rrset_init_empty(&rrsig);
+ int ret = kasp_db_load_offline_rrsig(*ctx->kasp_db, ctx->zone->dname, when, &rrsig);
+ if (ret == KNOT_EOK) {
+ size_t out_size = 512;
+ char *out = malloc(out_size);
+ if (out == NULL) {
+ ret = KNOT_ENOMEM;
+ } else {
+ ret = knot_rrset_txt_dump(&rrsig, &out, &out_size, &KNOT_DUMP_STYLE_DEFAULT);
+ if (ret >= 0) {
+ printf("%s\n", out);
+ ret = KNOT_EOK;
+ }
+ free(out);
+ }
+ }
+ knot_rrset_clear(&rrsig, NULL);
+ return ret;
+}
+
+int keymgr_del_all_old(kdnssec_ctx_t *ctx)
+{
+ for (size_t i = 0; i < ctx->zone->num_keys; i++) {
+ knot_kasp_key_t *key = &ctx->zone->keys[i];
+ if (knot_time_cmp(key->timing.remove, ctx->now) < 0) {
+ int ret = kdnssec_delete_key(ctx, key);
+ printf("- %s\n", knot_strerror(ret));
+ }
+ }
+ return kdnssec_ctx_commit(ctx);
+}
--- /dev/null
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "knot/dnssec/context.h"
+
+int keymgr_pregenerate_zsks(kdnssec_ctx_t *ctx, knot_time_t upto);
+
+int keymgr_presign_zsks(kdnssec_ctx_t *ctx, knot_time_t upto);
+
+int keymgr_print_rrsig(kdnssec_ctx_t *ctx, knot_time_t when);
+
+int keymgr_del_all_old(kdnssec_ctx_t *ctx);
--- /dev/null
+#!/usr/bin/env python3
+
+"""
+Test of offline KSK by trying pre-generated ZSK rollover.
+"""
+
+import collections
+import os
+import shutil
+import datetime
+import subprocess
+import time
+from subprocess import check_call
+
+from dnstest.utils import *
+from dnstest.keys import Keymgr
+from dnstest.test import Test
+
+# check zone if keys are present and used for signing
+def check_zone(server, zone, dnskeys, dnskey_rrsigs, soa_rrsigs, msg):
+ qdnskeys = server.dig("example.com", "DNSKEY", bufsize=4096)
+ found_dnskeys = qdnskeys.count("DNSKEY")
+
+ qdnskeyrrsig = server.dig("example.com", "DNSKEY", dnssec=True, bufsize=4096)
+ found_rrsigs = qdnskeyrrsig.count("RRSIG")
+
+ qsoa = server.dig("example.com", "SOA", dnssec=True, bufsize=4096)
+ found_soa_rrsigs = qsoa.count("RRSIG")
+
+ check_log("DNSKEYs: %d (expected %d)" % (found_dnskeys, dnskeys));
+ check_log("RRSIGs: %d (expected %d)" % (found_soa_rrsigs, soa_rrsigs));
+ check_log("DNSKEY-RRSIGs: %d (expected %d)" % (found_rrsigs, dnskey_rrsigs));
+
+ if found_dnskeys != dnskeys:
+ set_err("BAD DNSKEY COUNT: " + msg)
+ detail_log("!DNSKEYs not published and activated as expected: " + msg)
+
+ if found_soa_rrsigs != soa_rrsigs:
+ set_err("BAD RRSIG COUNT: " + msg)
+ detail_log("!RRSIGs not published and activated as expected: " + msg)
+
+ if found_rrsigs != dnskey_rrsigs:
+ set_err("BAD DNSKEY RRSIG COUNT: " + msg)
+ detail_log("!RRSIGs not published and activated as expected: " + msg)
+
+ detail_log(SEP)
+
+ # Valgrind delay breaks the timing!
+ if not server.valgrind:
+ server.zone_backup(zone, flush=True)
+ server.zone_verify(zone)
+
+def wait_for_rrsig_count(t, server, rrtype, rrsig_count, timeout):
+ rtime = 0
+ while True:
+ qdnskeyrrsig = server.dig("example.com", rrtype, dnssec=True, bufsize=4096)
+ found_rrsigs = qdnskeyrrsig.count("RRSIG")
+ if found_rrsigs == rrsig_count:
+ break
+ rtime = rtime + 1
+ t.sleep(1)
+ if rtime > timeout:
+ break
+
+def wait_for_dnskey_count(t, server, dnskey_count, timeout):
+ for rtime in range(1, timeout):
+ qdnskeyrrsig = server.dig("example.com", "DNSKEY", dnssec=True, bufsize=4096)
+ found_dnskeys = qdnskeyrrsig.count("DNSKEY")
+ if found_dnskeys == dnskey_count:
+ break
+ t.sleep(1)
+
+t = Test()
+
+knot = t.server("knot")
+ZONE = "example.com."
+FUTURE = 100
+
+zone = t.zone(ZONE)
+t.link(zone, knot)
+
+knot.zonefile_sync = 24 * 60 * 60
+
+knot.dnssec(zone).enable = True
+knot.dnssec(zone).manual = True
+knot.dnssec(zone).alg = "ECDSAP384SHA384"
+knot.dnssec(zone).dnskey_ttl = 2
+knot.dnssec(zone).zsk_lifetime = 12
+knot.dnssec(zone).ksk_lifetime = 300 # this can be possibly left also infinity
+knot.dnssec(zone).propagation_delay = 3
+knot.port = 1234 # dummy, will be overwritten
+knot.gen_confile()
+
+key_ksk = knot.gen_key(zone, ksk=True, alg="ECDSAP384SHA384", key_len=384)
+key_zsk = knot.gen_key(zone, ksk=False, alg="ECDSAP384SHA384", key_len=384)
+
+Keymgr.run_check(knot.confile, ZONE, "pregenerate", str(FUTURE))
+Keymgr.run_check(knot.confile, ZONE, "presign", str(FUTURE))
+
+os.remove(knot.keydir + "/keys/" + key_ksk.keyid + ".pem")
+
+# parameters
+
+t.start()
+knot.zone_wait(zone)
+check_zone(knot, zone, 2, 1, 1, "init")
+
+wait_for_dnskey_count(t, knot, 3, knot.dnssec(zone).zsk_lifetime)
+check_zone(knot, zone, 3, 1, 1, "ZSK rollover")
+
+t.sleep(2)
+
+wait_for_dnskey_count(t, knot, 2, 2 * (knot.dnssec(zone).propagation_delay + knot.dnssec(zone).dnskey_ttl))
+check_zone(knot, zone, 2, 1, 1, "end")
+
+t.end()