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