key-events.c 12.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*  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>

19
#include "knot/common/log.h"
20 21 22 23 24 25 26 27 28 29
#include "knot/dnssec/kasp/keystate.h"
#include "knot/dnssec/key-events.h"
#include "knot/dnssec/policy.h"
#include "knot/dnssec/zone-keys.h"

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++) {
30
		knot_kasp_key_t *key = &ctx->zone->keys[i];
31 32 33 34 35 36 37
		if (dnssec_key_get_flags(key->key) == flag) {
			return true;
		}
	}
	return false;
}

38 39 40 41 42 43 44 45 46 47 48 49 50 51
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;
}

52 53 54 55 56 57 58 59 60 61 62 63 64
static knot_kasp_key_t *key_get_by_id(kdnssec_ctx_t *ctx, const char *keyid)
{
	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) {
			return key;
		}
	}
	return NULL;
}

65
static int generate_key(kdnssec_ctx_t *ctx, bool ksk, knot_time_t when_active)
66
{
67
	knot_kasp_key_t *key = NULL;
68 69 70
	int ret = kdnssec_generate_key(ctx, ksk, &key);
	if (ret != KNOT_EOK) {
		return ret;
71 72
	}

73 74
	key->timing.remove = 0;
	key->timing.retire = 0;
75
	key->timing.active  = (ksk ? 0 : when_active);
76
	key->timing.ready   = when_active;
77 78 79 80 81
	key->timing.publish = ctx->now;

	return KNOT_EOK;
}

82
static int share_or_generate_key(kdnssec_ctx_t *ctx, bool ksk, knot_time_t when_active)
83 84 85 86 87 88 89 90
{
	knot_dname_t *borrow_zone = NULL;
	char *borrow_key = NULL;

	if (!ksk) {
		return KNOT_EINVAL;
	} // for now not designed for rotating shared ZSK

Daniel Salzman's avatar
Daniel Salzman committed
91 92
	int ret = kasp_db_get_policy_last(*ctx->kasp_db, ctx->policy->string,
	                                  &borrow_zone, &borrow_key);
93 94 95 96 97
	if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
		return ret;
	}

	// if we already have the policy-last key, we have to generate new one
98
	if (ret == KNOT_ENOENT || key_id_present(ctx, borrow_key, DNSKEY_FLAGS_KSK)) {
99 100 101 102 103
		knot_kasp_key_t *key = NULL;
		ret = kdnssec_generate_key(ctx, ksk, &key);
		if (ret != KNOT_EOK) {
			return ret;
		}
104 105
		key->timing.remove = 0;
		key->timing.retire = 0;
106
		key->timing.active  = (ksk ? 0 : when_active);
107 108 109 110 111 112 113 114
		key->timing.ready   = when_active;
		key->timing.publish = ctx->now;

		ret = kdnssec_ctx_commit(ctx);
		if (ret != KNOT_EOK) {
			return ret;
		}

Daniel Salzman's avatar
Daniel Salzman committed
115 116
		ret = kasp_db_set_policy_last(*ctx->kasp_db, ctx->policy->string,
		                              borrow_key, ctx->zone->dname, key->id);
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
		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;
			}

Daniel Salzman's avatar
Daniel Salzman committed
134 135
			ret = kasp_db_get_policy_last(*ctx->kasp_db, ctx->policy->string,
			                              &borrow_zone, &borrow_key);
136 137 138 139 140
		}
	}

	if (ret == KNOT_EOK) {
		ret = kdnssec_share_key(ctx, borrow_zone, borrow_key);
141 142 143 144 145
		if (ret == KNOT_EOK) {
			knot_kasp_key_t *newkey = key_get_by_id(ctx, borrow_key);
			assert(newkey != NULL);
			newkey->timing.publish = ctx->now;
			newkey->timing.ready = when_active;
146
			newkey->timing.active = (ksk ? 0 : when_active);
147
		}
148 149 150 151 152 153
	}
	free(borrow_zone);
	free(borrow_key);
	return ret;
}

154 155 156
typedef enum {
	INVALID = 0,
	PUBLISH = 1,
157
	SUBMIT,
158 159
	REPLACE,
	REMOVE,
160 161 162 163 164
} roll_action_type;

typedef struct {
	roll_action_type type;
	bool ksk;
165
	knot_time_t time;
166
	knot_kasp_key_t *key;
167 168
} roll_action;

169
static knot_time_t zsk_publish_time(knot_time_t active_time, const kdnssec_ctx_t *ctx)
170
{
171 172
	if (active_time <= 0) {
		return 0;
173
	}
174
	return knot_time_add(active_time, ctx->policy->zsk_lifetime);
175
}
176

