zone-sign.c 33 KB
Newer Older
1
/*  Copyright (C) 2018 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 18

    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 <sys/types.h>
19

20 21 22 23
#include "libdnssec/error.h"
#include "libdnssec/key.h"
#include "libdnssec/keytag.h"
#include "libdnssec/sign.h"
24
#include "knot/dnssec/key-events.h"
25
#include "knot/dnssec/rrset-sign.h"
26
#include "knot/dnssec/zone-sign.h"
27
#include "libknot/libknot.h"
28
#include "contrib/dynarray.h"
29
#include "contrib/macros.h"
30
#include "contrib/wire_ctx.h"
31

32
typedef struct {
33 34 35 36
	node_t n;
	uint16_t type;
} type_node_t;

37
typedef struct {
38 39 40 41 42
	knot_dname_t *dname;
	knot_dname_t *hashed_dname;
	list_t *type_list;
} signed_info_t;

43 44
/*- private API - common functions -------------------------------------------*/

45 46 47 48 49 50 51
/*!
 * \brief Initializes RR set and set owner and rclass from template RR set.
 */
static knot_rrset_t rrset_init_from(const knot_rrset_t *src, uint16_t type)
{
	assert(src);
	knot_rrset_t rrset;
52
	knot_rrset_init(&rrset, src->owner, type, src->rclass, src->ttl);
53 54 55
	return rrset;
}

56 57 58
/*!
 * \brief Create empty RRSIG RR set for a given RR set to be covered.
 */
59
static knot_rrset_t create_empty_rrsigs_for(const knot_rrset_t *covered)
60
{
61
	assert(!knot_rrset_empty(covered));
62 63 64
	return rrset_init_from(covered, KNOT_RRTYPE_RRSIG);
}

65 66 67 68 69 70 71 72 73 74 75 76 77 78
static bool apex_rr_changed(const zone_node_t *old_apex,
                            const zone_node_t *new_apex,
                            uint16_t type)
{
	assert(old_apex);
	assert(new_apex);
	knot_rrset_t old_rr = node_rrset(old_apex, type);
	knot_rrset_t new_rr = node_rrset(new_apex, type);

	return !knot_rrset_equal(&old_rr, &new_rr, KNOT_RRSET_COMPARE_WHOLE);
}

static bool apex_dnssec_changed(zone_update_t *update)
{
79 80
	if (update->zone->contents == NULL || update->new_cont == NULL) {
		return false;
81
	}
82 83 84 85
	return apex_rr_changed(update->zone->contents->apex,
			       update->new_cont->apex, KNOT_RRTYPE_DNSKEY) ||
	       apex_rr_changed(update->zone->contents->apex,
			       update->new_cont->apex, KNOT_RRTYPE_NSEC3PARAM);
86 87
}

88 89 90 91 92 93 94 95 96 97 98 99 100
/*- private API - signing of in-zone nodes -----------------------------------*/

/*!
 * \brief Check if there is a valid signature for a given RR set and key.
 *
 * \param covered  RR set with covered records.
 * \param rrsigs   RR set with RRSIGs.
 * \param key      Signing key.
 * \param ctx      Signing context.
 * \param policy   DNSSEC policy.
 *
 * \return The signature exists and is valid.
 */
101 102
static bool valid_signature_exists(const knot_rrset_t *covered,
				   const knot_rrset_t *rrsigs,
103 104
				   const dnssec_key_t *key,
				   dnssec_sign_ctx_t *ctx,
105
				   const kdnssec_ctx_t *dnssec_ctx)
106
{
107 108
	assert(key);

109
	if (knot_rrset_empty(rrsigs)) {
110
		return false;
111
	}
112

113
	uint16_t rrsigs_rdata_count = rrsigs->rrs.count;
114
	for (uint16_t i = 0; i < rrsigs_rdata_count; i++) {
115 116 117 118 119
		uint16_t rr_keytag = knot_rrsig_key_tag(&rrsigs->rrs, i);
		uint16_t rr_covered = knot_rrsig_type_covered(&rrsigs->rrs, i);

		uint16_t keytag = dnssec_key_get_keytag(key);
		if (rr_keytag != keytag || rr_covered != covered->type) {
120
			continue;
121
		}
122

123 124 125 126
		if (knot_check_signature(covered, rrsigs, i, key, ctx,
		                         dnssec_ctx) == KNOT_EOK) {
			return true;
		}
127
	}
128

129 130
	return false;
}
131

132
/*!
133
 * \brief Check if key can be used to sign given RR.
134
 *
135 136
 * \param key      Zone key.
 * \param covered  RR to be checked.
137 138 139
 *
 * \return The RR should be signed.
 */
140
static bool use_key(const zone_key_t *key, const knot_rrset_t *covered)
141 142
{
	assert(key);
143
	assert(covered);
144

145
	if (!key->is_active) {
146 147 148
		return false;
	}

149 150 151 152
	// this may be a problem with offline KSK
	bool cds_sign_by_ksk = true;

	assert(key->is_zsk || key->is_ksk);
153 154
	bool is_apex = knot_dname_is_equal(covered->owner,
	                                   dnssec_key_get_dname(key->key));
155 156 157
	if (!is_apex) {
		return key->is_zsk;
	}
158

159 160 161 162 163 164 165 166 167
	switch (covered->type) {
	case KNOT_RRTYPE_DNSKEY:
		return key->is_ksk;
	case KNOT_RRTYPE_CDS:
	case KNOT_RRTYPE_CDNSKEY:
		return (cds_sign_by_ksk ? key->is_ksk : key->is_zsk);
	default:
		return key->is_zsk;
	}
168 169
}

