Coverage Report

Created: 2026-11-12 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/tls/ticket_stateless.c
Line
Count
Source
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: 410b0c3752d6eb455a8d32901adebaf85aeea9cd $
19
 *
20
 * @file tls/ticket_stateless.c
21
 * @brief Stateless TLS session tickets, and the `encode session` and `decode session` sections
22
 *
23
 * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24
 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
25
 */
26
RCSID("$Id: 410b0c3752d6eb455a8d32901adebaf85aeea9cd $")
27
USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
28
29
#ifdef WITH_TLS
30
0
#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/unlang/function.h>
36
#include <freeradius-devel/unlang/subrequest.h>
37
#include <freeradius-devel/util/debug.h>
38
39
#include <freeradius-devel/protocol/tls/freeradius.h>
40
41
#include "attrs.h"
42
#include "base.h"
43
#include "log.h"
44
#include "strerror.h"
45
#include "ticket.h"
46
#include "verify.h"
47
48
#include <openssl/ssl.h>
49
#include <openssl/kdf.h>
50
51
/** Resume after processing `encode session { ... }` or `decode session { ... }`
52
 *
53
 * Check the result and return success / fail depending.
54
 */
55
static unlang_action_t tls_ticket_stateless_resume(request_t *request, void *uctx)
56
0
{
57
0
  fr_tls_session_t  *tls_session = talloc_get_type_abort(uctx, fr_tls_session_t);
58
0
  fr_pair_t   *vp;
59
60
0
  fr_assert((tls_session->ticket == FR_TLS_TICKET_STATELESS_ENCODE_REQUESTED) ||
61
0
      (tls_session->ticket == FR_TLS_TICKET_STATELESS_DECODE_REQUESTED));
62
63
0
  vp = fr_pair_find_by_da(&request->reply_pairs, NULL, attr_tls_packet_type);
64
0
  if (!vp || (vp->vp_uint32 != enum_tls_packet_type_success->vb_uint32)) {
65
0
    tls_session->ticket = FR_TLS_TICKET_STATELESS_FAILED;
66
0
    return UNLANG_ACTION_CALCULATE_RESULT;
67
0
  }
68
69
0
  tls_session->ticket = FR_TLS_TICKET_STATELESS_SUCCESS;
70
71
0
  return UNLANG_ACTION_CALCULATE_RESULT;
72
0
}
73
74
/** Push `encode session { ... }` or `decode session { ... }`
75
 *
76
 * @param[in] request   The current request.
77
 * @param[in] tls_session The current TLS session.
78
 * @param[in] packet_type Which of the two sections to run.
79
 * @return
80
 *  - UNLANG_ACTION_PUSHED_CHILD on success.
81
 *  - UNLANG_ACTION_FAIL on failure.
82
 */
83
static unlang_action_t tls_ticket_stateless_push(request_t *request, fr_tls_session_t *tls_session,
84
               uint32_t packet_type)
85
0
{
86
0
  fr_tls_conf_t *conf = tls_session->conf;
87
0
  request_t *child;
88
0
  unlang_action_t ua;
89
90
0
  fr_assert(conf->virtual_server);
91
92
0
  MEM(child = tls_subrequest_alloc(request, packet_type, &tls_session->session_id));
93
94
0
  fr_tls_session_extra_pairs_copy_to_child(child, tls_session);
95
96
0
  ua = fr_tls_call_push(child, tls_ticket_stateless_resume, conf, tls_session, false);
97
0
  if (ua == UNLANG_ACTION_FAIL) {
98
0
    REDEBUG("(TLS) Failed calling TLS virtual server");
99
0
    talloc_free(child);
100
0
    return UNLANG_ACTION_FAIL;
101
0
  }
102
103
0
  return ua;
104
0
}
105
106
/** Push `encode session` or `decode session`, depending on what's needed.
107
 *
108
 * @param[in] request   The current request.
109
 * @param[in] tls_session The current TLS session.
110
 * @return
111
 *  - UNLANG_ACTION_CALCULATE_RESULT  - nothing was pending.
112
 *  - UNLANG_ACTION_PUSHED_CHILD    - a section is running.
113
 *  - UNLANG_ACTION_FAIL      - the frame could not be pushed.
114
 */
