mirror of https://github.com/getdnsapi/getdns.git
INSECURE when NSEC3 iteration count too high
Fix from Wouter's review
This commit is contained in:
parent
4b53d70199
commit
e48b0c7fd7
140
src/dnssec.c
140
src/dnssec.c
|
@ -210,6 +210,9 @@
|
||||||
/* Maximum number of canonical name redirections for one name */
|
/* Maximum number of canonical name redirections for one name */
|
||||||
#define MAX_CNAMES 100
|
#define MAX_CNAMES 100
|
||||||
|
|
||||||
|
#define SIGNATURE_VERIFIED 0x10000
|
||||||
|
#define NSEC3_ITERATION_COUNT_HIGH 0x20000
|
||||||
|
|
||||||
/******************* Frequently Used Utility Functions *********************
|
/******************* Frequently Used Utility Functions *********************
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
@ -497,7 +500,7 @@ typedef struct getdns_rrset {
|
||||||
uint16_t rr_type;
|
uint16_t rr_type;
|
||||||
uint8_t *pkt;
|
uint8_t *pkt;
|
||||||
size_t pkt_len;
|
size_t pkt_len;
|
||||||
uint8_t name_spc[];
|
uint8_t name_spc[1];
|
||||||
} getdns_rrset;
|
} getdns_rrset;
|
||||||
|
|
||||||
typedef struct rrtype_iter {
|
typedef struct rrtype_iter {
|
||||||
|
@ -1460,6 +1463,41 @@ static uint8_t *name2nsec3_label(
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int nsec3_iteration_count_high(rrtype_iter *dnskey, getdns_rrset *nsec3)
|
||||||
|
{
|
||||||
|
rrtype_iter rr_spc, *rr;
|
||||||
|
size_t bits;
|
||||||
|
|
||||||
|
/* No NSEC3, then iteration count is not too high */
|
||||||
|
if (nsec3->rr_type != GETDNS_RRTYPE_NSEC3)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Enough space to at least read algorithm field?
|
||||||
|
* Without key data iteration count is definitely too high.
|
||||||
|
*/
|
||||||
|
if (dnskey->rr_i.nxt < dnskey->rr_i.rr_type + 14)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (/* 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 + 14 > rr->rr_i.nxt)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
bits = ldns_rr_dnskey_key_size_raw(dnskey->rr_i.rr_type + 10,
|
||||||
|
dnskey->rr_i.nxt - dnskey->rr_i.rr_type - 10,
|
||||||
|
dnskey->rr_i.rr_type[13]);
|
||||||
|
|
||||||
|
if (bits > 2048)
|
||||||
|
return gldns_read_uint16(rr->rr_i.rr_type + 12) > 2500;
|
||||||
|
else if (bits > 1024)
|
||||||
|
return gldns_read_uint16(rr->rr_i.rr_type + 12) > 500;
|
||||||
|
else
|
||||||
|
return gldns_read_uint16(rr->rr_i.rr_type + 12) > 150;
|
||||||
|
}
|
||||||
|
|
||||||
/* Returns whether dnskey signed rrset. If the rrset was a valid wildcard
|
/* Returns whether dnskey signed rrset. If the rrset was a valid wildcard
|
||||||
* expansion, nc_name will point to the next closer part of the name in rrset.
|
* expansion, nc_name will point to the next closer part of the name in rrset.
|
||||||
*/
|
*/
|
||||||
|
@ -1512,7 +1550,14 @@ static int dnskey_signed_rrset(
|
||||||
debug_sec_print_rr("key ", &dnskey->rr_i);
|
debug_sec_print_rr("key ", &dnskey->rr_i);
|
||||||
debug_sec_print_rrset("signed ", rrset);
|
debug_sec_print_rrset("signed ", rrset);
|
||||||
|
|
||||||
return 0x10000 | keytag; /* in case keytag == 0 */
|
/* Signal insecurity by too high nsec3 iteration
|
||||||
|
* count with NSEC3_ITERATION_COUNT_HIGH
|
||||||
|
* bit in return value.
|
||||||
|
*/
|
||||||
|
return ( nsec3_iteration_count_high(dnskey, rrset)
|
||||||
|
? NSEC3_ITERATION_COUNT_HIGH
|
||||||
|
: SIGNATURE_VERIFIED
|
||||||
|
) | keytag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1618,7 +1663,7 @@ static int ds_authenticates_keys(getdns_rrset *ds_set, getdns_rrset *dnskey_set)
|
||||||
|
|
||||||
debug_sec_print_rrset(
|
debug_sec_print_rrset(
|
||||||
"keyset authenticated: ", dnskey_set);
|
"keyset authenticated: ", dnskey_set);
|
||||||
return 0x10000 | keytag; /* In case keytag == 0 */
|
return SIGNATURE_VERIFIED | keytag;
|
||||||
}
|
}
|
||||||
debug_sec_print_rrset(
|
debug_sec_print_rrset(
|
||||||
"keyset failed authentication: ", dnskey_set);
|
"keyset failed authentication: ", dnskey_set);
|
||||||
|
@ -1768,28 +1813,35 @@ static int find_nsec_covering_name(
|
||||||
; i ; i = rrset_iter_next(i)) {
|
; i ; i = rrset_iter_next(i)) {
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
/* Get the bitmap rdata field */
|
/* Get the bitmap rdata field */
|
||||||
&& (nsec_rr = rrtype_iter_init(&nsec_spc, n))
|
&& (nsec_rr = rrtype_iter_init(&nsec_spc, n))
|
||||||
&& (bitmap = priv_getdns_rdf_iter_init_at(
|
&& (bitmap = priv_getdns_rdf_iter_init_at(
|
||||||
&bitmap_spc, &nsec_rr->rr_i, 5))
|
&bitmap_spc, &nsec_rr->rr_i, 5))
|
||||||
|
|
||||||
/* NSEC should cover, but not match name...
|
&& (keytag = a_key_signed_rrset(dnskey, n))
|
||||||
* Unless it is wildcard match, but then we have to check
|
&& ( keytag & NSEC3_ITERATION_COUNT_HIGH
|
||||||
* that rrset->rr_type is not enlisted, because otherwise
|
|
||||||
* it should have matched the wildcard.
|
|| ( nsec3_covers_name(n, name, opt_out)
|
||||||
*
|
/* NSEC should cover, but not match name...
|
||||||
* Also no CNAME... cause that should have matched too.
|
* Unless it is wildcard match, but then we have to
|
||||||
*/
|
* check that rrset->rr_type is not enlisted,
|
||||||
&& ( !nsec3_matches_name(n, name)
|
* because otherwise it should have matched the
|
||||||
|| ( name[0] == 1 && name[1] == (uint8_t)'*'
|
* wildcard.
|
||||||
&& !bitmap_has_type(bitmap, rrset->rr_type)
|
*
|
||||||
&& !bitmap_has_type(bitmap, GETDNS_RRTYPE_CNAME)
|
* 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);
|
debug_sec_print_rrset("NSEC3: ", n);
|
||||||
debug_sec_print_dname("covered: ", name);
|
debug_sec_print_dname("covered: ", name);
|
||||||
|
@ -1869,11 +1921,14 @@ 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 (my_opt_out)
|
if (my_opt_out || keytag & NSEC3_ITERATION_COUNT_HIGH)
|
||||||
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));
|
if (_dname_len(nc_name) > sizeof(wc_name) - 2)
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
(void) memcpy(wc_name + 2, nc_name, _dname_len(nc_name));
|
||||||
|
|
||||||
return find_nsec_covering_name(dnskey, rrset, wc_name, opt_out);
|
return find_nsec_covering_name(dnskey, rrset, wc_name, opt_out);
|
||||||
}
|
}
|
||||||
|
@ -1882,8 +1937,10 @@ static int nsec3_find_next_closer(
|
||||||
* Does a key from keyset dnskey prove the nonexistence of the (name, type)
|
* Does a key from keyset dnskey prove the nonexistence of the (name, type)
|
||||||
* tuple in rrset?
|
* tuple in rrset?
|
||||||
*
|
*
|
||||||
* On success returns the keytag (+0x10000) of the key that signed the proof.
|
* On success returns the keytag + SIGNATURE_VERIFIED (0x10000) of the key
|
||||||
* Or else returns 0.
|
* that signed the proof.
|
||||||
|
* Or in case there were NSEC3's with too high iteration count for the
|
||||||
|
* verifying key: it returns keytag + NSEC3_ITERATION_COUNT_HIGH (0x20000)
|
||||||
*/
|
*/
|
||||||
static int key_proves_nonexistance(
|
static int key_proves_nonexistance(
|
||||||
getdns_rrset *keyset, getdns_rrset *rrset, int *opt_out)
|
getdns_rrset *keyset, getdns_rrset *rrset, int *opt_out)
|
||||||
|
@ -1960,8 +2017,7 @@ static int key_proves_nonexistance(
|
||||||
*
|
*
|
||||||
* - Or a wildcard match without the type. The wildcard owner name
|
* - Or a wildcard match without the type. The wildcard owner name
|
||||||
* match has special handing in the find_nsec_covering_name function.
|
* match has special handing in the find_nsec_covering_name function.
|
||||||
* We still expect a NSEC covering the name though. For names with
|
* We still expect a NSEC covering the name though.
|
||||||
* label < '*' there will thus be two NSECs. (is this true?)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* The NSEC Name error case
|
/* The NSEC Name error case
|
||||||
|
@ -2021,7 +2077,12 @@ static int key_proves_nonexistance(
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_sec_print_dname("Closest Encloser: ", ce_name);
|
debug_sec_print_dname("Closest Encloser: ", ce_name);
|
||||||
memcpy(wc_name + 2, ce_name, _dname_len(ce_name));
|
|
||||||
|
if (_dname_len(ce_name) > sizeof(wc_name) - 2)
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
(void) memcpy(wc_name+2, ce_name, _dname_len(ce_name));
|
||||||
|
|
||||||
debug_sec_print_dname(" Wildcard: ", wc_name);
|
debug_sec_print_dname(" Wildcard: ", wc_name);
|
||||||
|
|
||||||
return find_nsec_covering_name(keyset, rrset, wc_name, NULL);
|
return find_nsec_covering_name(keyset, rrset, wc_name, NULL);
|
||||||
|
@ -2075,13 +2136,12 @@ static int key_proves_nonexistance(
|
||||||
|| !bitmap_has_type(bitmap, GETDNS_RRTYPE_NS)
|
|| !bitmap_has_type(bitmap, GETDNS_RRTYPE_NS)
|
||||||
|| bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA))
|
|| bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA))
|
||||||
|
|
||||||
/* The qname must match the NSEC3!
|
/* It must have a valid signature */
|
||||||
* (expensive tests done last)
|
&& (keytag = a_key_signed_rrset(keyset, ce))
|
||||||
*/
|
|
||||||
&& nsec3_matches_name(ce, rrset->name)
|
|
||||||
|
|
||||||
/* And it must have a valid signature */
|
/* The qname must match the NSEC3 */
|
||||||
&& (keytag = a_key_signed_rrset(keyset, ce))) {
|
&& ( keytag & NSEC3_ITERATION_COUNT_HIGH
|
||||||
|
|| nsec3_matches_name(ce, rrset->name))) {
|
||||||
|
|
||||||
debug_sec_print_rrset("NSEC3 No Data for: ", rrset);
|
debug_sec_print_rrset("NSEC3 No Data for: ", rrset);
|
||||||
return keytag;
|
return keytag;
|
||||||
|
@ -2092,10 +2152,9 @@ static int key_proves_nonexistance(
|
||||||
* There are a few NSEC NODATA cases where qname doesn't match
|
* There are a few NSEC NODATA cases where qname doesn't match
|
||||||
* NSEC->name:
|
* NSEC->name:
|
||||||
*
|
*
|
||||||
* - NSEC3 ownername match for qtype != NSEC3 (TODO?)
|
* - NSEC3 ownername match for qtype == NSEC3 (TODO?)
|
||||||
* - Wildcard NODATA (wildcard owner name match has special handing
|
* - Wildcard NODATA (wildcard owner name match has special handing
|
||||||
* find_nsec_covering_name())
|
* find_nsec_covering_name())
|
||||||
* - Opt-In DS NODATA (TODO, don't understand yet)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* The NSEC3 Name error case
|
/* The NSEC3 Name error case
|
||||||
|
@ -2124,22 +2183,26 @@ static int key_proves_nonexistance(
|
||||||
* here (instead of bogus) when DS is also missing.
|
* here (instead of bogus) when DS is also missing.
|
||||||
* Should we not have followed the delegation then
|
* Should we not have followed the delegation then
|
||||||
* too?
|
* too?
|
||||||
|
* The NSEC could come from a parent zone!
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|| bitmap_has_type(bitmap, GETDNS_RRTYPE_DNAME)
|
|| bitmap_has_type(bitmap, GETDNS_RRTYPE_DNAME)
|
||||||
|| ( bitmap_has_type(bitmap, GETDNS_RRTYPE_NS)
|
|| ( bitmap_has_type(bitmap, GETDNS_RRTYPE_NS)
|
||||||
&& !bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)
|
&& !bitmap_has_type(bitmap, GETDNS_RRTYPE_SOA)
|
||||||
)
|
)
|
||||||
|
|
||||||
|| !nsec3_matches_name(ce, ce_name)
|
|| !(keytag = a_key_signed_rrset(keyset, ce))
|
||||||
|| !a_key_signed_rrset(keyset, ce))
|
|| ( !(keytag & NSEC3_ITERATION_COUNT_HIGH)
|
||||||
|
&& !nsec3_matches_name(ce, ce_name)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
debug_sec_print_rrset("Closest Encloser: ", ce);
|
debug_sec_print_rrset("Closest Encloser: ", ce);
|
||||||
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(
|
if ( keytag & NSEC3_ITERATION_COUNT_HIGH
|
||||||
keyset, rrset, nc_name, opt_out)))
|
|| (keytag = nsec3_find_next_closer(
|
||||||
|
keyset, rrset, nc_name, opt_out)))
|
||||||
|
|
||||||
return keytag;
|
return keytag;
|
||||||
}
|
}
|
||||||
|
@ -2180,7 +2243,7 @@ static int chain_node_get_trusted_keys(
|
||||||
node->dnskey_signer = keytag;
|
node->dnskey_signer = keytag;
|
||||||
return GETDNS_DNSSEC_SECURE;
|
return GETDNS_DNSSEC_SECURE;
|
||||||
}
|
}
|
||||||
/* ta is ZSK */
|
/* ta is parent's ZSK */
|
||||||
if ((keytag = key_proves_nonexistance(ta, &node->ds, NULL))) {
|
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;
|
||||||
|
@ -2252,7 +2315,8 @@ static int chain_head_validate_with_ta(chain_head *head, getdns_rrset *ta)
|
||||||
} else if ((keytag = key_proves_nonexistance(
|
} else if ((keytag = key_proves_nonexistance(
|
||||||
keys, &head->rrset, &opt_out))) {
|
keys, &head->rrset, &opt_out))) {
|
||||||
head->signer = keytag;
|
head->signer = keytag;
|
||||||
return opt_out ? GETDNS_DNSSEC_INSECURE : GETDNS_DNSSEC_SECURE;
|
return opt_out || (keytag & NSEC3_ITERATION_COUNT_HIGH)
|
||||||
|
? GETDNS_DNSSEC_INSECURE : GETDNS_DNSSEC_SECURE;
|
||||||
}
|
}
|
||||||
return GETDNS_DNSSEC_BOGUS;
|
return GETDNS_DNSSEC_BOGUS;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue