From bc2ec7cee36b067d699bd08e37f53a4cef7979a0 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Fri, 18 Dec 2015 15:16:48 +0100 Subject: [PATCH 1/4] Specify TSIG parameters with getdns_query --- src/test/getdns_query.c | 97 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/src/test/getdns_query.c b/src/test/getdns_query.c index 7fc569fa..ccf686f1 100644 --- a/src/test/getdns_query.c +++ b/src/test/getdns_query.c @@ -267,6 +267,66 @@ static enum { GENERAL, ADDRESS, HOSTNAME, SERVICE } calltype = GENERAL; int get_rrtype(const char *t); +int gqldns_b64_pton(char const *src, uint8_t *target, size_t targsize) +{ + const uint8_t pad64 = 64; /* is 64th in the b64 array */ + const char* s = src; + uint8_t in[4]; + size_t o = 0, incount = 0; + + while(*s) { + /* skip any character that is not base64 */ + /* conceptually we do: + const char* b64 = pad'=' is appended to array + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + const char* d = strchr(b64, *s++); + and use d-b64; + */ + char d = *s++; + if(d <= 'Z' && d >= 'A') + d -= 'A'; + else if(d <= 'z' && d >= 'a') + d = d - 'a' + 26; + else if(d <= '9' && d >= '0') + d = d - '0' + 52; + else if(d == '+') + d = 62; + else if(d == '/') + d = 63; + else if(d == '=') + d = 64; + else continue; + in[incount++] = (uint8_t)d; + if(incount != 4) + continue; + /* process whole block of 4 characters into 3 output bytes */ + if(in[3] == pad64 && in[2] == pad64) { /* A B = = */ + if(o+1 > targsize) + return -1; + target[o] = (in[0]<<2) | ((in[1]&0x30)>>4); + o += 1; + break; /* we are done */ + } else if(in[3] == pad64) { /* A B C = */ + if(o+2 > targsize) + return -1; + target[o] = (in[0]<<2) | ((in[1]&0x30)>>4); + target[o+1]= ((in[1]&0x0f)<<4) | ((in[2]&0x3c)>>2); + o += 2; + break; /* we are done */ + } else { + if(o+3 > targsize) + return -1; + /* write xxxxxxyy yyyyzzzz zzwwwwww */ + target[o] = (in[0]<<2) | ((in[1]&0x30)>>4); + target[o+1]= ((in[1]&0x0f)<<4) | ((in[2]&0x3c)>>2); + target[o+2]= ((in[2]&0x03)<<6) | in[3]; + o += 3; + } + incount = 0; + } + return (int)o; +} + getdns_dict * ipaddr_dict(getdns_context *context, char *ipstr) { @@ -275,6 +335,13 @@ ipaddr_dict(getdns_context *context, char *ipstr) char *p = strchr(ipstr, '@'), *portstr = ""; char *t = strchr(ipstr, '#'), *tls_portstr = ""; char *n = strchr(ipstr, '~'), *tls_namestr = ""; + /* ^[alg:]name:key */ + char *T = strchr(ipstr, '^'), *tsig_name_str = "" + , *tsig_secret_str = "" + , *tsig_algorithm_str = ""; + int tsig_secret_size; + uint8_t tsig_secret_buf[256]; /* 4 times SHA512 */ + getdns_bindata tsig_secret; uint8_t buf[sizeof(struct in6_addr)]; getdns_bindata addr; @@ -297,6 +364,22 @@ ipaddr_dict(getdns_context *context, char *ipstr) *n = 0; tls_namestr = n + 1; } + if (T) { + *T = 0; + tsig_name_str = T + 1; + if ((T = strchr(tsig_name_str, ':'))) { + *T = 0; + tsig_secret_str = T + 1; + if ((T = strchr(tsig_secret_str, ':'))) { + *T = 0; + tsig_algorithm_str = tsig_name_str; + tsig_name_str = tsig_secret_str; + tsig_secret_str = T + 1; + } + } else { + tsig_name_str = ""; + } + } if (strchr(ipstr, ':')) { getdns_dict_util_set_string(r, "address_type", "IPv6"); addr.size = 16; @@ -322,7 +405,19 @@ ipaddr_dict(getdns_context *context, char *ipstr) } if (*scope_id_str) getdns_dict_util_set_string(r, "scope_id", scope_id_str); - + if (*tsig_name_str) + getdns_dict_util_set_string(r, "tsig_name", tsig_name_str); + if (*tsig_algorithm_str) + getdns_dict_util_set_string(r, "tsig_algorithm", tsig_name_str); + if (*tsig_secret_str) { + tsig_secret_size = gqldns_b64_pton( + tsig_secret_str, tsig_secret_buf, sizeof(tsig_secret_buf)); + if (tsig_secret_size > 0) { + tsig_secret.size = tsig_secret_size; + tsig_secret.data = tsig_secret_buf; + getdns_dict_set_bindata(r, "tsig_secret", &tsig_secret); + } + } return r; } From 98dc4018c3fdadd0b20bdeffe5e07ad52efeae98 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Mon, 21 Dec 2015 12:22:59 +0100 Subject: [PATCH 2/4] Setting & getting of tsig info per upstream --- src/context.c | 283 +++++++++++++++++++++++++++++++++++++++----------- src/context.h | 30 ++++++ src/dict.c | 5 +- src/dict.h | 5 + 4 files changed, 264 insertions(+), 59 deletions(-) diff --git a/src/context.c b/src/context.c index eb242be0..87071f7b 100644 --- a/src/context.c +++ b/src/context.c @@ -54,6 +54,7 @@ #include "dnssec.h" #include "stub.h" #include "list.h" +#include "dict.h" #define GETDNS_PORT_ZERO 0 #define GETDNS_PORT_DNS 53 @@ -167,7 +168,7 @@ static inline void canonicalize_dname(uint8_t *dname) { uint8_t *next_label; - while (*dname) { + while (*dname && !(*dname & 0xC0)) { next_label = dname + *dname + 1; dname += 1; while (dname < next_label) { @@ -599,6 +600,64 @@ net_req_query_id_cmp(const void *id1, const void *id2) return (intptr_t)id1 - (intptr_t)id2; } +static getdns_tsig_info tsig_info[] = { + { GETDNS_NO_TSIG, NULL, 0, NULL, 0, 0, 0 } + , { GETDNS_HMAC_MD5 , "hmac-md5.sig-alg.reg.int", 24 + , (uint8_t *)"\x08hmac-md5\x07sig-alg\x03reg\x03int", 26, 10, 16 } + , { GETDNS_NO_TSIG, NULL, 0, NULL, 0, 0, 0 } + , { GETDNS_HMAC_SHA1 , "hmac-sha1" , 9 + , (uint8_t *)"\x09hmac-sha1" , 11, 10, 20 } + , { GETDNS_HMAC_SHA224, "hmac-sha224", 11 + , (uint8_t *)"\x0bhmac-sha224", 13, 14, 28 } + , { GETDNS_HMAC_SHA224, "hmac-sha256", 11 + , (uint8_t *)"\x0bhmac-sha256", 13, 16, 32 } + , { GETDNS_HMAC_SHA224, "hmac-sha384", 11 + , (uint8_t *)"\x0bhmac-sha383", 13, 24, 48 } + , { GETDNS_HMAC_SHA224, "hmac-sha512", 11 + , (uint8_t *)"\x0bhmac-sha512", 13, 32, 64 } + , { GETDNS_HMAC_MD5 , "hmac-md5" , 8 + , (uint8_t *)"\x08hmac-md5" , 10, 10, 16 } +}; + +const getdns_tsig_info *_getdns_get_tsig_info(getdns_tsig_algo tsig_alg) +{ + return tsig_alg > sizeof(tsig_info) - 1 + || tsig_info[tsig_alg].alg == GETDNS_NO_TSIG ? NULL + : &tsig_info[tsig_alg]; +} + +static const getdns_tsig_algo _getdns_get_tsig_algo(getdns_bindata *algo) +{ + getdns_tsig_info *i; + + if (!algo || algo->size == 0) + return GETDNS_NO_TSIG; + + if (algo->data[algo->size-1] != 0) { + /* Unterminated string */ + for (i = tsig_info; i < tsig_info + sizeof(tsig_info); i++) + if (algo->size == i->strlen_name && + strncasecmp((const char *)algo->data, i->name, + i->strlen_name) == 0) + return i->alg; + + } else if (!_getdns_bindata_is_dname(algo)) { + /* Terminated string */ + for (i = tsig_info; i < tsig_info + sizeof(tsig_info); i++) + if (algo->size - 1 == i->strlen_name && + strncasecmp((const char *)algo->data, i->name, + i->strlen_name) == 0) + return i->alg; + + } else { + /* fqdn, canonical_dname_compare is now safe to use! */ + for (i = tsig_info; i < tsig_info + sizeof(tsig_info); i++) + if (canonical_dname_compare(algo->data, i->dname) == 0) + return i->alg; + } + return GETDNS_NO_TSIG; +} + static void upstream_init(getdns_upstream *upstream, getdns_upstreams *parent, struct addrinfo *ai) @@ -635,6 +694,10 @@ upstream_init(getdns_upstream *upstream, upstream->has_prev_client_cookie = 0; upstream->has_server_cookie = 0; + upstream->tsig_alg = GETDNS_NO_TSIG; + upstream->tsig_dname_len = 0; + upstream->tsig_size = 0; + /* Tracking of network requests on this socket */ _getdns_rbtree_init(&upstream->netreq_by_query_id, net_req_query_id_cmp); @@ -1715,15 +1778,21 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, upstreams = upstreams_create( context, count * GETDNS_UPSTREAM_TRANSPORTS); for (i = 0; i < count; i++) { - getdns_dict *dict; + getdns_dict *dict; getdns_bindata *address_type; getdns_bindata *address_data; getdns_bindata *tls_auth_name; struct sockaddr_storage addr; - getdns_bindata *scope_id; + getdns_bindata *scope_id; getdns_upstream *upstream; + getdns_bindata *tsig_alg_name, *tsig_name, *tsig_key; + getdns_tsig_algo tsig_alg; + char tsig_name_str[1024]; + uint8_t tsig_dname_spc[256], *tsig_dname; + size_t tsig_dname_len; + if ((r = getdns_list_get_dict(upstream_list, i, &dict))) goto error; @@ -1760,6 +1829,63 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, eos[scope_id->size] = 0; } + tsig_alg_name = tsig_name = tsig_key = NULL; + tsig_dname = NULL; + tsig_dname_len = 0; + + if (getdns_dict_get_bindata(dict, + "tsig_algorithm", &tsig_alg_name) == GETDNS_RETURN_GOOD) + tsig_alg = _getdns_get_tsig_algo(tsig_alg_name); + else + tsig_alg = GETDNS_HMAC_MD5; + + if (getdns_dict_get_bindata(dict, "tsig_name", &tsig_name)) + tsig_alg = GETDNS_NO_TSIG; /* No name, no TSIG */ + + else if (tsig_name->size == 0) + tsig_alg = GETDNS_NO_TSIG; + + else if (tsig_name->data[tsig_name->size - 1] != 0) { + /* Unterminated string */ + if (tsig_name->size >= sizeof(tsig_name_str) - 1) + tsig_alg = GETDNS_NO_TSIG; + else { + (void) memcpy(tsig_name_str, tsig_name->data + , tsig_name->size); + tsig_name_str[tsig_name->size] = 0; + + tsig_dname_len = sizeof(tsig_dname_spc); + if (gldns_str2wire_dname_buf(tsig_name_str, + tsig_dname_spc, &tsig_dname_len)) + tsig_alg = GETDNS_NO_TSIG; + else + tsig_dname = tsig_dname_spc; + } + } else if (!_getdns_bindata_is_dname(tsig_name)) { + /* Terminated string */ + tsig_dname_len = sizeof(tsig_dname_spc); + if (gldns_str2wire_dname_buf(tsig_name_str, + tsig_dname_spc, &tsig_dname_len)) + tsig_alg = GETDNS_NO_TSIG; + else + tsig_dname = tsig_dname_spc; + + } else if (tsig_name->size > sizeof(tsig_dname_spc)) + tsig_alg = GETDNS_NO_TSIG; + + else { + /* fqdn */ + tsig_dname = memcpy(tsig_dname_spc, tsig_name->data + , tsig_name->size); + tsig_dname_len = tsig_name->size; + } + if (getdns_dict_get_bindata(dict, "tsig_secret", &tsig_key)) + tsig_alg = GETDNS_NO_TSIG; /* No key, no TSIG */ + + /* Don't check TSIG length contraints here. + * Let the upstream decide what is secure enough. + */ + /* Loop to create upstreams as needed*/ for (size_t j = 0; j < GETDNS_UPSTREAM_TRANSPORTS; j++) { uint32_t port; @@ -1798,6 +1924,25 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, upstream->tls_auth_name[tls_auth_name->size] = '\0'; } } + if ((upstream->tsig_alg = tsig_alg)) { + if (tsig_name) { + (void) memcpy(upstream->tsig_dname, + tsig_dname, tsig_dname_len); + upstream->tsig_dname_len = + tsig_dname_len; + } else + upstream->tsig_dname_len = 0; + + if (tsig_key) { + (void) memcpy(upstream->tsig_key, + tsig_key->data, tsig_key->size); + upstream->tsig_size = tsig_key->size; + } else + upstream->tsig_size = 0; + } else { + upstream->tsig_dname_len = 0; + upstream->tsig_size = 0; + } upstreams->count++; freeaddrinfo(ai); } @@ -2542,9 +2687,12 @@ upstream_port(getdns_upstream *upstream) } static getdns_dict* -_get_context_settings(getdns_context* context) { +_get_context_settings(getdns_context* context) +{ getdns_return_t r = GETDNS_RETURN_GOOD; getdns_dict* result = getdns_dict_create_with_context(context); + getdns_list *upstreams; + if (!result) { return NULL; } @@ -2562,34 +2710,8 @@ _get_context_settings(getdns_context* context) { r |= getdns_dict_set_int(result, "append_name", context->append_name); /* list fields */ if (context->suffix) r |= getdns_dict_set_list(result, "suffix", context->suffix); - if (context->upstreams && context->upstreams->count > 0) { - size_t i; - getdns_upstream *upstream; - getdns_list *upstreams = - getdns_list_create_with_context(context); - - for (i = 0; i < context->upstreams->count;) { - size_t j; - getdns_dict *d; - upstream = &context->upstreams->upstreams[i]; - d = sockaddr_dict(context, - (struct sockaddr *)&upstream->addr); - for ( j = 1, i++ - ; j < GETDNS_UPSTREAM_TRANSPORTS && - i < context->upstreams->count - ; j++, i++) { - - upstream = &context->upstreams->upstreams[i]; - if (upstream->transport != GETDNS_TRANSPORT_TLS) - continue; - if (upstream_port(upstream) != getdns_port_array[j]) - continue; - (void) getdns_dict_set_int(d, "tls_port", - (uint32_t) upstream_port(upstream)); - } - r |= _getdns_list_append_dict(upstreams, d); - getdns_dict_destroy(d); - } + + if (!getdns_context_get_upstream_recursive_servers(context, &upstreams)) { r |= getdns_dict_set_list(result, "upstream_recursive_servers", upstreams); getdns_list_destroy(upstreams); @@ -2956,43 +3078,88 @@ getdns_context_get_dnssec_allowed_skew(getdns_context *context, getdns_return_t getdns_context_get_upstream_recursive_servers(getdns_context *context, - getdns_list **upstream_list) { - RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); - RETURN_IF_NULL(upstream_list, GETDNS_RETURN_INVALID_PARAMETER); - *upstream_list = NULL; - if (context->upstreams && context->upstreams->count > 0) { - getdns_return_t r = GETDNS_RETURN_GOOD; - size_t i; - getdns_upstream *upstream; - getdns_list *upstreams = getdns_list_create(); - for (i = 0; i < context->upstreams->count;) { + getdns_list **upstreams_r) +{ + size_t i; + getdns_list *upstreams; + getdns_return_t r; + + if (!context || !upstreams_r) + return GETDNS_RETURN_INVALID_PARAMETER; + + if (!(upstreams = getdns_list_create_with_context(context))) + return GETDNS_RETURN_MEMORY_ERROR; + + if (!context->upstreams || context->upstreams->count == 0) { + *upstreams_r = upstreams; + return GETDNS_RETURN_GOOD; + } + r = GETDNS_RETURN_GOOD; + i = 0; + while (!r && i < context->upstreams->count) { size_t j; getdns_dict *d; - upstream = &context->upstreams->upstreams[i]; - d = sockaddr_dict(context, (struct sockaddr *)&upstream->addr); + getdns_upstream *upstream = &context->upstreams->upstreams[i]; + getdns_bindata bindata; + const getdns_tsig_info *tsig_info; + + if (!(d = + sockaddr_dict(context, (struct sockaddr*)&upstream->addr))) { + r = GETDNS_RETURN_MEMORY_ERROR; + break; + } + if (upstream->tsig_alg) { + tsig_info = _getdns_get_tsig_info(upstream->tsig_alg); + + bindata.data = tsig_info->dname; + bindata.size = tsig_info->dname_len; + if ((r = getdns_dict_set_bindata( + d, "tsig_algorithm", &bindata))) + break; + + if (upstream->tsig_dname_len) { + bindata.data = upstream->tsig_dname; + bindata.size = upstream->tsig_dname_len; + if ((r = getdns_dict_set_bindata( + d, "tsig_name", &bindata))) + break; + } + if (upstream->tsig_size) { + bindata.data = upstream->tsig_key; + bindata.size = upstream->tsig_size; + if ((r = getdns_dict_set_bindata( + d, "tsig_secret", &bindata))) + break; + } + } for ( j = 1, i++ ; j < GETDNS_UPSTREAM_TRANSPORTS && i < context->upstreams->count ; j++, i++) { upstream = &context->upstreams->upstreams[i]; - if (upstream->transport != GETDNS_TRANSPORT_TLS) - continue; - if (upstream_port(upstream) != getdns_port_array[j]) - continue; - (void) getdns_dict_set_int(d, "tls_port", - (uint32_t) upstream_port(upstream)); + + if (upstream->transport == GETDNS_TRANSPORT_UDP && + upstream_port(upstream) != getdns_port_array[j] && + (r = getdns_dict_set_int(d, "port", + (uint32_t)upstream_port(upstream)))) + break; + + if (upstream->transport == GETDNS_TRANSPORT_TLS && + upstream_port(upstream) != getdns_port_array[j] && + (r = getdns_dict_set_int(d, "tls_port", + (uint32_t)upstream_port(upstream)))) + break; } - r |= _getdns_list_append_dict(upstreams, d); + if (!r) + r = _getdns_list_append_dict(upstreams, d); getdns_dict_destroy(d); } - if (r != GETDNS_RETURN_GOOD) { - getdns_list_destroy(upstreams); - return GETDNS_RETURN_MEMORY_ERROR; - } - *upstream_list = upstreams; - } - return GETDNS_RETURN_GOOD; + if (r) + getdns_list_destroy(upstreams); + else + *upstreams_r = upstreams; + return r; } getdns_return_t diff --git a/src/context.h b/src/context.h index 1e489d2d..9df5369a 100644 --- a/src/context.h +++ b/src/context.h @@ -79,6 +79,29 @@ typedef enum getdns_tls_hs_state { GETDNS_HS_FAILED } getdns_tls_hs_state_t; +typedef enum getdns_tsig_algo { + GETDNS_NO_TSIG = 0, /* Do not use tsig */ + GETDNS_HMAC_MD5 = 1, /* 128 bits */ + GETDNS_GSS_TSIG = 2, /* Not supported */ + GETDNS_HMAC_SHA1 = 3, /* 160 bits */ + GETDNS_HMAC_SHA224 = 4, + GETDNS_HMAC_SHA256 = 5, + GETDNS_HMAC_SHA384 = 6, + GETDNS_HMAC_SHA512 = 7 +} getdns_tsig_algo; + +typedef struct getdns_tsig_info { + getdns_tsig_algo alg; + const char *name; + size_t strlen_name; + const uint8_t *dname; + size_t dname_len; + size_t min_size; /* in # octets */ + size_t max_size; /* Actual size in # octets */ +} getdns_tsig_info; + +const getdns_tsig_info *_getdns_get_tsig_info(getdns_tsig_algo tsig_alg); + typedef struct getdns_upstream { /* backpointer to containing upstreams structure */ struct getdns_upstreams *upstreams; @@ -120,6 +143,13 @@ typedef struct getdns_upstream { unsigned has_server_cookie : 1; unsigned server_cookie_len : 5; + /* TSIG */ + uint8_t tsig_dname[256]; + size_t tsig_dname_len; + size_t tsig_size; + uint8_t tsig_key[256]; + getdns_tsig_algo tsig_alg; + } getdns_upstream; typedef struct getdns_upstreams { diff --git a/src/dict.c b/src/dict.c index 822d5deb..5fa1c67c 100644 --- a/src/dict.c +++ b/src/dict.c @@ -656,12 +656,15 @@ getdns_indent(size_t indent) return spaces + 80 - (indent < 80 ? indent : 0); } /* getdns_indent */ -static int +int _getdns_bindata_is_dname(getdns_bindata *bindata) { size_t i = 0, n_labels = 0; while (i < bindata->size && bindata->data[i]) { + if (bindata->data[i] & 0xC0) /* Compression pointer! */ + return 0; + i += ((size_t)bindata->data[i]) + 1; n_labels++; } diff --git a/src/dict.h b/src/dict.h index c10a2bd8..90372dfb 100644 --- a/src/dict.h +++ b/src/dict.h @@ -71,6 +71,11 @@ getdns_return_t _getdns_dict_find( getdns_return_t _getdns_dict_find_and_add( getdns_dict *dict, const char *key, getdns_item **item); +/* Return 1 (true) if bindata can be interpreted as an + * uncompressed dname. + */ +int _getdns_bindata_is_dname(getdns_bindata *bindata); + #endif /* dict.h */ From 6c1e00fc3f50bbb407cb4efaff3cc49f2a786e07 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Mon, 21 Dec 2015 22:11:16 +0100 Subject: [PATCH 3/4] Send TSIG --- configure.ac | 2 +- src/gldns/gbuffer.h | 39 ++++++++++++++ src/request-internal.c | 116 ++++++++++++++++++++++++++++++++++++++++- src/stub.c | 6 +-- src/types-internal.h | 3 ++ 5 files changed, 160 insertions(+), 6 deletions(-) diff --git a/configure.ac b/configure.ac index 5e6d6632..eecff2b2 100644 --- a/configure.ac +++ b/configure.ac @@ -206,7 +206,7 @@ else fi AC_CHECK_HEADERS([openssl/conf.h],,, [AC_INCLUDES_DEFAULT]) AC_CHECK_HEADERS([openssl/engine.h],,, [AC_INCLUDES_DEFAULT]) -AC_CHECK_FUNCS([OPENSSL_config EVP_sha1 EVP_sha256 EVP_sha512 FIPS_mode]) +AC_CHECK_FUNCS([OPENSSL_config EVP_md5 EVP_sha1 EVP_sha224 EVP_sha256 EVP_sha384 EVP_sha512 FIPS_mode]) AC_CHECK_DECLS([SSL_COMP_get_compression_methods,sk_SSL_COMP_pop_free,SSL_CTX_set_ecdh_auto], [], [], [ AC_INCLUDES_DEFAULT #ifdef HAVE_OPENSSL_ERR_H diff --git a/src/gldns/gbuffer.h b/src/gldns/gbuffer.h index 56e19c78..333e1176 100644 --- a/src/gldns/gbuffer.h +++ b/src/gldns/gbuffer.h @@ -87,6 +87,19 @@ gldns_write_uint32(void *dst, uint32_t data) } +INLINE void +gldns_write_uint48(void *dst, uint64_t data) +{ + uint8_t *p = (uint8_t *) dst; + p[0] = (uint8_t) ((data >> 40) & 0xff); + p[1] = (uint8_t) ((data >> 32) & 0xff); + p[2] = (uint8_t) ((data >> 24) & 0xff); + p[3] = (uint8_t) ((data >> 16) & 0xff); + p[4] = (uint8_t) ((data >> 8) & 0xff); + p[5] = (uint8_t) (data & 0xff); +} + + /** * \file gbuffer.h * @@ -534,6 +547,20 @@ gldns_buffer_write_u32_at(gldns_buffer *buffer, size_t at, uint32_t data) gldns_write_uint32(buffer->_data + at, data); } +/** + * writes the given 6 byte integer at the given position in the buffer + * \param[in] buffer the buffer + * \param[in] at the position in the buffer + * \param[in] data the (lower) 48 bits to write + */ +INLINE void +gldns_buffer_write_u48_at(gldns_buffer *buffer, size_t at, uint64_t data) +{ + if (buffer->_fixed && at + 6 > buffer->_limit) return; + assert(gldns_buffer_available_at(buffer, at, 6)); + gldns_write_uint48(buffer->_data + at, data); +} + /** * writes the given 4 byte integer at the current position in the buffer * \param[in] buffer the buffer @@ -546,6 +573,18 @@ gldns_buffer_write_u32(gldns_buffer *buffer, uint32_t data) buffer->_position += sizeof(data); } +/** + * writes the given 6 byte integer at the current position in the buffer + * \param[in] buffer the buffer + * \param[in] data the 48 bits to write + */ +INLINE void +gldns_buffer_write_u48(gldns_buffer *buffer, uint64_t data) +{ + gldns_buffer_write_u48_at(buffer, buffer->_position, data); + buffer->_position += 6; +} + /** * copies count bytes of data at the given position to the given data-array * \param[in] buffer the buffer diff --git a/src/request-internal.c b/src/request-internal.c index 8d847ab6..f873c96e 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -41,6 +41,26 @@ #include "gldns/gbuffer.h" #include "gldns/pkthdr.h" #include "dict.h" +#include "debug.h" + +/* MAXIMUM_TSIG_SPACE = TSIG name (dname) : 256 + * TSIG type (uint16_t) : 2 + * TSIG class (uint16_t) : 2 + * TSIG TTL (uint32_t) : 4 + * RdLen (uint16_t) : 2 + * Algorithm name (dname) : 256 + * Time Signed (uint48_t) : 6 + * Fudge (uint16_t) : 2 + * Mac Size (uint16_t) : 2 + * Mac (variable) : EVP_MAX_MD_SIZE + * Original Id (uint16_t) : 2 + * Error (uint16_t) : 2 + * Other Len (uint16_t) : 2 + * Other Data (nothing) : 0 + * ---- + + * 538 + EVP_MAX_MD_SIZE + */ +#define MAXIMUM_TSIG_SPACE (538 + EVP_MAX_MD_SIZE) getdns_dict dnssec_ok_checking_disabled_spc = { { RBTREE_NULL, 0, (int (*)(const void *, const void *)) strcmp }, @@ -249,7 +269,7 @@ _getdns_network_req_add_upstream_option(getdns_network_req * req, uint16_t code, /* no overflow allowed for OPT size either (maybe this is overkill given the above check?) */ - oldlen = gldns_read_uint16(req->opt + 9); + oldlen = gldns_read_uint16(req->opt + 9); newlen = oldlen + 4 + sz; if (newlen > UINT16_MAX) return GETDNS_RETURN_GENERIC_ERROR; @@ -277,6 +297,98 @@ _getdns_network_req_add_upstream_option(getdns_network_req * req, uint16_t code, return GETDNS_RETURN_GOOD; } +size_t +_getdns_network_req_add_tsig(getdns_network_req *req) +{ + getdns_upstream *upstream = req->upstream; + gldns_buffer gbuf; + uint16_t arcount; + const getdns_tsig_info *tsig_info; + uint8_t md_buf[EVP_MAX_MD_SIZE]; + unsigned int md_len = EVP_MAX_MD_SIZE; + const EVP_MD *digester; + + if (upstream->tsig_alg == GETDNS_NO_TSIG || !upstream->tsig_dname_len) + return req->response - req->query; + + arcount = gldns_read_uint16(req->query + 10); + +#if defined(STUB_DEBUG) && STUB_DEBUG + /* TSIG should not have been written yet. */ + if (req->opt) { + assert(arcount == 1); + assert(req->opt + 11 + gldns_read_uint16(req->opt + 9) + == req->response); + } else + assert(arcount == 0); +#endif + tsig_info = _getdns_get_tsig_info(upstream->tsig_alg); + + gldns_buffer_init_frm_data(&gbuf, req->response, MAXIMUM_TSIG_SPACE); + gldns_buffer_write(&gbuf, + upstream->tsig_dname, upstream->tsig_dname_len); /* Name */ + gldns_buffer_write_u16(&gbuf, GETDNS_RRCLASS_ANY); /* Class */ + gldns_buffer_write_u32(&gbuf, 0); /* TTL */ + gldns_buffer_write(&gbuf, + tsig_info->dname, tsig_info->dname_len); /* Algorithm Name */ + gldns_buffer_write_u48(&gbuf, time(NULL)); /* Time Signed */ + gldns_buffer_write_u16(&gbuf, 300); /* Fudge */ + gldns_buffer_write_u16(&gbuf, 0); /* Error */ + gldns_buffer_write_u16(&gbuf, 0); /* Other len */ + + switch (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 req->response - req->query; + } + + (void) HMAC(digester, upstream->tsig_key, upstream->tsig_size, + (void *)req->query, gldns_buffer_current(&gbuf) - req->query, + md_buf, &md_len); + + gldns_buffer_rewind(&gbuf); + gldns_buffer_write(&gbuf, + upstream->tsig_dname, upstream->tsig_dname_len); /* Name */ + gldns_buffer_write_u16(&gbuf, GETDNS_RRTYPE_TSIG); /* Type*/ + gldns_buffer_write_u16(&gbuf, GETDNS_RRCLASS_ANY); /* Class */ + gldns_buffer_write_u32(&gbuf, 0); /* TTL */ + gldns_buffer_write_u16(&gbuf, + tsig_info->dname_len + 10 + md_len + 6); /* RdLen */ + gldns_buffer_write(&gbuf, + tsig_info->dname, tsig_info->dname_len); /* Algorithm Name */ + gldns_buffer_write_u48(&gbuf, time(NULL)); /* Time Signed */ + gldns_buffer_write_u16(&gbuf, 300); /* Fudge */ + gldns_buffer_write_u16(&gbuf, md_len); /* MAC Size */ + gldns_buffer_write(&gbuf, md_buf, md_len); /* MAC*/ + gldns_buffer_write(&gbuf, req->query, 2); /* Original ID */ + gldns_buffer_write_u16(&gbuf, 0); /* Error */ + gldns_buffer_write_u16(&gbuf, 0); /* Other len */ + + if (gldns_buffer_position(&gbuf) > gldns_buffer_limit(&gbuf)) + return req->response - req->query; + + DEBUG_STUB("Sending with TSIG, mac length: %d\n", (int)md_len); + gldns_write_uint16(req->query + 10, arcount + 1); + req->response = gldns_buffer_current(&gbuf); + return req->response - req->query; +} + void _getdns_dns_req_free(getdns_dns_req * req) { @@ -439,7 +551,7 @@ _getdns_dns_req_new(getdns_context *context, getdns_eventloop *loop, + strlen(name) + 1 + 4 /* dname always smaller then strlen(name) + 1 */ + 12 + opt_options_size /* space needed for OPT (if needed) */ + MAXIMUM_UPSTREAM_OPTION_SPACE - /* TODO: TSIG */ + + MAXIMUM_TSIG_SPACE + 7) / 8 * 8; } max_response_sz = (( edns_maximum_udp_payload_size != -1 diff --git a/src/stub.c b/src/stub.c index dc87418e..dfabccec 100644 --- a/src/stub.c +++ b/src/stub.c @@ -737,7 +737,7 @@ stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq) if (attach_edns_client_subnet_private(netreq)) return STUB_OUT_OF_OPTIONS; } - pkt_len = netreq->response - netreq->query; + pkt_len = _getdns_network_req_add_tsig(netreq); /* We have an initialized packet buffer. * Lets see how much of it we can write */ @@ -1212,7 +1212,7 @@ stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp, } } - pkt_len = netreq->response - netreq->query; + pkt_len = _getdns_network_req_add_tsig(netreq); /* We have an initialized packet buffer. * Lets see how much of it we can write */ @@ -1337,7 +1337,7 @@ stub_udp_write_cb(void *userarg) if (attach_edns_client_subnet_private(netreq)) return; /* too many upstream options */ } - pkt_len = netreq->response - netreq->query; + pkt_len = _getdns_network_req_add_tsig(netreq); if ((ssize_t)pkt_len != sendto(netreq->fd, netreq->query, pkt_len, 0, (struct sockaddr *)&netreq->upstream->addr, netreq->upstream->addr_len)) { diff --git a/src/types-internal.h b/src/types-internal.h index ef5a118e..7d3eb190 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -379,5 +379,8 @@ getdns_return_t _getdns_network_req_add_upstream_option(getdns_network_req * req uint16_t code, uint16_t sz, const void* data); 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); + #endif /* types-internal.h */ From 8a8a017fc5322411fcc83283d083f630560f65b5 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Tue, 22 Dec 2015 01:03:31 +0100 Subject: [PATCH 4/4] 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;