semantic-check.c 33.9 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
#include "libdnssec/error.h"
24 25 26
#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
	[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_NO_DNSKEY] =
	"CDNSKEY not match DNSKEY",

	[SEM_ERR_UNKNOWN] =
	"unknown error"
Marek Vavrusa's avatar
Marek Vavrusa committed
130 131
};

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

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

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

155
static int check_cname(const zone_node_t *node, semchecks_data_t *data);
156 157
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);
158 159
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);
160 161 162 163
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);
164
static int check_rrsig_signed(const zone_node_t *node, semchecks_data_t *data);
165 166
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);
167 168

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

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

189 190
static const int CHECK_FUNCTIONS_LEN = sizeof(CHECK_FUNCTIONS)
                                     / sizeof(struct check_function);
191

192 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
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);
249
	if (knot_sign_ctx_add_data(sign_ctx, rr_data->data, covered) != KNOT_EOK) {
250 251 252 253 254 255 256 257 258 259 260 261 262 263
		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;
}

264 265 266
/*!
 * \brief Semantic check - RRSIG rdata.
 *
267 268 269 270 271 272 273 274 275
 * \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.
276
 *
277
 * \retval KNOT_EOK on success.
278 279
 * \return Appropriate error code if error was found.
 */
280
static int check_rrsig_rdata(sem_handler_t *handler,
281
                             const zone_contents_t *zone,
282
                             const zone_node_t *node,
283
                             const knot_rdataset_t *rrsig,
284
                             size_t rr_pos,
285 286 287 288
                             const knot_rrset_t *rrset,
                             time_t context,
                             check_level_t level,
                             bool *verified)
289
{
290
	/* Prepare additional info string. */
291
	char info_str[50] = { '\0' };
292
	char type_str[16] = { '\0' };
293
	knot_rrtype_to_string(rrset->type, type_str, sizeof(type_str));
294
	int ret = snprintf(info_str, sizeof(info_str), "(record type %s)", type_str);
295 296 297
	if (ret < 0 || ret >= sizeof(info_str)) {
		return KNOT_ENOMEM;
	}
298

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

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

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

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

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

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

338
	/* Check signer name. */
339 340
	const knot_dname_t *signer = knot_rrsig_signer_name(rrsig, rr_pos);
	if (!knot_dname_is_equal(signer, zone->apex->owner)) {
341
		handler->cb(handler, zone, node, SEM_ERR_RRSIG_RDATA_OWNER,
342 343 344 345 346 347 348 349 350 351
		            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;
		}

352
		for (int i = 0; i < dnskeys->count; i++) {
353 354 355 356 357 358 359 360
			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,
361
				                            dnskey->data, dnskey->len);
362 363 364 365 366 367 368 369 370 371 372
				if (ret != KNOT_EOK) {
					continue;
				}

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

376
	return KNOT_EOK;
377 378
}

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

387
	return KNOT_EOK;
388
}
389 390 391
/*!
 * \brief Semantic check - RRSet's RRSIG.
 *
392 393 394 395 396 397
 * \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.
398 399 400 401
 *
 * \retval KNOT_EOK on success.
 * \return Appropriate error code if error was found.
 */
402
static int check_rrsig_in_rrset(sem_handler_t *handler,
403
                                const zone_contents_t *zone,
404
                                const zone_node_t *node,
405 406 407
                                const knot_rrset_t *rrset,
                                time_t context,
                                check_level_t level)
