From 6c05fb09c364f4ef33ac28cd70240a7501af15a1 Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Wed, 26 Jan 2022 15:26:08 +0200 Subject: [PATCH] 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. --- bin/named/server.c | 12 +++-- doc/arm/reference.rst | 16 +++++- lib/dns/xfrin.c | 91 ++++++++++++++++++++++++++++++++-- lib/ns/include/ns/listenlist.h | 1 + lib/ns/listenlist.c | 59 ++++++++++++++++++++-- 5 files changed, 166 insertions(+), 13 deletions(-) diff --git a/bin/named/server.c b/bin/named/server.c index 5b0ac382cf..bcc34730e8 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -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, diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 07970f7c89..a6fb84aca9 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -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 diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index 50deee0373..d7ff12c4c8 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -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); diff --git a/lib/ns/include/ns/listenlist.h b/lib/ns/include/ns/listenlist.h index e9afc7fca7..d5ce617cc3 100644 --- a/lib/ns/include/ns/listenlist.h +++ b/lib/ns/include/ns/listenlist.h @@ -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; diff --git a/lib/ns/listenlist.c b/lib/ns/listenlist.c index 25017b5c6a..32a72888d5 100644 --- a/lib/ns/listenlist.c +++ b/lib/ns/listenlist.c @@ -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