170
/*!
171 172 173 174 175 176 177 178
 * \brief Check if valid signature exist for all keys for a given RR set.
 *
 * \param covered    RR set with covered records.
 * \param rrsigs     RR set with RRSIGs.
 * \param zone_keys  Zone keys.
 * \param policy     DNSSEC policy.
 *
 * \return Valid signature exists for every key.
179
 */
180
static bool all_signatures_exist(const knot_rrset_t *covered,
Lubos Slovak's avatar
Lubos Slovak committed
181
                                 const knot_rrset_t *rrsigs,
182
                                 const zone_keyset_t *zone_keys,
183
                                 const kdnssec_ctx_t *dnssec_ctx)
184
{
185
	assert(!knot_rrset_empty(covered));
186 187
	assert(zone_keys);

188
	for (int i = 0; i < zone_keys->count; i++) {
189
		zone_key_t *key = &zone_keys->keys[i];
190
		if (!use_key(key, covered)) {
191
			continue;
192
		}
193

194
		if (!valid_signature_exists(covered, rrsigs, key->key,
195
		                            key->ctx, dnssec_ctx)) {
Lubos Slovak's avatar
Lubos Slovak committed
196
			return false;
197 198 199
		}
	}

Lubos Slovak's avatar
Lubos Slovak committed
200
	return true;
201
}
202

203
/*!
204
 * \brief Get zone key for given RRSIG (checks key tag only).
205
 *
206 207 208 209
 * \param rrsigs  RR set with RRSIGs.
 * \param pos     Number of RR in RR set.
 * \param keys    Zone keys.
 *
210
 * \return Dynarray of such keys.
211
 */
212
static keyptr_dynarray_t get_matching_zone_keys(const knot_rrset_t *rrsigs,
213
                                                     size_t pos, const zone_keyset_t *keys)
214
{
215 216 217
	assert(rrsigs && rrsigs->type == KNOT_RRTYPE_RRSIG);
	assert(keys);

218
	uint16_t keytag = knot_rrsig_key_tag(&rrsigs->rrs, pos);
219

220
	return get_zone_keys(keys, keytag);
221 222
}

223 224 225 226 227 228 229 230
/*!
 * \brief Note earliest expiration of a signature.
 *
 * \param rrsigs      RR set with RRSIGs.
 * \param pos         Position of RR in rrsigs.
 * \param expires_at  Current earliest expiration, will be updated.
 */
static void note_earliest_expiration(const knot_rrset_t *rrsigs, size_t pos,
231
                                     knot_time_t *expires_at)
Jan Kadlec's avatar
Jan Kadlec committed
232
{
233 234 235
	assert(rrsigs);
	assert(expires_at);

236 237 238
	uint32_t curr_rdata = knot_rrsig_sig_expiration(&rrsigs->rrs, pos);
	knot_time_t current = knot_time_from_u32(curr_rdata);
	*expires_at = knot_time_min(current, *expires_at);
Jan Kadlec's avatar
Jan Kadlec committed
239 240
}

241 242 243
/*!
 * \brief Add expired or invalid RRSIGs into the changeset for removal.
 *
244 245 246 247 248 249
 * \param covered     RR set with covered records.
 * \param rrsigs      RR set with RRSIGs.
 * \param zone_keys   Zone keys.
 * \param policy      DNSSEC policy.
 * \param changeset   Changeset to be updated.
 * \param expires_at  Earliest RRSIG expiration.
250 251 252
 *
 * \return Error code, KNOT_EOK if successful.
 */
253
static int remove_expired_rrsigs(const knot_rrset_t *covered,
Jan Kadlec's avatar
Jan Kadlec committed
254
                                 const knot_rrset_t *rrsigs,
255
                                 const zone_keyset_t *zone_keys,
256
                                 const kdnssec_ctx_t *dnssec_ctx,
257
                                 changeset_t *changeset,
258
                                 knot_time_t *expires_at)
