/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: 377e6b880f38e5236fda421b1065302ba4b18557 $ |
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: 377e6b880f38e5236fda421b1065302ba4b18557 $") |
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 | | /** Disable stateless session tickets for a given TLS ctx |
239 | | * |
240 | | * @param[in] ctx to disable session tickets for. |
241 | | * @param[in] stateful Are stateful tickets in use. |
242 | | */ |
243 | | void fr_tls_ticket_stateless_disable(SSL_CTX *ctx, bool stateful) |
244 | 0 | { |
245 | 0 | long ctx_options = SSL_CTX_get_options(ctx); |
246 | | |
247 | | /* |
248 | | * Disable session tickets for older TLS versions |
249 | | */ |
250 | 0 | ctx_options |= SSL_OP_NO_TICKET; |
251 | 0 | SSL_CTX_set_options(ctx, ctx_options); |
252 | | |
253 | | /* |
254 | | * This controls the number of stateful or stateless |
255 | | * tickets generated with TLS 1.3. |
256 | | * Set to 1 ticket if stateful tickets are in use. |
257 | | */ |
258 | 0 | SSL_CTX_set_num_tickets(ctx, stateful ? 1 : 0); |
259 | 0 | } |
260 | | |
261 | | /** Called when new tickets are being generated |
262 | | * |
263 | | * This adds additional application data to the session ticket to |
264 | | * allow us to perform validation checks when the session is |
265 | | * resumed. |
266 | | */ |
267 | | static int tls_ticket_stateless_app_data_set(SSL *ssl, void *arg) |
268 | 0 | { |
269 | 0 | fr_tls_session_t *tls_session = fr_tls_session(ssl); |
270 | 0 | fr_tls_ticket_conf_t *tls_cache_conf = arg; /* Not talloced */ |
271 | 0 | SSL_SESSION *sess; |
272 | 0 | request_t *request; |
273 | 0 | fr_tls_conf_t *conf; |
274 | 0 | int ret; |
275 | | |
276 | | /* |
277 | | * Check to see if we have a request bound |
278 | | * to the session. If we don't have a |
279 | | * request there's no application data to |
280 | | * add. |
281 | | */ |
282 | 0 | if (!fr_tls_session_request_bound(ssl)) return 1; |
283 | | |
284 | | /* |
285 | | * If the client doesn't support session tickets then there is no |
286 | | * point in adding the application data - it won't get sent. |
287 | | */ |
288 | 0 | if (!tls_session->session_ticket_supported) return 1; |
289 | | |
290 | | /* |
291 | | * Encode the complete session state list |
292 | | * as app data. Then, when the session is |
293 | | * resumed, the session-state list is |
294 | | * repopulated. |
295 | | */ |
296 | 0 | request = fr_tls_session_request(ssl); |
297 | | |
298 | | /* |
299 | | * Request was cancelled, don't do anything. |
300 | | */ |
301 | 0 | if (unlang_request_is_cancelled(request)) return 0; |
302 | | |
303 | | /* |
304 | | * Fatal error - We definitely shouldn't be |
305 | | * attempting to generate session tickets |
306 | | * if it's not permitted. |
307 | | */ |
308 | 0 | if (!tls_session->allow_session_resumption || |
309 | 0 | (!(tls_cache_conf->mode & FR_TLS_TICKET_STATELESS))) { |
310 | 0 | REDEBUG("(TLS) Generating session-tickets is not allowed"); |
311 | 0 | fr_tls_session_error_add(request, FR_ERROR_VALUE_SESSION_TICKET_NOT_ALLOWED); |
312 | 0 | return 0; |
313 | 0 | } |
314 | | |
315 | 0 | sess = SSL_get_session(ssl); |
316 | 0 | if (!sess) { |
317 | 0 | REDEBUG("(TLS) Failed retrieving session in session generation callback"); |
318 | 0 | return 0; |
319 | 0 | } |
320 | | |
321 | | /* |
322 | | * Run `encode session` to allow the admin to update the |
323 | | * `session-state` list, before we encode it into a |
324 | | * stateless session ticket. |
325 | | */ |
326 | 0 | conf = fr_tls_session_conf(ssl); |
327 | |
|
328 | 0 | if (conf->encode_session && !tls_ticket_stateless_encode_run(request, tls_session)) { |
329 | 0 | REDEBUG("(TLS) Not generating a session-ticket"); |
330 | 0 | return 0; |
331 | 0 | } |
332 | | |
333 | 0 | ret = tls_ticket_app_data_set(request, sess, &tls_session->session_id); |
334 | 0 | if (ret == 1) tls_session->session_ticket_created = true; |
335 | |
|
336 | 0 | return ret; |
337 | 0 | } |
338 | | |
339 | | /** Called when new tickets are being decoded |
340 | | * |
341 | | * This adds the session-state attributes back to the current request. |
342 | | */ |
343 | | static SSL_TICKET_RETURN tls_ticket_stateless_app_data_get(SSL *ssl, SSL_SESSION *sess, |
344 | | UNUSED unsigned char const *keyname, |
345 | | UNUSED size_t keyname_len, |
346 | | SSL_TICKET_STATUS status, |
347 | | void *arg) |
348 | 0 | { |
349 | 0 | fr_tls_session_t *tls_session = fr_tls_session(ssl); |
350 | 0 | fr_tls_conf_t *conf = tls_session->conf; |
351 | 0 | fr_tls_ticket_conf_t *tls_cache_conf = arg; /* Not talloced */ |
352 | 0 | request_t *request = NULL; |
353 | |
|
354 | 0 | if (fr_tls_session_request_bound(ssl)) { |
355 | 0 | request = fr_tls_session_request(ssl); |
356 | 0 | if (unlang_request_is_cancelled(request)) return SSL_TICKET_RETURN_ABORT; |
357 | 0 | } |
358 | | |
359 | 0 | if (!tls_session->allow_session_resumption || |
360 | 0 | (!(tls_cache_conf->mode & FR_TLS_TICKET_STATELESS))) { |
361 | 0 | ROPTIONAL(RDEBUG2, DEBUG2, "(TLS) Session resumption not enabled for this TLS session, " |
362 | 0 | "denying session resumption via session-ticket"); |
363 | 0 | return SSL_TICKET_RETURN_IGNORE; |
364 | 0 | } |
365 | | |
366 | 0 | switch (status) { |
367 | 0 | case SSL_TICKET_EMPTY: |
368 | 0 | case SSL_TICKET_NO_DECRYPT: |
369 | 0 | case SSL_TICKET_FATAL_ERR_MALLOC: |
370 | 0 | case SSL_TICKET_FATAL_ERR_OTHER: |
371 | 0 | case SSL_TICKET_NONE: |
372 | | #ifdef STATIC_ANALYZER |
373 | | default: |
374 | | #endif |
375 | 0 | return SSL_TICKET_RETURN_IGNORE_RENEW; /* Send a new ticket */ |
376 | | |
377 | 0 | case SSL_TICKET_SUCCESS: |
378 | 0 | if (!request) return SSL_TICKET_RETURN_USE; |
379 | 0 | break; |
380 | | |
381 | 0 | case SSL_TICKET_SUCCESS_RENEW: |
382 | 0 | if (!request) return SSL_TICKET_RETURN_USE_RENEW; |
383 | 0 | break; |
384 | 0 | } |
385 | | |
386 | | /* |
387 | | * This restores the contents of &session-state[*] |
388 | | * which hopefully still contains all the certificate |
389 | | * pairs. |
390 | | * |
391 | | * Although the SSL_SESSION does contain a copy of |
392 | | * the peer's certificate, it does not contain the |
393 | | * peer's certificate chain, and so isn't reliable |
394 | | * for performing re-validation. |
395 | | * |
396 | | * The session ticket includes a lifetime. But we didn't |
397 | | * generate it, so we don't trust it. If the ticket has |
398 | | * passed its lifetime, then we reject it and force full |
399 | | * reauthentication. |
400 | | */ |
401 | 0 | if (!tls_ticket_session_resumable(request, &tls_session->session_id, conf, sess)) { |
402 | 0 | REDEBUG("(TLS) Session-ticket has too little life left, denying session resumption"); |
403 | 0 | fr_tls_session_error_add(request, FR_ERROR_VALUE_SESSION_TICKET_EXPIRED); |
404 | 0 | return SSL_TICKET_RETURN_IGNORE_RENEW; |
405 | 0 | } |
406 | | |
407 | 0 | if (tls_ticket_app_data_get(request, sess, &tls_session->session_id) < 0) { |
408 | 0 | REDEBUG("(TLS) Denying session resumption via session-ticket"); |
409 | 0 | return SSL_TICKET_RETURN_IGNORE_RENEW; |
410 | 0 | } |
411 | | |
412 | | /* |
413 | | * The session-state list is back. Give policy the chance to |
414 | | * look at what the ticket carried before anything relies on |
415 | | * it, certificate re-validation below included. |
416 | | */ |
417 | 0 | if (conf->decode_session && !tls_ticket_stateless_decode_run(request, tls_session)) { |
418 | 0 | REDEBUG("(TLS) Denying session resumption via session-ticket"); |
419 | 0 | return SSL_TICKET_RETURN_IGNORE_RENEW; |
420 | 0 | } |
421 | | |
422 | 0 | if (conf->virtual_server && tls_session->verify_peer_cert) { |
423 | 0 | RDEBUG2("(TLS) Requesting certificate re-validation for session-ticket"); |
424 | | /* |
425 | | * This sets the validation state of the tls_session |
426 | | * so that when we call ASYNC_pause_job(), and execution |
427 | | * jumps back to tls_session_async_handshake_cont |
428 | | * (just under SSL_read()) |
429 | | * the code there knows what job it needs to push onto |
430 | | * the unlang stack. |
431 | | */ |
432 | 0 | fr_tls_verify_resumed_request(tls_session); |
433 | | |
434 | | /* |
435 | | * Cache functions are only allowed during the handshake |
436 | | * FIXME: With TLS 1.3 session tickets can be sent |
437 | | * later... Technically every point where we call |
438 | | * SSL_read() may need to be a yield point. |
439 | | */ |
440 | 0 | if (unlikely(!tls_session->can_pause)) { |
441 | 0 | fr_assert_msg("Unexpected call to %s. " |
442 | 0 | "tls_session_async_handshake_cont must be in call stack", __FUNCTION__); |
443 | 0 | return SSL_TICKET_RETURN_IGNORE_RENEW; |
444 | 0 | } |
445 | | |
446 | | /* |
447 | | * Jumps back to SSL_read() in session.c |
448 | | * |
449 | | * Be aware that if the request is cancelled |
450 | | * whatever was meant to be done during the |
451 | | * time we yielded may not have been completed. |
452 | | */ |
453 | 0 | ASYNC_pause_job(); |
454 | | |
455 | | /* |
456 | | * If the request was cancelled get everything back into |
457 | | * a known state. |
458 | | */ |
459 | 0 | if (unlang_request_is_cancelled(request)) { |
460 | 0 | fr_tls_verify_cert_reset(tls_session); |
461 | 0 | return SSL_TICKET_RETURN_ABORT; |
462 | 0 | } |
463 | | |
464 | | /* |
465 | | * If we couldn't validate the client certificate |
466 | | * give the client the opportunity to send a new |
467 | | * one, but _don't_ allow session resumption. |
468 | | */ |
469 | 0 | if (!fr_tls_verify_cert_result(tls_session)) { |
470 | 0 | RDEBUG2("(TLS) Certificate re-validation failed, denying session resumption via session-ticket"); |
471 | 0 | return SSL_TICKET_RETURN_IGNORE_RENEW; |
472 | 0 | } |
473 | 0 | } |
474 | | |
475 | 0 | return (status == SSL_TICKET_SUCCESS_RENEW) ? SSL_TICKET_RETURN_USE_RENEW : SSL_TICKET_RETURN_USE; |
476 | 0 | } |
477 | | |
478 | | /** Derive the session ticket key, and install the session ticket callbacks on an SSL_CTX |
479 | | * |
480 | | * The key the configuration gives is stretched to the length OpenSSL asks |
481 | | * for with HKDF. The callbacks embed the session-state list in each session |
482 | | * ticket the server issues, and extract the list again when a ticket is |
483 | | * presented. |
484 | | * |
485 | | * @param[in] ctx to install the key and callbacks on. |
486 | | * @param[in] cache_conf Session caching configuration, for the key and the callbacks. |
487 | | * @return |
488 | | * - 0 on success. |
489 | | * - -1 on failure. |
490 | | */ |
491 | | int fr_tls_ticket_stateless_ctx_init(SSL_CTX *ctx, fr_tls_ticket_conf_t const *cache_conf) |
492 | 0 | { |
493 | 0 | size_t key_len; |
494 | 0 | uint8_t *key_buff; |
495 | 0 | EVP_PKEY_CTX *pkey_ctx = NULL; |
496 | | |
497 | | /* |
498 | | * If keys is NULL, then OpenSSL returns the expected |
499 | | * key length, which may be different across different |
500 | | * flavours/versions of OpenSSL. |
501 | | * |
502 | | * We could calculate this in conf.c, but, if in future |
503 | | * OpenSSL decides to use different key lengths based |
504 | | * on other parameters in the ctx, that'd break. |
505 | | */ |
506 | 0 | key_len = SSL_CTX_set_tlsext_ticket_keys(ctx, NULL, 0); |
507 | |
|
508 | 0 | if (unlikely((pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL)) == NULL)) { |
509 | 0 | fr_tls_strerror_printf(NULL); |
510 | 0 | PERROR("Failed initialising KDF"); |
511 | 0 | kdf_error: |
512 | 0 | if (pkey_ctx) EVP_PKEY_CTX_free(pkey_ctx); |
513 | 0 | return -1; |
514 | 0 | } |
515 | 0 | if (unlikely(EVP_PKEY_derive_init(pkey_ctx) != 1)) { |
516 | 0 | fr_tls_strerror_printf(NULL); |
517 | 0 | PERROR("Failed initialising KDF derivation ctx"); |
518 | 0 | goto kdf_error; |
519 | 0 | } |
520 | 0 | if (unlikely(EVP_PKEY_CTX_set_hkdf_md(pkey_ctx, UNCONST(struct evp_md_st *, EVP_sha256())) != 1)) { |
521 | 0 | fr_tls_strerror_printf(NULL); |
522 | 0 | PERROR("Failed setting KDF MD"); |
523 | 0 | goto kdf_error; |
524 | 0 | } |
525 | 0 | if (unlikely(EVP_PKEY_CTX_set1_hkdf_key(pkey_ctx, |
526 | 0 | UNCONST(unsigned char *, cache_conf->session_ticket_key), |
527 | 0 | talloc_array_length(cache_conf->session_ticket_key)) != 1)) { |
528 | 0 | fr_tls_strerror_printf(NULL); |
529 | 0 | PERROR("Failed setting KDF key"); |
530 | 0 | goto kdf_error; |
531 | 0 | } |
532 | 0 | if (unlikely(EVP_PKEY_CTX_add1_hkdf_info(pkey_ctx, |
533 | 0 | UNCONST(unsigned char *, "freeradius-session-ticket"), |
534 | 0 | sizeof("freeradius-session-ticket") - 1) != 1)) { |
535 | 0 | fr_tls_strerror_printf(NULL); |
536 | 0 | PERROR("Failed setting KDF label"); |
537 | 0 | goto kdf_error; |
538 | 0 | } |
539 | | |
540 | | /* |
541 | | * SSL_CTX_set_tlsext_ticket_keys memcpys its |
542 | | * inputs so this is just a temporary buffer. |
543 | | */ |
544 | 0 | MEM(key_buff = talloc_array(NULL, uint8_t, key_len)); |
545 | 0 | if (EVP_PKEY_derive(pkey_ctx, key_buff, &key_len) != 1) { |
546 | 0 | fr_tls_strerror_printf(NULL); |
547 | 0 | PERROR("Failed deriving session ticket key"); |
548 | |
|
549 | 0 | key_buff_error: |
550 | 0 | talloc_free(key_buff); |
551 | 0 | goto kdf_error; |
552 | 0 | } |
553 | 0 | EVP_PKEY_CTX_free(pkey_ctx); |
554 | 0 | pkey_ctx = NULL; |
555 | |
|
556 | 0 | fr_assert(talloc_array_length(key_buff) == key_len); |
557 | | |
558 | | /* |
559 | | * Ensure the same keys are used across all threads |
560 | | */ |
561 | 0 | if (SSL_CTX_set_tlsext_ticket_keys(ctx, |
562 | 0 | key_buff, key_len) != 1) { |
563 | 0 | fr_tls_strerror_printf(NULL); |
564 | 0 | PERROR("Failed setting session ticket keys"); |
565 | 0 | goto key_buff_error; |
566 | 0 | } |
567 | | |
568 | 0 | DEBUG3("Derived session-ticket-key:"); |
569 | 0 | HEXDUMP3(key_buff, key_len, NULL); |
570 | 0 | TALLOC_FREE(key_buff); |
571 | | |
572 | | /* |
573 | | * These callbacks embed and extract the |
574 | | * session-state list from the session-ticket. |
575 | | */ |
576 | 0 | if (unlikely(SSL_CTX_set_session_ticket_cb(ctx, |
577 | 0 | tls_ticket_stateless_app_data_set, |
578 | 0 | tls_ticket_stateless_app_data_get, |
579 | 0 | UNCONST(fr_tls_ticket_conf_t *, cache_conf)) != 1)) { |
580 | 0 | fr_tls_strerror_printf(NULL); |
581 | 0 | PERROR("Failed setting session ticket callbacks"); |
582 | 0 | return -1; |
583 | 0 | } |
584 | | |
585 | | /* |
586 | | * Stateless resumption is enabled by default when |
587 | | * the TLS ctx is created, but OpenSSL sends too |
588 | | * many session tickets by default (2), and we only |
589 | | * need one. |
590 | | */ |
591 | 0 | SSL_CTX_set_num_tickets(ctx, 1); |
592 | |
|
593 | 0 | return 0; |
594 | 0 | } |
595 | | #endif /* WITH_TLS */ |