Commit 314b1e58 authored by Libor Peltan's avatar Libor Peltan Committed by Daniel Salzman

shared ksk rollover implemented

parent 0a387227
......@@ -178,6 +178,7 @@ static const yp_item_t desc_policy[] = {
{ C_KEYSTORE, YP_TREF, YP_VREF = { C_KEYSTORE }, CONF_IO_FRLD_ZONES,
{ check_ref_dflt } },
{ C_MANUAL, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
{ C_KSK_SHARED, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
{ C_SINGLE_TYPE_SIGNING, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
{ C_ALG, YP_TOPT, YP_VOPT = { dnssec_key_algs,
DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256 },
......
......@@ -61,6 +61,7 @@
#define C_KEY "\x03""key"
#define C_KEYSTORE "\x08""keystore"
#define C_KSK_LIFETIME "\x0C""ksk-lifetime"
#define C_KSK_SHARED "\x0a""ksk-shared"
#define C_KSK_SIZE "\x08""ksk-size"
#define C_KSK_SUBMITTION_CHECK "\x14""ksk-submittion-check"
#define C_KSK_SUBMITTION_CHECK_INTERVAL "\x1D""ksk-submittion-check-interval"
......
......@@ -24,6 +24,12 @@
static void policy_load(knot_kasp_policy_t *policy, conf_val_t *id)
{
if (conf_str(id) == NULL) {
policy->string = strdup("default");
} else {
policy->string = strdup(conf_str(id));
}
conf_val_t val = conf_id_get(conf(), C_POLICY, C_MANUAL, id);
policy->manual = conf_bool(&val);
......@@ -33,6 +39,9 @@ static void policy_load(knot_kasp_policy_t *policy, conf_val_t *id)
val = conf_id_get(conf(), C_POLICY, C_ALG, id);
policy->algorithm = conf_opt(&val);
val = conf_id_get(conf(), C_POLICY, C_KSK_SHARED, id);
policy->ksk_shared = conf_bool(&val);
val = conf_id_get(conf(), C_POLICY, C_KSK_SIZE, id);
int64_t num = conf_int(&val);
policy->ksk_size = (num != YP_NIL) ? num :
......@@ -162,7 +171,10 @@ void kdnssec_ctx_deinit(kdnssec_ctx_t *ctx)
return;
}
free(ctx->policy);
if (ctx->policy != NULL) {
free(ctx->policy->string);
free(ctx->policy);
}
dnssec_keystore_deinit(ctx->keystore);
kasp_zone_free(&ctx->zone);
free(ctx->kasp_zone_path);
......
......@@ -553,3 +553,72 @@ int kasp_db_load_nsec3salt(kasp_db_t *db, const knot_dname_t *zone_name,
with_txn_end(NULL);
return ret;
}
int kasp_db_get_policy_last(kasp_db_t *db, const char *policy_string, knot_dname_t **lp_zone,
char **lp_keyid)
{
if (db == NULL || db->keys_db == NULL || policy_string == NULL ||
lp_zone == NULL || lp_keyid == NULL) {
return KNOT_EINVAL;
}
with_txn(KEYS_RO, NULL);
knot_db_val_t key = make_key(KASPDBKEY_POLICYLAST, NULL, policy_string), val = { 0 };
ret = db_api->find(txn, &key, &val, 0);
free_key(&key);
if (ret == KNOT_EOK) {
if (*(uint8_t *)val.data != KASPDBKEY_PARAMS) {
ret = KNOT_EMALF;
} else {
*lp_zone = knot_dname_copy((knot_dname_t *)(val.data + 1), NULL);
*lp_keyid = keyid_fromkey(&val);
if (*lp_zone == NULL || *lp_keyid == NULL) {
free(*lp_zone);
free(*lp_keyid);
ret = KNOT_ENOMEM;
}
}
}
with_txn_end(NULL);
return ret;
}
int kasp_db_set_policy_last(kasp_db_t *db, const char *policy_string, const char *last_lp_keyid,
const knot_dname_t *new_lp_zone, const char *new_lp_keyid)
{
if (db == NULL || db->keys_db == NULL ||
new_lp_zone == NULL || new_lp_keyid == NULL) {
return KNOT_EINVAL;
}
with_txn(KEYS_RW, NULL);
knot_db_val_t key = make_key(KASPDBKEY_POLICYLAST, NULL, policy_string), val = { 0 };
ret = db_api->find(txn, &key, &val, 0);
switch (ret) {
case KNOT_EOK:
if (*(uint8_t *)val.data != KASPDBKEY_PARAMS) {
ret = KNOT_EMALF;
} else {
char *real_last = keyid_fromkey(&val);
if (real_last == NULL) {
ret = KNOT_ENOMEM;
} else {
if (last_lp_keyid == NULL || strcmp(real_last, last_lp_keyid) != 0) {
ret = KNOT_ESEMCHECK;
}
free(real_last);
}
}
break;
case KNOT_ENOENT:
ret = KNOT_EOK;
break;
}
if (ret == KNOT_EOK) {
val = make_key(KASPDBKEY_PARAMS, new_lp_zone, new_lp_keyid);
ret = db_api->insert(txn, &key, &val, 0);
free(val.data);
}
free(key.data);
with_txn_end(NULL);
return ret;
}
......@@ -116,7 +116,12 @@ int kasp_db_delete_all(kasp_db_t *db, const knot_dname_t *zone_name);
int kasp_db_add_key(kasp_db_t *db, const knot_dname_t *zone_name, const key_params_t *params);
/*!
TODO
* \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*
*/
......@@ -147,3 +152,33 @@ int kasp_db_store_nsec3salt(kasp_db_t *db, const knot_dname_t *zone_name,
*/
int kasp_db_load_nsec3salt(kasp_db_t *db, const knot_dname_t *zone_name,
dnssec_binary_t *nsec3salt, time_t *salt_created);
/*!
* \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
*
* \return KNOT_E*
*/
int kasp_db_get_policy_last(kasp_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(kasp_db_t *db, const char *policy_string, const char *last_lp_keyid,
const knot_dname_t *new_lp_zone, const char *new_lp_keyid);
......@@ -59,6 +59,7 @@ typedef struct {
*/
typedef struct {
bool manual;
char *string;
// DNSKEY
dnssec_key_algorithm_t algorithm;
uint16_t ksk_size;
......@@ -66,6 +67,7 @@ typedef struct {
uint32_t dnskey_ttl;
uint32_t zsk_lifetime;
uint32_t ksk_lifetime;
bool ksk_shared;
bool singe_type_signing;
// RRSIG
uint32_t rrsig_lifetime;
......
......@@ -45,7 +45,21 @@ static bool key_present(kdnssec_ctx_t *ctx, uint16_t flag)
return false;
}
static int generate_initial_key(kdnssec_ctx_t *ctx, bool ksk)
static bool key_id_present(kdnssec_ctx_t *ctx, const char *keyid, uint16_t flag)
{
assert(ctx);
assert(ctx->zone);
for (size_t i = 0; i < ctx->zone->num_keys; i++) {
knot_kasp_key_t *key = &ctx->zone->keys[i];
if (strcmp(keyid, key->id) == 0 &&
dnssec_key_get_flags(key->key) == flag) {
return true;
}
}
return false;
}
static int generate_key(kdnssec_ctx_t *ctx, bool ksk, time_t when_active)
{
knot_kasp_key_t *key = NULL;
int r = kdnssec_generate_key(ctx, ksk, &key);
......@@ -53,13 +67,73 @@ static int generate_initial_key(kdnssec_ctx_t *ctx, bool ksk)
return r;
}
key->timing.active = ctx->now;
key->timing.ready = ctx->now;
key->timing.active = when_active;
key->timing.ready = when_active;
key->timing.publish = ctx->now;
return KNOT_EOK;
}
static int share_or_generate_key(kdnssec_ctx_t *ctx, bool ksk, time_t when_active)
{
knot_dname_t *borrow_zone = NULL;
char *borrow_key = NULL;
if (!ksk) {
return KNOT_EINVAL;
} // for now not designed for rotating shared ZSK
int ret = kasp_db_get_policy_last(*ctx->kasp_db, ctx->policy->string, &borrow_zone, &borrow_key);
if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
return ret;
}
// if we already have the policy-last key, we have to generate new one
if (ret == KNOT_ENOENT || key_id_present(ctx, borrow_key, ksk ? DNSKEY_FLAGS_KSK : DNSKEY_FLAGS_ZSK)) {
knot_kasp_key_t *key = NULL;
ret = kdnssec_generate_key(ctx, ksk, &key);
if (ret != KNOT_EOK) {
return ret;
}
key->timing.active = when_active;
key->timing.ready = when_active;
key->timing.publish = ctx->now;
ret = kdnssec_ctx_commit(ctx);
if (ret != KNOT_EOK) {
return ret;
}
ret = kasp_db_set_policy_last(*ctx->kasp_db, ctx->policy->string, borrow_key, ctx->zone->dname, key->id);
free(borrow_zone);
free(borrow_key);
borrow_zone = NULL;
borrow_key = NULL;
if (ret != KNOT_ESEMCHECK) {
// all ok, we generated new kay and updated policy-last
return ret;
} else {
// another zone updated policy-last key in the meantime
ret = kdnssec_delete_key(ctx, key);
if (ret == KNOT_EOK) {
ret = kdnssec_ctx_commit(ctx);
}
if (ret != KNOT_EOK) {
return ret;
}
ret = kasp_db_get_policy_last(*ctx->kasp_db, ctx->policy->string, &borrow_zone, &borrow_key);
}
}
if (ret == KNOT_EOK) {
ret = kdnssec_share_key(ctx, borrow_zone, borrow_key);
}
free(borrow_zone);
free(borrow_key);
return ret;
}
typedef enum {
INVALID = 0,
PUBLISH = 1,
......@@ -226,22 +300,6 @@ static roll_action next_action(kdnssec_ctx_t *ctx)
return res;
}
static int exec_new_key(kdnssec_ctx_t *ctx, bool ksk)
{
knot_kasp_key_t *new_key = NULL;
int r = kdnssec_generate_key(ctx, ksk, &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.ready = TIME_INFINITY;
new_key->timing.active = TIME_INFINITY;
return KNOT_EOK;
}
static int submit_key(kdnssec_ctx_t *ctx, knot_kasp_key_t *newkey) {
assert(get_key_state(newkey, ctx->now) == DNSSEC_KEY_STATE_PUBLISHED);
newkey->timing.ready = ctx->now;
......@@ -288,10 +346,14 @@ int knot_dnssec_key_rollover(kdnssec_ctx_t *ctx, zone_t *zone, bool *keys_change
// generate initial keys if missing
if (!ctx->policy->singe_type_signing && !key_present(ctx, DNSKEY_FLAGS_KSK)) {
ret = generate_initial_key(ctx, true);
if (ctx->policy->ksk_shared) {
ret = share_or_generate_key(ctx, true, ctx->now);
} else {
ret = generate_key(ctx, true, ctx->now);
}
}
if ((ret == KNOT_EOK || ret == KNOT_ESEMCHECK) && !key_present(ctx, DNSKEY_FLAGS_ZSK)) {
ret = generate_initial_key(ctx, false);
ret = generate_key(ctx, false, ctx->now);
}
if (ret == KNOT_EOK) {
*keys_changed = true;
......@@ -308,7 +370,11 @@ int knot_dnssec_key_rollover(kdnssec_ctx_t *ctx, zone_t *zone, bool *keys_change
if (!ctx->policy->singe_type_signing && *next_rollover <= ctx->now) {
switch (next.type) {
case PUBLISH:
ret = exec_new_key(ctx, next.ksk);
if (next.ksk && ctx->policy->ksk_shared) {
ret = share_or_generate_key(ctx, next.ksk, TIME_INFINITY);
} else {
ret = generate_key(ctx, next.ksk, TIME_INFINITY);
}
break;
case SUBMIT:
ret = submit_key(ctx, next.key);
......
......@@ -106,6 +106,31 @@ int kdnssec_generate_key(kdnssec_ctx_t *ctx, bool ksk, knot_kasp_key_t **key_ptr
return KNOT_EOK;
}
int kdnssec_share_key(kdnssec_ctx_t *ctx, const knot_dname_t *from_zone, const char *key_id)
{
knot_dname_t *to_zone = knot_dname_copy(ctx->zone->dname, NULL);
if (to_zone == NULL) {
return KNOT_ENOMEM;
}
int ret = kdnssec_ctx_commit(ctx);
if (ret != KNOT_EOK) {
free(to_zone);
return ret;
}
ret = kasp_db_share_key(*ctx->kasp_db, from_zone, ctx->zone->dname, key_id);
if (ret != KNOT_EOK) {
free(to_zone);
return ret;
}
kasp_zone_clear(ctx->zone);
ret = kasp_zone_load(ctx->zone, to_zone, *ctx->kasp_db);
free(to_zone);
return ret;
}
int kdnssec_delete_key(kdnssec_ctx_t *ctx, knot_kasp_key_t *key_ptr)
{
assert(ctx);
......
......@@ -84,6 +84,17 @@ uint16_t dnskey_flags(bool is_ksk);
*/
int kdnssec_generate_key(kdnssec_ctx_t *ctx, bool ksk, knot_kasp_key_t **key_ptr);
/*!
* \brief Take a key from another zone (copying info, sharing privkey).
*
* \param ctx kasp context
* \param from_zone name of the zone to take from
* \param key_id ID of the key to take
*
* \return KNOT_E*
*/
int kdnssec_share_key(kdnssec_ctx_t *ctx, const knot_dname_t *from_zone, const char *key_id);
/*!
* \brief Remove key from zone.
*
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment