*/
#include <stdio.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
+
+#include "knot/zone/semantic-check.h"
#include "libdnssec/error.h"
-#include "contrib/base32hex.h"
+#include "libdnssec/key.h"
#include "contrib/string.h"
#include "libknot/libknot.h"
-#include "knot/zone/semantic-check.h"
-#include "knot/dnssec/rrset-sign.h"
+#include "knot/dnssec/key-events.h"
#include "knot/dnssec/zone-keys.h"
-#include "knot/dnssec/zone-nsec.h"
+#include "knot/updates/zone-update.h"
static const char *error_messages[SEM_ERR_UNKNOWN + 1] = {
[SEM_ERR_SOA_NONE] =
typedef struct {
zone_contents_t *zone;
sem_handler_t *handler;
- const zone_node_t *next_nsec;
check_level_t level;
time_t time;
} semchecks_data_t;
static int check_delegation(const zone_node_t *node, semchecks_data_t *data);
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);
-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);
-static int check_rrsig_signed(const zone_node_t *node, semchecks_data_t *data);
-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);
struct check_function {
int (*function)(const zone_node_t *, semchecks_data_t *);
{ check_delegation, MANDATORY | SOFT }, // mandatory for apex, optional for others
{ check_ds, OPTIONAL },
{ check_submission, NSEC | NSEC3 },
- { check_rrsig, NSEC | NSEC3 },
- { check_rrsig_signed, NSEC | NSEC3 },
- { check_nsec_bitmap, NSEC | NSEC3 },
- { check_nsec, NSEC },
- { check_nsec3, NSEC3 },
- { check_nsec3_presence, NSEC3 },
- { check_nsec3_opt_out, NSEC3 },
};
static const int CHECK_FUNCTIONS_LEN = sizeof(CHECK_FUNCTIONS)
/ sizeof(struct check_function);
-static int check_signature(const knot_rdata_t *rrsig, const dnssec_key_t *key,
- const knot_rrset_t *covered)
-{
- if (!rrsig || !key || !dnssec_key_can_verify(key)) {
- return KNOT_EINVAL;
- }
-
- int ret = KNOT_EOK;
- dnssec_sign_ctx_t *sign_ctx = NULL;
-
- dnssec_binary_t signature = {
- .size = knot_rrsig_signature_len(rrsig),
- .data = (uint8_t *)knot_rrsig_signature(rrsig)
- };
- if (!signature.data || !signature.size) {
- ret = KNOT_EINVAL;
- goto fail;
- }
-
- if (dnssec_sign_new(&sign_ctx, key) != KNOT_EOK) {
- ret = KNOT_ENOMEM;
- goto fail;
- }
-
- if (knot_sign_ctx_add_data(sign_ctx, rrsig->data, covered) != KNOT_EOK) {
- ret = KNOT_ENOMEM;
- goto fail;
- }
-
- if (dnssec_sign_verify(sign_ctx, false, &signature) != KNOT_EOK) {
- ret = KNOT_EINVAL;
- goto fail;
- }
-
-fail:
- dnssec_sign_free(sign_ctx);
- return ret;
-}
-
-/*!
- * \brief Semantic check - RRSIG rdata.
- *
- * \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 rdata.
- * \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.
- *
- * \retval KNOT_EOK on success.
- * \return Appropriate error code if error was found.
- */
-static int check_rrsig_rdata(sem_handler_t *handler,
- const zone_contents_t *zone,
- const zone_node_t *node,
- const knot_rdata_t *rrsig,
- const knot_rrset_t *rrset,
- time_t context,
- check_level_t level,
- bool *verified)
-{
- /* Prepare additional info string. */
- char info_str[50] = "";
- char type_str[16] = "";
- knot_rrtype_to_string(rrset->type, type_str, sizeof(type_str));
- int ret = snprintf(info_str, sizeof(info_str), "(record type %s)", type_str);
- if (ret < 0 || ret >= sizeof(info_str)) {
- return KNOT_ENOMEM;
- }
-
- if (knot_rrsig_type_covered(rrsig) != rrset->type) {
- handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_TYPE_COVERED,
- info_str);
- }
-
- /* label number at the 2nd index should be same as owner's */
- uint8_t labels_rdata = knot_rrsig_labels(rrsig);
-
- size_t tmp = knot_dname_labels(rrset->owner, NULL) - labels_rdata;
- if (tmp != 0) {
- /* if name has wildcard, label must not be included */
- if (!knot_dname_is_wildcard(rrset->owner)) {
- handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_LABELS,
- info_str);
- } else if (tmp != 1) {
- handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_LABELS,
- info_str);
- }
- }
-
- /* Check original TTL. */
- uint32_t original_ttl = knot_rrsig_original_ttl(rrsig);
- if (original_ttl != rrset->ttl) {
- handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_TTL,
- info_str);
- }
-
- /* Check for expired signature. */
- if (knot_rrsig_sig_expiration(rrsig) < context) {
- handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_EXPIRATION,
- info_str);
- }
-
- /* Check inception */
- if (knot_rrsig_sig_inception(rrsig) > context) {
- handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_INCEPTION,
- info_str);
- }
-
- /* Check signer name. */
- const knot_dname_t *signer = knot_rrsig_signer_name(rrsig);
- if (!knot_dname_is_equal(signer, zone->apex->owner)) {
- handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_OWNER,
- 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->count; i++) {
- knot_rdata_t *dnskey = knot_rdataset_at(dnskeys, i);
- uint16_t flags = knot_dnskey_flags(dnskey);
- uint8_t proto = knot_dnskey_proto(dnskey);
- /* RFC 4034 2.1.1 & 2.1.2 */
- if (flags & DNSKEY_FLAGS_ZSK && proto == 3) {
- dnssec_key_t *key;
-
- ret = dnssec_key_from_rdata(&key, zone->apex->owner,
- dnskey->data, dnskey->len);
- if (ret != KNOT_EOK) {
- continue;
- }
-
- if (dnssec_key_get_keytag(key) != knot_rrsig_key_tag(rrsig)) {
- dnssec_key_free(key);
- continue;
- }
-
- ret = check_signature(rrsig, key, rrset);
- dnssec_key_free(key);
- if (ret == KNOT_EOK) {
- *verified = true;
- break;
- }
- }
- }
- }
-
- return KNOT_EOK;
-}
-
-static int check_rrsig_signed(const zone_node_t *node, semchecks_data_t *data)
-{
- /* signed rrsig - nonsense */
- if (node_rrtype_is_signed(node, KNOT_RRTYPE_RRSIG)) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_RRSIG_SIGNED, NULL);
- }
-
- return KNOT_EOK;
-}
-/*!
- * \brief Semantic check - RRSet's RRSIG.
- *
- * \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.
- *
- * \retval KNOT_EOK on success.
- * \return Appropriate error code if error was found.
- */
-static int check_rrsig_in_rrset(sem_handler_t *handler,
- const zone_contents_t *zone,
- const zone_node_t *node,
- const knot_rrset_t *rrset,
- time_t context,
- check_level_t level)
-{
- if (handler == NULL || node == NULL || rrset == NULL) {
- return KNOT_EINVAL;
- }
- /* Prepare additional info string. */
- char info_str[50] = "";
- char type_str[16] = "";
- knot_rrtype_to_string(rrset->type, type_str, sizeof(type_str));
- int ret = snprintf(info_str, sizeof(info_str), "(record type %s)", type_str);
- if (ret < 0 || ret >= sizeof(info_str)) {
- return KNOT_ENOMEM;
- }
-
- knot_rrset_t node_rrsigs = node_rrset(node, KNOT_RRTYPE_RRSIG);
-
- knot_rdataset_t rrsigs;
- knot_rdataset_init(&rrsigs);
- ret = knot_synth_rrsig(rrset->type, &node_rrsigs.rrs, &rrsigs, NULL);
- if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
- return ret;
- }
- if (ret == KNOT_ENOENT) {
- handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_NO_RRSIG, info_str);
- return KNOT_EOK;
- }
-
- bool verified = false;
- knot_rdata_t *rrsig = rrsigs.rdata;
- for (uint16_t i = 0; ret == KNOT_EOK && i < rrsigs.count; ++i) {
- ret = check_rrsig_rdata(handler, zone, node, rrsig, rrset,
- context, level, &verified);
- rrsig = knot_rdataset_next(rrsig);
- }
- /* Only one rrsig of covered record needs to be verified by DNSKEY. */
- if (!verified) {
- handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_UNVERIFIABLE,
- info_str);
- }
-
- knot_rdataset_clear(&rrsigs, NULL);
- return KNOT_EOK;;
-}
-
/*!
* \brief Check if glue record for delegation is present.
*
return KNOT_EOK;
}
-/*!
- * \brief Run all semantic check related to RRSIG record
- *
- * \param node Node to check
- * \param data Semantic checks context data
- */
-static int check_rrsig(const zone_node_t *node, semchecks_data_t *data)
-{
- assert(node);
- if (node->flags & NODE_FLAGS_NONAUTH) {
- return KNOT_EOK;
- }
-
- bool deleg = node->flags & NODE_FLAGS_DELEG;
-
- int ret = KNOT_EOK;
-
- int rrset_count = node->rrset_count;
- for (int i = 0; ret == KNOT_EOK && i < rrset_count; i++) {
- knot_rrset_t rrset = node_rrset_at(node, i);
- if (rrset.type == KNOT_RRTYPE_RRSIG) {
- continue;
- }
- if (deleg && rrset.type != KNOT_RRTYPE_NSEC &&
- rrset.type != KNOT_RRTYPE_DS ) {
- continue;
- }
-
- ret = check_rrsig_in_rrset(data->handler, data->zone, node, &rrset,
- data->time, data->level);
- }
- return ret;
-}
-
-/*!
- * \brief Add all RR types from a node into the bitmap.
- */
-static void bitmap_add_all_node_rrsets(dnssec_nsec_bitmap_t *bitmap,
- const zone_node_t *node)
-{
- bool deleg = node->flags & NODE_FLAGS_DELEG;
- 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 &&
- rr.type != KNOT_RRTYPE_NSEC &&
- rr.type != KNOT_RRTYPE_RRSIG)) {
- continue;
- }
- dnssec_nsec_bitmap_add(bitmap, rr.type);
- }
-}
-
-static char *nsec3_info(const knot_dname_t *owner, char *out, size_t out_len)
-{
- knot_dname_txt_storage_t buff;
- 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;
-}
-
-/*!
- * \brief Check NSEC and NSEC3 type bitmap
- *
- * \param node Node to check
- * \param data Semantic checks context data
- */
-static int check_nsec_bitmap(const zone_node_t *node, semchecks_data_t *data)
-{
- assert(node);
- if (node->flags & NODE_FLAGS_NONAUTH) {
- return KNOT_EOK;
- }
-
- bool nsec = data->level & NSEC;
- knot_rdataset_t *nsec_rrs = NULL;
-
- zone_node_t *nsec3_node = node_nsec3_get(node);
-
- if (nsec) {
- nsec_rrs = node_rdataset(node, KNOT_RRTYPE_NSEC);
- } else if (nsec3_node != NULL) {
- nsec_rrs = node_rdataset(nsec3_node, KNOT_RRTYPE_NSEC3);
- }
- if (nsec_rrs == NULL) {
- return KNOT_EOK;
- }
-
- // create NSEC bitmap from node
- dnssec_nsec_bitmap_t *node_bitmap = dnssec_nsec_bitmap_new();
- if (node_bitmap == NULL) {
- return KNOT_ENOMEM;
- }
- 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);
- return KNOT_ENOMEM;
- }
- dnssec_nsec_bitmap_write(node_bitmap, node_wire);
- dnssec_nsec_bitmap_free(node_bitmap);
-
- // get NSEC bitmap from NSEC node
- const uint8_t *nsec_wire = NULL;
- uint16_t nsec_wire_size = 0;
- if (nsec) {
- nsec_wire = knot_nsec_bitmap(nsec_rrs->rdata);
- nsec_wire_size = knot_nsec_bitmap_len(nsec_rrs->rdata);
- } else {
- nsec_wire = knot_nsec3_bitmap(nsec_rrs->rdata);
- nsec_wire_size = knot_nsec3_bitmap_len(nsec_rrs->rdata);
- }
-
- if (node_wire_size != nsec_wire_size ||
- memcmp(node_wire, nsec_wire, node_wire_size) != 0) {
- char buff[50 + KNOT_DNAME_TXT_MAXLEN];
- char *info = nsec ? NULL : nsec3_info(nsec3_node->owner,
- buff, sizeof(buff));
- data->handler->cb(data->handler, data->zone, node->owner,
- (nsec ? SEM_ERR_NSEC_RDATA_BITMAP : SEM_ERR_NSEC3_RDATA_BITMAP),
- info);
- }
-
- free(node_wire);
- return KNOT_EOK;
-}
-
-/*!
- * \brief Run NSEC related semantic checks
- *
- * \param node Node to check
- * \param data Semantic checks context data
- */
-static int check_nsec(const zone_node_t *node, semchecks_data_t *data)
-{
- assert(node);
- if (node->flags & NODE_FLAGS_NONAUTH) {
- return KNOT_EOK;
- }
-
- if (node->rrset_count == 0) { // empty nonterminal
- return KNOT_EOK;
- }
-
- /* check for NSEC record */
- const knot_rdataset_t *nsec_rrs = node_rdataset(node, KNOT_RRTYPE_NSEC);
- if (nsec_rrs == NULL) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC_NONE, NULL);
- return KNOT_EOK;
- }
-
- /* Test that only one record is in the NSEC RRSet */
- if (nsec_rrs->count != 1) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC_RDATA_MULTIPLE, NULL);
- }
-
- if (data->next_nsec != node) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC_RDATA_CHAIN, NULL);
- }
-
- /*
- * 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.
- */
- knot_dname_storage_t next_domain;
- if (knot_dname_store(next_domain, knot_nsec_next(nsec_rrs->rdata)) == 0) {
- return KNOT_EINVAL;
- }
- knot_dname_to_lower(next_domain);
-
- data->next_nsec = zone_contents_find_node(data->zone, next_domain);
- if (data->next_nsec == NULL) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC_RDATA_CHAIN, NULL);
- }
-
- return KNOT_EOK;
-}
-
-static bool nsec3_optout_allow(const zone_node_t *node)
-{
- return (node->flags & NODE_FLAGS_DELEG) && !node_rrtype_exists(node, KNOT_RRTYPE_DS);
-}
-
-/*!
- * \brief Check if node has NSEC3 node.
- *
- * \param node Node to check
- * \param data Semantic checks context data
- */
-static int check_nsec3_presence(const zone_node_t *node, semchecks_data_t *data)
-{
- bool auth = (node->flags & NODE_FLAGS_NONAUTH) == 0;
- bool empty = (node->flags & NODE_FLAGS_EMPTY) != 0;
-
- if (!nsec3_optout_allow(node) && auth && !empty) {
- if (node_nsec3_get(node) == NULL) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC3_NONE, NULL);
- }
- }
-
- return KNOT_EOK;
-}
-
-/*!
- * \brief Check NSEC3 opt-out.
- *
- * \param node Node to check
- * \param data Semantic checks context data
- */
-static int check_nsec3_opt_out(const zone_node_t *node, semchecks_data_t *data)
-{
- if (!(node_nsec3_get(node) == NULL && node->flags & (NODE_FLAGS_DELEG | NODE_FLAGS_EMPTY))) {
- return KNOT_EOK;
- }
- /* Insecure delegation, check whether it is part of opt-out span. */
-
- 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) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC3_NONE, NULL);
- return KNOT_EOK;
- }
-
- const knot_rdataset_t *previous_rrs;
- previous_rrs = node_rdataset(nsec3_previous, KNOT_RRTYPE_NSEC3);
- assert(previous_rrs);
-
- /* Check for opt-out flag. */
- uint8_t flags = knot_nsec3_flags(previous_rrs->rdata);
- if (!(flags & 1)) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC3_INSECURE_DELEGATION_OPT, NULL);
- }
-
- return KNOT_EOK;
-}
-
-/*!
- * \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
- */
-static int check_nsec3(const zone_node_t *node, semchecks_data_t *data)
-{
- assert(node);
- bool auth = (node->flags & NODE_FLAGS_NONAUTH) == 0;
- bool deleg = (node->flags & NODE_FLAGS_DELEG) != 0;
-
- if (!auth && !deleg) {
- return KNOT_EOK;
- }
-
- zone_node_t *nsec3_node = node_nsec3_get(node);
- if (nsec3_node == NULL) {
- return KNOT_EOK;
- }
-
- dnssec_nsec3_params_t params_apex = { 0 };
- int ret = KNOT_EOK;
-
- char buff[50 + KNOT_DNAME_TXT_MAXLEN];
- char *info = nsec3_info(nsec3_node->owner, buff, sizeof(buff));
-
- knot_rrset_t nsec3_rrs = node_rrset(nsec3_node, KNOT_RRTYPE_NSEC3);
- if (knot_rrset_empty(&nsec3_rrs)) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC3_NONE, info);
- goto nsec3_cleanup;
- }
-
- knot_rrset_t soa_rrset = node_rrset(data->zone->apex, KNOT_RRTYPE_SOA);
- assert(!knot_rrset_empty(&soa_rrset));
- uint32_t minimum_ttl = knot_soa_minimum(soa_rrset.rrs.rdata);
- if (nsec3_rrs.ttl != MIN(minimum_ttl, soa_rrset.ttl)) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC3_RDATA_TTL, info);
- }
-
- // Check parameters.
- const knot_rdataset_t *nsec3param = node_rdataset(data->zone->apex,
- KNOT_RRTYPE_NSEC3PARAM);
- dnssec_binary_t rdata = {
- .size = nsec3param->rdata->len,
- .data = nsec3param->rdata->data
- };
- ret = dnssec_nsec3_params_from_rdata(¶ms_apex, &rdata);
- if (ret != DNSSEC_EOK) {
- ret = knot_error_from_libdnssec(ret);
- goto nsec3_cleanup;
- }
-
- if (knot_nsec3_flags(nsec3_rrs.rrs.rdata) > 1) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC3_RDATA_FLAGS, info);
- }
-
- dnssec_binary_t salt = {
- .size = knot_nsec3_salt_len(nsec3_rrs.rrs.rdata),
- .data = (uint8_t *)knot_nsec3_salt(nsec3_rrs.rrs.rdata),
- };
-
- if (dnssec_binary_cmp(&salt, ¶ms_apex.salt)) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC3_RDATA_SALT, info);
- }
-
- if (knot_nsec3_alg(nsec3_rrs.rrs.rdata) != params_apex.algorithm) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC3_RDATA_ALG, info);
- }
-
- if (knot_nsec3_iters(nsec3_rrs.rrs.rdata) != params_apex.iterations) {
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC3_RDATA_ITERS, info);
- }
-
- // Get next nsec3 node.
- const zone_node_t *apex = data->zone->apex;
- const uint8_t *next_dname_str = knot_nsec3_next(nsec3_rrs.rrs.rdata);
- uint8_t next_dname_str_size = knot_nsec3_next_len(nsec3_rrs.rrs.rdata);
- knot_dname_storage_t next_dname;
- ret = knot_nsec3_hash_to_dname(next_dname, sizeof(next_dname),
- next_dname_str, next_dname_str_size,
- apex->owner);
- if (ret != KNOT_EOK) {
- goto nsec3_cleanup;
- }
-
- const zone_node_t *next_nsec3 = zone_contents_find_nsec3_node(data->zone,
- next_dname);
- if (next_nsec3 == NULL || node_prev(next_nsec3) != nsec3_node) {
- uint8_t *next = NULL;
- int32_t next_len = knot_base32hex_encode_alloc(next_dname_str,
- next_dname_str_size,
- &next);
- char *hash_info = NULL;
- if (next != NULL) {
- hash_info = sprintf_alloc("(next hash %.*s)", next_len, next);
- free(next);
- }
- data->handler->cb(data->handler, data->zone, node->owner,
- SEM_ERR_NSEC3_RDATA_CHAIN, hash_info);
- free(hash_info);
- }
-
- ret = check_rrsig(nsec3_node, data);
- if (ret != KNOT_EOK) {
- goto nsec3_cleanup;
- }
-
- // Check that the node only contains NSEC3 and RRSIG.
- for (int i = 0; ret == KNOT_EOK && i < nsec3_node->rrset_count; i++) {
- knot_rrset_t rrset = node_rrset_at(nsec3_node, i);
- uint16_t type = rrset.type;
- if (type != KNOT_RRTYPE_NSEC3 && type != KNOT_RRTYPE_RRSIG) {
- data->handler->cb(data->handler, data->zone, nsec3_node->owner,
- SEM_ERR_NSEC3_EXTRA_RECORD, NULL);
- }
- }
-
-nsec3_cleanup:
- dnssec_nsec3_params_free(¶ms_apex);
-
- return ret;
-}
-
/*!
* \brief Check if apex node contains SOA record
*
return KNOT_EOK;
}
-/*!
- * \brief Check that NSEC chain is cyclic.
- *
- * Run only once per zone. Check that last NSEC node points to first one.
- * \param data Semantic checks context data
- */
-static int check_nsec_cyclic(semchecks_data_t *data)
-{
- if (data->next_nsec == NULL) {
- data->handler->cb(data->handler, data->zone, data->zone->apex->owner,
- SEM_ERR_NSEC_RDATA_CHAIN, NULL);
- return KNOT_EOK;
- }
- if (!knot_dname_is_equal(data->next_nsec->owner, data->zone->apex->owner)) {
- data->handler->cb(data->handler, data->zone, data->next_nsec->owner,
- SEM_ERR_NSEC_RDATA_CHAIN, NULL);
- }
-
- return KNOT_EOK;
-}
-
/*!
* \brief Call all semantic checks for each node.
*
}
}
-static void check_dnskey(zone_contents_t *zone, sem_handler_t *handler)
-{
- const knot_rdataset_t *dnskeys = node_rdataset(zone->apex, KNOT_RRTYPE_DNSKEY);
- if (dnskeys == NULL) {
- handler->cb(handler, zone, zone->apex->owner, SEM_ERR_DNSKEY_NONE, NULL);
- return;
- }
-
- for (int i = 0; i < dnskeys->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,
- dnskey->data, dnskey->len);
- if (ret == KNOT_EOK) {
- dnssec_key_free(key);
- } else {
- handler->cb(handler, zone, zone->apex->owner, SEM_ERR_DNSKEY_INVALID, NULL);
- }
-
- if (knot_dnskey_proto(dnskey) != 3) {
- handler->cb(handler, zone, zone->apex->owner, SEM_ERR_DNSKEY_RDATA_PROTOCOL,
- NULL);
- }
-
- dnssec_key_algorithm_t alg = knot_dnskey_alg(dnskey);
- if (!dnssec_algorithm_key_support(alg)) {
- char *info = sprintf_alloc("(unsupported algorithm %d)", alg);
- handler->cb(handler, zone, zone->apex->owner, SEM_ERR_DNSKEY_INVALID, info);
- free(info);
- }
- }
-}
-
-static int mark_nsec3_optout(zone_node_t *node, _unused_ void *ctx)
+static sem_error_t err_dnssec2sem(int err)
{
- if (nsec3_optout_allow(node) && node_nsec3_get(node) == NULL) {
- do {
- assert(!(node->flags & NODE_FLAGS_APEX));
- node->flags |= NODE_FLAGS_EMPTY;
- node = node_parent(node);
- node->children--;
- } while (node->rrset_count == 0 && node->children == 0 && node_nsec3_get(node) == NULL);
+ switch (err) {
+ case KNOT_DNSSEC_ENOSIG:
+ return SEM_ERR_RRSIG_UNVERIFIABLE;
+ case KNOT_DNSSEC_ENSEC_BITMAP:
+ return SEM_ERR_NSEC_RDATA_BITMAP;
+ case KNOT_DNSSEC_ENSEC_CHAIN:
+ return SEM_ERR_NSEC_RDATA_CHAIN;
+ case KNOT_DNSSEC_ENSEC3_OPTOUT:
+ return SEM_ERR_NSEC3_INSECURE_DELEGATION_OPT;
+ default:
+ return SEM_ERR_UNKNOWN;
}
- return KNOT_EOK;
}
-static int unmark_nsec3_optout(zone_node_t *node, _unused_ void *ctx)
+static int verify_dnssec(zone_contents_t *zone, sem_handler_t *handler, time_t time)
{
- if (node->flags & NODE_FLAGS_EMPTY) {
- node->flags &= ~NODE_FLAGS_EMPTY;
- node_parent(node)->children++;
+ zone_update_t fake_up = { .new_cont = zone, };
+ int ret = knot_dnssec_validate_zone(&fake_up, NULL, time, false);
+ if (fake_up.validation_hint.node != NULL) { // validation found an issue
+ char type_str[16] = { 0 };
+ knot_rrtype_to_string(fake_up.validation_hint.rrtype, type_str, sizeof(type_str));
+ handler->cb(handler, zone, fake_up.validation_hint.node, err_dnssec2sem(ret), type_str);
+ return KNOT_EOK;
+ } else if (ret == KNOT_INVALID_PUBLIC_KEY) { // validation failed due to invalid DNSKEY
+ handler->cb(handler, zone, zone->apex->owner, SEM_ERR_DNSKEY_INVALID, NULL);
+ return KNOT_EOK;
+ } else { // validation failed by itself
+ return ret;
}
- return KNOT_EOK;
}
int sem_checks_process(zone_contents_t *zone, semcheck_optional_t optional, sem_handler_t *handler,
semchecks_data_t data = {
.handler = handler,
.zone = zone,
- .next_nsec = zone->apex,
.level = MANDATORY,
.time = time,
};
} else {
data.level |= NSEC;
}
- check_dnskey(zone, handler);
}
}
- if (data.level & NSEC3) {
- int ret = zone_tree_apply(zone->nodes, mark_nsec3_optout, NULL);
- if (ret != KNOT_EOK) {
- return ret;
- }
- }
int ret = zone_contents_apply(zone, do_checks_in_tree, &data);
- if (data.level & NSEC3) {
- (void)zone_tree_apply(zone->nodes, unmark_nsec3_optout, NULL);
- }
if (ret != KNOT_EOK) {
return ret;
}
return KNOT_ESEMCHECK;
}
- // check cyclic chain after every node was checked
- if (data.level & NSEC) {
- check_nsec_cyclic(&data);
- }
- if (data.handler->fatal_error) {
- return KNOT_ESEMCHECK;
+ if (optional == SEMCHECK_DNSSEC_ON ||
+ (optional == SEMCHECK_DNSSEC_AUTO && zone->dnssec)) {
+ ret = verify_dnssec(zone, handler, time);
}
- return KNOT_EOK;
+ return ret;
}