From: Artem Boldariev Date: Wed, 22 Dec 2021 15:11:11 +0000 (+0200) Subject: Add TLS context cache X-Git-Tag: v9.17.22~21^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=eb37d967c2f15870f0c2b081c20891ccd21a6392;p=thirdparty%2Fbind9.git Add TLS context cache 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"). --- diff --git a/lib/isc/include/isc/tls.h b/lib/isc/include/isc/tls.h index fcc484df474..ec7382901d8 100644 --- a/lib/isc/include/isc/tls.h +++ b/lib/isc/include/isc/tls.h @@ -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. + */ diff --git a/lib/isc/tls.c b/lib/isc/tls.c index 7515d833cf6..cf781a23020 100644 --- a/lib/isc/tls.c +++ b/lib/isc/tls.c @@ -12,6 +12,7 @@ #include #include #include +#include #if HAVE_LIBNGHTTP2 #include #endif /* HAVE_LIBNGHTTP2 */ @@ -27,10 +28,14 @@ #include #include +#include #include +#include #include #include #include +#include +#include #include #include #include @@ -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); +}