diff --git a/src/dnssec.c b/src/dnssec.c index a63d613d..2fe9eb30 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -139,7 +139,7 @@ inline static void debug_sec_print_dname(const char *msg, uint8_t *label) { char str[1024]; - if (gldns_wire2str_dname_buf(label, 256, str, sizeof(str))) + if (label && gldns_wire2str_dname_buf(label, 256, str, sizeof(str))) DEBUG_SEC("%s%s\n", msg, str); else DEBUG_SEC("%s\n", msg); @@ -433,13 +433,20 @@ struct chain_node { chain_head *chains; }; -inline static int _dname_len(uint8_t *name) +inline static size_t _dname_len(uint8_t *name) { uint8_t *p; for (p = name; *p; p += *p + 1); return p - name + 1; } +inline static size_t _dname_label_count(uint8_t *name) +{ + size_t c; + for (c = 0; *name; name += *name + 1, c++); + return c; +} + #ifdef STUB_NATIVE_DNSSEC static int key_matches_signer(getdns_rrset *dnskey, getdns_rrset *rrset) { @@ -503,22 +510,59 @@ static ldns_rr_list *rrset2ldns_rr_list(getdns_rrset *rrset) return rr_list; } -static int _getdns_verify_rrsig(getdns_rrset *rrset, rrsig_iter *rrsig, rrtype_iter *key) + +/* Verifies the signature rrsig for rrset rrset with key key. + * When the rrset was a wildcard expansion (rrsig labels < labels owner name), + * nc_name will be set to the next closer (within rrset->name). + */ +static int _getdns_verify_rrsig( + getdns_rrset *rrset, rrsig_iter *rrsig, rrtype_iter *key, uint8_t **nc_name) { ldns_rr_list *rrset_l = rrset2ldns_rr_list(rrset); ldns_rr *rrsig_l = rr2ldns_rr(&rrsig->rr_i); ldns_rr *key_l = rr2ldns_rr(&key->rr_i); int r; + size_t to_skip; + + /* nc_name should already have been initialized by the parent! */ + assert(nc_name); + assert(!*nc_name); + + r = rrset_l && rrsig_l && key_l && + ldns_verify_rrsig(rrset_l, rrsig_l, key_l) == LDNS_STATUS_OK; - /* 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); ldns_rr_free(key_l); - return r; + + if (!r) + return 0; + + /* Verification has already been done, so the labels rdata field is + * definitely readable + */ + assert(rrsig->rr_i.rr_type + 14 <= rrsig->rr_i.nxt); + + /* If the number of labels in the owner name mathes the "labels" rdata + * field, then this was not a wildcard expansion, and everything is + * good. + */ + if ((size_t)rrsig->rr_i.rr_type[13] == _dname_label_count(rrset->name)) + return 1; + + /* This is a valid wildcard expansion. Calculate and return the + * "Next closer" name, because we need another NSEC to cover it + * for wildcards to hold...xi + * (except for rrsigs for NSECs, but those are dealt with later) + */ + to_skip = _dname_label_count(rrset->name) + - (size_t)rrsig->rr_i.rr_type[13] - 1; + + for ( *nc_name = rrset->name + ; to_skip + ; *nc_name += **nc_name + 1, to_skip--); + + return 1; } static uint8_t *_getdns_nsec3_hash_label(uint8_t *label, size_t label_len, @@ -542,12 +586,19 @@ static uint8_t *_getdns_nsec3_hash_label(uint8_t *label, size_t label_len, return label; } -static int dnskey_signed_rrset(rrtype_iter *dnskey, getdns_rrset *rrset) +static int dnskey_signed_rrset( + rrtype_iter *dnskey, getdns_rrset *rrset, uint8_t **nc_name) { 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); uint16_t keytag; assert(dnskey->rrset->rr_type == GETDNS_RRTYPE_DNSKEY); + assert(nc_name); + + *nc_name = NULL; keytag = gldns_calc_keytag_raw(dnskey->rr_i.rr_type + 10, dnskey->rr_i.nxt - dnskey->rr_i.rr_type - 10); @@ -556,18 +607,21 @@ static int dnskey_signed_rrset(rrtype_iter *dnskey, getdns_rrset *rrset) ; rrsig ; rrsig = rrsig_iter_next(rrsig) ) { if (/* Space for keytag & signer in rrsig rdata? */ - rrsig->rr_i.nxt >= rrsig->rr_i.rr_type + 29 && + rrsig->rr_i.nxt >= rrsig->rr_i.rr_type + 28 /* Does the keytag match? */ - gldns_read_uint16(rrsig->rr_i.rr_type + 26) - == keytag && + && gldns_read_uint16(rrsig->rr_i.rr_type + 26) == keytag /* Does the signer name match? */ - priv_getdns_dname_equal(dnskey->rrset->name, - rrsig->rr_i.rr_type + 28) && + && (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)) + + && priv_getdns_dname_equal(dnskey->rrset->name, signer) /* Does the signature verify? */ - _getdns_verify_rrsig(rrset, rrsig, dnskey)) { + && _getdns_verify_rrsig(rrset, rrsig, dnskey, nc_name)) { debug_sec_print_rr("key ", &dnskey->rr_i); debug_sec_print_rrset("signed ", rrset); @@ -578,16 +632,37 @@ static int dnskey_signed_rrset(rrtype_iter *dnskey, getdns_rrset *rrset) return 0; } +static int find_nsec_covering_name( + getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *name); + static int a_key_signed_rrset(getdns_rrset *keyset, getdns_rrset *rrset) { rrtype_iter dnskey_spc, *dnskey; + uint8_t *nc_name; assert(keyset->rr_type == GETDNS_RRTYPE_DNSKEY); for ( dnskey = rrtype_iter_init(&dnskey_spc, keyset) ; dnskey ; dnskey = rrtype_iter_next(dnskey) ) { - if (dnskey_signed_rrset(dnskey, rrset)) + if (!dnskey_signed_rrset(dnskey, rrset, &nc_name)) + continue; + + if (!nc_name) /* Not a wildcard, then success! */ + return 1; + + /* Wildcard RRSIG for a NSEC on the wildcard. + * There is no more specific! + */ + if (rrset->rr_type == GETDNS_RRTYPE_NSEC && + rrset->name[0] == 1 && rrset->name[1] == '*') + return 1; + + debug_sec_print_rrset("wildcard expanded to: ", rrset); + debug_sec_print_dname("Find NSEC covering the more sepecific: " + , nc_name); + + if (find_nsec_covering_name(keyset, rrset, nc_name)) return 1; } return 0; @@ -598,6 +673,7 @@ static int ds_authenticates_keys(getdns_rrset *ds_set, getdns_rrset *dnskey_set) rrtype_iter dnskey_spc, *dnskey; rrtype_iter ds_spc, *ds; uint16_t keytag; + uint8_t *nc_name; ldns_rr *dnskey_l; ldns_rr *ds_l = NULL; @@ -619,35 +695,36 @@ static int ds_authenticates_keys(getdns_rrset *ds_set, getdns_rrset *dnskey_set) ; ds ; ds = rrtype_iter_next(ds)) { if (/* Space for keytag & signer in rrsig rdata? */ - ds->rr_i.nxt >= ds->rr_i.rr_type + 12 && + ds->rr_i.nxt < ds->rr_i.rr_type + 12 /* Does the keytag match? */ - gldns_read_uint16(ds->rr_i.rr_type + 10) - == keytag) { + || gldns_read_uint16(ds->rr_i.rr_type+10)!=keytag) + continue; - if (!dnskey_l) - dnskey_l = rr2ldns_rr(&dnskey->rr_i); - if (dnskey_l && (ds_l = rr2ldns_rr(&ds->rr_i)) && - ldns_rr_compare_ds(ds_l, dnskey_l)) { + if (!dnskey_l) + if (!(dnskey_l = rr2ldns_rr(&dnskey->rr_i))) + continue; - debug_sec_print_rr("this DS: ", &ds->rr_i); - debug_sec_print_rr("matched DNSKEY: ", &dnskey->rr_i); + if (!(ds_l = rr2ldns_rr(&ds->rr_i))) + continue; - ldns_rr_free(dnskey_l); - ldns_rr_free(ds_l); - - if (dnskey_signed_rrset(dnskey, dnskey_set)) { - debug_sec_print_rrset("which signed: " - , dnskey_set); - return 1; - } else { - debug_sec_print_rrset("which did not sign: " - , dnskey_set); - } - } - } - if (ds_l) + if (!ldns_rr_compare_ds(ds_l, dnskey_l)) { ldns_rr_free(ds_l); + ds_l = NULL; + continue; + } + ldns_rr_free(dnskey_l); + + if (dnskey_signed_rrset(dnskey, dnskey_set, &nc_name) + && !nc_name /* No DNSKEY's on wildcards! */) { + + debug_sec_print_rrset( + "keyset authenticated: ", dnskey_set); + return 1; + } + DEBUG_SEC("nc_name: %p\n", nc_name); + debug_sec_print_dname("nc_name: ", nc_name); + return 0; } ldns_rr_free(dnskey_l); } @@ -701,6 +778,30 @@ static uint8_t **reverse_labels(uint8_t *dname, uint8_t **labels) return labels + 1; } +static uint8_t *dname_shared_parent(uint8_t *left, uint8_t *right) +{ + uint8_t *llabels[128], *rlabels[128], **last_llabel, **last_rlabel, + **llabel, **rlabel, *l, *r, sz; + + 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 + || (sz = **llabel) != **rlabel) + return llabel[-1]; + + for (l = *llabel+1, r = *rlabel+1; sz; l++, r++, sz-- ) + if (*l != *r && tolower((unsigned char)*l) != + tolower((unsigned char)*r)) + return llabel[-1]; + } + return llabel[-1]; +} + static int dname_compare(uint8_t *left, uint8_t *right) { uint8_t *llabels[128], *rlabels[128], **last_llabel, **last_rlabel, @@ -733,7 +834,8 @@ static int dname_compare(uint8_t *left, uint8_t *right) return rlabel == last_rlabel ? 0 : -1; } -static int nsec_covers_name(getdns_rrset *nsec, uint8_t *name) +static int nsec_covers_name( + getdns_rrset *nsec, uint8_t *name, uint8_t **ce_name) { uint8_t owner_spc[256], *owner; size_t owner_len = sizeof(owner_spc); @@ -742,6 +844,7 @@ static int nsec_covers_name(getdns_rrset *nsec, uint8_t *name) rrtype_iter rr_spc, *rr; priv_getdns_rdf_iter rdf_spc, *rdf; int nsec_cmp; + uint8_t *common1, *common2; if ( !(rr = rrtype_iter_init(&rr_spc, nsec)) || !(rdf = priv_getdns_rdf_iter_init(&rdf_spc, &rr->rr_i)) @@ -755,6 +858,14 @@ static int nsec_covers_name(getdns_rrset *nsec, uint8_t *name) debug_sec_print_dname("name : ", name); debug_sec_print_dname("nsec next : ", next); + if (ce_name) { + common1 = dname_shared_parent(name, owner); + common2 = dname_shared_parent(name, next); + *ce_name = _dname_label_count(common1) + > _dname_label_count(common2) ? common1 : common2; + debug_sec_print_dname("nsec closest encloser: ", *ce_name); + } + nsec_cmp = dname_compare(owner, next); if (nsec_cmp < 0) { DEBUG_SEC("nsec owner < next\n"); @@ -770,41 +881,6 @@ static int nsec_covers_name(getdns_rrset *nsec, uint8_t *name) } } -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) { @@ -878,7 +954,8 @@ static int nsec3_covers_name(getdns_rrset *nsec3, uint8_t *name) || *nsec3->name > sizeof(owner) - 2 || !memcpy(owner, nsec3->name, *nsec3->name + 1)) { - DEBUG_SEC("ERROR!!!!\n"); + DEBUG_SEC("Error getting NSEC3 owner & next labels\n"); + return 0; } owner[owner[0]+1] = 0; next[(next[0] = (uint8_t)nsz)+1] = 0; @@ -903,7 +980,7 @@ static int nsec3_covers_name(getdns_rrset *nsec3, uint8_t *name) } } -static int find_nsec3_covering_name( +static int find_nsec_covering_name( getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *name) { rrset_iter i_spc, *i; @@ -919,36 +996,47 @@ static int find_nsec3_covering_name( debug_sec_print_rrset("NSEC3: ", n); debug_sec_print_dname("covered: ", name); + return 1; + } + if ((n = rrset_iter_value(i))->rr_type == GETDNS_RRTYPE_NSEC + && nsec_covers_name(n, name, NULL) + && a_key_signed_rrset(dnskey, n)) { + + debug_sec_print_rrset("NSEC: ", 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) +static int nsec3_find_next_closer( + getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *nc_name) { uint8_t wc_name[256] = { 1, (uint8_t)'*' }; - if (!find_nsec3_covering_name(dnskey, rrset, nc_name)) + if (!find_nsec_covering_name(dnskey, rrset, nc_name)) return 0; - if (noerror) - return 1; /* TODO: Check for opt-out bit? */ + /* Wild card not needed on a "covering" * NODATA response, + * because of opt-out? + */ + if (GLDNS_RCODE_WIRE(rrset->pkt) == GETDNS_RCODE_NOERROR) + return 1; 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); + return find_nsec_covering_name(dnskey, rrset, wc_name); } static int key_proves_nonexistance(getdns_rrset *dnskey, getdns_rrset *rrset) { - getdns_rrset nsec_rrset, *cover, *wildcard, *ce; + getdns_rrset nsec_rrset, *cover, *ce; rrset_iter i_spc, *i; - rrset_iter j_spc, *j; uint8_t *ce_name, *nc_name; + uint8_t wc_name[256] = { 1, (uint8_t)'*' }; assert(dnskey->rr_type == GETDNS_RRTYPE_DNSKEY); @@ -966,33 +1054,23 @@ static int key_proves_nonexistance(getdns_rrset *dnskey, getdns_rrset *rrset) return 1; } - /* The NSEC NXDOMAIN case - * ====================== + /* The NSEC Name error 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)) { if ((cover=rrset_iter_value(i))->rr_type != GETDNS_RRTYPE_NSEC - || !nsec_covers_name(cover, rrset->name) + || !nsec_covers_name(cover, rrset->name, &ce_name) || !a_key_signed_rrset(dnskey, cover)) continue; - debug_sec_print_rrset(" NSEC: ", cover); - debug_sec_print_dname("covers name: ", rrset->name); + debug_sec_print_dname("Closest Encloser: ", ce_name); + memcpy(wc_name + 2, ce_name, _dname_len(ce_name)); + debug_sec_print_dname(" Wildcard: ", wc_name); - for ( j = rrset_iter_init(&j_spc, rrset->pkt, rrset->pkt_len) - ; j ; j = rrset_iter_next(i)) { - - 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; - } + return find_nsec_covering_name(dnskey, rrset, wc_name); } /* The NSEC3 NODATA case @@ -1013,8 +1091,8 @@ static int key_proves_nonexistance(getdns_rrset *dnskey, getdns_rrset *rrset) return 1; } } - /* The NSEC3 NXDOMAIN case - * ===================== + /* The NSEC3 Name error case + * ========================+ * First find the closest encloser. */ for ( nc_name = rrset->name, ce_name = rrset->name + *rrset->name + 1 @@ -1023,29 +1101,18 @@ static int key_proves_nonexistance(getdns_rrset *dnskey, getdns_rrset *rrset) 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)) { + if ( (ce = rrset_iter_value(i))->rr_type + != GETDNS_RRTYPE_NSEC3 + || !nsec3_matches_name(ce, ce_name) + || !a_key_signed_rrset(dnskey, ce)) + continue; - debug_sec_print_rrset( - "Closest Encloser: ", ce); - debug_sec_print_dname( - "Closest Encloser: ", ce_name); - debug_sec_print_dname( - " Next closer: ", nc_name); + 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; - } + if (nsec3_find_next_closer(dnskey, rrset, nc_name)) + return 1; } } return 0; diff --git a/src/util-internal.c b/src/util-internal.c index 84c09646..0273aea1 100644 --- a/src/util-internal.c +++ b/src/util-internal.c @@ -432,7 +432,8 @@ priv_getdns_dname_equal(const uint8_t *s1, const uint8_t *s2) else if (!*s1) return 1; for (i = *s1++, s2++; i > 0; i--, s1++, s2++) - if ((*s1 & 0xDF) != (*s2 & 0xDF)) + if (*s1 != *s2 && tolower((unsigned char)*s1) + != tolower((unsigned char)*s2)) return 0; } }