Coverage Report

Created: 2025-08-03 06:52

/src/libwebsockets/lib/tls/openssl/openssl-session.c
Line
Count
Source (jump to first uncovered line)
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
0
#define tlssess_loglevel    LLL_INFO
37
#if (_LWS_ENABLED_LOGS & tlssess_loglevel)
38
0
#define lwsl_tlssess(...)   _lws_log(tlssess_loglevel, __VA_ARGS__)
39
#else
40
#define lwsl_tlssess(...)
41
#endif
42
43
static void
44
__lws_tls_session_destroy(lws_tls_sco_t *ts)
45
0
{
46
0
  lwsl_tlssess("%s: %s (%u)\n", __func__, (const char *)&ts[1],
47
0
             ts->list.owner->count - 1);
48
49
0
  lws_sul_cancel(&ts->sul_ttl);
50
0
  SSL_SESSION_free(ts->session);
51
0
  lws_dll2_remove(&ts->list);   /* vh lock */
52
53
0
  lws_free(ts);
54
0
}
55
56
static lws_tls_sco_t *
57
__lws_tls_session_lookup_by_name(struct lws_vhost *vh, const char *name)
58
0
{
59
0
  lws_start_foreach_dll(struct lws_dll2 *, p,
60
0
            lws_dll2_get_head(&vh->tls_sessions)) {
61
0
    lws_tls_sco_t *ts = lws_container_of(p, lws_tls_sco_t, list);
62
0
    const char *ts_name = (const char *)&ts[1];
63
64
0
    if (!strcmp(name, ts_name))
65
0
      return ts;
66
67
0
  } lws_end_foreach_dll(p);
68
69
0
  return NULL;
70
0
}
71
72
/*
73
 * If possible, reuse an existing, cached session
74
 */
