zone-events.c 8.78 KB
Newer Older
1
/*  Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

    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>
18

19
#include "dnssec/error.h"
20
#include "dnssec/random.h"
21
#include "libknot/libknot.h"
22
#include "knot/conf/conf.h"
23
#include "knot/common/log.h"
24
#include "knot/dnssec/key-events.h"
Jan Včelák's avatar
Jan Včelák committed
25
#include "knot/dnssec/policy.h"
26 27 28 29
#include "knot/dnssec/zone-events.h"
#include "knot/dnssec/zone-keys.h"
#include "knot/dnssec/zone-nsec.h"
#include "knot/dnssec/zone-sign.h"
30

31 32
static int sign_init(const zone_contents_t *zone, zone_sign_flags_t flags,
		     kdnssec_ctx_t *ctx, zone_sign_reschedule_t *reschedule)
33 34 35 36 37 38
{
	assert(zone);
	assert(ctx);

	const knot_dname_t *zone_name = zone->apex->owner;

39
	int r = kdnssec_ctx_init(conf(), ctx, zone_name, NULL);
Jan Včelák's avatar
Jan Včelák committed
40 41
	if (r != KNOT_EOK) {
		return r;
42 43
	}

44 45 46 47 48 49
	// perform nsec3resalt if pending

	if (reschedule->allow_nsec3resalt) {
		r = knot_dnssec_nsec3resalt(ctx, &reschedule->allow_nsec3resalt, &reschedule->next_nsec3resalt);
	}

50 51 52 53 54 55 56 57 58
	// perform key rollover if needed

	if (reschedule->allow_rollover) {
		r = knot_dnssec_key_rollover(ctx, reschedule);
	}
	if (r != KNOT_EOK) {
		return r;
	}

Jan Včelák's avatar
Jan Včelák committed
59
	// update policy based on the zone content
60

61
	update_policy_from_zone(ctx->policy, zone);
Jan Včelák's avatar
Jan Včelák committed
62 63 64 65 66

	// RRSIG handling

	ctx->rrsig_drop_existing = flags & ZONE_SIGN_DROP_SIGNATURES;

67
	return KNOT_EOK;
68 69
}

70 71
static knot_time_t schedule_next(kdnssec_ctx_t *kctx, const zone_keyset_t *keyset,
				 knot_time_t zone_expire)
72
{
73
	knot_time_t zone_refresh = knot_time_add(zone_expire, -(knot_timediff_t)kctx->policy->rrsig_refresh_before);
74 75
	assert(zone_refresh > 0);

76 77
	knot_time_t dnskey_update = knot_get_next_zone_key_event(keyset);
	knot_time_t next = knot_time_min(zone_refresh, dnskey_update);
78

79 80
	return next;
}
81

82 83 84 85
static int generate_salt(dnssec_binary_t *salt, uint16_t length)
{
	assert(salt);
	dnssec_binary_t new_salt = { 0 };
86

87 88 89 90 91
	if (length > 0) {
		int r = dnssec_binary_alloc(&new_salt, length);
		if (r != KNOT_EOK) {
			return knot_error_from_libdnssec(r);
		}
92

93 94 95 96 97
		r = dnssec_random_binary(&new_salt);
		if (r != KNOT_EOK) {
			dnssec_binary_free(&new_salt);
			return knot_error_from_libdnssec(r);
		}
98 99
	}

100 101 102 103 104 105 106 107
	dnssec_binary_free(salt);
	*salt = new_salt;

	return KNOT_EOK;
}

// TODO preserve the resalt timeout in timers-db instead of kasp_db

108
int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, bool *salt_changed, knot_time_t *when_resalt)
109 110 111 112 113 114 115 116 117 118 119 120 121
{
	int ret = KNOT_EOK;

	if (!ctx->policy->nsec3_enabled || ctx->policy->nsec3_salt_length == 0) {
		return KNOT_EOK;
	}

	if (ctx->policy->manual) {
		return KNOT_EOK;
	}

	if (ctx->zone->nsec3_salt.size != ctx->policy->nsec3_salt_length) {
		*when_resalt = ctx->now;
122
	} else if (knot_time_cmp(ctx->now, ctx->zone->nsec3_salt_created) < 0) {
123 124 125 126 127
		return KNOT_EINVAL;
	} else {
		*when_resalt = ctx->zone->nsec3_salt_created + ctx->policy->nsec3_salt_lifetime;
	}

128
	if (knot_time_cmp(*when_resalt, ctx->now) <= 0) {
129 130 131 132 133 134 135
		ret = generate_salt(&ctx->zone->nsec3_salt, ctx->policy->nsec3_salt_length);
		if (ret == KNOT_EOK) {
			ctx->zone->nsec3_salt_created = ctx->now;
			ret = kdnssec_ctx_commit(ctx);
			*salt_changed = true;
		}
		// continue to planning next resalt even if NOK
136
		*when_resalt = knot_time_add(ctx->now, ctx->policy->nsec3_salt_lifetime);
137 138 139
	}

	return ret;
140
}
141

142 143 144
int knot_dnssec_zone_sign(zone_update_t *update,
                          zone_sign_flags_t flags,
                          zone_sign_reschedule_t *reschedule)
Jan Včelák's avatar
Jan Včelák committed
145
{
146
	if (!update || !reschedule) {
Jan Včelák's avatar
Jan Včelák committed
147 148 149 150
		return KNOT_EINVAL;
	}

	int result = KNOT_ERROR;
151
	const knot_dname_t *zone_name = update->new_cont->apex->owner;
Jan Včelák's avatar
Jan Včelák committed
152 153 154 155
	kdnssec_ctx_t ctx = { 0 };
	zone_keyset_t keyset = { 0 };

	// signing pipeline
Lubos Slovak's avatar
Lubos Slovak committed
156

157
	result = sign_init(update->new_cont, flags, &ctx, reschedule);
Jan Kadlec's avatar
Jan Kadlec committed
158
	if (result != KNOT_EOK) {
Jan Včelák's avatar
Jan Včelák committed
159
		log_zone_error(zone_name, "DNSSEC, failed to initialize (%s)",
160
		               knot_strerror(result));
Jan Včelák's avatar
Jan Včelák committed
161
		goto done;
Jan Kadlec's avatar
Jan Kadlec committed
162 163
	}

Jan Včelák's avatar
Jan Včelák committed
164 165
	result = load_zone_keys(ctx.zone, ctx.keystore,
	                        ctx.policy->nsec3_enabled, ctx.now, &keyset);
166
	if (result != KNOT_EOK) {
Jan Včelák's avatar
Jan Včelák committed
167
		log_zone_error(zone_name, "DNSSEC, failed to load keys (%s)",
168
		               knot_strerror(result));
Jan Včelák's avatar
Jan Včelák committed
169
		goto done;
170 171
	}

Jan Včelák's avatar
Jan Včelák committed
172
	log_zone_info(zone_name, "DNSSEC, signing started");
173

174 175 176 177 178 179 180
	result = knot_zone_sign_update_dnskeys(update, &keyset, &ctx);
	if (result != KNOT_EOK) {
		log_zone_error(zone_name, "DNSSEC, failed to update DNSKEY records (%s)",
			       knot_strerror(result));
		goto done;
	}

181
	result = knot_zone_create_nsec_chain(update, &keyset, &ctx, false);
182
	if (result != KNOT_EOK) {
183 184
		log_zone_error(zone_name, "DNSSEC, failed to create NSEC%s chain (%s)",
		               ctx.policy->nsec3_enabled ? "3" : "",
Jan Včelák's avatar
Jan Včelák committed
185 186
		               knot_strerror(result));
		goto done;
187 188
	}

189
	knot_time_t zone_expire = 0;
190
	result = knot_zone_sign(update, &keyset, &ctx, &zone_expire);
Jan Včelák's avatar
Jan Včelák committed
191 192 193 194 195
	if (result != KNOT_EOK) {
		log_zone_error(zone_name, "DNSSEC, failed to sign zone content (%s)",
		               knot_strerror(result));
		goto done;
	}
196

Jan Včelák's avatar
Jan Včelák committed
197
	// SOA finishing
Jan Kadlec's avatar
Jan Kadlec committed
198

199 200
	if (zone_update_no_change(update) &&
	    !knot_zone_sign_soa_expired(update->new_cont, &keyset, &ctx)) {
Jan Včelák's avatar
Jan Včelák committed
201 202 203
		log_zone_info(zone_name, "DNSSEC, zone is up-to-date");
		goto done;
	}
204

205 206 207 208 209 210 211 212 213 214
	if (!(flags & ZONE_SIGN_KEEP_SERIAL)) {
		result = zone_update_increment_soa(update, conf());
		if (result == KNOT_EOK) {
			result = knot_zone_sign_soa(update, &keyset, &ctx);
		}
		if (result != KNOT_EOK) {
			log_zone_error(zone_name, "DNSSEC, failed to update SOA record (%s)",
				       knot_strerror(result));
			goto done;
		}
215 216
	}

Jan Včelák's avatar
Jan Včelák committed
217
	log_zone_info(zone_name, "DNSSEC, successfully signed");
218

Jan Včelák's avatar
Jan Včelák committed
219
done:
220
	if (result == KNOT_EOK) {
221
		reschedule->next_sign = schedule_next(&ctx, &keyset, zone_expire);
222 223
	}

Jan Včelák's avatar
Jan Včelák committed
224 225
	free_zone_keys(&keyset);
	kdnssec_ctx_deinit(&ctx);
226

Jan Včelák's avatar
Jan Včelák committed
227
	return result;
228
}
229

230
int knot_dnssec_sign_update(zone_update_t *update, zone_sign_reschedule_t *reschedule)
231
{
232
	if (update == NULL || reschedule == NULL) {
233 234 235
		return KNOT_EINVAL;
	}

Jan Včelák's avatar
Jan Včelák committed
236
	int result = KNOT_ERROR;
237
	const knot_dname_t *zone_name = update->new_cont->apex->owner;
Jan Včelák's avatar
Jan Včelák committed
238 239
	kdnssec_ctx_t ctx = { 0 };
	zone_keyset_t keyset = { 0 };
240

Jan Včelák's avatar
Jan Včelák committed
241
	// signing pipeline
242

243
	result = sign_init(update->new_cont, 0, &ctx, reschedule);
Jan Včelák's avatar
Jan Včelák committed
244 245
	if (result != KNOT_EOK) {
		log_zone_error(zone_name, "DNSSEC, failed to initialize (%s)",
246
		               knot_strerror(result));
Jan Včelák's avatar
Jan Včelák committed
247
		goto done;
248 249
	}

Jan Včelák's avatar
Jan Včelák committed
250 251 252 253 254 255 256 257
	result = load_zone_keys(ctx.zone, ctx.keystore,
	                        ctx.policy->nsec3_enabled, ctx.now, &keyset);
	if (result != KNOT_EOK) {
		log_zone_error(zone_name, "DNSSEC, failed to load keys (%s)",
		               knot_strerror(result));
		goto done;
	}

258 259
	knot_time_t expire_at = 0;
	result = knot_zone_sign_update(update, &keyset, &ctx, &expire_at);
Jan Včelák's avatar
Jan Včelák committed
260
	if (result != KNOT_EOK) {
261
		log_zone_error(zone_name, "DNSSEC, failed to sign changeset (%s)",
Jan Včelák's avatar
Jan Včelák committed
262 263
		               knot_strerror(result));
		goto done;
264 265
	}

266
	result = knot_zone_fix_nsec_chain(update, &keyset, &ctx, true);
Jan Včelák's avatar
Jan Včelák committed
267
	if (result != KNOT_EOK) {
268
		log_zone_error(zone_name, "DNSSEC, failed to fix NSEC%s chain (%s)",
269
		               ctx.policy->nsec3_enabled ? "3" : "",
Jan Včelák's avatar
Jan Včelák committed
270 271
		               knot_strerror(result));
		goto done;
272 273
	}

274 275
	bool soa_changed = (knot_soa_serial(node_rdataset(update->zone->contents->apex, KNOT_RRTYPE_SOA)) !=
			    knot_soa_serial(node_rdataset(update->new_cont->apex, KNOT_RRTYPE_SOA)));
Jan Včelák's avatar
Jan Včelák committed
276

277 278 279 280 281 282 283 284 285 286 287 288 289
	if (zone_update_no_change(update) && !soa_changed &&
	    !knot_zone_sign_soa_expired(update->new_cont, &keyset, &ctx)) {
		log_zone_info(zone_name, "DNSSEC, zone is up-to-date");
		goto done;
	}

	if (!soa_changed) {
		// incrementing SOA just of it has not been modified by the update
		result = zone_update_increment_soa(update, conf());
	}
	if (result == KNOT_EOK) {
		result = knot_zone_sign_soa(update, &keyset, &ctx);
	}
Jan Včelák's avatar
Jan Včelák committed
290 291 292 293
	if (result != KNOT_EOK) {
		log_zone_error(zone_name, "DNSSEC, failed to update SOA record (%s)",
		               knot_strerror(result));
		goto done;
294
	}
Jan Kadlec's avatar
Jan Kadlec committed
295

296
	log_zone_info(zone_name, "DNSSEC, successfully signed");
Jan Včelák's avatar
Jan Včelák committed
297

298
	// schedule next re-signing (only new signatures are made)
299 300
	reschedule->next_sign = ctx.now + ctx.policy->rrsig_lifetime - ctx.policy->rrsig_refresh_before;
	assert(reschedule->next_sign > 0);
301 302 303
	(void)expire_at; // the result of expire_at is actually unused because we computed next_sign easily
			 // we can freely reschedule dnssec event to next_sign because if it's already scheduled
			 // to earlier time, it won't get postponed
Jan Kadlec's avatar
Jan Kadlec committed
304

Jan Včelák's avatar
Jan Včelák committed
305 306 307
done:
	free_zone_keys(&keyset);
	kdnssec_ctx_deinit(&ctx);
Jan Kadlec's avatar
Jan Kadlec committed
308

309
	return KNOT_EOK;
310
}