Commit afe4d521 authored by Daniel Salzman's avatar Daniel Salzman

Merge branch 'nsec3_opt_out' into 'master'

Nsec3 opt out

Closes #229

See merge request !844
parents b8d067e9 d4fabf99
......@@ -568,6 +568,7 @@ policy:
rrsig\-refresh: TIME
nsec3: BOOL
nsec3\-iterations: INT
nsec3\-opt\-out: BOOL
nsec3\-salt\-length: INT
nsec3\-salt\-lifetime: TIME
ksk\-submission: submission_id
......@@ -705,6 +706,20 @@ Specifies if NSEC3 will be used instead of NSEC.
A number of additional times the hashing is performed.
.sp
\fIDefault:\fP 5
.SS nsec3\-opt\-out
.sp
If set, NSEC3 records won\(aqt be created for insecure delegations.
This speeds up the zone signing and reduces overall zone size.
.sp
\fBWARNING:\fP
.INDENT 0.0
.INDENT 3.5
NSEC3 with the Opt\-Out bit set no longer works as a proof of non\-existence
in this zone.
.UNINDENT
.UNINDENT
.sp
\fIDefault:\fP off
.SS nsec3\-salt\-length
.sp
A length of a salt field in octets, which is appended to the original owner
......
......@@ -638,6 +638,7 @@ DNSSEC policy configuration.
rrsig-refresh: TIME
nsec3: BOOL
nsec3-iterations: INT
nsec3-opt-out: BOOL
nsec3-salt-length: INT
nsec3-salt-lifetime: TIME
ksk-submission: submission_id
......@@ -810,6 +811,20 @@ A number of additional times the hashing is performed.
*Default:* 5
.. _policy_nsec3-opt-out:
nsec3-opt-out
-------------
If set, NSEC3 records won't be created for insecure delegations.
This speeds up the zone signing and reduces overall zone size.
.. WARNING::
NSEC3 with the Opt-Out bit set no longer works as a proof of non-existence
in this zone.
*Default:* off
.. _policy_nsec3-salt-length:
nsec3-salt-length
......
......@@ -255,6 +255,7 @@ static const yp_item_t desc_policy[] = {
CONF_IO_FRLD_ZONES },
{ C_NSEC3, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
{ C_NSEC3_ITER, YP_TINT, YP_VINT = { 0, UINT16_MAX, 10 }, CONF_IO_FRLD_ZONES },
{ C_NSEC3_OPT_OUT, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
{ C_NSEC3_SALT_LEN, YP_TINT, YP_VINT = { 0, UINT8_MAX, 8 }, CONF_IO_FRLD_ZONES },
{ C_NSEC3_SALT_LIFETIME, YP_TINT, YP_VINT = { 1, UINT32_MAX, DAYS(30), YP_STIME },
CONF_IO_FRLD_ZONES },
......
......@@ -86,6 +86,7 @@
#define C_NOTIFY "\x06""notify"
#define C_NSEC3 "\x05""nsec3"
#define C_NSEC3_ITER "\x10""nsec3-iterations"
#define C_NSEC3_OPT_OUT "\x0D""nsec3-opt-out"
#define C_NSEC3_SALT_LEN "\x11""nsec3-salt-length"
#define C_NSEC3_SALT_LIFETIME "\x13""nsec3-salt-lifetime"
#define C_NSID "\x04""nsid"
......
......@@ -73,6 +73,9 @@ static void policy_load(knot_kasp_policy_t *policy, conf_val_t *id)
val = conf_id_get(conf(), C_POLICY, C_NSEC3, id);
policy->nsec3_enabled = conf_bool(&val);
val = conf_id_get(conf(), C_POLICY, C_NSEC3_OPT_OUT, id);
policy->nsec3_opt_out = conf_bool(&val);
val = conf_id_get(conf(), C_POLICY, C_NSEC3_ITER, id);
policy->nsec3_iterations = conf_int(&val);
......
......@@ -80,6 +80,7 @@ typedef struct {
uint32_t rrsig_refresh_before;
// NSEC3
bool nsec3_enabled;
bool nsec3_opt_out;
uint32_t nsec3_salt_lifetime;
uint16_t nsec3_iterations;
uint8_t nsec3_salt_length;
......
......@@ -65,6 +65,12 @@ static bool are_nsec3_nodes_equal(const zone_node_t *a, const zone_node_t *b)
return knot_rrset_equal(&a_rrset, &b_rrset, KNOT_RRSET_COMPARE_WHOLE);
}
static bool nsec3_opt_out(const zone_node_t *node, bool opt_out_enabled)
{
return (opt_out_enabled && (node->flags & NODE_FLAGS_DELEG) &&
!node_rrtype_exists(node, KNOT_RRTYPE_DS));
}
/*!
* \brief Check whether at least one RR type in node should be signed,
* used when signing with NSEC3.
......@@ -542,7 +548,7 @@ static int create_nsec3_nodes(const zone_contents_t *zone,
* \return KNOT_EOK, KNOT_E* if any error.
*/
static int fix_nsec3_for_node(zone_update_t *update, const dnssec_nsec3_params_t *params,
uint32_t ttl, changeset_t *chgset, const knot_dname_t *for_node)
uint32_t ttl, bool opt_out, changeset_t *chgset, const knot_dname_t *for_node)
{
// check if we need to do something
const zone_node_t *old_n = zone_contents_find_node(update->zone->contents, for_node);
......@@ -564,7 +570,8 @@ static int fix_nsec3_for_node(zone_update_t *update, const dnssec_nsec3_params_t
uint8_t *next_hash = NULL;
uint8_t next_length = 0;
bool add_nsec3 = (new_n != NULL && !node_empty(new_n) && !(new_n->flags & NODE_FLAGS_NONAUTH));
bool add_nsec3 = (new_n != NULL && !node_empty(new_n) && !(new_n->flags & NODE_FLAGS_NONAUTH) &&
!nsec3_opt_out(new_n, opt_out));
// remove (all) existing NSEC3
const zone_node_t *old_nsec3_n = zone_contents_find_nsec3_node(update->zone->contents, for_node_hashed);
......@@ -624,7 +631,7 @@ static int fix_nsec3_for_node(zone_update_t *update, const dnssec_nsec3_params_t
}
static int fix_nsec3_nodes(zone_update_t *update, const dnssec_nsec3_params_t *params,
uint32_t ttl, changeset_t *chgset)
uint32_t ttl, bool opt_out, changeset_t *chgset)
{
assert(update);
......@@ -633,7 +640,7 @@ static int fix_nsec3_nodes(zone_update_t *update, const dnssec_nsec3_params_t *p
trie_it_t *rem_it = trie_it_begin(update->change.remove->nodes);
while (!trie_it_finished(rem_it) && ret == KNOT_EOK) {
zone_node_t *n = (zone_node_t *)*trie_it_val(rem_it);
ret = fix_nsec3_for_node(update, params, ttl, chgset, n->owner);
ret = fix_nsec3_for_node(update, params, ttl, opt_out, chgset, n->owner);
trie_it_next(rem_it);
}
trie_it_free(rem_it);
......@@ -641,7 +648,7 @@ static int fix_nsec3_nodes(zone_update_t *update, const dnssec_nsec3_params_t *p
trie_it_t *add_it = trie_it_begin(update->change.add->nodes);
while (!trie_it_finished(add_it) && ret == KNOT_EOK) {
zone_node_t *n = (zone_node_t *)*trie_it_val(add_it);
ret = fix_nsec3_for_node(update, params, ttl, chgset, n->owner);
ret = fix_nsec3_for_node(update, params, ttl, opt_out, chgset, n->owner);
trie_it_next(add_it);
}
trie_it_free(add_it);
......@@ -656,13 +663,13 @@ static int fix_nsec3_nodes(zone_update_t *update, const dnssec_nsec3_params_t *p
* RRSIGs and NSECs.
* \retval false otherwise.
*/
static bool nsec3_is_empty(zone_node_t *node)
static bool nsec3_is_empty(zone_node_t *node, bool opt_out)
{
if (node->children > 0) {
return false;
}
return knot_nsec_empty_nsec_and_rrsigs_in_node(node);
return knot_nsec_empty_nsec_and_rrsigs_in_node(node) || nsec3_opt_out(node, opt_out);
}
/*!
......@@ -674,10 +681,9 @@ static bool nsec3_is_empty(zone_node_t *node)
*/
static int nsec3_mark_empty(zone_node_t **node_p, void *data)
{
UNUSED(data);
zone_node_t *node = *node_p;
if (!(node->flags & NODE_FLAGS_EMPTY) && nsec3_is_empty(node)) {
if (!(node->flags & NODE_FLAGS_EMPTY) && nsec3_is_empty(node, (data != NULL))) {
/*!
* Mark this node and all parent nodes that meet the same
* criteria as empty.
......@@ -730,6 +736,7 @@ static int nsec3_reset(zone_node_t **node_p, void *data)
int knot_nsec3_create_chain(const zone_contents_t *zone,
const dnssec_nsec3_params_t *params,
uint32_t ttl,
bool opt_out,
changeset_t *changeset)
{
assert(zone);
......@@ -751,7 +758,7 @@ int knot_nsec3_create_chain(const zone_contents_t *zone,
* The flag will be removed when the node is encountered during NSEC3
* creation procedure.
*/
result = zone_tree_apply(zone->nodes, nsec3_mark_empty, NULL);
result = zone_tree_apply(zone->nodes, nsec3_mark_empty, (opt_out ? (void *)zone : NULL));
if (result != KNOT_EOK) {
free_nsec3_tree(nsec3_nodes);
return result;
......@@ -793,10 +800,11 @@ int knot_nsec3_create_chain(const zone_contents_t *zone,
int knot_nsec3_fix_chain(zone_update_t *update,
const dnssec_nsec3_params_t *params,
uint32_t ttl,
bool opt_out,
changeset_t *changeset)
{
int ret = fix_nsec3_nodes(update, params, ttl, changeset);
int ret = fix_nsec3_nodes(update, params, ttl, opt_out, changeset);
if (ret != KNOT_EOK) {
return ret;
}
......
......@@ -28,6 +28,7 @@
* \param zone Zone to be checked.
* \param params NSEC3 parameters.
* \param ttl TTL for new records.
* \param opt_out NSEC3 opt-out enabled for insecure delegations.
* \param changeset Changeset to store changes into.
*
* \return KNOT_E*
......@@ -35,6 +36,7 @@
int knot_nsec3_create_chain(const zone_contents_t *zone,
const dnssec_nsec3_params_t *params,
uint32_t ttl,
bool opt_out,
changeset_t *changeset);
/*!
......@@ -43,6 +45,7 @@ int knot_nsec3_create_chain(const zone_contents_t *zone,
* \param update Zone Update structure holding the zone and its update. Also modified!
* \param params NSEC3 parameters.
* \param ttl TTL for new records.
* \param opt_out NSEC3 opt-out enabled for insecure delegations.
* \param changeset Changeset to store changes into. Some changes are pushed directly to update.
*
* \retval KNOT_ENORECORD if the chain must be recreated from scratch.
......@@ -50,5 +53,5 @@ int knot_nsec3_create_chain(const zone_contents_t *zone,
*/
int knot_nsec3_fix_chain(zone_update_t *update,
const dnssec_nsec3_params_t *params,
uint32_t ttl,
uint32_t ttl, bool opt_out,
changeset_t *changeset);
......@@ -276,7 +276,7 @@ static int set_nsec3param(knot_rrset_t *rrset, const dnssec_nsec3_params_t *para
wire_ctx_t wire = wire_ctx_init(rdata, rdata_len);
wire_ctx_write_u8(&wire, params->algorithm);
wire_ctx_write_u8(&wire, params->flags);
wire_ctx_write_u8(&wire, 0); // (RFC 5155 Section 4.1.2)
wire_ctx_write_u16(&wire, params->iterations);
wire_ctx_write_u8(&wire, params->salt.size);
wire_ctx_write(&wire, params->salt.data, params->salt.size);
......@@ -355,6 +355,7 @@ static dnssec_nsec3_params_t nsec3param_init(const knot_kasp_policy_t *policy,
params.algorithm = DNSSEC_NSEC3_ALGORITHM_SHA1;
params.iterations = policy->nsec3_iterations;
params.salt = zone->nsec3_salt;
params.flags = (policy->nsec3_opt_out ? KNOT_NSEC3_FLAG_OPT_OUT : 0);
}
return params;
......@@ -389,7 +390,8 @@ int knot_zone_create_nsec_chain(zone_update_t *update,
}
if (ctx->policy->nsec3_enabled) {
ret = knot_nsec3_create_chain(update->new_cont, &params, nsec_ttl, &ch);
ret = knot_nsec3_create_chain(update->new_cont, &params, nsec_ttl,
ctx->policy->nsec3_opt_out, &ch);
if (ret != KNOT_EOK) {
goto cleanup;
}
......@@ -449,7 +451,7 @@ int knot_zone_fix_nsec_chain(zone_update_t *update,
}
if (ctx->policy->nsec3_enabled) {
ret = knot_nsec3_fix_chain(update, &params, nsec_ttl, &ch);
ret = knot_nsec3_fix_chain(update, &params, nsec_ttl, ctx->policy->nsec3_opt_out, &ch);
} else {
ret = knot_nsec_fix_chain(update->zone->contents, update->new_cont, nsec_ttl, &ch);
}
......@@ -462,7 +464,8 @@ int knot_zone_fix_nsec_chain(zone_update_t *update,
return ret;
}
if (ctx->policy->nsec3_enabled) {
ret = knot_nsec3_create_chain(update->new_cont, &params, nsec_ttl, &ch);
ret = knot_nsec3_create_chain(update->new_cont, &params, nsec_ttl,
ctx->policy->nsec3_opt_out, &ch);
} else {
ret = knot_nsec_create_chain(update->new_cont, nsec_ttl, &ch);
}
......
......@@ -154,11 +154,4 @@ typedef enum {
KNOT_DNSSEC_ALG_PRIVATEOID = 254
} knot_dnssec_algorithm_t;
/*!
* \brief NSEC3 hash algorithm numbers.
*/
typedef enum {
KNOT_NSEC3_ALGORITHM_SHA1 = 1
} knot_nsec3_hash_algorithm_t;
/*! @} */
/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
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
......@@ -32,6 +32,12 @@
#include "libknot/rdataset.h"
#include "libknot/rrtype/nsec3param.h"
/*!
* \brief NSEC3 rdata constants.
*/
#define KNOT_NSEC3_ALGORITHM_SHA1 1
#define KNOT_NSEC3_FLAG_OPT_OUT 1
static inline
uint8_t knot_nsec3_algorithm(const knot_rdataset_t *rrs, size_t pos)
{
......
#!/usr/bin/env python3
'''Test for NSEC3 and delegation with and without opt-out'''
from dnstest.utils import *
from dnstest.test import Test
import random
zone_name = "example.com."
t = Test()
def check_deleg(deleg, nsec3_bitmap, opt_out_flag, msg):
t.sleep(2)
resp = master.dig(deleg + "." + zone_name, "A", dnssec=True, bufsize=4096)
first_nsec3 = str(resp.resp.authority[1]) # assert this is the first NSEC3 in the response
first_bitmap = ' '.join(first_nsec3.split()[9:])
check_log("NSEC3 bitmap '%s', expected '%s' for '%s'" % (first_bitmap, nsec3_bitmap, msg))
if first_bitmap != nsec3_bitmap:
set_err("NSEC3 bitmap for '%s'" % msg)
first_flags = first_nsec3.split()[5];
if first_flags != str(opt_out_flag):
set_err("NSEC3 opt-out flag %s != %s for '%s'" % (first_flags, str(opt_out_flag), msg))
detail_log(SEP)
master.zone_backup(zone, flush=True)
master.zone_verify(zone)
master = t.server("knot")
zone = t.zone(zone_name)
t.link(zone, master)
master.dnssec(zone).enable = True
master.dnssec(zone).nsec3 = True
master.dnssec(zone).nsec3_iters = 2
master.dnssec(zone).nsec3_salt_len = 8
master.dnssec(zone).nsec3_opt_out = False
t.start()
master.zones_wait(zone)
master.zone_backup(zone, flush=True)
master.zone_verify(zone)
# opt-out off, delegation added in changeset
up = master.update(zone)
up.add("deleg1", 3600, "NS", "nothing")
up.send("NOERROR")
check_deleg("deleg1", "NS", 0, "non-optout update")
# opt-out off, zone re-sign
master.ctl("zone-sign")
check_deleg("deleg1", "NS", 0, "non-optout re-sign")
# opt-out on, zone re-sign
master.dnssec(zone).nsec3_opt_out = True
master.gen_confile()
master.reload()
check_deleg("deleg1", "NS SOA MX RRSIG DNSKEY NSEC3PARAM CDS CDNSKEY", 1, "optout re-sign")
# opt-out on, delegation added in changeset
up = master.update(zone)
up.add("deleg2", 3600, "NS", "nothing")
up.send("NOERROR")
check_deleg("deleg2", "NS SOA MX RRSIG DNSKEY NSEC3PARAM CDS CDNSKEY", 1, "optout update")
t.end()
......@@ -51,6 +51,7 @@ class ZoneDnssec(object):
self.rrsig_refresh = None
self.nsec3 = None
self.nsec3_iters = None
self.nsec3_opt_out = None
self.nsec3_salt_lifetime = None
self.nsec3_salt_len = None
self.ksk_sbm_check = []
......@@ -1183,6 +1184,7 @@ class Knot(Server):
self._str(s, "rrsig-refresh", z.dnssec.rrsig_refresh)
self._bool(s, "nsec3", z.dnssec.nsec3)
self._str(s, "nsec3-iterations", z.dnssec.nsec3_iters)
self._bool(s, "nsec3-opt-out", z.dnssec.nsec3_opt_out)
self._str(s, "nsec3-salt-lifetime", z.dnssec.nsec3_salt_lifetime)
self._str(s, "nsec3-salt-length", z.dnssec.nsec3_salt_len)
if len(z.dnssec.ksk_sbm_check) > 0:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment