sematic-checks: use verification routines from dnssec-verify

This commit is contained in:
Libor Peltan 2022-01-07 17:06:45 +01:00 committed by Daniel Salzman
parent e80a0576ca
commit fa828c5da7
4 changed files with 64 additions and 757 deletions

View file

@ -309,10 +309,14 @@ int kdnssec_validation_ctx(conf_t *conf, kdnssec_ctx_t *ctx, const zone_contents
}
policy_from_zone(ctx->policy, zone);
conf_val_t policy_id = conf_zone_get(conf, C_DNSSEC_POLICY, zone->apex->owner);
conf_id_fix_default(&policy_id);
conf_val_t num_threads = conf_id_get(conf, C_POLICY, C_SIGNING_THREADS, &policy_id);
ctx->policy->signing_threads = conf_int(&num_threads);
if (conf != NULL) {
conf_val_t policy_id = conf_zone_get(conf, C_DNSSEC_POLICY, zone->apex->owner);
conf_id_fix_default(&policy_id);
conf_val_t num_threads = conf_id_get(conf, C_POLICY, C_SIGNING_THREADS, &policy_id);
ctx->policy->signing_threads = conf_int(&num_threads);
} else {
ctx->policy->signing_threads = 1;
}
int ret = kasp_zone_from_contents(ctx->zone, zone, ctx->policy->single_type_signing,
ctx->policy->nsec3_enabled, &ctx->policy->nsec3_iterations,

View file

@ -15,19 +15,16 @@
*/
#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] =
@ -153,7 +150,6 @@ typedef enum {
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;
@ -164,13 +160,6 @@ 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);
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 *);
@ -185,247 +174,11 @@ static const struct check_function CHECK_FUNCTIONS[] = {
{ 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.
*
@ -647,397 +400,6 @@ static int check_ds(const zone_node_t *node, semchecks_data_t *data)
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(&params_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, &params_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(&params_apex);
return ret;
}
/*!
* \brief Check if apex node contains SOA record
*
@ -1134,27 +496,6 @@ static int check_dname(const zone_node_t *node, semchecks_data_t *data)
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.
*
@ -1203,59 +544,37 @@ static void check_nsec3param(knot_rdataset_t *nsec3param, zone_contents_t *zone,
}
}
static void check_dnskey(zone_contents_t *zone, sem_handler_t *handler)
static sem_error_t err_dnssec2sem(int err)
{
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);
}
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;
}
}
static int mark_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 (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);
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;
}
static int unmark_nsec3_optout(zone_node_t *node, _unused_ void *ctx)
{
if (node->flags & NODE_FLAGS_EMPTY) {
node->flags &= ~NODE_FLAGS_EMPTY;
node_parent(node)->children++;
}
return KNOT_EOK;
}
int sem_checks_process(zone_contents_t *zone, semcheck_optional_t optional, sem_handler_t *handler,
@ -1272,7 +591,6 @@ int sem_checks_process(zone_contents_t *zone, semcheck_optional_t optional, sem_
semchecks_data_t data = {
.handler = handler,
.zone = zone,
.next_nsec = zone->apex,
.level = MANDATORY,
.time = time,
};
@ -1293,20 +611,10 @@ int sem_checks_process(zone_contents_t *zone, semcheck_optional_t optional, sem_
} 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;
}
@ -1314,13 +622,10 @@ int sem_checks_process(zone_contents_t *zone, semcheck_optional_t optional, sem_
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;
}

View file

@ -1,4 +1,4 @@
; Zone without any semantic error
; Delegation NS and glue signed despite mustn't.
example.com. 3600 IN SOA dns1.example.com. hostmaster.example.com. (
2010111220 ; serial

View file

@ -95,34 +95,31 @@ expect_error "glue_apex_one.missing" 0 1 "$NS_GLUE"
expect_error "glue_besides.missing" 0 1 "$NS_GLUE"
expect_error "glue_deleg.missing" 0 1 "$NS_GLUE"
expect_error "glue_in_apex.missing" 0 1 "$NS_GLUE"
expect_error "different_signer_name.signed" 0 1 "$RRSIG_RDATA_DNSKEY_OWNER \(record type NSEC\)"
expect_error "different_signer_name.signed" 0 1 "$RRSIG_UNVERIFIABLE \(record type NSEC\)"
expect_error "no_rrsig.signed" 0 1 "$RRSIG_NO_RRSIG \(record type A\)"
expect_error "no_rrsig.signed" 0 1 "$RRSIG_NO_RRSIG \(record type NSEC\)"
expect_error "no_rrsig_with_delegation.signed" 0 1 "$RRSIG_NO_RRSIG \(record type NSEC\)"
expect_error "different_signer_name.signed" 0 1 "$RRSIG_UNVERIFIABLE"
expect_error "no_rrsig.signed" 0 1 "$RRSIG_UNVERIFIABLE"
expect_error "no_rrsig_with_delegation.signed" 0 1 "$RRSIG_UNVERIFIABLE"
expect_error "nsec_broken_chain_01.signed" 0 1 "$NSEC_RDATA_CHAIN"
expect_error "nsec_broken_chain_02.signed" 0 1 "$NSEC_RDATA_CHAIN"
expect_error "nsec_missing.signed" 0 1 "$NSEC_NONE"
expect_error "nsec_multiple.signed" 0 1 "$NSEC_RDATA_MULTIPLE"
expect_error "nsec_missing.signed" 0 1 "$NSEC_RDATA_BITMAP"
expect_error "nsec_multiple.signed" 0 1 "$NSEC_RDATA_BITMAP"
expect_error "nsec_wrong_bitmap_01.signed" 0 1 "$NSEC_RDATA_BITMAP"
expect_error "nsec_wrong_bitmap_02.signed" 0 1 "$NSEC_RDATA_BITMAP"
expect_error "nsec3_missing.signed" 0 1 "$NSEC3_NONE"
expect_error "nsec3_optout_ent.invalid" 0 1 "$NSEC3_NONE"
expect_error "nsec3_wrong_bitmap_01.signed" 0 1 "$NSEC3_RDATA_BITMAP"
expect_error "nsec3_wrong_bitmap_02.signed" 0 1 "$NSEC3_RDATA_BITMAP"
expect_error "nsec3_ds.signed" 0 1 "$NSEC3_NONE"
expect_error "nsec3_missing.signed" 0 1 "$NSEC_RDATA_BITMAP"
expect_error "nsec3_optout_ent.invalid" 0 1 "$NSEC_RDATA_BITMAP"
expect_error "nsec3_wrong_bitmap_01.signed" 0 1 "$NSEC_RDATA_BITMAP"
expect_error "nsec3_wrong_bitmap_02.signed" 0 1 "$NSEC_RDATA_BITMAP"
expect_error "nsec3_ds.signed" 0 1 "$NSEC_RDATA_BITMAP"
expect_error "nsec3_optout.signed" 0 1 "$NSEC3_INSECURE_DELEGATION_OPT"
expect_error "nsec3_chain_01.signed" 0 1 "$NSEC3_RDATA_CHAIN"
expect_error "nsec3_chain_02.signed" 0 2 "$NSEC3_RDATA_CHAIN"
expect_error "nsec3_chain_03.signed" 0 2 "$NSEC3_RDATA_CHAIN"
expect_error "nsec3_param_invalid.signed" 0 1 "$NSEC3_ALG"
expect_error "nsec3_param_invalid.signed" 0 1 "$NSEC3_ITERS"
expect_error "nsec3_chain_01.signed" 0 1 "$NSEC_RDATA_CHAIN"
expect_error "nsec3_chain_02.signed" 0 1 "$NSEC_RDATA_CHAIN"
expect_error "nsec3_chain_03.signed" 0 1 "$NSEC_RDATA_CHAIN"
expect_error "nsec3_param_invalid.signed" 0 1 "$NSEC_RDATA_BITMAP"
expect_error "nsec3_param_invalid.signed" 0 1 "$NSEC3PARAM_FLAGS"
expect_error "rrsig_signed.signed" 0 1 "$RRSIG_SIGNED"
expect_error "rrsig_rdata_ttl.signed" 0 1 "$RRSIG_RDATA_TTL \(record type A\)"
expect_error "duplicate.signature" 0 7 "$RRSIG_EXPIRED"
expect_error "missing.signed" 0 1 "$NSEC_NONE"
expect_error "dnskey_param_error.signed" 0 1 "$DNSKEY_PROTO"
expect_error "rrsig_signed.signed" 0 1 "$RRSIG_UNVERIFIABLE"
expect_error "rrsig_rdata_ttl.signed" 0 1 "$RRSIG_UNVERIFIABLE"
expect_error "duplicate.signature" 0 1 "$RRSIG_UNVERIFIABLE"
expect_error "missing.signed" 0 1 "$NSEC_RDATA_BITMAP"
expect_error "dnskey_param_error.signed" 0 1 "$RRSIG_UNVERIFIABLE" # FIXME this shall rather fail on invalid pubkey
expect_error "invalid_ds.signed" 0 2 "$DS_ALG \(keytag 60485\)"
expect_error "cdnskey.invalid" 0 1 "$CDS_NOT_MATCH"
expect_error "cdnskey.invalid.param" 0 1 "$CDS_NOT_MATCH"
@ -133,10 +130,10 @@ expect_error "cdnskey.orphan.cds" 0 1 "$CDS_NOT_MATCH"
expect_error "cdnskey.orphan.cdnskey" 0 1 "$CDNSKEY_NO_CDS"
expect_error "cdnskey.delete.invalid.cds" 0 1 "$CDNSKEY_DELETE"
expect_error "cdnskey.delete.invalid.cdnskey" 0 1 "$CDNSKEY_DELETE"
expect_error "delegation.signed" 0 1 "$NSEC_RDATA_BITMAP"
test_correct "rrsig_ttl.signed"
test_correct "no_error_delegation_bitmap.signed"
test_correct "no_error_nsec3_delegation.signed"
test_correct "no_error_nsec3_optout.signed"
test_correct "glue_wildcard.valid"
test_correct "glue_no_foreign.valid"
@ -178,5 +175,6 @@ test_correct_no_dnssec "cdnskey.orphan.cds"
test_correct_no_dnssec "cdnskey.orphan.cdnskey"
test_correct_no_dnssec "cdnskey.delete.invalid.cds"
test_correct_no_dnssec "cdnskey.delete.invalid.cdnskey"
test_correct_no_dnssec "delegation.signed"
rm $LOG