void
isc_tlsctx_enable_http2server_alpn(isc_tlsctx_t *ctx);
/*%<
- *
* Enable HTTP/2 Application Layer Protocol Negotation for 'ctx'.
*
* Requires:
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.
+ */
#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>
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);
+}