semantic-check.c 34.3 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

    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/>.
*/

17
#include <stdio.h>
18 19
#include <sys/types.h>
#include <sys/socket.h>
20
#include <netinet/in.h>
21 22
#include <arpa/inet.h>

23 24 25 26
#include "dnssec/error.h"
#include "contrib/base32hex.h"
#include "contrib/string.h"
#include "libknot/libknot.h"
Daniel Salzman's avatar
Daniel Salzman committed
27
#include "knot/zone/semantic-check.h"
28
#include "knot/dnssec/rrset-sign.h"
Daniel Salzman's avatar
Daniel Salzman committed
29
#include "knot/dnssec/zone-nsec.h"
30

31 32 33
static const char *error_messages[SEM_ERR_UNKNOWN + 1] = {
	[SEM_ERR_SOA_NONE] =
	"missing SOA at the zone apex",
34

35 36 37 38 39 40 41
	[SEM_ERR_CNAME_EXTRA_RECORDS] =
	"more records exist at CNAME",
	[SEM_ERR_CNAME_MULTIPLE] =
	"multiple CNAME records",

	[SEM_ERR_DNAME_CHILDREN] =
	"child record exists under DNAME",
Marek Vavrusa's avatar
Marek Vavrusa committed
42

43 44 45 46 47 48
	[SEM_ERR_NS_APEX] =
	"missing NS at the zone apex",
	[SEM_ERR_NS_GLUE] =
	"missing glue record",

	[SEM_ERR_RRSIG_RDATA_TYPE_COVERED] =
49
	"wrong type covered in RRSIG",
50
	[SEM_ERR_RRSIG_RDATA_TTL] =
51
	"wrong original TTL in RRSIG",
52
	[SEM_ERR_RRSIG_RDATA_EXPIRATION] =
53
	"expired RRSIG",
54
	[SEM_ERR_RRSIG_RDATA_INCEPTION] =
55
	"RRSIG inception in the future",
56
	[SEM_ERR_RRSIG_RDATA_LABELS] =
57
	"wrong labels in RRSIG",
58
	[SEM_ERR_RRSIG_RDATA_OWNER] =
59
	"wrong signer's name in RRSIG",
60
	[SEM_ERR_RRSIG_NO_RRSIG] =
61
	"missing RRSIG",
62
	[SEM_ERR_RRSIG_SIGNED] =
63
	"signed RRSIG",
64
	[SEM_ERR_RRSIG_UNVERIFIABLE] =
65
	"unverifiable signature",
Marek Vavrusa's avatar
Marek Vavrusa committed
66

67
	[SEM_ERR_NSEC_NONE] =
68
	"missing NSEC",
69
	[SEM_ERR_NSEC_RDATA_BITMAP] =
70
	"incorrect type bitmap in NSEC",
71
	[SEM_ERR_NSEC_RDATA_MULTIPLE] =
72
	"multiple NSEC records",
73
	[SEM_ERR_NSEC_RDATA_CHAIN] =
74
	"incoherent NSEC chain",
Marek Vavrusa's avatar
Marek Vavrusa committed
75

76
	[SEM_ERR_NSEC3_NONE] =
77
	"missing NSEC3",
78
	[SEM_ERR_NSEC3_INSECURE_DELEGATION_OPT] =
79
	"insecure delegation outside NSEC3 opt-out",
80
	[SEM_ERR_NSEC3_EXTRA_RECORD] =
81
	"invalid record type in NSEC3 chain",
82
	[SEM_ERR_NSEC3_RDATA_TTL] =
83
	"inconsistent TTL for NSEC3 and minimum TTL in SOA",
84
	[SEM_ERR_NSEC3_RDATA_CHAIN] =
85
	"incoherent NSEC3 chain",
86
	[SEM_ERR_NSEC3_RDATA_BITMAP] =
87
	"incorrect type bitmap in NSEC3",
88
	[SEM_ERR_NSEC3_RDATA_FLAGS] =
89
	"incorrect flags in NSEC3",
90
	[SEM_ERR_NSEC3_RDATA_SALT] =
91
	"incorrect salt in NSEC3",
92
	[SEM_ERR_NSEC3_RDATA_ALG] =
93
	"incorrect algorithm in NSEC3",
94
	[SEM_ERR_NSEC3_RDATA_ITERS] =
95 96
	"incorrect number of iterations in NSEC3",

97
	[SEM_ERR_NSEC3PARAM_RDATA_FLAGS] =
98
	"invalid flags in NSEC3PARAM",
99
	[SEM_ERR_NSEC3PARAM_RDATA_ALG] =
100
	"invalid algorithm in NSEC3PARAM",
Marek Vavrusa's avatar
Marek Vavrusa committed
101

102
	[SEM_ERR_DS_RDATA_ALG] =
103
	"invalid algorithm in DS",
104
	[SEM_ERR_DS_RDATA_DIGLEN] =
105 106
	"invalid digest length in DS",

107 108 109
	[SEM_ERR_DNSKEY_NONE] =
	"missing DNSKEY",
	[SEM_ERR_DNSKEY_INVALID] =
110
	"invalid DNSKEY",
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
	[SEM_ERR_DNSKEY_RDATA_PROTOCOL] =
	"invalid protocol in DNSKEY",

	[SEM_ERR_CDS_NONE] =
	"missing CDS",
	[SEM_ERR_CDS_MULTIPLE] =
	"multiple CDS records",
	[SEM_ERR_CDS_NOT_MATCH] =
	"CDS not match CDNSKEY",

	[SEM_ERR_CDNSKEY_NONE] =
	"missing CDNSKEY",
	[SEM_ERR_CDNSKEY_MULTIPLE] =
	"multiple CDNSKEY records",
	[SEM_ERR_CDNSKEY_NOT_KSK] =
	"CDNSKEY not match KSK DNSKEY",
	[SEM_ERR_CDNSKEY_NO_DNSKEY] =
	"CDNSKEY not match DNSKEY",

	[SEM_ERR_UNKNOWN] =
	"unknown error"
Marek Vavrusa's avatar
Marek Vavrusa committed
132 133
};

134
const char *sem_error_msg(sem_error_t code)
135
{
136 137
	if (code > SEM_ERR_UNKNOWN) {
		code = SEM_ERR_UNKNOWN;
138
	}
139
	return error_messages[code];
140 141
}

