From f449f278dd3c70e479a035f50a9bb817a9b433ba Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 17:24:08 +0200 Subject: Adding upstream version 3.2.6. Signed-off-by: Daniel Baumann --- src/knot/dnssec/kasp/kasp_db.c | 610 +++++++++++++++++++++++++++++++++++++++ src/knot/dnssec/kasp/kasp_db.h | 296 +++++++++++++++++++ src/knot/dnssec/kasp/kasp_zone.c | 447 ++++++++++++++++++++++++++++ src/knot/dnssec/kasp/kasp_zone.h | 63 ++++ src/knot/dnssec/kasp/keystate.c | 74 +++++ src/knot/dnssec/kasp/keystate.h | 35 +++ src/knot/dnssec/kasp/keystore.c | 89 ++++++ src/knot/dnssec/kasp/keystore.h | 22 ++ src/knot/dnssec/kasp/policy.h | 135 +++++++++ 9 files changed, 1771 insertions(+) create mode 100644 src/knot/dnssec/kasp/kasp_db.c create mode 100644 src/knot/dnssec/kasp/kasp_db.h create mode 100644 src/knot/dnssec/kasp/kasp_zone.c create mode 100644 src/knot/dnssec/kasp/kasp_zone.h create mode 100644 src/knot/dnssec/kasp/keystate.c create mode 100644 src/knot/dnssec/kasp/keystate.h create mode 100644 src/knot/dnssec/kasp/keystore.c create mode 100644 src/knot/dnssec/kasp/keystore.h create mode 100644 src/knot/dnssec/kasp/policy.h (limited to 'src/knot/dnssec/kasp') diff --git a/src/knot/dnssec/kasp/kasp_db.c b/src/knot/dnssec/kasp/kasp_db.c new file mode 100644 index 0000000..29c6a7d --- /dev/null +++ b/src/knot/dnssec/kasp/kasp_db.c @@ -0,0 +1,610 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. + + 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 . + */ + +#include "knot/dnssec/kasp/kasp_db.h" + +#include +#include + +#include "contrib/strtonum.h" +#include "contrib/wire_ctx.h" +#include "knot/dnssec/key_records.h" + +typedef enum { + KASPDBKEY_PARAMS = 0x1, + KASPDBKEY_POLICYLAST = 0x2, + KASPDBKEY_NSEC3SALT = 0x3, + KASPDBKEY_NSEC3TIME = 0x4, + KASPDBKEY_MASTERSERIAL = 0x5, + KASPDBKEY_LASTSIGNEDSERIAL = 0x6, + KASPDBKEY_OFFLINE_RECORDS = 0x7, + KASPDBKEY_SAVED_TTLS = 0x8, +} keyclass_t; + +static const keyclass_t zone_related_classes[] = { + KASPDBKEY_PARAMS, + KASPDBKEY_NSEC3SALT, + KASPDBKEY_NSEC3TIME, + KASPDBKEY_MASTERSERIAL, + KASPDBKEY_LASTSIGNEDSERIAL, + KASPDBKEY_OFFLINE_RECORDS, + KASPDBKEY_SAVED_TTLS, +}; +static const size_t zone_related_classes_size = sizeof(zone_related_classes) / sizeof(*zone_related_classes); + +static bool is_zone_related_class(uint8_t class) +{ + for (size_t i = 0; i < zone_related_classes_size; i++) { + if (zone_related_classes[i] == class) { + return true; + } + } + return false; +} + +static bool is_zone_related(const MDB_val *key) +{ + return is_zone_related_class(*(uint8_t *)key->mv_data); +} + +static MDB_val make_key_str(keyclass_t kclass, const knot_dname_t *dname, const char *str) +{ + switch (kclass) { + case KASPDBKEY_POLICYLAST: + assert(dname == NULL && str != NULL); + return knot_lmdb_make_key("BS", (int)kclass, str); + case KASPDBKEY_NSEC3SALT: + case KASPDBKEY_NSEC3TIME: + case KASPDBKEY_LASTSIGNEDSERIAL: + case KASPDBKEY_MASTERSERIAL: + case KASPDBKEY_SAVED_TTLS: + assert(dname != NULL && str == NULL); + return knot_lmdb_make_key("BN", (int)kclass, dname); + case KASPDBKEY_PARAMS: + case KASPDBKEY_OFFLINE_RECORDS: + assert(dname != NULL); + if (str == NULL) { + return knot_lmdb_make_key("BN", (int)kclass, dname); + } else { + return knot_lmdb_make_key("BNS", (int)kclass, dname, str); + } + default: + assert(0); + MDB_val empty = { 0 }; + return empty; + } +} + +static MDB_val make_key_time(keyclass_t kclass, const knot_dname_t *dname, knot_time_t time) +{ + char tmp[21]; + (void)snprintf(tmp, sizeof(tmp), "%0*"PRIu64, (int)(sizeof(tmp) - 1), time); + return make_key_str(kclass, dname, tmp); +} + +static bool unmake_key_str(const MDB_val *keyv, char **str) +{ + uint8_t kclass; + const knot_dname_t *dname; + const char *s; + return (knot_lmdb_unmake_key(keyv->mv_data, keyv->mv_size, "BNS", &kclass, &dname, &s) && + ((*str = strdup(s)) != NULL)); +} + +static bool unmake_key_time(const MDB_val *keyv, knot_time_t *time) +{ + uint8_t kclass; + const knot_dname_t *dname; + const char *s; + return (knot_lmdb_unmake_key(keyv->mv_data, keyv->mv_size, "BNS", &kclass, &dname, &s) && + str_to_u64(s, time) == KNOT_EOK); +} + +static MDB_val params_serialize(const key_params_t *params) +{ + uint8_t flags = 0x02; + flags |= (params->is_ksk ? 0x01 : 0); + flags |= (params->is_pub_only ? 0x04 : 0); + flags |= (params->is_csk ? 0x08 : 0); + + return knot_lmdb_make_key("LLHBBLLLLLLLLLDL", (uint64_t)params->public_key.size, + (uint64_t)sizeof(params->timing.revoke), params->keytag, params->algorithm, flags, + params->timing.created, params->timing.pre_active, params->timing.publish, + params->timing.ready, params->timing.active, params->timing.retire_active, + params->timing.retire, params->timing.post_active, params->timing.remove, + params->public_key.data, params->public_key.size, params->timing.revoke); +} + +// this is no longer compatible with keys created by Knot 2.5.x (and unmodified since) +static bool params_deserialize(const MDB_val *val, key_params_t *params) +{ + if (val->mv_size < 2 * sizeof(uint64_t)) { + return false; + } + uint64_t keylen = knot_wire_read_u64(val->mv_data); + uint64_t future = knot_wire_read_u64(val->mv_data + sizeof(keylen)); + uint8_t flags; + + if ((params->public_key.data = malloc(keylen)) == NULL) { + return false; + } + + if (knot_lmdb_unmake_key(val->mv_data, val->mv_size - future, "LLHBBLLLLLLLLLD", + &keylen, &future, ¶ms->keytag, ¶ms->algorithm, &flags, + ¶ms->timing.created, ¶ms->timing.pre_active, ¶ms->timing.publish, + ¶ms->timing.ready, ¶ms->timing.active, ¶ms->timing.retire_active, + ¶ms->timing.retire, ¶ms->timing.post_active, ¶ms->timing.remove, + params->public_key.data, (size_t)keylen)) { + + params->public_key.size = keylen; + params->is_ksk = ((flags & 0x01) ? true : false); + params->is_pub_only = ((flags & 0x04) ? true : false); + params->is_csk = ((flags & 0x08) ? true : false); + + if (future > 0) { + if (future < sizeof(params->timing.revoke)) { + free(params->public_key.data); + params->public_key.data = NULL; + return false; + } + // 'revoked' timer is part of 'future' section since it was added later + params->timing.revoke = knot_wire_read_u64(val->mv_data + val->mv_size - future); + } + + if ((flags & 0x02) && (params->is_ksk || !params->is_csk)) { + return true; + } + } + free(params->public_key.data); + params->public_key.data = NULL; + return false; +} + +static key_params_t *txn2params(knot_lmdb_txn_t *txn) +{ + key_params_t *p = calloc(1, sizeof(*p)); + if (p == NULL) { + txn->ret = KNOT_ENOMEM; + } else { + if (!params_deserialize(&txn->cur_val, p) || + !unmake_key_str(&txn->cur_key, &p->id)) { + txn->ret = KNOT_EMALF; + free(p); + p = NULL; + } + } + return p; +} + +int kasp_db_list_keys(knot_lmdb_db_t *db, const knot_dname_t *zone_name, list_t *dst) +{ + init_list(dst); + knot_lmdb_txn_t txn = { 0 }; + MDB_val prefix = make_key_str(KASPDBKEY_PARAMS, zone_name, NULL); + knot_lmdb_begin(db, &txn, false); + knot_lmdb_foreach(&txn, &prefix) { + key_params_t *p = txn2params(&txn); + if (p != NULL) { + ptrlist_add(dst, p, NULL); + } + } + knot_lmdb_abort(&txn); + free(prefix.mv_data); + if (txn.ret != KNOT_EOK) { + ptrlist_deep_free(dst, NULL); + return txn.ret; + } + return (EMPTY_LIST(*dst) ? KNOT_ENOENT : KNOT_EOK); +} + +int kasp_db_get_key_algorithm(knot_lmdb_db_t *db, const knot_dname_t *zone_name, + const char *key_id) +{ + knot_lmdb_txn_t txn = { 0 }; + MDB_val search = make_key_str(KASPDBKEY_PARAMS, zone_name, key_id); + knot_lmdb_begin(db, &txn, false); + int ret = txn.ret == KNOT_EOK ? KNOT_ENOENT : txn.ret; + if (knot_lmdb_find(&txn, &search, KNOT_LMDB_EXACT)) { + key_params_t p = { 0 }; + ret = params_deserialize(&txn.cur_val, &p) ? p.algorithm : KNOT_EMALF; + free(p.public_key.data); + } + knot_lmdb_abort(&txn); + free(search.mv_data); + return ret; +} + +static bool keyid_inuse(knot_lmdb_txn_t *txn, const char *key_id, key_params_t **params) +{ + uint8_t pf = KASPDBKEY_PARAMS; + MDB_val prefix = { sizeof(pf), &pf }; + knot_lmdb_foreach(txn, &prefix) { + char *found_id = NULL; + if (unmake_key_str(&txn->cur_key, &found_id) && + strcmp(found_id, key_id) == 0) { + if (params != NULL) { + *params = txn2params(txn); + } + free(found_id); + return true; + } + free(found_id); + } + return false; +} + + +int kasp_db_delete_key(knot_lmdb_db_t *db, const knot_dname_t *zone_name, const char *key_id, bool *still_used) +{ + MDB_val search = make_key_str(KASPDBKEY_PARAMS, zone_name, key_id); + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, true); + knot_lmdb_del_prefix(&txn, &search); + if (still_used != NULL) { + *still_used = keyid_inuse(&txn, key_id, NULL); + } + knot_lmdb_commit(&txn); + free(search.mv_data); + return txn.ret; +} + + +int kasp_db_delete_all(knot_lmdb_db_t *db, const knot_dname_t *zone) +{ + MDB_val prefix = make_key_str(KASPDBKEY_PARAMS, zone, NULL); + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, true); + for (size_t i = 0; i < zone_related_classes_size && prefix.mv_data != NULL; i++) { + *(uint8_t *)prefix.mv_data = zone_related_classes[i]; + knot_lmdb_del_prefix(&txn, &prefix); + } + knot_lmdb_commit(&txn); + free(prefix.mv_data); + return txn.ret; +} + +int kasp_db_sweep(knot_lmdb_db_t *db, sweep_cb keep_zone, void *cb_data) +{ + if (knot_lmdb_exists(db) == KNOT_ENODB) { + return KNOT_EOK; + } + int ret = knot_lmdb_open(db); + if (ret != KNOT_EOK) { + return ret; + } + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, true); + knot_lmdb_forwhole(&txn) { + if (is_zone_related(&txn.cur_key) && + !keep_zone((const knot_dname_t *)txn.cur_key.mv_data + 1, cb_data)) { + knot_lmdb_del_cur(&txn); + } + } + knot_lmdb_commit(&txn); + return txn.ret; +} + +int kasp_db_list_zones(knot_lmdb_db_t *db, list_t *zones) +{ + if (knot_lmdb_exists(db) == KNOT_ENODB) { + return KNOT_EOK; + } + int ret = knot_lmdb_open(db); + if (ret != KNOT_EOK) { + return ret; + } + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, false); + + uint8_t prefix_data = KASPDBKEY_PARAMS; + MDB_val prefix = { sizeof(prefix_data), &prefix_data }; + knot_lmdb_foreach(&txn, &prefix) { + const knot_dname_t *found = txn.cur_key.mv_data + sizeof(prefix_data); + if (!knot_dname_is_equal(found, ((ptrnode_t *)TAIL(*zones))->d)) { + knot_dname_t *copy = knot_dname_copy(found, NULL); + if (copy == NULL || ptrlist_add(zones, copy, NULL) == NULL) { + free(copy); + ptrlist_deep_free(zones, NULL); + return KNOT_ENOMEM; + } + } + } + knot_lmdb_abort(&txn); + if (txn.ret != KNOT_EOK) { + ptrlist_deep_free(zones, NULL); + } + return txn.ret; +} + +int kasp_db_add_key(knot_lmdb_db_t *db, const knot_dname_t *zone_name, const key_params_t *params) +{ + MDB_val v = params_serialize(params); + MDB_val k = make_key_str(KASPDBKEY_PARAMS, zone_name, params->id); + return knot_lmdb_quick_insert(db, k, v); +} + +int kasp_db_share_key(knot_lmdb_db_t *db, const knot_dname_t *zone_from, + const knot_dname_t *zone_to, const char *key_id) +{ + MDB_val from = make_key_str(KASPDBKEY_PARAMS, zone_from, key_id); + MDB_val to = make_key_str(KASPDBKEY_PARAMS, zone_to, key_id); + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, true); + if (knot_lmdb_find(&txn, &from, KNOT_LMDB_EXACT | KNOT_LMDB_FORCE)) { + knot_lmdb_insert(&txn, &to, &txn.cur_val); + } + knot_lmdb_commit(&txn); + free(from.mv_data); + free(to.mv_data); + return txn.ret; +} + +int kasp_db_store_nsec3salt(knot_lmdb_db_t *db, const knot_dname_t *zone_name, + const dnssec_binary_t *nsec3salt, knot_time_t salt_created) +{ + MDB_val key = make_key_str(KASPDBKEY_NSEC3SALT, zone_name, NULL); + MDB_val val1 = { nsec3salt->size, nsec3salt->data }; + uint64_t tmp = htobe64(salt_created); + MDB_val val2 = { sizeof(tmp), &tmp }; + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, true); + knot_lmdb_insert(&txn, &key, &val1); + if (key.mv_data != NULL) { + *(uint8_t *)key.mv_data = KASPDBKEY_NSEC3TIME; + } + knot_lmdb_insert(&txn, &key, &val2); + knot_lmdb_commit(&txn); + free(key.mv_data); + return txn.ret; +} + +int kasp_db_load_nsec3salt(knot_lmdb_db_t *db, const knot_dname_t *zone_name, + dnssec_binary_t *nsec3salt, knot_time_t *salt_created) +{ + MDB_val key = make_key_str(KASPDBKEY_NSEC3SALT, zone_name, NULL); + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, false); + if (nsec3salt != NULL) { + memset(nsec3salt, 0, sizeof(*nsec3salt)); + if (knot_lmdb_find(&txn, &key, KNOT_LMDB_EXACT | KNOT_LMDB_FORCE)) { + nsec3salt->size = txn.cur_val.mv_size; + nsec3salt->data = malloc(txn.cur_val.mv_size + 1); // +1 because it can be zero + if (nsec3salt->data == NULL) { + txn.ret = KNOT_ENOMEM; + } else { + memcpy(nsec3salt->data, txn.cur_val.mv_data, txn.cur_val.mv_size); + } + } + } + *(uint8_t *)key.mv_data = KASPDBKEY_NSEC3TIME; + if (knot_lmdb_find(&txn, &key, KNOT_LMDB_EXACT | KNOT_LMDB_FORCE)) { + knot_lmdb_unmake_curval(&txn, "L", salt_created); + } + knot_lmdb_abort(&txn); + free(key.mv_data); + if (txn.ret != KNOT_EOK && nsec3salt != NULL) { + free(nsec3salt->data); + } + return txn.ret; +} + +int kasp_db_store_serial(knot_lmdb_db_t *db, const knot_dname_t *zone_name, + kaspdb_serial_t serial_type, uint32_t serial) +{ + int ret = knot_lmdb_open(db); + if (ret != KNOT_EOK) { + return ret; + } + MDB_val k = make_key_str((keyclass_t)serial_type, zone_name, NULL); + MDB_val v = knot_lmdb_make_key("I", serial); + return knot_lmdb_quick_insert(db, k, v); +} + +int kasp_db_load_serial(knot_lmdb_db_t *db, const knot_dname_t *zone_name, + kaspdb_serial_t serial_type, uint32_t *serial) +{ + if (knot_lmdb_exists(db) == KNOT_ENODB) { + return KNOT_ENOENT; + } + int ret = knot_lmdb_open(db); + if (ret != KNOT_EOK) { + return ret; + } + MDB_val k = make_key_str((keyclass_t)serial_type, zone_name, NULL); + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, false); + if (knot_lmdb_find(&txn, &k, KNOT_LMDB_EXACT | KNOT_LMDB_FORCE)) { + knot_lmdb_unmake_curval(&txn, "I", serial); + } + knot_lmdb_abort(&txn); + free(k.mv_data); + return txn.ret; +} + +int kasp_db_get_policy_last(knot_lmdb_db_t *db, const char *policy_string, + knot_dname_t **lp_zone, char **lp_keyid) +{ + MDB_val k = make_key_str(KASPDBKEY_POLICYLAST, NULL, policy_string); + uint8_t kclass = 0; + knot_lmdb_txn_t txn = { 0 }; + *lp_zone = NULL; + *lp_keyid = NULL; + knot_lmdb_begin(db, &txn, false); + if (knot_lmdb_find(&txn, &k, KNOT_LMDB_EXACT | KNOT_LMDB_FORCE) && + knot_lmdb_unmake_curval(&txn, "BNS", &kclass, lp_zone, lp_keyid)) { + assert(*lp_zone != NULL && *lp_keyid != NULL); + *lp_zone = knot_dname_copy(*lp_zone, NULL); + *lp_keyid = strdup(*lp_keyid); + if (kclass != KASPDBKEY_PARAMS) { + txn.ret = KNOT_EMALF; + } else if (*lp_keyid == NULL || *lp_zone == NULL) { + txn.ret = KNOT_ENOMEM; + } else { + // check that the referenced key really exists + knot_lmdb_find(&txn, &txn.cur_val, KNOT_LMDB_EXACT | KNOT_LMDB_FORCE); + } + } + knot_lmdb_abort(&txn); + free(k.mv_data); + + return txn.ret; +} + +int kasp_db_set_policy_last(knot_lmdb_db_t *db, const char *policy_string, const char *last_lp_keyid, + const knot_dname_t *new_lp_zone, const char *new_lp_keyid) +{ + MDB_val k = make_key_str(KASPDBKEY_POLICYLAST, NULL, policy_string); + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, true); + if (knot_lmdb_find(&txn, &k, KNOT_LMDB_EXACT)) { + // check that the last_lp_keyid matches + uint8_t unuse1, *unuse2; + const char *real_last_keyid; + if (knot_lmdb_unmake_curval(&txn, "BNS", &unuse1, &unuse2, &real_last_keyid) && + (last_lp_keyid == NULL || strcmp(last_lp_keyid, real_last_keyid) != 0)) { + txn.ret = KNOT_ESEMCHECK; + } + } + MDB_val v = make_key_str(KASPDBKEY_PARAMS, new_lp_zone, new_lp_keyid); + knot_lmdb_insert(&txn, &k, &v); + free(k.mv_data); + free(v.mv_data); + knot_lmdb_commit(&txn); + return txn.ret; +} + +int kasp_db_store_offline_records(knot_lmdb_db_t *db, knot_time_t for_time, const key_records_t *r) +{ + MDB_val k = make_key_time(KASPDBKEY_OFFLINE_RECORDS, r->rrsig.owner, for_time); + MDB_val v = { key_records_serialized_size(r), NULL }; + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, true); + if (knot_lmdb_insert(&txn, &k, &v)) { + wire_ctx_t wire = wire_ctx_init(v.mv_data, v.mv_size); + txn.ret = key_records_serialize(&wire, r); + } + knot_lmdb_commit(&txn); + free(k.mv_data); + return txn.ret; +} + +int kasp_db_load_offline_records(knot_lmdb_db_t *db, const knot_dname_t *for_dname, + knot_time_t *for_time, knot_time_t *next_time, + key_records_t *r) +{ + MDB_val prefix = make_key_str(KASPDBKEY_OFFLINE_RECORDS, for_dname, NULL); + if (prefix.mv_data == NULL) { + return KNOT_ENOMEM; + } + unsigned operator = KNOT_LMDB_GEQ; + MDB_val search = prefix; + bool zero_for_time = (*for_time == 0); + if (!zero_for_time) { + operator = KNOT_LMDB_LEQ; + search = make_key_time(KASPDBKEY_OFFLINE_RECORDS, for_dname, *for_time); + } + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, false); + if (knot_lmdb_find(&txn, &search, operator) && + knot_lmdb_is_prefix_of(&prefix, &txn.cur_key)) { + wire_ctx_t wire = wire_ctx_init(txn.cur_val.mv_data, txn.cur_val.mv_size); + txn.ret = key_records_deserialize(&wire, r); + if (zero_for_time) { + unmake_key_time(&txn.cur_key, for_time); + } + if (!knot_lmdb_next(&txn) || !knot_lmdb_is_prefix_of(&prefix, &txn.cur_key) || + !unmake_key_time(&txn.cur_key, next_time)) { + *next_time = 0; + } + } else if (txn.ret == KNOT_EOK) { + txn.ret = KNOT_ENOENT; + } + knot_lmdb_abort(&txn); + if (!zero_for_time) { + free(search.mv_data); + } + free(prefix.mv_data); + return txn.ret; +} + +int kasp_db_delete_offline_records(knot_lmdb_db_t *db, const knot_dname_t *zone, + knot_time_t from_time, knot_time_t to_time) +{ + MDB_val prefix = make_key_str(KASPDBKEY_OFFLINE_RECORDS, zone, NULL); + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, true); + knot_lmdb_foreach(&txn, &prefix) { + knot_time_t found; + if (unmake_key_time(&txn.cur_key, &found) && + knot_time_cmp(found, from_time) >= 0 && + knot_time_cmp(found, to_time) <= 0) { + knot_lmdb_del_cur(&txn); + } + } + knot_lmdb_commit(&txn); + free(prefix.mv_data); + return txn.ret; +} + +int kasp_db_get_saved_ttls(knot_lmdb_db_t *db, const knot_dname_t *zone, + uint32_t *max_ttl, uint32_t *key_ttl) +{ + MDB_val key = make_key_str(KASPDBKEY_SAVED_TTLS, zone, NULL); + knot_lmdb_txn_t txn = { 0 }; + knot_lmdb_begin(db, &txn, false); + if (knot_lmdb_find(&txn, &key, KNOT_LMDB_EXACT | KNOT_LMDB_FORCE)) { + knot_lmdb_unmake_curval(&txn, "II", max_ttl, key_ttl); + } + knot_lmdb_abort(&txn); + free(key.mv_data); + return txn.ret; +} + +int kasp_db_set_saved_ttls(knot_lmdb_db_t *db, const knot_dname_t *zone, + uint32_t max_ttl, uint32_t key_ttl) +{ + MDB_val key = make_key_str(KASPDBKEY_SAVED_TTLS, zone, NULL); + MDB_val val = knot_lmdb_make_key("II", max_ttl, key_ttl); + return knot_lmdb_quick_insert(db, key, val); +} + +void kasp_db_ensure_init(knot_lmdb_db_t *db, conf_t *conf) +{ + if (db->path == NULL) { + char *kasp_dir = conf_db(conf, C_KASP_DB); + conf_val_t kasp_size = conf_db_param(conf, C_KASP_DB_MAX_SIZE); + knot_lmdb_init(db, kasp_dir, conf_int(&kasp_size), 0, "keys_db"); + free(kasp_dir); + assert(db->path != NULL); + } +} + +int kasp_db_backup(const knot_dname_t *zone, knot_lmdb_db_t *db, knot_lmdb_db_t *backup_db) +{ + size_t n_prefs = zone_related_classes_size + 1; // NOTE: this and following must match number of record types + MDB_val prefixes[n_prefs]; + prefixes[0] = knot_lmdb_make_key("B", KASPDBKEY_POLICYLAST); // we copy all policy-last records, that doesn't harm + for (size_t i = 1; i < n_prefs; i++) { + prefixes[i] = make_key_str(zone_related_classes[i - 1], zone, NULL); + } + + int ret = knot_lmdb_copy_prefixes(db, backup_db, prefixes, n_prefs); + + for (int i = 0; i < n_prefs; i++) { + free(prefixes[i].mv_data); + } + return ret; +} diff --git a/src/knot/dnssec/kasp/kasp_db.h b/src/knot/dnssec/kasp/kasp_db.h new file mode 100644 index 0000000..e9eea4f --- /dev/null +++ b/src/knot/dnssec/kasp/kasp_db.h @@ -0,0 +1,296 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. + + 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 . + */ + +#pragma once + +#include + +#include "contrib/time.h" +#include "contrib/ucw/lists.h" +#include "libknot/db/db_lmdb.h" +#include "libknot/dname.h" +#include "knot/dnssec/kasp/policy.h" +#include "knot/journal/knot_lmdb.h" + +typedef struct kasp_db kasp_db_t; + +typedef enum { // the enum values MUST match those from keyclass_t !! + KASPDB_SERIAL_MASTER = 0x5, + KASPDB_SERIAL_LASTSIGNED = 0x6, +} kaspdb_serial_t; + +/*! + * \brief For given zone, list all keys (their IDs) belonging to it. + * + * \param db KASP db + * \param zone_name name of the zone in question + * \param dst output if KNOT_EOK: ptrlist of keys' params + * + * \return KNOT_E* (KNOT_ENOENT if no keys) + */ +int kasp_db_list_keys(knot_lmdb_db_t *db, const knot_dname_t *zone_name, list_t *dst); + +/*! + * \brief Obtain the algorithm of a key. + * + * \param db KASP db. + * \param zone_name name of the zone + * \param key_id ID of the key in question + * + * \retval KNOT_E* if error + * \return >0 The algorithm of the key. + */ +int kasp_db_get_key_algorithm(knot_lmdb_db_t *db, const knot_dname_t *zone_name, + const char *key_id); + +/*! + * \brief Remove a key from zone. Delete the key if no zone has it anymore. + * + * \param db KASP db + * \param zone_name zone to be removed from + * \param key_id ID of key to be removed + * \param still_used output if KNOT_EOK: is the key still in use by other zones? + * + * \return KNOT_E* + */ +int kasp_db_delete_key(knot_lmdb_db_t *db, const knot_dname_t *zone_name, const char *key_id, bool *still_used); + +/*! + * \brief Remove all zone's keys from DB, including nsec3param + * + * \param db KASP db + * \param zone_name zone to be removed + * + * \return KNOT_E* + */ +int kasp_db_delete_all(knot_lmdb_db_t *db, const knot_dname_t *zone_name); + +/*! + * \brief Selectively delete zones from the database. + * + * \param db KASP database. + * \param keep_zone Filtering callback. + * \param cb_data Data passed to callback function. + * + * \return KNOT_E* + */ +int kasp_db_sweep(knot_lmdb_db_t *db, sweep_cb keep_zone, void *cb_data); + +/*! + * \brief List all zones that have at least one key in KASP db. + * + * \param db KASP database. + * \param zones Output: ptrlist with zone names. + * + * \return KNOT_E* + */ +int kasp_db_list_zones(knot_lmdb_db_t *db, list_t *zones); + +/*! + * \brief Add a key to the DB (possibly overwrite) and link it to a zone. + * + * Stores new key with given params into KASP db. If a key with the same ID had been present + * in KASP db already, its params get silently overwritten by those new params. + * Moreover, the key ID is linked to the zone. + * + * \param db KASP db + * \param zone_name name of the zone the new key shall belong to + * \param params key params, incl. ID + * + * \return KNOT_E* + */ +int kasp_db_add_key(knot_lmdb_db_t *db, const knot_dname_t *zone_name, const key_params_t *params); + +/*! + * \brief Link a key from another zone. + * + * \param db KASP db + * \param zone_from name of the zone the key belongs to + * \param zone_to name of the zone the key shall belong to as well + * \param key_id ID of the key in question + * + * \return KNOT_E* + */ +int kasp_db_share_key(knot_lmdb_db_t *db, const knot_dname_t *zone_from, + const knot_dname_t *zone_to, const char *key_id); + +/*! + * \brief Store NSEC3 salt for given zone (possibly overwrites old salt). + * + * \param db KASP db + * \param zone_name zone name + * \param nsec3salt new NSEC3 salt + * \param salt_created timestamp when the salt was created + * + * \return KNOT_E* + */ +int kasp_db_store_nsec3salt(knot_lmdb_db_t *db, const knot_dname_t *zone_name, + const dnssec_binary_t *nsec3salt, knot_time_t salt_created); + +/*! + * \brief Load NSEC3 salt for given zone. + * + * \param db KASP db + * \param zone_name zone name + * \param nsec3salt output if KNOT_EOK: the zone's NSEC3 salt + * \param salt_created output if KNOT_EOK: timestamp when the salt was created + * + * \return KNOT_E* (KNOT_ENOENT if not stored before) + */ +int kasp_db_load_nsec3salt(knot_lmdb_db_t *db, const knot_dname_t *zone_name, + dnssec_binary_t *nsec3salt, knot_time_t *salt_created); + +/*! + * \brief Store SOA serial number of master or last signed serial. + * + * \param db KASP db + * \param zone_name zone name + * \param serial_type kind of serial to be stored + * \param serial new serial to be stored + * + * \return KNOT_E* + */ +int kasp_db_store_serial(knot_lmdb_db_t *db, const knot_dname_t *zone_name, + kaspdb_serial_t serial_type, uint32_t serial); + +/*! + * \brief Load saved SOA serial number of master or last signed serial. + * + * \param db KASP db + * \param zone_name zone name + * \param serial_type kind of serial to be loaded + * \param serial output if KNOT_EOK: desired serial number + * + * \return KNOT_E* (KNOT_ENOENT if not stored before) + */ +int kasp_db_load_serial(knot_lmdb_db_t *db, const knot_dname_t *zone_name, + kaspdb_serial_t serial_type, uint32_t *serial); + +/*! + * \brief For given policy name, obtain last generated key. + * + * \param db KASP db + * \param policy_string a name identifying the signing policy with shared keys + * \param lp_zone out: the zone owning the last generated key + * \param lp_keyid out: the ID of the last generated key + * + * \note lp_zone and lp_keyid must be freed even when an error is returned + * + * \return KNOT_E* + */ +int kasp_db_get_policy_last(knot_lmdb_db_t *db, const char *policy_string, + knot_dname_t **lp_zone, char **lp_keyid); + +/*! + * \brief For given policy name, try to reset last generated key. + * + * \param db KASP db + * \param policy_string a name identifying the signing policy with shared keys + * \param last_lp_keyid just for check: ID of the key the caller thinks is the policy-last + * \param new_lp_zone zone name of the new policy-last key + * \param new_lp_keyid ID of the new policy-last key + * + * \retval KNOT_ESEMCHECK lasp_lp_keyid does not correspond to real last key. Probably another zone + * changed policy-last key in the meantime. Re-run kasp_db_get_policy_last() + * \retval KNOT_EOK policy-last key set up successfully to given zone/ID + * \return KNOT_E* common error + */ +int kasp_db_set_policy_last(knot_lmdb_db_t *db, const char *policy_string, const char *last_lp_keyid, + const knot_dname_t *new_lp_zone, const char *new_lp_keyid); + +/*! + * \brief Store pre-generated records for offline KSK usage. + * + * \param db KASP db. + * \param for_time Timestamp in future in which the RRSIG shall be used. + * \param r Records to be stored. + * + * \return KNOT_E* + */ +int kasp_db_store_offline_records(knot_lmdb_db_t *db, knot_time_t for_time, const key_records_t *r); + +/*! + * \brief Load pregenerated records for offline signing. + * + * \param db KASP db. + * \param for_dname Name of the related zone. + * \param for_time Now. Closest RRSIG (timestamp equals or is closest lower). + * If zero, the first record is returned and its time is stored. + * \param next_time Out: timestamp of next saved RRSIG (for easy "iteration"). + * \param r Out: offline records. + * + * \return KNOT_E* + */ +int kasp_db_load_offline_records(knot_lmdb_db_t *db, const knot_dname_t *for_dname, + knot_time_t *for_time, knot_time_t *next_time, + key_records_t *r); + +/*! + * \brief Delete pregenerated records for specified time interval. + * + * \param db KASP db. + * \param zone Zone in question. + * \param from_time Lower bound of the time interval (0 = infinity). + * \param to_time Upper bound of the time interval (0 = infinity). + * + * \return KNOT_E* + */ +int kasp_db_delete_offline_records(knot_lmdb_db_t *db, const knot_dname_t *zone, + knot_time_t from_time, knot_time_t to_time); + +/*! + * \brief Load saved zone-max-TTL and DNSKEY-TTL. + * + * \param db KASP db. + * \param max_ttl Out: saved zone max TTL. + * \param key_ttl Out: saved DNSKEY TTL. + * + * \retval KNOT_ENOENT If not saved yet. + * \return KNOT_E* + */ +int kasp_db_get_saved_ttls(knot_lmdb_db_t *db, const knot_dname_t *zone, + uint32_t *max_ttl, uint32_t *key_ttl); + +/*! + * \brief Save current zone-max-TTL and DNSKEY-TTL. + * + * \param db KASP db. + * \param max_ttl Current zone max TTL. + * \param key_ttl Current DNSKEY TTL. + * + * \return KNOT_E* + */ +int kasp_db_set_saved_ttls(knot_lmdb_db_t *db, const knot_dname_t *zone, + uint32_t max_ttl, uint32_t key_ttl); + +/*! + * \brief Initialize KASP database according to conf, if not already. + * + * \param db KASP DB to be initialized. + * \param conf COnfiguration to take options from. + */ +void kasp_db_ensure_init(knot_lmdb_db_t *db, conf_t *conf); + +/*! + * \brief Backup KASP DB for one zone with keys and all metadata to backup location. + * + * \param zone Name of the zone to be backed up. + * \param db DB to backup from. + * \param backup_db DB to backup to. + * + * \return KNOT_E* + */ +int kasp_db_backup(const knot_dname_t *zone, knot_lmdb_db_t *db, knot_lmdb_db_t *backup_db); diff --git a/src/knot/dnssec/kasp/kasp_zone.c b/src/knot/dnssec/kasp/kasp_zone.c new file mode 100644 index 0000000..58925fa --- /dev/null +++ b/src/knot/dnssec/kasp/kasp_zone.c @@ -0,0 +1,447 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. + + 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 . + */ + +#include "knot/dnssec/kasp/kasp_zone.h" +#include "knot/dnssec/kasp/keystore.h" +#include "knot/dnssec/zone-keys.h" +#include "libdnssec/binary.h" + +// FIXME DNSSEC errors versus knot errors + +/*! + * Check if key parameters allow to create a key. + */ +static int key_params_check(key_params_t *params) +{ + assert(params); + + if (params->algorithm == 0) { + return KNOT_INVALID_KEY_ALGORITHM; + } + + if (params->public_key.size == 0) { + return KNOT_NO_PUBLIC_KEY; + } + + return KNOT_EOK; +} + +/*! \brief Determine presence of SEP bit by trial-end-error using known keytag. */ +static int dnskey_guess_flags(dnssec_key_t *key, uint16_t keytag) +{ + dnssec_key_set_flags(key, DNSKEY_FLAGS_KSK); + if (dnssec_key_get_keytag(key) == keytag) { + return KNOT_EOK; + } + + dnssec_key_set_flags(key, DNSKEY_FLAGS_ZSK); + if (dnssec_key_get_keytag(key) == keytag) { + return KNOT_EOK; + } + + dnssec_key_set_flags(key, DNSKEY_FLAGS_REVOKED); + if (dnssec_key_get_keytag(key) == keytag) { + return KNOT_EOK; + } + + return KNOT_EMALF; +} + +static int params2dnskey(const knot_dname_t *dname, key_params_t *params, + dnssec_key_t **key_ptr) +{ + assert(dname); + assert(params); + assert(key_ptr); + + int ret = key_params_check(params); + if (ret != KNOT_EOK) { + return ret; + } + + dnssec_key_t *key = NULL; + ret = dnssec_key_new(&key); + if (ret != KNOT_EOK) { + return knot_error_from_libdnssec(ret); + } + + ret = dnssec_key_set_dname(key, dname); + if (ret != KNOT_EOK) { + dnssec_key_free(key); + return knot_error_from_libdnssec(ret); + } + + dnssec_key_set_algorithm(key, params->algorithm); + + ret = dnssec_key_set_pubkey(key, ¶ms->public_key); + if (ret != KNOT_EOK) { + dnssec_key_free(key); + return knot_error_from_libdnssec(ret); + } + + ret = dnskey_guess_flags(key, params->keytag); + if (ret != KNOT_EOK) { + dnssec_key_free(key); + return ret; + } + + *key_ptr = key; + + return KNOT_EOK; +} + +static int params2kaspkey(const knot_dname_t *dname, key_params_t *params, + knot_kasp_key_t *key) +{ + assert(dname != NULL); + assert(params != NULL); + assert(key != NULL); + + int ret = params2dnskey(dname, params, &key->key); + if (ret != KNOT_EOK) { + return ret; + } + + key->id = strdup(params->id); + if (key->id == NULL) { + dnssec_key_free(key->key); + return KNOT_ENOMEM; + } + + key->timing = params->timing; + key->is_pub_only = params->is_pub_only; + assert(params->is_ksk || !params->is_csk); + key->is_ksk = params->is_ksk; + key->is_zsk = (params->is_csk || !params->is_ksk); + return KNOT_EOK; +} + +static void kaspkey2params(knot_kasp_key_t *key, key_params_t *params) +{ + assert(key); + assert(params); + + params->id = key->id; + params->keytag = dnssec_key_get_keytag(key->key); + dnssec_key_get_pubkey(key->key, ¶ms->public_key); + params->algorithm = dnssec_key_get_algorithm(key->key); + params->is_ksk = key->is_ksk; + params->is_csk = (key->is_ksk && key->is_zsk); + params->timing = key->timing; + params->is_pub_only = key->is_pub_only; +} + +static void detect_keytag_conflict(knot_kasp_zone_t *zone, bool *kt_cfl) +{ + *kt_cfl = false; + if (zone->num_keys == 0) { + return; + } + uint16_t keytags[zone->num_keys]; + for (size_t i = 0; i < zone->num_keys; i++) { + keytags[i] = dnssec_key_get_keytag(zone->keys[i].key); + for (size_t j = 0; j < i; j++) { + if (keytags[j] == keytags[i]) { + *kt_cfl = true; + return; + } + } + } +} + +int kasp_zone_load(knot_kasp_zone_t *zone, + const knot_dname_t *zone_name, + knot_lmdb_db_t *kdb, + bool *kt_cfl) +{ + if (zone == NULL || zone_name == NULL || kdb == NULL) { + return KNOT_EINVAL; + } + + knot_kasp_key_t *dkeys = NULL; + size_t num_dkeys = 0; + dnssec_binary_t salt = { 0 }; + knot_time_t sc = 0; + + list_t key_params; + init_list(&key_params); + int ret = kasp_db_list_keys(kdb, zone_name, &key_params); + if (ret == KNOT_ENOENT) { + zone->keys = NULL; + zone->num_keys = 0; + ret = KNOT_EOK; + goto kzl_salt; + } else if (ret != KNOT_EOK) { + goto kzl_end; + } + + num_dkeys = list_size(&key_params); + dkeys = calloc(num_dkeys, sizeof(*dkeys)); + if (dkeys == NULL) { + goto kzl_end; + } + + ptrnode_t *n; + int i = 0; + WALK_LIST(n, key_params) { + key_params_t *parm = n->d; + ret = params2kaspkey(zone_name, parm, &dkeys[i++]); + free_key_params(parm); + if (ret != KNOT_EOK) { + goto kzl_end; + } + } + +kzl_salt: + (void)kasp_db_load_nsec3salt(kdb, zone_name, &salt, &sc); + // if error, salt was probably not present, no problem to have zero ? + + zone->dname = knot_dname_copy(zone_name, NULL); + if (zone->dname == NULL) { + ret = KNOT_ENOMEM; + goto kzl_end; + } + zone->keys = dkeys; + zone->num_keys = num_dkeys; + zone->nsec3_salt = salt; + zone->nsec3_salt_created = sc; + + detect_keytag_conflict(zone, kt_cfl); + +kzl_end: + ptrlist_deep_free(&key_params, NULL); + if (ret != KNOT_EOK) { + free(dkeys); + } + return ret; +} + +int kasp_zone_append(knot_kasp_zone_t *zone, const knot_kasp_key_t *appkey) +{ + if (zone == NULL || appkey == NULL || (zone->keys == NULL && zone->num_keys > 0)) { + return KNOT_EINVAL; + } + + size_t new_num_keys = zone->num_keys + 1; + knot_kasp_key_t *new_keys = calloc(new_num_keys, sizeof(*new_keys)); + if (!new_keys) { + return KNOT_ENOMEM; + } + if (zone->num_keys > 0) { + memcpy(new_keys, zone->keys, zone->num_keys * sizeof(*new_keys)); + } + memcpy(&new_keys[new_num_keys - 1], appkey, sizeof(*appkey)); + free(zone->keys); + zone->keys = new_keys; + zone->num_keys = new_num_keys; + return KNOT_EOK; +} + +int kasp_zone_save(const knot_kasp_zone_t *zone, + const knot_dname_t *zone_name, + knot_lmdb_db_t *kdb) +{ + if (zone == NULL || zone_name == NULL || kdb == NULL) { + return KNOT_EINVAL; + } + + key_params_t parm; + for (size_t i = 0; i < zone->num_keys; i++) { + kaspkey2params(&zone->keys[i], &parm); + + // Force overwrite already existing key-val pairs. + int ret = kasp_db_add_key(kdb, zone_name, &parm); + if (ret != KNOT_EOK) { + return ret; + } + } + + + return kasp_db_store_nsec3salt(kdb, zone_name, &zone->nsec3_salt, + zone->nsec3_salt_created); +} + +static void kasp_zone_clear_keys(knot_kasp_zone_t *zone) +{ + for (size_t i = 0; i < zone->num_keys; i++) { + dnssec_key_free(zone->keys[i].key); + free(zone->keys[i].id); + } + free(zone->keys); + zone->keys = NULL; + zone->num_keys = 0; +} + +void kasp_zone_clear(knot_kasp_zone_t *zone) +{ + if (zone == NULL) { + return; + } + knot_dname_free(zone->dname, NULL); + kasp_zone_clear_keys(zone); + free(zone->nsec3_salt.data); + memset(zone, 0, sizeof(*zone)); +} + +void kasp_zone_free(knot_kasp_zone_t **zone) +{ + if (zone != NULL) { + kasp_zone_clear(*zone); + free(*zone); + *zone = NULL; + } +} + +void free_key_params(key_params_t *parm) +{ + if (parm != NULL) { + free(parm->id); + dnssec_binary_free(&parm->public_key); + memset(parm, 0 , sizeof(*parm)); + } +} + +int zone_init_keystore(conf_t *conf, conf_val_t *policy_id, + dnssec_keystore_t **keystore, unsigned *backend, bool *key_label) +{ + char *zone_path = conf_db(conf, C_KASP_DB); + if (zone_path == NULL) { + return KNOT_ENOMEM; + } + + conf_id_fix_default(policy_id); + + conf_val_t keystore_id = conf_id_get(conf, C_POLICY, C_KEYSTORE, policy_id); + conf_id_fix_default(&keystore_id); + + conf_val_t val = conf_id_get(conf, C_KEYSTORE, C_BACKEND, &keystore_id); + unsigned _backend = conf_opt(&val); + + val = conf_id_get(conf, C_KEYSTORE, C_CONFIG, &keystore_id); + const char *config = conf_str(&val); + + if (key_label != NULL) { + val = conf_id_get(conf, C_KEYSTORE, C_KEY_LABEL, &keystore_id); + *key_label = conf_bool(&val); + } + + int ret = keystore_load(config, _backend, zone_path, keystore); + + if (backend != NULL) { + *backend = _backend; + } + + free(zone_path); + return ret; +} + +int kasp_zone_keys_from_rr(knot_kasp_zone_t *zone, + const knot_rdataset_t *zone_dnskey, + bool policy_single_type_signing, + bool *keytag_conflict) +{ + if (zone == NULL || zone_dnskey == NULL || keytag_conflict == NULL) { + return KNOT_EINVAL; + } + + kasp_zone_clear_keys(zone); + + zone->num_keys = zone_dnskey->count; + zone->keys = calloc(zone->num_keys, sizeof(*zone->keys)); + if (zone->keys == NULL) { + zone->num_keys = 0; + return KNOT_ENOMEM; + } + + knot_rdata_t *zkey = zone_dnskey->rdata; + for (int i = 0; i < zone->num_keys; i++) { + int ret = dnssec_key_from_rdata(&zone->keys[i].key, zone->dname, + zkey->data, zkey->len); + if (ret == KNOT_EOK) { + ret = dnssec_key_get_keyid(zone->keys[i].key, &zone->keys[i].id); + } + if (ret != KNOT_EOK) { + free(zone->keys); + zone->keys = NULL; + zone->num_keys = 0; + return ret; + } + zone->keys[i].is_pub_only = true; + + zone->keys[i].is_ksk = (knot_dnskey_flags(zkey) == DNSKEY_FLAGS_KSK); + zone->keys[i].is_zsk = policy_single_type_signing || !zone->keys[i].is_ksk; + + zone->keys[i].timing.publish = 1; + zone->keys[i].timing.active = 1; + + zkey = knot_rdataset_next(zkey); + } + + detect_keytag_conflict(zone, keytag_conflict); + return KNOT_EOK; +} + +int kasp_zone_from_contents(knot_kasp_zone_t *zone, + const zone_contents_t *contents, + bool policy_single_type_signing, + bool policy_nsec3, + uint16_t *policy_nsec3_iters, + bool *keytag_conflict) +{ + if (zone == NULL || contents == NULL || contents->apex == NULL) { + return KNOT_EINVAL; + } + + memset(zone, 0, sizeof(*zone)); + zone->dname = knot_dname_copy(contents->apex->owner, NULL); + if (zone->dname == NULL) { + return KNOT_ENOMEM; + } + + knot_rdataset_t *zone_dnskey = node_rdataset(contents->apex, KNOT_RRTYPE_DNSKEY); + if (zone_dnskey == NULL || zone_dnskey->count < 1) { + free(zone->dname); + return KNOT_DNSSEC_ENOKEY; + } + + int ret = kasp_zone_keys_from_rr(zone, zone_dnskey, policy_single_type_signing, keytag_conflict); + if (ret != KNOT_EOK) { + free(zone->dname); + return ret; + } + + zone->nsec3_salt_created = 0; + if (policy_nsec3) { + knot_rdataset_t *zone_ns3p = node_rdataset(contents->apex, KNOT_RRTYPE_NSEC3PARAM); + if (zone_ns3p == NULL || zone_ns3p->count != 1) { + kasp_zone_clear(zone); + return KNOT_ENSEC3PAR; + } + zone->nsec3_salt.size = knot_nsec3param_salt_len(zone_ns3p->rdata); + zone->nsec3_salt.data = malloc(zone->nsec3_salt.size); + if (zone->nsec3_salt.data == NULL) { + kasp_zone_clear(zone); + return KNOT_ENOMEM; + } + memcpy(zone->nsec3_salt.data, + knot_nsec3param_salt(zone_ns3p->rdata), + zone->nsec3_salt.size); + + *policy_nsec3_iters = knot_nsec3param_iters(zone_ns3p->rdata); + } + + return KNOT_EOK; +} diff --git a/src/knot/dnssec/kasp/kasp_zone.h b/src/knot/dnssec/kasp/kasp_zone.h new file mode 100644 index 0000000..c4df282 --- /dev/null +++ b/src/knot/dnssec/kasp/kasp_zone.h @@ -0,0 +1,63 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. + + 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 . + */ + +#pragma once + +#include "knot/dnssec/kasp/kasp_db.h" +#include "knot/zone/contents.h" +#include "libdnssec/keystore.h" + +typedef struct { + knot_dname_t *dname; + + knot_kasp_key_t *keys; + size_t num_keys; + + dnssec_binary_t nsec3_salt; + knot_time_t nsec3_salt_created; +} knot_kasp_zone_t; + +int kasp_zone_load(knot_kasp_zone_t *zone, + const knot_dname_t *zone_name, + knot_lmdb_db_t *kdb, + bool *kt_cfl); + +int kasp_zone_save(const knot_kasp_zone_t *zone, + const knot_dname_t *zone_name, + knot_lmdb_db_t *kdb); + +int kasp_zone_append(knot_kasp_zone_t *zone, + const knot_kasp_key_t *appkey); + +void kasp_zone_clear(knot_kasp_zone_t *zone); +void kasp_zone_free(knot_kasp_zone_t **zone); + +void free_key_params(key_params_t *parm); + +int zone_init_keystore(conf_t *conf, conf_val_t *policy_id, + dnssec_keystore_t **keystore, unsigned *backend, bool *key_label); + +int kasp_zone_keys_from_rr(knot_kasp_zone_t *zone, + const knot_rdataset_t *zone_dnskey, + bool policy_single_type_signing, + bool *keytag_conflict); + +int kasp_zone_from_contents(knot_kasp_zone_t *zone, + const zone_contents_t *contents, + bool policy_single_type_signing, + bool policy_nsec3, + uint16_t *policy_nsec3_iters, + bool *keytag_conflict); diff --git a/src/knot/dnssec/kasp/keystate.c b/src/knot/dnssec/kasp/keystate.c new file mode 100644 index 0000000..f1eaa54 --- /dev/null +++ b/src/knot/dnssec/kasp/keystate.c @@ -0,0 +1,74 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. + + 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 . + */ + +#include "knot/dnssec/kasp/keystate.h" + +key_state_t get_key_state(const knot_kasp_key_t *key, knot_time_t moment) +{ + if (!key || moment <= 0) { + return DNSSEC_KEY_STATE_INVALID; + } + + const knot_kasp_key_timing_t *t = &key->timing; + + bool removed = (knot_time_cmp(t->remove, moment) <= 0); + bool revoked = (knot_time_cmp(t->revoke, moment) <= 0); + bool post_active = (knot_time_cmp(t->post_active, moment) <= 0); + bool retired = (knot_time_cmp(t->retire, moment) <= 0); + bool retire_active = (knot_time_cmp(t->retire_active, moment) <= 0); + bool active = (knot_time_cmp(t->active, moment) <= 0); + bool ready = (knot_time_cmp(t->ready, moment) <= 0); + bool published = (knot_time_cmp(t->publish, moment) <= 0); + bool pre_active = (knot_time_cmp(t->pre_active, moment) <= 0); + bool created = (knot_time_cmp(t->created, moment) <= 0); + + if (removed) { + return DNSSEC_KEY_STATE_REMOVED; + } + if (revoked) { + return DNSSEC_KEY_STATE_REVOKED; + } + if (post_active) { + if (retired) { + return DNSSEC_KEY_STATE_INVALID; + } else { + return DNSSEC_KEY_STATE_POST_ACTIVE; + } + } + if (retired) { + return DNSSEC_KEY_STATE_RETIRED; + } + if (retire_active) { + return DNSSEC_KEY_STATE_RETIRE_ACTIVE; + } + if (active) { + return DNSSEC_KEY_STATE_ACTIVE; + } + if (ready) { + return DNSSEC_KEY_STATE_READY; + } + if (published) { + return DNSSEC_KEY_STATE_PUBLISHED; + } + if (pre_active) { + return DNSSEC_KEY_STATE_PRE_ACTIVE; + } + if (created) { + // don't care + } + + return DNSSEC_KEY_STATE_INVALID; +} diff --git a/src/knot/dnssec/kasp/keystate.h b/src/knot/dnssec/kasp/keystate.h new file mode 100644 index 0000000..6b7d398 --- /dev/null +++ b/src/knot/dnssec/kasp/keystate.h @@ -0,0 +1,35 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. + + 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 . + */ + +#pragma once + +#include "contrib/time.h" +#include "knot/dnssec/kasp/policy.h" + +typedef enum { + DNSSEC_KEY_STATE_INVALID = 0, + DNSSEC_KEY_STATE_PRE_ACTIVE, + DNSSEC_KEY_STATE_PUBLISHED, + DNSSEC_KEY_STATE_READY, + DNSSEC_KEY_STATE_ACTIVE, + DNSSEC_KEY_STATE_RETIRE_ACTIVE, + DNSSEC_KEY_STATE_RETIRED, + DNSSEC_KEY_STATE_POST_ACTIVE, + DNSSEC_KEY_STATE_REVOKED, + DNSSEC_KEY_STATE_REMOVED, +} key_state_t; + +key_state_t get_key_state(const knot_kasp_key_t *key, knot_time_t moment); diff --git a/src/knot/dnssec/kasp/keystore.c b/src/knot/dnssec/kasp/keystore.c new file mode 100644 index 0000000..2ec5cd1 --- /dev/null +++ b/src/knot/dnssec/kasp/keystore.c @@ -0,0 +1,89 @@ +/* Copyright (C) 2019 CZ.NIC, z.s.p.o. + + 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 . + */ + +#include +#include +#include + +#include "libdnssec/error.h" +#include "knot/dnssec/kasp/keystore.h" +#include "knot/conf/schema.h" +#include "libknot/error.h" + +static char *fix_path(const char *config, const char *base_path) +{ + assert(config); + assert(base_path); + + char *path = NULL; + + if (config[0] == '/') { + path = strdup(config); + } else { + if (asprintf(&path, "%s/%s", base_path, config) == -1) { + path = NULL; + } + } + + return path; +} + +int keystore_load(const char *config, unsigned backend, + const char *kasp_base_path, dnssec_keystore_t **keystore) +{ + int ret = DNSSEC_EINVAL; + char *fixed_config = NULL; + + switch (backend) { + case KEYSTORE_BACKEND_PEM: + ret = dnssec_keystore_init_pkcs8(keystore); + fixed_config = fix_path(config, kasp_base_path); + break; + case KEYSTORE_BACKEND_PKCS11: + ret = dnssec_keystore_init_pkcs11(keystore); + fixed_config = strdup(config); + break; + default: + assert(0); + } + if (ret != DNSSEC_EOK) { + free(fixed_config); + return knot_error_from_libdnssec(ret); + } + if (fixed_config == NULL) { + dnssec_keystore_deinit(*keystore); + *keystore = NULL; + return KNOT_ENOMEM; + } + + ret = dnssec_keystore_init(*keystore, fixed_config); + if (ret != DNSSEC_EOK) { + free(fixed_config); + dnssec_keystore_deinit(*keystore); + *keystore = NULL; + return knot_error_from_libdnssec(ret); + } + + ret = dnssec_keystore_open(*keystore, fixed_config); + free(fixed_config); + if (ret != DNSSEC_EOK) { + dnssec_keystore_deinit(*keystore); + *keystore = NULL; + return knot_error_from_libdnssec(ret); + } + + return KNOT_EOK; +} diff --git a/src/knot/dnssec/kasp/keystore.h b/src/knot/dnssec/kasp/keystore.h new file mode 100644 index 0000000..bd62347 --- /dev/null +++ b/src/knot/dnssec/kasp/keystore.h @@ -0,0 +1,22 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. + + 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 . + */ + +#pragma once + +#include "libdnssec/keystore.h" + +int keystore_load(const char *config, unsigned backend, + const char *kasp_base_path, dnssec_keystore_t **keystore); diff --git a/src/knot/dnssec/kasp/policy.h b/src/knot/dnssec/kasp/policy.h new file mode 100644 index 0000000..4354b95 --- /dev/null +++ b/src/knot/dnssec/kasp/policy.h @@ -0,0 +1,135 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. + + 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 . + */ + +#pragma once + +#include + +#include "contrib/time.h" +#include "libdnssec/key.h" +#include "knot/conf/conf.h" + +/*! + * KASP key timing information. + */ +typedef struct { + knot_time_t created; /*!< Time the key was generated/imported. */ + knot_time_t pre_active; /*!< Signing start with new algorithm. */ + knot_time_t publish; /*!< Time of DNSKEY record publication. */ + knot_time_t ready; /*!< Start of RRSIG generation, waiting for parent zone. */ + knot_time_t active; /*!< RRSIG records generating, other keys can be retired */ + knot_time_t retire_active; /*!< Still active, but obsoleted. */ + knot_time_t retire; /*!< End of RRSIG records generating. */ + knot_time_t post_active; /*!< Still signing with old algorithm, not published. */ + knot_time_t revoke; /*!< RFC 5011 state of KSK with 'revoked' flag and signed by self. */ + knot_time_t remove; /*!< Time of DNSKEY record removal. */ +} knot_kasp_key_timing_t; + +/*! + * Key parameters as writing in zone config file. + */ +typedef struct { + char *id; + bool is_ksk; + bool is_csk; + bool is_pub_only; + uint16_t keytag; + uint8_t algorithm; + dnssec_binary_t public_key; + knot_kasp_key_timing_t timing; +} key_params_t; + +/*! + * Zone key. + */ +typedef struct { + char *id; /*!< Keystore unique key ID. */ + dnssec_key_t *key; /*!< Instance of the key. */ + knot_kasp_key_timing_t timing; /*!< Key timing information. */ + bool is_pub_only; + bool is_ksk; + bool is_zsk; +} knot_kasp_key_t; + +/*! + * Parent for DS checks. + */ +typedef struct { + conf_remote_t *addr; + size_t addrs; +} knot_kasp_parent_t; + +knot_dynarray_declare(parent, knot_kasp_parent_t, DYNARRAY_VISIBILITY_NORMAL, 3) + +/*! + * Set of DNSSEC key related records. + */ +typedef struct { + knot_rrset_t dnskey; + knot_rrset_t cdnskey; + knot_rrset_t cds; + knot_rrset_t rrsig; +} key_records_t; + +/*! + * Key and signature policy. + */ +typedef struct { + bool manual; + char *string; + // DNSKEY + dnssec_key_algorithm_t algorithm; + uint16_t ksk_size; + uint16_t zsk_size; + uint32_t dnskey_ttl; + uint32_t zsk_lifetime; // like knot_time_t + uint32_t ksk_lifetime; // like knot_time_t + uint32_t delete_delay; // like knot_timediff_t + bool ksk_shared; + bool single_type_signing; + bool sts_default; // single-type-signing was set to default value + // RRSIG + bool reproducible_sign; // (EC)DSA creates reproducible signatures + uint32_t rrsig_lifetime; // like knot_time_t + uint32_t rrsig_refresh_before; // like knot_timediff_t + uint32_t rrsig_prerefresh; // like knot_timediff_t + // NSEC3 + bool nsec3_enabled; + bool nsec3_opt_out; + int64_t nsec3_salt_lifetime; // like knot_time_t + uint16_t nsec3_iterations; + uint8_t nsec3_salt_length; + // zone + uint32_t zone_maximal_ttl; // like knot_timediff_t + uint32_t saved_max_ttl; + uint32_t saved_key_ttl; + // data propagation delay + uint32_t propagation_delay; // like knot_timediff_t + // various + uint32_t ksk_sbm_timeout; // like knot_time_t + uint32_t ksk_sbm_check_interval; // like knot_time_t + uint32_t ksk_sbm_delay; + unsigned cds_cdnskey_publish; + dnssec_key_digest_t cds_dt; // digest type for CDS + parent_dynarray_t parents; + uint16_t signing_threads; + bool ds_push; + bool offline_ksk; + bool incremental; + bool key_label; + unsigned unsafe; +} knot_kasp_policy_t; +// TODO make the time parameters knot_timediff_t ?? -- cgit v1.2.3