259
{
260 261
	assert(changeset);

262
	if (knot_rrset_empty(rrsigs)) {
263
		return KNOT_EOK;
264
	}
265 266

	assert(rrsigs->type == KNOT_RRTYPE_RRSIG);
267

268 269
	knot_rrset_t to_remove;
	knot_rrset_init_empty(&to_remove);
270
	int result = KNOT_EOK;
271

272
	knot_rrset_t synth_rrsig = rrset_init_from(rrsigs, KNOT_RRTYPE_RRSIG);
273
	result = knot_synth_rrsig(covered->type, &rrsigs->rrs, &synth_rrsig.rrs, NULL);
274
	if (result != KNOT_EOK) {
275 276 277 278
		if (result != KNOT_ENOENT) {
			return result;
		}
		return KNOT_EOK;
279 280
	}

281
	uint16_t rrsig_rdata_count = synth_rrsig.rrs.count;
282
	for (uint16_t i = 0; i < rrsig_rdata_count; i++) {
283 284
		struct keyptr_dynarray keys = get_matching_zone_keys(&synth_rrsig, i, zone_keys);
		int endloop = 0; // 1 - continue; 2 - break
285

286
		dynarray_foreach(keyptr, zone_key_t *, key, keys) {
287
			if (!(*key)->is_active) {
288 289
				continue;
			}
290
			result = knot_check_signature(covered, &synth_rrsig, i,
291
			                              (*key)->key, (*key)->ctx, dnssec_ctx);
292
			if (result == KNOT_EOK) {
293
				// valid signature
294
				note_earliest_expiration(&synth_rrsig, i, expires_at);
295
				endloop = 1;
296
				break;
297 298
			} else if (result != DNSSEC_INVALID_SIGNATURE) {
				endloop = 2;
299
				break;
300
			}
301 302
		}
		keyptr_dynarray_free(&keys);
303

304 305 306 307
		if (endloop == 2) {
			break;
		} else if (endloop == 1) {
			continue;
308
		}
309

310
		if (knot_rrset_empty(&to_remove)) {
311
			to_remove = create_empty_rrsigs_for(&synth_rrsig);
312
		}
313

314
		knot_rdata_t *rr_rem = knot_rdataset_at(&synth_rrsig.rrs, i);
315
		result = knot_rdataset_add(&to_remove.rrs, rr_rem, NULL);
316
		if (result != KNOT_EOK) {
317
			break;
318
		}
319
	}
320

321
	if (!knot_rrset_empty(&to_remove) && result == KNOT_EOK) {
322
		result = changeset_add_removal(changeset, &to_remove, 0);
323 324
	}

325
	knot_rdataset_clear(&synth_rrsig.rrs, NULL);
326
	knot_rdataset_clear(&to_remove.rrs, NULL);
327

328
	return result;
329 330
}

331 332 333
/*!
 * \brief Add missing RRSIGs into the changeset for adding.
 *
334 335 336 337 338
 * \param covered     RR set with covered records.
 * \param rrsigs      RR set with RRSIGs.
 * \param zone_keys   Zone keys.
 * \param dnssec_ctx  DNSSEC signing context
 * \param changeset   Changeset to be updated.
339 340 341
 *
 * \return Error code, KNOT_EOK if successful.
 */
342 343
static int add_missing_rrsigs(const knot_rrset_t *covered,
                              const knot_rrset_t *rrsigs,
344
                              const zone_keyset_t *zone_keys,
345
                              const kdnssec_ctx_t *dnssec_ctx,
346
                              changeset_t *changeset)
347
{
348
	assert(!knot_rrset_empty(covered));
349 350
	assert(zone_keys);
	assert(changeset);
351 352

	int result = KNOT_EOK;
353 354
	knot_rrset_t to_add;
	knot_rrset_init_empty(&to_add);
355

356
	for (int i = 0; i < zone_keys->count; i++) {
357
		const zone_key_t *key = &zone_keys->keys[i];
358
		if (!use_key(key, covered)) {
359
			continue;
360
		}
361

362
		if (valid_signature_exists(covered, rrsigs, key->key, key->ctx, dnssec_ctx)) {
363
			continue;
364
		}
365

366
		if (knot_rrset_empty(&to_add)) {
367 368 369
			to_add = create_empty_rrsigs_for(covered);
		}

370
		result = knot_sign_rrset(&to_add, covered, key->key, key->ctx, dnssec_ctx, NULL);
371
		if (result != KNOT_EOK) {
372
			break;
373
		}
374 375
	}

376
	if (!knot_rrset_empty(&to_add) && result == KNOT_EOK) {
377
		result = changeset_add_addition(changeset, &to_add, 0);
378 379
	}

380
	knot_rdataset_clear(&to_add.rrs, NULL);
381

382
	return result;
383 384
}

385 386 387 388 389 390 391 392
/*!
 * \brief Add all RRSIGs into the changeset for removal.
 *
 * \param covered    RR set with covered records.
 * \param changeset  Changeset to be updated.
 *
 * \return Error code, KNOT_EOK if successful.
 */
393
static int remove_rrset_rrsigs(const knot_dname_t *owner, uint16_t type,
394
                               const knot_rrset_t *rrsigs,
395
                               changeset_t *changeset)
396
{
397
	assert(owner);
398
	assert(changeset);
399 400
	knot_rrset_t synth_rrsig;
	knot_rrset_init(&synth_rrsig, (knot_dname_t *)owner,
401
	                KNOT_RRTYPE_RRSIG, rrsigs->rclass, rrsigs->ttl);
402
	int ret = knot_synth_rrsig(type, &rrsigs->rrs, &synth_rrsig.rrs, NULL);
403
	if (ret != KNOT_EOK) {
404 405 406 407
		if (ret != KNOT_ENOENT) {
			return ret;
		}
		return KNOT_EOK;
408 409
	}

410
	ret = changeset_add_removal(changeset, &synth_rrsig, 0);
411
	knot_rdataset_clear(&synth_rrsig.rrs, NULL);
412 413

	return ret;
414 415
}

416 417 418 419 420 421 422 423 424 425 426
/*!
 * \brief Drop all existing and create new RRSIGs for covered records.
 *
 * \param covered    RR set with covered records.
 * \param zone_keys  Zone keys.
 * \param policy     DNSSEC policy.
 * \param changeset  Changeset to be updated.
 *
 * \return Error code, KNOT_EOK if successful.
 */
