]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add TLS context cache
authorArtem Boldariev <artem@boldariev.com>
Wed, 22 Dec 2021 15:11:11 +0000 (17:11 +0200)
committerArtem Boldariev <artem@boldariev.com>
Wed, 29 Dec 2021 08:25:11 +0000 (10:25 +0200)
This commit adds a TLS context object cache implementation. The
intention of having this object is manyfold:

- In the case of client-side contexts: allow reusing the previously
created contexts to employ the context-specific TLS session resumption
cache. That will enable XoT connection to be reestablished faster and
with fewer resources by not going through the full TLS handshake
procedure.

- In the case of server-side contexts: reduce the number of contexts
created on startup. That could reduce startup time in a case when
there are many "listen-on" statements referring to a smaller amount of
`tls` statements, especially when "ephemeral" certificates are
involved.

- The long-term goal is to provide in-memory storage for additional
data associated with the certificates, like runtime
representation (X509_STORE) of intermediate CA-certificates bundle for
Strict TLS/Mutual TLS ("ca-file").

lib/isc/include/isc/tls.h
lib/isc/tls.c

index fcc484df4741119cb271ffa94e09d36571678b73..ec7382901d842ddc6a2ec3511a807c7dc02034b1 100644 (file)
@@ -158,7 +158,6 @@ isc_tlsctx_enable_http2client_alpn(isc_tlsctx_t *ctx);
 void
 isc_tlsctx_enable_http2server_alpn(isc_tlsctx_t *ctx);
 /*%<
- *
  * Enable HTTP/2 Application Layer Protocol Negotation for 'ctx'.
  *
  * Requires:
@@ -178,9 +177,118 @@ isc_tlsctx_enable_dot_client_alpn(isc_tlsctx_t *ctx);
 void
 isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *ctx);
 /*%<
- *
  * Enable DoT Application Layer Protocol Negotation for 'ctx'.
  *
  * Requires:
  *\li  'ctx' is not NULL.
  */
+
+typedef struct isc_tlsctx_cache isc_tlsctx_cache_t;
+/*%<
+ * The TLS context cache is an object which allows retrieving a
+ * previously created TLS context based on the following tuple:
+ *
+ * 1. The name of a TLS entry, as defined in the configuration file;
+ * 2. A transport type. Currently, only TLS (DoT) and HTTPS (DoH) are
+ *    supported;
+ * 3. An IP address family (AF_INET or AF_INET6).
+ *
+ * There are multiple uses for this object:
+ *
+ * First, it allows reuse of client-side contexts during zone transfers.
+ * That, in turn, allows use of session caches associated with these
+ * contexts, which enables TLS session resumption, making establishment
+ * of XoT connections faster and computationally cheaper.
+ *
+ * Second, it can be extended to be used as storage for TLS context related
+ * data, as defined in 'tls' statements in the configuration file (for
+ * example, CA-bundle intermediate certificate storage, client-side contexts
+ * with pre-loaded certificates in a case of Mutual TLS, etc). This will
+ * be used to implement Strict/Mutual TLS.
+ *
+ * Third, it avoids creating an excessive number of server-side TLS
+ * contexts, which might help to reduce the number of contexts
+ * created during server initialisation and reconfiguration.
+ */
+
+typedef enum {
+       isc_tlsctx_cache_none = 0,
+       isc_tlsctx_cache_tls,
+       isc_tlsctx_cache_https,
+       isc_tlsctx_cache_count
+} isc_tlsctx_cache_transport_t;
+/*%< TLS context cache transport type values. */
+
+isc_tlsctx_cache_t *
+isc_tlsctx_cache_new(isc_mem_t *mctx);
+/*%<
+ * Create a new TLS context cache object.
+ *
+ * Requires:
+ *\li  'mctx' is a valid memory context.
+ */
+
+void
+isc_tlsctx_cache_attach(isc_tlsctx_cache_t  *source,
+                       isc_tlsctx_cache_t **targetp);
+/*%<
+ * Create a reference to the TLS context cache object.
+ *
+ * Requires:
+ *\li  'source' is a valid TLS context cache object;
+ *\li  'targetp' is a valid pointer to a pointer which must equal NULL.
+ */
+
+void
+isc_tlsctx_cache_detach(isc_tlsctx_cache_t **pcache);
+/*%<
+ * Remove a reference to the TLS context cache object.
+ *
+ * Requires:
+ *\li  'pcache' is a valid pointer to a pointer which must point to a
+ *      valid TLS context cache object.
+ */
+
+isc_result_t
+isc_tlsctx_cache_add(isc_tlsctx_cache_t *cache, const char *name,
+                    const isc_tlsctx_cache_transport_t transport,
+                    const uint16_t family, isc_tlsctx_t *ctx,
+                    isc_tlsctx_t **pfound);
+/*%<
+ *
+ * Add a new TLS context to the TLS context cache. 'pfound' is an
+ * optional pointer, which can be used to retrieve an already
+ * existing TLS context object in a case it exists.
+ *
+ * Requires:
+ *\li  'cache' is a valid pointer to a TLS context cache object;
+ *\li  'name' is a valid pointer to a non-empty string;
+ *\li  'transport' is a valid transport identifier (currently only
+ *       TLS/DoT and HTTPS/DoH are supported);
+ *\li  'family' - either 'AF_INET' or 'AF_INET6';
+ *\li   'ctx' - a valid pointer to a valid TLS context object.
+ *
+ * Returns:
+ *\li  #ISC_R_EXISTS - node of the same key already exists;
+ *\li  #ISC_R_SUCCESS - the new entry has been added successfully.
+ */
+
+isc_result_t
+isc_tlsctx_cache_find(isc_tlsctx_cache_t *cache, const char *name,
+                     const isc_tlsctx_cache_transport_t transport,
+                     const uint16_t family, isc_tlsctx_t **pctx);
+/*%<
+ * Look up a TLS context in the TLS context cache.
+ *
+ * Requires:
+ *\li  'cache' is a valid pointer to a TLS context cache object;
+ *\li  'name' is a valid pointer to a non empty string;
+ *\li  'transport' - a valid transport identifier (currently only
+ *       TLS/DoT and HTTPS/DoH are supported;
+ *\li  'family' - either 'AF_INET' or 'AF_INET6';
+ *\li   'pctx' - a valid pointer to a non-NULL pointer.
+ *
+ * Returns:
+ *\li  #ISC_R_SUCCESS - the context has been found;
+ *\li  #ISC_R_NOTFOUND - the context has not been found.
+ */
index 7515d833cf6901a77f4c8bb2cb26979d74adbe92..cf781a23020e860df6494f012f9cd7d98f3292e6 100644 (file)
@@ -12,6 +12,7 @@
 #include <inttypes.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/socket.h>
 #if HAVE_LIBNGHTTP2
 #include <nghttp2/nghttp2.h>
 #endif /* HAVE_LIBNGHTTP2 */
 #include <openssl/rsa.h>
 
 #include <isc/atomic.h>
+#include <isc/ht.h>
 #include <isc/log.h>
+#include <isc/magic.h>
 #include <isc/mutex.h>
 #include <isc/mutexblock.h>
 #include <isc/once.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
 #include <isc/thread.h>
 #include <isc/tls.h>
 #include <isc/util.h>
@@ -885,3 +890,200 @@ isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *tls) {
        SSL_CTX_set_alpn_select_cb(tls, dot_alpn_select_proto_cb, NULL);
 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
 }