75
76
void
77
lws_tls_reuse_session(struct lws *wsi)
78
0
{
79
0
  char tag[LWS_SESSION_TAG_LEN];
80
0
  lws_tls_sco_t *ts;
81
82
0
  if (!wsi->a.vhost ||
83
0
      wsi->a.vhost->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
84
0
    return;
85
86
0
  lws_context_lock(wsi->a.context, __func__); /* -------------- cx { */
87
0
  lws_vhost_lock(wsi->a.vhost); /* -------------- vh { */
88
89
0
  if (lws_tls_session_tag_from_wsi(wsi, tag, sizeof(tag)))
90
0
    goto bail;
91
0
  ts = __lws_tls_session_lookup_by_name(wsi->a.vhost, tag);
92
93
0
  if (!ts) {
94
0
    lwsl_tlssess("%s: no existing session for %s\n", __func__, tag);
95
0
    goto bail;
96
0
  }
97
98
0
  lwsl_tlssess("%s: %s\n", __func__, (const char *)&ts[1]);
99
100
0
  if (!SSL_set_session(wsi->tls.ssl, ts->session)) {
101
0
    lwsl_err("%s: session not set for %s\n", __func__, tag);
102
0
    goto bail;
103
0
  }
104
105
0
#if !defined(USE_WOLFSSL)
106
  /* extend session lifetime */
107
0
  SSL_SESSION_set_time(ts->session,
108
#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
109
      (unsigned long)
110
#else
111
0
      (long)
112
0
#endif
113
0
      time(NULL));
114
0
#endif
115
116
  /* keep our session list sorted in lru -> mru order */
117
118
0
  lws_dll2_remove(&ts->list);
119
0
  lws_dll2_add_tail(&ts->list, &wsi->a.vhost->tls_sessions);
120
121
0
bail:
122
0
  lws_vhost_unlock(wsi->a.vhost); /* } vh --------------  */
123
0
  lws_context_unlock(wsi->a.context); /* } cx --------------  */
124
0
}
125
126
int
127
lws_tls_session_is_reused(struct lws *wsi)
128
0
{
129
0
#if defined(LWS_WITH_CLIENT)
130
0
  struct lws *nwsi = lws_get_network_wsi(wsi);
131
132
0
  if (!nwsi || !nwsi->tls.ssl)
133
0
    return 0;
134
135
0
       return (int)SSL_session_reused(nwsi->tls.ssl);
136
#else
137
       return 0;
138
#endif
139
0
}
140
141
static int
142
lws_tls_session_destroy_dll(struct lws_dll2 *d, void *user)
143
0
{
144
0
  lws_tls_sco_t *ts = lws_container_of(d, lws_tls_sco_t, list);
145
146
0
  __lws_tls_session_destroy(ts);
147
148
0
  return 0;
149
0
}
150
151
void
152
lws_tls_session_vh_destroy(struct lws_vhost *vh)
153
0
{
154
0
  lws_dll2_foreach_safe(&vh->tls_sessions, NULL,
155
0
            lws_tls_session_destroy_dll);
156
0
}
157
158
static void
159
lws_tls_session_expiry_cb(lws_sorted_usec_list_t *sul)
160
0
{
161
0
  lws_tls_sco_t *ts = lws_container_of(sul, lws_tls_sco_t, sul_ttl);
162
0
  struct lws_vhost *vh = lws_container_of(ts->list.owner,
163
0
            struct lws_vhost, tls_sessions);
164
165
0
  lws_context_lock(vh->context, __func__); /* -------------- cx { */
166
0
  lws_vhost_lock(vh); /* -------------- vh { */
167
0
  __lws_tls_session_destroy(ts);
168
0
  lws_vhost_unlock(vh); /* } vh --------------  */
169
0
  lws_context_unlock(vh->context); /* } cx --------------  */
170
0
}
171
172
static lws_tls_sco_t *
173
lws_tls_session_add_entry(struct lws_vhost *vh, const char *tag)
174
0
{
175
0
  lws_tls_sco_t *ts;
176
0
  size_t nl = strlen(tag);
177
178
0
  if (vh->tls_sessions.count == (vh->tls_session_cache_max ?
179
0
              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
0
    ts = lws_container_of(vh->tls_sessions.head,
186
0
              lws_tls_sco_t, list);
187
188
0
    if (ts) { /* centos 7 ... */
189
0
      lwsl_tlssess("%s: pruning oldest session\n", __func__);
190
191
0
      lws_vhost_lock(vh); /* -------------- vh { */
192
0
      __lws_tls_session_destroy(ts);
193
0
      lws_vhost_unlock(vh); /* } vh --------------  */
194
0
    }
195
0
  }
196
197
0
  ts = lws_malloc(sizeof(*ts) + nl + 1, __func__);
198
199
0
  if (!ts)
200
0
    return NULL;
201
202
0
  memset(ts, 0, sizeof(*ts));
203
0
  memcpy(&ts[1], tag, nl + 1);
204
205
0
  lws_dll2_add_tail(&ts->list, &vh->tls_sessions);
206
207
0
  return ts;
208
0
}
209
210
static int
211
lws_tls_session_new_cb(SSL *ssl, SSL_SESSION *sess)
212
0
{
213
0
  struct lws *wsi = (struct lws *)SSL_get_ex_data(ssl,
214
0
          openssl_websocket_private_data_index);
215
0
  char tag[LWS_SESSION_TAG_LEN];
216
0
  struct lws_vhost *vh;
217
0
  lws_tls_sco_t *ts;
218
0
  long ttl;
219
0
#if (_LWS_ENABLED_LOGS & tlssess_loglevel)
220
0
  const char *disposition = "reuse";
221
0
#endif
222
223
0
  if (!wsi) {
224
0
    lwsl_warn("%s: can't get wsi from ssl privdata\n", __func__);
225
226
0
    return 0;
227
0
  }
228
229
0
  vh = wsi->a.vhost;
230
0
  if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
231
0
    return 0;
232
233
0
  if (lws_tls_session_tag_from_wsi(wsi, tag, sizeof(tag)))
234
0
    return 0;
235
236
  /* api return is long, although we only support setting
237
   * default (300s) or max uint32_t */
238
0
  ttl = SSL_SESSION_get_timeout(sess);
239
240
0
  lws_context_lock(vh->context, __func__); /* -------------- cx { */
241
0
  lws_vhost_lock(vh); /* -------------- vh { */
242
243
0
  ts = __lws_tls_session_lookup_by_name(vh, tag);
244
245
0
  if (!ts) {
246
0
    ts = lws_tls_session_add_entry(vh, tag);
247
248
0
    if (!ts)
249
0
      goto bail;
250
251
0
    lws_sul_schedule(wsi->a.context, wsi->tsi, &ts->sul_ttl,
252
0
         lws_tls_session_expiry_cb,
253
0
         ttl * LWS_US_PER_SEC);
254
255
0
#if (_LWS_ENABLED_LOGS & tlssess_loglevel)
256
0
    disposition = "new";
257
0
#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
0
  } else {
265
    /*
266
     * Give up our refcount on the session we are about to replace
267
     * with a newer one
268
     */
269
0
    SSL_SESSION_free(ts->session);
270
271
    /* keep our session list sorted in lru -> mru order */
272
273
0
    lws_dll2_remove(&ts->list);
274
0
    lws_dll2_add_tail(&ts->list, &vh->tls_sessions);
275
0
  }
276
277
0
  ts->session = sess;
278
279
0
  lws_vhost_unlock(vh); /* } vh --------------  */
280
0
  lws_context_unlock(vh->context); /* } cx --------------  */
281
282
0
  lwsl_tlssess("%s: %p: %s: %s %s, ttl %lds (%s:%u)\n", __func__,
283
0
         sess, wsi->lc.gutag, disposition, tag, ttl, vh->name,
284
0
         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
0
  return 1;
292
293
0
bail:
294
0
  lws_vhost_unlock(vh); /* } vh --------------  */
295
0
  lws_context_unlock(vh->context); /* } cx --------------  */
296
297
0
  return 0;
298
0
}
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
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
342
lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl)
343
0
{
344
0
  long cmode;
345
346
0
  if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
347
0
    return;
348
349
0
  cmode = SSL_CTX_get_session_cache_mode(vh->tls.ssl_client_ctx);
350
351
0
  SSL_CTX_set_session_cache_mode(vh->tls.ssl_client_ctx,
352
0
               (int)(cmode | SSL_SESS_CACHE_CLIENT));
353
354
0
  SSL_CTX_sess_set_new_cb(vh->tls.ssl_client_ctx, lws_tls_session_new_cb);
355
356
0
  if (!ttl)
357
0
    return;
358
359
#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
360
  SSL_CTX_set_timeout(vh->tls.ssl_client_ctx, ttl);
361
#else
362
0
  SSL_CTX_set_timeout(vh->tls.ssl_client_ctx, (long)ttl);
363
0
#endif
364
0
}
365
366
int
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
0
{
370
0
  struct lws_tls_session_dump d;
371
0
  lws_tls_sco_t *ts;
372
0
  int ret = 1, bl;
373
0
  void *v;
374
375
0
  if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
376
0
    return 1;
377
378
0
  lws_tls_session_tag_discrete(vh->name, host, port, d.tag, sizeof(d.tag));
379
380
0
  lws_context_lock(vh->context, __func__); /* -------------- cx { */
381
0
  lws_vhost_lock(vh); /* -------------- vh { */
382
383
0
  ts = __lws_tls_session_lookup_by_name(vh, d.tag);
384
0
  if (!ts)
385
0
    goto bail;
386
387
  /* We have a ref on the session, exit via bail to clean it... */
388
389
0
  bl = i2d_SSL_SESSION(ts->session, NULL);
390
0
  if (!bl)
391
0
    goto bail;
392
393
0
  d.blob_len = (size_t)bl;
394
0
  v = d.blob = lws_malloc(d.blob_len, __func__);
395
396
0
  if (d.blob) {
397
398
    /* this advances d.blob by the blob size ;-) */
399
0
    i2d_SSL_SESSION(ts->session, (uint8_t **)&d.blob);
400
401
0
    d.opaque = opq;
402
0
    d.blob = v;
403
0
    if (cb_save(vh->context, &d))
404
0
      lwsl_notice("%s: save failed\n", __func__);
405
0
    else
406
0
      ret = 0;
407
408
0
    lws_free(v);
409
0
  }
410
411
0
bail:
412
0
  lws_vhost_unlock(vh); /* } vh --------------  */
413
0
  lws_context_unlock(vh->context); /* } cx --------------  */
414
415
0
  return ret;
416
0
}
417
418
int
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
0
{
422
0
  struct lws_tls_session_dump d;
423
0
  lws_tls_sco_t *ts;
424
0
  SSL_SESSION *sess = NULL; /* allow it to "bail" early */
425
0
  void *v;
426
427
0
  if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
428
0
    return 1;
429
430
0
  d.opaque = opq;
431
0
  lws_tls_session_tag_discrete(vh->name, host, port, d.tag, sizeof(d.tag));
432
433
0
  lws_context_lock(vh->context, __func__); /* -------------- cx { */
434
0
  lws_vhost_lock(vh); /* -------------- vh { */
435
436
0
  ts = __lws_tls_session_lookup_by_name(vh, d.tag);
437
438
0
  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
0
    lwsl_notice("%s: session already exists for %s\n", __func__,
444
0
        d.tag);
445
0
    goto bail1;
446
0
  }
447
448
0
  if (cb_load(vh->context, &d)) {
449
0
    lwsl_warn("%s: load failed\n", __func__);
450
451
0
    goto bail1;
452
0
  }
453
454
  /* the callback has allocated the blob and set d.blob / d.blob_len */
455
456
0
  v = d.blob;
457
  /* this advances d.blob by the blob size ;-) */
458
0
  sess = d2i_SSL_SESSION(NULL, (const uint8_t **)&d.blob,
459
0
              (long)d.blob_len);
460
0
  free(v); /* user code will have used malloc() */
461
0
  if (!sess) {
462
0
    lwsl_warn("%s: d2i_SSL_SESSION failed\n", __func__);
463
0
    goto bail;
464
0
  }
465
466
0
  lws_vhost_lock(vh); /* -------------- vh { */
467
0
  ts = lws_tls_session_add_entry(vh, d.tag);
468
0
  lws_vhost_unlock(vh); /* } vh --------------  */
469
470
0
  if (!ts) {
471
0
    lwsl_warn("%s: unable to add cache entry\n", __func__);
472
0
    goto bail;
473
0
  }
474
475
0
  ts->session = sess;
476
0
  lwsl_tlssess("%s: session loaded OK\n", __func__);
477
478
0
  lws_vhost_unlock(vh); /* } vh --------------  */
479
0
  lws_context_unlock(vh->context); /* } cx --------------  */
480
481
0
  return 0;
482
483
0
bail:
484
0
  SSL_SESSION_free(sess);
485
0
bail1:
486
487
0
  lws_vhost_unlock(vh); /* } vh --------------  */
488
0
  lws_context_unlock(vh->context); /* } cx --------------  */
489
490
0
  return 1;
491
0
}