Commit 15f4e488 authored by Marek Vavruša's avatar Marek Vavruša

lib/iterate: detect trust chain before writing to packet

this is a workaround for missing DEFER operation, as the
validator module can only detect trust chain breakage
(caused by answering from different authority) after the
iterator writes answer. this causes duplicated answer on
uncached queries
parent 917622db
......@@ -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;
}
......
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment