]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Use the TLS context cache for client-side contexts (XoT)
authorArtem Boldariev <artem@boldariev.com>
Thu, 23 Dec 2021 14:08:41 +0000 (16:08 +0200)
committerArtem Boldariev <artem@boldariev.com>
Wed, 29 Dec 2021 08:25:15 +0000 (10:25 +0200)
This commit enables client-side TLS contexts re-use for zone transfers
over TLS. That, in turn, makes it possible to use the internal session
cache associated with the contexts, allowing the TLS connections to be
established faster and requiring fewer resources by not going through
the full TLS handshake procedure.

Previously that would recreate the context on every connection, making
TLS session resumption impossible.

Also, this change lays down a foundation for Strict TLS (when the
client validates a server certificate), as the TLS context cache can
be extended to store additional data required for validation (like
intermediates CA chain).

bin/named/include/named/server.h
bin/named/server.c
bin/named/transportconf.c
lib/dns/include/dns/transport.h
lib/dns/include/dns/xfrin.h
lib/dns/include/dns/zone.h
lib/dns/transport.c
lib/dns/xfrin.c
lib/dns/zone.c

index dd0d6701185d0bc05f9a4888e42dcd7b8fd7dff1..869f5f764ea31ef9f77843baf056179ae4992363 100644 (file)
@@ -112,6 +112,7 @@ struct named_server {
        char *lockfile;
 
        isc_tlsctx_cache_t *tlsctx_server_cache;
+       isc_tlsctx_cache_t *tlsctx_client_cache;
 };
 
 #define NAMED_SERVER_MAGIC    ISC_MAGIC('S', 'V', 'E', 'R')
