Coverage Report

Created: 2025-12-16 08:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libwebsockets/lib/tls/tls.c
Line
Count
Source
1
/*
2
 * libwebsockets - small server side websockets and web server implementation
3
 *
4
 * Copyright (C) 2010 - 2019 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
#include "private-lib-tls.h"
27
28
#if defined(LWS_HAVE_SSL_CTX_set_keylog_callback) && defined(LWS_WITH_NETWORK) && \
29
  defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS) && \
30
  (!defined(LWS_WITHOUT_CLIENT) || !defined(LWS_WITHOUT_SERVER))
31
void
32
lws_klog_dump(const SSL *ssl, const char *line)
33
0
{
34
0
  struct lws *wsi = (struct lws *)SSL_get_ex_data(ssl,
35
0
            openssl_websocket_private_data_index);
36
0
  char path[128], hdr[128], ts[64];
37
0
  size_t w = 0, wx = 0;
38
0
  int fd, t;
39
40
0
  if (!wsi || !wsi->a.context->keylog_file[0] || !wsi->a.vhost)
41
0
    return;
42
43
0
  lws_snprintf(path, sizeof(path), "%s.%s", wsi->a.context->keylog_file,
44
0
      wsi->a.vhost->name);
45
46
0
  fd = open(path, O_CREAT | O_RDWR | O_APPEND, 0600);
47
0
  if (fd == -1) {
48
0
    lwsl_vhost_warn(wsi->a.vhost, "Failed to append %s", path);
49
0
    return;
50
0
  }
51
52
  /* the first item in the chunk */
53
0
  if (!strncmp(line, "SERVER_HANDSHAKE_TRAFFIC_SECRET", 31)) {
54
0
    w += (size_t)write(fd, "\n# ", 3);
55
0
    wx += 3;
56
0
    t = lwsl_timestamp(LLL_WARN, ts, sizeof(ts));
57
0
    wx += (size_t)t;
58
0
    w += (size_t)write(fd, ts, (size_t)t);
59
60
0
    t = lws_snprintf(hdr, sizeof(hdr), "%s\n", wsi->lc.gutag);
61
0
    w += (size_t)write(fd, hdr, (size_t)t);
62
0
    wx += (size_t)t;
63
64
0
    lwsl_vhost_warn(wsi->a.vhost, "appended ssl keylog: %s", path);
65
0
  }
66
67
0
  wx += strlen(line) + 1;
68
0
  w += (size_t)write(fd, line, 
69
#if defined(WIN32)
70
      (unsigned int)
71
#endif
72
0
      strlen(line));
73
0
  w += (size_t)write(fd, "\n", 1);
74
0
  close(fd);
75
76
0
  if (w != wx) {
77
0
    lwsl_vhost_warn(wsi->a.vhost, "Failed to write %s", path);
78
0
    return;
79
0
  }
80
0
}
81
#endif
82
83
84
#if defined(LWS_WITH_NETWORK)
85
#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \
86
          OPENSSL_VERSION_NUMBER >= 0x10002000L)
87
static int
88
alpn_cb(SSL *s, const unsigned char **out, unsigned char *outlen,
89
  const unsigned char *in, unsigned int inlen, void *arg)