115
unlang_action_t fr_tls_ticket_stateless_pending_push(request_t *request, fr_tls_session_t *tls_session)
116
0
{
117
0
  switch (tls_session->ticket) {
118
0
  case FR_TLS_TICKET_STATELESS_ENCODE_REQUESTED:
119
0
    return tls_ticket_stateless_push(request, tls_session,
120
0
               enum_tls_packet_type_encode_session->vb_uint32);
121
122
0
  case FR_TLS_TICKET_STATELESS_DECODE_REQUESTED:
123
0
    return tls_ticket_stateless_push(request, tls_session,
124
0
               enum_tls_packet_type_decode_session->vb_uint32);
125
126
0
  default:
127
0
    return UNLANG_ACTION_CALCULATE_RESULT;
128
0
  }
129
0
}
130
131
/** Request `encode session { ... }`, and tell the handshake the section is pending
132
 */
133
static inline CC_HINT(always_inline) void tls_ticket_stateless_encode_request(fr_tls_session_t *tls_session)
134
0
{
135
0
  tls_session->ticket = FR_TLS_TICKET_STATELESS_ENCODE_REQUESTED;
136
0
  TLS_PENDING_SET(tls_session, FR_TLS_PENDING_STATELESS_TICKET);
137
0
}
138
139
/** Request `decode session { ... }`, and tell the handshake the section is pending
140
 */
141
static inline CC_HINT(always_inline) void tls_ticket_stateless_decode_request(fr_tls_session_t *tls_session)
142
0
{
143
0
  tls_session->ticket = FR_TLS_TICKET_STATELESS_DECODE_REQUESTED;
144
0
  TLS_PENDING_SET(tls_session, FR_TLS_PENDING_STATELESS_TICKET);
145
0
}
146
147
/** Pause the handshake for `encode session { ... }` or `decode session { ... }`, and read the result
148
 *
149
 * We can't run the interpreter inside of a callback, so the caller records
150
 * what we want to do, and this function tells OpenSSL to pause its
151
 * processing.  We then return to session.c, which determines that there's
152
 * work to do, pushes the section, runs it, and calls us again.  That resumes
153
 * after the ASYNC_pause_job() call.
154
 *
155
 * @param[in] request   bound to the session.
156
 * @param[in] tls_session the ticket belongs to.
157
 * @param[in] name    of the section, for the log.
158
 * @param[in] error   to record when the section does not return success.
159
 * @return
160
 *  - true if the section ran and approved the session-state list.
161
 *  - false if it did not, or if it could not be run at all.
162
 */
163
static inline CC_HINT(always_inline)
164
bool tls_ticket_stateless_section_run(request_t *request, fr_tls_session_t *tls_session,
165
              char const *name, uint32_t error)
166
0
{
167
  /*
168
   *  Sections are only allowed during the handshake, as
169
   *  with certificate re-validation.  See the FIXME in
170
   *  tls_ticket_stateless_app_data_get().
171
   */
172
0
  if (unlikely(!tls_session->can_pause)) {
173
0
    fr_assert_msg("Unexpected call to %s. "
174
0
            "tls_session_async_handshake_cont must be in call stack", __FUNCTION__);
175
0
    tls_session->ticket = FR_TLS_TICKET_STATELESS_INIT;
176
0
    return false;
177
0
  }
178
179
0
  ASYNC_pause_job();
180
181
  /*
182
   *  If the request was cancelled, reset the ticket state
183
   *  so that we don't do anything.
184
   */
185
0
  if (unlang_request_is_cancelled(request)) {
186
0
    tls_session->ticket = FR_TLS_TICKET_STATELESS_INIT;
187
0
    return false;
188
0
  }
189
190
0
  if (tls_session->ticket != FR_TLS_TICKET_STATELESS_SUCCESS) {
191
0
    REDEBUG("(TLS) `%s` did not return success", name);
192
0
    fr_tls_session_error_add(request, error);
193
0
    tls_session->ticket = FR_TLS_TICKET_STATELESS_INIT;
194
0
    return false;
195
0
  }
196
197
0
  tls_session->ticket = FR_TLS_TICKET_STATELESS_INIT;
198
199
0
  return true;
200
0
}
201
202
/** Run `encode session { ... }` from inside an OpenSSL callback
203
 *
204
 * @param[in] request   bound to the session.
205
 * @param[in] tls_session the ticket belongs to.
206
 * @return
207
 *  - true if the section ran and approved the session-state list.
208
 *  - false if it did not, or if it could not be run at all.
209
 */
