From: Karel Slany Date: Wed, 5 Aug 2015 16:02:46 +0000 (+0200) Subject: layer/validate: NSEC authenticated denial of existence check X-Git-Tag: v1.0.0-beta1~53^2~117 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3c01628e8bc35336df968222bd2fcaa7dd013728;p=thirdparty%2Fknot-resolver.git layer/validate: NSEC authenticated denial of existence check --- diff --git a/lib/dnssec.c b/lib/dnssec.c index 23b7ac177..6b9822c51 100644 --- a/lib/dnssec.c +++ b/lib/dnssec.c @@ -94,7 +94,7 @@ static int validate_rrsig_rr(int *flags, const knot_rrset_t *covered, { int rrsig_labels = knot_rrsig_labels(&rrsigs->rrs, sig_pos); int dname_labels = knot_dname_labels(covered->owner, NULL); - if ((covered->owner[0] == 1) && (covered->owner[1] == '*')) { + if (knot_dname_is_wildcard(covered->owner)) { /* The asterisk does not count, RFC4034 3.1.3, paragraph 3. */ --dname_labels; } @@ -144,21 +144,6 @@ static int wildcard_radix_len_diff(const knot_dname_t *expanded, return knot_dname_labels(expanded, NULL) - knot_rrsig_labels(&rrsigs->rrs, sig_pos); } -/* RFC4035 5.4, bullet 2 */ -static int nsec_nomatch_validate(const knot_rrset_t *nsec, const knot_dname_t *name) -{ - const knot_dname_t *next = knot_nsec_next(&nsec->rrs); - - if ((knot_dname_cmp(nsec->owner, name) < 0) && - (knot_dname_cmp(name, next) < 0)) { - return kr_ok(); - } else { - return 1; - } - -#warning TODO: Is an additional request for NSEC name or wildcard necessary? -} - /** * Validates the non-existence of closer/exact match. * @param pkt Packet to be validated. @@ -186,7 +171,7 @@ static int closer_match_nonexistence_validate(const knot_pkt_t *pkt, knot_sectio continue; } if (rrset->type == KNOT_RRTYPE_NSEC) { - if (nsec_nomatch_validate(rrset, name) == 0) { + if (kr_nsec_nomatch_validate(rrset, name) == 0) { return kr_ok(); } } else { diff --git a/lib/dnssec.h b/lib/dnssec.h index a23a7560f..77e819ff6 100644 --- a/lib/dnssec.h +++ b/lib/dnssec.h @@ -16,6 +16,7 @@ #pragma once +#include #include /** diff --git a/lib/dnssec/nsec.c b/lib/dnssec/nsec.c new file mode 100644 index 000000000..cb976927d --- /dev/null +++ b/lib/dnssec/nsec.c @@ -0,0 +1,232 @@ +/* Copyright (C) 2015 CZ.NIC, z.s.p.o. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include + +#include +#include +#include +#include +#include + +#include "lib/defines.h" +#include "lib/dnssec/nsec.h" + +int kr_nsec_nomatch_validate(const knot_rrset_t *nsec, const knot_dname_t *name) +{ + const knot_dname_t *next = knot_nsec_next(&nsec->rrs); + + if ((knot_dname_cmp(nsec->owner, name) < 0) && + (knot_dname_cmp(name, next) < 0)) { + return kr_ok(); + } else { + return 1; + } + +#warning TODO: Is an additional request for NSEC name or wildcard necessary? +} + +#define FLG_NOEXIST_RRTYPE 0x01 /**< RR type has been proven not to exist. */ +#define FLG_NOEXIST_RRSET 0x02 /**< RRSet has been proven not to exist. */ +#define FLG_NOEXIST_WILDCARD 0x03 /**< No cowering wildcard exists. */ + +/** + * Checks whether the given type exists in the supplied NSEC bitmap. + * @param nsec NSEC RR. + * @param type Type to search for. + */ +bool nsec_bitmap_has_type(const knot_rrset_t *nsec, uint16_t type) +{ + if (!nsec) { + return false; + } + + uint8_t *bm; + uint16_t bm_size; + knot_nsec_bitmap(&nsec->rrs, &bm, &bm_size); + if (!bm) { + return false; + } + + uint8_t sought_win = (type >> 8 ) & 0xff; + uint8_t bitmap_idx = (type >> 3) & 0x1f; + uint8_t bitmap_bit_mask = 1 << (7 - (type & 0x07)); + + size_t bm_pos = 0; + while (bm_pos < bm_size) { + uint8_t win = bm[bm_pos++]; + uint8_t win_size = bm[bm_pos++]; + + if (win == sought_win) { + if (win_size >= bitmap_idx) { + return bm[bm_pos + bitmap_idx] & bitmap_bit_mask; + } + return false; + } + + bm_pos += win_size; + } + + return false; +} + +/** + * Returns the labels from the covering RRSIG RRs. + * @note The number must be the same in all covering RRSIGs. + * @param nsec NSEC RR. + * @param sec Packet section. + * @param Number of labels or (negative) error code. + */ +static int coverign_rrsig_labels(const knot_rrset_t *nsec, const knot_pktsection_t *sec) +{ + assert(nsec && sec); + + int ret = kr_error(ENOENT); + + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *rrset = knot_pkt_rr(sec, i); + if ((rrset->type != KNOT_RRTYPE_RRSIG) || + (!knot_dname_is_equal(rrset->owner, nsec->owner))) { + continue; + } + + for (uint16_t j = 0; j < rrset->rrs.rr_count; ++j) { + if (knot_rrsig_type_covered(&rrset->rrs, j) != KNOT_RRTYPE_NSEC) { + continue; + } + + if (ret < 0) { + ret = knot_rrsig_labels(&rrset->rrs, j); + } else { + if (ret != knot_rrsig_labels(&rrset->rrs, j)) { + return kr_error(EINVAL); + } + } + } + } + + return ret; +} + +/** + * Perform check of RR type existence denial according to RFC4035 5.4, bullet 1. + * @param flags Flags to be set according to check outcome. + * @param nsec NSEC RR. + * @param sec Packet section to work with. + * @param type Type to be checked. + */ +static int rr_type_existence_denial(int *flags, const knot_rrset_t *nsec, + const knot_pktsection_t *sec, uint16_t type) +{ + assert(flags && nsec && sec); + + if (nsec_bitmap_has_type(nsec, type)) { + return kr_ok(); + } + /* The type is not listed in the NSEC bitmap. */ + *flags |= FLG_NOEXIST_RRTYPE; + + int rrsig_labels = coverign_rrsig_labels(nsec, sec); + if (rrsig_labels < 0) { + return rrsig_labels; + } + int nsec_labels = knot_dname_labels(nsec->owner, NULL); + if (nsec_labels < 0) { + return nsec_labels; + } + + if (rrsig_labels == nsec_labels) { + *flags |= FLG_NOEXIST_WILDCARD; + } + + return kr_ok(); +} + +/** + * Perform check of RRSet existence denial according to RFC4035 5.4, bullet 2. + * @param flags Flags to be set according to check outcome. + * @param nsec NSEC RR. + * @param name Name to be checked. + * @param pool + * @return 0 or error code. + */ +static int rrset_existence_denial(int *flags, const knot_rrset_t *nsec, + const knot_dname_t *name, mm_ctx_t *pool) +{ + assert(flags && nsec && name); + + if (kr_nsec_nomatch_validate(nsec, name) == 0) { + *flags |= FLG_NOEXIST_RRSET; + return kr_ok(); + } + + if (!pool) { + return kr_error(EINVAL); + } + + knot_dname_t *name_copy = knot_dname_copy(name, pool); + if (!name_copy) { + return kr_error(ENOMEM); + } + knot_dname_t *ptr = name_copy; + while (ptr[0]) { + /* Remove leftmost label and replace it with '*.'. */ + ptr = (uint8_t *) knot_wire_next_label(ptr, NULL); + *(--ptr) = '*'; + *(--ptr) = 1; + + if (kr_nsec_nomatch_validate(nsec, ptr) == 0) { + *flags |= FLG_NOEXIST_WILDCARD; + break; + } + + /* Remove added leftmost asterisk. */ + ptr += 2; + } + + knot_dname_free(&name_copy, pool); + return kr_ok(); +} + +int kr_nsec_existence_denial(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *name, uint16_t type, mm_ctx_t *pool) +{ + const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); + if (!sec) { + return kr_error(EINVAL); + } + + int ret = kr_error(ENOENT); + int flags = 0; + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *rrset = knot_pkt_rr(sec, i); + if (rrset->type != KNOT_RRTYPE_NSEC) { + continue; + } + if (knot_dname_is_equal(rrset->owner, name)) { + rr_type_existence_denial(&flags, rrset, sec, type); + } else { + rrset_existence_denial(&flags, rrset, name, pool); + } + } + + if (((flags & FLG_NOEXIST_RRTYPE) || (flags & FLG_NOEXIST_RRSET)) && + (flags & FLG_NOEXIST_WILDCARD)) { + ret = kr_ok(); + } + + return ret; +} diff --git a/lib/dnssec/nsec.h b/lib/dnssec/nsec.h new file mode 100644 index 000000000..43d22d61f --- /dev/null +++ b/lib/dnssec/nsec.h @@ -0,0 +1,42 @@ +/* Copyright (C) 2015 CZ.NIC, z.s.p.o. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +/** + * Check the non-existence of an exact/closer match according to RFC4035 5.4, bullet 2. + * @note No RRSIGs are checked. + * @param nsec NSEC RRSet containing a single record. + * @param name Domain name checked against the NSEC record. + * @return 0 or error code. + */ +int kr_nsec_nomatch_validate(const knot_rrset_t *nsec, const knot_dname_t *name); + +/** + * Authenticated denial of existence according to RFC4035 5.4. + * @note No RRSIGs are validated. + * @param pkt Packet structure to be processed. + * @param section_id Packet section to be processed. + * @param name Queried domain name. + * @param type Queried type. + * @return 0 or error code. + */ +int kr_nsec_existence_denial(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *name, uint16_t type, mm_ctx_t *pool); diff --git a/lib/dnssec/ta.h b/lib/dnssec/ta.h index c00cdd494..cf5250594 100644 --- a/lib/dnssec/ta.h +++ b/lib/dnssec/ta.h @@ -16,6 +16,7 @@ #pragma once +#include #include //#define ROOT_TA ". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5" diff --git a/lib/layer/validate.c b/lib/layer/validate.c index e980cba3f..b49b558f2 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -21,6 +21,7 @@ #include #include +#include "lib/dnssec/nsec.h" #include "lib/dnssec/ta.h" #include "lib/dnssec.h" #include "lib/layer.h" @@ -185,10 +186,11 @@ static int validate_records(struct kr_query *qry, knot_pkt_t *answer, mm_ctx_t * return ret; } -static int validate_proof(struct kr_query *qry, knot_pkt_t *answer) +static int validate_proof(struct kr_query *qry, knot_pkt_t *answer, mm_ctx_t *pool) { #warning TODO: validate NSECx proof, RRSIGs will be checked later if it matches - return kr_ok(); + int ret = kr_nsec_existence_denial(answer, KNOT_AUTHORITY, qry->sname, qry->stype, pool); + return ret; } static int validate_keyset(struct kr_query *qry, knot_pkt_t *answer) @@ -342,7 +344,7 @@ static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) /* Validate non-existence proof if not positive answer. */ if (knot_wire_get_rcode(pkt->wire) == KNOT_RCODE_NXDOMAIN) { - ret = validate_proof(qry, pkt); + ret = validate_proof(qry, pkt, &req->pool); if (ret != 0) { DEBUG_MSG("<= bad NXDOMAIN proof\n"); qry->flags |= QUERY_DNSSEC_BOGUS; @@ -409,6 +411,7 @@ static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) mm_free(qry->parent->zone_cut.pool, qry->parent->zone_cut.name); qry->parent->zone_cut.name = knot_dname_copy(qry->zone_cut.trust_anchor->owner, qry->parent->zone_cut.pool); } + if ((qtype == KNOT_RRTYPE_DNSKEY) && (qry->parent != NULL) && (qry->parent->zone_cut.key == NULL)) { DEBUG_MSG("updating keys in zone cut\n"); qry->parent->zone_cut.key = knot_rrset_copy(qry->zone_cut.key, qry->parent->zone_cut.pool); diff --git a/lib/lib.mk b/lib/lib.mk index d016aeecc..0407fe6b0 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -11,6 +11,7 @@ libkres_SOURCES := \ lib/layer/validate.c \ lib/layer/rrcache.c \ lib/layer/pktcache.c \ + lib/dnssec/nsec.c \ lib/dnssec/signature.c \ lib/dnssec/ta.c \ lib/dnssec.c \ @@ -28,6 +29,8 @@ libkres_HEADERS := \ lib/generic/set.h \ lib/layer.h \ lib/kayer/rrset/ds.h \ + lib/dnssec/nsec.h \ + lib/dnssec/rrtype/ds.h \ lib/dnssec/signature.h \ lib/dnssec/ta.h \ lib/dnssec.h \