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;
}
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)
{
if (*dname)
@ -849,7 +817,10 @@ static int nsec_covers_name(
nsec_cmp = dname_compare(owner, next);
if (nsec_cmp < 0) {
/* Regular NSEC */
/* Regular NSEC
* >= so it can match the wildcard
* (for wildcard NODATA proofs).
*/
return dname_compare(name, owner) >= 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))
return 0;
label[label[0]+1] = 0;
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);
nsec_cmp = dname_compare(owner, next);
if (nsec_cmp < 0) {
DEBUG_SEC("nsec owner < next\n");
if (nsec_cmp >= 0) {
/* 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
&& 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
&& 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))) {
debug_sec_print_rrset("NSEC3: ", n);
@ -1056,14 +1049,21 @@ static int find_nsec_covering_name(
}
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)'*' };
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;
if (opt_out)
*opt_out = my_opt_out;
/* Wild card not needed on a "covering" NODATA response,
* 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
* rcode is always NOERROR.
*/
if (opt_out)
if (my_opt_out)
return keytag;
nc_name += *nc_name + 1;
(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.
* 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;
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);
if (opt_out)
opt_out = 0;
/* The NSEC NODATA case
* ====================
* 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
* and not the child (so no SOA).
* Except for the root that is checked by itself.
*/
&& ( 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,
* 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
* =====================
* 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)) {
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)
/* ce is potentially the NSEC3 that matches complete qname
* (so is also the closest encloser)
*/
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)
/* And it must have a valid signature */
&& (keytag = a_key_signed_rrset(keyset, ce))) {
debug_sec_print_rrset("NSEC3 No Data for: ", rrset);
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
* ========================+
* 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(" 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;
}
}
@ -1294,7 +1356,7 @@ static int chain_node_get_trusted_keys(
return GETDNS_DNSSEC_SECURE;
}
/* ta is ZSK */
if ((keytag = key_proves_nonexistance(ta, &node->ds))) {
if ((keytag = key_proves_nonexistance(ta, &node->ds, NULL))) {
node->ds_signer = keytag;
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) */
ta = *keys;
/* 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;
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)
{
getdns_rrset *keys;
int s, keytag;
int s, keytag, opt_out;
if ((s = chain_node_get_trusted_keys(head->parent, ta, &keys))
!= 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))) {
head->signer = keytag;
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;
return GETDNS_DNSSEC_SECURE;
return opt_out ? GETDNS_DNSSEC_INSECURE : GETDNS_DNSSEC_SECURE;
}
return GETDNS_DNSSEC_BOGUS;
}
@ -1560,15 +1631,24 @@ static void check_chain_complete(chain_head *chain)
#ifdef STUB_NATIVE_DNSSEC
/* Perform validation only on GETDNS_RESOLUTION_STUB (unbound_id == -1)
* TODO: When minimizing the validation chain (i.e. returning a single
* RRSIG per RRSET, it might be usefull to perform a fake dnssec
* validation to find out which RRSIGs should be returned.
* Or when asked for the validation chain (to identify the RRSIGs that
* signed the RRSETs, so that only those will be included in the
* 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,
context->trust_anchors, context->trust_anchors_len));
#endif
val_chain_list = dnsreq->dnssec_return_validation_chain
? getdns_list_create_with_context(context) : NULL;