210
static bool tls_ticket_stateless_encode_run(request_t *request, fr_tls_session_t *tls_session)
211
0
{
212
0
  fr_assert(tls_session->ticket == FR_TLS_TICKET_STATELESS_INIT);
213
214
0
  tls_ticket_stateless_encode_request(tls_session);
215
216
0
  return tls_ticket_stateless_section_run(request, tls_session, "encode session",
217
0
            FR_ERROR_VALUE_ENCODE_SESSION_FAILED);
218
0
}
219
220
/** Run `decode session { ... }` from inside an OpenSSL callback
221
 *
222
 * @param[in] request   bound to the session.
223
 * @param[in] tls_session the ticket belongs to.
224
 * @return
225
 *  - true if the section ran and approved the session-state list.
226
 *  - false if it did not, or if it could not be run at all.
227
 */
228
static bool tls_ticket_stateless_decode_run(request_t *request, fr_tls_session_t *tls_session)
229
0
{
230
0
  fr_assert(tls_session->ticket == FR_TLS_TICKET_STATELESS_INIT);
231
232
0
  tls_ticket_stateless_decode_request(tls_session);
233
234
0
  return tls_ticket_stateless_section_run(request, tls_session, "decode session",
235
0
            FR_ERROR_VALUE_DECODE_SESSION_FAILED);
236
0
}
237
238
/** Push `encode session { ... }`, and leave the section's result in tls_session->ticket
239
 *
240
 * Unlike the section which the ticket callback runs, this section runs as an
241
 * ordinary unlang frame.  It does not pause OpenSSL.
242
 *
243
 * @param[in] request   The current request.
244
 * @param[in] tls_session The current TLS session.
245
 * @return
246
 *  - UNLANG_ACTION_PUSHED_CHILD on success.
247
 *  - UNLANG_ACTION_FAIL on failure.
248
 */
249
static unlang_action_t tls_ticket_stateless_encode_push(request_t *request, fr_tls_session_t *tls_session)
250
0
{
251
0
  tls_session->ticket = FR_TLS_TICKET_STATELESS_ENCODE_REQUESTED;
252
253
0
  return tls_ticket_stateless_push(request, tls_session, enum_tls_packet_type_encode_session->vb_uint32);
254
0
}
255
256
/** State of a frame which creates a stateless ticket, and then runs a function
257
 */
258
typedef struct {
259
  fr_tls_session_t    *tls_session; //!< to create a ticket for.
260
  unlang_function_with_result_t resume;   //!< to call once the ticket is created.  May be NULL.
261
  void        *uctx;    //!< passed to `resume`.
262
} tls_ticket_stateless_issue_t;
263
264
/** Run `encode session`, put the session-state list into the session, create the ticket, and resume
265
 *
266
 * The function runs twice when `encode session` exists.  The first run pushes the section,
267
 * and the second run is the repeat, which runs after the section finishes.  After the ticket is
268
 * written, the function calls the resume function.
269
 *
270
 * @param[in] request   The current request.
271
 * @param[in] uctx    The #tls_ticket_stateless_issue_t.
272
 * @return
273
 *  - UNLANG_ACTION_PUSHED_CHILD  - `encode session` is running.
274
 *  - UNLANG_ACTION_CALCULATE_RESULT - the ticket was created, and there was no resume function.
275
 *  - UNLANG_ACTION_FAIL    - a step failed.
276
 *  - anything else the resume function returns, once the ticket is created.
277
 */