index 0ac53c9a76384289f7d42353adab1939224e01ab..8e27090da8580eaadcc6441b5cf8ee5107115b56 100644 (file)
@@ -8431,6 +8431,15 @@ load_configuration(const char *filename, named_server_t *server,
 
        server->tlsctx_server_cache = isc_tlsctx_cache_new(named_g_mctx);
 
+       if (server->tlsctx_client_cache != NULL) {
+               isc_tlsctx_cache_detach(&server->tlsctx_client_cache);
+       }
+
+       server->tlsctx_client_cache = isc_tlsctx_cache_new(named_g_mctx);
+
+       dns_zonemgr_set_tlsctx_cache(server->zonemgr,
+                                    server->tlsctx_client_cache);
+
        /*
         * Fill in the maps array, used for resolving defaults.
         */
@@ -10182,6 +10191,7 @@ named_server_create(isc_mem_t *mctx, named_server_t **serverp) {
        server->magic = NAMED_SERVER_MAGIC;
 
        server->tlsctx_server_cache = NULL;
+       server->tlsctx_client_cache = NULL;
 
        *serverp = server;
 }
@@ -10241,6 +10251,10 @@ named_server_destroy(named_server_t **serverp) {
                isc_tlsctx_cache_detach(&server->tlsctx_server_cache);
        }
 
+       if (server->tlsctx_client_cache != NULL) {
+               isc_tlsctx_cache_detach(&server->tlsctx_client_cache);
+       }
+
        server->magic = 0;
        isc_mem_put(server->mctx, server, sizeof(*server));
        *serverp = NULL;
index 61c7462f0b064a6fc2b6f3f16237411ae4a84afe..40c21a6cd0e41649fca78b492a5bb1c8aa7b7170 100644 (file)
@@ -108,6 +108,7 @@ add_doh_transports(const cfg_obj_t *transportlist, dns_transport_list_t *list) {
                transport = dns_transport_new(&dohname, DNS_TRANSPORT_HTTP,
                                              list);
 
+               dns_transport_set_tlsname(transport, dohid);
                parse_transport_option(doh, transport, "key-file",
                                       dns_transport_set_keyfile);
                parse_transport_option(doh, transport, "cert-file",
@@ -165,6 +166,7 @@ add_tls_transports(const cfg_obj_t *transportlist, dns_transport_list_t *list) {
                transport = dns_transport_new(&tlsname, DNS_TRANSPORT_TLS,
                                              list);
 
+               dns_transport_set_tlsname(transport, tlsid);
                parse_transport_option(tls, transport, "key-file",
                                       dns_transport_set_keyfile);
                parse_transport_option(tls, transport, "cert-file",
@@ -228,10 +230,12 @@ static void
 transport_list_add_ephemeral(dns_transport_list_t *list) {
        isc_result_t result;
        dns_name_t tlsname;
+       dns_transport_t *transport;
 
        create_name("ephemeral", &tlsname);
 
-       (void)dns_transport_new(&tlsname, DNS_TRANSPORT_TLS, list);
+       transport = dns_transport_new(&tlsname, DNS_TRANSPORT_TLS, list);
+       dns_transport_set_tlsname(transport, "ephemeral");
 
        return;
 failure:
index 91fe38263072ec028ff7365ca097161101049beb..69ed013c87c3a037f9292f7b9b925c3a08153777 100644 (file)
@@ -54,6 +54,8 @@ dns_http_mode_t
 dns_transport_get_mode(dns_transport_t *transport);
 char *
 dns_transport_get_ciphers(dns_transport_t *transport);
+char *
+dns_transport_get_tlsname(dns_transport_t *transport);
 uint32_t
 dns_transport_get_tls_versions(const dns_transport_t *transport);
 bool
@@ -82,6 +84,9 @@ void
 dns_transport_set_mode(dns_transport_t *transport, dns_http_mode_t mode);
 void
 dns_transport_set_ciphers(dns_transport_t *transport, const char *ciphers);
+void
+dns_transport_set_tlsname(dns_transport_t *transport, const char *tlsname);
+
 void
 dns_transport_set_tls_versions(dns_transport_t *transport,
                               const uint32_t   tls_versions);
index d080b089cb3fda17248d2bd43b1b447b38c63120..6cbac87f903cd8bca1427a9ce7e0f6f09baabd10 100644 (file)
@@ -25,6 +25,7 @@
  ***/
 
 #include <isc/lang.h>
+#include <isc/tls.h>
 
 #include <dns/transport.h>
 #include <dns/types.h>
@@ -49,7 +50,8 @@ dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype,
                 const isc_sockaddr_t *primaryaddr,
                 const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp,
                 dns_tsigkey_t *tsigkey, dns_transport_t *transport,
-                isc_mem_t *mctx, isc_nm_t *netmgr, dns_xfrindone_t done,
+                isc_tlsctx_cache_t *tlsctx_cache, isc_mem_t *mctx,
+                isc_nm_t *netmgr, dns_xfrindone_t done,
                 dns_xfrin_ctx_t **xfrp);
 /*%<
  * Attempt to start an incoming zone transfer of 'zone'
index d82d6eb0dadfc9107490936bd0d93d806de20b9a..521174040600054c0d7d477296b46fd946996fc1 100644 (file)
@@ -24,6 +24,7 @@
 #include <isc/formatcheck.h>
 #include <isc/lang.h>
 #include <isc/rwlock.h>
+#include <isc/tls.h>
 
 #include <dns/catz.h>
 #include <dns/master.h>
@@ -2068,6 +2069,18 @@ dns_zonemgr_unreachabledel(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote,
  *\li  'local' to be a valid sockaddr.
  */
 
+void
+dns_zonemgr_set_tlsctx_cache(dns_zonemgr_t        *zmgr,
+                            isc_tlsctx_cache_t *tlsctx_cache);
+/*%<
+ *     Set the TLS client context cache used for zone transfers via
+ *     encrypted transports (e.g. XoT).
+ *
+ * Requires:
+ *\li  'zmgr' is a valid zone manager.
+ *\li  'tlsctx_cache' is a valid TLS context cache.
+ */
+
 void
 dns_zone_forcereload(dns_zone_t *zone);
 /*%<
index 9f7e2bdc79f3ccaca600976ef08cc4be3ee734d0..36df552634095b12a35a7dbcd87a9602bf86bb51 100644 (file)
@@ -44,6 +44,7 @@ struct dns_transport {
        isc_mem_t *mctx;
        dns_transport_type_t type;
        struct {
+               char *tlsname;
                char *certfile;
                char *keyfile;
                char *cafile;
@@ -269,6 +270,22 @@ dns_transport_set_ciphers(dns_transport_t *transport, const char *ciphers) {
        }
 }
 
+void
+dns_transport_set_tlsname(dns_transport_t *transport, const char *tlsname) {
+       REQUIRE(VALID_TRANSPORT(transport));
+       REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
+               transport->type == DNS_TRANSPORT_HTTP);
+
+       if (transport->tls.tlsname != NULL) {
+               isc_mem_free(transport->mctx, transport->tls.tlsname);
+       }
+
+       if (tlsname != NULL) {
+               transport->tls.tlsname = isc_mem_strdup(transport->mctx,
+                                                       tlsname);
+       }
+}
+
 char *
 dns_transport_get_ciphers(dns_transport_t *transport) {
        REQUIRE(VALID_TRANSPORT(transport));
@@ -276,6 +293,13 @@ dns_transport_get_ciphers(dns_transport_t *transport) {
        return (transport->tls.ciphers);
 }
 
+char *
+dns_transport_get_tlsname(dns_transport_t *transport) {
+       REQUIRE(VALID_TRANSPORT(transport));
+
+       return (transport->tls.tlsname);
+}
+
 void
 dns_transport_set_prefer_server_ciphers(dns_transport_t *transport,
                                        const bool prefer) {
@@ -330,6 +354,10 @@ transport_destroy(dns_transport_t *transport) {
                isc_mem_free(transport->mctx, transport->tls.ciphers);
        }
 
+       if (transport->tls.tlsname != NULL) {
+               isc_mem_free(transport->mctx, transport->tls.tlsname);
+       }
+
        isc_mem_putanddetach(&transport->mctx, transport, sizeof(*transport));
 }
 
index bdda30cbe2ecff85544b08a6a4ed7215e0920029..d48cebbed11904ebbef82c42ecbf8abef20ad327 100644 (file)
@@ -163,7 +163,6 @@ struct dns_xfrin_ctx {
        unsigned int sincetsig; /*%< recvd since the last TSIG */
 
        dns_transport_t *transport;
-       isc_tlsctx_t *tlsctx;
 
        dns_xfrindone_t done;
 
@@ -183,6 +182,8 @@ struct dns_xfrin_ctx {
 
        dns_rdata_t firstsoa;
        unsigned char *firstsoa_data;
+
+       isc_tlsctx_cache_t *tlsctx_cache;
 };
 
 #define XFRIN_MAGIC    ISC_MAGIC('X', 'f', 'r', 'I')
@@ -199,7 +200,7 @@ xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr,
             dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
             const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp,
             dns_tsigkey_t *tsigkey, dns_transport_t *transport,
-            dns_xfrin_ctx_t **xfrp);
+            isc_tlsctx_cache_t *tlsctx_cache, dns_xfrin_ctx_t **xfrp);
 
 static isc_result_t
 axfr_init(dns_xfrin_ctx_t *xfr);
@@ -693,7 +694,8 @@ dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype,
                 const isc_sockaddr_t *primaryaddr,
                 const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp,
                 dns_tsigkey_t *tsigkey, dns_transport_t *transport,
-                isc_mem_t *mctx, isc_nm_t *netmgr, dns_xfrindone_t done,
+                isc_tlsctx_cache_t *tlsctx_cache, isc_mem_t *mctx,
+                isc_nm_t *netmgr, dns_xfrindone_t done,
                 dns_xfrin_ctx_t **xfrp) {
        dns_name_t *zonename = dns_zone_getorigin(zone);
        dns_xfrin_ctx_t *xfr = NULL;
@@ -712,7 +714,7 @@ dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype,
 
        xfrin_create(mctx, zone, db, netmgr, zonename, dns_zone_getclass(zone),
                     xfrtype, primaryaddr, sourceaddr, dscp, tsigkey, transport,
-                    &xfr);
+                    tlsctx_cache, &xfr);
 
        if (db != NULL) {
                xfr->zone_had_db = true;
@@ -860,7 +862,7 @@ xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr,
             dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
             const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp,
             dns_tsigkey_t *tsigkey, dns_transport_t *transport,
-            dns_xfrin_ctx_t **xfrp) {
+            isc_tlsctx_cache_t *tlsctx_cache, dns_xfrin_ctx_t **xfrp) {
        dns_xfrin_ctx_t *xfr = NULL;
 
        xfr = isc_mem_get(mctx, sizeof(*xfr));
@@ -918,6 +920,8 @@ xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr,
        isc_buffer_init(&xfr->qbuffer, &xfr->qbuffer_data[2],
                        sizeof(xfr->qbuffer_data) - 2);
 
+       isc_tlsctx_cache_attach(tlsctx_cache, &xfr->tlsctx_cache);
+
        xfr->magic = XFRIN_MAGIC;
 
        *xfrp = xfr;
@@ -928,6 +932,7 @@ xfrin_start(dns_xfrin_ctx_t *xfr) {
        isc_result_t result;
        dns_xfrin_ctx_t *connect_xfr = NULL;
        dns_transport_type_t transport_type = DNS_TRANSPORT_TCP;
+       isc_tlsctx_t *tlsctx = NULL, *found = NULL;
 
        (void)isc_refcount_increment0(&xfr->connects);
        dns_xfrin_attach(xfr, &connect_xfr);
@@ -948,32 +953,79 @@ xfrin_start(dns_xfrin_ctx_t *xfr) {
                break;
        case DNS_TRANSPORT_TLS: {
                uint32_t tls_versions;
-               const char *ciphers;
+               const char *ciphers = NULL;
                bool prefer_server_ciphers;
-               CHECK(isc_tlsctx_createclient(&xfr->tlsctx));
-               if (xfr->transport != NULL) {
+               const uint16_t family = isc_sockaddr_pf(&xfr->primaryaddr) ==
+                                                       PF_INET6
+                                               ? AF_INET6
+                                               : AF_INET;
+               const char *tlsname = NULL;
+
+               INSIST(xfr->transport != NULL);
+               tlsname = dns_transport_get_tlsname(xfr->transport);
+               INSIST(tlsname != NULL && *tlsname != '\0');
+
+               /*
+                * Let's try to re-use the already created context. This way
+                * we have a chance to resume the TLS session, bypassing the
+                * full TLS handshake procedure, making establishing
+                * subsequent TLS connections for XoT faster.
+                */
+               result = isc_tlsctx_cache_find(xfr->tlsctx_cache, tlsname,
+                                              isc_tlsctx_cache_tls, family,
+                                              &tlsctx);
+               if (result != ISC_R_SUCCESS) {
+                       /*
+                        * So, no context exists. Let's create one using the
+                        * parameters from the configuration file and try to
+                        * store it for further reuse.
+                        */
+                       CHECK(isc_tlsctx_createclient(&tlsctx));
                        tls_versions =
                                dns_transport_get_tls_versions(xfr->transport);
                        if (tls_versions != 0) {
-                               isc_tlsctx_set_protocols(xfr->tlsctx,
-                                                        tls_versions);
+                               isc_tlsctx_set_protocols(tlsctx, tls_versions);
                        }
                        ciphers = dns_transport_get_ciphers(xfr->transport);
                        if (ciphers != NULL) {
-                               isc_tlsctx_set_cipherlist(xfr->tlsctx, ciphers);
+                               isc_tlsctx_set_cipherlist(tlsctx, ciphers);
                        }
 
                        if (dns_transport_get_prefer_server_ciphers(
                                    xfr->transport, &prefer_server_ciphers))
                        {
                                isc_tlsctx_prefer_server_ciphers(
-                                       xfr->tlsctx, prefer_server_ciphers);
+                                       tlsctx, prefer_server_ciphers);
+                       }
+                       isc_tlsctx_enable_dot_client_alpn(tlsctx);
+
+                       result = isc_tlsctx_cache_add(
+                               xfr->tlsctx_cache, tlsname,
+                               isc_tlsctx_cache_tls, family, tlsctx, &found);
+                       if (result == ISC_R_EXISTS) {
+                               /*
+                                * It seems the entry has just been created
+                                * from within another thread while we were
+                                * initialising ours. Although this is
+                                * unlikely, it could happen after
+                                * startup/re-initialisation. In such a case,
+                                * discard the new context and use the already
+                                * established one from now on.
+                                *
+                                * Such situation will not occur after the
+                                * initial 'warm-up', so it is not critical
+                                * performance-wise.
+                                */
+                               INSIST(found != NULL);
+                               isc_tlsctx_free(&tlsctx);
+                               tlsctx = found;
+                       } else {
+                               INSIST(result == ISC_R_SUCCESS);
                        }
                }
-               isc_tlsctx_enable_dot_client_alpn(xfr->tlsctx);
                isc_nm_tlsdnsconnect(xfr->netmgr, &xfr->sourceaddr,
                                     &xfr->primaryaddr, xfrin_connect_done,
-                                    connect_xfr, 30000, 0, xfr->tlsctx);
+                                    connect_xfr, 30000, 0, tlsctx);
        } break;
        default:
                INSIST(0);
@@ -983,8 +1035,13 @@ xfrin_start(dns_xfrin_ctx_t *xfr) {
        return (ISC_R_SUCCESS);
 
 failure:
-       if (xfr->tlsctx != NULL) {
-               isc_tlsctx_free(&xfr->tlsctx);
+       /*
+        * The 'found' context is being managed by the TLS context cache.
+        * Thus, we should keep it as it is, as it will get destroyed
+        * alongside the cache.
+        */
+       if (tlsctx != NULL && found != tlsctx) {
+               isc_tlsctx_free(&tlsctx);
        }
        isc_refcount_decrement0(&xfr->connects);
        dns_xfrin_detach(&connect_xfr);
@@ -1032,10 +1089,6 @@ xfrin_connect_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
 
        isc_refcount_decrement0(&xfr->connects);
 
-       if (xfr->tlsctx != NULL) {
-               isc_tlsctx_free(&xfr->tlsctx);
-       }
-
        if (atomic_load(&xfr->shuttingdown)) {
                result = ISC_R_SHUTTINGDOWN;
        }
@@ -1670,6 +1723,10 @@ xfrin_destroy(dns_xfrin_ctx_t *xfr) {
                isc_mem_free(xfr->mctx, xfr->firstsoa_data);
        }
 
+       if (xfr->tlsctx_cache != NULL) {
+               isc_tlsctx_cache_detach(&xfr->tlsctx_cache);
+       }
+
        isc_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr));
 }
 
index 08112ac2b508f206729a6c3e070e9031fe1aa5ca..2e54038b3cf6585bd0c9da08025192c96d958084 100644 (file)
@@ -35,6 +35,7 @@
 #include <isc/taskpool.h>
 #include <isc/thread.h>
 #include <isc/timer.h>
+#include <isc/tls.h>
 #include <isc/util.h>
 
 #include <dns/acl.h>
@@ -629,6 +630,8 @@ struct dns_zonemgr {
        struct dns_unreachable unreachable[UNREACH_CACHE_SIZE];
 
        dns_keymgmt_t *keymgmt;
+
+       isc_tlsctx_cache_t *tlsctx_cache;
 };
 
 /*%
@@ -18133,7 +18136,8 @@ got_transfer_quota(isc_task_t *task, isc_event_t *event) {
        }
 
        CHECK(dns_xfrin_create(zone, xfrtype, &primaryaddr, &sourceaddr, dscp,
-                              zone->tsigkey, zone->transport, zone->mctx,
+                              zone->tsigkey, zone->transport,
+                              zone->zmgr->tlsctx_cache, zone->mctx,
                               zone->zmgr->netmgr, zone_xfrdone, &zone->xfr));
        LOCK_ZONE(zone);
        if (xfrtype == dns_rdatatype_axfr) {
@@ -18831,6 +18835,8 @@ dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
 
        isc_mutex_init(&zmgr->iolock);
 
+       zmgr->tlsctx_cache = NULL;
+
        zmgr->magic = ZONEMGR_MAGIC;
 
        *zmgrp = zmgr;
@@ -19188,6 +19194,9 @@ zonemgr_free(dns_zonemgr_t *zmgr) {
        zonemgr_keymgmt_destroy(zmgr);
 
        mctx = zmgr->mctx;
+       if (zmgr->tlsctx_cache != NULL) {
+               isc_tlsctx_cache_detach(&zmgr->tlsctx_cache);
+       }
        isc_mem_put(zmgr->mctx, zmgr, sizeof(*zmgr));
        isc_mem_detach(&mctx);
 }
@@ -23627,3 +23636,20 @@ zone_nsecttl(dns_zone_t *zone) {
 
        return (ISC_MIN(zone->minimum, zone->soattl));
 }
+
+void
+dns_zonemgr_set_tlsctx_cache(dns_zonemgr_t *zmgr,
+                            isc_tlsctx_cache_t *tlsctx_cache) {
+       REQUIRE(DNS_ZONEMGR_VALID(zmgr));
+       REQUIRE(tlsctx_cache != NULL);
+
+       RWLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+
+       if (zmgr->tlsctx_cache != NULL) {
+               isc_tlsctx_cache_detach(&zmgr->tlsctx_cache);
+       }
+
+       isc_tlsctx_cache_attach(tlsctx_cache, &zmgr->tlsctx_cache);
+
+       RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write);
+}