177
static knot_time_t zsk_active_time(knot_time_t publish_time, const kdnssec_ctx_t *ctx)
178
{
179 180
	if (publish_time <= 0) {
		return 0;
181
	}
182
	return knot_time_add(publish_time, ctx->policy->propagation_delay + ctx->policy->dnskey_ttl);
183 184
}

185
static knot_time_t zsk_remove_time(knot_time_t retire_time, const kdnssec_ctx_t *ctx)
186
{
187 188
	if (retire_time <= 0) {
		return 0;
189
	}
190
	return knot_time_add(retire_time, ctx->policy->propagation_delay + ctx->policy->zone_maximal_ttl);
191 192
}

193
static knot_time_t ksk_publish_time(knot_time_t created_time, const kdnssec_ctx_t *ctx)
Libor Peltan's avatar
Libor Peltan committed
194
{
195 196
	if (created_time <= 0 || ctx->policy->ksk_lifetime == 0) {
		return 0;
Libor Peltan's avatar
Libor Peltan committed
197
	}
198
	return knot_time_add(created_time, ctx->policy->ksk_lifetime);
Libor Peltan's avatar
Libor Peltan committed
199 200
}

201
static knot_time_t ksk_ready_time(knot_time_t publish_time, const kdnssec_ctx_t *ctx)
Libor Peltan's avatar
Libor Peltan committed
202
{
203 204
	if (publish_time <= 0) {
		return 0;
Libor Peltan's avatar
Libor Peltan committed
205
	}
206
	return knot_time_add(publish_time, ctx->policy->propagation_delay + ctx->policy->dnskey_ttl);
Libor Peltan's avatar
Libor Peltan committed
207 208
}

209
static knot_time_t ksk_sbm_max_time(knot_time_t ready_time, const kdnssec_ctx_t *ctx)
210
{
211 212
	if (ready_time <= 0 || ctx->policy->ksk_sbm_timeout == 0) {
		return 0;
213
	}
214
	return knot_time_add(ready_time, ctx->policy->ksk_sbm_timeout);
215 216
}

217
static knot_time_t ksk_remove_time(knot_time_t retire_time, const kdnssec_ctx_t *ctx)
Libor Peltan's avatar
Libor Peltan committed
218
{
219 220
	if (retire_time <= 0) {
		return 0;
Libor Peltan's avatar
Libor Peltan committed
221
	}
222 223 224 225 226
	knot_timediff_t use_ttl = ctx->policy->dnskey_ttl;
	if (ctx->policy->singe_type_signing && ctx->policy->zone_maximal_ttl > use_ttl) {
		use_ttl = ctx->policy->zone_maximal_ttl;
	}
	return knot_time_add(retire_time, ctx->policy->propagation_delay + use_ttl);
Libor Peltan's avatar
Libor Peltan committed
227 228
}

229 230 231
static roll_action next_action(kdnssec_ctx_t *ctx)
{
	roll_action res = { 0 };
232
	res.time = 0;
233 234

	bool is_zsk_published = false;
Libor Peltan's avatar
Libor Peltan committed
235
	bool is_ksk_published = false;
236 237 238 239 240 241
	for (size_t i = 0; i < ctx->zone->num_keys; i++) {
		knot_kasp_key_t *key = &ctx->zone->keys[i];
		key_state_t keystate = get_key_state(key, ctx->now);
		if (dnssec_key_get_flags(key->key) == DNSKEY_FLAGS_ZSK && (
		    keystate == DNSSEC_KEY_STATE_PUBLISHED)) {
			is_zsk_published = true;
242
		}
Libor Peltan's avatar
Libor Peltan committed
243 244 245 246
		if (dnssec_key_get_flags(key->key) == DNSKEY_FLAGS_KSK && (
		    keystate == DNSSEC_KEY_STATE_PUBLISHED || keystate == DNSSEC_KEY_STATE_READY)) {
			is_ksk_published = true;
		}
247 248
	}

249 250
	for (size_t i = 0; i < ctx->zone->num_keys; i++) {
		knot_kasp_key_t *key = &ctx->zone->keys[i];
251
		knot_time_t keytime = 0;
252
		roll_action_type restype = INVALID;
Libor Peltan's avatar
Libor Peltan committed
253 254 255 256 257 258 259 260
		bool isksk = (dnssec_key_get_flags(key->key) == DNSKEY_FLAGS_KSK);
		if (isksk) {
			switch (get_key_state(key, ctx->now)) {
			case DNSSEC_KEY_STATE_PUBLISHED:
				keytime = ksk_ready_time(key->timing.publish, ctx);
				restype = SUBMIT;
				break;
			case DNSSEC_KEY_STATE_READY:
261
				keytime = ksk_sbm_max_time(key->timing.ready, ctx);
262
				restype = REPLACE;
263
				break;
Libor Peltan's avatar
Libor Peltan committed
264 265
			case DNSSEC_KEY_STATE_ACTIVE:
				if (!is_ksk_published) {
266
					keytime = ksk_publish_time(key->timing.created, ctx);
Libor Peltan's avatar
Libor Peltan committed
267 268 269 270
					restype = PUBLISH;
				}
				break;
			case DNSSEC_KEY_STATE_RETIRED:
271 272 273
			case DNSSEC_KEY_STATE_REMOVED:
				// ad REMOVED state: normally this wouldn't happen (key in removed state is instantly deleted)
				// but if imported keys, they can be in this state
Libor Peltan's avatar
Libor Peltan committed
274 275 276 277
				keytime = ksk_remove_time(key->timing.retire, ctx);
				restype = REMOVE;
				break;
			default:
278
				continue;
Libor Peltan's avatar
Libor Peltan committed
279 280
			}
		} else {
281 282 283 284 285 286 287 288 289 290 291 292
			switch (get_key_state(key, ctx->now)) {
			case DNSSEC_KEY_STATE_PUBLISHED:
				keytime = zsk_active_time(key->timing.publish, ctx);
				restype = REPLACE;
				break;
			case DNSSEC_KEY_STATE_ACTIVE:
				if (!is_zsk_published) {
					keytime = zsk_publish_time(key->timing.active, ctx);
					restype = PUBLISH;
				}
				break;
			case DNSSEC_KEY_STATE_RETIRED:
293 294 295
			case DNSSEC_KEY_STATE_REMOVED:
				// ad REMOVED state: normally this wouldn't happen (key in removed state is instantly deleted)
				// but if imported keys, they can be in this state
296 297 298 299 300
				keytime = zsk_remove_time(key->timing.retire, ctx);
				restype = REMOVE;
				break;
			case DNSSEC_KEY_STATE_READY:
			default:
301
				continue;
302 303
			}
		}
304
		if (knot_time_cmp(keytime, res.time) < 0) {
305
			res.key = key;
Libor Peltan's avatar
Libor Peltan committed
306
			res.ksk = isksk;
307 308
			res.time = keytime;
			res.type = restype;
309 310 311
		}
	}

312
	return res;
313 314
}