278
static unlang_action_t tls_ticket_stateless_issue(unlang_result_t *p_result, request_t *request, void *uctx)
279
0
{
280
0
  tls_ticket_stateless_issue_t  *issue = talloc_get_type_abort(uctx, tls_ticket_stateless_issue_t);
281
0
  fr_tls_session_t    *tls_session = issue->tls_session;
282
0
  fr_tls_conf_t     *conf = tls_session->conf;
283
0
  SSL_SESSION     *sess;
284
0
  int       ret;
285
286
0
  switch (tls_session->ticket) {
287
0
  case FR_TLS_TICKET_STATELESS_INIT:
288
0
    if (!conf->encode_session) break;
289
290
0
    if (unlikely(unlang_function_repeat_set(request, tls_ticket_stateless_issue) < 0)) {
291
0
      return UNLANG_ACTION_FAIL;
292
0
    }
293
294
0
    switch (tls_ticket_stateless_encode_push(request, tls_session)) {
295
0
    case UNLANG_ACTION_PUSHED_CHILD:
296
0
      return UNLANG_ACTION_PUSHED_CHILD;
297
298
0
    default:
299
0
      IGNORE(unlang_function_clear(request), int);
300
0
      tls_session->ticket = FR_TLS_TICKET_STATELESS_INIT;
301
0
      return UNLANG_ACTION_FAIL;
302
0
    }
303
304
0
  case FR_TLS_TICKET_STATELESS_SUCCESS:
305
0
    IGNORE(unlang_function_clear(request), int);
306
0
    tls_session->ticket = FR_TLS_TICKET_STATELESS_INIT;
307
0
    break;
308
309
0
  default:
310
0
    IGNORE(unlang_function_clear(request), int);
311
0
    tls_session->ticket = FR_TLS_TICKET_STATELESS_INIT;
312
0
    REDEBUG("(TLS) `encode session` did not return success");
313
0
    fr_tls_session_error_add(request, FR_ERROR_VALUE_ENCODE_SESSION_FAILED);
314
0
    return UNLANG_ACTION_FAIL;
315
0
  }
316
317
0
  if (unlang_request_is_cancelled(request)) return UNLANG_ACTION_FAIL;
318
319
0
  sess = SSL_get_session(tls_session->ssl);
320
0
  if (!sess) {
321
0
    REDEBUG("(TLS) Failed retrieving session when creating a session ticket");
322
0
    return UNLANG_ACTION_FAIL;
323
0
  }
324
325
0
  ret = tls_ticket_app_data_set(request, sess, &tls_session->session_id);
326
0
  if (ret != 1) {
327
0
    REDEBUG("(TLS) Not creating a session ticket");
328
0
    return UNLANG_ACTION_FAIL;
329
0
  }
330
331
0
  tls_session->app_data_ready = true;
332
333
0
  if (fr_tls_session_ticket_write(request, tls_session) < 0) return UNLANG_ACTION_FAIL;
334
335
0
  if (issue->resume) return issue->resume(p_result, request, issue->uctx);
336
337
0
  return UNLANG_ACTION_CALCULATE_RESULT;
338
0
}
339
340
/** Push a frame which runs `encode session`, creates a TLS 1.3 stateless session ticket, and resumes
341
 *
342
 * @param[in] request   The current request.
343
 * @param[in] tls_session to create a ticket for.
344
 * @param[in] resume    function to call once the ticket is created.  May be NULL.
345
 * @param[in] uctx    passed to `resume`.
346
 * @return
347
 *  - UNLANG_ACTION_PUSHED_CHILD  - the frame is running.
348
 *  - UNLANG_ACTION_FAIL    - the frame could not be pushed.
349
 */
350
unlang_action_t tls_ticket_stateless_issue_push(request_t *request, fr_tls_session_t *tls_session,
351
            unlang_function_with_result_t resume, void *uctx)
352
{
353
  tls_ticket_stateless_issue_t *issue;
354
355
  MEM(issue = talloc_zero(request, tls_ticket_stateless_issue_t));
356
  issue->tls_session = tls_session;
357
  issue->resume = resume;
358
  issue->uctx = uctx;
359
360
  return unlang_function_push_with_result(NULL, request, tls_ticket_stateless_issue, NULL, NULL, FR_SIGNAL_NONE,
361
            UNLANG_SUB_FRAME, issue);
362
}
363
364
/** Disable stateless session tickets for a given TLS ctx
365
 *
366
 * @param[in] ctx to disable session tickets for.
367
 * @param[in] stateful Are stateful tickets in use.
368
 */
