diff --git a/lib/dnssec/nsec3.c b/lib/dnssec/nsec3.c index b1cc11e26f382b257961e06c3a654d74d2ef52fb..fdc4d5b8ff9e21772b63274e9e5096953cfb9f22 100644 --- a/lib/dnssec/nsec3.c +++ b/lib/dnssec/nsec3.c @@ -37,7 +37,6 @@ #define FLG_NAME_MATCHED (1 << 3) #define FLG_TYPE_BIT_MISSING (1 << 4) #define FLG_CNAME_BIT_MISSING (1 << 5) -#define FLG_OPT_OUT_SET (1 << 6) /** * Obtains NSEC3 parameters from RR. @@ -253,9 +252,6 @@ static int covers_name(int *flags, const knot_rrset_t *nsec3, const knot_dname_t ret = kr_error(EINVAL); } - if (nsec3_flags & OPT_OUT_BIT) { - *flags |= FLG_OPT_OUT_SET; - } ret = kr_ok(); fail: @@ -268,6 +264,28 @@ fail: return ret; } +/** + * Checks whether NSEC3 RR has the opt-out bit set. + * @param flags Flags to be set according to check outcome. + * @param nsec3 NSEC3 RR. + * @param name Name to be checked. + * @return 0 or error code. + */ +static bool has_optout(const knot_rrset_t *nsec3) +{ + if (!nsec3) { + return false; + } + + uint8_t nsec3_flags = knot_nsec3_flags(&nsec3->rrs, 0); + if (nsec3_flags & ~OPT_OUT_BIT) { + /* RFC5155 3.1.2 */ + return false; + } + + return nsec3_flags & OPT_OUT_BIT; +} + /** * Checks whether NSEC3 RR matches the supplied name. * @param flags Flags to be set according to check outcome. @@ -319,25 +337,49 @@ fail: } #undef MAX_HASH_BYTES +/** + * Prepends an asterisk label to given name. + * + * @param tgt Target buffer to write domain name into. + * @param name Name to be added to the asterisk. + * @return 0 or error code + */ +int prepend_asterisk(uint8_t tgt[KNOT_DNAME_MAXLEN], const knot_dname_t *name) +{ + tgt[0] = 1; + tgt[1] = '*'; + tgt[2] = 0; + int name_len = knot_dname_size(name); + if (name_len < 0) { + return name_len; + } + memcpy(tgt + 2, name, name_len); + return 0; +} + /** * Closest encloser proof (RFC5155 7.2.1). * @note No RRSIGs are validated. - * @param pkt Packet structure to be processed. - * @param section_id Packet section to be processed. - * @param sname Name to be checked. - * @param encloser Returned matching encloser, if found. - * @param opt_out_set Returned value of the opt-out bit of the NSEC3 RR covering the next closer name. - * @return 0 or error code. + * @param pkt Packet structure to be processed. + * @param section_id Packet section to be processed. + * @param sname Name to be checked. + * @param encloser_name Returned matching encloser name, if found. + * @param matching_ecloser_nsec3 Pointer to matching encloser NSEC RRSet. + * @param covering_next_nsec3 Pointer to covering next closer NSEC3 RRSet. + * @return 0 or error code. */ static int closest_encloser_proof(const knot_pkt_t *pkt, knot_section_t section_id, - const knot_dname_t *sname, const knot_dname_t **encloser, - int *opt_out_set) + const knot_dname_t *sname, const knot_dname_t **encloser_name, + const knot_rrset_t **matching_ecloser_nsec3, const knot_rrset_t **covering_next_nsec3) { const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); if (!sec || !sname) { return kr_error(EINVAL); } + const knot_rrset_t *matching = NULL; + const knot_rrset_t *covering = NULL; + int ret = kr_error(ENOENT); int flags; const knot_dname_t *next_closer = NULL; @@ -355,6 +397,7 @@ static int closest_encloser_proof(const knot_pkt_t *pkt, knot_section_t section_ if (!(flags & FLG_CLOSEST_PROVABLE_ENCLOSER)) { continue; } + matching = rrset; --skipped; next_closer = sname; for (unsigned j = 0; j < skipped; ++j) { @@ -370,21 +413,26 @@ static int closest_encloser_proof(const knot_pkt_t *pkt, knot_section_t section_ return ret; } if (flags & FLG_NAME_COVERED) { + covering = rrset; break; } } if (flags & FLG_NAME_COVERED) { break; } + flags = 0; // } if ((flags & FLG_CLOSEST_PROVABLE_ENCLOSER) && (flags & FLG_NAME_COVERED)) { - if (encloser) { - *encloser = knot_wire_next_label(next_closer, NULL); + if (encloser_name) { + *encloser_name = knot_wire_next_label(next_closer, NULL); } - if (opt_out_set) { - *opt_out_set = (flags & FLG_OPT_OUT_SET) ? 1 : 0; + if (matching_ecloser_nsec3) { + *matching_ecloser_nsec3 = matching; + } + if (covering_next_nsec3) { + *covering_next_nsec3 = covering; } return kr_ok(); } @@ -438,7 +486,7 @@ int kr_nsec3_name_error_response_check(const knot_pkt_t *pkt, knot_section_t sec const knot_dname_t *sname) { const knot_dname_t *encloser = NULL; - int ret = closest_encloser_proof(pkt, section_id, sname, &encloser, NULL); + int ret = closest_encloser_proof(pkt, section_id, sname, &encloser, NULL, NULL); if (ret != 0) { return ret; } @@ -544,13 +592,13 @@ static int no_data_response_ds(const knot_pkt_t *pkt, knot_section_t section_id, return kr_error(EINVAL); } - int opt_out_set = 0; - int ret = closest_encloser_proof(pkt, section_id, sname, NULL, &opt_out_set); + const knot_rrset_t *covering_nsec3 = NULL; + int ret = closest_encloser_proof(pkt, section_id, sname, NULL, NULL, &covering_nsec3); if (ret != 0) { return ret; } - if (opt_out_set) { + if (has_optout(covering_nsec3)) { return kr_ok(); } @@ -586,13 +634,10 @@ static int matches_closest_encloser_wildcard(const knot_pkt_t *pkt, knot_section } uint8_t wildcard[KNOT_DNAME_MAXLEN]; - wildcard[0] = 1; - wildcard[1] = '*'; - int encloser_len = knot_dname_size(encloser); - if (encloser_len < 0) { - return encloser_len; + int ret = prepend_asterisk(wildcard, encloser); + if (ret != 0) { + return ret; } - memcpy(wildcard + 2, encloser, encloser_len); int flags; for (unsigned i = 0; i < sec->count; ++i) { @@ -622,7 +667,7 @@ int kr_nsec3_wildcard_no_data_response_check(const knot_pkt_t *pkt, knot_section const knot_dname_t *sname, uint16_t stype) { const knot_dname_t *encloser = NULL; - int ret = closest_encloser_proof(pkt, section_id, sname, &encloser, NULL); + int ret = closest_encloser_proof(pkt, section_id, sname, &encloser, NULL, NULL); if (ret != 0) { return ret; } @@ -659,3 +704,32 @@ int kr_nsec3_wildcard_answer_response_check(const knot_pkt_t *pkt, knot_section_ return kr_error(ENOENT); } + +int kr_nsec3_no_data(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *sname, uint16_t stype) +{ + /* DS record may be also matched by an existing NSEC3 RR. */ + int ret = no_data_response_no_ds(pkt, section_id, sname, stype); + if (ret == 0) { + /* Satisfies RFC5155 8.5 and 8.6, first paragraph. */ + return ret; + } + + /* Find closest provable encloser. */ + const knot_dname_t *encloser_name = NULL; + const knot_rrset_t *covering_next_nsec3 = NULL; + ret = closest_encloser_proof(pkt, section_id, sname, &encloser_name, + NULL, &covering_next_nsec3); + if (ret != 0) { + return ret; + } + + assert(encloser_name && covering_next_nsec3); + if ((stype == KNOT_RRTYPE_DS) && has_optout(covering_next_nsec3)) { + /* Satisfies RFC5155 8.6, second paragraph. */ + return 0; + } + + return matches_closest_encloser_wildcard(pkt, section_id, + encloser_name, stype); +} diff --git a/lib/dnssec/nsec3.h b/lib/dnssec/nsec3.h index 1995e2be5697065439b7cb4860f026f65f8ea7c6..b4a50405c5121881a69f2052eb158e95b4150747 100644 --- a/lib/dnssec/nsec3.h +++ b/lib/dnssec/nsec3.h @@ -63,3 +63,15 @@ int kr_nsec3_wildcard_no_data_response_check(const knot_pkt_t *pkt, knot_section */ int kr_nsec3_wildcard_answer_response_check(const knot_pkt_t *pkt, knot_section_t section_id, const knot_dname_t *sname, int trim_to_next); + +/** + * Authenticated denial of existence according to RFC5155 8.5, 8.6 and 8.7. + * @note No RRSIGs are validated. + * @param pkt Packet structure to be processed. + * @param section_id Packet section to be processed. + * @param sname Queried domain name. + * @param stype Queried type. + * @return 0 or error code. + */ +int kr_nsec3_no_data(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *sname, uint16_t stype); diff --git a/lib/layer/validate.c b/lib/layer/validate.c index e7629c9f00ea1dff8f401a8a49107b61021efde7..fa9d10fbdde7cf4dcbffdc2ac0fa2ab040a1a9c1 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -400,10 +400,7 @@ static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) ret = kr_nsec_empty_nonterminal_response_check(pkt, KNOT_AUTHORITY, knot_pkt_qname(pkt)); } } else { - ret = kr_nsec3_no_data_response_check(pkt, KNOT_AUTHORITY, knot_pkt_qname(pkt), knot_pkt_qtype(pkt)); - if (ret != 0) { - ret = kr_nsec3_wildcard_no_data_response_check(pkt, KNOT_AUTHORITY, knot_pkt_qname(pkt), knot_pkt_qtype(pkt)); - } + ret = kr_nsec3_no_data(pkt, KNOT_AUTHORITY, knot_pkt_qname(pkt), knot_pkt_qtype(pkt)); } if (ret != 0) { DEBUG_MSG(qry, "<= bad no data response proof\n");