408
{
Lubos Slovak's avatar
Lubos Slovak committed
409
	if (handler == NULL || node == NULL || rrset == NULL) {
410
		return KNOT_EINVAL;
411 412
	}
	/* Prepare additional info string. */
413 414
	char info_str[50] = { '\0' };
	char type_str[16] = { '\0' };
415
	knot_rrtype_to_string(rrset->type, type_str, sizeof(type_str));
416
	int ret = snprintf(info_str, sizeof(info_str), "(record type %s)", type_str);
417 418 419
	if (ret < 0 || ret >= sizeof(info_str)) {
		return KNOT_ENOMEM;
	}
420 421 422

	knot_rrset_t node_rrsigs = node_rrset(node, KNOT_RRTYPE_RRSIG);

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

434
	bool verified = false;
435
	for (uint16_t i = 0; ret == KNOT_EOK && i < (&rrsigs)->count; ++i) {
436 437 438 439 440
		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) {
441
		handler->cb(handler, zone, node, SEM_ERR_RRSIG_UNVERIFIABLE,
442
		            info_str);
443
	}
444

445
	knot_rdataset_clear(&rrsigs, NULL);
446
	return KNOT_EOK;;
447 448
}

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

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

	// check glue record for delegation
471
	for (int i = 0; i < ns_rrs->count; ++i) {
Vitezslav Kriz's avatar
Vitezslav Kriz committed
472
		const knot_dname_t *ns_dname = knot_ns_name(ns_rrs, i);
473
		if (!knot_dname_is_sub(ns_dname, node->owner)) {
Vitezslav Kriz's avatar
Vitezslav Kriz committed
474 475 476 477 478 479 480 481 482 483 484 485 486
			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);
487
			glue_node = zone_contents_find_node(data->zone, wildcard);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
488 489 490
		}
		if (!node_rrtype_exists(glue_node, KNOT_RRTYPE_A) &&
		    !node_rrtype_exists(glue_node, KNOT_RRTYPE_AAAA)) {
491
			data->handler->cb(data->handler, data->zone, node,
492
			                  SEM_ERR_NS_GLUE, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
493 494
		}
	}
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509

	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,
510
		                  SEM_ERR_CDS_NONE, NULL);
511 512 513
		return KNOT_EOK;
	} else if (cdnskeys == NULL) {
		data->handler->cb(data->handler, data->zone, node,
514
		                  SEM_ERR_CDNSKEY_NONE, NULL);
515 516 517
		return KNOT_EOK;
	}

518
	if (cdss->count != 1) {
519
		data->handler->cb(data->handler, data->zone, node,
520
		                  SEM_ERR_CDS_MULTIPLE, NULL);
521
	}
522
	if (cdnskeys->count != 1) {
523
		data->handler->cb(data->handler, data->zone, node,
524
		                  SEM_ERR_CDNSKEY_MULTIPLE, NULL);
525 526 527 528 529 530 531 532
	}

	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);
533
	for (int i = 0; dnskeys != NULL && i < dnskeys->count; i++) {
534 535 536 537 538 539 540
		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,
541
		                                dnskey->data, dnskey->len);
542 543 544 545 546
		if (ret != KNOT_EOK) {
			return ret;
		}

		dnssec_binary_t cds_calc = { 0 };
547
		dnssec_binary_t cds_orig = { .size = cds->len, .data = cds->data };
548 549 550 551 552 553 554 555 556 557
		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) {
558
			return KNOT_EOK;
559 560
		} else {
			data->handler->cb(data->handler, data->zone, node,
561
			                  SEM_ERR_CDS_NOT_MATCH, NULL);
562 563 564 565
		}
	}

	if (dnskeys != NULL) {
566 567
		data->handler->cb(data->handler, data->zone, node,
		                  SEM_ERR_CDNSKEY_NO_DNSKEY, NULL);
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
	}
	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;
	}

588
	for (int i = 0; i < dss->count; i++) {
589 590 591 592 593 594 595 596
		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,
597
			                  SEM_ERR_DS_RDATA_ALG, info);
598 599 600 601 602 603 604 605 606 607
		} 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,
608
				                  SEM_ERR_DS_RDATA_DIGLEN, info);
609 610 611 612 613
			}
		}
	}

	return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
614 615
}

