From: Arran Cudbard-Bell Date: Fri, 19 Feb 2021 22:51:53 +0000 (+0000) Subject: Add TLS engine configuration code X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6ac7eb476862a8d0c43e2c5b3af62da33f2e79dc;p=thirdparty%2Ffreeradius-server.git Add TLS engine configuration code --- diff --git a/src/lib/tls/all.mk b/src/lib/tls/all.mk index 9b8673a6830..3874873b5c2 100644 --- a/src/lib/tls/all.mk +++ b/src/lib/tls/all.mk @@ -9,6 +9,7 @@ SOURCES := \ cache.c \ conf.c \ ctx.c \ + engine.c \ log.c \ ocsp.c \ session.c \ diff --git a/src/lib/tls/base.c b/src/lib/tls/base.c index d63c0e00399..da773a0833c 100644 --- a/src/lib/tls/base.c +++ b/src/lib/tls/base.c @@ -33,9 +33,10 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #include #include +#include +#include +#include #include -#include "base.h" -#include "attrs.h" static uint32_t instance_count = 0; @@ -467,10 +468,15 @@ void fr_openssl_free(void) * exiting to prevent memory leaks. */ ERR_remove_thread_state(NULL); - ENGINE_cleanup(); + + fr_tls_engine_free_all(); + CONF_modules_unload(1); + ERR_free_strings(); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); TALLOC_FREE(global_mutexes); @@ -487,13 +493,16 @@ void fr_openssl_free(void) */ void fr_openssl_free(void) { + if (--instance_count > 0) return; + + fr_tls_engine_free_all(); + OPENSSL_cleanup(); + fr_dict_autofree(tls_dict); } #endif - - /** Add all the default ciphers and message digests to our context. * * This should be called exactly once from main, before reading the main config @@ -501,8 +510,6 @@ void fr_openssl_free(void) */ int fr_openssl_init(void) { - ENGINE *rand_engine; - if (instance_count > 0) { instance_count++; return 0; @@ -521,8 +528,6 @@ int fr_openssl_init(void) SSL_load_error_strings(); /* Readable error messages (examples show call before library_init) */ SSL_library_init(); /* Initialize library */ OpenSSL_add_all_algorithms(); /* Required for SHA2 in OpenSSL < 0.9.8o and 1.0.0.a */ - ENGINE_load_builtin_engines(); /* Needed to load AES-NI engine (also loads rdrand, boo) */ - # ifdef HAVE_OPENSSL_EVP_SHA256 /* * SHA256 is in all versions of OpenSSL, but isn't @@ -544,17 +549,14 @@ int fr_openssl_init(void) OPENSSL_config(NULL); #else - OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_ENGINE_ALL_BUILTIN, NULL); + OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL); #endif /* - * Mirror the paranoia found elsewhere on the net, - * and disable rdrand as the default random number - * generator. + * FIXME - This should be done _after_ + * running any engine controls. */ - rand_engine = ENGINE_get_default_RAND(); - if (rand_engine && (strcmp(ENGINE_get_id(rand_engine), "rdrand") == 0)) ENGINE_unregister_RAND(rand_engine); - ENGINE_register_all_complete(); + fr_tls_engine_load_builtin(); instance_count++; diff --git a/src/lib/tls/engine.c b/src/lib/tls/engine.c new file mode 100644 index 00000000000..ec7df9a2a1b --- /dev/null +++ b/src/lib/tls/engine.c @@ -0,0 +1,491 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file tls/engine.c + * @brief Initialise and manage OpenSSL engines + * + * @copyright 2021 The FreeRADIUS server project + * @copyright 2021 Arran Cudbard-Bell + */ +RCSID("$Id$") +USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + +#ifdef WITH_TLS +#define LOG_PREFIX "tls - " + +#include +#include +#include +#include +#include +#include +#include + +#include + +/** Our wrapper around an OpenSSL engine + * + */ +typedef struct { + fr_rb_node_t node; //!< rbtree node fields. + + char const *id; //!< Engine identifier. + char const *instance; //!< Instance identifier for the engine. + + ENGINE *e; //!< Engine that was loaded. + fr_tls_engine_ctrl_list_t *pre_ctrls; //!< Pre controls applied to the engine. + fr_tls_engine_ctrl_list_t *post_ctrls; //!< Post controls applied to the engine. +} tls_engine_t; + +/** Engines that we've loaded + * + * This is the global set of OpenSSL engines that are in use by the + * current configuration. + * + * We could use OpenSSL's reference counting system, but this doesn't + * work well for dynamically loaded engines, where we may want one + * instance of the engine per thread. + */ +static rbtree_t *tls_engines; + +/** Compares two engines + * + */ +static int tls_engine_cmp(void const *a, void const *b) +{ + tls_engine_t const *our_a = talloc_get_type_abort(a, tls_engine_t); + tls_engine_t const *our_b = talloc_get_type_abort(b, tls_engine_t); + int8_t ret; + + ret = strcmp(our_a->id, our_b->id); + if (ret != 0) return ret; + + /* + * May not have an instance ID + */ + if (!our_a->instance && !our_b->instance) return 0; + if (!our_a->instance) return -1; + if (!our_b->instance) return +1; + + return strcmp(our_a->instance, our_b->instance); +} + +/** Add the list of supported engine commands to the error stack + * + * Uses OpenSSL's ridiculously complex ENGINE_ctrl API to provide useful + * information about the controls the given engine provides. + * + * @param[in] e Engine to return commands for. + */ +static void CC_HINT(nonnull) tls_engine_control_notfound_strerror(ENGINE *e, char const *bad_ctrl) +{ + int cmd, ret; + TALLOC_CTX *pool; + + /* + * ENGINE_HAS_CTRL_FUNCTION doesn't seem + * to be available in OpenSSL 1.1.0 so + * we fudge it with this. + */ + bool first = true; + + pool = talloc_pool(NULL, 256); /* Avoid lots of mallocs */ + if (unlikely(!pool)) return; + + fr_strerror_printf("engine %s does not export control %s", ENGINE_get_id(e), bad_ctrl); + + for (cmd = ENGINE_ctrl(e, ENGINE_CTRL_GET_FIRST_CMD_TYPE, 0, NULL, NULL); + cmd > 0; + cmd = ENGINE_ctrl(e, ENGINE_CTRL_GET_NEXT_CMD_TYPE, cmd, NULL, NULL)) { + size_t name_len, desc_len; + char *name, *desc; + char const *flags; + + if (!ENGINE_cmd_is_executable(e, cmd)) continue; + + /* + * Copy the ctrl name out to a temporary buffer + */ + name_len = ENGINE_ctrl(e, ENGINE_CTRL_GET_NAME_LEN_FROM_CMD, 0, NULL, NULL); + if (unlikely(name_len == 0)) continue; + + name = talloc_array(pool, char, name_len + 1); + if (unlikely(!name)) break; + + if (unlikely(ENGINE_ctrl(e, ENGINE_CTRL_GET_NAME_FROM_CMD, 0, name, NULL) <= 0)) break; + + /* + * Copy the ctrl description out to a temporary buffer + */ + desc_len = ENGINE_ctrl(e, ENGINE_CTRL_GET_DESC_LEN_FROM_CMD, 0, NULL, NULL); + if (desc_len > 0) { + desc = talloc_array(pool, char, desc_len + 1); + if (unlikely(!desc)) break; + + if (unlikely(ENGINE_ctrl(e, ENGINE_CTRL_GET_DESC_FROM_CMD, 0, desc, NULL) <= 0)) break; + } else { + desc = NULL; + } + + ret = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FLAGS, 0, NULL, NULL); + if (ret & ENGINE_CMD_FLAG_NO_INPUT) { + flags = "none"; + } else if ((ret & ENGINE_CMD_FLAG_NUMERIC) && (ret & ENGINE_CMD_FLAG_STRING)) { + flags = "number and string"; + } else if (ret & ENGINE_CMD_FLAG_NUMERIC) { + flags = "number"; + } else if (ret & ENGINE_CMD_FLAG_STRING) { + flags = "string"; + } else { + flags = "unavailable"; + } + + if (first) { + fr_strerror_const_push("available controls are:"); + first = false; + } + fr_strerror_printf_push("%s, arg(s) %s%s%s", name, flags, desc ? " - " : "", desc); + talloc_free_children(pool); + } + if (first) fr_strerror_const_push("no controls available"); + + talloc_free(pool); +} + +/** Duplicate an engine control + * + * @param[in] ctx To allocate new control in. + * @param[in] in control to copy. + * @return + * - A copy of the engine control on success. + * - NULL on failure. + */ +static inline CC_HINT(always_inline) fr_tls_engine_ctrl_t *tls_engine_ctrl_dup(TALLOC_CTX *ctx, + fr_tls_engine_ctrl_t const *in) +{ + fr_tls_engine_ctrl_t *n; + + n = talloc(ctx, fr_tls_engine_ctrl_t); + if (unlikely(!n)) { + fr_strerror_const("Out of memory"); + return n; + } + + *n = (fr_tls_engine_ctrl_t){ + .name = talloc_typed_strdup(n, in->name), + .value = talloc_typed_strdup(n, in->value) + }; + + return n; +} + +/** Unloads the underlying OpenSSL engine + * + */ +static int _tls_engine_free(tls_engine_t *our_e) +{ + /* + * Make memory leaks very explicit + * so someone will investigate. + */ + if (unlikely(ENGINE_finish(our_e->e) != 1)) { + fr_tls_log_error(NULL, "de-init on engine %s failed", our_e->id); + return -1; + } + + if (unlikely(ENGINE_free(our_e->e) != 1)) { + fr_tls_log_error(NULL, "free on engine %s failed", our_e->id); + return -1; + } + + return 0; +} + +/** Initialise an OpenSSL engine, adding it to our list of engines + * + * @note Errors should be retrieved with fr_strerror(). + * + * @param[out] e_out The engine that was just initialised. + * The caller must not free/finish this engine + * it will be called up when the server exits. + * @param[in] id Engine identifier. This is usually identifier + * that matches OpenSSL's engine ID e.g. "pkcs11". + * @param[in] instance Instance identifier for a given engine. + * This is useful for "dynamic" engines, i.e. ones + * OpenSSL dynamically loads. + * @param[in] pre_ctrls Engine ctls to be used after obtaining a + * structural reference but before obtaining a + * functional reference (after loading before init). + * Will be duplicated to avoid ordering issues. + * @param[in] post_ctrls Engine ctls to be used before unloading an + * engine (to shut it down in a graceful way). + * Will be duplicated to avoid ordering issues. + * @return + * - 0 on success. + * - -1 on failure. + */ +int fr_tls_engine_init(ENGINE **e_out, + char const *id, char const *instance, + fr_tls_engine_ctrl_list_t const *pre_ctrls, fr_tls_engine_ctrl_list_t const *post_ctrls) +{ + tls_engine_t *our_e = NULL; + ENGINE *e; + fr_tls_engine_ctrl_t *ctrl, *n; + + if (!tls_engines) { + tls_engines = rbtree_alloc(NULL, tls_engine_t, node, tls_engine_cmp, rbtree_node_talloc_free, 0); + if (unlikely(!tls_engines)) { + oom: + fr_strerror_const("Out of memory"); + return -1; + } + } else { + tls_engine_t *found = NULL; + + found = rbtree_finddata(tls_engines, &(tls_engine_t){ .id = id, .instance = instance }); + if (found) { + fr_strerror_printf("engine %s%s%s%salready initialised", id, + instance ? " (" : "", + instance ? instance : "", + instance ? ") " : ""); + return -1; + } + } + + e = ENGINE_by_id(id); + if (!e) { + fr_strerror_printf("%s engine is not available", id); + return -1; + } + + if (pre_ctrls) while ((ctrl = fr_dlist_next(pre_ctrls, ctrl))) { + int cmd, flags, ret; + + cmd = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FROM_NAME, 0, UNCONST(void *, ctrl->name), NULL); + if (cmd == 0) { + fr_strerror_printf("%s engine does not implement \"%s\" control", id, ctrl->name); + /* + * Dumps all available controls to + * the error stack. + */ + tls_engine_control_notfound_strerror(e, ctrl->name); + error: + ENGINE_free(e); + return -1; + } + + /* + * If the command has the ENGINE_CMD_FLAG_NO_INPUT flag set, + * arg must be NULL and ENGINE_ctrl() is called with i set to + * 0 and p set to NULL. Otherwise, arg must not be NULL. + * If the command accepts string input, i is set to 0 and arg + * is passed as the p argument to ENGINE_ctrl(). Otherwise, arg + * is converted with strtol(3) and passed as the i argument to + * ENGINE_ctrl(), setting p to NULL. + */ + flags = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FLAGS, 0, NULL, NULL); + if (flags & ENGINE_CMD_FLAG_NO_INPUT) { + ret = ENGINE_ctrl(e, cmd, 0, NULL, NULL); + /* + * Do an explicit sanity check for this + */ + } else if (unlikely((flags & ENGINE_CMD_FLAG_STRING) && (flags & ENGINE_CMD_FLAG_NUMERIC))) { + fr_strerror_printf("File bug against freeradius-server stating " + "both numeric and string commands needed for OpenSSL engine controls"); + goto error; + /* + * We do an explicit conversion to provide more useful feedback + * to the user in case the log + */ + } else if (flags & ENGINE_CMD_FLAG_NUMERIC) { + fr_value_box_t vb; + + if (fr_value_box_cast(NULL, &vb, FR_TYPE_INT32, NULL, fr_box_strvalue(ctrl->value)) < 0) { + fr_strerror_printf_push("control %s requires an integer value", ctrl->name); + goto error; + } + ret = ENGINE_ctrl(e, cmd, vb.vb_int32, NULL, 0); + } else if (flags & ENGINE_CMD_FLAG_STRING) { + ret = ENGINE_ctrl(e, cmd, 0, UNCONST(void *, ctrl->value), NULL); + } else { + fr_strerror_printf("control %s exports invalid flags", ctrl->name); + goto error; + } + + /* + * ENGINE_ctrl_cmd() and ENGINE_ctrl_cmd_string() return 1 on + * success or 0 on error. + */ + if (ret != 1) { + tls_strerror_printf("control %s failed (%i)", ctrl->name, ret); + goto error; + } + } + + if (unlikely(ENGINE_init(e) != 1)) { + tls_strerror_printf("failed initialising engine %s", id); + goto error; + } + + our_e = talloc(tls_engines, tls_engine_t); + if (unlikely(!our_e)) goto oom; + + *our_e = (tls_engine_t){ + .id = talloc_typed_strdup(our_e, id), + .instance = talloc_typed_strdup(our_e, instance), + .e = e + }; + talloc_set_destructor(our_e, _tls_engine_free); + + /* + * Duplicate pre and post ctrl lists + * + * This will allow us to create thread-specific + * dynamic engines later. + */ + fr_dlist_talloc_init(our_e->pre_ctrls, fr_tls_engine_ctrl_t, entry); + fr_dlist_talloc_init(our_e->post_ctrls, fr_tls_engine_ctrl_t, entry); + + if (pre_ctrls) { + ctrl = NULL; + while ((ctrl = fr_dlist_next(pre_ctrls, ctrl))) { + n = tls_engine_ctrl_dup(our_e, ctrl); + if (unlikely(!n)) { + talloc_free(our_e); + return -1; + } + fr_dlist_insert_tail(our_e->pre_ctrls, n); + } + } + + if (post_ctrls) { + ctrl = NULL; + while ((ctrl = fr_dlist_next(post_ctrls, ctrl))) { + n = tls_engine_ctrl_dup(our_e, ctrl); + if (unlikely(!n)) { + talloc_free(our_e); + return -1; + } + fr_dlist_insert_tail(our_e->post_ctrls, n); + } + } + + *e_out = e; + return 0; +} + +/** Retrieve a pointer to an OpenSSL engine + * + * Does not change the reference count to the engine (we don't use this + * particular OpenSSL feature). + * + * If the engine is not found in the current engine tree and auto_init + * if true then it will be initialised with no pre or post ctrls. + * + * @note Errors should be retrieved with fr_strerror(). + * + * @param[out] e_out The engine that was just initialised. + * The caller must not free/finish this engine + * it will be called up when the server exits. + * @param[in] id Engine identifier. This is usually identifier + * that matches OpenSSL's engine ID e.g. "pkcs11". + * @param[in] instance Instance identifier for a given engine. + * This is useful for "dynamic" engines, i.e. ones + * OpenSSL dl loads. + * @param[in] auto_init If the engine hasn't already been initialised + * auto-initialise it now, with no pre or post + * ctrls. + * @return + * - 0 on success. + * - -1 on failure. + */ +int fr_tls_engine(ENGINE **e_out, char const *id, char const *instance, bool auto_init) +{ + tls_engine_t *found = NULL; + + if (!tls_engines) { + if (!auto_init) { + not_init: + fr_strerror_printf("engine %s%s%s%snot initialised", id, + instance ? " (" : "", + instance ? instance : "", + instance ? ") " : ""); + return -1; + } + + do_init: + return fr_tls_engine_init(e_out, id, instance, NULL, NULL); + } + + + found = rbtree_finddata(tls_engines, &(tls_engine_t){ .id = id, .instance = instance }); + if (!found) { + if (!auto_init) goto not_init; + goto do_init; + } + + *e_out = found->e; + return 0; +} + +/** Should be called after any engine configuration has been completed + * + */ +void fr_tls_engine_load_builtin(void) +{ + ENGINE_load_builtin_engines(); /* Needed to load AES-NI engine (also loads rdrand, boo) */ + + /* + * Mitigate against CrossTalk (CVE-2020-0543) + */ + if (!tls_engines || !rbtree_finddata(tls_engines, &(tls_engine_t){ .id = "rdrand" })) { + ENGINE *rand_engine; + + ENGINE_register_all_RAND(); /* Give rand engines a chance to register */ + + /* + * If OpenSSL settled on Intel's rdrand + * unregister it and unload rdrand. + */ + rand_engine = ENGINE_get_default_RAND(); + if (rand_engine && (strcmp(ENGINE_get_id(rand_engine), "rdrand") == 0)) { + ENGINE_unregister_RAND(rand_engine); + ENGINE_finish(rand_engine); /* Unload rdrand */ + } + } + ENGINE_register_all_complete(); +} + +/** Free any engines we've loaded + * + */ +void fr_tls_engine_free_all(void) +{ + TALLOC_FREE(tls_engines); + +#if OPENSSL_API_COMPAT < 0x10100000L + /* + * Free any lingering memory + * OpenSSL man pages say to do this. + */ + ENGINE_cleanup(); +#endif +} + +#endif diff --git a/src/lib/tls/engine.h b/src/lib/tls/engine.h new file mode 100644 index 00000000000..e2291a0cb6f --- /dev/null +++ b/src/lib/tls/engine.h @@ -0,0 +1,61 @@ +#pragma once +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#ifdef WITH_TLS +/** + * $Id$ + * + * @file lib/tls/engine.h + * @brief Initialise and manage OpenSSL engines + * + * @copyright 2021 The FreeRADIUS server project + * @copyright 2021 Arran Cudbard-Bell + */ +RCSIDH(tls_engine_h, "$Id$") + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Engine configuration parameters + * + * Used by fr_tls_global_engine_conf_t. + */ +typedef struct { + fr_dlist_t entry; //!< Entry in list of controls. + char const *name; //!< Name of control. + char const *value; //!< Value to pass to control. +} fr_tls_engine_ctrl_t; + +typedef fr_dlist_head_t fr_tls_engine_ctrl_list_t; + +int fr_tls_engine_init(ENGINE **e_out, + char const *id, char const *instance, + fr_tls_engine_ctrl_list_t const *pre_ctrls, fr_tls_engine_ctrl_list_t const *post_ctrls); + +int fr_tls_engine(ENGINE **e_out, char const *id, char const *instance, bool auto_init); + +void fr_tls_engine_load_builtin(void); + +void fr_tls_engine_free_all(void); + +#ifdef __cplusplus +} +#endif +#endif