diff --git a/lib/layer/validate.c b/lib/layer/validate.c index 87c61c402f4864c720e59d4b3e56069b2cc6803e..11ac4d220d7c61eac6fd0acb725030d063802794 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -19,6 +19,7 @@ #include <libknot/descriptor.h> #include <libknot/rrtype/rdname.h> +#include <libknot/rrtype/dnskey.h> #include "lib/layer/iterate.h" #include "lib/resolve.h" @@ -27,7 +28,7 @@ #include "lib/nsrep.h" #include "lib/module.h" -#define DEBUG_MSG(fmt...) QRDEBUG(kr_rplan_current(&req->rplan), "vldr", fmt) +#define DEBUG_MSG(fmt...) QRDEBUG(qry, "vldr", fmt) /* Set resolution context and parameters. */ static int begin(knot_layer_t *ctx, void *module_param) @@ -36,6 +37,7 @@ static int begin(knot_layer_t *ctx, void *module_param) return KNOT_STATE_PRODUCE; } +#if 0 static int secure_query(knot_layer_t *ctx, knot_pkt_t *pkt) { assert(pkt && ctx); @@ -91,20 +93,105 @@ static int secure_query(knot_layer_t *ctx, knot_pkt_t *pkt) #endif return ctx->state; } +#endif + +static int validate_records(struct kr_query *qry, knot_pkt_t *answer) +{ +#warning TODO: validate RRSIGS (records with ZSK, keys with KSK), return FAIL if failed + if (!qry->zone_cut.key) { + DEBUG_MSG("<= no DNSKEY, can't validate\n"); + } + + DEBUG_MSG("!! validation not implemented\n"); + return kr_error(ENOSYS); +} + +static int validate_proof(struct kr_query *qry, knot_pkt_t *answer) +{ +#warning TODO: validate NSECx proof, RRSIGs will be checked later if it matches + return kr_ok(); +} + +static int validate_keyset(struct kr_query *qry, knot_pkt_t *answer) +{ + /* Merge DNSKEY records from answer */ + 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) { + DEBUG_MSG("+= DNSKEY flags: %hu algo: %x\n", + knot_dnskey_flags(&rr->rrs, 0), + 0xff & knot_dnskey_alg(&rr->rrs, 0)); +#warning TODO: merge with zone cut 'key' RRSet + } + } + /* Check if there's a key for current TA. */ +#warning TODO: check if there is a DNSKEY we can trust (matching current TA) + return kr_ok(); +} + +static int update_delegation(struct kr_query *qry, knot_pkt_t *answer) +{ + DEBUG_MSG("<= referral, checking DS\n"); +#warning TODO: delegation, check DS record presence + return kr_ok(); +} static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) { + int ret = 0; struct kr_request *req = ctx->data; - struct kr_query *query = kr_rplan_current(&req->rplan); + struct kr_query *qry = kr_rplan_current(&req->rplan); if (ctx->state & KNOT_STATE_FAIL) { return ctx->state; } -#warning TODO: check if we have DNSKEY in qry->zone_cut and validate RRSIGS/proof, return FAIL if failed -#warning TODO: we must also validate incoming DNSKEY records against the current zone cut TA -#warning FLOW: first answer that comes here must have the DNSKEY that we can validate using TA - DEBUG_MSG("checking query, dnskey: %d, secured: %d\n", - knot_pkt_qtype(pkt) == KNOT_RRTYPE_DNSKEY, - knot_pkt_has_dnssec(pkt)); + + /* Pass-through if user doesn't want secure answer. */ + if (!(req->flags & KR_REQ_DNSSEC)) { + 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("<= asked with DO=1, got insecure response\n"); +#warning TODO: fail and retry if it has TA, otherwise flag as INSECURE and continue + return KNOT_STATE_FAIL; + } + + /* Validate non-existence proof if not positive answer. */ + if (knot_wire_get_rcode(pkt->wire) == KNOT_RCODE_NXDOMAIN) { + ret = validate_proof(qry, pkt); + if (ret != 0) { + DEBUG_MSG("<= bad NXDOMAIN proof\n"); + qry->flags |= QUERY_DNSSEC_BOGUS; + return KNOT_STATE_FAIL; + } + } + + /* Check if this is a DNSKEY answer, check trust chain and store. */ + uint16_t qtype = knot_pkt_qtype(pkt); + if (qtype == KNOT_RRTYPE_DNSKEY) { + ret = validate_keyset(qry, pkt); + if (ret != 0) { + DEBUG_MSG("<= bad keys, broken trust chain\n"); + qry->flags |= QUERY_DNSSEC_BOGUS; + return KNOT_STATE_FAIL; + } + /* Update trust anchor. */ + } else if (qtype == KNOT_RRTYPE_NS) { + update_delegation(qry, pkt); + } + + /* Validate all records, fail as bogus if it doesn't match. */ + ret = validate_records(qry, pkt); + if (ret != 0) { + DEBUG_MSG("<= couldn't validate RRSIGs\n"); + qry->flags |= QUERY_DNSSEC_BOGUS; + return KNOT_STATE_FAIL; + } + + DEBUG_MSG("<= answer valid, OK\n"); return ctx->state; } diff --git a/lib/resolve.c b/lib/resolve.c index dfcb78da65c1d8da702cace3a3f69410af9a431b..c4c718aa82c23c0410cca0b4314b11a926bcb1f7 100644 --- a/lib/resolve.c +++ b/lib/resolve.c @@ -397,6 +397,7 @@ int kr_resolve_consume(struct kr_request *request, knot_pkt_t *packet) if (state == KNOT_STATE_FAIL) { kr_nsrep_update_rtt(&qry->ns, KR_NS_TIMEOUT, ctx->cache_rtt); invalidate_ns(rplan, qry); + qry->flags &= ~QUERY_RESOLVED; /* Track RTT for iterative answers */ } else if (!(qry->flags & QUERY_CACHED)) { struct timeval now; @@ -414,6 +415,11 @@ int kr_resolve_consume(struct kr_request *request, knot_pkt_t *packet) } knot_overlay_reset(&request->overlay); + + /* Do not finish with bogus answer. */ + if (qry->flags & QUERY_DNSSEC_BOGUS) { + return KNOT_STATE_FAIL; + } return kr_rplan_empty(&request->rplan) ? KNOT_STATE_DONE : KNOT_STATE_PRODUCE; } @@ -466,13 +472,11 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t } /* Try to fetch missing DNSKEY. */ if (want_secured && !qry->zone_cut.key && qry->stype != KNOT_RRTYPE_DNSKEY) { - /* TODO -- Fetch all missing DNSKEYS and DS records. */ - /* TODO -- Fetch DS at parent side of a zone cut. Fetch NS at the child side of the zone cut. */ - /* TODO -- Handle holes (sequences with missing delegation). */ struct kr_query *next = kr_rplan_push(rplan, qry, qry->zone_cut.name, KNOT_CLASS_IN, KNOT_RRTYPE_DNSKEY); if (!next) { return kr_error(ENOMEM); } + next->flags |= QUERY_AWAIT_CUT; return KNOT_STATE_PRODUCE; } /* Update minimized QNAME if zone cut changed */ @@ -539,7 +543,7 @@ int kr_resolve_finish(struct kr_request *request, int state) DEBUG_MSG("finished: %d, mempool: %zu B\n", state, (size_t) mp_total_size(request->pool.ctx)); #endif /* Finalize answer */ - if (answer_finalize(request->answer) != 0) { + if (answer_finalize(request, state) != 0) { state = KNOT_STATE_FAIL; } /* Error during procesing, internal failure */ diff --git a/lib/rplan.h b/lib/rplan.h index ccf300a1baf625eb03fb533a100ea00f09b325ef..71ce52b3ee894ad1e2c62fe18a2d1c5c2f4dc903 100644 --- a/lib/rplan.h +++ b/lib/rplan.h @@ -27,16 +27,17 @@ #include "lib/nsrep.h" #define QUERY_FLAGS(X) \ - X(NO_MINIMIZE, 1 << 0) /**< Don't minimize QNAME. */ \ - X(NO_THROTTLE, 1 << 1) /**< No query/slow NS throttling. */ \ - X(TCP , 1 << 2) /**< Use TCP for this query. */ \ - X(RESOLVED , 1 << 3) /**< Query is resolved. */ \ - X(AWAIT_IPV4 , 1 << 4) /**< Query is waiting for A address. */ \ - X(AWAIT_IPV6 , 1 << 5) /**< Query is waiting for AAAA address. */ \ - X(AWAIT_CUT , 1 << 6) /**< Query is waiting for zone cut lookup */ \ - X(SAFEMODE , 1 << 7) /**< Don't use fancy stuff (EDNS...) */ \ - X(CACHED , 1 << 8) /**< Query response is cached. */ \ - X(EXPIRING , 1 << 9) /**< Query response is cached, but expiring. */ + X(NO_MINIMIZE , 1 << 0) /**< Don't minimize QNAME. */ \ + X(NO_THROTTLE , 1 << 1) /**< No query/slow NS throttling. */ \ + X(TCP , 1 << 2) /**< Use TCP for this query. */ \ + X(RESOLVED , 1 << 3) /**< Query is resolved. */ \ + X(AWAIT_IPV4 , 1 << 4) /**< Query is waiting for A address. */ \ + X(AWAIT_IPV6 , 1 << 5) /**< Query is waiting for AAAA address. */ \ + X(AWAIT_CUT , 1 << 6) /**< Query is waiting for zone cut lookup */ \ + X(SAFEMODE , 1 << 7) /**< Don't use fancy stuff (EDNS...) */ \ + X(CACHED , 1 << 8) /**< Query response is cached. */ \ + X(EXPIRING , 1 << 9) /**< Query response is cached, but expiring. */ \ + X(DNSSEC_BOGUS , 1 << 10) /**< Query response is DNSSEC bogus. */ \ /** Query flags */ enum kr_query_flag { diff --git a/lib/zonecut.c b/lib/zonecut.c index 3e9754a7e7bd56a929ceff21efd421b347d736f7..3532fc844ef1e55ae7db01d55f8d39f0788eb785 100644 --- a/lib/zonecut.c +++ b/lib/zonecut.c @@ -265,6 +265,7 @@ int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut) } } +#warning TODO: set root trust anchor from config, or hardcode for now return kr_ok(); } @@ -358,6 +359,7 @@ int kr_zonecut_find_cached(struct kr_context *ctx, struct kr_zonecut *cut, const /* Start at QNAME parent. */ while (txn) { +#warning TODO: find closest trust anchor here, then find NS bool has_key = !secured || fetch_dnskey(ctx, cut, name, txn, timestamp) == 0; if (has_key && fetch_ns(ctx, cut, name, txn, timestamp) == 0) { update_cut_name(cut, name); diff --git a/lib/zonecut.h b/lib/zonecut.h index 1cd101655bc0d99f65a89afb213d0fa658267b5e..e9376aa6824fc381c8273a088a3b3229b731a36c 100644 --- a/lib/zonecut.h +++ b/lib/zonecut.h @@ -31,6 +31,7 @@ struct kr_zonecut { mm_ctx_t *pool; /**< Memory pool. */ map_t nsset; /**< Map of nameserver => address_set. */ knot_rrset_t* key; /**< Zone cut DNSKEY. */ + knot_rrset_t* trust_anchor; /**< Current trust anchor. */ }; /** diff --git a/tests/test_integration.c b/tests/test_integration.c index 4b8ee463b9a207649a82ade0db1141dd5234ebac..b853a18395bfddb14cc36a1323fbc00adccb50b5 100644 --- a/tests/test_integration.c +++ b/tests/test_integration.c @@ -52,8 +52,8 @@ static PyObject* init(PyObject* self, PyObject* args) /* Load basic modules. */ array_init(global_modules); - const char *load_modules[3] = {"iterate", "rrcache", "pktcache"}; - for (unsigned i = 0; i < 3; ++i) { + const char *load_modules[4] = {"iterate", "validate", "rrcache", "pktcache" }; + for (unsigned i = 0; i < 4; ++i) { struct kr_module *mod = malloc(sizeof(*mod)); kr_module_load(mod, load_modules[i], NULL); array_push(global_modules, mod); diff --git a/tests/testdata/iter_validate.rpl b/tests/testdata/iter_validate.rpl new file mode 100644 index 0000000000000000000000000000000000000000..cc1fbeb8654472d207faf980ecfb9fb1b9093f91 --- /dev/null +++ b/tests/testdata/iter_validate.rpl @@ -0,0 +1,117 @@ +; config options +server: + trust-anchor: ". 3600 IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5" + val-override-date: "1436477112" + +stub-zone: + name: "." + stub-addr: 198.41.0.4 # a.root-servers.net. +CONFIG_END + +SCENARIO_BEGIN Test basic validation of NS cz. (two levels) + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 198.41.0.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. 518400 IN NS a.root-servers.net. +. 518400 IN NS b.root-servers.net. +. 518400 IN NS c.root-servers.net. +. 518400 IN NS d.root-servers.net. +. 518400 IN NS e.root-servers.net. +. 518400 IN NS f.root-servers.net. +. 518400 IN NS g.root-servers.net. +. 518400 IN NS h.root-servers.net. +. 518400 IN NS i.root-servers.net. +. 518400 IN NS j.root-servers.net. +. 518400 IN NS k.root-servers.net. +. 518400 IN NS l.root-servers.net. +. 518400 IN NS m.root-servers.net. +. 518400 IN RRSIG NS 8 0 518400 20150719170000 20150709160000 1518 . qoRtgQy1XMDlvBufwjClMyMJRXlcHEl7+Z9mn8BRqZJiAmpYbk+Ku1Z2 omfnUsHX9fLhyLuIRS/FKN4/WPVmcVOxZ09EgZ9hFBH/pn2LOlhQ6abP OIrpy9slr/i9DGZ2YXirKIUWUKUfpcLv0O+g7DZZcqnmhGCikNtXgJ76 njw= +SECTION ADDITIONAL +a.root-servers.net. 518400 IN A 198.41.0.4 +ENTRY_END + + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +. IN DNSKEY +SECTION ANSWER +. 172800 IN DNSKEY 256 3 8 AwEAAZyIkCwEYeG29NV+4cOdKE4DPng/4BqJeoOhKqzJbl+LR33TPWsr wBRfmAi9wvR/Qc6IV4MFMXjmkclXns+atIQZ9uQV3YAvKv/cVuO7Mneu MssIQixaMw+jp73R7zIUNMbLBgJRQXI57Rl+pvXBAkgHndVwv+aJkf7y GEuE9Dtj +. 172800 IN DNSKEY 256 3 8 AwEAAa67bQck1JjopOOFc+iMISFcp/osWrEst2wbKbuQSUWu77QC9UHL ipiHgWN7JlqVAEjKITZz49hhkLmOpmLK55pTq+RD2kwoyNWk9cvpc+tS nIxT7i93O+3oVeLYjMWrkDAz7K45rObbHDuSBwYZKrcSIUCZnCpNMUtn PFl/04cb +. 172800 IN DNSKEY 257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq QxA+Uk1ihz0= +. 172800 IN RRSIG DNSKEY 8 0 172800 20150715235959 20150701000000 19036 . PCLEqe8X433LWIWcrP5jC3Ejjws7XST8CFpiccRKXuB9YGMi3AngOXf4 4wiXG1WXLNb+5Rj/na6/4znyTd3sb21T9syHol9kaVMSXzIGg07yZ0hk 62BVdwKOPphtivSwYYjZYxXXXMR/X1MZNwD6a63Tz2Q+zMnS897CTo3M gMsU8qUUwPAqS52rP82Bf0L3Uhr7KWwfHrfH9CN4kAxi8ha2EWSaHNCo jTHUXnaGDDOtUQUSvrgI8vlBRjncr45ktmUh7a8OF2AxoPlfd/FTp6Dy 1f3B90G8fGml0LU/dQVOr3PWMRmmELhY/QQpL+FptAnxIVPeg7jJsZ9A P3OrmA== +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +cz. IN NS +SECTION AUTHORITY +cz. 172800 IN NS a.ns.nic.cz. +cz. 86400 IN DS 54576 10 2 397E50C85EDE9CDE33F363A9E66FD1B216D788F8DD438A57A423A386 869C8F06 +cz. 86400 IN RRSIG DS 8 1 86400 20150719170000 20150709160000 1518 . nzjFR1npBJ8enQ4jXm9DZ8S8FheWzMps9xourERlhaal8buDVwyGnWWW wy9z3d+a3hXtYCL+rOJLm9tz+GVRQuZRk6Yp//5ckTGn0HwymIIXw3nU LEfGafsgBIQZiW7eyMq/zwZMjOSQ9KRMomjh1clUxQsuxUrkw/mJMMZI 0R0= +SECTION ADDITIONAL +a.ns.nic.cz. 172800 IN A 194.0.12.1 +ENTRY_END +RANGE_END + +; a.ns.nic.cz. +RANGE_BEGIN 0 100 + ADDRESS 194.0.12.1 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +cz. IN NS +SECTION ANSWER +cz. 18000 IN NS a.ns.nic.cz. +cz. 18000 IN NS b.ns.nic.cz. +cz. 18000 IN NS c.ns.nic.cz. +cz. 18000 IN NS d.ns.nic.cz. +cz. 18000 IN RRSIG NS 10 1 18000 20150722014826 20150708113847 39788 cz. fXbYxeKOypz1mouiC3PTYSUv16rGy93f1GNRwIvNkJQHrDAXImLX6JoS aESL9cm671WJ4d8MgjrCcdaeILyFPnsb2zoSn96sreABtk3zZz54xk23 dzQmXIMSwXDHIpfdF2Adsh+JOblOYuLrgepyG59IRKdPS0UVyDUlIfia slQ= +SECTION ADDITIONAL +a.ns.nic.cz. 18000 IN A 194.0.12.1 +b.ns.nic.cz. 18000 IN A 194.0.12.1 +c.ns.nic.cz. 18000 IN A 194.0.12.1 +d.ns.nic.cz. 18000 IN A 194.0.12.1 +ENTRY_END + +RANGE_END + + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +cz. IN NS +ENTRY_END + +; recursion happens here. +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AD NOERROR +SECTION QUESTION +cz. IN NS +SECTION ANSWER +cz. 18000 IN NS a.ns.nic.cz. +cz. 18000 IN NS b.ns.nic.cz. +cz. 18000 IN NS c.ns.nic.cz. +cz. 18000 IN NS d.ns.nic.cz. +cz. 18000 IN RRSIG NS 10 1 18000 20150722014826 20150708113847 39788 cz. fXbYxeKOypz1mouiC3PTYSUv16rGy93f1GNRwIvNkJQHrDAXImLX6JoS aESL9cm671WJ4d8MgjrCcdaeILyFPnsb2zoSn96sreABtk3zZz54xk23 dzQmXIMSwXDHIpfdF2Adsh+JOblOYuLrgepyG59IRKdPS0UVyDUlIfia slQ= +ENTRY_END + +SCENARIO_END