NSEC NXDOMAIN + NSEC3 denial of exist. validation

This commit is contained in:
Willem Toorop 2015-06-26 00:26:40 +02:00
parent ea69d30e64
commit 19b79b066f
1 changed files with 435 additions and 87 deletions

View File

@ -50,6 +50,7 @@
#include "gldns/str2wire.h"
#include "gldns/wire2str.h"
#include "gldns/keyraw.h"
#include "gldns/parseutil.h"
#include "general.h"
#include "dict.h"
@ -257,6 +258,12 @@ static rrtype_iter *rrtype_iter_init(rrtype_iter *i, getdns_rrset *rrset)
i->rrset->name, i->rrset->rr_class, i->rrset->rr_type);
}
inline static int rrset_has_rrs(getdns_rrset *rrset)
{
rrtype_iter rr_spc;
return rrtype_iter_init(&rr_spc, rrset) != NULL;
}
static rrsig_iter *rrsig_iter_next(rrsig_iter *i)
{
return (rrsig_iter *) rr_iter_rrsig_covering(
@ -426,9 +433,14 @@ struct chain_node {
chain_head *chains;
};
inline static int _dname_len(uint8_t *name)
{
uint8_t *p;
for (p = name; *p; p += *p + 1);
return p - name + 1;
}
#ifdef STUB_NATIVE_DNSSEC
static int key_matches_signer(getdns_rrset *dnskey, getdns_rrset *rrset)
{
rrtype_iter rr_spc, *rr;
@ -497,11 +509,11 @@ static int _getdns_verify_rrsig(getdns_rrset *rrset, rrsig_iter *rrsig, rrtype_i
ldns_rr *rrsig_l = rr2ldns_rr(&rrsig->rr_i);
ldns_rr *key_l = rr2ldns_rr(&key->rr_i);
int r;
/*
fprintf(stderr, "rrsig: " ); ldns_rr_print(stderr, rrsig_l); fprintf(stderr, "\n");
fprintf(stderr, "dnskey: "); ldns_rr_print(stderr, key_l); fprintf(stderr, "\n");
fprintf(stderr, "rrset: " ); ldns_rr_list_print(stderr, rrset_l); fprintf(stderr, "\n");
*/
/* TODO: In case of wildcard (rrsig's "Labels" rdata field is smaller
* than the number of labels in the owner name) also validate the NSEC
* or NSEC3 that deniese existance of a more specific answer.
*/
r = rrset_l && rrsig_l && key_l && ldns_verify_rrsig(rrset_l, rrsig_l, key_l) == LDNS_STATUS_OK;
ldns_rr_list_deep_free(rrset_l);
ldns_rr_free(rrsig_l);
@ -509,6 +521,27 @@ static int _getdns_verify_rrsig(getdns_rrset *rrset, rrsig_iter *rrsig, rrtype_i
return r;
}
static uint8_t *_getdns_nsec3_hash_label(uint8_t *label, size_t label_len,
uint8_t *name, uint8_t algorithm, uint16_t iterations, uint8_t *salt)
{
ldns_rdf name_l = { _dname_len(name), LDNS_RDF_TYPE_DNAME, name };
ldns_rdf *hname_l;
if (!(hname_l = ldns_nsec3_hash_name(
&name_l, algorithm, iterations, *salt, salt + 1)))
return NULL;
if ( label_len < hname_l->_size-1
|| label_len < *((uint8_t *)hname_l->_data) + 1
|| hname_l->_size-1 < *((uint8_t *)hname_l->_data) + 1) {
ldns_rdf_deep_free(hname_l);
return NULL;
}
memcpy(label, hname_l->_data, *((uint8_t *)hname_l->_data) + 1);
ldns_rdf_deep_free(hname_l);
return label;
}
static int dnskey_signed_rrset(rrtype_iter *dnskey, getdns_rrset *rrset)
{
rrsig_iter rrsig_spc, *rrsig;
@ -621,43 +654,13 @@ static int ds_authenticates_keys(getdns_rrset *ds_set, getdns_rrset *dnskey_set)
return 0;
}
#if 0
uint8_t *name = rrset->name, cname_spc[256], *cname = rrset->name;
size_t anti_loop = 1000, cname_len = sizeof(cname_spc);
priv_getdns_rdf_iter rdf_spc, *rdf;
/* First, try to find the canonical name that is denied.
* CNAMEs authentication is checked with their own chain_head
* entry points, so no need to check signatures here.
*/
while (anti_loop--) {
for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len)
; i
; i = rrset_iter_next(i)) {
if (i->rrset.rr_type != GETDNS_RRTYPE_CNAME)
continue;
if (priv_getdns_dname_equal(name, i->rrset.name) &&
(rdf = priv_getdns_rdf_iter_init(
&rdf_spc, &i->rr_i)) &&
(cname = priv_getdns_rdf_if_or_as_decompressed(
rdf, cname_spc, &cname_len)))
break;
}
if (priv_getdns_dname_equal(name, cname))
break;
name = cname;
}
#endif
static int bitmap_contains_rrtype(priv_getdns_rdf_iter *bitmap, uint16_t rr_type)
{
uint8_t *dptr, *dend;
uint8_t window = rr_type >> 8;
uint8_t subtype = rr_type & 0xFF;
DEBUG_SEC("bitmap: %p, type: %d\n", bitmap, (int)rr_type);
if (!bitmap)
return 0;
dptr = bitmap->pos;
@ -675,60 +678,376 @@ static int bitmap_contains_rrtype(priv_getdns_rdf_iter *bitmap, uint16_t rr_type
return 0;
}
static int key_proves_nonexistance(getdns_rrset *dnskey, getdns_rrset *rrset)
static int nsec_bitmap_excludes_rrtype(getdns_rrset *nsec_rrset, uint16_t rr_type)
{
rrset_iter i_spc, *i;
size_t n_nsecs = 0, n_nsec3s = 0;
getdns_rrset nsecs[2], nsec3s[3];
rrtype_iter nsec_space, *nsec;
rrtype_iter nsec_space, *nsec_rr;
priv_getdns_rdf_iter bitmap_spc, *bitmap;
size_t j;
return (nsec_rrset->rr_type == GETDNS_RRTYPE_NSEC ||
nsec_rrset->rr_type == GETDNS_RRTYPE_NSEC3 )
&& (nsec_rr = rrtype_iter_init(&nsec_space, nsec_rrset))
&& (bitmap = priv_getdns_rdf_iter_init_at(&bitmap_spc, &nsec_rr->rr_i,
nsec_rrset->rr_type == GETDNS_RRTYPE_NSEC ? 1: 5))
&& !bitmap_contains_rrtype(bitmap, rr_type);
return 0;
}
static uint8_t **reverse_labels(uint8_t *dname, uint8_t **labels)
{
if (*dname)
labels = reverse_labels(dname + *dname + 1, labels);
*labels = dname;
return labels + 1;
}
static int dname_compare(uint8_t *left, uint8_t *right)
{
uint8_t *llabels[128], *rlabels[128], **last_llabel, **last_rlabel,
**llabel, **rlabel, *l, *r, lsz, rsz;
last_llabel = reverse_labels(left, llabels);
last_rlabel = reverse_labels(right, rlabels);
for ( llabel = llabels, rlabel = rlabels
; llabel < last_llabel
; llabel++, rlabel++ ) {
if (rlabel == last_rlabel)
return 1;
for ( l = *llabel, lsz = *l++, r = *rlabel, rsz = *r++
; lsz; l++, r++, lsz--, rsz-- ) {
if (!rsz)
return 1;
if (*l != *r && tolower((unsigned char)*l) !=
tolower((unsigned char)*r)) {
if (tolower((unsigned char)*l) <
tolower((unsigned char)*r))
return -1;
return 1;
}
}
}
return rlabel == last_rlabel ? 0 : -1;
}
static int nsec_covers_name(getdns_rrset *nsec, uint8_t *name)
{
uint8_t owner_spc[256], *owner;
size_t owner_len = sizeof(owner_spc);
uint8_t next_spc[256], *next;
size_t next_len = sizeof(next_spc);
rrtype_iter rr_spc, *rr;
priv_getdns_rdf_iter rdf_spc, *rdf;
int nsec_cmp;
if ( !(rr = rrtype_iter_init(&rr_spc, nsec))
|| !(rdf = priv_getdns_rdf_iter_init(&rdf_spc, &rr->rr_i))
|| !(owner = priv_getdns_owner_if_or_as_decompressed(
&rr->rr_i, owner_spc, &owner_len))
|| !(next = priv_getdns_rdf_if_or_as_decompressed(
rdf, next_spc, &next_len)))
return 0;
debug_sec_print_dname("nsec owner: ", owner);
debug_sec_print_dname("name : ", name);
debug_sec_print_dname("nsec next : ", next);
nsec_cmp = dname_compare(owner, next);
if (nsec_cmp < 0) {
DEBUG_SEC("nsec owner < next\n");
return dname_compare(name, owner) >= 0
&& dname_compare(name, next) < 0;
} else if (nsec_cmp > 0) {
/* The wrap around nsec */
DEBUG_SEC("nsec owner > next\n");
return dname_compare(name, owner) >= 0;
} else {
DEBUG_SEC("nsec owner == next\n");
return 1;
}
}
static int nsec_covers_wildcard(getdns_rrset *nsec, uint8_t *name)
{
uint8_t name_spc[256];
uint8_t signer_spc[256], *signer;
size_t signer_len = sizeof(signer_spc);
priv_getdns_rdf_iter rdf_spc, *rdf;
rrsig_iter rrsig_spc, *rrsig;
if ( !(rrsig = rrsig_iter_init(&rrsig_spc, nsec))
|| !(rdf = priv_getdns_rdf_iter_init_at(&rdf_spc, &rrsig->rr_i, 7))
|| !(signer = priv_getdns_rdf_if_or_as_decompressed(
rdf, signer_spc, &signer_len)))
return 0;
name = memcpy(name_spc, name, _dname_len(name));
while (*name && is_subdomain(signer, name)
&& !priv_getdns_dname_equal(signer, name)) {
debug_sec_print_dname(" name: ", name);
debug_sec_print_dname("is a subdomain of: ", signer);
name += *name - 1;
name[0] = 1;
name[1] = (uint8_t)'*';
if (nsec_covers_name(nsec, name))
return 1;
name += 2;
}
return 0;
}
static uint8_t *name2nsec3_label(
getdns_rrset *nsec3, uint8_t *name, uint8_t *label, size_t label_len)
{
rrsig_iter rrsig_spc, *rrsig;
priv_getdns_rdf_iter rdf_spc, *rdf;
uint8_t signer_spc[256], *signer;
size_t signer_len = sizeof(signer_spc);
rrtype_iter rr_spc, *rr;
if (/* With the "first" signature */
(rrsig = rrsig_iter_init(&rrsig_spc, nsec3))
/* Access the signer name rdata field (7th) */
&& (rdf = priv_getdns_rdf_iter_init_at(
&rdf_spc, &rrsig->rr_i, 7))
/* Verify & decompress */
&& (signer = priv_getdns_rdf_if_or_as_decompressed(
rdf, signer_spc, &signer_len))
/* signer of the NSEC3 is direct parent for this NSEC3? */
&& priv_getdns_dname_equal(
signer, nsec3->name + *nsec3->name + 1)
/* signer of the NSEC3 is parent of name? */
&& is_subdomain(signer, name)
/* Initialize rr for getting NSEC3 rdata fields */
&& (rr = rrtype_iter_init(&rr_spc, nsec3))
/* Check for available space to get rdata fields */
&& rr->rr_i.rr_type + 15 <= rr->rr_i.nxt
&& rr->rr_i.rr_type + 14 + rr->rr_i.rr_type[14] <= rr->rr_i.nxt)
/* Get the hashed label */
return _getdns_nsec3_hash_label(label, label_len, name,
rr->rr_i.rr_type[10],
gldns_read_uint16(rr->rr_i.rr_type + 12),
rr->rr_i.rr_type + 14);
return NULL;
}
static int nsec3_matches_name(getdns_rrset *nsec3, uint8_t *name)
{
uint8_t label[64];
if (name2nsec3_label(nsec3, name, label, sizeof(label)))
return *nsec3->name == label[0] /* Labels same size? */
&& memcmp(nsec3->name + 1, label + 1, label[0]) == 0;
return 0;
}
static int nsec3_covers_name(getdns_rrset *nsec3, uint8_t *name)
{
uint8_t label[65], next[65], owner[65];
rrtype_iter rr_spc, *rr;
priv_getdns_rdf_iter rdf_spc, *rdf;
int nsz = 0, nsec_cmp;
if (!name2nsec3_label(nsec3, name, label, sizeof(label)-1))
return 0;
label[label[0]+1] = 0;
if ( !(rr = rrtype_iter_init(&rr_spc, nsec3))
|| !(rdf = priv_getdns_rdf_iter_init_at(&rdf_spc, &rr->rr_i, 4))
|| rdf->pos + *rdf->pos + 1 > rdf->nxt
|| (nsz = gldns_b32_ntop_extended_hex(rdf->pos + 1, *rdf->pos,
(char *)next + 1, sizeof(next)-2)) < 0
|| *nsec3->name > sizeof(owner) - 2
|| !memcpy(owner, nsec3->name, *nsec3->name + 1)) {
DEBUG_SEC("ERROR!!!!\n");
}
owner[owner[0]+1] = 0;
next[(next[0] = (uint8_t)nsz)+1] = 0;
debug_sec_print_dname("NSEC3 for: ", name);
debug_sec_print_dname(" is: ", label);
debug_sec_print_dname("inbetween: ", owner);
debug_sec_print_dname(" and: ", next);
nsec_cmp = dname_compare(owner, next);
if (nsec_cmp < 0) {
DEBUG_SEC("nsec owner < next\n");
return dname_compare(label, owner) >= 0
&& dname_compare(label, next) < 0;
} else if (nsec_cmp > 0) {
/* The wrap around nsec */
DEBUG_SEC("nsec owner > next\n");
return dname_compare(label, owner) >= 0;
} else {
DEBUG_SEC("nsec owner == next\n");
return 1;
}
}
static int find_nsec3_covering_name(
getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *name)
{
rrset_iter i_spc, *i;
getdns_rrset *n;
for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len)
; i ; i = rrset_iter_next(i)) {
if ((n = rrset_iter_value(i))->rr_type == GETDNS_RRTYPE_NSEC3
&& nsec3_covers_name(n, name)
&& a_key_signed_rrset(dnskey, n)) {
debug_sec_print_rrset("NSEC3: ", n);
debug_sec_print_dname("covered: ", name);
return 1;
}
}
return 0;
}
static int nsec3_find_next_closer_and_wildcard(
getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *nc_name, int noerror)
{
uint8_t wc_name[256] = { 1, (uint8_t)'*' };
if (!find_nsec3_covering_name(dnskey, rrset, nc_name))
return 0;
if (noerror)
return 1; /* TODO: Check for opt-out bit? */
nc_name += *nc_name + 1;
(void) memcpy(wc_name + 2, nc_name, _dname_len(nc_name));
return find_nsec3_covering_name(dnskey, rrset, wc_name);
}
static int key_proves_nonexistance(getdns_rrset *dnskey, getdns_rrset *rrset)
{
getdns_rrset nsec_rrset, *cover, *wildcard, *ce;
rrset_iter i_spc, *i;
rrset_iter j_spc, *j;
uint8_t *ce_name, *nc_name;
assert(dnskey->rr_type == GETDNS_RRTYPE_DNSKEY);
/* First collect nsecs and nsec3s */
/* The NSEC NODATA case
* ====================
* NSEC has same ownername as the rrset to deny.
* Only the rr_type is missing from the bitmap.
*/
nsec_rrset = *rrset;
nsec_rrset.rr_type = GETDNS_RRTYPE_NSEC;
if (nsec_bitmap_excludes_rrtype(&nsec_rrset, rrset->rr_type) &&
a_key_signed_rrset(dnskey, &nsec_rrset)) {
debug_sec_print_rrset("NSEC NODATA proof for: ", rrset);
return 1;
}
/* The NSEC NXDOMAIN case
* ======================
* - First find the NSEC that covers the owner name.
*/
for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len)
; i
; i = rrset_iter_next(i)) {
; i ; i = rrset_iter_next(i)) {
if (i->rrset.rr_type == GETDNS_RRTYPE_NSEC) {
if ((cover=rrset_iter_value(i))->rr_type != GETDNS_RRTYPE_NSEC
|| !nsec_covers_name(cover, rrset->name)
|| !a_key_signed_rrset(dnskey, cover))
continue;
nsecs[n_nsecs] = i->rrset;
if (++n_nsecs >= sizeof(nsecs))
break;
debug_sec_print_rrset(" NSEC: ", cover);
debug_sec_print_dname("covers name: ", rrset->name);
} else if (i->rrset.rr_type == GETDNS_RRTYPE_NSEC3) {
for ( j = rrset_iter_init(&j_spc, rrset->pkt, rrset->pkt_len)
; j ; j = rrset_iter_next(i)) {
nsecs[n_nsecs] = i->rrset;
if (++n_nsec3s >= sizeof(nsec3s))
break;
if ((wildcard = rrset_iter_value(j))->rr_type
!= GETDNS_RRTYPE_NSEC
|| !nsec_covers_wildcard(wildcard, rrset->name)
|| !a_key_signed_rrset(dnskey, wildcard))
continue;
debug_sec_print_rrset("NSEC NXDOMAIN proof for: ", rrset);
return 1;
}
}
if (!n_nsecs && !n_nsec3s)
return 0;
/* We assume rrset already contains the canonical name */
if (n_nsecs) {
/* The NOERROR case */
for (j = 0; j < n_nsecs; j++) {
if (priv_getdns_dname_equal(rrset->name, nsecs[j].name))
return (nsec = rrtype_iter_init(
&nsec_space, &nsecs[j])) &&
/* The NSEC3 NODATA case
* =====================
* NSEC3 has same (hashed) ownername as the rrset to deny.
* Only the rr_type (and CNAME) is missing from the bitmap.
*/
for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len)
; i ; i = rrset_iter_next(i)) {
(bitmap = priv_getdns_rdf_iter_init_at(
&bitmap_spc, &nsec->rr_i, 1)) &&
if ((ce = rrset_iter_value(i))->rr_type == GETDNS_RRTYPE_NSEC3
&& nsec_bitmap_excludes_rrtype(ce, rrset->rr_type)
&& nsec_bitmap_excludes_rrtype(ce, GETDNS_RRTYPE_CNAME)
&& nsec3_matches_name(ce, rrset->name)
&& a_key_signed_rrset(dnskey, ce)) {
!bitmap_contains_rrtype(
bitmap, rrset->rr_type) &&
a_key_signed_rrset(dnskey, &nsecs[j]);
debug_sec_print_rrset("NSEC3 No Data for: ", rrset);
return 1;
}
/* The NXDOMAIN case */
/* One should cover the name */
/* One should cover the wildcard */
}
/* TODO: proof nsec3 non existance */
}
/* The NSEC3 NXDOMAIN case
* =====================
* First find the closest encloser.
*/
for ( nc_name = rrset->name, ce_name = rrset->name + *rrset->name + 1
; *ce_name ; nc_name = ce_name, ce_name += *ce_name + 1) {
for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len)
; i ; i = rrset_iter_next(i)) {
if ((ce = rrset_iter_value(i))->rr_type
== GETDNS_RRTYPE_NSEC3
&& nsec3_matches_name(ce, ce_name)
&& a_key_signed_rrset(dnskey, ce)) {
debug_sec_print_rrset(
"Closest Encloser: ", ce);
debug_sec_print_dname(
"Closest Encloser: ", ce_name);
debug_sec_print_dname(
" Next closer: ", nc_name);
if (nsec3_find_next_closer_and_wildcard(
dnskey, rrset, nc_name,
/* Wild card not needed on a "covering"
* NODATA response, because of opt-out?
*/
GLDNS_RCODE_WIRE(rrset->pkt) ==
GETDNS_RCODE_NOERROR))
return 1;
}
}
}
return 0;
}
@ -800,10 +1119,14 @@ static int chain_head_validate_with_ta(chain_head *head, getdns_rrset *ta)
!= GETDNS_DNSSEC_SECURE)
return s;
if (a_key_signed_rrset(keys, &head->rrset))
if (rrset_has_rrs(&head->rrset)) {
if (a_key_signed_rrset(keys, &head->rrset))
return GETDNS_DNSSEC_SECURE;
} else if (key_proves_nonexistance(keys, &head->rrset))
return GETDNS_DNSSEC_SECURE;
else
return GETDNS_DNSSEC_BOGUS;
return GETDNS_DNSSEC_BOGUS;
}
static int chain_head_validate(chain_head *head, rrset_iter *tas)
@ -851,7 +1174,6 @@ static void chain_validate_dnssec(chain_head *chain, rrset_iter *tas)
default : break;
}
}
/* TODO: For secure nxdomains, test if the nsecs are correct */
}
#endif
@ -1186,14 +1508,6 @@ static void val_chain_node_soa_cb(getdns_dns_req *dnsreq)
check_chain_complete(node->chains);
}
static uint8_t **reverse_labels(uint8_t *dname, uint8_t **labels)
{
if (*dname)
labels = reverse_labels(dname + *dname + 1, labels);
*labels = dname;
return labels + 1;
}
static chain_head *add_rrset2val_chain(struct mem_funcs *mf,
chain_head **chain_p, getdns_rrset *rrset, getdns_network_req *netreq)
{
@ -1324,6 +1638,13 @@ static void add_netreq2val_chain(
struct mem_funcs *mf;
getdns_rrset empty_rrset;
getdns_rrset q_rrset;
uint8_t cname_spc[256];
size_t cname_len = sizeof(cname_spc);
size_t anti_loop;
priv_getdns_rdf_iter rdf_spc, *rdf;
rrtype_iter *rr, rr_spc;
assert(netreq->response);
assert(netreq->response_len >= GLDNS_HEADER_SIZE);
@ -1370,6 +1691,33 @@ static void add_netreq2val_chain(
else
val_chain_sched_soa(head, rrset->name);
}
/* For NOERROR/NODATA or NXDOMAIN responses add extra rrset to
* the validation chain so the denial of existence will be
* checked eventually.
*/
/* First find the canonical name for the question */
q_rrset.name = netreq->query + GLDNS_HEADER_SIZE;
q_rrset.rr_type = GETDNS_RRTYPE_CNAME;
q_rrset.rr_class = netreq->request_class;
q_rrset.pkt = netreq->response;
q_rrset.pkt_len = netreq->response_len;
for (anti_loop = 1000; anti_loop; anti_loop--) {
if (!(rr = rrtype_iter_init(&rr_spc, &q_rrset)))
break;
if (!(rdf = priv_getdns_rdf_iter_init(&rdf_spc, &rr->rr_i)))
break;
q_rrset.name = priv_getdns_rdf_if_or_as_decompressed(
rdf, cname_spc, &cname_len);
}
q_rrset.rr_type = netreq->request_type;
if (!(rr = rrtype_iter_init(&rr_spc, &q_rrset))) {
debug_sec_print_rrset("Adding NX rrset: ", &q_rrset);
add_rrset2val_chain(mf, chain_p, &q_rrset, netreq);
}
}
static void get_val_chain(getdns_dns_req *dnsreq)