90
0
{
91
0
#if !defined(LWS_WITH_MBEDTLS)
92
0
  struct alpn_ctx *alpn_ctx = (struct alpn_ctx *)arg;
93
94
0
  if (SSL_select_next_proto((unsigned char **)out, outlen, alpn_ctx->data,
95
0
          alpn_ctx->len, in, inlen) !=
96
0
      OPENSSL_NPN_NEGOTIATED)
97
0
    return SSL_TLSEXT_ERR_NOACK;
98
0
#endif
99
100
0
  return SSL_TLSEXT_ERR_OK;
101
0
}
102
#endif
103
104
int
105
lws_tls_restrict_borrow(struct lws *wsi)
106
0
{
107
0
  struct lws_context *cx = wsi->a.context;
108
109
0
  if (cx->simultaneous_ssl_restriction &&
110
0
      cx->simultaneous_ssl >= cx->simultaneous_ssl_restriction) {
111
0
    lwsl_notice("%s: tls connection limit %d\n", __func__,
112
0
          cx->simultaneous_ssl);
113
0
    return 1;
114
0
  }
115
116
0
  if (cx->simultaneous_ssl_handshake_restriction &&
117
0
      cx->simultaneous_ssl_handshake >=
118
0
          cx->simultaneous_ssl_handshake_restriction) {
119
0
    lwsl_notice("%s: tls handshake limit %d\n", __func__,
120
0
          cx->simultaneous_ssl_handshake);
121
0
    return 1;
122
0
  }
123
124
0
  cx->simultaneous_ssl++;
125
0
  cx->simultaneous_ssl_handshake++;
126
0
  wsi->tls_borrowed_hs = 1;
127
0
  wsi->tls_borrowed = 1;
128
129
0
  lwsl_info("%s: %d -> %d\n", __func__,
130
0
      cx->simultaneous_ssl - 1,
131
0
      cx->simultaneous_ssl);
132
133
0
  assert(!cx->simultaneous_ssl_restriction ||
134
0
      cx->simultaneous_ssl <=
135
0
        cx->simultaneous_ssl_restriction);
136
0
  assert(!cx->simultaneous_ssl_handshake_restriction ||
137
0
      cx->simultaneous_ssl_handshake <=
138
0
        cx->simultaneous_ssl_handshake_restriction);
139
140
0
#if defined(LWS_WITH_SERVER)
141
0
  lws_gate_accepts(cx,
142
0
      (cx->simultaneous_ssl_restriction &&
143
0
       cx->simultaneous_ssl == cx->simultaneous_ssl_restriction) ||
144
0
      (cx->simultaneous_ssl_handshake_restriction &&
145
0
       cx->simultaneous_ssl_handshake == cx->simultaneous_ssl_handshake_restriction));
146
0
#endif
147
148
0
  return 0;
149
0
}
150
151
static void
152
_lws_tls_restrict_return(struct lws *wsi)
153
0
{
154
0
#if defined(LWS_WITH_SERVER)
155
0
  struct lws_context *cx = wsi->a.context;
156
157
0
  assert(cx->simultaneous_ssl_handshake >= 0);
158
0
  assert(cx->simultaneous_ssl >= 0);
159
160
0
  lws_gate_accepts(cx,
161
0
      (cx->simultaneous_ssl_restriction &&
162
0
       cx->simultaneous_ssl == cx->simultaneous_ssl_restriction) ||
163
0
      (cx->simultaneous_ssl_handshake_restriction &&
164
0
       cx->simultaneous_ssl_handshake == cx->simultaneous_ssl_handshake_restriction));
165
0
#endif
166
0
}
167
168
void
169
lws_tls_restrict_return_handshake(struct lws *wsi)
170
0
{
171
0
  struct lws_context *cx = wsi->a.context;
172
173
  /* we're just returning the hs part */
174
175
0
  if (!wsi->tls_borrowed_hs)
176
0
    return;
177
178
0
  wsi->tls_borrowed_hs = 0; /* return it one time per wsi */
179
0
  cx->simultaneous_ssl_handshake--;
180
181
0
  lwsl_info("%s:  %d -> %d\n", __func__,
182
0
      cx->simultaneous_ssl_handshake + 1,
183
0
      cx->simultaneous_ssl_handshake);
184
185
0
  _lws_tls_restrict_return(wsi);
186
0
}
187
188
void
189
lws_tls_restrict_return(struct lws *wsi)
190
0
{
191
0
  struct lws_context *cx = wsi->a.context;
192
193
0
  if (!wsi->tls_borrowed)
194
0
    return;
195
196
0
  wsi->tls_borrowed = 0;
197
0
  cx->simultaneous_ssl--;
198
199
0
  lwsl_info("%s: %d -> %d\n", __func__,
200
0
      cx->simultaneous_ssl + 1,
201
0
      cx->simultaneous_ssl);
202
203
  /* We're returning everything, even if hs didn't complete */
204
205
0
  if (wsi->tls_borrowed_hs)
206
0
    lws_tls_restrict_return_handshake(wsi);
207
0
  else
208
0
    _lws_tls_restrict_return(wsi);
209
0
}
210
211
void
212
lws_context_init_alpn(struct lws_vhost *vhost)
213
0
{
214
0
#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \
215
0
          OPENSSL_VERSION_NUMBER >= 0x10002000L)