369
void fr_tls_ticket_stateless_disable(SSL_CTX *ctx, bool stateful)
370
0
{
371
0
  long ctx_options = SSL_CTX_get_options(ctx);
372
373
  /*
374
   *  Disable session tickets for older TLS versions
375
   */
376
0
  ctx_options |= SSL_OP_NO_TICKET;
377
0
  SSL_CTX_set_options(ctx, ctx_options);
378
379
  /*
380
   *  This controls the number of stateful or stateless
381
   *  tickets generated with TLS 1.3.
382
   *  Set to 1 ticket if stateful tickets are in use.
383
   */
384
0
  SSL_CTX_set_num_tickets(ctx, stateful ? 1 : 0);
385
0
}
386
387
/** Called when new tickets are being generated
388
 *
389
 * This adds additional application data to the session ticket to
390
 * allow us to perform validation checks when the session is
391
 * resumed.
392
 */
393
static int tls_ticket_stateless_app_data_set(SSL *ssl, void *arg)
394
0
{
395
0
  fr_tls_session_t  *tls_session = fr_tls_session(ssl);
396
0
  fr_tls_ticket_conf_t  *tls_cache_conf = arg;  /* Not talloced */
397
0
  SSL_SESSION   *sess;
398
0
  request_t   *request;
399
0
  fr_tls_conf_t   *conf;
400
0
  int     ret;
401
402
  /*
403
   *  Check to see if we have a request bound
404
   *  to the session.  If we don't have a
405
   *  request there's no application data to
406
   *  add.
407
   */
408
0
  if (!fr_tls_session_request_bound(ssl)) return 1;
409
410
  /*
411
   *  If the client doesn't support session tickets then there is no
412
   *  point in adding the application data - it won't get sent.
413
   */
414
0
  if (!tls_session->session_ticket_supported) return 1;
415
416
  /*
417
   *  fr_tls_ticket_issue() already ran `encode session`, and put the session-state data into the
418
   *  SSL_SESSION.  OpenSSL keeps that data through SSL_new_session_ticket(), so we just return
419
   *  "yes, the data is ready".
420
   */
421
0
  if (tls_session->app_data_ready) {
422
0
    tls_session->app_data_ready = false;
423
0
    tls_session->session_ticket_created = true;
424
0
    return 1;
425
0
  }
426
427
  /*
428
   *  Encode the complete session state list
429
   *  as app data.  Then, when the session is
430
   *  resumed, the session-state list is
431
   *  repopulated.
432
   */
433
0
  request = fr_tls_session_request(ssl);
434
435
  /*
436
   *  Request was cancelled, don't do anything.
437
   */
438
0
  if (unlang_request_is_cancelled(request)) return 0;
439
440
  /*
441
   *  Fatal error - We definitely shouldn't be
442
   *      attempting to generate session tickets
443
   *      if it's not permitted.
444
   */
445
0
  if (!tls_session->allow_session_resumption ||
446
0
      (!(tls_cache_conf->mode & FR_TLS_TICKET_STATELESS))) {
447
0
    REDEBUG("(TLS) Generating session-tickets is not allowed");
448
0
    fr_tls_session_error_add(request, FR_ERROR_VALUE_SESSION_TICKET_NOT_ALLOWED);
449
0
    return 0;
450
0
  }
451
452
0
  sess = SSL_get_session(ssl);
453
0
  if (!sess) {
454
0
    REDEBUG("(TLS) Failed retrieving session in session generation callback");
455
0
    return 0;
456
0
  }
457
458
  /*
459
   *  Run `encode session` to allow the admin to update the
460
   *  `session-state` list, before we encode it into a
461
   *  stateless session ticket.
462
   */
463
0
  conf = fr_tls_session_conf(ssl);
464
465
0
  if (conf->encode_session && !tls_ticket_stateless_encode_run(request, tls_session)) {
466
0
    REDEBUG("(TLS) Not generating a session-ticket");
467
0
    return 0;
468
0
  }
469
470
0
  ret = tls_ticket_app_data_set(request, sess, &tls_session->session_id);
471
0
  tls_session->session_ticket_created = (ret == 1);
472
473
0
  return ret;
474
0
}
475
476
/** Called when new tickets are being decoded
477
 *
478
 * This adds the session-state attributes back to the current request.
479
 */