+
+#define TLSCTX_CACHE_MAGIC    ISC_MAGIC('T', 'l', 'S', 'c')
+#define VALID_TLSCTX_CACHE(t) ISC_MAGIC_VALID(t, TLSCTX_CACHE_MAGIC)
+
+typedef struct isc_tlsctx_cache_entry {
+       /*
+        * We need a TLS context entry for each transport on both IPv4 and
+        * IPv6 in order to avoid cluttering a context-specific
+        * session-resumption cache.
+        */
+       isc_tlsctx_t *ctx[isc_tlsctx_cache_count - 1][2];
+       /*
+        * TODO: add a certificate store for an intermediate certificates
+        * from a CA-bundle file. One is enough for all the contexts defined
+        * above. We will need that for validation.
+        *
+        * X509_STORE *ca_bundle_store; // TODO:  define the utilities to
+        * operate on these ones
+        */
+} isc_tlsctx_cache_entry_t;
+
+struct isc_tlsctx_cache {
+       uint32_t magic;
+       isc_refcount_t references;
+       isc_mem_t *mctx;
+
+       isc_rwlock_t rwlock;
+       isc_ht_t *data;
+};
+
+isc_tlsctx_cache_t *
+isc_tlsctx_cache_new(isc_mem_t *mctx) {
+       isc_tlsctx_cache_t *nc;
+
+       nc = isc_mem_get(mctx, sizeof(*nc));
+
+       *nc = (isc_tlsctx_cache_t){ .magic = TLSCTX_CACHE_MAGIC };
+       isc_refcount_init(&nc->references, 1);
+       isc_mem_attach(mctx, &nc->mctx);
+
+       RUNTIME_CHECK(isc_ht_init(&nc->data, mctx, 5) == ISC_R_SUCCESS);
+       isc_rwlock_init(&nc->rwlock, 0, 0);
+
+       return (nc);
+}
+
+void
+isc_tlsctx_cache_attach(isc_tlsctx_cache_t *source,
+                       isc_tlsctx_cache_t **targetp) {
+       REQUIRE(VALID_TLSCTX_CACHE(source));
+       REQUIRE(targetp != NULL && *targetp == NULL);
+
+       isc_refcount_increment(&source->references);
+
+       *targetp = source;
+}
+
+static void
+tlsctx_cache_entry_destroy(isc_mem_t *mctx, isc_tlsctx_cache_entry_t *entry) {
+       size_t i, k;
+
+       for (i = 0; i < (isc_tlsctx_cache_count - 1); i++) {
+               for (k = 0; k < 2; k++) {
+                       if (entry->ctx[i][k] != NULL) {
+                               isc_tlsctx_free(&entry->ctx[i][k]);
+                       }
+               }
+       }
+       isc_mem_put(mctx, entry, sizeof(*entry));
+}
+
+void
+isc_tlsctx_cache_detach(isc_tlsctx_cache_t **pcache) {
+       isc_tlsctx_cache_t *cache;
+       isc_ht_iter_t *it = NULL;
+       isc_result_t result;
+       REQUIRE(pcache != NULL);
+       cache = *pcache;
+       REQUIRE(VALID_TLSCTX_CACHE(cache));
+
+       if (isc_refcount_decrement(&cache->references) > 1) {
+               return;
+       }
+
+       RUNTIME_CHECK(isc_ht_iter_create(cache->data, &it) == ISC_R_SUCCESS);
+       for (result = isc_ht_iter_first(it); result == ISC_R_SUCCESS;
+            result = isc_ht_iter_delcurrent_next(it))
+       {
+               isc_tlsctx_cache_entry_t *entry = NULL;
+               isc_ht_iter_current(it, (void **)&entry);
+               tlsctx_cache_entry_destroy(cache->mctx, entry);
+       }
+       isc_ht_iter_destroy(&it);
+       isc_ht_destroy(&cache->data);
+
+       isc_rwlock_destroy(&cache->rwlock);
+       cache->magic = 0;
+       isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
+       *pcache = NULL;
+}
+
+isc_result_t
+isc_tlsctx_cache_add(isc_tlsctx_cache_t *cache, const char *name,
+                    const isc_tlsctx_cache_transport_t transport,
+                    const uint16_t family, isc_tlsctx_t *ctx,
+                    isc_tlsctx_t **pfound) {
+       isc_result_t result = ISC_R_FAILURE;
+       size_t name_len, tr_offset;
+       isc_tlsctx_cache_entry_t *entry = NULL;
+       bool ipv6;
+
+       REQUIRE(VALID_TLSCTX_CACHE(cache));
+       REQUIRE(name != NULL && *name != '\0');
+       REQUIRE(transport > isc_tlsctx_cache_none &&
+               transport < isc_tlsctx_cache_count);
+       REQUIRE(family == AF_INET || family == AF_INET6);
+       REQUIRE(ctx != NULL);
+
+       tr_offset = (transport - 1);
+       ipv6 = (family == AF_INET6);
+
+       RWLOCK(&cache->rwlock, isc_rwlocktype_write);
+
+       name_len = strlen(name);
+       result = isc_ht_find(cache->data, (const uint8_t *)name, name_len,
+                            (void **)&entry);
+       if (result == ISC_R_SUCCESS && entry->ctx[tr_offset][ipv6] != NULL) {
+               /* The entry exists. */
+               if (pfound != NULL) {
+                       INSIST(*pfound == NULL);
+                       *pfound = entry->ctx[tr_offset][ipv6];
+               }
+               result = ISC_R_EXISTS;
+       } else if (result == ISC_R_SUCCESS &&
+                  entry->ctx[tr_offset][ipv6] == NULL) {
+               /*
+                * The hast table entry exists, but is not filled for this
+                * particular transport/IP type combination.
+                */
+               entry->ctx[tr_offset][ipv6] = ctx;
+               result = ISC_R_SUCCESS;
+       } else {
+               /*
+                * The hash table entry does not exist, let's create one.
+                */
+               INSIST(result != ISC_R_SUCCESS);
+               entry = isc_mem_get(cache->mctx, sizeof(*entry));
+               /* Oracle/Red Hat Linux, GCC bug #53119 */
+               memset(entry, 0, sizeof(*entry));
+               entry->ctx[tr_offset][ipv6] = ctx;
+               RUNTIME_CHECK(isc_ht_add(cache->data, (const uint8_t *)name,
+                                        name_len,
+                                        (void *)entry) == ISC_R_SUCCESS);
+               result = ISC_R_SUCCESS;
+       }
+
+       RWUNLOCK(&cache->rwlock, isc_rwlocktype_write);
+
+       return (result);
+}
+
+isc_result_t
+isc_tlsctx_cache_find(isc_tlsctx_cache_t *cache, const char *name,
+                     const isc_tlsctx_cache_transport_t transport,
+                     const uint16_t family, isc_tlsctx_t **pctx) {
+       isc_result_t result = ISC_R_FAILURE;
+       size_t tr_offset;
+       isc_tlsctx_cache_entry_t *entry = NULL;
+       bool ipv6;
+
+       REQUIRE(VALID_TLSCTX_CACHE(cache));
+       REQUIRE(name != NULL && *name != '\0');
+       REQUIRE(transport > isc_tlsctx_cache_none &&
+               transport < isc_tlsctx_cache_count);
+       REQUIRE(family == AF_INET || family == AF_INET6);
+       REQUIRE(pctx != NULL && *pctx == NULL);
+
+       tr_offset = (transport - 1);
+       ipv6 = (family == AF_INET6);
+
+       RWLOCK(&cache->rwlock, isc_rwlocktype_read);
+
+       result = isc_ht_find(cache->data, (const uint8_t *)name, strlen(name),
+                            (void **)&entry);
+       if (result == ISC_R_SUCCESS && entry->ctx[tr_offset][ipv6] != NULL) {
+               *pctx = entry->ctx[tr_offset][ipv6];
+       } else if (result == ISC_R_SUCCESS &&
+                  entry->ctx[tr_offset][ipv6] == NULL) {
+               result = ISC_R_NOTFOUND;
+       } else {
+               INSIST(result != ISC_R_SUCCESS);
+       }
+
+       RWUNLOCK(&cache->rwlock, isc_rwlocktype_read);
+
+       return (result);
+}