Commit 706c7d8d authored by Libor Peltan's avatar Libor Peltan

dnssec/kasp major refactoring, many changes

parent e309dcdd
......@@ -242,6 +242,8 @@ src/knot/dnssec/kasp/keystate.c
src/knot/dnssec/kasp/keystate.h
src/knot/dnssec/kasp/keystore.c
src/knot/dnssec/kasp/keystore.h
src/knot/dnssec/key-events.c
src/knot/dnssec/key-events.h
src/knot/dnssec/nsec-chain.c
src/knot/dnssec/nsec-chain.h
src/knot/dnssec/nsec3-chain.c
......@@ -267,8 +269,10 @@ src/knot/events/handlers/flush.c
src/knot/events/handlers/freeze_thaw.c
src/knot/events/handlers/load.c
src/knot/events/handlers/notify.c
src/knot/events/handlers/nsec3resalt.c
src/knot/events/handlers/refresh.c
src/knot/events/handlers/update.c
src/knot/events/handlers/zsk_rollover.c
src/knot/events/log.c
src/knot/events/log.h
src/knot/events/replan.c
......
......@@ -255,6 +255,8 @@ libknotd_la_SOURCES = \
knot/dnssec/kasp/keystate.h \
knot/dnssec/kasp/keystore.c \
knot/dnssec/kasp/keystore.h \
knot/dnssec/key-events.c \
knot/dnssec/key-events.h \
knot/dnssec/nsec-chain.c \
knot/dnssec/nsec-chain.h \
knot/dnssec/nsec3-chain.c \
......@@ -280,8 +282,10 @@ libknotd_la_SOURCES = \
knot/events/handlers/freeze_thaw.c \
knot/events/handlers/load.c \
knot/events/handlers/notify.c \
knot/events/handlers/nsec3resalt.c \
knot/events/handlers/refresh.c \
knot/events/handlers/update.c \
knot/events/handlers/zsk_rollover.c \
knot/events/log.c \
knot/events/log.h \
knot/events/replan.c \
......
......@@ -17,7 +17,6 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <dnssec/error.h>
......@@ -27,28 +26,10 @@
#include "libknot/libknot.h"
#include "knot/conf/conf.h"
#include "knot/dnssec/context.h"
#include "knot/dnssec/kasp/keystore.h"
#include "contrib/files.h"
static int zone_save(void *ctx, const dnssec_kasp_zone_t *zone)
{
return dnssec_kasp_dir_api()->zone_save(ctx, zone);
}
static int zone_load(void *ctx, dnssec_kasp_zone_t *zone)
{
int r = dnssec_kasp_dir_api()->zone_load(ctx, zone);
if (r != DNSSEC_EOK && r != DNSSEC_NOT_FOUND) {
return r;
}
free(zone->policy);
conf_val_t val = conf_zone_get(conf(), C_DNSSEC_POLICY, zone->dname);
zone->policy = strdup(conf_str(&val));
return DNSSEC_EOK;
}
static int policy_load(void *ctx, dnssec_kasp_policy_t *policy)
static int policy_load(dnssec_kasp_policy_t *policy)
{
const uint8_t *id = (const uint8_t *)policy->name;
const size_t id_len = strlen(policy->name) + 1;
......@@ -102,120 +83,67 @@ static int policy_load(void *ctx, dnssec_kasp_policy_t *policy)
val = conf_rawid_get(conf(), C_POLICY, C_NSEC3_SALT_LIFETIME, id, id_len);
policy->nsec3_salt_lifetime = conf_int(&val);
return DNSSEC_EOK;
return KNOT_EOK;
}
static int keystore_load(void *ctx, dnssec_kasp_keystore_t *keystore)
int kdnssec_kasp_init(kdnssec_ctx_t *ctx, const char *kasp_path, size_t kasp_mapsize,
const knot_dname_t *zone_name, const char *policy_name)
{
const uint8_t *id = (const uint8_t *)keystore->name;
const size_t id_len = strlen(keystore->name) + 1;
conf_val_t val = conf_rawid_get(conf(), C_KEYSTORE, C_BACKEND, id, id_len);
switch (conf_opt(&val)) {
case KEYSTORE_BACKEND_PEM:
keystore->backend = strdup(DNSSEC_KASP_KEYSTORE_PKCS8);
break;
case KEYSTORE_BACKEND_PKCS11:
keystore->backend = strdup(DNSSEC_KASP_KEYSTORE_PKCS11);
break;
default:
return DNSSEC_EINVAL;
if (ctx == NULL || kasp_path == NULL || zone_name == NULL) {
return KNOT_EINVAL;
}
val = conf_rawid_get(conf(), C_KEYSTORE, C_CONFIG, id, id_len);
keystore->config = strdup(conf_str(&val));
return DNSSEC_EOK;
}
int kdnssec_kasp(dnssec_kasp_t **kasp, bool legacy)
{
if (legacy) {
return dnssec_kasp_init_dir(kasp);
} else {
static dnssec_kasp_store_functions_t conf_api = {
.zone_load = zone_load,
.zone_save = zone_save,
.policy_load = policy_load,
.keystore_load = keystore_load
};
conf_api.init = dnssec_kasp_dir_api()->init;
conf_api.open = dnssec_kasp_dir_api()->open;
conf_api.close = dnssec_kasp_dir_api()->close;
conf_api.base_path = dnssec_kasp_dir_api()->base_path;
return dnssec_kasp_init_custom(kasp, &conf_api);
ctx->zone = calloc(1, sizeof(*ctx->zone));
if (ctx->zone == NULL) {
return KNOT_ENOMEM;
}
}
ctx->kasp_db = kaspdb();
static int get_keystore(dnssec_kasp_t *kasp, const char *name,
dnssec_keystore_t **keystore, bool legacy)
{
dnssec_kasp_keystore_t *info = NULL;
int r = dnssec_kasp_keystore_load(kasp, name, &info);
if (r != DNSSEC_EOK) {
int r = kasp_db_open(*ctx->kasp_db);
if (r != KNOT_EOK) {
return r;
}
// Initialize keystore directory.
if (!legacy) {
// TODO: A keystore should be initialized during the zone setup/load.
r = dnssec_kasp_keystore_init(kasp, info->backend, info->config,
keystore);
if (r != DNSSEC_EOK) {
dnssec_kasp_keystore_free(info);
return r;
}
dnssec_keystore_deinit(*keystore);
r = kasp_zone_load(ctx->zone, zone_name, *ctx->kasp_db);
if (r != KNOT_EOK) {
return r;
}
r = dnssec_kasp_keystore_open(kasp, info->backend, info->config, keystore);
dnssec_kasp_keystore_free(info);
ctx->kasp_zone_path = strdup(kasp_path);
return r;
}
int kdnssec_kasp_init(kdnssec_ctx_t *ctx, const char *kasp_path, const char *zone_name)
{
if (ctx == NULL || kasp_path == NULL || zone_name == NULL) {
return KNOT_EINVAL;
}
int r = kdnssec_kasp(&ctx->kasp, ctx->legacy);
if (r != DNSSEC_EOK) {
return r;
ctx->policy = dnssec_kasp_policy_new(policy_name);
if (ctx->policy == NULL) {
return KNOT_ENOMEM;
}
r = make_dir(kasp_path, S_IRWXU | S_IRGRP | S_IXGRP, true);
r = policy_load(ctx->policy);
if (r != KNOT_EOK) {
return r;
}
r = dnssec_kasp_open(ctx->kasp, kasp_path);
if (r != DNSSEC_EOK) {
return r;
}
const uint8_t *id = (const uint8_t *)policy_name;
const size_t id_len = strlen(policy_name) + 1;
conf_val_t val = conf_rawid_get(conf(), C_KEYSTORE, C_BACKEND, id, id_len);
int backend = conf_opt(&val);
val = conf_rawid_get(conf(), C_KEYSTORE, C_CONFIG, id, id_len);
r = dnssec_kasp_zone_load(ctx->kasp, zone_name, &ctx->zone);
if (r != DNSSEC_EOK) {
r = keystore_load(conf_str(&val), backend, kasp_path, &ctx->keystore);
if (r != KNOT_EOK) {
return r;
}
// Overide policy name if provided.
if (ctx->policy_name != NULL) {
free(ctx->zone->policy);
ctx->zone->policy = strdup(ctx->policy_name);
}
return KNOT_EOK;
}
r = dnssec_kasp_policy_load(ctx->kasp, ctx->zone->policy, &ctx->policy);
if (r != DNSSEC_EOK) {
return r;
int kdnssec_ctx_commit(kdnssec_ctx_t *ctx)
{
if (ctx == NULL || ctx->kasp_zone_path == NULL) {
return KNOT_EINVAL;
}
return get_keystore(ctx->kasp, ctx->policy->keystore, &ctx->keystore,
ctx->legacy);
// do something with keytore? Probably not..
return kasp_zone_save(ctx->zone, ctx->zone->dname, *ctx->kasp_db);
}
void kdnssec_ctx_deinit(kdnssec_ctx_t *ctx)
......@@ -224,45 +152,28 @@ void kdnssec_ctx_deinit(kdnssec_ctx_t *ctx)
return;
}
free(ctx->policy_name);
dnssec_keystore_deinit(ctx->keystore);
dnssec_kasp_policy_free(ctx->policy);
dnssec_kasp_zone_free(ctx->zone);
dnssec_kasp_deinit(ctx->kasp);
kasp_zone_free(&ctx->zone);
free(ctx->kasp_zone_path);
memset(ctx, 0, sizeof(*ctx));
}
int kdnssec_ctx_init(kdnssec_ctx_t *ctx, const knot_dname_t *zone_name,
conf_val_t *policy, bool disable_legacy)
conf_val_t *policy)
{
if (ctx == NULL || zone_name == NULL) {
return KNOT_EINVAL;
}
char zone_str[KNOT_DNAME_TXT_MAXLEN + 1];
if (knot_dname_to_str(zone_str, zone_name, sizeof(zone_str)) == NULL) {
return KNOT_ENOMEM;
}
kdnssec_ctx_t new_ctx = { 0 };
if (!disable_legacy && policy->code != KNOT_EOK) {
new_ctx.legacy = true;
}
if (conf_str(policy) != NULL) {
new_ctx.policy_name = strdup(conf_str(policy));
}
conf_val_t val = conf_zone_get(conf(), C_STORAGE, zone_name);
char *storage = conf_abs_path(&val, NULL);
val = conf_zone_get(conf(), C_KASP_DB, zone_name);
char *kasp_path = conf_abs_path(&val, storage);
free(storage);
char *kasp_dir = conf_kaspdir(conf());
conf_val_t kasp_db_mapsize = conf_default_get(conf(), C_KASP_DB_MAPSIZE);
int r = kdnssec_kasp_init(&new_ctx, kasp_path, zone_str);
free(kasp_path);
int r = kdnssec_kasp_init(&new_ctx, kasp_dir, conf_int(&kasp_db_mapsize), zone_name, conf_str(policy));
free(kasp_dir);
if (r != KNOT_EOK) {
kdnssec_ctx_deinit(&new_ctx);
return r;
......
......@@ -22,6 +22,7 @@
#include <dnssec/keystore.h>
#include "knot/conf/conf.h"
#include "knot/dnssec/kasp/kasp_zone.h"
#include "libknot/dname.h"
/*!
......@@ -30,14 +31,13 @@
struct kdnssec_ctx {
time_t now;
bool legacy;
char *policy_name;
dnssec_kasp_t *kasp;
dnssec_kasp_zone_t *zone;
kasp_db_t **kasp_db;
knot_kasp_zone_t *zone;
dnssec_kasp_policy_t *policy;
dnssec_keystore_t *keystore;
char *kasp_zone_path;
uint32_t old_serial;
uint32_t new_serial;
bool rrsig_drop_existing;
......@@ -45,17 +45,13 @@ struct kdnssec_ctx {
typedef struct kdnssec_ctx kdnssec_ctx_t;
/*!
* \brief Initialize DNSSEC KASP context.
*/
int kdnssec_kasp(dnssec_kasp_t **kasp, bool legacy);
/*!
* \brief Initialize DNSSEC parameters of the DNSSEC context.
*
* No cleanup is performed on failure.
*/
int kdnssec_kasp_init(kdnssec_ctx_t *ctx, const char *kasp_path, const char *zone_name);
int kdnssec_kasp_init(kdnssec_ctx_t *ctx, const char *kasp_path, size_t kasp_mapsize,
const knot_dname_t *zone_name, const char *policy_name);
/*!
* \brief Initialize DNSSEC signing context.
......@@ -66,7 +62,12 @@ int kdnssec_kasp_init(kdnssec_ctx_t *ctx, const char *kasp_path, const char *zon
* \param disable_legacy Disable legacy detection indication.
*/
int kdnssec_ctx_init(kdnssec_ctx_t *ctx, const knot_dname_t *zone_name,
conf_val_t *policy, bool disable_legacy);
conf_val_t *policy);
/*!
* \brief Save the changes in ctx (in kasp zone).
*/
int kdnssec_ctx_commit(kdnssec_ctx_t *ctx);
/*!
* \brief Cleanup DNSSEC signing context.
......
/* Copyright (C) 2017 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 <assert.h>
#include "dnssec/random.h"
#include "libknot/libknot.h"
#include "knot/conf/conf.h"
#include "knot/common/log.h"
#include "knot/dnssec/kasp/keystate.h"
#include "knot/dnssec/key-events.h"
#include "knot/dnssec/policy.h"
#include "knot/dnssec/zone-keys.h"
#include "knot/dnssec/zone-nsec.h"
#include "knot/dnssec/zone-sign.h"
#include "knot/zone/serial.h"
static dnssec_kasp_key_t *last_key(kdnssec_ctx_t *ctx, key_state_t state)
{
assert(ctx);
assert(ctx->zone);
dnssec_kasp_key_t *match = NULL;
for (size_t i = 0; i < ctx->zone->num_keys; i++) {
dnssec_kasp_key_t *key = &ctx->zone->keys[i];
if (match == NULL || key->timing.created == 0 ||
key->timing.created >= match->timing.created) {
if (dnssec_key_get_flags(key->key) == DNSKEY_FLAGS_ZSK &&
get_key_state(key, ctx->now) == state) {
match = key;
}
}
}
assert(match == NULL || dnssec_key_get_flags(match->key) == DNSKEY_FLAGS_ZSK);
return match;
}
static bool key_present(kdnssec_ctx_t *ctx, uint16_t flag)
{
assert(ctx);
assert(ctx->zone);
for (size_t i = 0; i < ctx->zone->num_keys; i++) {
dnssec_kasp_key_t *key = &ctx->zone->keys[i];
if (dnssec_key_get_flags(key->key) == flag) {
return true;
}
}
return false;
}
static int generate_initial_key(kdnssec_ctx_t *ctx, bool ksk)
{
dnssec_kasp_key_t *key = NULL;
int r = kdnssec_generate_key(ctx, ksk, &key);
if (r != KNOT_EOK) {
return r;
}
key->timing.active = ctx->now;
key->timing.publish = ctx->now;
return KNOT_EOK;
}
typedef enum {
INVALID = 0,
PUBLISH = 1,
REPLACE,
REMOVE,
} roll_action;
inline static time_t time_max(time_t a, time_t b)
{
return ((a > b) ? a : b);
}
static roll_action next_action(kdnssec_ctx_t *ctx, time_t *next_action_time)
{
assert(next_action_time);
dnssec_kasp_key_t *kk = last_key(ctx, DNSSEC_KEY_STATE_RETIRED);
if (kk != NULL) {
if (ctx->now < kk->timing.retire) {
return KNOT_EINVAL;
}
*next_action_time = time_max(ctx->now, kk->timing.retire +
ctx->policy->propagation_delay +
ctx->policy->zone_maximal_ttl);
return REMOVE;
}
kk = last_key(ctx, DNSSEC_KEY_STATE_PUBLISHED);
if (kk != NULL) {
if (ctx->now < kk->timing.publish) {
return KNOT_EINVAL;
}
*next_action_time = time_max(ctx->now, kk->timing.publish +
ctx->policy->propagation_delay +
ctx->policy->dnskey_ttl);
return REPLACE;
}
kk = last_key(ctx, DNSSEC_KEY_STATE_ACTIVE);
if (kk != NULL) {
if (ctx->now < kk->timing.active) {
return KNOT_EINVAL;
}
*next_action_time = time_max(ctx->now, kk->timing.active +
ctx->policy->zsk_lifetime);
return PUBLISH;
}
return INVALID;
}
static int exec_new_key(kdnssec_ctx_t *ctx)
{
dnssec_kasp_key_t *new_key = NULL;
int r = kdnssec_generate_key(ctx, false, &new_key);
if (r != KNOT_EOK) {
return r;
}
//! \todo Cannot set "active" to zero, using upper bound instead.
new_key->timing.publish = ctx->now;
new_key->timing.active = UINT64_MAX;
return KNOT_EOK;
}
static int exec_new_signatures(kdnssec_ctx_t *ctx)
{
dnssec_kasp_key_t *active = last_key(ctx, DNSSEC_KEY_STATE_ACTIVE);
dnssec_kasp_key_t *rolling = last_key(ctx, DNSSEC_KEY_STATE_PUBLISHED);
if (!active || !rolling) {
return KNOT_EINVAL;
}
assert(dnssec_key_get_flags(active->key) == DNSKEY_FLAGS_ZSK);
assert(dnssec_key_get_flags(rolling->key) == DNSKEY_FLAGS_ZSK);
active->timing.retire = ctx->now;
rolling->timing.active = ctx->now;
return KNOT_EOK;
}
static int exec_remove_old_key(kdnssec_ctx_t *ctx)
{
dnssec_kasp_key_t *retired = last_key(ctx, DNSSEC_KEY_STATE_RETIRED);
if (!retired) {
return KNOT_EINVAL;
}
retired->timing.remove = ctx->now;
return kdnssec_delete_key(ctx, retired);
}
// TODO refactor next event calculation to be straightforward based on the previous event,
// and store the timing in timers-db
// problem: we need to rollover each key independently (?) but in timers we just store event time
int knot_dnssec_zsk_rollover(kdnssec_ctx_t *ctx, bool *keys_changed, time_t *next_rollover)
{
if (ctx->policy->manual) {
return KNOT_EOK;
}
int ret = KNOT_EOK;
// generate initial keys if missing
if (!ctx->policy->singe_type_signing && !key_present(ctx, DNSKEY_FLAGS_KSK)) {
ret = generate_initial_key(ctx, true);
}
if (ret == KNOT_EOK && !key_present(ctx, DNSKEY_FLAGS_ZSK)) {
ret = generate_initial_key(ctx, false);
}
if (ret == KNOT_EOK) {
*keys_changed = true;
}
if (ret != KNOT_EOK) {
return ret;
}
roll_action next = next_action(ctx, next_rollover);
if (!ctx->policy->singe_type_signing && *next_rollover <= ctx->now) {
switch (next) {
case PUBLISH:
ret = exec_new_key(ctx);
break;
case REPLACE:
ret = exec_new_signatures(ctx);
break;
case REMOVE:
ret = exec_remove_old_key(ctx);
break;
default:
ret = KNOT_EINVAL;
}
if (ret == KNOT_EOK) {
*keys_changed = true;
(void)next_action(ctx, next_rollover);
} else {
*next_rollover = time(NULL) + 10; // fail => try in 10seconds #TODO better?
}
}
if (*keys_changed) {
ret = kdnssec_ctx_commit(ctx);
}
return ret;
}
/* Copyright (C) 2017 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 <time.h>
#include "knot/dnssec/context.h"
/*!
* \brief Perform correct ZSK rollover action and plan next one.
*
* For given zone, check keys in KASP db and decide what shall be done
* according to their timers. Perform the action if they shall be done now,
* and tell the user the next time it shall be called.
*
* This function is optimized to be called from ZSK_ROLLOVER_EVENT,
* but also during zone load so that the zone gets loaded already with
* proper DNSSEC chain.
*
* \param ctx zone signing context
* \param keys_changed output if KNOT_EOK: were any keys changed ? (if so, please re-sign)
* \param next_rollover output if KNOT_EOK: tmestamp when next rollover action takes place
*
* \return KNOT_E*
*/
int knot_dnssec_zsk_rollover(kdnssec_ctx_t *ctx, bool *keys_changed, time_t *next_rollover);
......@@ -40,7 +40,7 @@ static int sign_init(const zone_contents_t *zone, int flags, kdnssec_ctx_t *ctx)
conf_val_t policy = conf_zone_get(conf(), C_DNSSEC_POLICY, zone_name);
int r = kdnssec_ctx_init(ctx, zone_name, &policy, false);
int r = kdnssec_ctx_init(ctx, zone_name, &policy);
if (r != KNOT_EOK) {
return r;
}
......@@ -66,53 +66,6 @@ static int sign_init(const zone_contents_t *zone, int flags, kdnssec_ctx_t *ctx)
return KNOT_EOK;
}
static dnssec_event_ctx_t kctx2ctx(const kdnssec_ctx_t *kctx)
{
dnssec_event_ctx_t ctx = {
.now = kctx->now,
.kasp = kctx->kasp,
.zone = kctx->zone,
.policy = kctx->policy,
.keystore = kctx->keystore
};
return ctx;
}
int knot_dnssec_sign_process_events(const kdnssec_ctx_t *kctx,
const knot_dname_t *zone_name)
{
if (kctx == NULL || zone_name == NULL) {
return KNOT_EINVAL;
}
dnssec_event_t event = { 0 };
dnssec_event_ctx_t ctx = kctx2ctx(kctx);
for (;;) {
int r = dnssec_event_get_next(&ctx, &event);
if (r != DNSSEC_EOK) {
log_zone_error(zone_name, "DNSSEC, failed to get next event (%s)",
dnssec_strerror(r));
return r;
}
if (event.type == DNSSEC_EVENT_NONE || kctx->now < event.time) {
return KNOT_EOK;
}
log_zone_info(zone_name, "DNSSEC, executing event '%s'",
dnssec_event_name(event.type));
r = dnssec_event_execute(&ctx, &event);
if (r != DNSSEC_EOK) {
log_zone_error(zone_name, "DNSSEC, failed to execute event (%s)",
dnssec_strerror(r));
return r;
}
}
}
static int sign_update_soa(const zone_contents_t *zone, changeset_t *chset,
kdnssec_ctx_t *ctx, zone_keyset_t *keyset)
{
......@@ -125,30 +78,74 @@ static int sign_update_soa(const zone_contents_t *zone, changeset_t *chset,
static uint32_t schedule_next(kdnssec_ctx_t *kctx, const zone_keyset_t *keyset,
uint32_t zone_expire)
{
// signatures refresh
uint32_t zone_refresh = zone_expire - kctx->policy->rrsig_refresh_before;
assert(zone_refresh > 0);
// DNSKEY modification
uint32_t dnskey_update = MIN(MAX(knot_get_next_zone_key_event(keyset), 0),
UINT32_MAX);
uint32_t next = MIN(zone_refresh, dnskey_update);
// zone events
return next;
}
dnssec_event_t event = { 0 };
dnssec_event_ctx_t ctx = kctx2ctx(kctx);
dnssec_event_get_next(&ctx, &event);
static int generate_salt(dnssec_binary_t *salt, uint16_t length)
{
assert(salt);
dnssec_binary_t new_salt = { 0 };
// result
if (length > 0) {
int r = dnssec_binary_alloc(&new_salt, length);
if (r != KNOT_EOK) {
return knot_error_from_libdnssec(r);
}
uint32_t next = MIN(zone_refresh, dnskey_update);
if (event.type != DNSSEC_EVENT_NONE) {
next = MIN(next, event.time);
r = dnssec_random_binary(&new_salt);
if (r != KNOT_EOK) {
dnssec_binary_free(&new_salt);
return knot_error_from_libdnssec(r);
}
}
return next;
dnssec_binary_free(salt);
*salt = new_salt;
return KNOT_EOK;
}