Implement DoT support for nsupdate

Implement DNS-over-TLS support for nsupdate. Use DiG's DoT
implementation as a model for the newly added features.
This commit is contained in:
Aram Sargsyan
2022-09-21 08:20:22 +00:00
parent 73cc9a3232
commit 13000c28c2
3 changed files with 210 additions and 29 deletions

View File

@@ -46,6 +46,7 @@
#include <isc/stdio.h>
#include <isc/string.h>
#include <isc/task.h>
#include <isc/tls.h>
#include <isc/types.h>
#include <isc/util.h>
@@ -68,6 +69,7 @@
#include <dns/rdatatype.h>
#include <dns/request.h>
#include <dns/tkey.h>
#include <dns/transport.h>
#include <dns/tsig.h>
#include <dst/dst.h>
@@ -120,6 +122,7 @@ static bool memdebugging = false;
static bool have_ipv4 = false;
static bool have_ipv6 = false;
static bool is_dst_up = false;
static bool use_tls = false;
static bool usevc = false;
static bool usegsstsig = false;
static bool use_win2k_gsstsig = false;
@@ -147,6 +150,14 @@ static dns_tsigkey_t *tsigkey = NULL;
static dst_key_t *sig0key = NULL;
static isc_sockaddr_t *servers = NULL;
static isc_sockaddr_t *primary_servers = NULL;
static dns_transport_list_t *transport_list = NULL;
static dns_transport_t *transport = NULL;
static isc_tlsctx_cache_t *tls_ctx_cache = NULL;
static char *tls_hostname = NULL;
static char *tls_client_key_file = NULL;
static char *tls_client_cert_file = NULL;
static char *tls_ca_file = NULL;
static bool tls_always_verify_remote = true;
static bool default_servers = true;
static int ns_inuse = 0;
static int primary_inuse = 0;
@@ -793,6 +804,19 @@ set_source_ports(dns_dispatchmgr_t *manager) {
isc_portset_destroy(gmctx, &v6portset);
}
static isc_result_t
create_name(const char *str, char *namedata, size_t len, dns_name_t *name) {
isc_buffer_t namesrc, namebuf;
dns_name_init(name, NULL);
isc_buffer_constinit(&namesrc, str, strlen(str));
isc_buffer_add(&namesrc, strlen(str));
isc_buffer_init(&namebuf, namedata, len);
return (dns_name_fromtext(name, &namesrc, dns_rootname,
DNS_NAME_DOWNCASE, &namebuf));
}
static void
setup_system(void) {
isc_result_t result;
@@ -800,6 +824,8 @@ setup_system(void) {
isc_sockaddrlist_t *nslist;
isc_logconfig_t *logconfig = NULL;
irs_resconf_t *resconf = NULL;
dns_name_t tlsname;
char namedata[DNS_NAME_FORMATSIZE + 1];
ddebug("setup_system()");
@@ -936,6 +962,31 @@ setup_system(void) {
&dispatchv4);
check_result(result, "dns_dispatch_createudp (v4)");
}
transport_list = dns_transport_list_new(gmctx);
tls_ctx_cache = isc_tlsctx_cache_new(gmctx);
if (tls_client_key_file == NULL) {
result = create_name("tls-non-auth-client", namedata,
sizeof(namedata), &tlsname);
check_result(result, "create_name (tls-non-auth-client)");
transport = dns_transport_new(&tlsname, DNS_TRANSPORT_TLS,
transport_list);
dns_transport_set_tlsname(transport, "tls-non-auth-client");
} else {
result = create_name("tls-auth-client", namedata,
sizeof(namedata), &tlsname);
check_result(result, "create_name (tls-auth-client)");
transport = dns_transport_new(&tlsname, DNS_TRANSPORT_TLS,
transport_list);
dns_transport_set_tlsname(transport, "tls-auth-client");
dns_transport_set_keyfile(transport, tls_client_key_file);
dns_transport_set_certfile(transport, tls_client_cert_file);
}
dns_transport_set_cafile(transport, tls_ca_file);
dns_transport_set_remote_hostname(transport, tls_hostname);
dns_transport_set_always_verify_remote(transport,
tls_always_verify_remote);
result = dns_requestmgr_create(gmctx, taskmgr, dispatchmgr, dispatchv4,
dispatchv6, &requestmgr);
@@ -972,7 +1023,7 @@ get_addresses(char *host, in_port_t port, isc_sockaddr_t *sockaddr,
return (count);
}
#define PARSE_ARGS_FMT "46C:dDghilL:Mok:p:Pr:R:t:Tu:vVy:"
#define PARSE_ARGS_FMT "46A:C:dDE:ghH:iK:lL:MoOk:p:Pr:R:St:Tu:vVy:"
static void
pre_parse_args(int argc, char **argv) {
@@ -1015,7 +1066,9 @@ pre_parse_args(int argc, char **argv) {
fprintf(stderr, "usage: nsupdate [-CdDi] [-L level] "
"[-l] [-g | -o | -y keyname:secret "
"| -k keyfile] [-p port] "
"[-v] [-V] [-P] [-T] [-4 | -6] "
"[ -S [-K tlskeyfile] [-E tlscertfile] "
"[-A tlscafile] [-H tlshostname] "
"[-O] ] [-v] [-V] [-P] [-T] [-4 | -6] "
"[filename]\n");
exit(1);
@@ -1087,6 +1140,11 @@ parse_args(int argc, char **argv) {
fatal("can't find IPv6 networking");
}
break;
case 'A':
use_tls = true;
usevc = true;
tls_ca_file = isc_commandline_argument;
break;
case 'C':
resolvconf = isc_commandline_argument;
break;
@@ -1097,12 +1155,27 @@ parse_args(int argc, char **argv) {
debugging = true;
ddebugging = true;
break;
case 'E':
use_tls = true;
usevc = true;
tls_client_cert_file = isc_commandline_argument;
break;
case 'H':
use_tls = true;
usevc = true;
tls_hostname = isc_commandline_argument;
break;
case 'M':
break;
case 'i':
force_interactive = true;
interactive = true;
break;
case 'K':
use_tls = true;
usevc = true;
tls_client_key_file = isc_commandline_argument;
break;
case 'l':
local_only = true;
break;
@@ -1135,6 +1208,11 @@ parse_args(int argc, char **argv) {
usegsstsig = true;
use_win2k_gsstsig = true;
break;
case 'O':
use_tls = true;
usevc = true;
tls_always_verify_remote = false;
break;
case 'p':
result = isc_parse_uint16(&dnsport,
isc_commandline_argument, 10);
@@ -1146,6 +1224,10 @@ parse_args(int argc, char **argv) {
exit(1);
}
break;
case 'S':
use_tls = true;
usevc = true;
break;
case 't':
result = isc_parse_uint32(&timeout,
isc_commandline_argument, 10);
@@ -1211,6 +1293,24 @@ parse_args(int argc, char **argv) {
}
#endif /* HAVE_GSSAPI */
if (use_tls) {
if ((tls_client_key_file == NULL) !=
(tls_client_cert_file == NULL)) {
fprintf(stderr,
"%s: cannot specify the -K option without"
"the -E option, and vice versa.\n",
argv[0]);
exit(1);
}
if (tls_ca_file != NULL && tls_always_verify_remote == false) {
fprintf(stderr,
"%s: cannot specify the -A option in "
"conjuction with the -O option.\n",
argv[0]);
exit(1);
}
}
if (argv[isc_commandline_index] != NULL) {
if (strcmp(argv[isc_commandline_index], "-") == 0) {
input = stdin;
@@ -2448,8 +2548,10 @@ static void
send_update(dns_name_t *zone, isc_sockaddr_t *primary) {
isc_result_t result;
dns_request_t *request = NULL;
unsigned int options = DNS_REQUESTOPT_CASE;
isc_sockaddr_t *srcaddr;
unsigned int options = DNS_REQUESTOPT_CASE;
dns_transport_t *req_transport = NULL;
isc_tlsctx_cache_t *req_tls_ctx_cache = NULL;
ddebug("send_update()");
@@ -2457,7 +2559,12 @@ send_update(dns_name_t *zone, isc_sockaddr_t *primary) {
if (usevc) {
options |= DNS_REQUESTOPT_TCP;
if (use_tls) {
req_transport = transport;
req_tls_ctx_cache = tls_ctx_cache;
}
}
if (tsigkey == NULL && sig0key != NULL) {
result = dns_message_setsig0key(updatemsg, sig0key);
check_result(result, "dns_message_setsig0key");
@@ -2480,10 +2587,10 @@ send_update(dns_name_t *zone, isc_sockaddr_t *primary) {
updatemsg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS;
}
result = dns_request_create(requestmgr, updatemsg, srcaddr, primary,
NULL, NULL, -1, options, tsigkey, timeout,
udp_timeout, udp_retries, global_task,
update_completed, NULL, &request);
result = dns_request_create(
requestmgr, updatemsg, srcaddr, primary, req_transport,
req_tls_ctx_cache, -1, options, tsigkey, timeout, udp_timeout,
udp_retries, global_task, update_completed, NULL, &request);
check_result(result, "dns_request_create");
if (debugging) {
@@ -2574,6 +2681,10 @@ recvsoa(isc_task_t *task, isc_event_t *event) {
result = dns_request_getresponse(request, rcvmsg,
DNS_MESSAGEPARSE_PRESERVEORDER);
if (result == DNS_R_TSIGERRORSET && servers != NULL) {
unsigned int options = DNS_REQUESTOPT_CASE;
dns_transport_t *req_transport = NULL;
isc_tlsctx_cache_t *req_tls_ctx_cache = NULL;
dns_message_detach(&rcvmsg);
ddebug("Destroying request [%p]", request);
dns_request_destroy(&request);
@@ -2583,6 +2694,14 @@ recvsoa(isc_task_t *task, isc_event_t *event) {
dns_message_renderreset(soaquery);
ddebug("retrying soa request without TSIG");
if (usevc) {
options |= DNS_REQUESTOPT_TCP;
if (!default_servers && use_tls) {
req_transport = transport;
req_tls_ctx_cache = tls_ctx_cache;
}
}
if (isc_sockaddr_pf(addr) == AF_INET6) {
srcaddr = localaddr6;
} else {
@@ -2590,9 +2709,10 @@ recvsoa(isc_task_t *task, isc_event_t *event) {
}
result = dns_request_create(
requestmgr, soaquery, srcaddr, addr, NULL, NULL, -1, 0,
NULL, FIND_TIMEOUT * 20, FIND_TIMEOUT, 3, global_task,
recvsoa, reqinfo, &request);
requestmgr, soaquery, srcaddr, addr, req_transport,
req_tls_ctx_cache, -1, options, NULL, FIND_TIMEOUT * 20,
FIND_TIMEOUT, 3, global_task, recvsoa, reqinfo,
&request);
check_result(result, "dns_request_create");
requests++;
return;
@@ -2797,6 +2917,17 @@ sendrequest(isc_sockaddr_t *destaddr, dns_message_t *msg,
isc_result_t result;
nsu_requestinfo_t *reqinfo;
isc_sockaddr_t *srcaddr;
unsigned int options = DNS_REQUESTOPT_CASE;
dns_transport_t *req_transport = NULL;
isc_tlsctx_cache_t *req_tls_ctx_cache = NULL;
if (usevc) {
options |= DNS_REQUESTOPT_TCP;
if (!default_servers && use_tls) {
req_transport = transport;
req_tls_ctx_cache = tls_ctx_cache;
}
}
reqinfo = isc_mem_get(gmctx, sizeof(nsu_requestinfo_t));
reqinfo->msg = msg;
@@ -2808,10 +2939,11 @@ sendrequest(isc_sockaddr_t *destaddr, dns_message_t *msg,
srcaddr = localaddr4;
}
result = dns_request_create(
requestmgr, msg, srcaddr, destaddr, NULL, NULL, -1, 0,
default_servers ? NULL : tsigkey, FIND_TIMEOUT * 20,
FIND_TIMEOUT, 3, global_task, recvsoa, reqinfo, request);
result = dns_request_create(requestmgr, msg, srcaddr, destaddr,
req_transport, req_tls_ctx_cache, -1,
options, default_servers ? NULL : tsigkey,
FIND_TIMEOUT * 20, FIND_TIMEOUT, 3,
global_task, recvsoa, reqinfo, request);
check_result(result, "dns_request_create");
requests++;
}
@@ -2991,8 +3123,15 @@ send_gssrequest(isc_sockaddr_t *destaddr, dns_message_t *msg,
dns_request_t **request, gss_ctx_id_t context) {
isc_result_t result;
nsu_gssinfo_t *reqinfo;
unsigned int options = 0;
isc_sockaddr_t *srcaddr;
unsigned int options = DNS_REQUESTOPT_CASE | DNS_REQUESTOPT_TCP;
dns_transport_t *req_transport = NULL;
isc_tlsctx_cache_t *req_tls_ctx_cache = NULL;
if (!default_servers && use_tls) {
req_transport = transport;
req_tls_ctx_cache = tls_ctx_cache;
}
debug("send_gssrequest");
REQUIRE(destaddr != NULL);
@@ -3002,18 +3141,16 @@ send_gssrequest(isc_sockaddr_t *destaddr, dns_message_t *msg,
reqinfo->addr = destaddr;
reqinfo->context = context;
options |= DNS_REQUESTOPT_TCP;
if (isc_sockaddr_pf(destaddr) == AF_INET6) {
srcaddr = localaddr6;
} else {
srcaddr = localaddr4;
}
result = dns_request_create(requestmgr, msg, srcaddr, destaddr, NULL,
NULL, -1, options, tsigkey,
FIND_TIMEOUT * 20, FIND_TIMEOUT, 3,
global_task, recvgss, reqinfo, request);
result = dns_request_create(
requestmgr, msg, srcaddr, destaddr, req_transport,
req_tls_ctx_cache, -1, options, tsigkey, FIND_TIMEOUT * 20,
FIND_TIMEOUT, 3, global_task, recvgss, reqinfo, request);
check_result(result, "dns_request_create");
if (debugging) {
show_message(stdout, msg, "Outgoing update query:");
@@ -3271,6 +3408,14 @@ static void
cleanup(void) {
ddebug("cleanup()");
if (tls_ctx_cache != NULL) {
isc_tlsctx_cache_detach(&tls_ctx_cache);
}
if (transport_list != NULL) {
dns_transport_list_detach(&transport_list);
}
LOCK(&answer_lock);
if (answer != NULL) {
dns_message_detach(&answer);

View File

@@ -62,9 +62,13 @@ dns_transport_get_tls_versions(const dns_transport_t *transport);
bool
dns_transport_get_prefer_server_ciphers(const dns_transport_t *transport,
bool *preferp);
bool
dns_transport_get_always_verify_remote(dns_transport_t *transport);
/*%<
* Getter functions: return the type, cert file, key file, CA file,
* hostname, HTTP endpoint, or HTTP mode (GET or POST) for 'transport'.
* hostname, HTTP endpoint, HTTP mode (GET or POST), ciphers, TLS name,
* TLS version, server ciphers preference mode, and always enabling
* authentication mode for 'transport'.
*
* dns_transport_get_prefer_server_ciphers() returns 'true' is value
* was set, 'false' otherwise. The actual value is returned via
@@ -80,6 +84,13 @@ dns_transport_get_tlsctx(dns_transport_t *transport, const isc_sockaddr_t *peer,
* Get the transport's TLS Context and the TLS Client Session Cache associated
* with it.
*
* When neither the TLS hostname, nor the TLS certificates authorities (CA)
* file are set for the 'transport', then Opportunistic TLS (no authentication
* of the remote peer) will be used, unless the 'always_verify_remote' mode is
* enabled on the 'transport', in which case the remote peer will be
* authenticated by its IP address using the system's default certificates
* authorities store.
*
* Requires:
*\li 'transport' is a valid, 'DNS_TRANSPORT_TLS' type transport.
*\li 'peer' is not NULL.
@@ -113,9 +124,14 @@ dns_transport_set_tls_versions(dns_transport_t *transport,
void
dns_transport_set_prefer_server_ciphers(dns_transport_t *transport,
const bool prefer);
void
dns_transport_set_always_verify_remote(dns_transport_t *transport,
const bool always_verify_remote);
/*%<
* Setter functions: set the type, cert file, key file, CA file,
* hostname, HTTP endpoint, or HTTP mode (GET or POST) for 'transport'.
* hostname, HTTP endpoint, HTTP mode (GET or POST), ciphers, TLS name,
* TLS version, server ciphers preference mode, and always enabling
* authentication mode for 'transport'.
*
* Requires:
*\li 'transport' is valid.

View File

@@ -56,6 +56,7 @@ struct dns_transport {
char *ciphers;
uint32_t protocol_versions;
ternary_t prefer_server_ciphers;
bool always_verify_remote;
} tls;
struct {
char *endpoint;
@@ -334,6 +335,25 @@ dns_transport_get_prefer_server_ciphers(const dns_transport_t *transport,
return false;
}
void
dns_transport_set_always_verify_remote(dns_transport_t *transport,
const bool always_verify_remote) {
REQUIRE(VALID_TRANSPORT(transport));
REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
transport->type == DNS_TRANSPORT_HTTP);
transport->tls.always_verify_remote = always_verify_remote;
}
bool
dns_transport_get_always_verify_remote(dns_transport_t *transport) {
REQUIRE(VALID_TRANSPORT(transport));
REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
transport->type == DNS_TRANSPORT_HTTP);
return transport->tls.always_verify_remote;
}
isc_result_t
dns_transport_get_tlsctx(dns_transport_t *transport, const isc_sockaddr_t *peer,
isc_tlsctx_cache_t *tlsctx_cache, isc_mem_t *mctx,
@@ -378,6 +398,8 @@ dns_transport_get_tlsctx(dns_transport_t *transport, const isc_sockaddr_t *peer,
const char *ca_file = dns_transport_get_cafile(transport);
const char *cert_file = dns_transport_get_certfile(transport);
const char *key_file = dns_transport_get_keyfile(transport);
const bool always_verify_remote =
dns_transport_get_always_verify_remote(transport);
char peer_addr_str[INET6_ADDRSTRLEN] = { 0 };
isc_netaddr_t peer_netaddr = { 0 };
bool hostname_ignore_subject;
@@ -406,7 +428,8 @@ dns_transport_get_tlsctx(dns_transport_t *transport, const isc_sockaddr_t *peer,
prefer_server_ciphers);
}
if (hostname != NULL || ca_file != NULL) {
if (always_verify_remote || hostname != NULL || ca_file != NULL)
{
/*
* The situation when 'found_store != NULL' while
* 'found == NULL' may occur as there is a one-to-many
@@ -433,12 +456,9 @@ dns_transport_get_tlsctx(dns_transport_t *transport, const isc_sockaddr_t *peer,
INSIST(store != NULL);
if (hostname == NULL) {
/*
* If CA bundle file is specified, but
* hostname is not, then use the peer
* IP address for validation, just like
* dig does.
* If hostname is not specified, then use the
* peer IP address for validation.
*/
INSIST(ca_file != NULL);
isc_netaddr_fromsockaddr(&peer_netaddr, peer);
isc_netaddr_format(&peer_netaddr, peer_addr_str,
sizeof(peer_addr_str));