int kr_nsec_existence_denial(const knot_pkt_t *pkt, knot_section_t section_id, const knot_dname_t *sname, uint16_t stype) { const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); if (!sec || !sname) { return kr_error(EINVAL); } 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; } /* NSEC proves that name exists, but has no data (RFC4035 4.9, 1) */ if (knot_dname_is_equal(rrset->owner, sname)) { no_data_response_check_rrtype(&flags, rrset, stype); } else { /* NSEC proves that name doesn't exist (RFC4035, 4.9, 2) */ name_error_response_check_rr(&flags, rrset, sname); } no_data_wildcard_existence_check(&flags, rrset, sec); } return kr_nsec_existence_denied(flags) ? kr_ok() : kr_error(ENOENT); }
static int insert_rr(zone_contents_t *z, const knot_rrset_t *rr, zone_node_t **n, bool nsec3) { if (z == NULL || knot_rrset_empty(rr) || n == NULL) { return KNOT_EINVAL; } // check if the RRSet belongs to the zone if (!knot_dname_is_sub(rr->owner, z->apex->owner) && !knot_dname_is_equal(rr->owner, z->apex->owner)) { return KNOT_EOUTOFZONE; } int ret = KNOT_EOK; if (*n == NULL) { *n = nsec3 ? zone_contents_get_nsec3_node(z, rr->owner) : zone_contents_get_node(z, rr->owner); if (*n == NULL) { // Create new, insert *n = node_new(rr->owner, NULL); if (*n == NULL) { return KNOT_ENOMEM; } ret = nsec3 ? zone_contents_add_nsec3_node(z, *n) : zone_contents_add_node(z, *n, true); if (ret != KNOT_EOK) { node_free(n, NULL); } } } return node_add_rrset(*n, rr, NULL); }
/** * 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; }
/** * Check the RRSIG RR validity according to RFC4035 5.3.1 . * @param flags The flags are going to be set according to validation result. * @param cov_labels Covered RRSet owner label count. * @param rrsigs RRSet containing the signatures. * @param sig_pos Specifies the signature within the RRSIG RRSet. * @param keys Associated DNSKEY RRSet. * @param key_pos Specifies the key within the DNSKEY RRSet, * @param keytag Used key tag. * @param zone_name The name of the zone cut. * @param timestamp Validation time. */ static int validate_rrsig_rr(int *flags, int cov_labels, const knot_rrset_t *rrsigs, size_t sig_pos, const knot_rrset_t *keys, size_t key_pos, uint16_t keytag, const knot_dname_t *zone_name, uint32_t timestamp) { if (!flags || !rrsigs || !keys || !zone_name) { return kr_error(EINVAL); } /* bullet 5 */ if (knot_rrsig_sig_expiration(&rrsigs->rrs, sig_pos) < timestamp) { return kr_error(EINVAL); } /* bullet 6 */ if (knot_rrsig_sig_inception(&rrsigs->rrs, sig_pos) > timestamp) { return kr_error(EINVAL); } /* bullet 2 */ const knot_dname_t *signer_name = knot_rrsig_signer_name(&rrsigs->rrs, sig_pos); if (!signer_name || !knot_dname_is_equal(signer_name, zone_name)) { return kr_error(EINVAL); } /* bullet 4 */ { int rrsig_labels = knot_rrsig_labels(&rrsigs->rrs, sig_pos); if (rrsig_labels > cov_labels) { return kr_error(EINVAL); } if (rrsig_labels < cov_labels) { *flags |= FLG_WILDCARD_EXPANSION; } } /* bullet 7 */ if ((!knot_dname_is_equal(keys->owner, signer_name)) || (knot_dnskey_alg(&keys->rrs, key_pos) != knot_rrsig_algorithm(&rrsigs->rrs, sig_pos)) || (keytag != knot_rrsig_key_tag(&rrsigs->rrs, sig_pos))) { return kr_error(EINVAL); } /* bullet 8 */ /* Checked somewhere else. */ /* bullet 9 and 10 */ /* One of the requirements should be always fulfilled. */ return kr_ok(); }
static int answer_query(knot_pkt_t *pkt, pack_t *addr_set, struct kr_query *qry) { uint16_t rrtype = qry->stype; uint16_t rrclass = qry->sclass; if (rrtype != KNOT_RRTYPE_A && rrtype != KNOT_RRTYPE_AAAA) { return kr_error(ENOENT); } knot_dname_t *qname = knot_dname_copy(qry->sname, &pkt->mm); knot_rrset_t rr; knot_rrset_init(&rr, qname, rrtype, rrclass); int family_len = sizeof(struct in_addr); if (rr.type == KNOT_RRTYPE_AAAA) { family_len = sizeof(struct in6_addr); } /* Append address records from hints */ uint8_t *addr = pack_head(*addr_set); while (addr != pack_tail(*addr_set)) { size_t len = pack_obj_len(addr); void *addr_val = pack_obj_val(addr); if (len == family_len) { knot_rrset_add_rdata(&rr, addr_val, len, 0, &pkt->mm); } addr = pack_obj_next(addr); } int ret = kr_error(ENOENT); if (!knot_rrset_empty(&rr)) { /* Update packet question */ if (!knot_dname_is_equal(knot_pkt_qname(pkt), qname)) { KR_PKT_RECYCLE(pkt); knot_pkt_put_question(pkt, qname, rrclass, rrtype); } /* Append to packet */ ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, &rr, KNOT_PF_FREE); } /* Clear RR if failed */ if (ret != 0) { knot_rrset_clear(&rr, &pkt->mm); } return ret; }
int kr_nsec_no_data_response_check(const knot_pkt_t *pkt, knot_section_t section_id, const knot_dname_t *sname, uint16_t stype) { const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); if (!sec || !sname) { return kr_error(EINVAL); } 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, sname)) { int ret = no_data_response_check_rrtype(&flags, rrset, stype); if (ret != 0) { return ret; } } } return (flags & FLG_NOEXIST_RRTYPE) ? kr_ok() : kr_error(ENOENT); }
static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) { int ret = 0; struct kr_request *req = ctx->data; struct kr_query *qry = req->current_query; /* Ignore faulty or unprocessed responses. */ if (ctx->state & (KNOT_STATE_FAIL|KNOT_STATE_CONSUME)) { return ctx->state; } /* Pass-through if user doesn't want secure answer or stub. */ /* @todo: Validating stub resolver mode. */ if (!(qry->flags & QUERY_DNSSEC_WANT) || (qry->flags & QUERY_STUB)) { return ctx->state; } /* Answer for RRSIG may not set DO=1, but all records MUST still validate. */ bool use_signatures = (knot_pkt_qtype(pkt) != KNOT_RRTYPE_RRSIG); 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; } /* 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. */ 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))) { if (ctx->state == KNOT_STATE_YIELD) { /* Already yielded for revalidation. */ return KNOT_STATE_FAIL; } DEBUG_MSG(qry, ">< cut changed, needs revalidation\n"); if (!signer) { /* Not a DNSSEC-signed response, ask parent for DS to prove transition to INSECURE. */ } else if (knot_dname_is_sub(signer, qry->zone_cut.name)) { /* Key signer is below current cut, advance and refetch keys. */ 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. */ return KNOT_STATE_YIELD; } /* 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); bool has_nsec3 = pkt_has_type(pkt, KNOT_RRTYPE_NSEC3); if (knot_wire_get_aa(pkt->wire) && qtype == KNOT_RRTYPE_DNSKEY) { ret = validate_keyset(qry, pkt, has_nsec3); if (ret != 0) { DEBUG_MSG(qry, "<= bad keys, broken trust chain\n"); qry->flags |= QUERY_DNSSEC_BOGUS; return KNOT_STATE_FAIL; } } /* Validate non-existence proof if not positive answer. */ if (!(qry->flags & QUERY_CACHED) && pkt_rcode == KNOT_RCODE_NXDOMAIN) { /* @todo If knot_pkt_qname(pkt) is used instead of qry->sname then the tests crash. */ if (!has_nsec3) { ret = kr_nsec_name_error_response_check(pkt, KNOT_AUTHORITY, qry->sname); } else { ret = kr_nsec3_name_error_response_check(pkt, KNOT_AUTHORITY, qry->sname); } if (ret != 0) { DEBUG_MSG(qry, "<= bad NXDOMAIN proof\n"); qry->flags |= QUERY_DNSSEC_BOGUS; return KNOT_STATE_FAIL; } } /* @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 */ if (!(qry->flags & QUERY_CACHED)) { const knot_pktsection_t *an = knot_pkt_section(pkt, KNOT_ANSWER); if (pkt_rcode == KNOT_RCODE_NOERROR && an->count == 0 && knot_wire_get_aa(pkt->wire)) { /* @todo * ? quick mechanism to determine which check to preform first * ? merge the functionality together to share code/resources */ if (!has_nsec3) { ret = kr_nsec_existence_denial(pkt, KNOT_AUTHORITY, knot_pkt_qname(pkt), knot_pkt_qtype(pkt)); } else { ret = kr_nsec3_no_data(pkt, KNOT_AUTHORITY, knot_pkt_qname(pkt), knot_pkt_qtype(pkt)); } if (ret != 0) { if (has_nsec3 && (ret == kr_error(DNSSEC_NOT_FOUND))) { DEBUG_MSG(qry, "<= can't prove NODATA due to optout, going insecure\n"); qry->flags &= ~QUERY_DNSSEC_WANT; qry->flags |= QUERY_DNSSEC_INSECURE; } else { DEBUG_MSG(qry, "<= bad NODATA proof\n"); qry->flags |= QUERY_DNSSEC_BOGUS; return KNOT_STATE_FAIL; } } } } /* Validate all records, fail as bogus if it doesn't match. * Do not revalidate data from cache, as it's already trusted. */ if (!(qry->flags & QUERY_CACHED)) { ret = validate_records(qry, pkt, req->rplan.pool, has_nsec3); if (ret != 0) { DEBUG_MSG(qry, "<= couldn't validate RRSIGs\n"); qry->flags |= QUERY_DNSSEC_BOGUS; return KNOT_STATE_FAIL; } } /* Check if wildcard expansion detected for final query. * If yes, copy authority. */ if ((qry->parent == NULL) && (qry->flags & QUERY_DNSSEC_WEXPAND)) { const knot_pktsection_t *auth = knot_pkt_section(pkt, KNOT_AUTHORITY); for (unsigned i = 0; i < auth->count; ++i) { const knot_rrset_t *rr = knot_pkt_rr(auth, i); kr_rrarray_add(&req->authority, rr, &req->answer->mm); } } /* Check and update current delegation point security status. */ ret = update_delegation(req, qry, pkt, has_nsec3); if (ret != 0) { return KNOT_STATE_FAIL; } /* Update parent query zone cut */ if (qry->parent) { if (update_parent_keys(qry, qtype) != 0) { return KNOT_STATE_FAIL; } } DEBUG_MSG(qry, "<= answer valid, OK\n"); return KNOT_STATE_DONE; }
static int validate_records(struct kr_query *qry, knot_pkt_t *answer, knot_mm_t *pool, bool has_nsec3) { if (!qry->zone_cut.key) { DEBUG_MSG(qry, "<= no DNSKEY, can't validate\n"); return kr_error(EBADMSG); } kr_rrset_validation_ctx_t vctx = { .pkt = answer, .section_id = KNOT_ANSWER, .keys = qry->zone_cut.key, .zone_name = qry->zone_cut.name, .timestamp = qry->timestamp.tv_sec, .has_nsec3 = has_nsec3, .flags = 0, .result = 0 }; int ret = validate_section(&vctx, pool); if (ret != 0) { return ret; } uint32_t an_flags = vctx.flags; vctx.section_id = KNOT_AUTHORITY; /* zone_name can be changed by validate_section(), restore it */ vctx.zone_name = qry->zone_cut.name; vctx.flags = 0; vctx.result = 0; ret = validate_section(&vctx, pool); if (ret != 0) { return ret; } /* Records were validated. * If there is wildcard expansion in answer, flag the query. */ if (an_flags & KR_DNSSEC_VFLG_WEXPAND) { qry->flags |= QUERY_DNSSEC_WEXPAND; } return ret; } static int validate_keyset(struct kr_query *qry, knot_pkt_t *answer, bool has_nsec3) { /* Merge DNSKEY records from answer that are below/at current cut. */ bool updated_key = false; const knot_pktsection_t *an = knot_pkt_section(answer, KNOT_ANSWER); for (unsigned i = 0; i < an->count; ++i) { const knot_rrset_t *rr = knot_pkt_rr(an, i); if ((rr->type != KNOT_RRTYPE_DNSKEY) || !knot_dname_in(qry->zone_cut.name, rr->owner)) { continue; } /* Merge with zone cut (or replace ancestor key). */ if (!qry->zone_cut.key || !knot_dname_is_equal(qry->zone_cut.key->owner, rr->owner)) { qry->zone_cut.key = knot_rrset_copy(rr, qry->zone_cut.pool); if (!qry->zone_cut.key) { return kr_error(ENOMEM); } updated_key = true; } else { int ret = knot_rdataset_merge(&qry->zone_cut.key->rrs, &rr->rrs, qry->zone_cut.pool); if (ret != 0) { knot_rrset_free(&qry->zone_cut.key, qry->zone_cut.pool); return ret; } updated_key = true; } } /* Check if there's a key for current TA. */ if (updated_key && !(qry->flags & QUERY_CACHED)) { kr_rrset_validation_ctx_t vctx = { .pkt = answer, .section_id = KNOT_ANSWER, .keys = qry->zone_cut.key, .zone_name = qry->zone_cut.name, .timestamp = qry->timestamp.tv_sec, .has_nsec3 = has_nsec3, .flags = 0, .result = 0 }; int ret = kr_dnskeys_trusted(&vctx, qry->zone_cut.trust_anchor); if (ret != 0) { knot_rrset_free(&qry->zone_cut.key, qry->zone_cut.pool); return ret; } if (vctx.flags & KR_DNSSEC_VFLG_WEXPAND) { qry->flags |= QUERY_DNSSEC_WEXPAND; } } return kr_ok(); }
/*----------------------------------------------------------------------------*/ _public_ bool knot_dname_in(const knot_dname_t *domain, const knot_dname_t *sub) { return knot_dname_is_equal(domain, sub) || knot_dname_is_sub(sub, domain); }
int kr_rrset_validate_with_key(const knot_pkt_t *pkt, knot_section_t section_id, const knot_rrset_t *covered, const knot_rrset_t *keys, size_t key_pos, const struct dseckey *key, const knot_dname_t *zone_name, uint32_t timestamp, bool has_nsec3) { struct dseckey *created_key = NULL; if (key == NULL) { const knot_rdata_t *krr = knot_rdataset_at(&keys->rrs, key_pos); int ret = kr_dnssec_key_from_rdata(&created_key, keys->owner, knot_rdata_data(krr), knot_rdata_rdlen(krr)); if (ret != 0) { return ret; } key = created_key; } uint16_t keytag = dnssec_key_get_keytag((dnssec_key_t *)key); int covered_labels = knot_dname_labels(covered->owner, NULL); if (knot_dname_is_wildcard(covered->owner)) { /* The asterisk does not count, RFC4034 3.1.3, paragraph 3. */ --covered_labels; } const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); for (unsigned i = 0; i < sec->count; ++i) { /* Consider every RRSIG that matches owner and covers the class/type. */ const knot_rrset_t *rrsig = knot_pkt_rr(sec, i); if (rrsig->type != KNOT_RRTYPE_RRSIG) { continue; } if ((covered->rclass != rrsig->rclass) || !knot_dname_is_equal(covered->owner, rrsig->owner)) { continue; } for (uint16_t j = 0; j < rrsig->rrs.rr_count; ++j) { int val_flgs = 0; int trim_labels = 0; if (knot_rrsig_type_covered(&rrsig->rrs, j) != covered->type) { continue; } if (validate_rrsig_rr(&val_flgs, covered_labels, rrsig, j, keys, key_pos, keytag, zone_name, timestamp) != 0) { continue; } if (val_flgs & FLG_WILDCARD_EXPANSION) { trim_labels = wildcard_radix_len_diff(covered->owner, rrsig, j); if (trim_labels < 0) { break; } } if (kr_check_signature(rrsig, j, (dnssec_key_t *) key, covered, trim_labels) != 0) { continue; } if (val_flgs & FLG_WILDCARD_EXPANSION) { int ret = 0; if (!has_nsec3) { ret = kr_nsec_wildcard_answer_response_check(pkt, KNOT_AUTHORITY, covered->owner); } else { ret = kr_nsec3_wildcard_answer_response_check(pkt, KNOT_AUTHORITY, covered->owner, trim_labels - 1); } if (ret != 0) { continue; } } /* Validated with current key, OK */ kr_dnssec_key_free(&created_key); return kr_ok(); } } /* No applicable key found, cannot be validated. */ kr_dnssec_key_free(&created_key); return kr_error(ENOENT); }