mirror of
https://github.com/NLnetLabs/unbound.git
synced 2026-01-16 11:42:55 -05:00
nsec3 work.
git-svn-id: file:///svn/unbound/trunk@609 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
parent
db452ffc16
commit
d85debfae4
15 changed files with 639 additions and 53 deletions
|
|
@ -1,5 +1,6 @@
|
|||
12 September 2007: Wouter
|
||||
- fixup of manual page warnings.
|
||||
- fixup of manual page warnings, like for NSD bugreport.
|
||||
- nsec3 work, config, max iterations, filter, and hash cache.
|
||||
|
||||
6 September 2007: Wouter
|
||||
- fixup to find libevent on mac port install.
|
||||
|
|
|
|||
3
doc/TODO
3
doc/TODO
|
|
@ -37,7 +37,8 @@ o store primed key data in a overlaid keyhints file (sort of like drafttimers).
|
|||
o windows version, auto update feature, a query to check for the version.
|
||||
o autoreport of problems
|
||||
o logrotation, syslog
|
||||
o command the server with TSIG inband.
|
||||
o command the server with TSIG inband. get-config, clearcache,
|
||||
get stats, get memstats, get ..., reload, clear one zone from cache
|
||||
o watch for spoof nearmisses.
|
||||
o improve compression of DNS packets by first puttig uncompressible rrs, then
|
||||
compress to their rdata.
|
||||
|
|
|
|||
|
|
@ -197,6 +197,12 @@ server:
|
|||
# result in interesting log files and possibly the AD bit in
|
||||
# replies if the message is found secure. The default is off.
|
||||
# val-permissive-mode: no
|
||||
|
||||
# It is possible to configure NSEC3 maximum iteration counts per
|
||||
# keysize. Keep this table very short, as linear search is done.
|
||||
# A message with an NSEC3 with larger count is marked insecure.
|
||||
# List in ascending order the keysize and count values.
|
||||
# val-nsec3-keysize-iterations: "1024 150 2048 500 4096 2500"
|
||||
|
||||
# the amount of memory to use for the key cache.
|
||||
# in bytes. default is 4 Mb
|
||||
|
|
|
|||
|
|
@ -232,6 +232,14 @@ reply is not withheld from the client with SERVFAIL as usual. The client
|
|||
receives the bogus data. For messages that are found to be secure the AD bit
|
||||
is set in replies. Also logging is performed as for full validation.
|
||||
The default value is "no".
|
||||
.It \fBval-nsec3-keysize-iterations:\fR <"list of values">
|
||||
List of keysize and iteration count values, separated by spaces, surrounded
|
||||
by quotes. Default is "1024 150 2048 500 4096 2500". This determines the
|
||||
maximum allowed NSEC3 iteration count before a message is simply marked
|
||||
insecure instead of performing the many hashing iterations. The list must
|
||||
be in ascending order and have at least one entry. If you set it to
|
||||
"1024 65535" there is no restriction to NSEC3 iteration values.
|
||||
This table must be kept short; a very long list could cause slower operation.
|
||||
.It \fBkey-cache-size:\fR <number>
|
||||
Number of bytes size of the key cache. Default is 4 megabytes.
|
||||
.It \fBkey-cache-slabs:\fR <number>
|
||||
|
|
|
|||
|
|
@ -57,32 +57,6 @@
|
|||
#include "util/data/msgparse.h"
|
||||
#include "util/random.h"
|
||||
|
||||
/** count number of integers in fetch policy string */
|
||||
static int
|
||||
fetch_count(const char* s)
|
||||
{
|
||||
/* format ::= (sp num)+ sp */
|
||||
/* num ::= [-](0-9)+ */
|
||||
/* sp ::= (space|tab)* */
|
||||
int num = 0;
|
||||
while(*s) {
|
||||
while(*s && isspace(*s))
|
||||
s++;
|
||||
if(!*s) /* end of string */
|
||||
break;
|
||||
if(*s == '-')
|
||||
s++;
|
||||
if(!*s) /* only - not allowed */
|
||||
return 0;
|
||||
if(!isdigit(*s)) /* bad character */
|
||||
return 0;
|
||||
while(*s && isdigit(*s))
|
||||
s++;
|
||||
num++;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
/** fillup fetch policy array */
|
||||
static void
|
||||
fetch_fill(struct iter_env* ie, const char* str)
|
||||
|
|
@ -91,7 +65,8 @@ fetch_fill(struct iter_env* ie, const char* str)
|
|||
int i;
|
||||
for(i=0; i<ie->max_dependency_depth+1; i++) {
|
||||
ie->target_fetch_policy[i] = strtol(s, &e, 10);
|
||||
log_assert(s != e); /* parsed syntax already */
|
||||
if(s == e)
|
||||
fatal_exit("cannot parse fetch policy number %s", s);
|
||||
s = e;
|
||||
}
|
||||
}
|
||||
|
|
@ -100,7 +75,7 @@ fetch_fill(struct iter_env* ie, const char* str)
|
|||
static int
|
||||
read_fetch_policy(struct iter_env* ie, const char* str)
|
||||
{
|
||||
int count = fetch_count(str);
|
||||
int count = cfg_count_numbers(str);
|
||||
if(count < 1) {
|
||||
log_err("Cannot parse target fetch policy: \"%s\"", str);
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ config_create()
|
|||
cfg->key_cache_size = 4 * 1024 * 1024;
|
||||
cfg->key_cache_slabs = 4;
|
||||
if(!(cfg->module_conf = strdup("validator iterator"))) goto error_exit;
|
||||
if(!(cfg->val_nsec3_key_iterations =
|
||||
strdup("1024 150 2048 500 4096 2500"))) goto error_exit;
|
||||
return cfg;
|
||||
error_exit:
|
||||
config_delete(cfg);
|
||||
|
|
@ -219,6 +221,7 @@ config_delete(struct config_file* cfg)
|
|||
config_delstrlist(cfg->trust_anchor_file_list);
|
||||
config_delstrlist(cfg->trusted_keys_file_list);
|
||||
config_delstrlist(cfg->trust_anchor_list);
|
||||
free(cfg->val_nsec3_key_iterations);
|
||||
free(cfg);
|
||||
}
|
||||
|
||||
|
|
@ -290,3 +293,28 @@ cfg_convert_timeval(const char* str)
|
|||
t = mktime_from_utc(&tm);
|
||||
return t;
|
||||
}
|
||||
|
||||
int
|
||||
cfg_count_numbers(const char* s)
|
||||
{
|
||||
/* format ::= (sp num)+ sp */
|
||||
/* num ::= [-](0-9)+ */
|
||||
/* sp ::= (space|tab)* */
|
||||
int num = 0;
|
||||
while(*s) {
|
||||
while(*s && isspace(*s))
|
||||
s++;
|
||||
if(!*s) /* end of string */
|
||||
break;
|
||||
if(*s == '-')
|
||||
s++;
|
||||
if(!*s) /* only - not allowed */
|
||||
return 0;
|
||||
if(!isdigit(*s)) /* bad character */
|
||||
return 0;
|
||||
while(*s && isdigit(*s))
|
||||
s++;
|
||||
num++;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,8 @@ struct config_file {
|
|||
int val_clean_additional;
|
||||
/** should validator allow bogus messages to go through */
|
||||
int val_permissive_mode;
|
||||
/** nsec3 maximum iterations per key size, string */
|
||||
char* val_nsec3_key_iterations;
|
||||
|
||||
/** size of the key cache */
|
||||
size_t key_cache_size;
|
||||
|
|
@ -227,6 +229,18 @@ int cfg_strlist_insert(struct config_strlist** head, char* item);
|
|||
*/
|
||||
uint32_t cfg_convert_timeval(const char* str);
|
||||
|
||||
/**
|
||||
* Count number of values in the string.
|
||||
* format ::= (sp num)+ sp
|
||||
* num ::= [-](0-9)+
|
||||
* sp ::= (space|tab)*
|
||||
*
|
||||
* @param str: string
|
||||
* @return: 0 on parse error, or empty string, else
|
||||
* number of integer values in the string.
|
||||
*/
|
||||
int cfg_count_numbers(const char* str);
|
||||
|
||||
/**
|
||||
* Used during options parsing
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ val-clean-additional{COLON} { YDOUT; return VAR_VAL_CLEAN_ADDITIONAL;}
|
|||
val-permissive-mode{COLON} { YDOUT; return VAR_VAL_PERMISSIVE_MODE;}
|
||||
key-cache-size{COLON} { YDOUT; return VAR_KEY_CACHE_SIZE;}
|
||||
key-cache-slabs{COLON} { YDOUT; return VAR_KEY_CACHE_SLABS;}
|
||||
val-nsec3-keysize-iterations{COLON} { YDOUT; return VAR_VAL_NSEC3_KEYSIZE_ITERATIONS;}
|
||||
{NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;}
|
||||
|
||||
/* Quoted strings. Strip leading and ending quotes */
|
||||
|
|
|
|||
|
|
@ -83,7 +83,8 @@ extern struct config_parser_state* cfg_parser;
|
|||
%token VAR_TRUST_ANCHOR_FILE VAR_TRUST_ANCHOR VAR_VAL_OVERRIDE_DATE
|
||||
%token VAR_BOGUS_TTL VAR_VAL_CLEAN_ADDITIONAL VAR_VAL_PERMISSIVE_MODE
|
||||
%token VAR_INCOMING_NUM_TCP VAR_MSG_BUFFER_SIZE VAR_KEY_CACHE_SIZE
|
||||
%token VAR_KEY_CACHE_SLABS VAR_TRUSTED_KEYS_FILE
|
||||
%token VAR_KEY_CACHE_SLABS VAR_TRUSTED_KEYS_FILE
|
||||
%token VAR_VAL_NSEC3_KEYSIZE_ITERATIONS
|
||||
|
||||
%%
|
||||
toplevelvars: /* empty */ | toplevelvars toplevelvar ;
|
||||
|
|
@ -121,7 +122,7 @@ content_server: server_num_threads | server_verbosity | server_port |
|
|||
server_val_clean_additional | server_val_permissive_mode |
|
||||
server_incoming_num_tcp | server_msg_buffer_size |
|
||||
server_key_cache_size | server_key_cache_slabs |
|
||||
server_trusted_keys_file
|
||||
server_trusted_keys_file | server_val_nsec3_keysize_iterations
|
||||
;
|
||||
stubstart: VAR_STUB_ZONE
|
||||
{
|
||||
|
|
@ -568,6 +569,13 @@ server_val_permissive_mode: VAR_VAL_PERMISSIVE_MODE STRING
|
|||
free($2);
|
||||
}
|
||||
;
|
||||
server_val_nsec3_keysize_iterations: VAR_VAL_NSEC3_KEYSIZE_ITERATIONS STRING
|
||||
{
|
||||
OUTYY(("P(server_val_nsec3_keysize_iterations:%s)\n", $2));
|
||||
free(cfg_parser->cfg->val_nsec3_key_iterations);
|
||||
cfg_parser->cfg->val_nsec3_key_iterations = $2;
|
||||
}
|
||||
;
|
||||
server_key_cache_size: VAR_KEY_CACHE_SIZE STRING
|
||||
{
|
||||
OUTYY(("P(server_key_cache_size:%s)\n", $2));
|
||||
|
|
|
|||
|
|
@ -302,3 +302,49 @@ key_entry_get_rrset(struct key_entry_key* kkey, struct region* region)
|
|||
packed_rrset_ptr_fixup(rrd);
|
||||
return rrk;
|
||||
}
|
||||
|
||||
/** Get size of key in keyset */
|
||||
static size_t
|
||||
dnskey_get_keysize(struct packed_rrset_data* data, size_t idx)
|
||||
{
|
||||
unsigned char* pk;
|
||||
unsigned int pklen = 0;
|
||||
int algo;
|
||||
if(data->rr_len[idx] < 2+5)
|
||||
return 0;
|
||||
algo = (int)data->rr_data[idx][2+3];
|
||||
pk = (unsigned char*)data->rr_data[idx]+2+4;
|
||||
pklen = (unsigned)data->rr_len[idx]-2-4;
|
||||
return ldns_rr_dnskey_key_size_raw(pk, pklen, algo);
|
||||
}
|
||||
|
||||
/** get dnskey flags from data */
|
||||
static uint16_t
|
||||
kd_get_flags(struct packed_rrset_data* data, size_t idx)
|
||||
{
|
||||
uint16_t f;
|
||||
if(data->rr_len[idx] < 2+2)
|
||||
return 0;
|
||||
memmove(&f, data->rr_data[idx]+2, 2);
|
||||
f = ntohs(f);
|
||||
return f;
|
||||
}
|
||||
|
||||
size_t
|
||||
key_entry_keysize(struct key_entry_key* kkey)
|
||||
{
|
||||
struct packed_rrset_data* d;
|
||||
/* compute size of smallest ZSK key in the rrset */
|
||||
size_t i;
|
||||
size_t bits = 0;
|
||||
if(!key_entry_isgood(kkey))
|
||||
return 0;
|
||||
d = ((struct key_entry_data*)kkey->entry.data)->rrset_data;
|
||||
for(i=0; i<d->count; i++) {
|
||||
if(!(kd_get_flags(d, i) & DNSKEY_BIT_ZSK))
|
||||
continue;
|
||||
if(i==0 || dnskey_get_keysize(d, i) < bits)
|
||||
bits = dnskey_get_keysize(d, i);
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,4 +183,11 @@ struct key_entry_key* key_entry_create_bad(struct region* region,
|
|||
struct ub_packed_rrset_key* key_entry_get_rrset(struct key_entry_key* kkey,
|
||||
struct region* region);
|
||||
|
||||
/**
|
||||
* Get keysize of the keyentry.
|
||||
* @param kkey: key, must be a good key, with contents.
|
||||
* @return size in bits of the key.
|
||||
*/
|
||||
size_t key_entry_keysize(struct key_entry_key* kkey);
|
||||
|
||||
#endif /* VALIDATOR_VAL_KENTRY_H */
|
||||
|
|
|
|||
|
|
@ -42,8 +42,13 @@
|
|||
*/
|
||||
#include "config.h"
|
||||
#include "validator/val_nsec3.h"
|
||||
#include "validator/validator.h"
|
||||
#include "validator/val_kentry.h"
|
||||
#include "util/region-allocator.h"
|
||||
#include "util/rbtree.h"
|
||||
#include "util/data/packed_rrset.h"
|
||||
#include "util/data/dname.h"
|
||||
#include "util/data/msgreply.h"
|
||||
|
||||
/**
|
||||
* The NSEC3 hash result storage.
|
||||
|
|
@ -55,9 +60,9 @@ struct nsec3_cached_hash {
|
|||
/** rbtree node, key is this structure */
|
||||
rbnode_t node;
|
||||
/** where are the parameters for conversion, in this rrset data */
|
||||
struct packed_rrset_data* data;
|
||||
struct ub_packed_rrset_key* nsec3;
|
||||
/** where are the parameters for conversion, this RR number in data */
|
||||
size_t rr;
|
||||
int rr;
|
||||
/** the name to convert */
|
||||
uint8_t* dname;
|
||||
/** length of the dname */
|
||||
|
|
@ -65,7 +70,7 @@ struct nsec3_cached_hash {
|
|||
/** the hash result (not base32 encoded) */
|
||||
uint8_t* hash;
|
||||
/** length of hash in bytes */
|
||||
size_t hashlen;
|
||||
size_t hash_len;
|
||||
/** the hash result in base32 encoding */
|
||||
uint8_t* b32;
|
||||
/** length of base32 encoding (as a label) */
|
||||
|
|
@ -93,6 +98,7 @@ struct ce_response {
|
|||
|
||||
/**
|
||||
* Filter conditions for NSEC3 proof
|
||||
* Used to iterate over the applicable NSEC3 RRs.
|
||||
*/
|
||||
struct nsec3_filter {
|
||||
/** Zone name, only NSEC3 records for this zone are considered */
|
||||
|
|
@ -103,25 +109,182 @@ struct nsec3_filter {
|
|||
struct ub_packed_rrset_key** list;
|
||||
/** number of rrsets in list */
|
||||
size_t num;
|
||||
/** class of records for the NSEC3, only this class applies */
|
||||
uint16_t fclass;
|
||||
};
|
||||
|
||||
/** return number of rrs in an rrset */
|
||||
static size_t
|
||||
rrset_get_count(struct ub_packed_rrset_key* rrset)
|
||||
{
|
||||
struct packed_rrset_data* d = (struct packed_rrset_data*)
|
||||
rrset->entry.data;
|
||||
if(!d) return 0;
|
||||
return d->count;
|
||||
}
|
||||
|
||||
/** return if nsec3 RR has unknown flags */
|
||||
static int
|
||||
nsec3_unknown_flags(struct ub_packed_rrset_key* rrset, int r)
|
||||
{
|
||||
struct packed_rrset_data* d = (struct packed_rrset_data*)
|
||||
rrset->entry.data;
|
||||
log_assert(d && r < (int)d->count);
|
||||
if(d->rr_len[r] < 2+2)
|
||||
return 0; /* malformed */
|
||||
return (int)(d->rr_data[r][2+1] & NSEC3_UNKNOWN_FLAGS);
|
||||
}
|
||||
|
||||
/** return nsec3 RR algorithm */
|
||||
static int
|
||||
nsec3_get_algo(struct ub_packed_rrset_key* rrset, int r)
|
||||
{
|
||||
struct packed_rrset_data* d = (struct packed_rrset_data*)
|
||||
rrset->entry.data;
|
||||
log_assert(d && r < (int)d->count);
|
||||
if(d->rr_len[r] < 2+1)
|
||||
return 0; /* malformed */
|
||||
return (int)(d->rr_data[r][2+0]);
|
||||
}
|
||||
|
||||
/** return if nsec3 RR has known algorithm */
|
||||
static int
|
||||
nsec3_known_algo(struct ub_packed_rrset_key* rrset, int r)
|
||||
{
|
||||
struct packed_rrset_data* d = (struct packed_rrset_data*)
|
||||
rrset->entry.data;
|
||||
log_assert(d && r < (int)d->count);
|
||||
if(d->rr_len[r] < 2+1)
|
||||
return 0; /* malformed */
|
||||
switch(d->rr_data[r][2+0]) {
|
||||
case NSEC3_HASH_SHA1:
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** return nsec3 RR iteration count */
|
||||
static size_t
|
||||
nsec3_get_iter(struct ub_packed_rrset_key* rrset, int r)
|
||||
{
|
||||
uint16_t i;
|
||||
struct packed_rrset_data* d = (struct packed_rrset_data*)
|
||||
rrset->entry.data;
|
||||
log_assert(d && r < (int)d->count);
|
||||
if(d->rr_len[r] < 2+4)
|
||||
return 0; /* malformed */
|
||||
memmove(&i, d->rr_data[r]+2+2, sizeof(i));
|
||||
i = ntohs(i);
|
||||
return (size_t)i;
|
||||
}
|
||||
|
||||
/** return nsec3 RR salt */
|
||||
static int
|
||||
nsec3_get_salt(struct ub_packed_rrset_key* rrset, int r,
|
||||
uint8_t** salt, size_t* saltlen)
|
||||
{
|
||||
struct packed_rrset_data* d = (struct packed_rrset_data*)
|
||||
rrset->entry.data;
|
||||
log_assert(d && r < (int)d->count);
|
||||
if(d->rr_len[r] < 2+5) {
|
||||
*salt = 0;
|
||||
*saltlen = 0;
|
||||
return 0; /* malformed */
|
||||
}
|
||||
*saltlen = (size_t)d->rr_data[r][2+4];
|
||||
if(d->rr_len[r] < 2+5+(size_t)*saltlen) {
|
||||
*salt = 0;
|
||||
*saltlen = 0;
|
||||
return 0; /* malformed */
|
||||
}
|
||||
*salt = d->rr_data[r]+2+5;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate through NSEC3 list, per RR
|
||||
* Start with rrset = list, rrnum = 0.
|
||||
* End when rrset becomes NULL.
|
||||
* This routine gives the next RR in the list (or sets rrset null).
|
||||
* This routine gives the next RR in the list (or sets rrset null).
|
||||
* Usage:
|
||||
*
|
||||
* size_t rrsetnum;
|
||||
* int rrnum;
|
||||
* struct ub_packed_rrset_key* rrset;
|
||||
* for(rrset=filter_first(filter, &rrsetnum, &rrnum); rrset;
|
||||
* rrset=filter_next(filter, &rrsetnum, &rrnum))
|
||||
* do_stuff;
|
||||
*
|
||||
* Also filters out
|
||||
* o unknown flag NSEC3s
|
||||
* o unknown algorithm NSEC3s.
|
||||
* @param filter: nsec3 filter structure.
|
||||
* @param rrset: in/out rrset to look at.
|
||||
* @param rrsetnum: in/out rrset number to look at.
|
||||
* @param rrnum: in/out rr number in rrset to look at.
|
||||
* @returns ptr to the next rrset (or NULL at end).
|
||||
*/
|
||||
static void
|
||||
filter_next(struct nsec3_filter* filter, struct ub_packed_rrset_key** rrset,
|
||||
size_t rrnum)
|
||||
static struct ub_packed_rrset_key*
|
||||
filter_next(struct nsec3_filter* filter, size_t* rrsetnum, int* rrnum)
|
||||
{
|
||||
size_t i;
|
||||
int r;
|
||||
uint8_t* nm;
|
||||
size_t nmlen;
|
||||
if(!filter->zone) /* empty list */
|
||||
return NULL;
|
||||
for(i=*rrsetnum; i<filter->num; i++) {
|
||||
/* see if RRset qualifies */
|
||||
if(ntohs(filter->list[i]->rk.type) != LDNS_RR_TYPE_NSEC3 ||
|
||||
ntohs(filter->list[i]->rk.rrset_class) !=
|
||||
filter->fclass)
|
||||
continue;
|
||||
/* check RRset zone */
|
||||
nm = filter->list[i]->rk.dname;
|
||||
nmlen = filter->list[i]->rk.dname_len;
|
||||
dname_remove_label(&nm, &nmlen);
|
||||
if(query_dname_compare(nm, filter->zone) != 0)
|
||||
continue;
|
||||
if(i == *rrsetnum)
|
||||
r = (*rrnum) + 1; /* continue at next RR */
|
||||
else r = 0; /* new RRset start at first RR */
|
||||
for(; r < (int)rrset_get_count(filter->list[i]); r++) {
|
||||
/* skip unknown flags, algo */
|
||||
if(nsec3_unknown_flags(filter->list[i], r) ||
|
||||
!nsec3_known_algo(filter->list[i], r))
|
||||
continue;
|
||||
/* this one is a good target */
|
||||
*rrsetnum = i;
|
||||
*rrnum = r;
|
||||
return filter->list[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start iterating over NSEC3 records.
|
||||
* @param filter: the filter structure, must have been filter_init-ed.
|
||||
* @param rrsetnum: can be undefined on call, inited.
|
||||
* @param rrnum: can be undefined on call, inited.
|
||||
* @return first rrset of an NSEC3, together with rrnum this points to
|
||||
* the first RR to examine. Is NULL on empty list.
|
||||
*/
|
||||
static struct ub_packed_rrset_key*
|
||||
filter_first(struct nsec3_filter* filter, size_t* rrsetnum, int* rrnum)
|
||||
{
|
||||
*rrsetnum = 0;
|
||||
*rrnum = -1;
|
||||
return filter_next(filter, rrsetnum, rrnum);
|
||||
}
|
||||
|
||||
/** see if at least one RR is known (flags, algo) */
|
||||
static int
|
||||
nsec3_rrset_has_known(struct ub_packed_rrset_key* s)
|
||||
{
|
||||
int r;
|
||||
for(r=0; r < (int)rrset_get_count(s); r++) {
|
||||
if(!nsec3_unknown_flags(s, r) && nsec3_known_algo(s, r))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -130,14 +293,71 @@ filter_next(struct nsec3_filter* filter, struct ub_packed_rrset_key** rrset,
|
|||
* (skips the unknown flag and unknown algo NSEC3s).
|
||||
*
|
||||
* @param filter: nsec3 filter structure.
|
||||
* @param list: list of rrsets.
|
||||
* @param list: list of rrsets, an array of them.
|
||||
* @param num: number of rrsets in list.
|
||||
* @param qtype: query type (if DS a higher zone must be chosen)
|
||||
* @param qinfo:
|
||||
* query name to match a zone for.
|
||||
* query type (if DS a higher zone must be chosen)
|
||||
* qclass, to filter NSEC3s with.
|
||||
*/
|
||||
static void
|
||||
filter_init(struct nsec3_filter* filter, struct ub_packed_rrset_key* list,
|
||||
size_t num, uint16_t qtype)
|
||||
filter_init(struct nsec3_filter* filter, struct ub_packed_rrset_key** list,
|
||||
size_t num, struct query_info* qinfo)
|
||||
{
|
||||
size_t i;
|
||||
uint8_t* nm;
|
||||
size_t nmlen;
|
||||
filter->zone = NULL;
|
||||
filter->zone_len = 0;
|
||||
filter->list = list;
|
||||
filter->num = num;
|
||||
filter->fclass = qinfo->qclass;
|
||||
for(i=0; i<num; i++) {
|
||||
/* ignore other stuff in the list */
|
||||
if(ntohs(list[i]->rk.type) != LDNS_RR_TYPE_NSEC3 ||
|
||||
ntohs(list[i]->rk.rrset_class) != qinfo->qclass)
|
||||
continue;
|
||||
/* skip unknown flags, algo */
|
||||
if(!nsec3_rrset_has_known(list[i]))
|
||||
continue;
|
||||
|
||||
/* since NSECs are base32.zonename, we can find the zone
|
||||
* name by stripping off the first label of the record */
|
||||
nm = list[i]->rk.dname;
|
||||
nmlen = list[i]->rk.dname_len;
|
||||
dname_remove_label(&nm, &nmlen);
|
||||
/* if we find a domain that can prove about the qname,
|
||||
* and if this domain is closer to the qname */
|
||||
if(dname_subdomain_c(qinfo->qname, nm) && (!filter->zone ||
|
||||
dname_subdomain_c(nm, filter->zone))) {
|
||||
/* for a type DS do not accept a zone equal to qname*/
|
||||
if(qinfo->qtype == LDNS_RR_TYPE_DS &&
|
||||
query_dname_compare(qinfo->qname, nm) == 0)
|
||||
continue;
|
||||
filter->zone = nm;
|
||||
filter->zone_len = nmlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find max iteration count using config settings and key size
|
||||
* @param ve: validator environment with iteration count config settings.
|
||||
* @param bits: key size
|
||||
* @return max iteration count
|
||||
*/
|
||||
static size_t
|
||||
get_max_iter(struct val_env* ve, size_t bits)
|
||||
{
|
||||
int i;
|
||||
log_assert(ve->nsec3_keyiter_count > 0);
|
||||
/* round up to nearest config keysize, linear search, keep it small */
|
||||
for(i=0; i<ve->nsec3_keyiter_count; i++) {
|
||||
if(bits <= ve->nsec3_keysize[i])
|
||||
return ve->nsec3_maxiter[i];
|
||||
}
|
||||
/* else, use value for biggest key */
|
||||
return ve->nsec3_maxiter[ve->nsec3_keyiter_count-1];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -145,18 +365,141 @@ filter_init(struct nsec3_filter* filter, struct ub_packed_rrset_key* list,
|
|||
* @param ve: validator environment with iteration count config settings.
|
||||
* @param filter: what NSEC3s to loop over.
|
||||
* @param kkey: key entry used for verification; used for iteration counts.
|
||||
* @return 0 if some nsec3s are above the max iteration count.
|
||||
* @return 1 if some nsec3s are above the max iteration count.
|
||||
*/
|
||||
static int
|
||||
nsec3_iteration_count_high(struct val_env* ve, struct nsec3_filter* filter,
|
||||
struct key_entry_key* kkey)
|
||||
{
|
||||
size_t rrsetnum;
|
||||
int rrnum;
|
||||
struct ub_packed_rrset_key* rrset;
|
||||
/* first determine the max number of iterations */
|
||||
size_t bits = key_entry_keysize(kkey);
|
||||
size_t max_iter = get_max_iter(ve, bits);
|
||||
verbose(VERB_ALGO, "nsec3: keysize %d bits, max iterations %d",
|
||||
(int)bits, (int)max_iter);
|
||||
|
||||
/** perform hash of name */
|
||||
for(rrset=filter_first(filter, &rrsetnum, &rrnum); rrset;
|
||||
rrset=filter_next(filter, &rrsetnum, &rrnum)) {
|
||||
if(nsec3_get_iter(rrset, rrnum) > max_iter)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** nsec3_cache_compare for rbtree */
|
||||
static int
|
||||
nsec3_hash_cmp(const void* c1, const void* c2)
|
||||
{
|
||||
struct nsec3_cached_hash* h1 = (struct nsec3_cached_hash*)c1;
|
||||
struct nsec3_cached_hash* h2 = (struct nsec3_cached_hash*)c2;
|
||||
uint8_t* s1, *s2;
|
||||
size_t s1len, s2len;
|
||||
int c = query_dname_compare(h1->dname, h2->dname);
|
||||
if(c != 0)
|
||||
return c;
|
||||
/* compare parameters */
|
||||
/* if both malformed, its equal, robustness */
|
||||
if(nsec3_get_algo(h1->nsec3, h1->rr) !=
|
||||
nsec3_get_algo(h2->nsec3, h2->rr)) {
|
||||
if(nsec3_get_algo(h1->nsec3, h1->rr) <
|
||||
nsec3_get_algo(h2->nsec3, h2->rr))
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
if(nsec3_get_iter(h1->nsec3, h1->rr) !=
|
||||
nsec3_get_iter(h2->nsec3, h2->rr)) {
|
||||
if(nsec3_get_iter(h1->nsec3, h1->rr) <
|
||||
nsec3_get_iter(h2->nsec3, h2->rr))
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
(void)nsec3_get_salt(h1->nsec3, h1->rr, &s1, &s1len);
|
||||
(void)nsec3_get_salt(h2->nsec3, h2->rr, &s2, &s2len);
|
||||
if(s1len != s2len) {
|
||||
if(s1len < s2len)
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
return memcmp(s1, s2, s1len);
|
||||
}
|
||||
|
||||
/** perform hash of name */
|
||||
static int
|
||||
nsec3_calc_hash(struct region* region, ldns_buffer* buf,
|
||||
struct nsec3_cached_hash* c)
|
||||
{
|
||||
int algo = nsec3_get_algo(c->nsec3, c->rr);
|
||||
size_t iter = nsec3_get_iter(c->nsec3, c->rr);
|
||||
uint8_t* salt;
|
||||
size_t saltlen, i;
|
||||
if(!nsec3_get_salt(c->nsec3, c->rr, &salt, &saltlen))
|
||||
return -1;
|
||||
/* prepare buffer for first iteration */
|
||||
ldns_buffer_clear(buf);
|
||||
ldns_buffer_write(buf, c->dname, c->dname_len);
|
||||
query_dname_tolower(ldns_buffer_begin(buf));
|
||||
ldns_buffer_write(buf, salt, saltlen);
|
||||
ldns_buffer_flip(buf);
|
||||
switch(algo) {
|
||||
#ifdef SHA_DIGEST_LENGTH
|
||||
case NSEC3_HASH_SHA1:
|
||||
c->hash_len = SHA_DIGEST_LENGTH;
|
||||
c->hash = (uint8_t*)region_alloc(region, c->hash_len);
|
||||
if(!c->hash)
|
||||
return 0;
|
||||
(void)SHA1((unsigned char*)ldns_buffer_begin(buf),
|
||||
(unsigned long)ldns_buffer_limit(buf),
|
||||
(unsigned char*)c->hash);
|
||||
for(i=0; i<iter; i++) {
|
||||
ldns_buffer_clear(buf);
|
||||
ldns_buffer_write(buf, c->hash, c->hash_len);
|
||||
ldns_buffer_write(buf, salt, saltlen);
|
||||
ldns_buffer_flip(buf);
|
||||
(void)SHA1(
|
||||
(unsigned char*)ldns_buffer_begin(buf),
|
||||
(unsigned long)ldns_buffer_limit(buf),
|
||||
(unsigned char*)c->hash);
|
||||
}
|
||||
#endif /* SHA_DIGEST_LENGTH */
|
||||
default:
|
||||
log_err("nsec3 hash of unknown algo %d", algo);
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** This function we get from ldns-compat or from base system */
|
||||
int b32_ntop_extended_hex(uint8_t const *src, size_t srclength,
|
||||
char *target, size_t targsize);
|
||||
|
||||
/** perform b32 encoding of hash */
|
||||
static int
|
||||
nsec3_calc_b32(struct region* region, ldns_buffer* buf,
|
||||
struct nsec3_cached_hash* c)
|
||||
{
|
||||
int r;
|
||||
ldns_buffer_clear(buf);
|
||||
r = b32_ntop_extended_hex(c->hash, c->hash_len,
|
||||
(char*)ldns_buffer_begin(buf), ldns_buffer_limit(buf));
|
||||
if(r < 1) {
|
||||
log_err("b32_ntop_extended_hex: error in encoding: %d", r);
|
||||
return 0;
|
||||
}
|
||||
c->b32_len = (size_t)r;
|
||||
c->b32 = region_alloc_init(region, ldns_buffer_begin(buf), c->b32_len);
|
||||
if(!c->b32)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the hash of an owner name.
|
||||
* @param table: the cache table.
|
||||
* @param table: the cache table. Must be inited at start.
|
||||
* @param region: scratch region to use for allocation.
|
||||
* @param d: the rrset data
|
||||
* @param buf: temporary buffer.
|
||||
* @param nsec3: the rrset with parameters
|
||||
* @param rr: rr number from d that has the NSEC3 parameters to hash to.
|
||||
* @param dname: name to hash
|
||||
* @param dname_len: the length of the name.
|
||||
|
|
@ -167,10 +510,43 @@ filter_init(struct nsec3_filter* filter, struct ub_packed_rrset_key* list,
|
|||
* -1 if the NSEC3 rr was badly formatted (i.e. formerr).
|
||||
*/
|
||||
static int
|
||||
nsec3_hash_name(rbtree_t* table, struct region* region,
|
||||
struct packed_rrset_data* data, size_t rr, uint8_t* dname,
|
||||
nsec3_hash_name(rbtree_t* table, struct region* region, ldns_buffer* buf,
|
||||
struct ub_packed_rrset_key* nsec3, int rr, uint8_t* dname,
|
||||
size_t dname_len, struct nsec3_cached_hash** hash)
|
||||
{
|
||||
struct nsec3_cached_hash* c;
|
||||
struct nsec3_cached_hash looki;
|
||||
rbnode_t* n;
|
||||
int r;
|
||||
looki.node.key = &looki;
|
||||
looki.nsec3 = nsec3;
|
||||
looki.rr = rr;
|
||||
looki.dname = dname;
|
||||
looki.dname_len = dname_len;
|
||||
/* lookup first in cache */
|
||||
c = (struct nsec3_cached_hash*)rbtree_search(table, &looki);
|
||||
if(c) {
|
||||
*hash = c;
|
||||
return 1;
|
||||
}
|
||||
/* create a new entry */
|
||||
c = (struct nsec3_cached_hash*)region_alloc(region, sizeof(*c));
|
||||
if(!c) return 0;
|
||||
c->node.key = c;
|
||||
c->nsec3 = nsec3;
|
||||
c->rr = rr;
|
||||
c->dname = dname;
|
||||
c->dname_len = dname_len;
|
||||
r = nsec3_calc_hash(region, buf, c);
|
||||
if(r != 1)
|
||||
return r;
|
||||
r = nsec3_calc_b32(region, buf, c);
|
||||
if(r != 1)
|
||||
return r;
|
||||
n = rbtree_insert(table, &c->node);
|
||||
log_assert(n); /* cannot be duplicate, just did lookup */
|
||||
*hash = c;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -39,6 +39,29 @@
|
|||
* This file contains helper functions for the validator module.
|
||||
* The functions help with NSEC3 checking, the different NSEC3 proofs
|
||||
* for denial of existance, and proofs for presence of types.
|
||||
*
|
||||
* NSEC3
|
||||
* 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Hash Alg. | Flags | Iterations |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Salt Length | Salt /
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Hash Length | Next Hashed Owner Name /
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* / Type Bit Maps /
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*
|
||||
* NSEC3PARAM
|
||||
* 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Hash Alg. | Flags | Iterations |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Salt Length | Salt /
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef VALIDATOR_VAL_NSEC3_H
|
||||
|
|
@ -51,4 +74,23 @@ struct reply_info;
|
|||
struct query_info;
|
||||
struct key_entry_key;
|
||||
|
||||
/**
|
||||
* 0 1 2 3 4 5 6 7
|
||||
* +-+-+-+-+-+-+-+-+
|
||||
* | |O|
|
||||
* +-+-+-+-+-+-+-+-+
|
||||
* The OPT-OUT bit in the NSEC3 flags field.
|
||||
* If enabled, there can be zero or more unsigned delegations in the span.
|
||||
* If disabled, there are zero unsigned delegations in the span.
|
||||
*/
|
||||
#define NSEC3_OPTOUT 0x01
|
||||
/**
|
||||
* The unknown flags in the NSEC3 flags field.
|
||||
* They must be zero, or the NSEC3 is ignored.
|
||||
*/
|
||||
#define NSEC3_UNKNOWN_FLAGS 0xFE
|
||||
|
||||
/** The SHA1 hash algorithm for NSEC3 */
|
||||
#define NSEC3_HASH_SHA1 0x01
|
||||
|
||||
#endif /* VALIDATOR_VAL_NSEC3_H */
|
||||
|
|
|
|||
|
|
@ -54,10 +54,50 @@
|
|||
#include "util/region-allocator.h"
|
||||
#include "util/config_file.h"
|
||||
|
||||
/** fill up nsec3 key iterations config entry */
|
||||
static int
|
||||
fill_nsec3_iter(struct val_env* ve, char* s, int c)
|
||||
{
|
||||
char* e;
|
||||
int i;
|
||||
free(ve->nsec3_keysize);
|
||||
free(ve->nsec3_maxiter);
|
||||
ve->nsec3_keysize = (size_t*)calloc(sizeof(size_t), (size_t)c);
|
||||
ve->nsec3_maxiter = (size_t*)calloc(sizeof(size_t), (size_t)c);
|
||||
if(!ve->nsec3_keysize || !ve->nsec3_maxiter) {
|
||||
log_err("out of memory");
|
||||
return 0;
|
||||
}
|
||||
for(i=0; i<c; i++) {
|
||||
ve->nsec3_keysize[i] = (size_t)strtol(s, &e, 10);
|
||||
if(s == e) {
|
||||
log_err("cannot parse: %s", s);
|
||||
return 0;
|
||||
}
|
||||
s = e;
|
||||
ve->nsec3_maxiter[i] = (size_t)strtol(s, &e, 10);
|
||||
if(s == e) {
|
||||
log_err("cannot parse: %s", s);
|
||||
return 0;
|
||||
}
|
||||
s = e;
|
||||
if(i>0 && ve->nsec3_keysize[i-1] >= ve->nsec3_keysize[i]) {
|
||||
log_err("nsec3 key iterations not ascending: %d %d",
|
||||
(int)ve->nsec3_keysize[i-1],
|
||||
(int)ve->nsec3_keysize[i]);
|
||||
return 0;
|
||||
}
|
||||
verbose(VERB_ALGO, "validator nsec3cfg keysz %d mxiter %d",
|
||||
(int)ve->nsec3_keysize[i], (int)ve->nsec3_maxiter[i]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** apply config settings to validator */
|
||||
static int
|
||||
val_apply_cfg(struct val_env* val_env, struct config_file* cfg)
|
||||
{
|
||||
int c;
|
||||
val_env->bogus_ttl = (uint32_t)cfg->bogus_ttl;
|
||||
val_env->clean_additional = cfg->val_clean_additional;
|
||||
val_env->permissive_mode = cfg->val_permissive_mode;
|
||||
|
|
@ -78,6 +118,17 @@ val_apply_cfg(struct val_env* val_env, struct config_file* cfg)
|
|||
return 0;
|
||||
}
|
||||
val_env->date_override = cfg->val_date_override;
|
||||
c = cfg_count_numbers(cfg->val_nsec3_key_iterations);
|
||||
if(c < 1 || (c&1)) {
|
||||
log_err("validator: unparseable or odd nsec3 key "
|
||||
"iterations: %s", cfg->val_nsec3_key_iterations);
|
||||
return 0;
|
||||
}
|
||||
val_env->nsec3_keyiter_count = c/2;
|
||||
if(!fill_nsec3_iter(val_env, cfg->val_nsec3_key_iterations, c/2)) {
|
||||
log_err("validator: cannot apply nsec3 key iterations");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +162,8 @@ val_deinit(struct module_env* env, int id)
|
|||
val_env = (struct val_env*)env->modinfo[id];
|
||||
anchors_delete(val_env->anchors);
|
||||
key_cache_delete(val_env->kcache);
|
||||
free(val_env->nsec3_keysize);
|
||||
free(val_env->nsec3_maxiter);
|
||||
free(val_env);
|
||||
}
|
||||
|
||||
|
|
@ -1819,7 +1872,8 @@ val_get_mem(struct module_env* env, int id)
|
|||
if(!ve)
|
||||
return 0;
|
||||
return sizeof(*ve) + key_cache_get_mem(ve->kcache) +
|
||||
anchors_get_mem(ve->anchors);
|
||||
anchors_get_mem(ve->anchors) +
|
||||
sizeof(size_t)*2*ve->nsec3_keyiter_count;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -87,6 +87,25 @@ struct val_env {
|
|||
* hurting responses to clients.
|
||||
*/
|
||||
int permissive_mode;
|
||||
|
||||
/**
|
||||
* Number of entries in the NSEC3 maximum iteration count table.
|
||||
* Keep this table short, and sorted by size
|
||||
*/
|
||||
int nsec3_keyiter_count;
|
||||
|
||||
/**
|
||||
* NSEC3 maximum iteration count per signing key size.
|
||||
* This array contains key size values (in increasing order)
|
||||
*/
|
||||
size_t* nsec3_keysize;
|
||||
|
||||
/**
|
||||
* NSEC3 maximum iteration count per signing key size.
|
||||
* This array contains the maximum iteration count for the keysize
|
||||
* in the keysize array.
|
||||
*/
|
||||
size_t* nsec3_maxiter;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue