From 8a8a017fc5322411fcc83283d083f630560f65b5 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Tue, 22 Dec 2015 01:03:31 +0100 Subject: [PATCH] Validate received TSIG reply --- src/dict.c | 1 + src/request-internal.c | 170 +++++++++++++++++++++++++++++++++++++++++ src/types-internal.h | 10 +++ src/util-internal.c | 11 ++- 4 files changed, 191 insertions(+), 1 deletion(-) diff --git a/src/dict.c b/src/dict.c index 5fa1c67c..b434992c 100644 --- a/src/dict.c +++ b/src/dict.c @@ -995,6 +995,7 @@ getdns_pp_dict(gldns_buffer * buf, size_t indent, if (!json && (strcmp(item->node.key, "answer_type") == 0 || strcmp(item->node.key, "dnssec_status") == 0 || + strcmp(item->node.key, "tsig_status") == 0 || strcmp(item->node.key, "status") == 0 || strcmp(item->node.key, "append_name") == 0 || strcmp(item->node.key, "follow_redirects") == 0 || diff --git a/src/request-internal.c b/src/request-internal.c index f873c96e..08900622 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -134,6 +134,7 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner, net_req->owner = owner; net_req->dnssec_status = GETDNS_DNSSEC_INDETERMINATE; + net_req->tsig_status = GETDNS_DNSSEC_INDETERMINATE; net_req->upstream = NULL; net_req->fd = -1; @@ -308,6 +309,9 @@ _getdns_network_req_add_tsig(getdns_network_req *req) unsigned int md_len = EVP_MAX_MD_SIZE; const EVP_MD *digester; + /* Should only be called when in stub mode */ + assert(req->query); + if (upstream->tsig_alg == GETDNS_NO_TSIG || !upstream->tsig_dname_len) return req->response - req->query; @@ -384,11 +388,177 @@ _getdns_network_req_add_tsig(getdns_network_req *req) return req->response - req->query; DEBUG_STUB("Sending with TSIG, mac length: %d\n", (int)md_len); + req->tsig_status = GETDNS_DNSSEC_INSECURE; gldns_write_uint16(req->query + 10, arcount + 1); req->response = gldns_buffer_current(&gbuf); return req->response - req->query; } + + +void +_getdns_network_validate_tsig(getdns_network_req *req) +{ + _getdns_rr_iter rr_spc, *rr; + _getdns_rdf_iter rdf_spc, *rdf; + uint8_t *request_mac; + uint16_t request_mac_len; + uint8_t tsig_vars[MAXIMUM_TSIG_SPACE]; + gldns_buffer gbuf; + uint8_t *dname; + size_t dname_len; + uint8_t *response_mac; + uint16_t response_mac_len; + uint8_t other_len; + uint8_t result_mac[EVP_MAX_MD_SIZE]; + unsigned int result_mac_len = EVP_MAX_MD_SIZE; + uint16_t original_id; + const EVP_MD *digester; + HMAC_CTX ctx; + + DEBUG_STUB("Validate TSIG\n"); + for ( rr = _getdns_rr_iter_init(&rr_spc, req->query, + (req->response - req->query)) + ; rr + ; rr = _getdns_rr_iter_next(rr)) { + + if (_getdns_rr_iter_section(rr) == GLDNS_SECTION_ADDITIONAL && + gldns_read_uint16(rr->rr_type) == GETDNS_RRTYPE_TSIG) + break; + } + if (!rr || !(rdf = _getdns_rdf_iter_init_at(&rdf_spc, rr, 3))) + return; /* No good TSIG sent, so nothing expected on reply */ + + request_mac_len = gldns_read_uint16(rdf->pos); + if (request_mac_len != rdf->nxt - rdf->pos - 2) + return; + DEBUG_STUB("Request MAC found length: %d\n", (int)(request_mac_len)); + request_mac = rdf->pos + 2; + + /* Now we expect a TSIG on the response! */ + req->tsig_status = GETDNS_DNSSEC_BOGUS; + + for ( rr = _getdns_rr_iter_init( + &rr_spc, req->response, req->response_len) + ; rr + ; rr = _getdns_rr_iter_next(rr)) { + + if (_getdns_rr_iter_section(rr) == GLDNS_SECTION_ADDITIONAL && + gldns_read_uint16(rr->rr_type) == GETDNS_RRTYPE_TSIG) + break; + } + if (!rr || !(rdf = _getdns_rdf_iter_init(&rdf_spc, rr))) + return; + gldns_buffer_init_frm_data(&gbuf, tsig_vars, MAXIMUM_TSIG_SPACE); + + dname_len = gldns_buffer_remaining(&gbuf); + if (!(dname = _getdns_owner_if_or_as_decompressed( + rr, gldns_buffer_current(&gbuf), &dname_len))) + return; + if (dname == gldns_buffer_current(&gbuf)) + gldns_buffer_skip(&gbuf, dname_len); + else + gldns_buffer_write(&gbuf, dname, dname_len); + + gldns_buffer_write(&gbuf, rr->rr_type + 2, 2); /* Class */ + gldns_buffer_write(&gbuf, rr->rr_type + 4, 4); /* TTL */ + + dname_len = gldns_buffer_remaining(&gbuf); + if (!(dname = _getdns_rdf_if_or_as_decompressed( + rdf, gldns_buffer_current(&gbuf), &dname_len))) + return; + if (dname == gldns_buffer_current(&gbuf)) + gldns_buffer_skip(&gbuf, dname_len); + else + gldns_buffer_write(&gbuf, dname, dname_len); + + if (!(rdf = _getdns_rdf_iter_next(rdf)) || + rdf->nxt - rdf->pos != 6) + return; + gldns_buffer_write(&gbuf, rdf->pos, 6); /* Time Signed */ + + if (!(rdf = _getdns_rdf_iter_next(rdf)) || + rdf->nxt - rdf->pos != 2) + return; + gldns_buffer_write(&gbuf, rdf->pos, 2); /* Fudge */ + + if (!(rdf = _getdns_rdf_iter_next(rdf))) /* mac */ + return; + response_mac_len = gldns_read_uint16(rdf->pos); + if (response_mac_len != rdf->nxt - rdf->pos - 2) + return; + DEBUG_STUB("Response MAC found length: %d\n", (int)(response_mac_len)); + response_mac = rdf->pos + 2; + + if (!(rdf = _getdns_rdf_iter_next(rdf)) || + rdf->nxt -rdf->pos != 2) /* Original ID */ + return; + original_id = gldns_read_uint16(rdf->pos); + + if (!(rdf = _getdns_rdf_iter_next(rdf)) || + rdf->nxt - rdf->pos != 2) + return; + gldns_buffer_write(&gbuf, rdf->pos, 2); /* Error */ + + if (!(rdf = _getdns_rdf_iter_next(rdf))) /* Other */ + return; + + gldns_buffer_write_u16(&gbuf, 0); /* Other len */ + other_len = gldns_read_uint16(rdf->pos); + if (other_len != rdf->nxt - rdf->pos - 2) + return; + if (other_len) + gldns_buffer_write(&gbuf, rdf->pos, other_len); + + /* TSIG found */ + DEBUG_STUB("TSIG found, original ID: %d\n", (int)original_id); + + gldns_write_uint16(req->response + 10, + gldns_read_uint16(req->response + 10) - 1); + gldns_write_uint16(req->response, original_id); + + switch (req->upstream->tsig_alg) { +#ifdef HAVE_EVP_MD5 + case GETDNS_HMAC_MD5 : digester = EVP_md5() ; break; +#endif +#ifdef HAVE_EVP_SHA1 + case GETDNS_HMAC_SHA1 : digester = EVP_sha1() ; break; +#endif +#ifdef HAVE_EVP_SHA224 + case GETDNS_HMAC_SHA224: digester = EVP_sha224(); break; +#endif +#ifdef HAVE_EVP_SHA256 + case GETDNS_HMAC_SHA256: digester = EVP_sha256(); break; +#endif +#ifdef HAVE_EVP_SHA384 + case GETDNS_HMAC_SHA384: digester = EVP_sha384(); break; +#endif +#ifdef HAVE_EVP_SHA512 + case GETDNS_HMAC_SHA512: digester = EVP_sha512(); break; +#endif + default : return; + } + + HMAC_CTX_init(&ctx); + (void) HMAC_Init_ex(&ctx, req->upstream->tsig_key, + req->upstream->tsig_size, digester, NULL); + (void) HMAC_Update(&ctx, request_mac - 2, request_mac_len + 2); + (void) HMAC_Update(&ctx, req->response, rr->pos - req->response); + (void) HMAC_Update(&ctx, tsig_vars, gldns_buffer_position(&gbuf)); + HMAC_Final(&ctx, result_mac, &result_mac_len); + + DEBUG_STUB("Result MAC length: %d\n", (int)(result_mac_len)); + if (result_mac_len == response_mac_len && + memcmp(result_mac, response_mac, result_mac_len) == 0) + req->tsig_status = GETDNS_DNSSEC_SECURE; + + HMAC_CTX_cleanup(&ctx); + + gldns_write_uint16(req->response, gldns_read_uint16(req->query)); + gldns_write_uint16(req->response + 10, + gldns_read_uint16(req->response + 10) + 1); +} + void _getdns_dns_req_free(getdns_dns_req * req) { diff --git a/src/types-internal.h b/src/types-internal.h index 7d3eb190..21d47db6 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -209,6 +209,14 @@ typedef struct getdns_network_req /* dnssec status */ int dnssec_status; + /* tsig status: + * GETDNS_DNSSEC_INDETERMINATE means "No TSIG processing" + * GETDNS_DNSSEC_INSECURE means "TSIG sent, validate reply" + * GETDNS_DNSSEC_SECURE means "Validated" + * GETDNS_DNSSEC_BOGUS means "Validation failed" + */ + int tsig_status; + /* For stub resolving */ struct getdns_upstream *upstream; int fd; @@ -382,5 +390,7 @@ void _getdns_network_req_clear_upstream_options(getdns_network_req * req); /* Adds TSIG signature (if needed) and returns query length */ size_t _getdns_network_req_add_tsig(getdns_network_req *req); +void _getdns_network_validate_tsig(getdns_network_req *req); + #endif /* types-internal.h */ diff --git a/src/util-internal.c b/src/util-internal.c index 4649a79c..3a5c1516 100644 --- a/src/util-internal.c +++ b/src/util-internal.c @@ -802,6 +802,9 @@ _getdns_create_getdns_response(getdns_dns_req *completed_request) if (! netreq->response_len) continue; + if (netreq->tsig_status == GETDNS_DNSSEC_INSECURE) + _getdns_network_validate_tsig(netreq); + nreplies++; if (netreq->dnssec_status == GETDNS_DNSSEC_SECURE) nsecure++; @@ -820,6 +823,8 @@ _getdns_create_getdns_response(getdns_dns_req *completed_request) else if (completed_request->dnssec_return_only_secure && netreq->dnssec_status != GETDNS_DNSSEC_SECURE) continue; + else if (netreq->tsig_status == GETDNS_DNSSEC_BOGUS) + continue; } if (!(reply = _getdns_create_reply_dict(context, netreq, just_addrs, &rrsigs_in_answer))) @@ -847,7 +852,11 @@ _getdns_create_getdns_response(getdns_dns_req *completed_request) netreq->dnssec_status)) goto error; } - + if (netreq->tsig_status != GETDNS_DNSSEC_INDETERMINATE) { + if (getdns_dict_set_int(reply, "tsig_status", + netreq->tsig_status)) + goto error; + } if (_getdns_list_append_dict(replies_tree, reply)) { getdns_dict_destroy(reply); goto error;