616 617 618 619 620 621
/*!
 * \brief Run all semantic check related to RRSIG record
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
622
static int check_rrsig(const zone_node_t *node, semchecks_data_t *data)
Vitezslav Kriz's avatar
Vitezslav Kriz committed
623 624 625
{
	assert(node);
	if (node->flags & NODE_FLAGS_NONAUTH) {
626
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
627 628
	}

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

631 632
	int ret = KNOT_EOK;

633
	int rrset_count = node->rrset_count;
634
	for (int i = 0; ret == KNOT_EOK && i < rrset_count; i++) {
Vitezslav Kriz's avatar
Vitezslav Kriz committed
635
		knot_rrset_t rrset = node_rrset_at(node, i);
636 637 638
		if (rrset.type == KNOT_RRTYPE_RRSIG) {
			continue;
		}
639 640
		if (deleg && rrset.type != KNOT_RRTYPE_NSEC &&
		    rrset.type != KNOT_RRTYPE_DS ) {
641
			continue;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
642
		}
643

644
		ret = check_rrsig_in_rrset(data->handler, data->zone, node, &rrset,
645
		                           data->time, data->level);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
646
	}
647
	return ret;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
648 649
}

650 651 652
/*!
 * \brief Add all RR types from a node into the bitmap.
 */
653 654
static void bitmap_add_all_node_rrsets(dnssec_nsec_bitmap_t *bitmap,
                                       const zone_node_t *node)
655
{
656
	bool deleg = node->flags & NODE_FLAGS_DELEG;
657 658 659 660
	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 &&
661 662
		              rr.type != KNOT_RRTYPE_NSEC &&
		              rr.type != KNOT_RRTYPE_RRSIG)) {
663 664 665 666 667 668
			continue;
		}
		dnssec_nsec_bitmap_add(bitmap, rr.type);
	}
}

669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
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;
}

685 686 687 688 689 690
/*!
 * \brief Check NSEC and NSEC3 type bitmap
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
691
static int check_nsec_bitmap(const zone_node_t *node, semchecks_data_t *data)
692 693 694
{
	assert(node);
	if (node->flags & NODE_FLAGS_NONAUTH) {
695
		return KNOT_EOK;
696 697
	}

698 699
	bool nsec = data->level & NSEC;
	knot_rdataset_t *nsec_rrs = NULL;
700

701
	if (nsec) {
702
		nsec_rrs = node_rdataset(node, KNOT_RRTYPE_NSEC);
703
	} else if (node->nsec3_node != NULL) {
704 705 706
		nsec_rrs = node_rdataset(node->nsec3_node, KNOT_RRTYPE_NSEC3);
	}
	if (nsec_rrs == NULL) {
707
		return KNOT_EOK;
708 709 710 711 712
	}

	// create NSEC bitmap from node
	dnssec_nsec_bitmap_t *node_bitmap = dnssec_nsec_bitmap_new();
	if (node_bitmap == NULL) {
713
		return KNOT_ENOMEM;
714 715 716 717 718 719 720
	}
	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);
721
		return KNOT_ENOMEM;
722 723
	}
	dnssec_nsec_bitmap_write(node_bitmap, node_wire);
724
	dnssec_nsec_bitmap_free(node_bitmap);
725 726 727 728

	// get NSEC bitmap from NSEC node
	uint8_t *nsec_wire = NULL;
	uint16_t nsec_wire_size = 0;
729
	if (nsec) {
730 731 732 733 734 735 736
		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) {
737 738 739 740
		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,
741
		                  (nsec ? SEM_ERR_NSEC_RDATA_BITMAP : SEM_ERR_NSEC3_RDATA_BITMAP),
742
		                  info);
743
	}
744 745

	free(node_wire);
746
	return KNOT_EOK;
747 748
}

749 750 751 752 753 754
/*!
 * \brief Run NSEC related semantic checks
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
755
static int check_nsec(const zone_node_t *node, semchecks_data_t *data)
Vitezslav Kriz's avatar
Vitezslav Kriz committed
756 757 758
{
	assert(node);
	if (node->flags & NODE_FLAGS_NONAUTH) {
759
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
760 761
	}

762 763 764 765
	if (node->rrset_count == 0) { // empty nonterminal
		return KNOT_EOK;
	}

Vitezslav Kriz's avatar
Vitezslav Kriz committed
766
	/* check for NSEC record */
