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: d71f2ebb64c2a671da0ab93ac084a56c2ffaaf03 $
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: d71f2ebb64c2a671da0ab93ac084a56c2ffaaf03 $")
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 (void) fr_rb_remove(tls_engines, our_e);
206
207 /*
208 * Make memory leaks very explicit
209 * so someone will investigate.
210 */
211 if (unlikely(ENGINE_finish(our_e->e) != 1)) {
212 fr_tls_log(NULL, "de-init on engine %s failed", our_e->id);
213 return -1;
214 }
215
216 if (unlikely(ENGINE_free(our_e->e) != 1)) {
217 fr_tls_log(NULL, "free on engine %s failed", our_e->id);
218 return -1;
219 }
220
221 return 0;
222}
223
224/** Initialise an OpenSSL engine, adding it to our list of engines
225 *
226 * @note Errors should be retrieved with fr_strerror().
227 *
228 * @param[out] e_out The engine that was just initialised.
229 * The caller must not free/finish this engine
230 * it will be called up when the server exits.
231 * @param[in] id Engine identifier. This is usually identifier
232 * that matches OpenSSL's engine ID e.g. "pkcs11".
233 * @param[in] instance Instance identifier for a given engine.
234 * This is useful for "dynamic" engines, i.e. ones
235 * OpenSSL dynamically loads.
236 * @param[in] pre_ctrls Engine ctls to be used after obtaining a
237 * structural reference but before obtaining a
238 * functional reference (after loading before init).
239 * Will be duplicated to avoid ordering issues.
240 * @param[in] post_ctrls Engine ctls to be used before unloading an
241 * engine (to shut it down in a graceful way).
242 * Will be duplicated to avoid ordering issues.
243 * @return
244 * - 0 on success.
245 * - -1 on failure.
246 */
247int fr_tls_engine_init(ENGINE **e_out,
248 char const *id, char const *instance,
249 fr_tls_engine_ctrl_list_t const *pre_ctrls, fr_tls_engine_ctrl_list_t const *post_ctrls)
250{
251 tls_engine_t *our_e = NULL;
252 ENGINE *e;
253 fr_tls_engine_ctrl_t *ctrl = NULL, *n;
254
255 if (!tls_engines) {
256 tls_engines = fr_rb_inline_alloc(NULL, tls_engine_t, node, tls_engine_cmp, talloc_free_data);
257 if (unlikely(!tls_engines)) {
258 oom:
259 fr_strerror_const("Out of memory");
260 return -1;
261 }
262 } else {
263 tls_engine_t *found = NULL;
264
265 found = fr_rb_find(tls_engines, &(tls_engine_t){ .id = id, .instance = instance });
266 if (found) {
267 fr_strerror_printf("engine %s%s%s%salready initialised", id,
268 instance ? " (" : "",
269 instance ? instance : "",
270 instance ? ") " : "");
271 return -1;
272 }
273 }
274
275 e = ENGINE_by_id(id);
276 if (!e) {
277 fr_strerror_printf("%s engine is not available", id);
278 return -1;
279 }
280
281 if (pre_ctrls) while ((ctrl = fr_dlist_next(pre_ctrls, ctrl))) {
282 int cmd, flags, ret;
283
284 cmd = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FROM_NAME, 0, UNCONST(void *, ctrl->name), NULL);
285 if (cmd == 0) {
286 fr_strerror_printf("%s engine does not implement \"%s\" control", id, ctrl->name);
287 /*
288 * Dumps all available controls to
289 * the error stack.
290 */
291 tls_engine_control_notfound_strerror(e, ctrl->name);
292 error:
293 ENGINE_free(e);
294 return -1;
295 }
296
297 /*
298 * If the command has the ENGINE_CMD_FLAG_NO_INPUT flag set,
299 * arg must be NULL and ENGINE_ctrl() is called with i set to
300 * 0 and p set to NULL. Otherwise, arg must not be NULL.
301 * If the command accepts string input, i is set to 0 and arg
302 * is passed as the p argument to ENGINE_ctrl(). Otherwise, arg
303 * is converted with strtol(3) and passed as the i argument to
304 * ENGINE_ctrl(), setting p to NULL.
305 */
306 flags = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FLAGS, 0, NULL, NULL);
307 if (flags & ENGINE_CMD_FLAG_NO_INPUT) {
308 ret = ENGINE_ctrl(e, cmd, 0, NULL, NULL);
309 /*
310 * Do an explicit sanity check for this
311 */
312 } else if (unlikely((flags & ENGINE_CMD_FLAG_STRING) && (flags & ENGINE_CMD_FLAG_NUMERIC))) {
313 fr_strerror_printf("File bug against freeradius-server stating "
314 "both numeric and string commands needed for OpenSSL engine controls");
315 goto error;
316 /*
317 * We do an explicit conversion to provide more useful feedback
318 * to the user in case the log
319 */
320 } else if (flags & ENGINE_CMD_FLAG_NUMERIC) {
322
323 if (fr_value_box_cast(NULL, &vb, FR_TYPE_INT32, NULL, fr_box_strvalue(ctrl->value)) < 0) {
324 fr_strerror_printf_push("control %s requires an integer value", ctrl->name);
325 goto error;
326 }
327 ret = ENGINE_ctrl(e, cmd, vb.vb_int32, NULL, 0);
328 } else if (flags & ENGINE_CMD_FLAG_STRING) {
329 ret = ENGINE_ctrl(e, cmd, 0, UNCONST(void *, ctrl->value), NULL);
330 } else {
331 fr_strerror_printf("control %s exports invalid flags", ctrl->name);
332 goto error;
333 }
334
335 /*
336 * ENGINE_ctrl_cmd() and ENGINE_ctrl_cmd_string() return 1 on
337 * success or 0 on error.
338 */
339 if (ret != 1) {
340 fr_tls_strerror_printf("control %s failed (%i)", ctrl->name, ret);
341 goto error;
342 }
343 }
344
345 if (unlikely(ENGINE_init(e) != 1)) {
346 fr_tls_strerror_printf("failed initialising engine %s", id);
347 goto error;
348 }
349
350 our_e = talloc(tls_engines, tls_engine_t);
351 if (unlikely(!our_e)) goto oom;
352
353 *our_e = (tls_engine_t){
354 .id = talloc_typed_strdup(our_e, id),
355 .instance = talloc_typed_strdup(our_e, instance),
356 .e = e
357 };
358 talloc_set_destructor(our_e, _tls_engine_free);
359
360 /*
361 * Duplicate pre and post ctrl lists
362 *
363 * This will allow us to create thread-specific
364 * dynamic engines later.
365 */
366 fr_dlist_talloc_init(our_e->pre_ctrls, fr_tls_engine_ctrl_t, entry);
367 fr_dlist_talloc_init(our_e->post_ctrls, fr_tls_engine_ctrl_t, entry);
368
369 if (pre_ctrls) {
370 ctrl = NULL;
371 while ((ctrl = fr_dlist_next(pre_ctrls, ctrl))) {
372 n = tls_engine_ctrl_dup(our_e, ctrl);
373 if (unlikely(!n)) {
374 talloc_free(our_e);
375 return -1;
376 }
377 fr_dlist_insert_tail(our_e->pre_ctrls, n);
378 }
379 }
380
381 if (post_ctrls) {
382 ctrl = NULL;
383 while ((ctrl = fr_dlist_next(post_ctrls, ctrl))) {
384 n = tls_engine_ctrl_dup(our_e, ctrl);
385 if (unlikely(!n)) {
386 talloc_free(our_e);
387 return -1;
388 }
389 fr_dlist_insert_tail(our_e->post_ctrls, n);
390 }
391 }
392
393 if (!fr_rb_insert(tls_engines, our_e)) {
394 talloc_free(our_e);
395 return -1;
396 }
397
398 *e_out = e;
399 return 0;
400}
401
402/** Retrieve a pointer to an OpenSSL engine
403 *
404 * Does not change the reference count to the engine (we don't use this
405 * particular OpenSSL feature).
406 *
407 * If the engine is not found in the current engine tree and auto_init
408 * if true then it will be initialised with no pre or post ctrls.
409 *
410 * @note Errors should be retrieved with fr_strerror().
411 *
412 * @param[out] e_out The engine that was just initialised.
413 * The caller must not free/finish this engine
414 * it will be called up when the server exits.
415 * @param[in] id Engine identifier. This is usually identifier
416 * that matches OpenSSL's engine ID e.g. "pkcs11".
417 * @param[in] instance Instance identifier for a given engine.
418 * This is useful for "dynamic" engines, i.e. ones
419 * OpenSSL dl loads.
420 * @param[in] auto_init If the engine hasn't already been initialised
421 * auto-initialise it now, with no pre or post
422 * ctrls.
423 * @return
424 * - 0 on success.
425 * - -1 on failure.
426 */
427int fr_tls_engine(ENGINE **e_out, char const *id, char const *instance, bool auto_init)
428{
429 tls_engine_t *found = NULL;
430
431 if (!tls_engines) {
432 if (!auto_init) {
433 not_init:
434 fr_strerror_printf("engine %s%s%s%snot initialised", id,
435 instance ? " (" : "",
436 instance ? instance : "",
437 instance ? ") " : "");
438 return -1;
439 }
440
441 do_init:
442 return fr_tls_engine_init(e_out, id, instance, NULL, NULL);
443 }
444
445
446 found = fr_rb_find(tls_engines, &(tls_engine_t){ .id = id, .instance = instance });
447 if (!found) {
448 if (!auto_init) goto not_init;
449 goto do_init;
450 }
451
452 *e_out = found->e;
453 return 0;
454}
455
456/** Should be called after any engine configuration has been completed
457 *
458 */
459void fr_tls_engine_load_builtin(void)
460{
461 ENGINE_load_builtin_engines(); /* Needed to load AES-NI engine (also loads rdrand, boo) */
462
463 /*
464 * Mitigate against CrossTalk (CVE-2020-0543)
465 */
466 if (!tls_engines || !fr_rb_find(tls_engines, &(tls_engine_t){ .id = "rdrand" })) {
467 ENGINE *rand_engine;
468
469 ENGINE_register_all_RAND(); /* Give rand engines a chance to register */
470
471 /*
472 * If OpenSSL settled on Intel's rdrand,
473 * unregister it and unload rdrand.
474 */
475 rand_engine = ENGINE_get_default_RAND();
476 if (!rand_engine) break;
477
478 if(strcmp(ENGINE_get_id(rand_engine), "rdrand") == 0) {
479 ENGINE_unregister_RAND(rand_engine); /* unregister it */
480 }
481
482 /*
483 * Remove our reference to the engine we found.
484 */
485 ENGINE_finish(rand_engine);
486 ENGINE_free(rand_engine);
487 }
488 ENGINE_register_all_complete();
489}
490
491/** Free any engines we've loaded
492 *
493 */
494void fr_tls_engine_free_all(void)
495{
496 TALLOC_FREE(tls_engines);
497
498#if OPENSSL_VERSION_NUMBER < 0x10100000L
499 /*
500 * Free any lingering memory
501 * OpenSSL man pages say to do this.
502 */
503 ENGINE_cleanup();
504#endif
505}
506
507#endif
508#endif
int n
Definition acutest.h:579
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:167
#define USES_APPLE_DEPRECATED_API
Definition build.h:474
#define RCSID(id)
Definition build.h:487
#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:383
static fr_slen_t in
Definition dict.h:884
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.
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: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:468
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:245
#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:3961
#define fr_box_strvalue(_val)
Definition value.h:308
int nonnull(2, 5))