216
0
  const char *alpn_comma = vhost->context->tls.alpn_default;
217
218
0
  if (vhost->tls.alpn)
219
0
    alpn_comma = vhost->tls.alpn;
220
221
0
  lwsl_info(" Server '%s' advertising ALPN: %s\n",
222
0
        vhost->name, alpn_comma);
223
224
0
  vhost->tls.alpn_ctx.len = (uint8_t)lws_alpn_comma_to_openssl(alpn_comma,
225
0
          vhost->tls.alpn_ctx.data,
226
0
          sizeof(vhost->tls.alpn_ctx.data) - 1);
227
228
0
  SSL_CTX_set_alpn_select_cb(vhost->tls.ssl_ctx, alpn_cb,
229
0
           &vhost->tls.alpn_ctx);
230
#else
231
  lwsl_err(" HTTP2 / ALPN configured "
232
     "but not supported by OpenSSL 0x%lx\n",
233
     OPENSSL_VERSION_NUMBER);
234
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
235
0
}
236
237
int
238
lws_tls_server_conn_alpn(struct lws *wsi)
239
0
{
240
0
#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \
241
0
          OPENSSL_VERSION_NUMBER >= 0x10002000L)
242
0
  const unsigned char *name = NULL;
243
0
  char cstr[10];
244
0
  unsigned len;
245
246
0
  lwsl_info("%s\n", __func__);
247
248
0
  if (!wsi->tls.ssl) {
249
0
    lwsl_err("%s: non-ssl\n", __func__);
250
0
    return 0;
251
0
  }
252
253
0
  SSL_get0_alpn_selected(wsi->tls.ssl, &name, &len);
254
0
  if (!len) {
255
0
    lwsl_info("no ALPN upgrade\n");
256
0
    return 0;
257
0
  }
258
259
0
  if (len > sizeof(cstr) - 1)
260
0
    len = sizeof(cstr) - 1;
261
262
0
  memcpy(cstr, name, len);
263
0
  cstr[len] = '\0';
264
265
0
  lwsl_info("%s: negotiated '%s' using ALPN\n", __func__, cstr);
266
0
  wsi->tls.use_ssl |= LCCSCF_USE_SSL;
267
268
0
  return lws_role_call_alpn_negotiated(wsi, (const char *)cstr);
269
#else
270
  lwsl_err("%s: openssl too old\n", __func__);
271
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
272
273
0
  return 0;
274
0
}
275
#endif
276
277
#if !defined(LWS_PLAT_OPTEE) && !defined(OPTEE_DEV_KIT)
278
#if defined(LWS_PLAT_FREERTOS) && !defined(LWS_AMAZON_RTOS)
279
int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
280
         lws_filepos_t *amount)
281
{
282
  nvs_handle nvh;
283
  size_t s;
284
  int n = 0;
285
286
  ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
287
  if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK) {
288
    n = 1;
289
    goto bail;
290
  }
291
  *buf = lws_malloc(s + 1, "alloc_file");
292
  if (!*buf) {
293
    n = 2;
294
    goto bail;
295
  }
296
  if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK) {
297
    lws_free(*buf);
298
    n = 1;
299
    goto bail;
300
  }
301
302
  *amount = s;
303
  (*buf)[s] = '\0';
304
305
  lwsl_notice("%s: nvs: read %s, %d bytes\n", __func__, filename, (int)s);
306
307
bail:
308
  nvs_close(nvh);
309
310
  return n;
311
}
312
#else
313
int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
314
    lws_filepos_t *amount)