Jan Včelák's avatar
Jan Včelák committed
767
	const knot_rdataset_t *nsec_rrs = node_rdataset(node, KNOT_RRTYPE_NSEC);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
768
	if (nsec_rrs == NULL) {
769
		data->handler->cb(data->handler, data->zone, node,
770
		                  SEM_ERR_NSEC_NONE, NULL);
771
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
772 773 774
	}

	/* Test that only one record is in the NSEC RRSet */
775
	if (nsec_rrs->count != 1) {
776
		data->handler->cb(data->handler, data->zone, node,
777
		                  SEM_ERR_NSEC_RDATA_MULTIPLE, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
778 779
	}

Jan Včelák's avatar
Jan Včelák committed
780
	if (data->next_nsec != node) {
781
		data->handler->cb(data->handler, data->zone, node,
782
		                  SEM_ERR_NSEC_RDATA_CHAIN, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
783 784 785 786 787 788 789 790 791 792 793 794 795
	}

	/*
	 * 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) {
796
		data->handler->cb(data->handler, data->zone, node,
797
		                  SEM_ERR_NSEC_RDATA_CHAIN, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
798
	}
799

800
	return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
801 802
}

803 804 805 806 807 808
/*!
 * \brief Check if node has NSEC3 node.
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
809
static int check_nsec3_presence(const zone_node_t *node, semchecks_data_t *data)
810 811 812 813
{
	bool auth = (node->flags & NODE_FLAGS_NONAUTH) == 0;
	bool deleg = (node->flags & NODE_FLAGS_DELEG) != 0;

814
	if ((deleg && node_rrtype_exists(node, KNOT_RRTYPE_DS)) || (auth && !deleg)) {
815 816
		if (node->nsec3_node == NULL) {
			data->handler->cb(data->handler, data->zone, node,
817
			                  SEM_ERR_NSEC3_NONE, NULL);
818 819
		}
	}
820

821
	return KNOT_EOK;
822 823 824
}

/*!
825
 * \brief Check NSEC3 opt-out.
826 827 828 829
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
830
static int check_nsec3_opt_out(const zone_node_t *node, semchecks_data_t *data)
831
{
832
	if (!(node->nsec3_node == NULL && node->flags & NODE_FLAGS_DELEG)) {
833
		return KNOT_EOK;
834
	}
835
	/* Insecure delegation, check whether it is part of opt-out span. */
836

837 838 839 840 841 842
	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) {
843
		data->handler->cb(data->handler, data->zone, node,
844
		                  SEM_ERR_NSEC3_NONE, NULL);
845
		return KNOT_EOK;
846
	}
847

848 849 850
	const knot_rdataset_t *previous_rrs;
	previous_rrs = node_rdataset(nsec3_previous, KNOT_RRTYPE_NSEC3);
	assert(previous_rrs);
851

852
	/* Check for opt-out flag. */
853
	uint8_t flags = knot_nsec3_flags(previous_rrs, 0);
854 855
	if (!(flags & 1)) {
		data->handler->cb(data->handler, data->zone, node,
856
		                  SEM_ERR_NSEC3_INSECURE_DELEGATION_OPT, NULL);
857
	}
858

859
	return KNOT_EOK;
860 861
}

862
/*!
863 864 865 866 867 868
 * \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
869
 */
870
static int check_nsec3(const zone_node_t *node, semchecks_data_t *data)
871
{
Vitezslav Kriz's avatar
Vitezslav Kriz committed
872
	assert(node);
Jan Včelák's avatar
Jan Včelák committed
873 874
	bool auth = (node->flags & NODE_FLAGS_NONAUTH) == 0;
	bool deleg = (node->flags & NODE_FLAGS_DELEG) != 0;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
875

Jan Včelák's avatar
Jan Včelák committed
876
	if (!auth && !deleg) {
877
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
878
	}
879
	if (node->nsec3_node == NULL) {
880
		return KNOT_EOK;
881 882
	}

883 884 885 886 887 888
	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));