static int force_resign_rrset(const knot_rrset_t *covered,
427
                              const knot_rrset_t *rrsigs,
428
                              const zone_keyset_t *zone_keys,
429
                              const kdnssec_ctx_t *dnssec_ctx,
430
                              changeset_t *changeset)
431
{
432
	assert(!knot_rrset_empty(covered));
433

434
	if (!knot_rrset_empty(rrsigs)) {
435 436
		int result = remove_rrset_rrsigs(covered->owner, covered->type,
		                                 rrsigs, changeset);
437 438
		if (result != KNOT_EOK) {
			return result;
439 440 441
		}
	}

442
	return add_missing_rrsigs(covered, NULL, zone_keys, dnssec_ctx, changeset);
443 444
}

445 446 447
/*!
 * \brief Drop all expired and create new RRSIGs for covered records.
 *
448 449 450 451 452
 * \param covered     RR set with covered records.
 * \param zone_keys   Zone keys.
 * \param policy      DNSSEC policy.
 * \param changeset   Changeset to be updated.
 * \param expires_at  Current earliest expiration, will be updated.
453 454 455 456
 *
 * \return Error code, KNOT_EOK if successful.
 */
static int resign_rrset(const knot_rrset_t *covered,
457
                        const knot_rrset_t *rrsigs,
458
                        const zone_keyset_t *zone_keys,
459
                        const kdnssec_ctx_t *dnssec_ctx,
460
                        changeset_t *changeset,
461
                        knot_time_t *expires_at)
462
{
463
	assert(!knot_rrset_empty(covered));
464

Jan Kadlec's avatar
Jan Kadlec committed
465
	// TODO this function creates some signatures twice (for checking)
466
	int result = remove_expired_rrsigs(covered, rrsigs, zone_keys,
467
	                                   dnssec_ctx, changeset, expires_at);
468 469 470 471
	if (result != KNOT_EOK) {
		return result;
	}

472
	return add_missing_rrsigs(covered, rrsigs, zone_keys, dnssec_ctx,
473
	                          changeset);
474 475
}

476
static int remove_standalone_rrsigs(const zone_node_t *node,
477
                                    const knot_rrset_t *rrsigs,
478
                                    changeset_t *changeset)
479 480 481 482 483
{
	if (rrsigs == NULL) {
		return KNOT_EOK;
	}

484
	uint16_t rrsigs_rdata_count = rrsigs->rrs.count;
485
	for (uint16_t i = 0; i < rrsigs_rdata_count; ++i) {
486
		uint16_t type_covered = knot_rrsig_type_covered(&rrsigs->rrs, i);
487
		if (!node_rrtype_exists(node, type_covered)) {
488 489
			knot_rrset_t to_remove;
			knot_rrset_init(&to_remove, rrsigs->owner, rrsigs->type,
490
			                rrsigs->rclass, rrsigs->ttl);
491
			knot_rdata_t *rr_rem = knot_rdataset_at(&rrsigs->rrs, i);
492
			int ret = knot_rdataset_add(&to_remove.rrs, rr_rem, NULL);
493 494 495
			if (ret != KNOT_EOK) {
				return ret;
			}
496
			ret = changeset_add_removal(changeset, &to_remove, 0);
497
			knot_rdataset_clear(&to_remove.rrs, NULL);
498 499 500 501 502 503 504 505 506
			if (ret != KNOT_EOK) {
				return ret;
			}
		}
	}

	return KNOT_EOK;
}

507 508 509
/*!
 * \brief Update RRSIGs in a given node by updating changeset.
 *
510 511 512 513 514
 * \param node        Node to be signed.
 * \param zone_keys   Zone keys.
 * \param policy      DNSSEC policy.
 * \param changeset   Changeset to be updated.
 * \param expires_at  Current earliest expiration, will be updated.
515 516 517
 *
 * \return Error code, KNOT_EOK if successful.
 */
518
static int sign_node_rrsets(const zone_node_t *node,
519
                            const zone_keyset_t *zone_keys,
520
                            const kdnssec_ctx_t *dnssec_ctx,
521
                            changeset_t *changeset,
522
                            knot_time_t *expires_at)
523
{
524
	assert(node);
525
	assert(dnssec_ctx);
526 527

	int result = KNOT_EOK;
528
	knot_rrset_t rrsigs = node_rrset(node, KNOT_RRTYPE_RRSIG);
529

530
	for (int i = 0; i < node->rrset_count; i++) {
531
		knot_rrset_t rrset = node_rrset_at(node, i);
532
		if (rrset.type == KNOT_RRTYPE_RRSIG) {
533 534
			continue;
		}
535 536

		if (!knot_zone_sign_rr_should_be_signed(node, &rrset)) {
Lubos Slovak's avatar
Lubos Slovak committed
537 538 539
			continue;
		}

540 541 542
		if (dnssec_ctx->rrsig_drop_existing) {
			result = force_resign_rrset(&rrset, &rrsigs, zone_keys,
			                            dnssec_ctx, changeset);
543
		} else {
544 545
			result = resign_rrset(&rrset, &rrsigs, zone_keys,
			                      dnssec_ctx, changeset, expires_at);
546
		}
547

548
		if (result != KNOT_EOK) {
549
			return result;
550
		}
551 552
	}

553
	return remove_standalone_rrsigs(node, &rrsigs, changeset);
554
}
555

