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