Libor Peltan's avatar
Libor Peltan committed
315 316 317 318 319 320
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;
	return KNOT_EOK;
}

321
static int exec_new_signatures(kdnssec_ctx_t *ctx, knot_kasp_key_t *newkey)
322
{
Libor Peltan's avatar
Libor Peltan committed
323
	uint16_t kskflag = dnssec_key_get_flags(newkey->key);
Daniel Salzman's avatar
Daniel Salzman committed
324 325

	// A delay to avoid left-behind of behind-a-loadbalancer parent NSs
326
	// for now we use (incorrectly) ksk_sbm_check_interval, to avoid too many conf options
327
	knot_timediff_t delay = 0;
Daniel Salzman's avatar
Daniel Salzman committed
328 329 330
	if (kskflag == DNSKEY_FLAGS_KSK && ctx->policy->ksk_sbm_check_interval != 0) {
		delay = ctx->policy->ksk_sbm_check_interval;
	}
Libor Peltan's avatar
Libor Peltan committed
331

332 333
	for (size_t i = 0; i < ctx->zone->num_keys; i++) {
		knot_kasp_key_t *key = &ctx->zone->keys[i];
Libor Peltan's avatar
Libor Peltan committed
334
		if (dnssec_key_get_flags(key->key) == kskflag &&
335
		    get_key_state(key, ctx->now) == DNSSEC_KEY_STATE_ACTIVE) {
336
			key->timing.retire = knot_time_min(knot_time_add(ctx->now, delay), key->timing.retire);
337
		}
338 339
	}

Libor Peltan's avatar
Libor Peltan committed
340 341 342 343
	if (kskflag == DNSKEY_FLAGS_KSK) {
		assert(get_key_state(newkey, ctx->now) == DNSSEC_KEY_STATE_READY);
	} else {
		assert(get_key_state(newkey, ctx->now) == DNSSEC_KEY_STATE_PUBLISHED);
344
		assert(delay == 0);
345
		newkey->timing.ready = knot_time_min(knot_time_add(ctx->now, delay), newkey->timing.ready);
Libor Peltan's avatar
Libor Peltan committed
346
	}
347
	newkey->timing.active = knot_time_min(knot_time_add(ctx->now, delay), newkey->timing.active);
348 349 350 351

	return KNOT_EOK;
}

352
static int exec_remove_old_key(kdnssec_ctx_t *ctx, knot_kasp_key_t *key)
353
{
354 355
	assert(get_key_state(key, ctx->now) == DNSSEC_KEY_STATE_RETIRED ||
	       get_key_state(key, ctx->now) == DNSSEC_KEY_STATE_REMOVED);
356
	key->timing.remove = ctx->now;
357

358
	return kdnssec_delete_key(ctx, key);
359 360
}