480
static SSL_TICKET_RETURN tls_ticket_stateless_app_data_get(SSL *ssl, SSL_SESSION *sess,
481
                     UNUSED unsigned char const *keyname,
482
                     UNUSED size_t keyname_len,
483
                     SSL_TICKET_STATUS status,
484
                     void *arg)
485
0
{
486
0
  fr_tls_session_t  *tls_session = fr_tls_session(ssl);
487
0
  fr_tls_conf_t   *conf = tls_session->conf;
488
0
  fr_tls_ticket_conf_t  *tls_cache_conf = arg;  /* Not talloced */
489
0
  request_t   *request = NULL;
490
0
  fr_pair_t   *vp;
491
492
0
  if (fr_tls_session_request_bound(ssl)) {
493
0
    request = fr_tls_session_request(ssl);
494
0
    if (unlang_request_is_cancelled(request)) return SSL_TICKET_RETURN_ABORT;
495
0
  }
496
497
0
  if (!tls_session->allow_session_resumption ||
498
0
      (!(tls_cache_conf->mode & FR_TLS_TICKET_STATELESS))) {
499
0
    ROPTIONAL(RDEBUG2, DEBUG2, "(TLS) Session resumption not enabled for this TLS session, "
500
0
        "denying session resumption via session-ticket");
501
0
        return SSL_TICKET_RETURN_IGNORE;
502
0
  }
503
504
0
  switch (status) {
505
0
  case SSL_TICKET_EMPTY:
506
0
  case SSL_TICKET_NO_DECRYPT:
507
0
  case SSL_TICKET_FATAL_ERR_MALLOC:
508
0
  case SSL_TICKET_FATAL_ERR_OTHER:
509
0
  case SSL_TICKET_NONE:
510
#ifdef STATIC_ANALYZER
511
  default:
512
#endif
513
0
    return SSL_TICKET_RETURN_IGNORE_RENEW; /* Send a new ticket */
514
515
0
  case SSL_TICKET_SUCCESS:
516
0
    if (!request) return SSL_TICKET_RETURN_USE;
517
0
    break;
518
519
0
  case SSL_TICKET_SUCCESS_RENEW:
520
0
    if (!request) return SSL_TICKET_RETURN_USE_RENEW;
521
0
    break;
522
0
  }
523
524
  /*
525
   *  This restores the contents of &session-state[*]
526
   *  which hopefully still contains all the certificate
527
   *  pairs.
528
   *
529
   *  Although the SSL_SESSION does contain a copy of
530
   *  the peer's certificate, it does not contain the
531
   *  peer's certificate chain, and so isn't reliable
532
   *  for performing re-validation.
533
   *
534
   *  The session ticket includes a lifetime.  But we didn't
535
   *  generate it, so we don't trust it.  If the ticket has
536
   *  passed its lifetime, then we reject it and force full
537
   *  reauthentication.
538
   */
539
0
  if (!tls_ticket_session_resumable(request, &tls_session->session_id, conf, sess)) {
540
0
    REDEBUG("(TLS) Session-ticket has too little life left, denying session resumption");
541
0
    fr_tls_session_error_add(request, FR_ERROR_VALUE_SESSION_TICKET_EXPIRED);
542
0
    return SSL_TICKET_RETURN_IGNORE_RENEW;
543
0
  }
544
545
0
  if (tls_ticket_app_data_get(request, sess, &tls_session->session_id) < 0) {
546
0
    REDEBUG("(TLS) Denying session resumption via session-ticket");
547
0
    return SSL_TICKET_RETURN_IGNORE_RENEW;
548
0
  }
549
550
  /*
551
   *  The session-state list is back.  Give policy the chance to
552
   *  look at what the ticket carried before anything relies on
553
   *  it, certificate re-validation below included.
554
   */
555
0
  if (conf->decode_session && !tls_ticket_stateless_decode_run(request, tls_session)) {
556
0
    REDEBUG("(TLS) Denying session resumption via session-ticket");
557
0
    return SSL_TICKET_RETURN_IGNORE_RENEW;
558
0
  }
559
560
0
  if (conf->virtual_server && tls_session->verify_peer_cert) {
561
0
    RDEBUG2("(TLS) Requesting certificate re-validation for session-ticket");
562
    /*
563
     *  This sets the validation state of the tls_session
564
     *  so that when we call ASYNC_pause_job(), and execution
565
     *  jumps back to tls_session_async_handshake_cont
566
     *  (just under SSL_read())
567
     *  the code there knows what job it needs to push onto
568
     *  the unlang stack.
569
     */
570
0
    fr_tls_verify_resumed_request(tls_session);
571
572
    /*
573
     *  Cache functions are only allowed during the handshake
574
     *  FIXME: With TLS 1.3 session tickets can be sent
575
     *  later... Technically every point where we call
576
     *  SSL_read() may need to be a yield point.
577
     */
578
0
    if (unlikely(!tls_session->can_pause)) {
579
0
      fr_assert_msg("Unexpected call to %s. "
580
0
              "tls_session_async_handshake_cont must be in call stack", __FUNCTION__);
581
0
      return SSL_TICKET_RETURN_IGNORE_RENEW;
582
0
    }
583
584
    /*
585
     *  Jumps back to SSL_read() in session.c
586
     *
587
     *  Be aware that if the request is cancelled
588
     *  whatever was meant to be done during the
589
     *  time we yielded may not have been completed.
590
     */
591
0
    ASYNC_pause_job();
592
593
    /*
594
     *  If the request was cancelled get everything back into
595
     *  a known state.
596
     */
597
0
    if (unlang_request_is_cancelled(request)) {
598
0
      fr_tls_verify_cert_reset(tls_session);
599
0
      return SSL_TICKET_RETURN_ABORT;
600
0
    }
601
602
    /*
603
     *  If we couldn't validate the client certificate
604
     *  give the client the opportunity to send a new
605
     *  one, but _don't_ allow session resumption.
606
     */
607
0
    if (!fr_tls_verify_cert_result(tls_session)) {
608
0
      RDEBUG2("(TLS) Certificate re-validation failed, denying session resumption via session-ticket");
609
0
      return SSL_TICKET_RETURN_IGNORE_RENEW;
610
0
    }
611
0
  }
612
613
  /*
614
   *  Add TLS-Session-Resume-Type to report how the session was resumed.
615
   */
616
0
  if (unlikely(pair_update_session_state(&vp, attr_tls_session_resume_type)) < 0) {
617
0
    RWDEBUG("(TLS) Session ID %pV - Failed adding %s attribute", &tls_session->session_id,
618
0
      attr_tls_session_resume_type->name);
619
0
  } else {
620
0
    vp->vp_uint32 = enum_tls_session_resumed_stateless->vb_uint32;
621
0
  }
622
623
0
  return (status == SSL_TICKET_SUCCESS_RENEW) ? SSL_TICKET_RETURN_USE_RENEW : SSL_TICKET_RETURN_USE;
624
0
}
625
626
/** Derive the session ticket key, and install the session ticket callbacks on an SSL_CTX
627
 *
628
 * The key the configuration gives is stretched to the length OpenSSL asks
629
 * for with HKDF.  The callbacks embed the session-state list in each session
630
 * ticket the server issues, and extract the list again when a ticket is
631
 * presented.
632
 *
633
 * @param[in] ctx   to install the key and callbacks on.
634
 * @param[in] cache_conf  Session caching configuration, for the key and the callbacks.
635
 * @return
636
 *  - 0 on success.
637
 *  - -1 on failure.
638
 */
