From da5c386dc0ffe7f44d8ec11c3ca4c42e6a40211a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Va=C5=A1ek?= Date: Mon, 9 Mar 2026 15:35:56 +0100 Subject: [PATCH] kaspdb: list keys in the "trash bin" --- src/knot/dnssec/kasp/kasp_db.c | 73 ++++++++++++++++++++++++++++++-- src/knot/dnssec/kasp/kasp_db.h | 8 +++- src/knot/dnssec/kasp/kasp_zone.c | 5 ++- src/knot/dnssec/kasp/policy.h | 3 +- src/knot/zone/backup.c | 2 +- tests/knot/test_kasp_db.c | 12 +++--- 6 files changed, 88 insertions(+), 15 deletions(-) diff --git a/src/knot/dnssec/kasp/kasp_db.c b/src/knot/dnssec/kasp/kasp_db.c index 630dd75d3..edbba082f 100644 --- a/src/knot/dnssec/kasp/kasp_db.c +++ b/src/knot/dnssec/kasp/kasp_db.c @@ -239,6 +239,25 @@ static MDB_val trash_serialize(const key_params_t *params, params->keytag, params->algorithm, flags); } +// For trash keys, timers in key_params_t are used as follows: +// params->timing.created -> deleted, i.e. time when the key has been moved to the trash bin +// params->timing.remove -> discard, i.e. time when the key will be discarded from the trash bin + +static bool trash_deserialize(const MDB_val *val, key_params_t *params) +{ + uint8_t flags; + + if (knot_lmdb_unmake_key(val->mv_data, val->mv_size, "LLHBB", + ¶ms->timing.remove, ¶ms->timing.created, + ¶ms->keytag, ¶ms->algorithm, &flags)) { + bool flags_ok = flags_deserialize(params, flags); + if (flags_ok && (params->is_ksk || !params->is_csk)) { + return true; + } + } + return false; +} + static key_params_t *txn2params(knot_lmdb_txn_t *txn) { key_params_t *p = calloc(1, sizeof(*p)); @@ -256,14 +275,62 @@ static key_params_t *txn2params(knot_lmdb_txn_t *txn) return p; } -int kasp_db_list_keys(knot_lmdb_db_t *db, const knot_dname_t *zone_name, list_t *dst) +static key_params_t *txn2params_trash(knot_lmdb_txn_t *txn, const knot_dname_t *zone_name) +{ + const uint8_t kclass; + const char *kid; + const knot_dname_t *kdname; + + if (!knot_lmdb_unmake_key(txn->cur_key.mv_data, txn->cur_key.mv_size, + "BSN", &kclass, &kid, &kdname)) { + txn->ret = KNOT_EMALF; + return NULL; + } + assert(kclass == KASPDBKEY_TRASH); + + if (zone_name != NULL && knot_dname_cmp(zone_name, kdname)) { + // Not an error, just skip this key. + return NULL; + } + + key_params_t *p; + char *id = NULL; + knot_dname_t *dname = NULL; + if ((p = calloc(1, sizeof(*p))) == NULL || + (id = strdup(kid)) == NULL || + (dname = knot_dname_copy(kdname, NULL)) == NULL) { + txn->ret = KNOT_ENOMEM; + goto fail; + } + p->id = id; + p->dname = dname; + + if (!trash_deserialize(&txn->cur_val, p)) { + txn->ret = KNOT_EMALF; + goto fail; + } + + return p; + +fail: + free(p); + free(id); + free(dname); + return NULL; +} + +int kasp_db_list_keys(knot_lmdb_db_t *db, const knot_dname_t *zone_name, list_t *dst, + bool trash) { init_list(dst); knot_lmdb_txn_t txn = { 0 }; - MDB_val prefix = make_key_str(KASPDBKEY_PARAMS, zone_name, NULL); + MDB_val prefix = trash ? knot_lmdb_make_key("B", KASPDBKEY_TRASH) : + 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); + key_params_t *p; + p = trash ? txn2params_trash(&txn, zone_name) : + txn2params(&txn); if (p != NULL) { ptrlist_add(dst, p, NULL); } diff --git a/src/knot/dnssec/kasp/kasp_db.h b/src/knot/dnssec/kasp/kasp_db.h index f4a1a6f0d..327c64d42 100644 --- a/src/knot/dnssec/kasp/kasp_db.h +++ b/src/knot/dnssec/kasp/kasp_db.h @@ -22,15 +22,19 @@ typedef enum { // the enum values MUST match those from keyclass_t !! } kaspdb_serial_t; /*! - * \brief For given zone, list all keys (their IDs) belonging to it. + * \brief For given zone, list all keys (their IDs) belonging to it, + * or optionally list trashed keys instead. * * \param db KASP db * \param zone_name name of the zone in question + * if NULL for trashed keys, list trashed keys belonging to any zone * \param dst output if KNOT_EOK: ptrlist of keys' params + * \param trash if false, list all valid keys, if true, list all trashed keys * * \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); +int kasp_db_list_keys(knot_lmdb_db_t *db, const knot_dname_t *zone_name, list_t *dst, + bool trash); /*! * \brief Obtain the algorithm of a key. diff --git a/src/knot/dnssec/kasp/kasp_zone.c b/src/knot/dnssec/kasp/kasp_zone.c index dd662983f..a8d0b1ab6 100644 --- a/src/knot/dnssec/kasp/kasp_zone.c +++ b/src/knot/dnssec/kasp/kasp_zone.c @@ -178,7 +178,7 @@ int kasp_zone_load(knot_kasp_zone_t *zone, list_t key_params; init_list(&key_params); - int ret = kasp_db_list_keys(kdb, zone_name, &key_params); + int ret = kasp_db_list_keys(kdb, zone_name, &key_params, false); if (ret == KNOT_ENOENT) { zone->keys = NULL; zone->num_keys = 0; @@ -310,8 +310,9 @@ void free_key_params(key_params_t *parm) { if (parm != NULL) { free(parm->id); + free(parm->dname); dnssec_binary_free(&parm->public_key); - memset(parm, 0 , sizeof(*parm)); + memset(parm, 0, sizeof(*parm)); } } diff --git a/src/knot/dnssec/kasp/policy.h b/src/knot/dnssec/kasp/policy.h index 5e68cc61a..575aa6510 100644 --- a/src/knot/dnssec/kasp/policy.h +++ b/src/knot/dnssec/kasp/policy.h @@ -24,7 +24,7 @@ typedef struct { 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 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; @@ -33,6 +33,7 @@ typedef struct { */ typedef struct { char *id; + knot_dname_t *dname; /*!< Zone that was using the key. Relevant for trash keys only. */ bool is_ksk; bool is_csk; bool is_pub_only; diff --git a/src/knot/zone/backup.c b/src/knot/zone/backup.c index 73ec28c50..890ba4c03 100644 --- a/src/knot/zone/backup.c +++ b/src/knot/zone/backup.c @@ -399,7 +399,7 @@ static int backup_keystore(conf_t *conf, zone_t *zone, zone_backup_ctx_t *ctx) list_t key_params; init_list(&key_params); - ret = kasp_db_list_keys(zone_kaspdb(zone), zone->name, &key_params); + ret = kasp_db_list_keys(zone_kaspdb(zone), zone->name, &key_params, false); ret = (ret == KNOT_ENOENT ? KNOT_EOK : ret); if (ret != KNOT_EOK) { LOG_FAIL("keystore list"); diff --git a/tests/knot/test_kasp_db.c b/tests/knot/test_kasp_db.c index fb1f846af..54003f60c 100644 --- a/tests/knot/test_kasp_db.c +++ b/tests/knot/test_kasp_db.c @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) ret = kasp_db_add_key(db, zone1, ¶ms1); is_int(KNOT_EOK, ret, "kasp_db: add key 1 eok"); - ret = kasp_db_list_keys(db, zone1, &l); + ret = kasp_db_list_keys(db, zone1, &l, false); is_int(KNOT_EOK, ret, "kasp_db: list keys 1 eok"); is_int(1, list_size(&l), "kasp_db: list keys reports one key 1"); params = ((ptrnode_t *)HEAD(l))->d; @@ -91,7 +91,7 @@ int main(int argc, char *argv[]) free_params ptrlist_deep_free(&l, NULL); - ret = kasp_db_list_keys(db, zone2, &l); + ret = kasp_db_list_keys(db, zone2, &l, false); is_int(KNOT_ENOENT, ret, "kasp_db: list keys 1 enoent"); is_int(0, list_size(&l), "kasp_db: list keys reports no keys 1"); ptrlist_deep_free(&l, NULL); @@ -99,7 +99,7 @@ int main(int argc, char *argv[]) ret = kasp_db_share_key(db, zone1, zone2, params1.id); is_int(KNOT_EOK, ret, "kasp_db: share key eok"); - ret = kasp_db_list_keys(db, zone2, &l); + ret = kasp_db_list_keys(db, zone2, &l, false); is_int(KNOT_EOK, ret, "kasp_db: list keys 3 eok"); is_int(1, list_size(&l), "kasp_db: list keys reports one key 2"); params = ((ptrnode_t *)HEAD(l))->d; @@ -109,7 +109,7 @@ int main(int argc, char *argv[]) ret = kasp_db_add_key(db, zone2, ¶ms2); is_int(KNOT_EOK, ret, "kasp_db: add key 2 eok"); - ret = kasp_db_list_keys(db, zone2, &l); + ret = kasp_db_list_keys(db, zone2, &l, false); is_int(KNOT_EOK, ret, "kasp_db: list keys 4 eok"); is_int(2, list_size(&l), "kasp_db: list keys reports two keys 1"); params = ((ptrnode_t *)TAIL(l))->d; @@ -122,7 +122,7 @@ int main(int argc, char *argv[]) ret = kasp_db_delete_key(db, zone1, params1.id, 0, &ignore); is_int(KNOT_EOK, ret, "kasp_db: delete key 1 eok"); - ret = kasp_db_list_keys(db, zone1, &l); + ret = kasp_db_list_keys(db, zone1, &l, false); is_int(KNOT_ENOENT, ret, "kasp_db: list keys 2 enoent"); is_int(list_size(&l), 0, "kasp_db: list keys reports no keys 2"); ptrlist_deep_free(&l, NULL); @@ -147,7 +147,7 @@ int main(int argc, char *argv[]) ret = kasp_db_delete_all(db, zone2); is_int(KNOT_EOK, ret, "kasp_db: delete all"); - ret = kasp_db_list_keys(db, zone2, &l); + ret = kasp_db_list_keys(db, zone2, &l, false); is_int(KNOT_EOK, ret, "kasp_db: do not delete keys"); is_int(2, list_size(&l), "kasp_db: list keys reports two keys 2"); params = ((ptrnode_t *)TAIL(l))->d;