889 890
	knot_rrset_t nsec3_rrs = node_rrset(node->nsec3_node, KNOT_RRTYPE_NSEC3);
	if (knot_rrset_empty(&nsec3_rrs)) {
891
		data->handler->cb(data->handler, data->zone, node,
892
		                  SEM_ERR_NSEC3_NONE, info);
893
		goto nsec3_cleanup;
894
	}
895

Vitezslav Kriz's avatar
Vitezslav Kriz committed
896
	const knot_rdataset_t *soa_rrs = node_rdataset(data->zone->apex, KNOT_RRTYPE_SOA);
897
	assert(soa_rrs);
898
	uint32_t minimum_ttl = knot_soa_minimum(soa_rrs);
899
	if (nsec3_rrs.ttl != minimum_ttl) {
900
		data->handler->cb(data->handler, data->zone, node,
901
		                  SEM_ERR_NSEC3_RDATA_TTL, info);
902 903 904 905 906 907
	}

	// Check parameters.
	const knot_rdataset_t *nsec3param = node_rdataset(data->zone->apex,
	                                                  KNOT_RRTYPE_NSEC3PARAM);
	knot_rdata_t *rrd = knot_rdataset_at(nsec3param, 0);
908
	dnssec_binary_t rdata = { .size = rrd->len, .data = rrd->data };
909 910
	ret = dnssec_nsec3_params_from_rdata(&params_apex, &rdata);
	if (ret != DNSSEC_EOK) {
911
		ret = knot_error_from_libdnssec(ret);
912 913 914
		goto nsec3_cleanup;
	}

915
	if (knot_nsec3_flags(&nsec3_rrs.rrs, 0) > 1) {
916
		data->handler->cb(data->handler, data->zone, node,
917
		                  SEM_ERR_NSEC3_RDATA_FLAGS, info);
918 919 920
	}

	dnssec_binary_t salt = {
921 922
		.size = knot_nsec3_salt_length(&nsec3_rrs.rrs, 0),
		.data = (uint8_t *)knot_nsec3_salt(&nsec3_rrs.rrs, 0),
923 924 925 926
	};

	if (dnssec_binary_cmp(&salt, &params_apex.salt)) {
		data->handler->cb(data->handler, data->zone, node,
927
		                  SEM_ERR_NSEC3_RDATA_SALT, info);
928 929
	}

930
	if (knot_nsec3_algorithm(&nsec3_rrs.rrs, 0) != params_apex.algorithm) {
931
		data->handler->cb(data->handler, data->zone, node,
932
		                  SEM_ERR_NSEC3_RDATA_ALG, info);
933 934
	}

935
	if (knot_nsec3_iterations(&nsec3_rrs.rrs, 0) != params_apex.iterations) {
936
		data->handler->cb(data->handler, data->zone, node,
937
		                  SEM_ERR_NSEC3_RDATA_ITERS, info);
938 939 940
	}

	// Get next nsec3 node.
Vitezslav Kriz's avatar
Vitezslav Kriz committed
941
	const zone_node_t *apex = data->zone->apex;
942
	uint8_t *next_dname_str = NULL;
943
	uint8_t next_dname_str_size = 0;
944
	knot_nsec3_next_hashed(&nsec3_rrs.rrs, 0, &next_dname_str, &next_dname_str_size);
945 946 947 948 949
	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) {
950
		goto nsec3_cleanup;
951
	}
952

953 954 955
	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) {
956
		uint8_t *next = NULL;
957 958 959 960
		int32_t next_len = base32hex_encode_alloc(next_dname_str,
		                                          next_dname_str_size,
		                                          &next);
		char *hash_info = NULL;
961
		if (next != NULL) {
962 963 964 965
			hash_info = sprintf_alloc("(next hash %.*s)", next_len, next);
			free(next);
		}
		data->handler->cb(data->handler, data->zone, node,
966
		                  SEM_ERR_NSEC3_RDATA_CHAIN, hash_info);
967 968
		free(hash_info);
	}
