Merge branch 'features/tsig' into develop

This commit is contained in:
Willem Toorop 2015-12-22 01:08:25 +01:00
commit ee2a1fbfe6
11 changed files with 711 additions and 67 deletions

View File

@ -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

View File

@ -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) {
@ -598,6 +599,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)
@ -634,6 +693,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);
@ -1701,15 +1764,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;
@ -1746,6 +1815,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;
@ -1783,6 +1909,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);
}
@ -2527,9 +2672,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;
}
@ -2547,34 +2695,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);
@ -2935,43 +3057,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

View File

@ -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 {

View File

@ -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++;
}
@ -992,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 ||

View File

@ -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 */

View File

@ -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

View File

@ -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 },
@ -114,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;
@ -250,7 +271,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;
@ -278,6 +299,267 @@ _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;
/* 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;
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);
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)
{
@ -440,7 +722,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

View File

@ -728,7 +728,7 @@ stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq)
netreq->keepalive_sent = 1;
}
}
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
*/
@ -1207,7 +1207,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 */
@ -1332,7 +1332,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)) {

View File

@ -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;
}

View File

@ -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;
@ -381,5 +389,10 @@ 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);
void _getdns_network_validate_tsig(getdns_network_req *req);
#endif
/* types-internal.h */

View File

@ -819,6 +819,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++;
@ -837,6 +840,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)))
@ -864,7 +869,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;