639
int fr_tls_ticket_stateless_ctx_init(SSL_CTX *ctx, fr_tls_ticket_conf_t const *cache_conf)
640
0
{
641
0
  size_t    key_len;
642
0
  uint8_t   *key_buff;
643
0
  EVP_PKEY_CTX  *pkey_ctx = NULL;
644
645
  /*
646
   *  If keys is NULL, then OpenSSL returns the expected
647
   *  key length, which may be different across different
648
   *  flavours/versions of OpenSSL.
649
   *
650
   *  We could calculate this in conf.c, but, if in future
651
   *  OpenSSL decides to use different key lengths based
652
   *  on other parameters in the ctx, that'd break.
653
   */
654
0
  key_len = SSL_CTX_set_tlsext_ticket_keys(ctx, NULL, 0);
655
656
0
  if (unlikely((pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL)) == NULL)) {
657
0
    fr_tls_strerror_printf(NULL);
658
0
    PERROR("Failed initialising KDF");
659
0
  kdf_error:
660
0
    if (pkey_ctx) EVP_PKEY_CTX_free(pkey_ctx);
661
0
    return -1;
662
0
  }
663
0
  if (unlikely(EVP_PKEY_derive_init(pkey_ctx) != 1)) {
664
0
    fr_tls_strerror_printf(NULL);
665
0
    PERROR("Failed initialising KDF derivation ctx");
666
0
    goto kdf_error;
667
0
  }