969

970 971 972
	ret = check_rrsig(node->nsec3_node, data);
	if (ret != KNOT_EOK) {
		goto nsec3_cleanup;
Jan Kadlec's avatar
Jan Kadlec committed
973
	}
974

975 976 977
	// 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);
978
		uint16_t type = rrset.type;
979 980
		if (type != KNOT_RRTYPE_NSEC3 && type != KNOT_RRTYPE_RRSIG) {
			data->handler->cb(data->handler, data->zone, node->nsec3_node,
981
			                  SEM_ERR_NSEC3_EXTRA_RECORD, NULL);
982 983
		}
	}
984 985 986 987

nsec3_cleanup:
	dnssec_nsec3_params_free(&params_apex);

988
	return ret;
989
}
990

991 992 993 994 995 996
/*!
 * \brief Check if CNAME record contains other records
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
997
static int check_cname(const zone_node_t *node, semchecks_data_t *data)
998
{
999
	const  knot_rdataset_t *cname_rrs = node_rdataset(node, KNOT_RRTYPE_CNAME);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1000
	if (cname_rrs == NULL) {
1001
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1002
	}
1003

Vitezslav Kriz's avatar
Vitezslav Kriz committed
1004 1005 1006 1007 1008
	unsigned rrset_limit = 1;
	/* With DNSSEC node can contain RRSIGs or NSEC */
	if (node_rrtype_exists(node, KNOT_RRTYPE_NSEC)) {
		rrset_limit += 1;
	}
1009
	if (node_rrtype_exists(node, KNOT_RRTYPE_RRSIG)) {
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1010 1011
		rrset_limit += 1;
	}
1012

Vitezslav Kriz's avatar
Vitezslav Kriz committed
1013
	if (node->rrset_count > rrset_limit) {
1014 1015
		data->handler->fatal_error = true;
		data->handler->cb(data->handler, data->zone, node,
1016
		                  SEM_ERR_CNAME_EXTRA_RECORDS, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1017
	}
1018
	if (cname_rrs->count != 1) {
1019 1020
		data->handler->fatal_error = true;
		data->handler->cb(data->handler, data->zone, node,
1021
		                  SEM_ERR_CNAME_MULTIPLE, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1022
	}
1023 1024

	return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1025
}
1026

1027 1028 1029 1030 1031 1032
/*!
 * \brief Check if DNAME record has children.
 *
 * \param node Node to check
 * \param data Semantic checks context data
 */
1033
static int check_dname(const zone_node_t *node, semchecks_data_t *data)
1034 1035
{
	if (node->parent != NULL && node_rrtype_exists(node->parent, KNOT_RRTYPE_DNAME)) {
1036
		data->handler->fatal_error = true;
1037 1038
		data->handler->cb(data->handler, data->zone, node,
		                  SEM_ERR_DNAME_CHILDREN, NULL);
1039
	}
1040 1041

	return KNOT_EOK;
1042 1043
}

1044
/*!
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1045
 * \brief Check that NSEC chain is cyclic.
1046
 *
1047
 * Run only once per zone. Check that last NSEC node points to first one.
1048 1049
 * \param data Semantic checks context data
 */
1050
static int check_nsec_cyclic(semchecks_data_t *data)
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1051 1052
{
	if (data->next_nsec == NULL) {
1053
		data->handler->cb(data->handler, data->zone, data->zone->apex,
1054
		                  SEM_ERR_NSEC_RDATA_CHAIN, NULL);
1055
		return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1056 1057
	}
	if (!knot_dname_is_equal(data->next_nsec->owner, data->zone->apex->owner)) {
1058
		data->handler->cb(data->handler, data->zone, data->next_nsec,
1059
		                  SEM_ERR_NSEC_RDATA_CHAIN, NULL);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1060
	}
1061

1062
	return KNOT_EOK;
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1063
}
1064

Vitezslav Kriz's avatar
Vitezslav Kriz committed
1065
/*!
1066 1067 1068 1069
 * \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
1070
 *
1071 1072
 * \param node Node to be checked
 * \param data Semantic checks context data
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1073 1074 1075
 */
static int do_checks_in_tree(zone_node_t *node, void *data)
{
1076
	semchecks_data_t *s_data = (semchecks_data_t *)data;
1077

1078 1079
	int ret = KNOT_EOK;

1080 1081 1082
	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);
1083
		}
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1084
	}