556 557 558
/*!
 * \brief Struct to carry data for 'sign_data' callback function.
 */
Jan Kadlec's avatar
Jan Kadlec committed
559
typedef struct node_sign_args {
560
	const zone_keyset_t *zone_keys;
561
	const kdnssec_ctx_t *dnssec_ctx;
562
	changeset_t *changeset;
563
	knot_time_t expires_at;
Jan Kadlec's avatar
Jan Kadlec committed
564 565
} node_sign_args_t;

566 567 568 569 570 571
/*!
 * \brief Sign node (callback function).
 *
 * \param node  Node to be signed.
 * \param data  Callback data, node_sign_args_t.
 */
572
static int sign_node(zone_node_t **node, void *data)
573
{
574
	assert(node && *node);
Jan Kadlec's avatar
Jan Kadlec committed
575
	assert(data);
576

577
	node_sign_args_t *args = (node_sign_args_t *)data;
578

Jan Kadlec's avatar
Jan Kadlec committed
579
	if ((*node)->rrset_count == 0) {
580
		return KNOT_EOK;
Jan Kadlec's avatar
Jan Kadlec committed
581 582
	}

583
	if ((*node)->flags & NODE_FLAGS_NONAUTH) {
584
		return KNOT_EOK;
Jan Kadlec's avatar
Jan Kadlec committed
585
	}
586

587
	int result = sign_node_rrsets(*node, args->zone_keys, args->dnssec_ctx,
588
	                              args->changeset, &args->expires_at);
589
	(*node)->flags &= ~NODE_FLAGS_REMOVED_NSEC;
590 591

	return result;
592 593
}

594 595 596
/*!
 * \brief Update RRSIGs in a given zone tree by updating changeset.
 *
Jan Kadlec's avatar
Jan Kadlec committed
597 598 599 600 601
 * \param tree        Zone tree to be signed.
 * \param zone_keys   Zone keys.
 * \param policy      DNSSEC policy.
 * \param changeset   Changeset to be updated.
 * \param expires_at  Expiration time of the oldest signature in zone.
602 603 604
 *
 * \return Error code, KNOT_EOK if successful.
 */
605
static int zone_tree_sign(zone_tree_t *tree,
606
                          const zone_keyset_t *zone_keys,
607
                          const kdnssec_ctx_t *dnssec_ctx,
608
                          changeset_t *changeset,
609
                          knot_time_t *expires_at)
610
{
611
	assert(zone_keys);
612
	assert(dnssec_ctx);
613
	assert(changeset);
614

615 616
	node_sign_args_t args = {
		.zone_keys = zone_keys,
617
		.dnssec_ctx = dnssec_ctx,
618
		.changeset = changeset,
619
		.expires_at = knot_time_add(dnssec_ctx->now, dnssec_ctx->policy->rrsig_lifetime),
620 621
	};

622
	int result = zone_tree_apply(tree, sign_node, &args);
623 624 625
	*expires_at = args.expires_at;

	return result;
626 627
}

628 629 630
/*- private API - signing of NSEC(3) in changeset ----------------------------*/

/*!
631
 * \brief Struct to carry data for changeset signing callback functions.
632
 */
633
typedef struct {
634
	const zone_contents_t *zone;
635
	const zone_keyset_t *zone_keys;
636
	const kdnssec_ctx_t *dnssec_ctx;
637
	changeset_t *changeset;
638
	trie_t *signed_tree;
639
} changeset_signing_data_t;
640

641 642
/*- private API - DNSKEY handling --------------------------------------------*/

643
static int rrset_add_zone_key(knot_rrset_t *rrset, zone_key_t *zone_key)
644 645 646 647
{
	assert(rrset);
	assert(zone_key);

648 649
	dnssec_binary_t dnskey_rdata = { 0 };
	dnssec_key_get_rdata(zone_key->key, &dnskey_rdata);
650

651
	return knot_rrset_add_rdata(rrset, dnskey_rdata.data, dnskey_rdata.size, NULL);
652 653
}

654
static int rrset_add_zone_ds(knot_rrset_t *rrset, zone_key_t *zone_key)
655 656 657 658 659 660 661
{
	assert(rrset);
	assert(zone_key);

	dnssec_binary_t cds_rdata = { 0 };
	zone_key_calculate_ds(zone_key, &cds_rdata);

662
	return knot_rrset_add_rdata(rrset, cds_rdata.data, cds_rdata.size, NULL);
663 664
}

665 666 667 668 669
/*!
 * \brief Goes through list and looks for RRSet type there.
 *
 * \return True if RR type is in the list, false otherwise.
 */
670 671
static bool rr_type_in_list(const knot_rrset_t *rr, const list_t *l)
{
672
	if (l == NULL || EMPTY_LIST(*l)) {
673 674 675 676
		return false;
	}
	assert(rr);

677
	type_node_t *n = NULL;
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
	WALK_LIST(n, *l) {
		type_node_t *type_node = (type_node_t *)n;
		if (type_node->type == rr->type) {
			return true;
		}
	};

	return false;
}

