Add support for Strict/Mutual TLS into BIND

This commit adds support for Strict/Mutual TLS into BIND. It does so
by implementing the backing code for 'hostname' and 'ca-file' options
of the 'tls' statement. The commit also updates the documentation
accordingly.
This commit is contained in:
Artem Boldariev 2022-01-26 15:26:08 +02:00
parent 05091f0095
commit 6c05fb09c3
5 changed files with 166 additions and 13 deletions

View file

@ -11106,8 +11106,8 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config,
const cfg_obj_t *http_server = NULL;
in_port_t port = 0;
isc_dscp_t dscp = -1;
const char *key = NULL, *cert = NULL, *dhparam_file = NULL,
*ciphers = NULL;
const char *key = NULL, *cert = NULL, *ca_file = NULL,
*dhparam_file = NULL, *ciphers = NULL;
bool tls_prefer_server_ciphers = false,
tls_prefer_server_ciphers_set = false;
bool tls_session_tickets = false, tls_session_tickets_set = false;
@ -11132,7 +11132,7 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config,
do_tls = true;
} else {
const cfg_obj_t *keyobj = NULL, *certobj = NULL,
*dhparam_obj = NULL;
*ca_obj = NULL, *dhparam_obj = NULL;
const cfg_obj_t *tlsmap = NULL;
const cfg_obj_t *tls_proto_list = NULL;
const cfg_obj_t *ciphers_obj = NULL;
@ -11155,6 +11155,11 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config,
CHECK(cfg_map_get(tlsmap, "cert-file", &certobj));
cert = cfg_obj_asstring(certobj);
if (cfg_map_get(tlsmap, "ca-file", &ca_obj) ==
ISC_R_SUCCESS) {
ca_file = cfg_obj_asstring(ca_obj);
}
if (cfg_map_get(tlsmap, "protocols", &tls_proto_list) ==
ISC_R_SUCCESS) {
const cfg_listelt_t *proto = NULL;
@ -11210,6 +11215,7 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config,
.name = tlsname,
.key = key,
.cert = cert,
.ca_file = ca_file,
.protocols = tls_protos,
.dhparam_file = dhparam_file,
.ciphers = ciphers,

View file

@ -4832,7 +4832,13 @@ The following options can be specified in a ``tls`` statement:
the connection.
``ca-file``
Path to a file containing trusted TLS certificates.
Path to a file containing trusted CA-authorities TLS
certificates used to verify remote peer certificates. Specifying
this option enables remote peer certificates verification. For
incoming connections specifying this option will make BIND require
a valid TLS certificate from a client. In the case of outgoing
connections, if ``hostname`` is not specified, then the remote
server IP address is used instead.
``dhparam-file``
Path to a file containing Diffie-Hellman parameters,
@ -4842,7 +4848,13 @@ The following options can be specified in a ``tls`` statement:
ciphers in TLSv1.2.
``hostname``
The hostname associated with the certificate.
The expected hostname in the TLS certificate of the
remote server. This option enables a remote server certificate
verification. If ``ca-file`` is not specified, then the
platform-specific certificates store is used for
verification. This option is used when connecting to a remote peer
only and, thus, is ignored when ``tls`` statements are referenced
by ``listen-on`` or ``listen-on-v6`` statements.
``protocols``
Allowed versions of the TLS protocol. TLS version 1.2 and higher are

View file

@ -933,6 +933,7 @@ xfrin_start(dns_xfrin_ctx_t *xfr) {
dns_xfrin_ctx_t *connect_xfr = NULL;
dns_transport_type_t transport_type = DNS_TRANSPORT_TCP;
isc_tlsctx_t *tlsctx = NULL, *found = NULL;
isc_tls_cert_store_t *store = NULL, *found_store = NULL;
(void)isc_refcount_increment0(&xfr->connects);
dns_xfrin_attach(xfr, &connect_xfr);
@ -973,8 +974,19 @@ xfrin_start(dns_xfrin_ctx_t *xfr) {
*/
result = isc_tlsctx_cache_find(xfr->tlsctx_cache, tlsname,
isc_tlsctx_cache_tls, family,
&tlsctx, NULL);
&tlsctx, &found_store);
if (result != ISC_R_SUCCESS) {
const char *hostname =
dns_transport_get_hostname(xfr->transport);
const char *ca_file =
dns_transport_get_cafile(xfr->transport);
const char *cert_file =
dns_transport_get_certfile(xfr->transport);
const char *key_file =
dns_transport_get_keyfile(xfr->transport);
char primary_addr_str[INET6_ADDRSTRLEN] = { 0 };
isc_netaddr_t primary_netaddr = { 0 };
bool hostname_ignore_subject;
/*
* So, no context exists. Let's create one using the
* parameters from the configuration file and try to
@ -997,12 +1009,80 @@ xfrin_start(dns_xfrin_ctx_t *xfr) {
isc_tlsctx_prefer_server_ciphers(
tlsctx, prefer_server_ciphers);
}
if (hostname != NULL || ca_file != NULL) {
if (found_store == NULL) {
/*
* 'ca_file' can equal 'NULL' here, in
* that case the store with system-wide
* CA certificates will be created, just
* as planned.
*/
result = isc_tls_cert_store_create(
ca_file, &store);
if (result != ISC_R_SUCCESS) {
goto failure;
}
} else {
store = found_store;
}
INSIST(store != NULL);
if (hostname == NULL) {
/*
* If CA bundle file is specified, but
* hostname is not, then use the primary
* IP address for validation, just like
* dig does.
*/
INSIST(ca_file != NULL);
isc_netaddr_fromsockaddr(
&primary_netaddr,
&xfr->primaryaddr);
isc_netaddr_format(
&primary_netaddr,
primary_addr_str,
sizeof(primary_addr_str));
hostname = primary_addr_str;
}
/*
* According to RFC 8310, Subject field MUST NOT
* be inspected when verifying hostname for DoT.
* Only SubjectAltName must be checked.
*/
hostname_ignore_subject = true;
result = isc_tlsctx_enable_peer_verification(
tlsctx, false, store, hostname,
hostname_ignore_subject);
if (result != ISC_R_SUCCESS) {
goto failure;
}
/*
* Let's load client certificate and enable
* Mutual TLS. We do that only in the case when
* Strict TLS is enabled, because Mutual TLS is
* an extension of it.
*/
if (cert_file != NULL) {
INSIST(key_file != NULL);
result = isc_tlsctx_load_certificate(
tlsctx, key_file, cert_file);
if (result != ISC_R_SUCCESS) {
goto failure;
}
}
}
isc_tlsctx_enable_dot_client_alpn(tlsctx);
found_store = NULL;
result = isc_tlsctx_cache_add(
xfr->tlsctx_cache, tlsname,
isc_tlsctx_cache_tls, family, tlsctx, NULL,
&found, NULL);
isc_tlsctx_cache_tls, family, tlsctx, store,
&found, &found_store);
if (result == ISC_R_EXISTS) {
/*
* It seems the entry has just been created
@ -1019,6 +1099,7 @@ xfrin_start(dns_xfrin_ctx_t *xfr) {
*/
INSIST(found != NULL);
isc_tlsctx_free(&tlsctx);
isc_tls_cert_store_free(&store);
tlsctx = found;
} else {
INSIST(result == ISC_R_SUCCESS);
@ -1043,6 +1124,10 @@ failure:
if (tlsctx != NULL && found != tlsctx) {
isc_tlsctx_free(&tlsctx);
}
if (store != NULL && store != found_store) {
isc_tls_cert_store_free(&store);
}
isc_refcount_decrement0(&xfr->connects);
dns_xfrin_detach(&connect_xfr);
return (result);

View file

@ -65,6 +65,7 @@ typedef struct ns_listen_tls_params {
const char *name;
const char *key;
const char *cert;
const char *ca_file;
uint32_t protocols;
const char *dhparam_file;
const char *ciphers;

View file

@ -34,6 +34,7 @@ listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp,
ns_listenelt_t *elt = NULL;
isc_result_t result = ISC_R_SUCCESS;
isc_tlsctx_t *sslctx = NULL;
isc_tls_cert_store_t *store = NULL, *found_store = NULL;
REQUIRE(target != NULL && *target == NULL);
REQUIRE(!tls || (tls_params != NULL && tlsctx_cache != NULL));
@ -48,7 +49,7 @@ listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp,
*/
result = isc_tlsctx_cache_find(tlsctx_cache, tls_params->name,
transport, family, &sslctx,
NULL);
&found_store);
if (result != ISC_R_SUCCESS) {
/*
* The lookup failed, let's try to create a new context
@ -60,7 +61,39 @@ listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp,
result = isc_tlsctx_createserver(
tls_params->key, tls_params->cert, &sslctx);
if (result != ISC_R_SUCCESS) {
return (result);
goto tls_error;
}
/*
* If CA-bundle file is specified - enable client
* certificates validation.
*/
if (tls_params->ca_file != NULL) {
if (found_store == NULL) {
result = isc_tls_cert_store_create(
tls_params->ca_file, &store);
if (result != ISC_R_SUCCESS) {
goto tls_error;
}
} else {
store = found_store;
}
result = isc_tlsctx_enable_peer_verification(
sslctx, true, store, NULL, false);
if (result != ISC_R_SUCCESS) {
goto tls_error;
}
/*
* Load the list of allowed client certificate
* issuers to send to TLS clients.
*/
result = isc_tlsctx_load_client_ca_names(
sslctx, tls_params->ca_file);
if (result != ISC_R_SUCCESS) {
goto tls_error;
}
}
if (tls_params->protocols != 0) {
@ -71,8 +104,8 @@ listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp,
if (tls_params->dhparam_file != NULL) {
if (!isc_tlsctx_load_dhparams(
sslctx, tls_params->dhparam_file)) {
isc_tlsctx_free(&sslctx);
return (ISC_R_FAILURE);
result = ISC_R_FAILURE;
goto tls_error;
}
}
@ -106,10 +139,17 @@ listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp,
* The storing in the cache should not fail because the
* (re)initialisation happens from within a single
* thread.
*
* Taking into account that the most recent call to
* 'isc_tlsctx_cache_find()' has failed, it means that
* the TLS context has not been found. Considering that
* the initialisation happens from within the context of
* a single thread, the call to 'isc_tlsctx_cache_add()'
* is expected not to fail.
*/
RUNTIME_CHECK(isc_tlsctx_cache_add(
tlsctx_cache, tls_params->name,
transport, family, sslctx, NULL,
transport, family, sslctx, store,
NULL, NULL) == ISC_R_SUCCESS);
} else {
INSIST(sslctx != NULL);
@ -134,6 +174,15 @@ listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp,
*target = elt;
return (ISC_R_SUCCESS);
tls_error:
if (sslctx != NULL) {
isc_tlsctx_free(&sslctx);
}
if (store != NULL && store != found_store) {
isc_tls_cert_store_free(&store);
}
return (result);
}
isc_result_t