]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add support for Strict/Mutual TLS into BIND
authorArtem Boldariev <artem@boldariev.com>
Wed, 26 Jan 2022 13:26:08 +0000 (15:26 +0200)
committerArtem Boldariev <artem@boldariev.com>
Mon, 28 Mar 2022 13:22:53 +0000 (16:22 +0300)
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
doc/arm/reference.rst
lib/dns/xfrin.c
lib/ns/include/ns/listenlist.h
lib/ns/listenlist.c

index d6f74d3789de9acda88255496ffa026d7afeaa41..536dcdac725a1cab7f694dc9c40e6996e193a9ae 100644 (file)
@@ -11044,8 +11044,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;
@@ -11070,7 +11070,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;
@@ -11093,6 +11093,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;
@@ -11148,6 +11153,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,
index 8a6c7ee67c800d04b6d033c8e7998e32bde22b64..94b3f90834dc29461afa0cf9b7fa2fe6d4bd3c40 100644 (file)
@@ -4804,7 +4804,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,
@@ -4814,7 +4820,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
index 8e2dd17e6421f0c2ef0d69bdb9c56376fd64f3fb..2f587f81f93082d30ddc28952a294ad7e99b09af 100644 (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);
index e9afc7fca7fc0e1457c74d4184b0331da5559f7b..d5ce617cc3265e6b449db8a55da3176af245608f 100644 (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;
index 25017b5c6a17e606b2188142df8aa68f038501a5..32a72888d50c40921a0c78d67c974af3d36942eb 100644 (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