static int add_rr_type_to_list(const knot_rrset_t *rr, list_t *l)
{
	assert(rr);
	assert(l);

	type_node_t *n = malloc(sizeof(type_node_t));
	if (n == NULL) {
		return KNOT_ENOMEM;
	}
697
	n->type = rr->type;
698 699 700 701 702

	add_head(l, (node_t *)n);
	return KNOT_EOK;
}

703 704 705 706 707
/*!
 * \brief Checks whether RRSet is not already in the hash table, automatically
 *        stores its pointer to the table if not found, but returns false in
 *        that case.
 *
708 709
 * \param rrset      RRSet to be checked for.
 * \param tree       Tree with already signed RRs.
Jan Kadlec's avatar
Jan Kadlec committed
710
 * \param rr_signed  Set to true if RR is signed already, false otherwise.
711
 *
712
 * \return KNOT_E*
713
 */
714
static int rr_already_signed(const knot_rrset_t *rrset, trie_t *t,
715
                             bool *rr_signed)
716 717 718
{
	assert(rrset);
	assert(t);
719
	*rr_signed = false;
720
	// Create a key = RRSet owner converted to sortable format
721
	knot_dname_storage_t lf_storage;
722
	uint8_t *lf = knot_dname_lf(rrset->owner, lf_storage);
723
	assert(lf);
724
	trie_val_t stored_info = (signed_info_t *)trie_get_try(t, (char *)lf+1,
725
	                                                      *lf);
726
	if (stored_info == NULL) {
727
		// Create new info struct
728
		signed_info_t *info = malloc(sizeof(signed_info_t));
729
		if (info == NULL) {
730
			return KNOT_ENOMEM;
731
		}
732
		memset(info, 0, sizeof(signed_info_t));
733
		// Store actual dname repr
734
		info->dname = knot_dname_copy(rrset->owner, NULL);
735 736
		if (info->dname == NULL) {
			free(info);
737
			return KNOT_ENOMEM;
738
		}
739
		// Create new list to insert as a value
740
		info->type_list = malloc(sizeof(list_t));
741
		if (info->type_list == NULL) {
742 743
			free(info->dname);
			free(info);
744
			return KNOT_ENOMEM;
745
		}
746
		init_list(info->type_list);
747
		// Insert type to list
748
		int ret = add_rr_type_to_list(rrset, info->type_list);
749
		if (ret != KNOT_EOK) {
750 751 752
			free(info->type_list);
			free(info->dname);
			free(info);
753 754
			return ret;
		}
755
		*trie_get_ins(t, (char *)lf+1, *lf) = info;
756
	} else {
757
		signed_info_t *info = *((signed_info_t **)stored_info);
758
		assert(info->type_list);
759
		// Check whether the type is in the list already
760
		if (rr_type_in_list(rrset, info->type_list)) {
761 762
			*rr_signed = true;
			return KNOT_EOK;
763 764
		}
		// Just update the existing list
765
		int ret = add_rr_type_to_list(rrset, info->type_list);
766
		if (ret != KNOT_EOK) {
767 768
			*rr_signed = false;
			return KNOT_EOK;
769 770 771
		}
	}

772 773
	*rr_signed = false;
	return KNOT_EOK;
774 775
}

776 777 778 779
/*!
 * \brief Wrapper function for changeset signing - to be used with changeset
 *        apply functions.
 *
780 781
 * \param chg_rrset  RRSet to be signed (potentially)
 * \param data       Signing data
782 783 784
 *
 * \return Error code, KNOT_EOK if successful.
 */
785
static int sign_changeset_wrap(knot_rrset_t *chg_rrset, changeset_signing_data_t *args)
786 787
{
	// Find RR's node in zone, find out if we need to sign this RR
788
	const zone_node_t *node =
789
		zone_contents_find_node(args->zone, chg_rrset->owner);
790

791 792
	// If node is not in zone, all its RRSIGs were dropped - no-op
	if (node) {
793 794
		knot_rrset_t zone_rrset = node_rrset(node, chg_rrset->type);
		knot_rrset_t rrsigs = node_rrset(node, KNOT_RRTYPE_RRSIG);
795

796
		bool should_sign = knot_zone_sign_rr_should_be_signed(node, &zone_rrset);
797 798

		// Check for RRSet in the 'already_signed' table
799
		if (args->signed_tree && (should_sign && knot_rrset_empty(&zone_rrset))) {
800 801 802 803 804 805 806 807 808 809 810 811 812
			bool already_signed = false;

			int ret = rr_already_signed(chg_rrset, args->signed_tree,
			                            &already_signed);
			if (ret != KNOT_EOK) {
				return ret;
			}
			if (already_signed) {
				/* Do not sign again. */
				should_sign = false;
			}
		}

813
		if (should_sign) {
814
			return force_resign_rrset(&zone_rrset, &rrsigs,
815
			                          args->zone_keys,
816
			                          args->dnssec_ctx,
817
			                          args->changeset);
818 819
		} else {
			/*
820 821
			 * If RRSet in zone DOES have RRSIGs although we
			 * should not sign it, DDNS-caused change to node/rr
822
			 * occurred and we have to drop all RRSIGs.
823 824 825 826
			 *
			 * OR
			 *
			 * The whole RRSet was removed, but RRSIGs remained in
Jan Kadlec's avatar
Jan Kadlec committed
827
			 * the zone. We need to drop them as well.
828
			 */
829
			return remove_rrset_rrsigs(chg_rrset->owner,
830
			                           chg_rrset->type, &rrsigs,
831
			                           args->changeset);
832
		}
833
	}
Jan Kadlec's avatar
Jan Kadlec committed
834

835
	return KNOT_EOK;
836 837
}

