The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
ticket.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: b6d1caa2bc507f44a7cdb60e47e3c0bfd894eb31 $
19 *
20 * @file tls/ticket.c
21 * @brief Functions shared by stateful and stateless TLS session resumption
22 *
23 * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
25 */
26RCSID("$Id: b6d1caa2bc507f44a7cdb60e47e3c0bfd894eb31 $")
27USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
28
29#ifdef WITH_TLS
30#define LOG_PREFIX "tls"
31#define _TLS_PRIVATE 1
32
33#include <freeradius-devel/internal/internal.h>
34#include <freeradius-devel/server/pair.h>
35#include <freeradius-devel/server/module_rlm.h>
36#include <freeradius-devel/unlang/function.h>
37#include <freeradius-devel/unlang/subrequest.h>
38#include <freeradius-devel/util/debug.h>
39
40#include <freeradius-devel/protocol/tls/freeradius.h>
41
42#include "attrs.h"
43#include "base.h"
44#include "ticket.h"
45#include "log.h"
46#include "strerror.h"
47#include "verify.h"
48
49#include <openssl/ssl.h>
50
51/** Check if TLS caching is disabled.
52 *
53 * The TLS cache can be disabled for a host of reasons. Using a macro
54 * lets us check all of them at once:
55 *
56 * - there is no cache configuration
57 * - there is no virtual_server to run when poking the cache
58 *
59 * This check is only for static configuration. A particular session
60 * can still be marked as !tls_session->allow_session_resumption. In
61 * which case we don't load any cache entries, but we may still clear
62 * them.
63 */
64
65/** Copy the ID of a session into a box
66 *
67 * @param[in] ctx to allocate the ID in.
68 * @param[out] out box to fill. Left as-is when the session has no ID.
69 * @param[in] sess to retrieve the ID from.
70 * @return
71 * - 0 on success.
72 * - -1 if the session had no ID.
73 */
74int tls_ticket_id_to_box(TALLOC_CTX *ctx, fr_value_box_t *out, SSL_SESSION *sess)
75{
76 unsigned int len;
77 uint8_t const *id;
78
79 id = SSL_SESSION_get_id(sess, &len);
80 if (unlikely(!id)) return -1;
81
82 MEM(fr_value_box_memdup(ctx, out, NULL, id, len, true) == 0);
83
84 return 0;
85}
86
87/** Cache the ID of a session, if it is not cached already
88 *
89 * A session ID is fixed for the life of the session, so whichever code path
90 * sees the session first records the ID, and everything after that logs the
91 * cached copy instead of asking OpenSSL again.
92 *
93 * @param[in] tls_session to cache the ID in.
94 * @param[in] sess to take the ID from. May be NULL.
95 */
96void tls_session_id_cache(fr_tls_session_t *tls_session, SSL_SESSION *sess)
97{
98 if (!sess || !fr_type_is_null(tls_session->session_id.type)) return;
99
100 if (tls_ticket_id_to_box(tls_session, &tls_session->session_id, sess) < 0) return;
101
102 /*
103 * Copy the ID to the cache, instead of pointing the cache at the session.
104 */
105 if (tls_session->cache) tls_session->cache->session_id = &tls_session->session_id;
106}
107
108/** Allocate and initialize a subrequest for one of the TLS policy sections
109 *
110 * - allocate a subrequest
111 * - set the packet type
112 * - set the session ID
113 *
114 * @param[in] parent both allocation context and holds the subrequest stack frame
115 * @param[in] packet_type which section to run, as an
116 * enum_tls_packet_type_* value.
117 * @param[in] id of the session, or #FR_TYPE_NULL when there is none.
118 * @return the child request.
119 */
120request_t *tls_subrequest_alloc(request_t *parent, uint32_t packet_type, fr_value_box_t const *id)
121{
123 fr_pair_t *vp;
124
126
127 /*
128 * Setup the child request for the section being called.
129 */
131 vp->vp_uint32 = packet_type;
132
133 /*
134 * Add the session ID, if it exists. A session gets an
135 * ID only after it's established. And it can fail
136 * before then. So the ID might not exist.
137 *
138 * The ID is supplied by the peer, and is therefore
139 * tainted.
140 */
141 if (!fr_type_is_null(id->type)) {
143 fr_pair_value_memdup(vp, id->vb_octets, id->vb_length, true);
144 }
145
146 return request;
147}
148
149/** Serialize the session-state list and store it in the SSL_SESSION *
150 *
151 */
152int tls_ticket_app_data_set(request_t *request, SSL_SESSION *sess,
153 fr_value_box_t const *session_id)
154{
155 fr_dbuff_t dbuff;
156 fr_dbuff_uctx_talloc_t tctx;
157 fr_dcursor_t dcursor;
158 fr_pair_t *vp;
159 ssize_t slen;
160 int ret;
161
162 if (RDEBUG_ENABLED2) {
163 RDEBUG2("(TLS) Session ID %pV - Adding session-state[*] to data", session_id);
164 RINDENT();
165 log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->session_state_pairs, NULL);
166 REXDENT();
167 }
168
169 /*
170 * Absolute maximum is `0..2^16-1`.
171 *
172 * We leave OpenSSL 2k to add anything else
173 */
174 MEM(fr_dbuff_init_talloc(NULL, &dbuff, &tctx, 1024, 1024 * 62));
175
176 /*
177 * Encode the session-state contents and add it to the ticket.
178 */
179 for (vp = fr_pair_dcursor_init(&dcursor, &request->session_state_pairs);
180 vp;
181 vp = fr_dcursor_current(&dcursor)) {
182 slen = fr_internal_encode_pair(&dbuff, &dcursor, NULL);
183 if (slen < 0) {
184 RPERROR("(TLS) Session ID %pV - Failed serialising session-state list", session_id);
185 fr_dbuff_free_talloc(&dbuff);
186 return 0; /* didn't store data */
187 }
188 if (slen == 0) (void) fr_dcursor_next(&dcursor);
189 }
190
191 RHEXDUMP4(fr_dbuff_start(&dbuff), fr_dbuff_used(&dbuff), "(TLS) session-ticket application data");
192
193 /*
194 * Pass the serialized session-state list
195 * over to OpenSSL.
196 */
197 ret = SSL_SESSION_set1_ticket_appdata(sess, fr_dbuff_start(&dbuff), fr_dbuff_used(&dbuff));
198 fr_dbuff_free_talloc(&dbuff); /* OpenSSL memdups the data */
199 if (ret != 1) {
200 fr_tls_log_perror(request, "Session ID %pV - Failed setting application data", session_id);
201 return -1;
202 }
203
204 return 1; /* successfully stored data */
205}
206
207int tls_ticket_app_data_get(request_t *request, SSL_SESSION *sess,
208 fr_value_box_t const *session_id)
209{
210 uint8_t *data;
211 size_t data_len;
212 fr_dbuff_t dbuff;
213 fr_pair_list_t tmp;
214
215 /*
216 * Extract the session-state list from the ticket.
217 */
218 if (SSL_SESSION_get0_ticket_appdata(sess, (void **)&data, &data_len) != 1) {
219 fr_tls_log_perror(request, "Session ID %pV - Failed retrieving application data", session_id);
220 return -1;
221 }
222
223 if (unlikely(!data)) {
224 fr_tls_log_perror(request, "Session ID %pV - Got NULL session application data", session_id);
225 return -1;
226 }
227
228 fr_pair_list_init(&tmp);
229 fr_dbuff_init(&dbuff, data, data_len);
230
231 RHEXDUMP4(fr_dbuff_start(&dbuff), fr_dbuff_len(&dbuff), "(TLS) session application data");
232
233 /*
234 * Decode the session-state data into a temporary list.
235 *
236 * It's very important that we decode _all_ attributes,
237 * or disallow session resumption.
238 */
239 while (fr_dbuff_remaining(&dbuff) > 0) {
240 if (fr_internal_decode_pair_dbuff(request->session_state_ctx, &tmp,
241 fr_dict_root(request->proto_dict), &dbuff, NULL) < 0) {
242 fr_pair_list_free(&tmp);
243 RPEDEBUG("(TLS) Session-ID %pV - Failed decoding session-state", session_id);
244 fr_tls_session_error_add(request, FR_ERROR_VALUE_SESSION_DATA_DECODE_FAILED);
245 return -1;
246 }
247 }
248
249 if (RDEBUG_ENABLED2) {
250 RDEBUG2("(TLS) Session-ID %pV - Restoring session-state[*]", session_id);
251 RINDENT();
252 log_request_pair_list(L_DBG_LVL_2, request, NULL, &tmp, "session-state.");
253 REXDENT();
254 }
255
256 fr_pair_list_append(&request->session_state_pairs, &tmp);
257
258 return 0;
259}
260
261/** Enforce lifetime on a TLS session ticket.
262 *
263 * There are three limits, and we pick the lowest one.
264 *
265 * - the lifetime OpenSSL recorded in the session itself,
266 * - `lifetime` from the configuration, so that a policy change reaches
267 * sessions which were stored before it,
268 * - FR_TLS_MAX_SESSION_LIFETIME (7 days), which is required by RFC
269 * 8446 for TLS and by RFC 9190 for EAP-TLS.
270 *
271 * @param[in] request for logging.
272 * @param[in] session_id of the session, for logging.
273 * @param[in] conf holding the configured lifetime.
274 * @param[in] sess to examine.
275 * @return
276 * - how long the session has left, if it is still live.
277 * - <= 0 if the session has expired.
278 */
279fr_time_delta_t tls_ticket_session_lifetime(request_t *request, fr_value_box_t const *session_id,
280 fr_tls_conf_t const *conf, SSL_SESSION *sess)
281{
282 time_t timeout = SSL_get_timeout(sess);
283 time_t lifetime = (time_t) fr_time_delta_to_sec(conf->cache.lifetime);
284 fr_time_t expires;
285
286 if (lifetime && (lifetime < timeout)) timeout = lifetime;
287
288 if (timeout > FR_TLS_MAX_SESSION_LIFETIME) {
289 RWDEBUG("(TLS) Session ID %pV - Session lifetime %pV is longer than the maximum of %pV, limiting it",
290 session_id, fr_box_time_delta(fr_time_delta_from_sec(timeout)),
291 fr_box_time_delta(fr_time_delta_from_sec(FR_TLS_MAX_SESSION_LIFETIME)));
292
293 timeout = FR_TLS_MAX_SESSION_LIFETIME;
294 }
295
296#if OPENSSL_VERSION_NUMBER >= 0x30400000L
297 expires = fr_time_from_sec((time_t)(SSL_SESSION_get_time_ex(sess) + timeout));
298#else
299 expires = fr_time_from_sec((time_t)(SSL_SESSION_get_time(sess) + timeout));
300#endif
301
302 return fr_time_sub(expires, fr_time());
303}
304
305/** Is the session ticket resumable?
306 *
307 * If the ticket is close to expiry, then we just force a full
308 * re-authentication.
309 *
310 * @param[in] request to log through.
311 * @param[in] session_id of the session, for the log message.
312 * @param[in] conf holding `lifetime` and `min_lifetime`.
313 * @param[in] sess to examine.
314 * @return
315 * - true if the session has at least `min_lifetime` left.
316 * - false if it has less, or has expired.
317 */
318bool tls_ticket_session_resumable(request_t *request, fr_value_box_t const *session_id,
319 fr_tls_conf_t const *conf, SSL_SESSION *sess)
320{
321 fr_time_delta_t left = tls_ticket_session_lifetime(request, session_id, conf, sess);
322
323 if (!fr_time_delta_ispos(left)) return false;
324
325 if (fr_time_delta_lt(left, conf->cache.min_lifetime)) {
326 RDEBUG2("(TLS) Session ID %pV - %pV left is less than min_lifetime of %pV",
327 session_id, fr_box_time_delta(left), fr_box_time_delta(conf->cache.min_lifetime));
328 return false;
329 }
330
331 return true;
332}
333
334/** Prevent a TLS session from being resumed in future
335 *
336 * @note In OpenSSL > 1.1.0 this should not be called directly, but passed as a callback to
337 * SSL_CTX_set_not_resumable_session_callback.
338 *
339 * @param ssl The current OpenSSL session.
340 * @param is_forward_secure Whether the cipher is forward secure, pass -1 if unknown.
341 * @return
342 * - 0 if session-resumption is allowed.
343 * - 1 if enabling session-resumption was disabled for this session.
344 */
345int fr_tls_ticket_disable_cb(SSL *ssl, int is_forward_secure)
346{
348
349 fr_tls_session_t *tls_session;
350 fr_pair_t *vp;
351
352 tls_session = fr_tls_session(ssl);
353 request = fr_tls_session_request(tls_session->ssl);
354
355 /*
356 * Request was cancelled, try and get OpenSSL to
357 * do as little work as possible.
358 */
360
361 {
362 fr_tls_conf_t *conf;
363
364 conf = talloc_get_type_abort(SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF), fr_tls_conf_t);
365 if (conf->cache.require_extms && (SSL_get_extms_support(tls_session->ssl) == 0)) {
366 RDEBUG2("(TLS) Client does not support the Extended Master Secret extension, "
367 "denying session resumption");
368 goto disable;
369 }
370
371 if (conf->cache.require_pfs && !is_forward_secure) {
372 RDEBUG2("(TLS) Cipher suite is not forward secure, denying session resumption");
373 goto disable;
374 }
375 }
376
377 /*
378 * If there's no session resumption, delete the entry
379 * from the cache. This means either it's disabled
380 * globally for this SSL context, OR we were told to
381 * disable it for this user.
382 *
383 * This also means you can't turn it on just for one
384 * user.
385 */
386 if (!tls_session->allow_session_resumption) {
387 RDEBUG2("(TLS) Session resumption not enabled for this TLS session, denying session resumption");
388 goto disable;
389 }
390
392 if (vp && (vp->vp_uint32 == 0)) {
393 RDEBUG2("(TLS) control.Allow-Session-Resumption == no, denying session resumption");
394 disable:
395 SSL_CTX_remove_session(tls_session->ctx, tls_session->session);
396 tls_session->allow_session_resumption = false;
397 return 1;
398 }
399
400 RDEBUG2("(TLS) Allowing future session-resumption");
401
402 return 0;
403}
404
405/** Sets callbacks and flags on a SSL_CTX to enable/disable session resumption
406 *
407 * @param[in] ctx to modify.
408 * @param[in] cache_conf Session caching configuration.
409 * @return
410 * - 0 on success.
411 * - -1 on failure.
412 */
413int fr_tls_ticket_ctx_init(SSL_CTX *ctx, fr_tls_ticket_conf_t const *cache_conf, bool client)
414{
415 switch (cache_conf->mode) {
416 case FR_TLS_TICKET_DISABLED:
417 fr_tls_ticket_stateless_disable(ctx, false);
418 fr_tls_ticket_stateful_disable(ctx);
419 return 0;
420
421 case FR_TLS_TICKET_AUTO:
422 case FR_TLS_TICKET_STATEFUL:
423 fr_tls_ticket_stateful_ctx_init(ctx, cache_conf, client);
424
425 /*
426 * Disables stateless session tickets for TLS 1.3.
427 */
428 if (!(cache_conf->mode & FR_TLS_TICKET_STATELESS)) {
429 fr_tls_ticket_stateless_disable(ctx, true);
430 break;
431 }
433
434 case FR_TLS_TICKET_STATELESS:
435 {
436 /*
437 * For stateless session tickets, the server
438 * doesn't call `load session` or `store
439 * session`. Don't register those callbacks.
440 *
441 * For stateless session tickets, the client has
442 * to store the ticket somewhere, so that it's
443 * read back on the next session.
444 *
445 * The client therefore runs the `store session`
446 * and `load session` policies, even for
447 * stateless session resumption.
448 */
449 if (!(cache_conf->mode & FR_TLS_TICKET_STATEFUL)) {
450 if (!client) {
451 fr_tls_ticket_stateful_disable(ctx);
452 } else {
453 fr_tls_ticket_stateful_ctx_init(ctx, cache_conf, client);
454 }
455 }
456
457 if (fr_tls_ticket_stateless_ctx_init(ctx, cache_conf) < 0) return -1;
458 }
459 break;
460 }
461
462 SSL_CTX_set_not_resumable_session_callback(ctx, fr_tls_ticket_disable_cb);
463 SSL_CTX_set_quiet_shutdown(ctx, 1);
464
465 return 0;
466}
467#endif /* WITH_TLS */
static uint8_t request[4]
#define USES_APPLE_DEPRECATED_API
Definition build.h:590
#define RCSID(id)
Definition build.h:603
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:422
#define unlikely(_x)
Definition build.h:486
#define fr_dbuff_used(_dbuff_or_marker)
Return the number of bytes remaining between the start of the dbuff or marker and the current positio...
Definition dbuff.h:819
static void fr_dbuff_free_talloc(fr_dbuff_t *dbuff)
Free the talloc buffer associated with a dbuff.
Definition dbuff.h:495
#define fr_dbuff_len(_dbuff_or_marker)
The length of the underlying buffer.
Definition dbuff.h:828
#define fr_dbuff_init(_out, _start, _len_or_end)
Initialise an dbuff for encoding or decoding.
Definition dbuff.h:387
#define fr_dbuff_start(_dbuff_or_marker)
Return the 'start' position of a dbuff or marker.
Definition dbuff.h:950
#define fr_dbuff_remaining(_dbuff_or_marker)
Return the number of bytes remaining between the dbuff or marker and the end of the buffer.
Definition dbuff.h:795
static fr_dbuff_t * fr_dbuff_init_talloc(TALLOC_CTX *ctx, fr_dbuff_t *dbuff, fr_dbuff_uctx_talloc_t *tctx, size_t init, size_t max)
Initialise a special dbuff which automatically extends as additional data is written.
Definition dbuff.h:453
static void * fr_dcursor_next(fr_dcursor_t *cursor)
Advanced the cursor to the next item.
Definition dcursor.h:288
static void * fr_dcursor_current(fr_dcursor_t *cursor)
Return the item the cursor current points to.
Definition dcursor.h:337
#define MEM(x)
Definition debug.h:38
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition dict_util.c:2717
bool unlang_request_is_cancelled(request_t const *request)
Return whether a request has been cancelled.
Definition interpret.c:1971
void log_request_pair_list(fr_log_lvl_t lvl, request_t *request, fr_pair_t const *parent, fr_pair_list_t const *vps, char const *prefix)
Print a fr_pair_list_t.
Definition log.c:831
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition log.h:460
#define RWDEBUG(fmt,...)
Definition log.h:378
#define RHEXDUMP4(_data, _len, _fmt,...)
Definition log.h:723
#define RPERROR(fmt,...)
Definition log.h:319
#define RPEDEBUG(fmt,...)
Definition log.h:393
#define RINDENT()
Indent R* messages by one level.
Definition log.h:447
HIDDEN fr_dict_attr_t const * attr_tls_packet_type
HIDDEN fr_dict_attr_t const * attr_tls_session_id
HIDDEN fr_dict_attr_t const * attr_allow_session_resumption
#define fr_time()
Definition event.c:58
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition log.h:68
unsigned int uint32_t
long int ssize_t
unsigned char uint8_t
int fr_pair_value_memdup(fr_pair_t *vp, uint8_t const *src, size_t len, bool tainted)
Copy data into an "octets" data type.
Definition pair.c:2897
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:707
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:47
ssize_t fr_internal_decode_pair_dbuff(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *dbuff, void *decode_ctx)
Definition decode.c:281
ssize_t fr_internal_encode_pair(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, void *encode_ctx)
Encode a data structure into an internal attribute.
Definition encode.c:282
#define RDEBUG_ENABLED2()
#define RDEBUG2(fmt,...)
static rs_t * conf
Definition radsniff.c:52
#define pair_append_request(_attr, _da)
Allocate and append a fr_pair_t to the request list.
Definition pair.h:37
#define pair_prepend_request(_attr, _da)
Allocate and prepend a fr_pair_t to the request list.
Definition pair.h:77
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
request_t * unlang_subrequest_alloc(request_t *parent, fr_dict_t const *namespace)
Allocate a subrequest to run through a virtual server at some point in the future.
Definition subrequest.c:316
#define fr_time_delta_lt(_a, _b)
Definition time.h:285
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition time.h:590
#define fr_time_delta_ispos(_a)
Definition time.h:290
static int64_t fr_time_delta_to_sec(fr_time_delta_t delta)
Definition time.h:647
static fr_time_t fr_time_from_sec(time_t when)
Convert a time_t (wallclock time) to a fr_time_t (internal time)
Definition time.h:858
#define fr_time_sub(_a, _b)
Subtract one time from another.
Definition time.h:229
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
"server local" time.
Definition time.h:69
static fr_dict_t const * dict_tls
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
void fr_pair_list_append(fr_pair_list_t *dst, fr_pair_list_t *src)
Appends a list of fr_pair_t from a temporary list to a destination list.
#define fr_pair_dcursor_init(_cursor, _list)
Initialises a special dcursor with callbacks that will maintain the attr sublists correctly.
Definition pair.h:605
static fr_slen_t parent
Definition pair.h:864
#define fr_type_is_null(_x)
Definition types.h:347
int fr_value_box_memdup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, uint8_t const *src, size_t len, bool tainted)
Copy a buffer to a fr_value_box_t.
Definition value.c:5194
static fr_slen_t data
Definition value.h:1366
static fr_sbuff_err_t char ** out
Definition value.h:1061
static fr_sbuff_err_t char size_t * len
Definition value.h:1061
#define fr_box_time_delta(_val)
Definition value.h:397