1085

1086 1087

	return ret;
1088 1089
}

1090
static void check_nsec3param(knot_rdataset_t *nsec3param, zone_contents_t *zone,
1091
                             sem_handler_t *handler, semchecks_data_t *data)
1092 1093 1094 1095 1096 1097
{
	assert(nsec3param);

	data->level |= NSEC3;
	uint8_t param = knot_nsec3param_flags(nsec3param, 0);
	if ((param & ~1) != 0) {
1098
		handler->cb(handler, zone, zone->apex, SEM_ERR_NSEC3PARAM_RDATA_FLAGS,
1099 1100 1101 1102 1103
		            NULL);
	}

	param = knot_nsec3param_algorithm(nsec3param, 0);
	if (param != DNSSEC_NSEC3_ALGORITHM_SHA1) {
1104
		handler->cb(handler, zone, zone->apex, SEM_ERR_NSEC3PARAM_RDATA_ALG,
1105 1106 1107 1108
		            NULL);
	}
}

1109
static void check_dnskey(zone_contents_t *zone, sem_handler_t *handler)
1110 1111 1112
{
	const knot_rdataset_t *dnskeys = node_rdataset(zone->apex, KNOT_RRTYPE_DNSKEY);
	if (dnskeys == NULL) {
1113
		handler->cb(handler, zone, zone->apex, SEM_ERR_DNSKEY_NONE, NULL);
1114
		return;
1115 1116
	}

1117
	for (int i = 0; i < dnskeys->count; i++) {
1118 1119 1120
		knot_rdata_t *dnskey = knot_rdataset_at(dnskeys, i);
		dnssec_key_t *key;
		int ret = dnssec_key_from_rdata(&key, zone->apex->owner,
1121
		                                dnskey->data, dnskey->len);
1122 1123 1124
		if (ret == KNOT_EOK) {
			dnssec_key_free(key);
		} else {
1125
			handler->cb(handler, zone, zone->apex, SEM_ERR_DNSKEY_INVALID, NULL);
1126 1127 1128
		}

		if (knot_dnskey_proto(dnskeys, i) != 3) {
1129 1130
			handler->cb(handler, zone, zone->apex, SEM_ERR_DNSKEY_RDATA_PROTOCOL,
			            NULL);
1131 1132 1133 1134 1135
		}

		dnssec_key_algorithm_t alg = knot_dnskey_alg(dnskeys, i);
		if (!dnssec_algorithm_key_support(alg)) {
			char *info = sprintf_alloc("(unsupported algorithm %d)", alg);
1136
			handler->cb(handler, zone, zone->apex, SEM_ERR_DNSKEY_INVALID, info);
1137 1138 1139 1140 1141
			free(info);
		}
	}
}

1142 1143
int sem_checks_process(zone_contents_t *zone, bool optional, sem_handler_t *handler,
                       time_t time)
1144
{
1145
	if (zone == NULL || handler == NULL) {
1146
		return KNOT_EINVAL;
1147
	}
Vitezslav Kriz's avatar
Vitezslav Kriz committed
1148

1149 1150 1151 1152 1153
	semchecks_data_t data = {
		.handler = handler,
		.zone = zone,
		.next_nsec = zone->apex,
		.level = MANDATORY,
1154 1155
		.time = time,
	};