The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
engine.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: 6267e530bb6b6102c5630b30becfbf269014360c $
19 *
20 * @file tls/engine.c
21 * @brief Initialise and manage OpenSSL engines
22 *
23 * @copyright 2021 The FreeRADIUS server project
24 * @copyright 2021 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
25 */
26RCSID("$Id: 6267e530bb6b6102c5630b30becfbf269014360c $")
27USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
28
29#ifdef WITH_TLS
30#define LOG_PREFIX "tls"
31
32#include <freeradius-devel/tls/base.h>
33#include <freeradius-devel/tls/engine.h>
34#include <freeradius-devel/tls/log.h>
35#include <freeradius-devel/tls/strerror.h>
36#include <freeradius-devel/util/dlist.h>
37#include <freeradius-devel/util/rb.h>
38#include <freeradius-devel/util/strerror.h>
39#include <freeradius-devel/util/value.h>
40
41#if OPENSSL_VERSION_NUMBER < 0x30000000L
42#include <openssl/engine.h>
43
44/** Our wrapper around an OpenSSL engine
45 *
46 */
47typedef struct {
48 fr_rb_node_t node; //!< rbtree node fields.
49
50 char const *id; //!< Engine identifier.
51 char const *instance; //!< Instance identifier for the engine.
52
53 ENGINE *e; //!< Engine that was loaded.
54 fr_tls_engine_ctrl_list_t *pre_ctrls; //!< Pre controls applied to the engine.
55 fr_tls_engine_ctrl_list_t *post_ctrls; //!< Post controls applied to the engine.
56} tls_engine_t;
57
58/** Engines that we've loaded
59 *
60 * This is the global set of OpenSSL engines that are in use by the
61 * current configuration.
62 *
63 * We could use OpenSSL's reference counting system, but this doesn't
64 * work well for dynamically loaded engines, where we may want one
65 * instance of the engine per thread.
66 */
67static fr_rb_tree_t *tls_engines;
68
69/** Compares two engines
70 *
71 */
72static int8_t tls_engine_cmp(void const *one, void const *two)
73{
74 tls_engine_t const *a = talloc_get_type_abort_const(one, tls_engine_t);
75 tls_engine_t const *b = talloc_get_type_abort_const(two, tls_engine_t);
76 int8_t ret;
77
78 ret = CMP(strcmp(a->id, b->id), 0);
79 if (ret != 0) return ret;
80
81 /*
82 * May not have an instance ID
83 */
84 if (!a->instance && !b->instance) return 0;
85 if (!a->instance) return -1;
86 if (!b->instance) return +1;
87
88 return CMP(strcmp(a->instance, b->instance), 0);
89}
90
91/** Add the list of supported engine commands to the error stack
92 *
93 * Uses OpenSSL's ridiculously complex ENGINE_ctrl API to provide useful
94 * information about the controls the given engine provides.
95 *
96 * @param[in] e Engine to return commands for.
97 */
98static void CC_HINT(nonnull) tls_engine_control_notfound_strerror(ENGINE *e, char const *bad_ctrl)
99{
100 int cmd, ret;
101 TALLOC_CTX *pool;
102
103 /*
104 * ENGINE_HAS_CTRL_FUNCTION doesn't seem
105 * to be available in OpenSSL 1.1.0 so
106 * we fudge it with this.
107 */
108 bool first = true;
109
110 pool = talloc_pool(NULL, 256); /* Avoid lots of mallocs */
111 if (unlikely(!pool)) return;
112
113 fr_strerror_printf("engine %s does not export control %s", ENGINE_get_id(e), bad_ctrl);
114
115 for (cmd = ENGINE_ctrl(e, ENGINE_CTRL_GET_FIRST_CMD_TYPE, 0, NULL, NULL);
116 cmd > 0;
117 cmd = ENGINE_ctrl(e, ENGINE_CTRL_GET_NEXT_CMD_TYPE, cmd, NULL, NULL)) {
118 size_t name_len, desc_len;
119 char *name, *desc;
120 char const *flags;
121
122 if (!ENGINE_cmd_is_executable(e, cmd)) continue;
123
124 /*
125 * Copy the ctrl name out to a temporary buffer
126 */
127 name_len = ENGINE_ctrl(e, ENGINE_CTRL_GET_NAME_LEN_FROM_CMD, 0, NULL, NULL);
128 if (unlikely(name_len == 0)) continue;
129
130 name = talloc_array(pool, char, name_len + 1);
131 if (unlikely(!name)) break;
132
133 if (unlikely(ENGINE_ctrl(e, ENGINE_CTRL_GET_NAME_FROM_CMD, 0, name, NULL) <= 0)) break;
134
135 /*
136 * Copy the ctrl description out to a temporary buffer
137 */
138 desc_len = ENGINE_ctrl(e, ENGINE_CTRL_GET_DESC_LEN_FROM_CMD, 0, NULL, NULL);
139 if (desc_len > 0) {
140 desc = talloc_array(pool, char, desc_len + 1);
141 if (unlikely(!desc)) break;
142
143 if (unlikely(ENGINE_ctrl(e, ENGINE_CTRL_GET_DESC_FROM_CMD, 0, desc, NULL) <= 0)) break;
144 } else {
145 desc = NULL;
146 }
147
148 ret = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FLAGS, 0, NULL, NULL);
149 if (ret & ENGINE_CMD_FLAG_NO_INPUT) {
150 flags = "none";
151 } else if ((ret & ENGINE_CMD_FLAG_NUMERIC) && (ret & ENGINE_CMD_FLAG_STRING)) {
152 flags = "number and string";
153 } else if (ret & ENGINE_CMD_FLAG_NUMERIC) {
154 flags = "number";
155 } else if (ret & ENGINE_CMD_FLAG_STRING) {
156 flags = "string";
157 } else {
158 flags = "unavailable";
159 }
160
161 if (first) {
162 fr_strerror_const_push("available controls are:");
163 first = false;
164 }
165 fr_strerror_printf_push("%s, arg(s) %s%s%s", name, flags, desc ? " - " : "", desc ? desc : "");
166 talloc_free_children(pool);
167 }
168 if (first) fr_strerror_const_push("no controls available");
169
170 talloc_free(pool);
171}
172
173/** Duplicate an engine control
174 *
175 * @param[in] ctx To allocate new control in.
176 * @param[in] in control to copy.
177 * @return
178 * - A copy of the engine control on success.
179 * - NULL on failure.
180 */
181static inline CC_HINT(always_inline) fr_tls_engine_ctrl_t *tls_engine_ctrl_dup(TALLOC_CTX *ctx,
182 fr_tls_engine_ctrl_t const *in)
183{
184 fr_tls_engine_ctrl_t *n;
185
186 n = talloc(ctx, fr_tls_engine_ctrl_t);
187 if (unlikely(!n)) {
188 fr_strerror_const("Out of memory");
189 return n;
190 }
191
192 *n = (fr_tls_engine_ctrl_t){
193 .name = talloc_typed_strdup(n, in->name),
194 .value = talloc_typed_strdup(n, in->value)
195 };
196
197 return n;
198}
199
200/** Unloads the underlying OpenSSL engine
201 *
202 */
203static int _tls_engine_free(tls_engine_t *our_e)
204{
205 /*
206 * Make memory leaks very explicit
207 * so someone will investigate.
208 */
209 if (unlikely(ENGINE_finish(our_e->e) != 1)) {
210 fr_tls_log(NULL, "de-init on engine %s failed", our_e->id);
211 return -1;
212 }
213
214 if (unlikely(ENGINE_free(our_e->e) != 1)) {
215 fr_tls_log(NULL, "free on engine %s failed", our_e->id);
216 return -1;
217 }
218
219 return 0;
220}
221
222/** Initialise an OpenSSL engine, adding it to our list of engines
223 *
224 * @note Errors should be retrieved with fr_strerror().
225 *
226 * @param[out] e_out The engine that was just initialised.
227 * The caller must not free/finish this engine
228 * it will be called up when the server exits.
229 * @param[in] id Engine identifier. This is usually identifier
230 * that matches OpenSSL's engine ID e.g. "pkcs11".
231 * @param[in] instance Instance identifier for a given engine.
232 * This is useful for "dynamic" engines, i.e. ones
233 * OpenSSL dynamically loads.
234 * @param[in] pre_ctrls Engine ctls to be used after obtaining a
235 * structural reference but before obtaining a
236 * functional reference (after loading before init).
237 * Will be duplicated to avoid ordering issues.
238 * @param[in] post_ctrls Engine ctls to be used before unloading an
239 * engine (to shut it down in a graceful way).
240 * Will be duplicated to avoid ordering issues.
241 * @return
242 * - 0 on success.
243 * - -1 on failure.
244 */
245int fr_tls_engine_init(ENGINE **e_out,
246 char const *id, char const *instance,
247 fr_tls_engine_ctrl_list_t const *pre_ctrls, fr_tls_engine_ctrl_list_t const *post_ctrls)
248{
249 tls_engine_t *our_e = NULL;
250 ENGINE *e;
251 fr_tls_engine_ctrl_t *ctrl = NULL, *n;
252
253 if (!tls_engines) {
254 tls_engines = fr_rb_inline_alloc(NULL, tls_engine_t, node, tls_engine_cmp, talloc_free_data);
255 if (unlikely(!tls_engines)) {
256 oom:
257 fr_strerror_const("Out of memory");
258 return -1;
259 }
260 } else {
261 tls_engine_t *found = NULL;
262
263 found = fr_rb_find(tls_engines, &(tls_engine_t){ .id = id, .instance = instance });
264 if (found) {
265 fr_strerror_printf("engine %s%s%s%salready initialised", id,
266 instance ? " (" : "",
267 instance ? instance : "",
268 instance ? ") " : "");
269 return -1;
270 }
271 }
272
273 e = ENGINE_by_id(id);
274 if (!e) {
275 fr_strerror_printf("%s engine is not available", id);
276 return -1;
277 }
278
279 if (pre_ctrls) while ((ctrl = fr_dlist_next(pre_ctrls, ctrl))) {
280 int cmd, flags, ret;
281
282 cmd = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FROM_NAME, 0, UNCONST(void *, ctrl->name), NULL);
283 if (cmd == 0) {
284 fr_strerror_printf("%s engine does not implement \"%s\" control", id, ctrl->name);
285 /*
286 * Dumps all available controls to
287 * the error stack.
288 */
289 tls_engine_control_notfound_strerror(e, ctrl->name);
290 error:
291 ENGINE_free(e);
292 return -1;
293 }
294
295 /*
296 * If the command has the ENGINE_CMD_FLAG_NO_INPUT flag set,
297 * arg must be NULL and ENGINE_ctrl() is called with i set to
298 * 0 and p set to NULL. Otherwise, arg must not be NULL.
299 * If the command accepts string input, i is set to 0 and arg
300 * is passed as the p argument to ENGINE_ctrl(). Otherwise, arg
301 * is converted with strtol(3) and passed as the i argument to
302 * ENGINE_ctrl(), setting p to NULL.
303 */
304 flags = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FLAGS, 0, NULL, NULL);
305 if (flags & ENGINE_CMD_FLAG_NO_INPUT) {
306 ret = ENGINE_ctrl(e, cmd, 0, NULL, NULL);
307 /*
308 * Do an explicit sanity check for this
309 */
310 } else if (unlikely((flags & ENGINE_CMD_FLAG_STRING) && (flags & ENGINE_CMD_FLAG_NUMERIC))) {
311 fr_strerror_printf("File bug against freeradius-server stating "
312 "both numeric and string commands needed for OpenSSL engine controls");
313 goto error;
314 /*
315 * We do an explicit conversion to provide more useful feedback
316 * to the user in case the log
317 */
318 } else if (flags & ENGINE_CMD_FLAG_NUMERIC) {
320
321 if (fr_value_box_cast(NULL, &vb, FR_TYPE_INT32, NULL, fr_box_strvalue(ctrl->value)) < 0) {
322 fr_strerror_printf_push("control %s requires an integer value", ctrl->name);
323 goto error;
324 }
325 ret = ENGINE_ctrl(e, cmd, vb.vb_int32, NULL, 0);
326 } else if (flags & ENGINE_CMD_FLAG_STRING) {
327 ret = ENGINE_ctrl(e, cmd, 0, UNCONST(void *, ctrl->value), NULL);
328 } else {
329 fr_strerror_printf("control %s exports invalid flags", ctrl->name);
330 goto error;
331 }
332
333 /*
334 * ENGINE_ctrl_cmd() and ENGINE_ctrl_cmd_string() return 1 on
335 * success or 0 on error.
336 */
337 if (ret != 1) {
338 fr_tls_strerror_printf("control %s failed (%i)", ctrl->name, ret);
339 goto error;
340 }
341 }
342
343 if (unlikely(ENGINE_init(e) != 1)) {
344 fr_tls_strerror_printf("failed initialising engine %s", id);
345 goto error;
346 }
347
348 our_e = talloc(tls_engines, tls_engine_t);
349 if (unlikely(!our_e)) goto oom;
350
351 *our_e = (tls_engine_t){
352 .id = talloc_typed_strdup(our_e, id),
353 .instance = talloc_typed_strdup(our_e, instance),
354 .e = e
355 };
356 talloc_set_destructor(our_e, _tls_engine_free);
357
358 /*
359 * Duplicate pre and post ctrl lists
360 *
361 * This will allow us to create thread-specific
362 * dynamic engines later.
363 */
364 fr_dlist_talloc_init(our_e->pre_ctrls, fr_tls_engine_ctrl_t, entry);
365 fr_dlist_talloc_init(our_e->post_ctrls, fr_tls_engine_ctrl_t, entry);
366
367 if (pre_ctrls) {
368 ctrl = NULL;
369 while ((ctrl = fr_dlist_next(pre_ctrls, ctrl))) {
370 n = tls_engine_ctrl_dup(our_e, ctrl);
371 if (unlikely(!n)) {
372 talloc_free(our_e);
373 return -1;
374 }
375 fr_dlist_insert_tail(our_e->pre_ctrls, n);
376 }
377 }
378
379 if (post_ctrls) {
380 ctrl = NULL;
381 while ((ctrl = fr_dlist_next(post_ctrls, ctrl))) {
382 n = tls_engine_ctrl_dup(our_e, ctrl);
383 if (unlikely(!n)) {
384 talloc_free(our_e);
385 return -1;
386 }
387 fr_dlist_insert_tail(our_e->post_ctrls, n);
388 }
389 }
390
391 *e_out = e;
392 return 0;
393}
394
395/** Retrieve a pointer to an OpenSSL engine
396 *
397 * Does not change the reference count to the engine (we don't use this
398 * particular OpenSSL feature).
399 *
400 * If the engine is not found in the current engine tree and auto_init
401 * if true then it will be initialised with no pre or post ctrls.
402 *
403 * @note Errors should be retrieved with fr_strerror().
404 *
405 * @param[out] e_out The engine that was just initialised.
406 * The caller must not free/finish this engine
407 * it will be called up when the server exits.
408 * @param[in] id Engine identifier. This is usually identifier
409 * that matches OpenSSL's engine ID e.g. "pkcs11".
410 * @param[in] instance Instance identifier for a given engine.
411 * This is useful for "dynamic" engines, i.e. ones
412 * OpenSSL dl loads.
413 * @param[in] auto_init If the engine hasn't already been initialised
414 * auto-initialise it now, with no pre or post
415 * ctrls.
416 * @return
417 * - 0 on success.
418 * - -1 on failure.
419 */
420int fr_tls_engine(ENGINE **e_out, char const *id, char const *instance, bool auto_init)
421{
422 tls_engine_t *found = NULL;
423
424 if (!tls_engines) {
425 if (!auto_init) {
426 not_init:
427 fr_strerror_printf("engine %s%s%s%snot initialised", id,
428 instance ? " (" : "",
429 instance ? instance : "",
430 instance ? ") " : "");
431 return -1;
432 }
433
434 do_init:
435 return fr_tls_engine_init(e_out, id, instance, NULL, NULL);
436 }
437
438
439 found = fr_rb_find(tls_engines, &(tls_engine_t){ .id = id, .instance = instance });
440 if (!found) {
441 if (!auto_init) goto not_init;
442 goto do_init;
443 }
444
445 *e_out = found->e;
446 return 0;
447}
448
449/** Should be called after any engine configuration has been completed
450 *
451 */
452void fr_tls_engine_load_builtin(void)
453{
454 ENGINE_load_builtin_engines(); /* Needed to load AES-NI engine (also loads rdrand, boo) */
455
456 /*
457 * Mitigate against CrossTalk (CVE-2020-0543)
458 */
459 if (!tls_engines || !fr_rb_find(tls_engines, &(tls_engine_t){ .id = "rdrand" })) {
460 ENGINE *rand_engine;
461
462 ENGINE_register_all_RAND(); /* Give rand engines a chance to register */
463
464 /*
465 * If OpenSSL settled on Intel's rdrand
466 * unregister it and unload rdrand.
467 */
468 rand_engine = ENGINE_get_default_RAND();
469 if (rand_engine && (strcmp(ENGINE_get_id(rand_engine), "rdrand") == 0)) {
470 ENGINE_unregister_RAND(rand_engine);
471 ENGINE_finish(rand_engine); /* Unload rdrand */
472 }
473 }
474 ENGINE_register_all_complete();
475}
476
477/** Free any engines we've loaded
478 *
479 */
480void fr_tls_engine_free_all(void)
481{
482 TALLOC_FREE(tls_engines);
483
484#if OPENSSL_VERSION_NUMBER < 0x10100000L
485 /*
486 * Free any lingering memory
487 * OpenSSL man pages say to do this.
488 */
489 ENGINE_cleanup();
490#endif
491}
492
493#endif
494#endif
int n
Definition acutest.h:577
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:167
#define USES_APPLE_DEPRECATED_API
Definition build.h:470
#define RCSID(id)
Definition build.h:483
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition build.h:112
#define unlikely(_x)
Definition build.h:381
static fr_slen_t in
Definition dict.h:824
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
Definition dlist.h:378
#define fr_dlist_talloc_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition dlist.h:275
static void * fr_dlist_next(fr_dlist_head_t const *list_head, void const *ptr)
Get the next item in a list.
Definition dlist.h:555
talloc_free(reap)
@ FR_TYPE_INT32
32 Bit signed integer.
void * fr_rb_find(fr_rb_tree_t const *tree, void const *data)
Find an element in the tree, returning the data, not the node.
Definition rb.c:577
#define fr_rb_inline_alloc(_ctx, _type, _field, _data_cmp, _data_free)
Allocs a red black tree.
Definition rb.h:271
The main red black tree structure.
Definition rb.h:73
static char const * name
char * talloc_typed_strdup(TALLOC_CTX *ctx, char const *p)
Call talloc_strdup, setting the type on the new chunk correctly.
Definition talloc.c:445
void talloc_free_data(void *data)
A wrapper that can be passed to tree or hash alloc functions that take a fr_free_t.
Definition talloc.c:41
#define talloc_get_type_abort_const
Definition talloc.h:282
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_printf_push(_fmt,...)
Add a message to an existing stack of messages at the tail.
Definition strerror.h:84
#define fr_strerror_const_push(_msg)
Definition strerror.h:227
#define fr_strerror_const(_msg)
Definition strerror.h:223
int fr_value_box_cast(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, fr_value_box_t const *src)
Convert one type of fr_value_box_t to another.
Definition value.c:3352
#define fr_box_strvalue(_val)
Definition value.h:285
int nonnull(2, 5))