142
typedef enum {
143 144 145 146
	MANDATORY = 1 << 0,
	OPTIONAL =  1 << 1,
	NSEC =      1 << 2,
	NSEC3 =     1 << 3,
147
} check_level_t;
148

149
typedef struct {
150
	zone_contents_t *zone;
151
	sem_handler_t *handler;
152
	const zone_node_t *next_nsec;
153
	check_level_t level;
154
	time_t time;
155 156
} semchecks_data_t;

157
static int check_cname(const zone_node_t *node, semchecks_data_t *data);
158 159
static int check_dname(const zone_node_t *node, semchecks_data_t *data);
static int check_delegation(const zone_node_t *node, semchecks_data_t *data);
160 161
static int check_submission(const zone_node_t *node, semchecks_data_t *data);
static int check_ds(const zone_node_t *node, semchecks_data_t *data);
162 163 164 165
static int check_nsec(const zone_node_t *node, semchecks_data_t *data);
static int check_nsec3(const zone_node_t *node, semchecks_data_t *data);
static int check_nsec3_opt_out(const zone_node_t *node, semchecks_data_t *data);
static int check_rrsig(const zone_node_t *node, semchecks_data_t *data);
166
static int check_rrsig_signed(const zone_node_t *node, semchecks_data_t *data);
167 168
static int check_nsec_bitmap(const zone_node_t *node, semchecks_data_t *data);
static int check_nsec3_presence(const zone_node_t *node, semchecks_data_t *data);
169 170

struct check_function {
171
	int (*function)(const zone_node_t *, semchecks_data_t *);
172
	check_level_t level;
173 174
};

175
/* List of function callbacks for defined check_level */
176
static const struct check_function CHECK_FUNCTIONS[] = {
177
	{ check_cname,          MANDATORY },
178 179 180
	{ check_dname,          MANDATORY },
	{ check_delegation,     OPTIONAL },
	{ check_submission,     OPTIONAL },
181
	{ check_ds,             OPTIONAL },
182
	{ check_rrsig,          NSEC | NSEC3 },
183 184
	{ check_rrsig_signed,   NSEC | NSEC3 },
	{ check_nsec_bitmap,    NSEC | NSEC3 },
185 186 187 188
	{ check_nsec,           NSEC },
	{ check_nsec3,          NSEC3 },
	{ check_nsec3_presence, NSEC3 },
	{ check_nsec3_opt_out,  NSEC3 },
189 190
};

191 192
static const int CHECK_FUNCTIONS_LEN = sizeof(CHECK_FUNCTIONS)
                                     / sizeof(struct check_function);
193

194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
static int dnssec_key_from_rdata(dnssec_key_t **key, const knot_dname_t *owner,
				 const uint8_t *rdata, size_t rdlen)
{
	if (!key || !rdata || rdlen == 0) {
		return KNOT_EINVAL;
	}

	const dnssec_binary_t binary_key = {
		.size = rdlen,
		.data = (uint8_t *)rdata
	};

	dnssec_key_t *new_key = NULL;
	int ret = dnssec_key_new(&new_key);
	if (ret != DNSSEC_EOK) {
		return KNOT_ENOMEM;
	}
	ret = dnssec_key_set_rdata(new_key, &binary_key);
	if (ret != DNSSEC_EOK) {
		dnssec_key_free(new_key);
		return KNOT_ENOMEM;
	}
	if (owner) {
		ret = dnssec_key_set_dname(new_key, owner);
		if (ret != DNSSEC_EOK) {
			dnssec_key_free(new_key);
			return KNOT_ENOMEM;
		}
	}

	*key = new_key;
	return KNOT_EOK;
}

static int check_signature(const knot_rdataset_t *rrsigs, size_t pos,
                           const dnssec_key_t *key, const knot_rrset_t *covered)
{
	if (!rrsigs || !key || !dnssec_key_can_verify(key)) {
		return KNOT_EINVAL;
	}

	int ret = KNOT_EOK;
	dnssec_sign_ctx_t *sign_ctx = NULL;
	dnssec_binary_t signature = { 0 };

	knot_rrsig_signature(rrsigs, pos, &signature.data, &signature.size);
	if (!signature.data || !signature.size) {
		ret = KNOT_EINVAL;
		goto fail;
	}

	if (dnssec_sign_new(&sign_ctx, key) != KNOT_EOK) {
		ret = KNOT_ENOMEM;
		goto fail;
	}

	const knot_rdata_t *rr_data = knot_rdataset_at(rrsigs, pos);
251
	if (knot_sign_ctx_add_data(sign_ctx, rr_data->data, covered) != KNOT_EOK) {
252 253 254 255 256 257 258 259 260 261 262 263 264 265
		ret = KNOT_ENOMEM;
		goto fail;
	}

	if (dnssec_sign_verify(sign_ctx, &signature) != KNOT_EOK) {
		ret = KNOT_EINVAL;
		goto fail;
	}

fail:
	dnssec_sign_free(sign_ctx);
	return ret;
}

266 267 268
/*!
 * \brief Semantic check - RRSIG rdata.
 *
269 270 271 272 273 274 275 276 277
 * \param handler    Pointer on function to be called in case of negative check.
 * \param zone       The zone the rrset is in.
 * \param node       The node in the zone contents.
 * \param rrsig      RRSIG rdataset.
 * \param rr_pos     Position of the RRSIG rdata in question (in the rdataset).
 * \param rrset      RRSet signed by the RRSIG.
 * \param context    The time stamp we check the rrsig validity according to.
 * \param level      Level of the check.
 * \param verified   Out: the RRSIG has been verified to be signed by existing DNSKEY.
278
 *
279
 * \retval KNOT_EOK on success.
280 281
 * \return Appropriate error code if error was found.
 */
282
static int check_rrsig_rdata(sem_handler_t *handler,
283
                             const zone_contents_t *zone,
284
                             const zone_node_t *node,
285
                             const knot_rdataset_t *rrsig,
286
                             size_t rr_pos,
287 288 289 290
                             const knot_rrset_t *rrset,
                             time_t context,
                             check_level_t level,
                             bool *verified)
