diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c index 1eea00aace1031fa2c7cfe847849e139c44e46b6..79d2a405f3d53d117901370f03688d39154a5ca1 100644 --- a/lib/layer/iterate.c +++ b/lib/layer/iterate.c @@ -18,6 +18,7 @@ #include <libknot/descriptor.h> #include <libknot/rrtype/rdname.h> +#include <libknot/rrtype/rrsig.h> #include "lib/layer/iterate.h" #include "lib/resolve.h" @@ -256,6 +257,24 @@ static int update_cut(knot_pkt_t *pkt, const knot_rrset_t *rr, struct kr_request return state; } +static const knot_dname_t *signature_authority(knot_pkt_t *pkt) +{ + /* Can't find signer for RRSIGs, bail out. */ + if (knot_pkt_qtype(pkt) == KNOT_RRTYPE_RRSIG) { + return NULL; + } + for (knot_section_t i = KNOT_ANSWER; i <= KNOT_AUTHORITY; ++i) { + const knot_pktsection_t *sec = knot_pkt_section(pkt, i); + for (unsigned k = 0; k < sec->count; ++k) { + const knot_rrset_t *rr = knot_pkt_rr(sec, k); + if (rr->type == KNOT_RRTYPE_RRSIG) { + return knot_rrsig_signer_name(&rr->rrs, 0); + } + } + } + return NULL; +} + static int process_authority(knot_pkt_t *pkt, struct kr_request *req) { int result = KNOT_STATE_CONSUME; @@ -288,20 +307,41 @@ static int process_authority(knot_pkt_t *pkt, struct kr_request *req) case KNOT_STATE_FAIL: return state; break; default: /* continue */ break; } - } else if (rr->type == KNOT_RRTYPE_SOA) { + } else if (rr->type == KNOT_RRTYPE_SOA && knot_dname_is_sub(rr->owner, qry->zone_cut.name)) { /* SOA below cut in authority indicates different authority, but same NS set. */ - if (knot_dname_is_sub(rr->owner, qry->zone_cut.name)) { - qry->zone_cut.name = knot_dname_copy(rr->owner, &req->pool); - if (qry->flags & QUERY_DNSSEC_WANT) { /* Treat as a referral */ - result = KNOT_STATE_DONE; - break; - } - } + qry->zone_cut.name = knot_dname_copy(rr->owner, &req->pool); } } + /* Track difference between current TA and signer name. + * This indicates that the NS is auth for both parent-child, and we must update DS/DNSKEY to validate it. + * @todo: This has to be checked here before we put the data into packet, there is no "DEFER" or "PAUSE" action yet. + */ + const bool track_pc_change = (!(qry->flags & QUERY_CACHED) && (qry->flags & QUERY_DNSSEC_WANT)); + const knot_dname_t *ta_name = qry->zone_cut.trust_anchor ? qry->zone_cut.trust_anchor->owner : NULL; + const knot_dname_t *signer = signature_authority(pkt); + if (track_pc_change && ta_name && signer && !knot_dname_is_equal(ta_name, signer)) { + DEBUG_MSG(">< cut changed, needs revalidation\n"); + if (knot_dname_is_sub(signer, qry->zone_cut.name)) { + qry->zone_cut.name = knot_dname_copy(signer, &req->pool); + } else if (!knot_dname_is_equal(signer, qry->zone_cut.name)) { + /* Key signer is above the current cut, so we can't validate it. This happens when + a server is authoritative for both grandparent, parent and child zone. + Ascend to parent cut, and refetch authority for signer. */ + if (qry->zone_cut.parent) { + memcpy(&qry->zone_cut, qry->zone_cut.parent, sizeof(qry->zone_cut)); + } else { + qry->flags |= QUERY_AWAIT_CUT; + } + qry->zone_cut.name = knot_dname_copy(signer, &req->pool); + } /* else zone cut matches, but DS/DNSKEY doesn't => refetch. */ + knot_wire_set_tc(pkt->wire); + result = KNOT_STATE_NOOP; + } + /* CONSUME => Unhelpful referral. - * DONE => Zone cut updated. */ + * DONE => Zone cut updated. + * NOOP => Ignore this answer. */ return result; } @@ -545,6 +585,9 @@ static int resolve(knot_layer_t *ctx, knot_pkt_t *pkt) case KNOT_STATE_DONE: /* Referral */ DEBUG_MSG("<= referral response, follow\n"); break; + case KNOT_STATE_NOOP: /* Deferred, bail out. */ + state = KNOT_STATE_CONSUME; + break; default: break; } diff --git a/lib/layer/validate.c b/lib/layer/validate.c index 0d700bcd6e5ccabab0861d33aa98cc1cded074cf..cedda57b805bf74ca77560ec43f588cf4d00fe67 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -162,9 +162,6 @@ static int validate_keyset(struct kr_query *qry, knot_pkt_t *answer, bool has_ns updated_key = true; } } - if (!qry->zone_cut.key) { - return kr_error(EBADMSG); - } /* Check if there's a key for current TA. */ if (updated_key && !(qry->flags & QUERY_CACHED)) { @@ -179,43 +176,6 @@ static int validate_keyset(struct kr_query *qry, knot_pkt_t *answer, bool has_ns return kr_ok(); } -static const knot_dname_t *section_first_signer_name(knot_pkt_t *pkt, knot_section_t section_id) -{ - const knot_dname_t *sname = NULL; - const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); - if (!sec) { - return sname; - } - - for (unsigned i = 0; i < sec->count; ++i) { - const knot_rrset_t *rr = knot_pkt_rr(sec, i); - if (rr->type != KNOT_RRTYPE_RRSIG) { - continue; - } - - sname = knot_rrsig_signer_name(&rr->rrs, 0); - break; - } - - return sname; -} - -static const knot_dname_t *first_rrsig_signer_name(knot_pkt_t *answer) -{ - const knot_dname_t *ans_sname = section_first_signer_name(answer, KNOT_ANSWER); - const knot_dname_t *auth_sname = section_first_signer_name(answer, KNOT_AUTHORITY); - - if (!ans_sname) { - return auth_sname; - } else if (!auth_sname) { - return ans_sname; - } else if (knot_dname_is_equal(ans_sname, auth_sname)) { - return ans_sname; - } else { - return NULL; - } -} - static knot_rrset_t *update_ds(struct kr_zonecut *cut, const knot_pktsection_t *sec) { /* Aggregate DS records (if using multiple keys) */ @@ -328,44 +288,12 @@ static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) } /* Answer for RRSIG may not set DO=1, but all records MUST still validate. */ bool use_signatures = (knot_pkt_qtype(pkt) != KNOT_RRTYPE_RRSIG); - /* @todo do not cache RRSIG answers until RFC2181 credibility is implemented */ - if (!use_signatures) { - knot_wire_set_rcode(pkt->wire, KNOT_RCODE_SERVFAIL); /* Prevent caching */ - } if (!(qry->flags & QUERY_CACHED) && !knot_pkt_has_dnssec(pkt) && !use_signatures) { DEBUG_MSG(qry, "<= got insecure response\n"); qry->flags |= QUERY_DNSSEC_BOGUS; return KNOT_STATE_FAIL; } - /* Check if we descended to a NS, which is authoritative for both parent-child, this means that - * the signatures are made using a different key than we have. - * Although we can't "pause" the response procesing and fetch the keys, we can - * say "do not cache this answer, and try again". This way, the resolver will realise - * that the keys are missing and will schedule a subrequest before retrying. - */ - const knot_dname_t *key_own = qry->zone_cut.trust_anchor ? qry->zone_cut.trust_anchor->owner : NULL; - const knot_dname_t *sig_name = first_rrsig_signer_name(pkt); - if (use_signatures && sig_name && key_own && !knot_dname_is_equal(key_own, sig_name)) { - DEBUG_MSG(qry, ">< cut changed, needs revalidation\n"); - if (knot_dname_is_sub(sig_name, qry->zone_cut.name)) { - qry->zone_cut.name = knot_dname_copy(sig_name, &req->pool); - } else if (!knot_dname_is_equal(sig_name, qry->zone_cut.name)) { - /* Key signer is above the current cut, so we can't validate it. This happens when - a server is authoritative for both grandparent, parent and child zone. - Ascend to parent cut, and refetch authority for signer. */ - if (qry->zone_cut.parent) { - memcpy(&qry->zone_cut, qry->zone_cut.parent, sizeof(qry->zone_cut)); - } else { - qry->flags |= QUERY_AWAIT_CUT; - } - qry->zone_cut.name = knot_dname_copy(sig_name, &req->pool); - } /* else zone cut matches, but DS/DNSKEY doesn't => refetch. */ - knot_wire_set_rcode(pkt->wire, KNOT_RCODE_SERVFAIL); /* Prevent caching */ - qry->flags &= ~QUERY_RESOLVED; - return KNOT_STATE_CONSUME; - } - /* Check if this is a DNSKEY answer, check trust chain and store. */ uint8_t pkt_rcode = knot_wire_get_rcode(pkt->wire); uint16_t qtype = knot_pkt_qtype(pkt);