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