291
{
292
	/* Prepare additional info string. */
293
	char info_str[50] = { '\0' };
294
	char type_str[16] = { '\0' };
295
	knot_rrtype_to_string(rrset->type, type_str, sizeof(type_str));
296
	int ret = snprintf(info_str, sizeof(info_str), "(record type %s)", type_str);
297 298 299
	if (ret < 0 || ret >= sizeof(info_str)) {
		return KNOT_ENOMEM;
	}
300

301
	if (knot_rrsig_type_covered(rrsig, 0) != rrset->type) {
302
		handler->cb(handler, zone, node, SEM_ERR_RRSIG_RDATA_TYPE_COVERED,
303
		            info_str);
304 305 306
	}

	/* label number at the 2nd index should be same as owner's */
307
	uint8_t labels_rdata = knot_rrsig_labels(rrsig, rr_pos);
308

309
	size_t tmp = knot_dname_labels(rrset->owner, NULL) - labels_rdata;
310 311
	if (tmp != 0) {
		/* if name has wildcard, label must not be included */
312
		if (!knot_dname_is_wildcard(rrset->owner)) {
313
			handler->cb(handler, zone, node, SEM_ERR_RRSIG_RDATA_LABELS,
314
			            info_str);
315
		} else if (tmp != 1) {
316
			handler->cb(handler, zone, node, SEM_ERR_RRSIG_RDATA_LABELS,
317
			            info_str);
318
		}
319 320
	}

321
	/* Check original TTL. */
322
	uint32_t original_ttl = knot_rrsig_original_ttl(rrsig, rr_pos);
323 324 325
	if (original_ttl != rrset->ttl) {
		handler->cb(handler, zone, node, SEM_ERR_RRSIG_RDATA_TTL,
		            info_str);
326 327
	}

328
	/* Check for expired signature. */
329
	if (knot_rrsig_sig_expiration(rrsig, rr_pos) < context) {
330
		handler->cb(handler, zone, node, SEM_ERR_RRSIG_RDATA_EXPIRATION,
331
		            info_str);
332 333
	}

334 335
	/* Check inception */
	if (knot_rrsig_sig_inception(rrsig, rr_pos) > context) {
336
		handler->cb(handler, zone, node, SEM_ERR_RRSIG_RDATA_INCEPTION,
337 338
		            info_str);
	}
339

340
	/* Check signer name. */
341 342
	const knot_dname_t *signer = knot_rrsig_signer_name(rrsig, rr_pos);
	if (!knot_dname_is_equal(signer, zone->apex->owner)) {
343
		handler->cb(handler, zone, node, SEM_ERR_RRSIG_RDATA_OWNER,
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
		            info_str);
	}

	/* Verify with public key - only one RRSIG of covered record needed */
	if (level & OPTIONAL && !*verified) {
		const knot_rdataset_t *dnskeys = node_rdataset(zone->apex, KNOT_RRTYPE_DNSKEY);
		if (dnskeys == NULL) {
			return KNOT_EOK;
		}

		for (int i = 0; i < dnskeys->rr_count; i++) {
			uint16_t flags = knot_dnskey_flags(dnskeys, i);
			uint8_t proto = knot_dnskey_proto(dnskeys, i);
			/* RFC 4034 2.1.1 & 2.1.2 */
			if (flags & DNSKEY_FLAGS_ZSK && proto == 3) {
				knot_rdata_t *dnskey = knot_rdataset_at(dnskeys, i);
				dnssec_key_t *key;

				ret = dnssec_key_from_rdata(&key, zone->apex->owner,
363
				                            dnskey->data, dnskey->len);
364 365 366 367 368 369 370 371 372 373 374
				if (ret != KNOT_EOK) {
					continue;
				}

				ret = check_signature(rrsig, rr_pos, key, rrset);
				dnssec_key_free(key);
				if (ret == KNOT_EOK) {
					*verified = true;
					break;
				}
			}
375 376
		}
	}
377

378
	return KNOT_EOK;
379 380
}

381
static int check_rrsig_signed(const zone_node_t *node, semchecks_data_t *data)
382 383 384
{
	/* signed rrsig - nonsense */
	if (node_rrtype_is_signed(node, KNOT_RRTYPE_RRSIG)) {
385
		data->handler->cb(data->handler, data->zone, node,
386
		                  SEM_ERR_RRSIG_SIGNED, NULL);
387
	}
388

389
	return KNOT_EOK;
390
}
391 392 393
/*!
 * \brief Semantic check - RRSet's RRSIG.
 *
394 395 396 397 398 399
 * \param handler    Pointer on function to be called in case of negative check.
 * \param zone       The zone the rrset is in.
 * \param node       The node in the zone contents.
 * \param rrset      RRSet signed by the RRSIG.
 * \param context    The time stamp we check the rrsig validity according to.
 * \param level      Level of the check.
400 401 402 403
 *
 * \retval KNOT_EOK on success.
 * \return Appropriate error code if error was found.
 */
404
static int check_rrsig_in_rrset(sem_handler_t *handler,
405
                                const zone_contents_t *zone,
406
                                const zone_node_t *node,
407 408 409
                                const knot_rrset_t *rrset,
                                time_t context,
                                check_level_t level)
410
{
Lubos Slovak's avatar
Lubos Slovak committed
411
	if (handler == NULL || node == NULL || rrset == NULL) {
412
		return KNOT_EINVAL;
413 414
	}
	/* Prepare additional info string. */
415 416
	char info_str[50] = { '\0' };
	char type_str[16] = { '\0' };
417
	knot_rrtype_to_string(rrset->type, type_str, sizeof(type_str));
418
	int ret = snprintf(info_str, sizeof(info_str), "(record type %s)", type_str);
419 420 421
	if (ret < 0 || ret >= sizeof(info_str)) {
		return KNOT_ENOMEM;
	}
422 423 424

	knot_rrset_t node_rrsigs = node_rrset(node, KNOT_RRTYPE_RRSIG);

425 426
	knot_rdataset_t rrsigs;
	knot_rdataset_init(&rrsigs);
427
	ret = knot_synth_rrsig(rrset->type, &node_rrsigs.rrs, &rrsigs, NULL);
428 429
	if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
		return ret;
430
	}
431
	if (ret == KNOT_ENOENT) {
432
		handler->cb(handler, zone, node, SEM_ERR_RRSIG_NO_RRSIG, info_str);
433
		return KNOT_EOK;
434 435
	}

436
	bool verified = false;
437
	for (uint16_t i = 0; ret == KNOT_EOK && i < (&rrsigs)->rr_count; ++i) {
438 439 440 441 442
		ret = check_rrsig_rdata(handler, zone, node, &rrsigs, i, rrset,
		                        context, level, &verified);
	}
	/* Only one rrsig of covered record needs to be verified by DNSKEY. */
	if (!verified) {
443
		handler->cb(handler, zone, node, SEM_ERR_RRSIG_UNVERIFIABLE,
444
		            info_str);
445
	}
