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 27typedef 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 43static void 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 56static lws_tls_sco_t * 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 76void 77lws_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 121bail: 122 lws_vhost_unlock(wsi->a.vhost); /* } vh -------------- */ 123 lws_context_unlock(wsi->a.context); /* } cx -------------- */ 124} 125 126int 127lws_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 141static int 142lws_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 151void 152lws_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 158static void 159lws_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 172static lws_tls_sco_t * 173lws_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 210static int 211lws_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 293bail: 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 314void 315lws_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 341void 342lws_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 366int 367lws_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 411bail: 412 lws_vhost_unlock(vh); /* } vh -------------- */ 413 lws_context_unlock(vh->context); /* } cx -------------- */ 414 415 return ret; 416} 417 418int 419lws_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 483bail: 484 SSL_SESSION_free(sess); 485bail1: 486 487 lws_vhost_unlock(vh); /* } vh -------------- */ 488 lws_context_unlock(vh->context); /* } cx -------------- */ 489 490 return 1; 491} 492