]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
lib/validate: fixed referrals, insecure delegations, cut updates
authorMarek Vavruša <marek.vavrusa@nic.cz>
Sat, 19 Sep 2015 19:33:30 +0000 (21:33 +0200)
committerMarek Vavruša <marek.vavrusa@nic.cz>
Sat, 19 Sep 2015 19:33:30 +0000 (21:33 +0200)
as per rfc4035 all secured referrals must have either DS or proof
of non-existence. there is one use case where the resolver doesn’t
learn a DS this way, when a single server hosts both parent and
child zone. in this case, DS must be requested separetely

lib/layer/validate.c
lib/resolve.c

index 5d182617135d85f99fe219ed88feadc6dd4484cf..e09a67289aafc3bd0e11068fa77c258993bf57a7 100644 (file)
@@ -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);
index 92311e15fba9c708f6c1acba66536067dad1150a..6e6280e9fd3bbaaf3b80891e1b8d4900b8c6d646 100644 (file)
@@ -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;