446

447
	knot_rdataset_clear(&rrsigs, NULL);
448
	return KNOT_EOK;;
449 450
}

451
/*!
Jan Včelák's avatar
Jan Včelák committed
452
 * \brief Check if glue record for delegation is present.
453 454 455 456 457
 *
 * Also check if there is NS record in the zone.
 * \param node Node to check
 * \param data Semantic checks context data
 */
458
static int check_delegation(const zone_node_t *node, semchecks_data_t *data)
Vitezslav Kriz's avatar
Vitezslav Kriz committed
459 460
{
	if (!((node->flags & NODE_FLAGS_DELEG) || data->zone->apex == node)) {
461
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
462
	}
463

Vitezslav Kriz's avatar
Vitezslav Kriz committed
464 465
	const knot_rdataset_t *ns_rrs = node_rdataset(node, KNOT_RRTYPE_NS);
	if (ns_rrs == NULL) {
466
		assert(data->zone->apex == node);
467
		data->handler->cb(data->handler, data->zone, node,
468
		                  SEM_ERR_NS_APEX, NULL);
469
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
470 471 472
	}

	// check glue record for delegation
473
	for (int i = 0; i < ns_rrs->rr_count; ++i) {
Vitezslav Kriz's avatar
Vitezslav Kriz committed
474
		const knot_dname_t *ns_dname = knot_ns_name(ns_rrs, i);
475
		if (!knot_dname_is_sub(ns_dname, node->owner)) {
Vitezslav Kriz's avatar
Vitezslav Kriz committed
476 477 478 479 480 481 482 483 484 485 486 487 488
			continue;
		}

		const zone_node_t *glue_node =
			zone_contents_find_node(data->zone, ns_dname);

		if (glue_node == NULL) {
			/* Try wildcard ([1]* + suffix). */
			knot_dname_t wildcard[KNOT_DNAME_MAXLEN];
			memcpy(wildcard, "\x1""*", 2);
			knot_dname_to_wire(wildcard + 2,
			                   knot_wire_next_label(ns_dname, NULL),
			                   sizeof(wildcard) - 2);
489
			glue_node = zone_contents_find_node(data->zone, wildcard);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
490 491 492
		}
		if (!node_rrtype_exists(glue_node, KNOT_RRTYPE_A) &&
		    !node_rrtype_exists(glue_node, KNOT_RRTYPE_AAAA)) {
493
			data->handler->cb(data->handler, data->zone, node,
494
			                  SEM_ERR_NS_GLUE, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
495 496
		}
	}
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511

	return KNOT_EOK;
}

/*!
 * \brief check_submission_records Check CDS and CDNSKEY
 */
static int check_submission(const zone_node_t *node, semchecks_data_t *data)
{
	const knot_rdataset_t *cdss = node_rdataset(node, KNOT_RRTYPE_CDS);
	const knot_rdataset_t *cdnskeys = node_rdataset(node, KNOT_RRTYPE_CDNSKEY);
	if (cdss == NULL && cdnskeys == NULL) {
		return KNOT_EOK;
	} else if (cdss == NULL) {
		data->handler->cb(data->handler, data->zone, node,
512
		                  SEM_ERR_CDS_NONE, NULL);
513 514 515
		return KNOT_EOK;
	} else if (cdnskeys == NULL) {
		data->handler->cb(data->handler, data->zone, node,
516
		                  SEM_ERR_CDNSKEY_NONE, NULL);
517 518 519 520 521
		return KNOT_EOK;
	}

	if (cdss->rr_count != 1) {
		data->handler->cb(data->handler, data->zone, node,
522
		                  SEM_ERR_CDS_MULTIPLE, NULL);
523 524 525
	}
	if (cdnskeys->rr_count != 1) {
		data->handler->cb(data->handler, data->zone, node,
526
		                  SEM_ERR_CDNSKEY_MULTIPLE, NULL);
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
	}

	knot_rdata_t *cdnskey = knot_rdataset_at(cdnskeys, 0);
	knot_rdata_t *cds = knot_rdataset_at(cdss, 0);
	uint8_t digest_type = knot_ds_digest_type(cdss, 0);

	const knot_rdataset_t *dnskeys = node_rdataset(data->zone->apex,
	                                               KNOT_RRTYPE_DNSKEY);
	for (int i = 0; dnskeys != NULL && i < dnskeys->rr_count; i++) {
		knot_rdata_t *dnskey = knot_rdataset_at(dnskeys, i);
		if (knot_rdata_cmp(dnskey, cdnskey) != 0) {
			continue;
		}

		dnssec_key_t *key;
		int ret = dnssec_key_from_rdata(&key, data->zone->apex->owner,
543
		                                dnskey->data, dnskey->len);
544 545 546 547 548 549
		if (ret != KNOT_EOK) {
			return ret;
		}

		uint16_t flags = dnssec_key_get_flags(key);
		dnssec_binary_t cds_calc = { 0 };
550
		dnssec_binary_t cds_orig = { .size = cds->len, .data = cds->data };
551 552 553 554 555 556 557 558 559 560 561 562
		ret = dnssec_key_create_ds(key, digest_type, &cds_calc);
		if (ret != KNOT_EOK) {
			dnssec_key_free(key);
			return ret;
		}

		ret = dnssec_binary_cmp(&cds_orig, &cds_calc);
		dnssec_binary_free(&cds_calc);
		dnssec_key_free(key);
		if (ret == 0) {
			if (!(flags & DNSKEY_FLAGS_KSK)) {
				data->handler->cb(data->handler, data->zone,
563 564
				                  node, SEM_ERR_CDNSKEY_NOT_KSK,
				                  NULL);
565 566 567 568 569
			} else {
				return KNOT_EOK;
			}
		} else {
			data->handler->cb(data->handler, data->zone, node,
570
			                  SEM_ERR_CDS_NOT_MATCH, NULL);
571 572 573 574
		}
	}

	if (dnskeys != NULL) {
575 576
		data->handler->cb(data->handler, data->zone, node,
		                  SEM_ERR_CDNSKEY_NO_DNSKEY, NULL);
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
	}
	return KNOT_EOK;
}

/*!
 * \brief Semantic check - DS record.
 *
 * \param node Node to check
 * \param data Semantic checks context data
 *
 * \retval KNOT_EOK on success.
 * \return Appropriate error code if error was found.
 */
static int check_ds(const zone_node_t *node, semchecks_data_t *data)
{
	const knot_rdataset_t *dss = node_rdataset(node, KNOT_RRTYPE_DS);
	if (dss == NULL) {
		return KNOT_EOK;
	}

	for (int i = 0; i < dss->rr_count; i++) {
		uint16_t keytag = knot_ds_key_tag(dss, i);
		uint8_t digest_type = knot_ds_digest_type(dss, i);

		char info[100] = "";
		(void)snprintf(info, sizeof(info), "(keytag %d)", keytag);

		if (!dnssec_algorithm_digest_support(digest_type)) {
			data->handler->cb(data->handler, data->zone, node,
606
			                  SEM_ERR_DS_RDATA_ALG, info);
607 608 609 610 611 612 613 614 615 616
		} else {
			// Sizes for different digest algorithms.
			const uint16_t digest_sizes [] = { 0, 20, 32, 32, 48};

			uint8_t *digest;
			uint16_t digest_size;
			knot_ds_digest(dss, i, &digest, &digest_size);

			if (digest_sizes[digest_type] != digest_size) {
				data->handler->cb(data->handler, data->zone, node,
617
				                  SEM_ERR_DS_RDATA_DIGLEN, info);
618 619 620 621 622
			}
		}
	}

	return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
623 624
}

625 626 627 628 629 630
/*!
 * \brief Run all semantic check related to RRSIG record
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
631
static int check_rrsig(const zone_node_t *node, semchecks_data_t *data)
Vitezslav Kriz's avatar
Vitezslav Kriz committed
632 633 634
{
	assert(node);
	if (node->flags & NODE_FLAGS_NONAUTH) {
635
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
636 637
	}

638
	bool deleg = node->flags & NODE_FLAGS_DELEG;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
639

640 641
	int ret = KNOT_EOK;

642
	int rrset_count = node->rrset_count;
643
	for (int i = 0; ret == KNOT_EOK && i < rrset_count; i++) {
Vitezslav Kriz's avatar
Vitezslav Kriz committed
644
		knot_rrset_t rrset = node_rrset_at(node, i);
645 646 647
		if (rrset.type == KNOT_RRTYPE_RRSIG) {
			continue;
		}
648 649
		if (deleg && rrset.type != KNOT_RRTYPE_NSEC &&
		    rrset.type != KNOT_RRTYPE_DS ) {
650
			continue;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
651
		}
652

653
		ret = check_rrsig_in_rrset(data->handler, data->zone, node, &rrset,
654
		                           data->time, data->level);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
655
	}
656
	return ret;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
657 658
}

659 660 661
/*!
 * \brief Add all RR types from a node into the bitmap.
 */
662 663
static void bitmap_add_all_node_rrsets(dnssec_nsec_bitmap_t *bitmap,
                                       const zone_node_t *node)
664
{
665
	bool deleg = node->flags & NODE_FLAGS_DELEG;
666 667 668 669
	for (int i = 0; i < node->rrset_count; i++) {
		knot_rrset_t rr = node_rrset_at(node, i);
		if (deleg && (rr.type != KNOT_RRTYPE_NS &&
		              rr.type != KNOT_RRTYPE_DS &&
670 671
		              rr.type != KNOT_RRTYPE_NSEC &&
		              rr.type != KNOT_RRTYPE_RRSIG)) {
672 673 674 675 676 677
			continue;
		}
		dnssec_nsec_bitmap_add(bitmap, rr.type);
	}
}

678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
static char *nsec3_info(const knot_dname_t *owner, char *out, size_t out_len)
{
	char buff[KNOT_DNAME_TXT_MAXLEN + 1];
	char *str = knot_dname_to_str(buff, owner, sizeof(buff));
	if (str == NULL) {
		return NULL;
	}

	int ret = snprintf(out, out_len, "(NSEC3 owner=%s)", str);
	if (ret <= 0 || ret >= out_len) {
		return NULL;
	}

	return out;
}

694 695 696 697 698 699
/*!
 * \brief Check NSEC and NSEC3 type bitmap
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
700
static int check_nsec_bitmap(const zone_node_t *node, semchecks_data_t *data)
701 702 703
{
	assert(node);
	if (node->flags & NODE_FLAGS_NONAUTH) {
704
		return KNOT_EOK;
705 706
	}

707 708
	bool nsec = data->level & NSEC;
	knot_rdataset_t *nsec_rrs = NULL;
709

710
	if (nsec) {
711
		nsec_rrs = node_rdataset(node, KNOT_RRTYPE_NSEC);
712
	} else if (node->nsec3_node != NULL) {
713 714 715
		nsec_rrs = node_rdataset(node->nsec3_node, KNOT_RRTYPE_NSEC3);
	}
	if (nsec_rrs == NULL) {
716
		return KNOT_EOK;
717 718 719 720 721
	}

	// create NSEC bitmap from node
	dnssec_nsec_bitmap_t *node_bitmap = dnssec_nsec_bitmap_new();
	if (node_bitmap == NULL) {
722
		return KNOT_ENOMEM;
723 724 725 726 727 728 729
	}
	bitmap_add_all_node_rrsets(node_bitmap, node);

	uint16_t node_wire_size = dnssec_nsec_bitmap_size(node_bitmap);
	uint8_t *node_wire = malloc(node_wire_size);
	if (node_wire == NULL) {
		dnssec_nsec_bitmap_free(node_bitmap);
730
		return KNOT_ENOMEM;
731 732
	}
	dnssec_nsec_bitmap_write(node_bitmap, node_wire);
733
	dnssec_nsec_bitmap_free(node_bitmap);
734 735 736 737

	// get NSEC bitmap from NSEC node
	uint8_t *nsec_wire = NULL;
	uint16_t nsec_wire_size = 0;
738
	if (nsec) {
739 740 741 742 743 744 745
		knot_nsec_bitmap(nsec_rrs, &nsec_wire, &nsec_wire_size);
	} else {
		knot_nsec3_bitmap(nsec_rrs, 0, &nsec_wire, &nsec_wire_size);
	}

	if (node_wire_size != nsec_wire_size ||
	    memcmp(node_wire, nsec_wire, node_wire_size) != 0) {
746 747 748 749
		char buff[50 + KNOT_DNAME_TXT_MAXLEN];
		char *info = nsec ? NULL : nsec3_info(node->nsec3_node->owner,
		                                      buff, sizeof(buff));
		data->handler->cb(data->handler, data->zone, node,
750
		                  (nsec ? SEM_ERR_NSEC_RDATA_BITMAP : SEM_ERR_NSEC3_RDATA_BITMAP),
751
		                  info);
752
	}
753 754

	free(node_wire);
755
	return KNOT_EOK;
756 757
}

758 759 760 761 762 763
/*!
 * \brief Run NSEC related semantic checks
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
764
static int check_nsec(const zone_node_t *node, semchecks_data_t *data)
Vitezslav Kriz's avatar
Vitezslav Kriz committed
765 766 767
{
	assert(node);
	if (node->flags & NODE_FLAGS_NONAUTH) {
768
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
769 770
	}

771 772 773 774
	if (node->rrset_count == 0) { // empty nonterminal
		return KNOT_EOK;
	}

Vitezslav Kriz's avatar
Vitezslav Kriz committed
775
	/* check for NSEC record */
Jan Včelák's avatar
Jan Včelák committed
776
	const knot_rdataset_t *nsec_rrs = node_rdataset(node, KNOT_RRTYPE_NSEC);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
777
	if (nsec_rrs == NULL) {
778
		data->handler->cb(data->handler, data->zone, node,
779
		                  SEM_ERR_NSEC_NONE, NULL);
780
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
781 782 783 784
	}

	/* Test that only one record is in the NSEC RRSet */
	if (nsec_rrs->rr_count != 1) {
785
		data->handler->cb(data->handler, data->zone, node,
786
		                  SEM_ERR_NSEC_RDATA_MULTIPLE, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
787 788
	}

Jan Včelák's avatar
Jan Včelák committed
789
	if (data->next_nsec != node) {
790
		data->handler->cb(data->handler, data->zone, node,
791
		                  SEM_ERR_NSEC_RDATA_CHAIN, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
792 793 794 795 796 797 798 799 800 801 802 803 804
	}

	/*
	 * Test that NSEC chain is coherent.
	 * We have already checked that every
	 * authoritative node contains NSEC record
	 * so checking should only be matter of testing
	 * the next link in each node.
	 */
	const knot_dname_t *next_domain = knot_nsec_next(nsec_rrs);

	data->next_nsec = zone_contents_find_node(data->zone, next_domain);
	if (data->next_nsec == NULL) {
805
		data->handler->cb(data->handler, data->zone, node,
806
		                  SEM_ERR_NSEC_RDATA_CHAIN, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
807
	}
808

809
	return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
810 811
}

812 813 814 815 816 817
/*!
 * \brief Check if node has NSEC3 node.
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
818
static int check_nsec3_presence(const zone_node_t *node, semchecks_data_t *data)
819 820 821 822
{
	bool auth = (node->flags & NODE_FLAGS_NONAUTH) == 0;
	bool deleg = (node->flags & NODE_FLAGS_DELEG) != 0;

823
	if ((deleg && node_rrtype_exists(node, KNOT_RRTYPE_DS)) || (auth && !deleg)) {
824 825
		if (node->nsec3_node == NULL) {
			data->handler->cb(data->handler, data->zone, node,
826
			                  SEM_ERR_NSEC3_NONE, NULL);
827 828
		}
	}
829

830
	return KNOT_EOK;
831 832 833
}

/*!
834
 * \brief Check NSEC3 opt-out.
835 836 837 838
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
839
static int check_nsec3_opt_out(const zone_node_t *node, semchecks_data_t *data)
840
{
841
	if (!(node->nsec3_node == NULL && node->flags & NODE_FLAGS_DELEG)) {
842
		return KNOT_EOK;
843
	}
844
	/* Insecure delegation, check whether it is part of opt-out span. */
845

846 847 848 849 850 851
	const zone_node_t *nsec3_previous = NULL;
	const zone_node_t *nsec3_node;
	zone_contents_find_nsec3_for_name(data->zone, node->owner, &nsec3_node,
	                                  &nsec3_previous);

	if (nsec3_previous == NULL) {
852
		data->handler->cb(data->handler, data->zone, node,
853
		                  SEM_ERR_NSEC3_NONE, NULL);
854
		return KNOT_EOK;
855
	}
856

857 858 859
	const knot_rdataset_t *previous_rrs;
	previous_rrs = node_rdataset(nsec3_previous, KNOT_RRTYPE_NSEC3);
	assert(previous_rrs);
860

861
	/* Check for opt-out flag. */
862
	uint8_t flags = knot_nsec3_flags(previous_rrs, 0);
863 864
	if (!(flags & 1)) {
		data->handler->cb(data->handler, data->zone, node,
865
		                  SEM_ERR_NSEC3_INSECURE_DELEGATION_OPT, NULL);
866
	}
867

868
	return KNOT_EOK;
869 870
}

871
/*!
872 873 874 875 876 877
 * \brief Run checks related to NSEC3.
 *
 * Check NSEC3 node for given node.
 * Check if NSEC3 chain is coherent and cyclic.
 * \param node Node to check
 * \param data Semantic checks context data
878
 */
879
static int check_nsec3(const zone_node_t *node, semchecks_data_t *data)
880
{
Vitezslav Kriz's avatar
Vitezslav Kriz committed
881
	assert(node);
Jan Včelák's avatar
Jan Včelák committed
882 883
	bool auth = (node->flags & NODE_FLAGS_NONAUTH) == 0;
	bool deleg = (node->flags & NODE_FLAGS_DELEG) != 0;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
884

Jan Včelák's avatar
Jan Včelák committed
885
	if (!auth && !deleg) {
886
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
887
	}
888
	if (node->nsec3_node == NULL) {
889
		return KNOT_EOK;
890 891
	}

892 893 894 895 896 897
	dnssec_nsec3_params_t params_apex = { 0 };
	int ret = KNOT_EOK;

	char buff[50 + KNOT_DNAME_TXT_MAXLEN];
	char *info = nsec3_info(node->nsec3_node->owner, buff, sizeof(buff));

898 899
	knot_rrset_t nsec3_rrs = node_rrset(node->nsec3_node, KNOT_RRTYPE_NSEC3);
	if (knot_rrset_empty(&nsec3_rrs)) {
900
		data->handler->cb(data->handler, data->zone, node,
901
		                  SEM_ERR_NSEC3_NONE, info);
902
		goto nsec3_cleanup;
903
	}
904

Vitezslav Kriz's avatar
Vitezslav Kriz committed
905
	const knot_rdataset_t *soa_rrs = node_rdataset(data->zone->apex, KNOT_RRTYPE_SOA);
906
	assert(soa_rrs);
907
	uint32_t minimum_ttl = knot_soa_minimum(soa_rrs);
908
	if (nsec3_rrs.ttl != minimum_ttl) {
909
		data->handler->cb(data->handler, data->zone, node,
910
		                  SEM_ERR_NSEC3_RDATA_TTL, info);
911 912 913 914 915 916
	}

	// Check parameters.
	const knot_rdataset_t *nsec3param = node_rdataset(data->zone->apex,
	                                                  KNOT_RRTYPE_NSEC3PARAM);
	knot_rdata_t *rrd = knot_rdataset_at(nsec3param, 0);
917
	dnssec_binary_t rdata = { .size = rrd->len, .data = rrd->data };
918 919
	ret = dnssec_nsec3_params_from_rdata(&params_apex, &rdata);
	if (ret != DNSSEC_EOK) {
920
		ret = knot_error_from_libdnssec(ret);
921 922 923
		goto nsec3_cleanup;
	}

924
	if (knot_nsec3_flags(&nsec3_rrs.rrs, 0) > 1) {
925
		data->handler->cb(data->handler, data->zone, node,
926
		                  SEM_ERR_NSEC3_RDATA_FLAGS, info);
927 928 929
	}

	dnssec_binary_t salt = {
930 931
		.size = knot_nsec3_salt_length(&nsec3_rrs.rrs, 0),
		.data = (uint8_t *)knot_nsec3_salt(&nsec3_rrs.rrs, 0),
932 933 934 935
	};

	if (dnssec_binary_cmp(&salt, &params_apex.salt)) {
		data->handler->cb(data->handler, data->zone, node,
936
		                  SEM_ERR_NSEC3_RDATA_SALT, info);
937 938
	}

939
	if (knot_nsec3_algorithm(&nsec3_rrs.rrs, 0) != params_apex.algorithm) {
940
		data->handler->cb(data->handler, data->zone, node,
941
		                  SEM_ERR_NSEC3_RDATA_ALG, info);
942 943
	}

944
	if (knot_nsec3_iterations(&nsec3_rrs.rrs, 0) != params_apex.iterations) {
945
		data->handler->cb(data->handler, data->zone, node,
946
		                  SEM_ERR_NSEC3_RDATA_ITERS, info);
947 948 949
	}

	// Get next nsec3 node.
Vitezslav Kriz's avatar
Vitezslav Kriz committed
950
	const zone_node_t *apex = data->zone->apex;
951
	uint8_t *next_dname_str = NULL;
952
	uint8_t next_dname_str_size = 0;
953
	knot_nsec3_next_hashed(&nsec3_rrs.rrs, 0, &next_dname_str, &next_dname_str_size);
954 955 956 957 958
	uint8_t next_dname[KNOT_DNAME_MAXLEN];
	ret = knot_nsec3_hash_to_dname(next_dname, sizeof(next_dname),
	                               next_dname_str, next_dname_str_size,
	                               apex->owner);
	if (ret != KNOT_EOK) {
959
		goto nsec3_cleanup;
960
	}
961

962 963 964
	const zone_node_t *next_nsec3 = zone_contents_find_nsec3_node(data->zone,
	                                                              next_dname);
	if (next_nsec3 == NULL || next_nsec3->prev != node->nsec3_node) {
965
		uint8_t *next = NULL;
966 967 968 969
		int32_t next_len = base32hex_encode_alloc(next_dname_str,
		                                          next_dname_str_size,
		                                          &next);
		char *hash_info = NULL;
970
		if (next != NULL) {
971 972 973 974
			hash_info = sprintf_alloc("(next hash %.*s)", next_len, next);
			free(next);
		}
		data->handler->cb(data->handler, data->zone, node,
975
		                  SEM_ERR_NSEC3_RDATA_CHAIN, hash_info);
976 977
		free(hash_info);
	}
978

979 980 981
	ret = check_rrsig(node->nsec3_node, data);
	if (ret != KNOT_EOK) {
		goto nsec3_cleanup;
Jan Kadlec's avatar
Jan Kadlec committed
982
	}
983

984 985 986
	// Check that the node only contains NSEC3 and RRSIG.
	for (int i = 0; ret == KNOT_EOK && i < node->nsec3_node->rrset_count; i++) {
		knot_rrset_t rrset = node_rrset_at(node->nsec3_node, i);
987
		uint16_t type = rrset.type;
988 989
		if (type != KNOT_RRTYPE_NSEC3 && type != KNOT_RRTYPE_RRSIG) {
			data->handler->cb(data->handler, data->zone, node->nsec3_node,
990
			                  SEM_ERR_NSEC3_EXTRA_RECORD, NULL);
991 992
		}
	}
993 994 995 996

nsec3_cleanup:
	dnssec_nsec3_params_free(&params_apex);

997
	return ret;
998
}
999

1000 1001 1002 1003 1004 1005
/*!
 * \brief Check if CNAME record contains other records
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
1006
static int check_cname(const zone_node_t *node, semchecks_data_t *data)
1007
{
1008
	const  knot_rdataset_t *cname_rrs = node_rdataset(node, KNOT_RRTYPE_CNAME);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1009
	if (cname_rrs == NULL) {
1010
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1011
	}
1012

Vitezslav Kriz's avatar
Vitezslav Kriz committed
1013 1014 1015 1016 1017
	unsigned rrset_limit = 1;
	/* With DNSSEC node can contain RRSIGs or NSEC */
	if (node_rrtype_exists(node, KNOT_RRTYPE_NSEC)) {
		rrset_limit += 1;
	}
1018
	if (node_rrtype_exists(node, KNOT_RRTYPE_RRSIG)) {
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1019 1020
		rrset_limit += 1;
	}
1021

Vitezslav Kriz's avatar
Vitezslav Kriz committed
1022
	if (node->rrset_count > rrset_limit) {
1023 1024
		data->handler->fatal_error = true;
		data->handler->cb(data->handler, data->zone, node,
1025
		                  SEM_ERR_CNAME_EXTRA_RECORDS, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1026 1027
	}
	if (cname_rrs->rr_count != 1) {
1028 1029
		data->handler->fatal_error = true;
		data->handler->cb(data->handler, data->zone, node,
1030
		                  SEM_ERR_CNAME_MULTIPLE, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1031
	}
1032 1033

	return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1034
}
1035

1036 1037 1038 1039 1040 1041
/*!
 * \brief Check if DNAME record has children.
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
1042
static int check_dname(const zone_node_t *node, semchecks_data_t *data)
1043 1044
{
	if (node->parent != NULL && node_rrtype_exists(node->parent, KNOT_RRTYPE_DNAME)) {
1045
		data->handler->fatal_error = true;
1046 1047
		data->handler->cb(data->handler, data->zone, node,
		                  SEM_ERR_DNAME_CHILDREN, NULL);
1048
	}
1049 1050

	return KNOT_EOK;
1051 1052
}

1053
/*!
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1054
 * \brief Check that NSEC chain is cyclic.
1055
 *
1056
 * Run only once per zone. Check that last NSEC node points to first one.
1057 1058
 * \param data Semantic checks context data
 */
1059
static int check_nsec_cyclic(semchecks_data_t *data)
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1060 1061
{
	if (data->next_nsec == NULL) {
1062
		data->handler->cb(data->handler, data->zone, data->zone->apex,
1063
		                  SEM_ERR_NSEC_RDATA_CHAIN, NULL);
1064
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1065 1066
	}
	if (!knot_dname_is_equal(data->next_nsec->owner, data->zone->apex->owner)) {
1067
		data->handler->cb(data->handler, data->zone, data->next_nsec,
1068
		                  SEM_ERR_NSEC_RDATA_CHAIN, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1069
	}
1070

1071
	return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1072
}
1073

Vitezslav Kriz's avatar
Vitezslav Kriz committed
1074
/*!
1075 1076 1077 1078
 * \brief Call all semantic checks for each node.
 *
 * This function is called as callback from zone_contents_tree_apply_inorder.
 * Checks are functions from global const array check_functions.
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1079
 *
1080 1081
 * \param node Node to be checked
 * \param data Semantic checks context data
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1082 1083 1084
 */
static int do_checks_in_tree(zone_node_t *node, void *data)
{
1085
	semchecks_data_t *s_data = (semchecks_data_t *)data;
1086

1087 1088
	int ret = KNOT_EOK;

1089 1090 1091
	for (int i = 0; ret == KNOT_EOK && i < CHECK_FUNCTIONS_LEN; ++i) {
		if (CHECK_FUNCTIONS[i].level & s_data->level) {
			ret = CHECK_FUNCTIONS[i].function(node, s_data);
1092
		}
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1093
	}
1094

1095 1096

	return ret;
1097 1098
}

1099
static void check_nsec3param(knot_rdataset_t *nsec3param, zone_contents_t *zone,
1100
                             sem_handler_t *handler, semchecks_data_t *data)
1101 1102 1103 1104 1105 1106
{
	assert(nsec3param);

	data->level |= NSEC3;
	uint8_t param = knot_nsec3param_flags(nsec3param, 0);
	if ((param & ~1) != 0) {
1107
		handler->cb(handler, zone, zone->apex, SEM_ERR_NSEC3PARAM_RDATA_FLAGS,
1108 1109 1110 1111 1112
		            NULL);
	}

	param = knot_nsec3param_algorithm(nsec3param, 0);
	if (param != DNSSEC_NSEC3_ALGORITHM_SHA1) {
1113
		handler->cb(handler, zone, zone->apex, SEM_ERR_NSEC3PARAM_RDATA_ALG,
1114 1115 1116 1117
		            NULL);
	}
}

1118
static void check_dnskey(zone_contents_t *zone, sem_handler_t *handler)
1119 1120 1121
{
	const knot_rdataset_t *dnskeys = node_rdataset(zone->apex, KNOT_RRTYPE_DNSKEY);
	if (dnskeys == NULL) {
1122
		handler->cb(handler, zone, zone->apex, SEM_ERR_DNSKEY_NONE, NULL);
1123
		return;
1124 1125 1126 1127 1128 1129
	}

	for (int i = 0; i < dnskeys->rr_count; i++) {
		knot_rdata_t *dnskey = knot_rdataset_at(dnskeys, i);
		dnssec_key_t *key;
		int ret = dnssec_key_from_rdata(&key, zone->apex->owner,
1130
		                                dnskey->data, dnskey->len);
1131 1132 1133
		if (ret == KNOT_EOK) {
			dnssec_key_free(key);
		} else {
1134
			handler->cb(handler, zone, zone->apex, SEM_ERR_DNSKEY_INVALID, NULL);
1135 1136 1137
		}

		if (knot_dnskey_proto(dnskeys, i) != 3) {
1138 1139
			handler->cb(handler, zone, zone->apex, SEM_ERR_DNSKEY_RDATA_PROTOCOL,
			            NULL);
1140 1141 1142 1143 1144
		}

		dnssec_key_algorithm_t alg = knot_dnskey_alg(dnskeys, i);
		if (!dnssec_algorithm_key_support(alg)) {
			char *info = sprintf_alloc("(unsupported algorithm %d)", alg);
1145
			handler->cb(handler, zone, zone->apex, SEM_ERR_DNSKEY_INVALID, info);
1146 1147 1148 1149 1150
			free(info);
		}
	}
}

1151 1152
int sem_checks_process(zone_contents_t *zone, bool optional, sem_handler_t *handler,
                       time_t time)
1153
{
1154
	if (zone == NULL || handler == NULL) {
1155
		return KNOT_EINVAL;
1156
	}
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1157

1158 1159 1160 1161 1162
	semchecks_data_t data = {
		.handler = handler,
		.zone = zone,
		.next_nsec = zone->apex,
		.level = MANDATORY,
1163 1164
		.time = time,
	};
1165

Vitezslav Kriz's avatar
Vitezslav Kriz committed
1166
	if (optional) {
1167
		data.level |= OPTIONAL;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1168
		if (zone_contents_is_signed(zone)) {
1169 1170 1171
			knot_rdataset_t *nsec3param = node_rdataset(zone->apex,
			                                            KNOT_RRTYPE_NSEC3PARAM);
			if (nsec3param != NULL) {
1172
				data.level |= NSEC3;