Three NSEC3 related things:

- Better checking for type bits
- NSEC3 Insecure proofs for opt-out on head's
- NSEC3 wildcard NODATA proof
This commit is contained in:
Willem Toorop 2015-07-04 10:23:02 +02:00
parent 99f0026961
commit f59b32414c
1 changed files with 144 additions and 64 deletions

View File

@ -716,38 +716,6 @@ static int bitmap_has_type(priv_getdns_rdf_iter *bitmap, uint16_t rr_type)
return 0; return 0;
} }
static int nsec_bitmap_excludes_rrtype(getdns_rrset *nsec_rrset, uint16_t rr_type)
{
rrtype_iter nsec_spc, *nsec_rr;
priv_getdns_rdf_iter bitmap_spc, *bitmap;
return (nsec_rrset->rr_type == GETDNS_RRTYPE_NSEC ||
nsec_rrset->rr_type == GETDNS_RRTYPE_NSEC3 )
&& (nsec_rr = rrtype_iter_init(&nsec_spc, nsec_rrset))
/* On empty non terminals there might not be a bitmap
* since that means rrtype is excluded, we must return true then
*/
&& ( !(bitmap = priv_getdns_rdf_iter_init_at(
&bitmap_spc, &nsec_rr->rr_i,
nsec_rrset->rr_type == GETDNS_RRTYPE_NSEC ? 1: 5))
|| !bitmap_has_type(bitmap, rr_type)
);
}
static int nsec_bitmap_includes_rrtype(getdns_rrset *nsec_rrset, uint16_t rr_type)
{
rrtype_iter nsec_spc, *nsec_rr;
priv_getdns_rdf_iter bitmap_spc, *bitmap;
return (nsec_rrset->rr_type == GETDNS_RRTYPE_NSEC ||
nsec_rrset->rr_type == GETDNS_RRTYPE_NSEC3 )
&& (nsec_rr = rrtype_iter_init(&nsec_spc, 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_has_type(bitmap, rr_type);
}
static uint8_t **reverse_labels(uint8_t *dname, uint8_t **labels) static uint8_t **reverse_labels(uint8_t *dname, uint8_t **labels)
{ {
if (*dname) if (*dname)
@ -849,7 +817,10 @@ static int nsec_covers_name(
nsec_cmp = dname_compare(owner, next); nsec_cmp = dname_compare(owner, next);
if (nsec_cmp < 0) { if (nsec_cmp < 0) {
/* Regular NSEC */ /* Regular NSEC
* >= so it can match the wildcard
* (for wildcard NODATA proofs).
*/
return dname_compare(name, owner) >= 0 return dname_compare(name, owner) >= 0
&& dname_compare(name, next) < 0; && dname_compare(name, next) < 0;
@ -947,6 +918,7 @@ static int nsec3_covers_name(getdns_rrset *nsec3, uint8_t *name, int *opt_out)
if (!name2nsec3_label(nsec3, name, label, sizeof(label)-1)) if (!name2nsec3_label(nsec3, name, label, sizeof(label)-1))
return 0; return 0;
label[label[0]+1] = 0; label[label[0]+1] = 0;
if ( !(rr = rrtype_iter_init(&rr_spc, nsec3)) if ( !(rr = rrtype_iter_init(&rr_spc, nsec3))
@ -972,17 +944,18 @@ static int nsec3_covers_name(getdns_rrset *nsec3, uint8_t *name, int *opt_out)
debug_sec_print_dname(" and: ", next); debug_sec_print_dname(" and: ", next);
nsec_cmp = dname_compare(owner, next); nsec_cmp = dname_compare(owner, next);
if (nsec_cmp < 0) { if (nsec_cmp >= 0) {
DEBUG_SEC("nsec owner < next\n"); /* The wrap around and apex-only nsec case */
return dname_compare(label, owner) > 0
|| dname_compare(label, next) < 0;
} else {
assert(nsec_cmp < 0);
/* The normal case
* >= so it can match the wildcard
* (for wildcard NODATA proofs).
*/
return dname_compare(label, owner) >= 0 return dname_compare(label, owner) >= 0
&& dname_compare(label, next) < 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;
} }
} }
@ -1003,6 +976,26 @@ static int find_nsec_covering_name(
if ((n = rrset_iter_value(i))->rr_type == GETDNS_RRTYPE_NSEC3 if ((n = rrset_iter_value(i))->rr_type == GETDNS_RRTYPE_NSEC3
&& nsec3_covers_name(n, name, opt_out) && nsec3_covers_name(n, name, opt_out)
/* Get the bitmap rdata field */
&& (nsec_rr = rrtype_iter_init(&nsec_spc, n))
&& (bitmap = priv_getdns_rdf_iter_init_at(
&bitmap_spc, &nsec_rr->rr_i, 5))
/* NSEC should cover, but not match name...
* Unless it is wildcard match, but then we have to check
* that rrset->rr_type is not enlisted, because otherwise
* it should have matched the wildcard.
*
* Also no CNAME... cause that should have matched too.
*/
&& ( !nsec3_matches_name(n, name)
|| ( name[0] == 1 && name[1] == (uint8_t)'*'
&& !bitmap_has_type(bitmap, rrset->rr_type)
&& !bitmap_has_type(bitmap, GETDNS_RRTYPE_CNAME)
)
)
&& (keytag = a_key_signed_rrset(dnskey, n))) { && (keytag = a_key_signed_rrset(dnskey, n))) {
debug_sec_print_rrset("NSEC3: ", n); debug_sec_print_rrset("NSEC3: ", n);
@ -1056,14 +1049,21 @@ static int find_nsec_covering_name(
} }
static int nsec3_find_next_closer( static int nsec3_find_next_closer(
getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *nc_name) getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *nc_name, int *opt_out)
{ {
uint8_t wc_name[256] = { 1, (uint8_t)'*' }; uint8_t wc_name[256] = { 1, (uint8_t)'*' };
int opt_out, keytag; int my_opt_out, keytag;
if (!(keytag = find_nsec_covering_name(dnskey, rrset, nc_name, &opt_out))) if (opt_out)
*opt_out = 0;
if (!(keytag = find_nsec_covering_name(
dnskey, rrset, nc_name, &my_opt_out)))
return 0; return 0;
if (opt_out)
*opt_out = my_opt_out;
/* Wild card not needed on a "covering" NODATA response, /* Wild card not needed on a "covering" NODATA response,
* because of opt-out? * because of opt-out?
* *
@ -1072,13 +1072,13 @@ static int nsec3_find_next_closer(
* (if we came here via getdns_validate_dnssec) in which case * (if we came here via getdns_validate_dnssec) in which case
* rcode is always NOERROR. * rcode is always NOERROR.
*/ */
if (opt_out) if (my_opt_out)
return keytag; return keytag;
nc_name += *nc_name + 1; nc_name += *nc_name + 1;
(void) memcpy(wc_name + 2, nc_name, _dname_len(nc_name)); (void) memcpy(wc_name + 2, nc_name, _dname_len(nc_name));
return find_nsec_covering_name(dnskey, rrset, wc_name, NULL); return find_nsec_covering_name(dnskey, rrset, wc_name, opt_out);
} }
/* /*
@ -1088,7 +1088,8 @@ static int nsec3_find_next_closer(
* On success returns the keytag (+0x10000) of the key that signed the proof. * On success returns the keytag (+0x10000) of the key that signed the proof.
* Or else returns 0. * Or else returns 0.
*/ */
static int key_proves_nonexistance(getdns_rrset *keyset, getdns_rrset *rrset) static int key_proves_nonexistance(
getdns_rrset *keyset, getdns_rrset *rrset, int *opt_out)
{ {
getdns_rrset nsec_rrset, *cover, *ce; getdns_rrset nsec_rrset, *cover, *ce;
rrtype_iter nsec_spc, *nsec_rr; rrtype_iter nsec_spc, *nsec_rr;
@ -1100,6 +1101,9 @@ static int key_proves_nonexistance(getdns_rrset *keyset, getdns_rrset *rrset)
assert(keyset->rr_type == GETDNS_RRTYPE_DNSKEY); assert(keyset->rr_type == GETDNS_RRTYPE_DNSKEY);
if (opt_out)
opt_out = 0;
/* The NSEC NODATA case /* The NSEC NODATA case
* ==================== * ====================
* NSEC has same ownername as the rrset to deny. * NSEC has same ownername as the rrset to deny.
@ -1125,9 +1129,12 @@ static int key_proves_nonexistance(getdns_rrset *keyset, getdns_rrset *rrset)
/* In case of a DS query, make sure we have the parent side NSEC /* In case of a DS query, make sure we have the parent side NSEC
* and not the child (so no SOA). * and not the child (so no SOA).
* Except for the root that is checked by itself.
*/ */
&& ( rrset->rr_type != GETDNS_RRTYPE_DS && ( rrset->rr_type != GETDNS_RRTYPE_DS
|| !bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)) || !bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)
|| *rrset->name == 0
)
/* If not a DS query, then make sure the NSEC does not contain NS, /* If not a DS query, then make sure the NSEC does not contain NS,
* or if it does, then also contains SOA, otherwise we have a parent * or if it does, then also contains SOA, otherwise we have a parent
@ -1226,21 +1233,74 @@ static int key_proves_nonexistance(getdns_rrset *keyset, getdns_rrset *rrset)
/* The NSEC3 NODATA case /* The NSEC3 NODATA case
* ===================== * =====================
* NSEC3 has same (hashed) ownername as the rrset to deny. * 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) 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 ((ce = rrset_iter_value(i))->rr_type == GETDNS_RRTYPE_NSEC3 /* ce is potentially the NSEC3 that matches complete qname
&& nsec_bitmap_excludes_rrtype(ce, rrset->rr_type) * (so is also the closest encloser)
&& nsec_bitmap_excludes_rrtype(ce, GETDNS_RRTYPE_CNAME) */
ce = rrset_iter_value(i);
if ( ce->rr_type == GETDNS_RRTYPE_NSEC3
/* A NSEC3 RR exists at the owner name of rrset
* (this is always true)
*/
&& (nsec_rr = rrtype_iter_init(&nsec_spc, ce))
/* Get the bitmap rdata field */
&& (bitmap = priv_getdns_rdf_iter_init_at(
&bitmap_spc, &nsec_rr->rr_i, 5))
/* At least the rr_type of rrset should be missing */
&& !bitmap_has_type(bitmap, rrset->rr_type)
/* If the name is a CNAME, then we should have gotten it,
* So no CNAME bit either.
*/
&& !bitmap_has_type(bitmap, GETDNS_RRTYPE_CNAME)
/* In case of a DS query, make sure we have the parent side
* NSEC and not the child (so no SOA).
* (except for the root...)
*/
&& ( rrset->rr_type != GETDNS_RRTYPE_DS
|| !bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)
|| *rrset->name == 0
)
/* If not a DS query, then make sure the NSEC does not
* contain NS, or if it does, then also contains SOA,
* otherwise we have a parent side delegation point NSEC
* where we should have gotten a child side NSEC!
*/
&& ( rrset->rr_type == GETDNS_RRTYPE_DS
|| !bitmap_has_type(bitmap, GETDNS_RRTYPE_NS)
|| bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA))
/* The qname must match the NSEC3!
* (expensive tests done last)
*/
&& nsec3_matches_name(ce, rrset->name) && nsec3_matches_name(ce, rrset->name)
/* And it must have a valid signature */
&& (keytag = a_key_signed_rrset(keyset, ce))) { && (keytag = a_key_signed_rrset(keyset, ce))) {
debug_sec_print_rrset("NSEC3 No Data for: ", rrset); debug_sec_print_rrset("NSEC3 No Data for: ", rrset);
return keytag; return keytag;
} }
} }
/* More NSEC3 NODATA cases
* ======================
* There are a few NSEC NODATA cases where qname doesn't match
* NSEC->name:
*
* - NSEC3 ownername match for qtype != NSEC3 (TODO?)
* - Wildcard NODATA (wildcard owner name match has special handing
* find_nsec_covering_name())
* - Opt-In DS NODATA (TODO, don't understand yet)
*/
/* The NSEC3 Name error case /* The NSEC3 Name error case
* ========================+ * ========================+
* First find the closest encloser. * First find the closest encloser.
@ -1261,7 +1321,9 @@ static int key_proves_nonexistance(getdns_rrset *keyset, getdns_rrset *rrset)
debug_sec_print_dname("Closest Encloser: ", ce_name); debug_sec_print_dname("Closest Encloser: ", ce_name);
debug_sec_print_dname(" Next closer: ", nc_name); debug_sec_print_dname(" Next closer: ", nc_name);
if ((keytag = nsec3_find_next_closer(keyset, rrset, nc_name))) if ((keytag = nsec3_find_next_closer(
keyset, rrset, nc_name, opt_out)))
return keytag; return keytag;
} }
} }
@ -1294,7 +1356,7 @@ static int chain_node_get_trusted_keys(
return GETDNS_DNSSEC_SECURE; return GETDNS_DNSSEC_SECURE;
} }
/* ta is ZSK */ /* ta is ZSK */
if ((keytag = key_proves_nonexistance(ta, &node->ds))) { if ((keytag = key_proves_nonexistance(ta, &node->ds, NULL))) {
node->ds_signer = keytag; node->ds_signer = keytag;
return GETDNS_DNSSEC_INSECURE; return GETDNS_DNSSEC_INSECURE;
} }
@ -1318,7 +1380,7 @@ static int chain_node_get_trusted_keys(
/* keys is an authenticated dnskey rrset always now (i.e. ZSK) */ /* keys is an authenticated dnskey rrset always now (i.e. ZSK) */
ta = *keys; ta = *keys;
/* Back up to the head */ /* Back up to the head */
if ((keytag = key_proves_nonexistance(ta, &node->ds))) { if ((keytag = key_proves_nonexistance(ta, &node->ds, NULL))) {
node->ds_signer = keytag; node->ds_signer = keytag;
return GETDNS_DNSSEC_INSECURE; return GETDNS_DNSSEC_INSECURE;
} }
@ -1339,7 +1401,7 @@ static int chain_node_get_trusted_keys(
static int chain_head_validate_with_ta(chain_head *head, getdns_rrset *ta) static int chain_head_validate_with_ta(chain_head *head, getdns_rrset *ta)
{ {
getdns_rrset *keys; getdns_rrset *keys;
int s, keytag; int s, keytag, opt_out;
if ((s = chain_node_get_trusted_keys(head->parent, ta, &keys)) if ((s = chain_node_get_trusted_keys(head->parent, ta, &keys))
!= GETDNS_DNSSEC_SECURE) != GETDNS_DNSSEC_SECURE)
@ -1349,10 +1411,19 @@ static int chain_head_validate_with_ta(chain_head *head, getdns_rrset *ta)
if ((keytag = a_key_signed_rrset(keys, &head->rrset))) { if ((keytag = a_key_signed_rrset(keys, &head->rrset))) {
head->signer = keytag; head->signer = keytag;
return GETDNS_DNSSEC_SECURE; return GETDNS_DNSSEC_SECURE;
} else if (!rrset_has_rrsigs(&head->rrset)
&& (keytag = key_proves_nonexistance(
keys, &head->rrset, &opt_out))
&& opt_out) {
head->signer = keytag;
return GETDNS_DNSSEC_INSECURE;
} }
} else if ((keytag = key_proves_nonexistance(keys, &head->rrset))) { } else if ((keytag = key_proves_nonexistance(
keys, &head->rrset, &opt_out))) {
head->signer = keytag; head->signer = keytag;
return GETDNS_DNSSEC_SECURE; return opt_out ? GETDNS_DNSSEC_INSECURE : GETDNS_DNSSEC_SECURE;
} }
return GETDNS_DNSSEC_BOGUS; return GETDNS_DNSSEC_BOGUS;
} }
@ -1560,15 +1631,24 @@ static void check_chain_complete(chain_head *chain)
#ifdef STUB_NATIVE_DNSSEC #ifdef STUB_NATIVE_DNSSEC
/* Perform validation only on GETDNS_RESOLUTION_STUB (unbound_id == -1) /* Perform validation only on GETDNS_RESOLUTION_STUB (unbound_id == -1)
* TODO: When minimizing the validation chain (i.e. returning a single * Or when asked for the validation chain (to identify the RRSIGs that
* RRSIG per RRSET, it might be usefull to perform a fake dnssec * signed the RRSETs, so that only those will be included in the
* validation to find out which RRSIGs should be returned. * validation chain)
* In any case we must have a trust anchor.
*/ */
if (chain->netreq->unbound_id == -1 && context->trust_anchors) if (( chain->netreq->unbound_id == -1
|| dnsreq->dnssec_return_validation_chain)
&& context->trust_anchors)
chain_set_netreq_dnssec_status(chain,rrset_iter_init(&tas_iter,
context->trust_anchors, context->trust_anchors_len));
#else
if (dnsreq->dnssec_return_validation_chain
&& context->trust_anchors)
chain_set_netreq_dnssec_status(chain,rrset_iter_init(&tas_iter, chain_set_netreq_dnssec_status(chain,rrset_iter_init(&tas_iter,
context->trust_anchors, context->trust_anchors_len)); context->trust_anchors, context->trust_anchors_len));
#endif #endif
val_chain_list = dnsreq->dnssec_return_validation_chain val_chain_list = dnsreq->dnssec_return_validation_chain
? getdns_list_create_with_context(context) : NULL; ? getdns_list_create_with_context(context) : NULL;