361
int knot_dnssec_key_rollover(kdnssec_ctx_t *ctx, zone_sign_reschedule_t *reschedule)
362
{
363 364 365
	if (ctx == NULL || reschedule == NULL) {
		return KNOT_EINVAL;
	}
366 367 368
	if (ctx->policy->manual) {
		return KNOT_EOK;
	}
Libor Peltan's avatar
Libor Peltan committed
369
	int ret = KNOT_EOK;
370
	// generate initial keys if missing
Libor Peltan's avatar
Libor Peltan committed
371
	if (!key_present(ctx, DNSKEY_FLAGS_KSK)) {
372 373 374 375 376
		if (ctx->policy->ksk_shared) {
			ret = share_or_generate_key(ctx, true, ctx->now);
		} else {
			ret = generate_key(ctx, true, ctx->now);
		}
377
		reschedule->plan_ds_query = true;
378 379 380
		if (ret == KNOT_EOK) {
			reschedule->keys_changed = true;
		}
381
	}
Libor Peltan's avatar
Libor Peltan committed
382
	if (!ctx->policy->singe_type_signing && ret == KNOT_EOK && !key_present(ctx, DNSKEY_FLAGS_ZSK)) {
383
		ret = generate_key(ctx, false, ctx->now);
384 385 386
		if (ret == KNOT_EOK) {
			reschedule->keys_changed = true;
		}
387
	}
Libor Peltan's avatar
Libor Peltan committed
388
	if (ret != KNOT_EOK) {
389 390 391
		return ret;
	}

392 393
	roll_action next = next_action(ctx);

394
	reschedule->next_rollover = next.time;
395

396
	if (knot_time_cmp(reschedule->next_rollover, ctx->now) <= 0) {
397
		switch (next.type) {
398
		case PUBLISH:
399
			if (next.ksk && ctx->policy->ksk_shared) {
400
				ret = share_or_generate_key(ctx, next.ksk, 0);
401
			} else {
402
				ret = generate_key(ctx, next.ksk, 0);
403
			}
404 405 406
			if (ret == KNOT_EOK) {
				log_zone_info(ctx->zone->dname, "DNSSEC, %cSK rollover started", (next.ksk ? 'K' : 'Z'));
			}
Libor Peltan's avatar
Libor Peltan committed
407 408 409
			break;
		case SUBMIT:
			ret = submit_key(ctx, next.key);
410
			reschedule->plan_ds_query = true;
411 412
			break;
		case REPLACE:
413
			ret = exec_new_signatures(ctx, next.key);
414 415
			break;
		case REMOVE:
416
			ret = exec_remove_old_key(ctx, next.key);
417 418 419 420 421 422
			break;
		default:
			ret = KNOT_EINVAL;
		}

		if (ret == KNOT_EOK) {
423
			reschedule->keys_changed = true;
424
			next = next_action(ctx);
425
			reschedule->next_rollover = next.time;
426
		} else {
427
			log_zone_warning(ctx->zone->dname, "DNSSEC, key rollover [%d] failed (%s)", (int)next.type, knot_strerror(ret));
428
			reschedule->next_rollover = knot_time_add(knot_time(), 10); // fail => try in 10seconds #TODO better?
429 430 431
		}
	}

432
	if (reschedule->keys_changed) {
433 434
		ret = kdnssec_ctx_commit(ctx);
	}
Libor Peltan's avatar
Libor Peltan committed
435
	return ret;
436
}
Libor Peltan's avatar
Libor Peltan committed
437

438
int knot_dnssec_ksk_sbm_confirm(kdnssec_ctx_t *ctx)
Libor Peltan's avatar
Libor Peltan committed
439 440 441 442 443 444 445 446 447 448 449 450 451 452
{
	for (size_t i = 0; i < ctx->zone->num_keys; i++) {
		knot_kasp_key_t *key = &ctx->zone->keys[i];
		if (dnssec_key_get_flags(key->key) == DNSKEY_FLAGS_KSK &&
		    get_key_state(key, ctx->now) == DNSSEC_KEY_STATE_READY) {
			int ret = exec_new_signatures(ctx, key);
			if (ret == KNOT_EOK) {
				ret = kdnssec_ctx_commit(ctx);
			}
			return ret;
		}
	}
	return KNOT_ENOENT;
}
453

454
bool zone_has_key_sbm(const kdnssec_ctx_t *ctx)
455 456 457 458 459 460 461 462 463 464 465 466
{
	assert(ctx->zone);

	for (size_t i = 0; i < ctx->zone->num_keys; i++) {
		knot_kasp_key_t *key = &ctx->zone->keys[i];
		if (dnssec_key_get_flags(key->key) == DNSKEY_FLAGS_KSK &&
		    get_key_state(key, ctx->now) == DNSSEC_KEY_STATE_READY) {
			return true;
		}
	}
	return false;
}