1 /*
2 * libwebsockets - small server side websockets and web server implementation
3 *
4 * Copyright (C) 2010 - 2022 Andy Green <andy@warmcat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 */
24
25 #include "private-lib-core.h"
26
27 typedef struct lws_tls_session_cache_openssl {
28 lws_dll2_t list;
29
30 SSL_SESSION *session;
31 lws_sorted_usec_list_t sul_ttl;
32
33 /* name is overallocated here */
34 } lws_tls_sco_t;
35
36 #define tlssess_loglevel LLL_INFO
37 #if (_LWS_ENABLED_LOGS & tlssess_loglevel)
38 #define lwsl_tlssess(...) _lws_log(tlssess_loglevel, __VA_ARGS__)
39 #else
40 #define lwsl_tlssess(...)
41 #endif
42
43 static void
__lws_tls_session_destroy(lws_tls_sco_t *ts)44 __lws_tls_session_destroy(lws_tls_sco_t *ts)
45 {
46 lwsl_tlssess("%s: %s (%u)\n", __func__, (const char *)&ts[1],
47 ts->list.owner->count - 1);
48
49 lws_sul_cancel(&ts->sul_ttl);
50 SSL_SESSION_free(ts->session);
51 lws_dll2_remove(&ts->list); /* vh lock */
52
53 lws_free(ts);
54 }
55
56 static lws_tls_sco_t *
__lws_tls_session_lookup_by_name(struct lws_vhost *vh, const char *name)57 __lws_tls_session_lookup_by_name(struct lws_vhost *vh, const char *name)
58 {
59 lws_start_foreach_dll(struct lws_dll2 *, p,
60 lws_dll2_get_head(&vh->tls_sessions)) {
61 lws_tls_sco_t *ts = lws_container_of(p, lws_tls_sco_t, list);
62 const char *ts_name = (const char *)&ts[1];
63
64 if (!strcmp(name, ts_name))
65 return ts;
66
67 } lws_end_foreach_dll(p);
68
69 return NULL;
70 }
71
72 /*
73 * If possible, reuse an existing, cached session
74 */
75
76 void
lws_tls_reuse_session(struct lws *wsi)77 lws_tls_reuse_session(struct lws *wsi)
78 {
79 char tag[LWS_SESSION_TAG_LEN];
80 lws_tls_sco_t *ts;
81
82 if (!wsi->a.vhost ||
83 wsi->a.vhost->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
84 return;
85
86 lws_context_lock(wsi->a.context, __func__); /* -------------- cx { */
87 lws_vhost_lock(wsi->a.vhost); /* -------------- vh { */
88
89 if (lws_tls_session_tag_from_wsi(wsi, tag, sizeof(tag)))
90 goto bail;
91 ts = __lws_tls_session_lookup_by_name(wsi->a.vhost, tag);
92
93 if (!ts) {
94 lwsl_tlssess("%s: no existing session for %s\n", __func__, tag);
95 goto bail;
96 }
97
98 lwsl_tlssess("%s: %s\n", __func__, (const char *)&ts[1]);
99
100 if (!SSL_set_session(wsi->tls.ssl, ts->session)) {
101 lwsl_err("%s: session not set for %s\n", __func__, tag);
102 goto bail;
103 }
104
105 #if !defined(USE_WOLFSSL)
106 /* extend session lifetime */
107 SSL_SESSION_set_time(ts->session,
108 #if defined(OPENSSL_IS_BORINGSSL)
109 (unsigned long)
110 #else
111 (long)
112 #endif
113 time(NULL));
114 #endif
115
116 /* keep our session list sorted in lru -> mru order */
117
118 lws_dll2_remove(&ts->list);
119 lws_dll2_add_tail(&ts->list, &wsi->a.vhost->tls_sessions);
120
121 bail:
122 lws_vhost_unlock(wsi->a.vhost); /* } vh -------------- */
123 lws_context_unlock(wsi->a.context); /* } cx -------------- */
124 }
125
126 int
lws_tls_session_is_reused(struct lws *wsi)127 lws_tls_session_is_reused(struct lws *wsi)
128 {
129 #if defined(LWS_WITH_CLIENT)
130 struct lws *nwsi = lws_get_network_wsi(wsi);
131
132 if (!nwsi || !nwsi->tls.ssl)
133 return 0;
134
135 return (int)SSL_session_reused(nwsi->tls.ssl);
136 #else
137 return 0;
138 #endif
139 }
140
141 static int
lws_tls_session_destroy_dll(struct lws_dll2 *d, void *user)142 lws_tls_session_destroy_dll(struct lws_dll2 *d, void *user)
143 {
144 lws_tls_sco_t *ts = lws_container_of(d, lws_tls_sco_t, list);
145
146 __lws_tls_session_destroy(ts);
147
148 return 0;
149 }
150
151 void
lws_tls_session_vh_destroy(struct lws_vhost *vh)152 lws_tls_session_vh_destroy(struct lws_vhost *vh)
153 {
154 lws_dll2_foreach_safe(&vh->tls_sessions, NULL,
155 lws_tls_session_destroy_dll);
156 }
157
158 static void
lws_tls_session_expiry_cb(lws_sorted_usec_list_t *sul)159 lws_tls_session_expiry_cb(lws_sorted_usec_list_t *sul)
160 {
161 lws_tls_sco_t *ts = lws_container_of(sul, lws_tls_sco_t, sul_ttl);
162 struct lws_vhost *vh = lws_container_of(ts->list.owner,
163 struct lws_vhost, tls_sessions);
164
165 lws_context_lock(vh->context, __func__); /* -------------- cx { */
166 lws_vhost_lock(vh); /* -------------- vh { */
167 __lws_tls_session_destroy(ts);
168 lws_vhost_unlock(vh); /* } vh -------------- */
169 lws_context_unlock(vh->context); /* } cx -------------- */
170 }
171
172 static lws_tls_sco_t *
lws_tls_session_add_entry(struct lws_vhost *vh, const char *tag)173 lws_tls_session_add_entry(struct lws_vhost *vh, const char *tag)
174 {
175 lws_tls_sco_t *ts;
176 size_t nl = strlen(tag);
177
178 if (vh->tls_sessions.count == (vh->tls_session_cache_max ?
179 vh->tls_session_cache_max : 10)) {
180
181 /*
182 * We have reached the vhost's session cache limit,
183 * prune the LRU / head
184 */
185 ts = lws_container_of(vh->tls_sessions.head,
186 lws_tls_sco_t, list);
187
188 if (ts) { /* centos 7 ... */
189 lwsl_tlssess("%s: pruning oldest session\n", __func__);
190
191 lws_vhost_lock(vh); /* -------------- vh { */
192 __lws_tls_session_destroy(ts);
193 lws_vhost_unlock(vh); /* } vh -------------- */
194 }
195 }
196
197 ts = lws_malloc(sizeof(*ts) + nl + 1, __func__);
198
199 if (!ts)
200 return NULL;
201
202 memset(ts, 0, sizeof(*ts));
203 memcpy(&ts[1], tag, nl + 1);
204
205 lws_dll2_add_tail(&ts->list, &vh->tls_sessions);
206
207 return ts;
208 }
209
210 static int
lws_tls_session_new_cb(SSL *ssl, SSL_SESSION *sess)211 lws_tls_session_new_cb(SSL *ssl, SSL_SESSION *sess)
212 {
213 struct lws *wsi = (struct lws *)SSL_get_ex_data(ssl,
214 openssl_websocket_private_data_index);
215 char tag[LWS_SESSION_TAG_LEN];
216 struct lws_vhost *vh;
217 lws_tls_sco_t *ts;
218 long ttl;
219 #if (_LWS_ENABLED_LOGS & tlssess_loglevel)
220 const char *disposition = "reuse";
221 #endif
222
223 if (!wsi) {
224 lwsl_warn("%s: can't get wsi from ssl privdata\n", __func__);
225
226 return 0;
227 }
228
229 vh = wsi->a.vhost;
230 if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
231 return 0;
232
233 if (lws_tls_session_tag_from_wsi(wsi, tag, sizeof(tag)))
234 return 0;
235
236 /* api return is long, although we only support setting
237 * default (300s) or max uint32_t */
238 ttl = SSL_SESSION_get_timeout(sess);
239
240 lws_context_lock(vh->context, __func__); /* -------------- cx { */
241 lws_vhost_lock(vh); /* -------------- vh { */
242
243 ts = __lws_tls_session_lookup_by_name(vh, tag);
244
245 if (!ts) {
246 ts = lws_tls_session_add_entry(vh, tag);
247
248 if (!ts)
249 goto bail;
250
251 lws_sul_schedule(wsi->a.context, wsi->tsi, &ts->sul_ttl,
252 lws_tls_session_expiry_cb,
253 ttl * LWS_US_PER_SEC);
254
255 #if (_LWS_ENABLED_LOGS & tlssess_loglevel)
256 disposition = "new";
257 #endif
258
259 /*
260 * We don't have to do a SSL_SESSION_up_ref() here, because
261 * we will return from this callback indicating that we kept the
262 * ref
263 */
264 } else {
265 /*
266 * Give up our refcount on the session we are about to replace
267 * with a newer one
268 */
269 SSL_SESSION_free(ts->session);
270
271 /* keep our session list sorted in lru -> mru order */
272
273 lws_dll2_remove(&ts->list);
274 lws_dll2_add_tail(&ts->list, &vh->tls_sessions);
275 }
276
277 ts->session = sess;
278
279 lws_vhost_unlock(vh); /* } vh -------------- */
280 lws_context_unlock(vh->context); /* } cx -------------- */
281
282 lwsl_tlssess("%s: %p: %s: %s %s, ttl %lds (%s:%u)\n", __func__,
283 sess, wsi->lc.gutag, disposition, tag, ttl, vh->name,
284 vh->tls_sessions.count);
285
286 /*
287 * indicate we will hold on to the SSL_SESSION reference, and take
288 * responsibility to call SSL_SESSION_free() on it ourselves
289 */
290
291 return 1;
292
293 bail:
294 lws_vhost_unlock(vh); /* } vh -------------- */
295 lws_context_unlock(vh->context); /* } cx -------------- */
296
297 return 0;
298 }
299
300 #if defined(LWS_TLS_SYNTHESIZE_CB)
301
302 /*
303 * On openssl, there is an async cb coming when the server issues the session
304 * information on the link, so we can pick it up and update the cache at the
305 * right time.
306 *
307 * On mbedtls and some version at least of borning ssl, this cb is either not
308 * part of the tls library apis or fails to arrive.
309 *
310 * This synthetic cb is called instead for those build cases, scheduled for
311 * +500ms after the tls negotiation completed.
312 */
313
314 void
lws_sess_cache_synth_cb(lws_sorted_usec_list_t *sul)315 lws_sess_cache_synth_cb(lws_sorted_usec_list_t *sul)
316 {
317 struct lws_lws_tls *tls = lws_container_of(sul, struct lws_lws_tls,
318 sul_cb_synth);
319 struct lws *wsi = lws_container_of(tls, struct lws, tls);
320 SSL_SESSION *sess;
321
322 if (lws_tls_session_is_reused(wsi))
323 return;
324
325 sess = SSL_get1_session(tls->ssl);
326 if (!sess)
327 return;
328
329 if (!SSL_SESSION_is_resumable(sess) || /* not worth caching, or... */
330 !lws_tls_session_new_cb(tls->ssl, sess)) { /* ...cb didn't keep it */
331 /*
332 * For now the policy if no session message after the wait,
333 * is just let it be. Typically the session info is sent
334 * early.
335 */
336 SSL_SESSION_free(sess);
337 }
338 }
339 #endif
340
341 void
lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl)342 lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl)
343 {
344 long cmode;
345
346 if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
347 return;
348
349 cmode = SSL_CTX_get_session_cache_mode(vh->tls.ssl_client_ctx);
350
351 SSL_CTX_set_session_cache_mode(vh->tls.ssl_client_ctx,
352 (int)(cmode | SSL_SESS_CACHE_CLIENT));
353
354 SSL_CTX_sess_set_new_cb(vh->tls.ssl_client_ctx, lws_tls_session_new_cb);
355
356 if (!ttl)
357 return;
358
359 #if defined(OPENSSL_IS_BORINGSSL)
360 SSL_CTX_set_timeout(vh->tls.ssl_client_ctx, ttl);
361 #else
362 SSL_CTX_set_timeout(vh->tls.ssl_client_ctx, (long)ttl);
363 #endif
364 }
365
366 int
lws_tls_session_dump_save(struct lws_vhost *vh, const char *host, uint16_t port, lws_tls_sess_cb_t cb_save, void *opq)367 lws_tls_session_dump_save(struct lws_vhost *vh, const char *host, uint16_t port,
368 lws_tls_sess_cb_t cb_save, void *opq)
369 {
370 struct lws_tls_session_dump d;
371 lws_tls_sco_t *ts;
372 int ret = 1, bl;
373 void *v;
374
375 if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
376 return 1;
377
378 lws_tls_session_tag_discrete(vh->name, host, port, d.tag, sizeof(d.tag));
379
380 lws_context_lock(vh->context, __func__); /* -------------- cx { */
381 lws_vhost_lock(vh); /* -------------- vh { */
382
383 ts = __lws_tls_session_lookup_by_name(vh, d.tag);
384 if (!ts)
385 goto bail;
386
387 /* We have a ref on the session, exit via bail to clean it... */
388
389 bl = i2d_SSL_SESSION(ts->session, NULL);
390 if (!bl)
391 goto bail;
392
393 d.blob_len = (size_t)bl;
394 v = d.blob = lws_malloc(d.blob_len, __func__);
395
396 if (d.blob) {
397
398 /* this advances d.blob by the blob size ;-) */
399 i2d_SSL_SESSION(ts->session, (uint8_t **)&d.blob);
400
401 d.opaque = opq;
402 d.blob = v;
403 if (cb_save(vh->context, &d))
404 lwsl_notice("%s: save failed\n", __func__);
405 else
406 ret = 0;
407
408 lws_free(v);
409 }
410
411 bail:
412 lws_vhost_unlock(vh); /* } vh -------------- */
413 lws_context_unlock(vh->context); /* } cx -------------- */
414
415 return ret;
416 }
417
418 int
lws_tls_session_dump_load(struct lws_vhost *vh, const char *host, uint16_t port, lws_tls_sess_cb_t cb_load, void *opq)419 lws_tls_session_dump_load(struct lws_vhost *vh, const char *host, uint16_t port,
420 lws_tls_sess_cb_t cb_load, void *opq)
421 {
422 struct lws_tls_session_dump d;
423 lws_tls_sco_t *ts;
424 SSL_SESSION *sess = NULL; /* allow it to "bail" early */
425 void *v;
426
427 if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
428 return 1;
429
430 d.opaque = opq;
431 lws_tls_session_tag_discrete(vh->name, host, port, d.tag, sizeof(d.tag));
432
433 lws_context_lock(vh->context, __func__); /* -------------- cx { */
434 lws_vhost_lock(vh); /* -------------- vh { */
435
436 ts = __lws_tls_session_lookup_by_name(vh, d.tag);
437
438 if (ts) {
439 /*
440 * Since we are getting this out of cold storage, we should
441 * not replace any existing session since it is likely newer
442 */
443 lwsl_notice("%s: session already exists for %s\n", __func__,
444 d.tag);
445 goto bail1;
446 }
447
448 if (cb_load(vh->context, &d)) {
449 lwsl_warn("%s: load failed\n", __func__);
450
451 goto bail1;
452 }
453
454 /* the callback has allocated the blob and set d.blob / d.blob_len */
455
456 v = d.blob;
457 /* this advances d.blob by the blob size ;-) */
458 sess = d2i_SSL_SESSION(NULL, (const uint8_t **)&d.blob,
459 (long)d.blob_len);
460 free(v); /* user code will have used malloc() */
461 if (!sess) {
462 lwsl_warn("%s: d2i_SSL_SESSION failed\n", __func__);
463 goto bail;
464 }
465
466 lws_vhost_lock(vh); /* -------------- vh { */
467 ts = lws_tls_session_add_entry(vh, d.tag);
468 lws_vhost_unlock(vh); /* } vh -------------- */
469
470 if (!ts) {
471 lwsl_warn("%s: unable to add cache entry\n", __func__);
472 goto bail;
473 }
474
475 ts->session = sess;
476 lwsl_tlssess("%s: session loaded OK\n", __func__);
477
478 lws_vhost_unlock(vh); /* } vh -------------- */
479 lws_context_unlock(vh->context); /* } cx -------------- */
480
481 return 0;
482
483 bail:
484 SSL_SESSION_free(sess);
485 bail1:
486
487 lws_vhost_unlock(vh); /* } vh -------------- */
488 lws_context_unlock(vh->context); /* } cx -------------- */
489
490 return 1;
491 }
492