diff --git a/lib/layer/validate.c b/lib/layer/validate.c index 5d182617135d85f99fe219ed88feadc6dd4484cf..e09a67289aafc3bd0e11068fa77c258993bf57a7 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -284,49 +284,101 @@ static const knot_dname_t *first_rrsig_signer_name(knot_pkt_t *answer) } } -static int update_delegation(struct kr_query *qry, knot_pkt_t *answer) +static knot_rrset_t *update_ds(struct kr_zonecut *cut, const knot_pktsection_t *sec) { - int ret = kr_ok(); - struct kr_zonecut *cut = &qry->zone_cut; - - DEBUG_MSG(qry, "<= referral, checking DS\n"); - - /* New trust anchor. */ + /* Aggregate DS records (if using multiple keys) */ knot_rrset_t *new_ds = NULL; - knot_section_t section_id = (knot_pkt_qtype(answer) == KNOT_RRTYPE_DS) ? KNOT_ANSWER : KNOT_AUTHORITY; - const knot_pktsection_t *sec = knot_pkt_section(answer, section_id); for (unsigned i = 0; i < sec->count; ++i) { const knot_rrset_t *rr = knot_pkt_rr(sec, i); - if ((rr->type != KNOT_RRTYPE_DS) || - (0)) { -// (knot_dname_cmp(rr->owner, cut->name) != 0)) { + if (rr->type != KNOT_RRTYPE_DS) { continue; } + int ret = 0; if (new_ds) { ret = knot_rdataset_merge(&new_ds->rrs, &rr->rrs, cut->pool); - if (ret != 0) { - goto fail; - } } else { new_ds = knot_rrset_copy(rr, cut->pool); if (!new_ds) { - ret = kr_error(ENOMEM); - goto fail; + return NULL; } } + if (ret != 0) { + knot_rrset_free(&new_ds, cut->pool); + return NULL; + } + } + return new_ds; +} + +static int update_parent(struct kr_query *qry, uint16_t answer_type) +{ + struct kr_query *parent = qry->parent; + assert(parent); + switch(answer_type) { + case KNOT_RRTYPE_DNSKEY: + DEBUG_MSG(qry, "<= parent: updating DNSKEY\n"); + parent->zone_cut.key = knot_rrset_copy(qry->zone_cut.key, parent->zone_cut.pool); + if (!parent->zone_cut.key) { + return KNOT_STATE_FAIL; + } + break; + case KNOT_RRTYPE_DS: + DEBUG_MSG(qry, "<= parent: updating DS\n"); + parent->zone_cut.trust_anchor = knot_rrset_copy(qry->zone_cut.key, parent->zone_cut.pool); + if (!parent->zone_cut.key) { + return KNOT_STATE_FAIL; + } + parent->flags &= ~QUERY_AWAIT_DS; + break; + default: break; } + return kr_ok(); +} - if (new_ds) { - knot_rrset_free(&cut->trust_anchor, cut->pool); - cut->trust_anchor = new_ds; - new_ds = NULL; +static int update_delegation(struct kr_request *req, struct kr_query *qry, knot_pkt_t *answer, bool has_nsec3) +{ + struct kr_zonecut *cut = &qry->zone_cut; - /* It is very likely, that the keys don't match now. */ - knot_rrset_free(&cut->key, cut->pool); + /* RFC4035 3.1.4. authoritative must send either DS or proof of non-existence. + * If it contains neither, the referral is bogus (or an attempted downgrade attack). + */ + + /* Aggregate DS records (if using multiple keys) */ + unsigned section = KNOT_ANSWER; + if (!knot_wire_get_aa(answer->wire)) { /* Referral */ + section = KNOT_AUTHORITY; + } else if (knot_pkt_qtype(answer) == KNOT_RRTYPE_DS) { /* Subrequest */ + section = KNOT_ANSWER; + } else { /* N/A */ + return kr_ok(); } -fail: - knot_rrset_free(&new_ds, cut->pool); + /* No DS provided, check for proof of non-existence. */ + int ret = 0; + knot_rrset_t *new_ds = update_ds(cut, knot_pkt_section(answer, section)); + if (!new_ds) { + if (has_nsec3) { + ret = kr_nsec3_no_data_response_check(answer, section, + knot_pkt_qname(answer), KNOT_RRTYPE_DS); + } else { + ret = kr_nsec_no_data_response_check(answer, section, + knot_pkt_qname(answer), KNOT_RRTYPE_DS); + } + if (ret != 0) { + DEBUG_MSG(qry, "<= bogus proof of DS non-existence\n"); + qry->flags |= QUERY_DNSSEC_BOGUS; + } else { + DEBUG_MSG(qry, "<= DS doesn't exist, going insecure\n"); + qry->flags &= ~QUERY_DNSSEC_WANT; + } + return ret; + } + + /* Extend trust anchor */ + DEBUG_MSG(qry, "<= DS: OK\n"); + knot_rrset_free(&cut->trust_anchor, cut->pool); + knot_rrset_free(&cut->key, cut->pool); + cut->trust_anchor = new_ds; return ret; } @@ -346,15 +398,26 @@ static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) if (!(qry->flags & QUERY_DNSSEC_WANT) || (qry->flags & QUERY_CACHED)) { return ctx->state; } - - /* Server didn't copy back DO=1, this is okay if it doesn't have DS => insecure. - * If it has DS, it must be secured, fail it as bogus. */ if (!knot_pkt_has_dnssec(pkt)) { - DEBUG_MSG(qry, "<= asked with DO=1, got insecure response\n"); -#warning TODO: fail and retry if it has TA, otherwise flag as INSECURE and continue + DEBUG_MSG(qry, "<= got insecure response\n"); + qry->flags |= QUERY_DNSSEC_BOGUS; return KNOT_STATE_FAIL; } + /* Check whether the current zone cut holds keys that can be used + * for validation (i.e. RRSIG signer name matches key owner). + */ + const knot_dname_t *key_own = qry->zone_cut.key ? qry->zone_cut.key->owner : NULL; + const knot_dname_t *sig_name = first_rrsig_signer_name(pkt); + if (key_own && sig_name && !knot_dname_is_equal(key_own, sig_name)) { + if (qry->flags & QUERY_AWAIT_DS) { + qry->flags |= QUERY_DNSSEC_BOGUS; + return KNOT_STATE_FAIL; /* This indicates a DS is not available. */ + } + qry->flags |= QUERY_AWAIT_DS; + return KNOT_STATE_CONSUME; + } + bool has_nsec3 = _knot_pkt_has_type(pkt, KNOT_RRTYPE_NSEC3); uint8_t pkt_rcode = knot_wire_get_rcode(pkt->wire); @@ -373,8 +436,11 @@ static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) } } + /* @todo WTH, this needs API that just tries to find a proof and the caller + * doesn't have to worry about NSEC/NSEC3 + * @todo rework this */ { - knot_pktsection_t *sec = knot_pkt_section(pkt, KNOT_ANSWER); + const knot_pktsection_t *sec = knot_pkt_section(pkt, KNOT_ANSWER); uint16_t answer_count = sec ? sec->count : 0; /* Validate no data response. */ @@ -406,30 +472,9 @@ static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) } } - /* Check whether the current zone cut holds keys that can be used - * for validation (i.e. RRSIG signer name matches key owner). - */ - const knot_dname_t *key_own = qry->zone_cut.key ? qry->zone_cut.key->owner : NULL; - const knot_dname_t *sig_name = first_rrsig_signer_name(pkt); - if (key_own && sig_name && !knot_dname_is_equal(key_own, sig_name)) { - mm_free(qry->zone_cut.pool, qry->zone_cut.missing_name); - qry->zone_cut.missing_name = knot_dname_copy(sig_name, qry->zone_cut.pool); - if (!qry->zone_cut.missing_name) { - return KNOT_STATE_FAIL; - } - qry->flags |= QUERY_AWAIT_DS; - qry->flags &= ~QUERY_RESOLVED; - return KNOT_STATE_CONSUME; - } - /* Check if this is a DNSKEY answer, check trust chain and store. */ uint16_t qtype = knot_pkt_qtype(pkt); if (qtype == KNOT_RRTYPE_DNSKEY) { - if (!qry->zone_cut.trust_anchor) { - DEBUG_MSG(qry, ">< missing trust anchor\n"); - kr_ta_get(&qry->zone_cut.trust_anchor, &global_trust_anchors, qry->zone_cut.name, qry->zone_cut.pool); - } - ret = validate_keyset(qry, pkt, has_nsec3); if (ret != 0) { DEBUG_MSG(qry, "<= bad keys, broken trust chain\n"); @@ -446,31 +491,17 @@ static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) return KNOT_STATE_FAIL; } - /* Update trust anchor. */ - ret = update_delegation(qry, pkt); + /* Check and update current delegation point security status. */ + ret = update_delegation(req, qry, pkt, has_nsec3); if (ret != 0) { return KNOT_STATE_FAIL; } - - if ((qtype == KNOT_RRTYPE_DS) && (qry->parent != NULL) && (qry->parent->zone_cut.trust_anchor == NULL)) { - DEBUG_MSG(qry, "<= updating trust anchor in zone cut\n"); - qry->parent->zone_cut.trust_anchor = knot_rrset_copy(qry->zone_cut.trust_anchor, qry->parent->zone_cut.pool); - if (!qry->parent->zone_cut.trust_anchor) { - return KNOT_STATE_FAIL; - } - /* Update zone cut name */ - 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(qry, "<= updating keys in zone cut\n"); - qry->parent->zone_cut.key = knot_rrset_copy(qry->zone_cut.key, qry->parent->zone_cut.pool); - if (!qry->parent->zone_cut.key) { + /* Update parent query zone cut */ + if (qry->parent) { + if (update_parent(qry, qtype) != 0) { return KNOT_STATE_FAIL; } } - DEBUG_MSG(qry, "<= answer valid, OK\n"); return ctx->state; } @@ -587,7 +618,6 @@ int validate_init(struct kr_module *module) return kr_ok(); } -#warning TODO: set root trust anchor from config int validate_config(struct kr_module *module, const char *conf) { int ret = kr_ta_reset(&global_trust_anchors, NULL); diff --git a/lib/resolve.c b/lib/resolve.c index 92311e15fba9c708f6c1acba66536067dad1150a..6e6280e9fd3bbaaf3b80891e1b8d4900b8c6d646 100644 --- a/lib/resolve.c +++ b/lib/resolve.c @@ -390,18 +390,16 @@ static int zone_cut_check(struct kr_request *request, struct kr_query *qry, knot kr_ta_get(&qry->zone_cut.trust_anchor, &global_trust_anchors, qry->zone_cut.name, qry->zone_cut.pool); } + /* Try to fetch missing DS. */ + if (want_secured && (qry->flags & QUERY_AWAIT_DS)) { + int ret = zone_cut_subreq(rplan, qry, qry->zone_cut.name, KNOT_RRTYPE_DS); if (ret != 0) { return KNOT_STATE_FAIL; } - /* The current trust anchor and keys cannot be used. */ - knot_rrset_free(&qry->zone_cut.key, qry->zone_cut.pool); - knot_rrset_free(&qry->zone_cut.trust_anchor, qry->zone_cut.pool); - qry->flags &= ~QUERY_AWAIT_DS; return KNOT_STATE_DONE; } - /* Try to fetch missing DNSKEY. */ - if (want_secured && !qry->zone_cut.key && qry->stype != KNOT_RRTYPE_DNSKEY) { + if (want_secured && qry->zone_cut.trust_anchor && !qry->zone_cut.key && qry->stype != KNOT_RRTYPE_DNSKEY) { int ret = zone_cut_subreq(rplan, qry, qry->zone_cut.name, KNOT_RRTYPE_DNSKEY); if (ret != 0) { return KNOT_STATE_FAIL;