838 839 840 841 842 843
/*!
 * \brief Frees info node about update signing.
 *
 * \param val  Node to free.
 * \param d    Unused.
 */
844
static int free_helper_trie_node(trie_val_t *val, void *d)
845
{
846
	UNUSED(d);
847
	signed_info_t *info = (signed_info_t *)*val;
848 849 850 851
	if (info->type_list && !EMPTY_LIST(*(info->type_list))) {
		WALK_LIST_FREE(*(info->type_list));
	}
	free(info->type_list);
852 853
	knot_dname_free(info->dname, NULL);
	knot_dname_free(info->hashed_dname, NULL);
854
	free(info);
855
	return KNOT_EOK;
856 857
}

858 859 860 861 862
/*!
 * \brief Clears trie with info about update signing.
 *
 * \param t  Trie to clear.
 */
863
static void knot_zone_clear_sorted_changes(trie_t *t)
864 865
{
	if (t) {
866
		trie_apply(t, free_helper_trie_node, NULL);
867 868 869
	}
}

870
/*- public API ---------------------------------------------------------------*/
871

872
int knot_zone_sign(zone_update_t *update,
873
                   zone_keyset_t *zone_keys,
874
                   const kdnssec_ctx_t *dnssec_ctx,
875
                   knot_time_t *expire_at)
876
{
877
	if (!update || !zone_keys || !dnssec_ctx || !expire_at) {
878 879
		return KNOT_EINVAL;
	}
880 881 882

	int result;

883 884
	changeset_t ch;
	result = changeset_init(&ch, update->new_cont->apex->owner);
885 886 887 888
	if (result != KNOT_EOK) {
		return result;
	}

889
	knot_time_t normal_expire = 0;
890
	result = zone_tree_sign(update->new_cont->nodes, zone_keys, dnssec_ctx, &ch, &normal_expire);
891
	if (result != KNOT_EOK) {
892
		changeset_clear(&ch);
893
		return result;
894
	}
895

896
	knot_time_t nsec3_expire = 0;
897 898
	result = zone_tree_sign(update->new_cont->nsec3_nodes, zone_keys, dnssec_ctx,
				&ch, &nsec3_expire);
899
	if (result != KNOT_EOK) {
900
		changeset_clear(&ch);
901
		return result;
902
	}
903

904 905 906 907
	*expire_at = knot_time_min(normal_expire, nsec3_expire);

	result = zone_update_apply_changeset(update, &ch); // _fix not needed
	changeset_clear(&ch);
Jan Kadlec's avatar
Jan Kadlec committed
908

909
	return result;
910
}
911

912 913 914
int knot_zone_sign_update_dnskeys(zone_update_t *update,
                                  zone_keyset_t *zone_keys,
                                  const kdnssec_ctx_t *dnssec_ctx)