315
0
{
316
0
  FILE *f;
317
0
  size_t s;
318
0
  ssize_t m;
319
0
  int n = 0;
320
321
0
  f = fopen(filename, "rb");
322
0
  if (f == NULL) {
323
0
    n = 1;
324
0
    goto bail;
325
0
  }
326
327
0
  if (fseek(f, 0, SEEK_END) != 0) {
328
0
    n = 1;
329
0
    goto bail;
330
0
  }
331
332
0
  m = (ssize_t)ftell(f);
333
0
  if (m == -1l) {
334
0
    n = 1;
335
0
    goto bail;
336
0
  }
337
0
  s = (size_t)m;
338
339
0
  if (fseek(f, 0, SEEK_SET) != 0) {
340
0
    n = 1;
341
0
    goto bail;
342
0
  }
343
344
0
  *buf = lws_malloc(s + 1, "alloc_file");
345
0
  if (!*buf) {
346
0
    n = 2;
347
0
    goto bail;
348
0
  }
349
350
0
  if (fread(*buf, s, 1, f) != 1) {
351
0
    lws_free(*buf);
352
0
    n = 1;
353
0
    goto bail;
354
0
  }
355
356
0
  *amount = s;
357
358
0
bail:
359
0
  if (f)
360
0
    fclose(f);
361
362
0
  return n;
363
364
0
}
365
#endif
366
367
/*
368
 * filename: NULL means use buffer inbuf length inlen directly, otherwise
369
 *           load the file "filename" into an allocated buffer.
370
 *
371
 * Allocates a separate DER output buffer if inbuf / inlen are the input,
372
 * since the
373
 *
374
 * Contents may be PEM or DER: returns with buf pointing to DER and amount
375
 * set to the DER length.
376
 */
377
378
int
379
lws_tls_alloc_pem_to_der_file(struct lws_context *context, const char *filename,
380
            const char *inbuf, lws_filepos_t inlen,
381
            uint8_t **buf, lws_filepos_t *amount)
382
0
{
383
0
  uint8_t *pem = NULL, *p, *end, *opem;
384
0
  lws_filepos_t len;
385
0
  uint8_t *q;
386
0
  int n;
387
388
0
  if (filename) {
389
0
    n = alloc_file(context, filename, (uint8_t **)&pem, &len);
390
0
    if (n)
391
0
      return n;
392
0
  } else {
393
0
    pem = (uint8_t *)inbuf;
394
0
    len = inlen;
395
0
  }
396
397
0
  opem = p = pem;
398
0
  end = p + len;
399
400
0
  if (strncmp((char *)p, "-----", 5)) {
401
402
    /* take it as being already DER */
403
404
0
    pem = lws_malloc((size_t)inlen, "alloc_der");
405
0
    if (!pem)
406
0
      return 1;
407
408
0
    memcpy(pem, inbuf, (size_t)inlen);
409
410
0
    *buf = pem;
411
0
    *amount = inlen;
412
413
0
    return 0;
414
0
  }
415
416
  /* PEM -> DER */
417
418
0
  if (!filename) {
419
    /* we don't know if it's in const memory... alloc the output */
420
0
    pem = lws_malloc(((size_t)(inlen + 3) * 3) / 4, "alloc_der");
421
0
    if (!pem) {
422
0
      lwsl_err("a\n");
423
0
      return 1;
424
0
    }
425
426
427
0
  } /* else overwrite the allocated, b64 input with decoded DER */
428
429
  /* trim the first line */
430
431
0
  p += 5;
432
0
  while (p < end && *p != '\n' && *p != '-')
433
0
    p++;
434
435
0
  if (*p != '-') {
436
0
    goto bail;
437
0
  }
438
439
0
  while (p < end && *p != '\n')
440
0
    p++;
441
442
0
  if (p >= end) {
443
0
    goto bail;
444
0
  }
445
446
0
  p++;
447
448
  /* trim the last line */
449
450
0
  q = (uint8_t *)end - 2;
451
452
0
  while (q > opem && *q != '\n')
453
0
    q--;
454
455
0
  if (*q != '\n')
456
0
    goto bail;
457
458
  /* we can't write into the input buffer for mem, since it may be in RO
459
   * const segment
460
   */
461
0
  if (filename)
462
0
    *q = '\0';
463
464
0
  n = lws_ptr_diff(q, p);
465
0
  if (n == -1) /* coverity */
466
0
    goto bail;
467
468
0
  n = lws_b64_decode_string_len((char *)p, n,
469
0
              (char *)pem, (int)(long long)len);
470
0
  if (n < 0) {
471
0
    lwsl_err("%s: base64 pem decode failed\n", __func__);
472
0
    goto bail;
473
0
  }
474
475
0
  *amount = (unsigned int)n;
476
0
  *buf = (uint8_t *)pem;
477
478
0
  return 0;
479
480
0
bail:
481
0
  lws_free((uint8_t *)pem);
482
483
0
  return 4;
484
0
}
485
486
487
#endif
488
489
#if !defined(LWS_PLAT_FREERTOS) && !defined(LWS_PLAT_OPTEE) && !defined(OPTEE_DEV_KIT)
490
491
492
static int
493
lws_tls_extant(const char *name)
494
0
{
495
  /* it exists if we can open it... */
496
0
  int fd = open(name, O_RDONLY);
497
0
  char buf[1];
498
0
  ssize_t n;
499
500
0
  if (fd < 0)
501
0
    return 1;
502
503
  /* and we can read at least one byte out of it */
504
0
  n = read(fd, buf, 1);
505
0
  close(fd);
506
507
0
  return n != 1;
508
0
}
509
#endif
510
/*
511
 * Returns 0 if the filepath "name" exists and can be read from.
512
 *
513
 * In addition, if "name".upd exists, backup "name" to "name.old.1"
514
 * and rename "name".upd to "name" before reporting its existence.
515
 *
516
 * There are four situations and three results possible:
517
 *
518
 * 1) LWS_TLS_EXTANT_NO: There are no certs at all (we are waiting for them to
519
 *    be provisioned).  We also feel like this if we need privs we don't have
520
 *    any more to look in the directory.
521
 *
522
 * 2) There are provisioned certs written (xxx.upd) and we still have root
523
 *    privs... in this case we rename any existing cert to have a backup name
524
 *    and move the upd cert into place with the correct name.  This then becomes
525
 *    situation 4 for the caller.
526
 *
527
 * 3) LWS_TLS_EXTANT_ALTERNATIVE: There are provisioned certs written (xxx.upd)
528
 *    but we no longer have the privs needed to read or rename them.  In this
529
 *    case, indicate that the caller should use temp copies if any we do have
530
 *    rights to access.  This is normal after we have updated the cert.
531
 *
532
 *    But if we dropped privs, we can't detect the provisioned xxx.upd cert +
533
 *    key, because we can't see in the dir.  So we have to upgrade NO to
534
 *    ALTERNATIVE when we actually have the in-memory alternative.
535
 *
536
 * 4) LWS_TLS_EXTANT_YES: The certs are present with the correct name and we
537
 *    have the rights to read them.
538
 */