668
0
  if (unlikely(EVP_PKEY_CTX_set_hkdf_md(pkey_ctx, UNCONST(struct evp_md_st *, EVP_sha256())) != 1)) {
669
0
    fr_tls_strerror_printf(NULL);
670
0
    PERROR("Failed setting KDF MD");
671
0
    goto kdf_error;
672
0
  }
673
0
  if (unlikely(EVP_PKEY_CTX_set1_hkdf_key(pkey_ctx,
674
0
            UNCONST(unsigned char *, cache_conf->session_ticket_key),
675
0
            (int)talloc_array_length(cache_conf->session_ticket_key)) != 1)) {
676
0
    fr_tls_strerror_printf(NULL);
677
0
    PERROR("Failed setting KDF key");
678
0
    goto kdf_error;
679
0
  }
680
0
  if (unlikely(EVP_PKEY_CTX_add1_hkdf_info(pkey_ctx,
681
0
             UNCONST(unsigned char *, "freeradius-session-ticket"),
682
0
             sizeof("freeradius-session-ticket") - 1) != 1)) {
683
0
    fr_tls_strerror_printf(NULL);
684
0
    PERROR("Failed setting KDF label");
685
0
    goto kdf_error;
686
0
  }
687
688
  /*
689
   *  SSL_CTX_set_tlsext_ticket_keys memcpys its
690
   *  inputs so this is just a temporary buffer.
691
   */
692
0
  MEM(key_buff = talloc_array(NULL, uint8_t, key_len));
693
0
  if (EVP_PKEY_derive(pkey_ctx, key_buff, &key_len) != 1) {
694
0
    fr_tls_strerror_printf(NULL);
695
0
    PERROR("Failed deriving session ticket key");
696
697
0
  key_buff_error:
698
0
    talloc_free(key_buff);
699
0
    goto kdf_error;
700
0
  }
701
0
  EVP_PKEY_CTX_free(pkey_ctx);
702
0
  pkey_ctx = NULL;
703
704
0
  fr_assert(talloc_array_length(key_buff) == key_len);
705
706
  /*
707
   *  Ensure the same keys are used across all threads
708
   */
709
0
  if (SSL_CTX_set_tlsext_ticket_keys(ctx,
710
0
             key_buff, key_len) != 1) {
711
0
    fr_tls_strerror_printf(NULL);
712
0
    PERROR("Failed setting session ticket keys");
713
0
    goto key_buff_error;
714
0
  }
715
716
0
  DEBUG3("Derived session-ticket-key:");
717
0
  HEXDUMP3(key_buff, key_len, NULL);
718
0
  TALLOC_FREE(key_buff);
719
720
  /*
721
   *  These callbacks embed and extract the
722
   *  session-state list from the session-ticket.
723
   */
724
0
  if (unlikely(SSL_CTX_set_session_ticket_cb(ctx,
725
0
               tls_ticket_stateless_app_data_set,
726
0
               tls_ticket_stateless_app_data_get,
727
0
               UNCONST(fr_tls_ticket_conf_t *, cache_conf)) != 1)) {
728
0
    fr_tls_strerror_printf(NULL);
729
0
    PERROR("Failed setting session ticket callbacks");
730
0
    return -1;
731
0
  }
732
733
  /*
734
   *  Stateless resumption is enabled by default when
735
   *  the TLS ctx is created, but OpenSSL sends too
736
   *  many session tickets by default (2), and we only
737
   *      need one.
738
   */
739
0
  SSL_CTX_set_num_tickets(ctx, 1);
740
741
0
  return 0;
742
0
}
743
#endif /* WITH_TLS */