915
{
916 917 918
	if (update == NULL || zone_keys == NULL || dnssec_ctx == NULL) {
		return KNOT_EINVAL;
	}
919

920 921 922 923 924 925 926 927 928 929
	const zone_node_t *apex = update->new_cont->apex;
	knot_rrset_t dnskeys = node_rrset(apex, KNOT_RRTYPE_DNSKEY);
	knot_rrset_t cdnskeys = node_rrset(apex, KNOT_RRTYPE_CDNSKEY);
	knot_rrset_t cdss = node_rrset(apex, KNOT_RRTYPE_CDS);
	knot_rrset_t *add_dnskeys = NULL;
	knot_rrset_t *add_cdnskeys = NULL;
	knot_rrset_t *add_cdss = NULL;
	uint32_t dnskey_ttl = dnssec_ctx->policy->dnskey_ttl;
	knot_rrset_t soa = node_rrset(apex, KNOT_RRTYPE_SOA);
	if (knot_rrset_empty(&soa)) {
930 931 932
		return KNOT_EINVAL;
	}

933 934 935 936 937
	changeset_t ch;
	int ret = changeset_init(&ch, apex->owner);
	if (ret != KNOT_EOK) {
		return ret;
	}
938

939
#define CHECK_RET if (ret != KNOT_EOK) goto cleanup
940

941 942 943 944 945 946 947
	// remove all. This will cancel out with additions later
	ret = changeset_add_removal(&ch, &dnskeys, 0);
	CHECK_RET;
	ret = changeset_add_removal(&ch, &cdnskeys, 0);
	CHECK_RET;
	ret = changeset_add_removal(&ch, &cdss, 0);
	CHECK_RET;
948

949
	// add DNSKEYs, CDNSKEYs and CDSs
950 951 952 953 954 955
	add_dnskeys = knot_rrset_new(apex->owner, KNOT_RRTYPE_DNSKEY, soa.rclass,
	                             dnskey_ttl, NULL);
	add_cdnskeys = knot_rrset_new(apex->owner, KNOT_RRTYPE_CDNSKEY, soa.rclass,
	                              0, NULL);
	add_cdss = knot_rrset_new(apex->owner, KNOT_RRTYPE_CDS, soa.rclass,
	                          0, NULL);
956 957
	if (add_dnskeys == NULL || add_cdnskeys == NULL || add_cdss == NULL) {
		ret = KNOT_ENOMEM;
958
		CHECK_RET;
959
	}
960
	zone_key_t *ksk_for_cds = NULL;
961 962
	unsigned crp = dnssec_ctx->policy->child_records_publish;
	int kfc_prio = (crp == CHILD_RECORDS_ALWAYS ? 0 : (crp == CHILD_RECORDS_ROLLOVER ? 1 : 2));
963
	for (int i = 0; i < zone_keys->count; i++) {
964
		zone_key_t *key = &zone_keys->keys[i];
965
		if (key->is_public) {
966
			ret = rrset_add_zone_key(add_dnskeys, key);
967
			CHECK_RET;
968
		}
969

970 971
		// determine which key (if any) will be the one for CDS/CDNSKEY
		if (key->is_ksk && key->cds_priority > kfc_prio) {
972 973 974 975 976 977
			ksk_for_cds = key;
			kfc_prio = key->cds_priority;
		}
	}

	if (ksk_for_cds != NULL) {
978
		ret = rrset_add_zone_key(add_cdnskeys, ksk_for_cds);
979
		CHECK_RET;
980
		ret = rrset_add_zone_ds(add_cdss, ksk_for_cds);
981
		CHECK_RET;
982
	}
983

984 985 986 987
	if (crp == CHILD_RECORDS_EMPTY) {
		const uint8_t cdnskey_empty[5] = { 0, 0, 3, 0, 0 };
		const uint8_t cds_empty[5] = { 0, 0, 0, 0, 0 };
		ret = knot_rrset_add_rdata(add_cdnskeys, cdnskey_empty,
988
		                           sizeof(cdnskey_empty), NULL);
989 990
		CHECK_RET;
		ret = knot_rrset_add_rdata(add_cdss, cds_empty,
991
		                           sizeof(cds_empty), NULL);
992 993 994
		CHECK_RET;
	}

995 996 997 998
	if (!knot_rrset_empty(add_cdnskeys)) {
		ret = changeset_add_addition(&ch, add_cdnskeys, CHANGESET_CHECK |
		                                                CHANGESET_CHECK_CANCELOUT);
		CHECK_RET;
999 1000
	}

1001 1002 1003 1004
	if (!knot_rrset_empty(add_cdss)) {
		ret = changeset_add_addition(&ch, add_cdss, CHANGESET_CHECK |
		                                            CHANGESET_CHECK_CANCELOUT);
		CHECK_RET;
1005 1006 1007
	}

	if (!knot_rrset_empty(add_dnskeys)) {
1008 1009 1010
		ret = changeset_add_addition(&ch, add_dnskeys, CHANGESET_CHECK |
		                                               CHANGESET_CHECK_CANCELOUT);
		CHECK_RET;
1011
	}
1012

1013 1014 1015
	ret = zone_update_apply_changeset(update, &ch);

#undef CHECK_RET
1016

1017
cleanup:
1018 1019 1020
	knot_rrset_free(add_dnskeys, NULL);
	knot_rrset_free(add_cdnskeys, NULL);
	knot_rrset_free(add_cdss, NULL);
1021 1022 1023
	changeset_clear(&ch);
	return ret;
}
1024

1025
bool knot_zone_sign_soa_expired(const zone_contents_t *zone,
1026
                                const zone_keyset_t *zone_keys,
1027
                                const kdnssec_ctx_t *dnssec_ctx)
1028
{
1029 1030 1031
	assert(zone);
	assert(zone_keys);
	assert(dnssec_ctx);
1032

1033 1034
	knot_rrset_t soa = node_rrset(zone->apex, KNOT_RRTYPE_SOA);
	knot_rrset_t rrsigs = node_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
1035
	assert(!knot_rrset_empty(&soa));
1036
	return !all_signatures_exist(&soa, &rrsigs, zone_keys, dnssec_ctx);
1037
}
1038

1039
static int sign_changeset(const zone_contents_t *zone,
1040 1041
                             const changeset_t *in_ch,
                             changeset_t *out_ch,
1042
                             const zone_keyset_t *zone_keys,
1043
                             const kdnssec_ctx_t *dnssec_ctx)
1044
{
1045
	if (zone == NULL || in_ch == NULL || out_ch == NULL) {
1046 1047 1048
		return KNOT_EINVAL;
	}

1049
	// Create args for wrapper function - trie for duplicate sigs
1050 1051 1052
	changeset_signing_data_t args = {
		.zone = zone,
		.zone_keys = zone_keys,
1053
		.dnssec_ctx = dnssec_ctx,
1054
		.changeset = out_ch,
1055
		.signed_tree = trie_create(NULL)
1056 1057
	};

Jan Kadlec's avatar
Jan Kadlec committed
1058 1059
	if (args.signed_tree == NULL) {
		return KNOT_ENOMEM;
1060 1061

	}
1062
	changeset_iter_t itt;
1063
	changeset_iter_all(&itt, in_ch);
1064

1065
	knot_rrset_t rr = changeset_iter_next(