539
540
enum lws_tls_extant
541
lws_tls_use_any_upgrade_check_extant(const char *name)
542
0
{
543
0
#if !defined(LWS_PLAT_OPTEE) && !defined(LWS_AMAZON_RTOS)
544
545
0
  int n;
546
547
0
#if !defined(LWS_PLAT_FREERTOS)
548
0
  char buf[256];
549
550
0
  lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", name);
551
0
  if (!lws_tls_extant(buf)) {
552
    /* ah there is an updated file... how about the desired file? */
553
0
    if (!lws_tls_extant(name)) {
554
      /* rename the desired file */
555
0
      for (n = 0; n < 50; n++) {
556
0
        lws_snprintf(buf, sizeof(buf) - 1,
557
0
               "%s.old.%d", name, n);
558
0
        if (!rename(name, buf))
559
0
          break;
560
0
      }
561
0
      if (n == 50) {
562
0
        lwsl_notice("unable to rename %s\n", name);
563
564
0
        return LWS_TLS_EXTANT_ALTERNATIVE;
565
0
      }
566
0
      lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", name);
567
0
    }
568
    /* desired file is out of the way, rename the updated file */
569
0
    if (rename(buf, name)) {
570
0
      lwsl_notice("unable to rename %s to %s\n", buf, name);
571
572
0
      return LWS_TLS_EXTANT_ALTERNATIVE;
573
0
    }
574
0
  }
575
576
0
  if (lws_tls_extant(name))
577
0
    return LWS_TLS_EXTANT_NO;
578
#else
579
  nvs_handle nvh;
580
  size_t s = 8192;
581
582
  if (nvs_open("lws-station", NVS_READWRITE, &nvh)) {
583
    lwsl_notice("%s: can't open nvs\n", __func__);
584
    return LWS_TLS_EXTANT_NO;
585
  }
586
587
  n = nvs_get_blob(nvh, name, NULL, &s);
588
  nvs_close(nvh);
589
590
  if (n)
591
    return LWS_TLS_EXTANT_NO;
592
#endif
593
0
#endif
594
0
  return LWS_TLS_EXTANT_YES;
595
0
}