1/*
2 * libwebsockets - small server side websockets and web server implementation
3 *
4 * Copyright (C) 2010 - 2021 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_mbedtls {
28	lws_dll2_t			list;
29
30 	mbedtls_ssl_session		session;
31	lws_sorted_usec_list_t		sul_ttl;
32
33	/* name is overallocated here */
34} lws_tls_scm_t;
35
36#define lwsl_tlssess lwsl_info
37
38
39
40static void
41__lws_tls_session_destroy(lws_tls_scm_t *ts)
42{
43	lwsl_tlssess("%s: %s (%u)\n", __func__, (const char *)&ts[1],
44				     (unsigned int)(ts->list.owner->count - 1));
45
46	lws_sul_cancel(&ts->sul_ttl);
47	mbedtls_ssl_session_free(&ts->session);
48	lws_dll2_remove(&ts->list);		/* vh lock */
49
50	lws_free(ts);
51}
52
53static lws_tls_scm_t *
54__lws_tls_session_lookup_by_name(struct lws_vhost *vh, const char *name)
55{
56	lws_start_foreach_dll(struct lws_dll2 *, p,
57			      lws_dll2_get_head(&vh->tls_sessions)) {
58		lws_tls_scm_t *ts = lws_container_of(p, lws_tls_scm_t, list);
59		const char *ts_name = (const char *)&ts[1];
60
61		if (!strcmp(name, ts_name))
62			return ts;
63
64	} lws_end_foreach_dll(p);
65
66	return NULL;
67}
68
69/*
70 * If possible, reuse an existing, cached session
71 */
72
73void
74lws_tls_reuse_session(struct lws *wsi)
75{
76	char buf[LWS_SESSION_TAG_LEN];
77	mbedtls_ssl_context *msc;
78	lws_tls_scm_t *ts;
79
80	if (!wsi->a.vhost ||
81	    wsi->a.vhost->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
82		return;
83
84	lws_context_lock(wsi->a.context, __func__); /* -------------- cx { */
85	lws_vhost_lock(wsi->a.vhost); /* -------------- vh { */
86
87	if (lws_tls_session_tag_from_wsi(wsi, buf, sizeof(buf)))
88		goto bail;
89
90	ts = __lws_tls_session_lookup_by_name(wsi->a.vhost, buf);
91
92	if (!ts) {
93		lwsl_tlssess("%s: no existing session for %s\n", __func__, buf);
94		goto bail;
95	}
96
97	lwsl_tlssess("%s: %s\n", __func__, (const char *)&ts[1]);
98	wsi->tls_session_reused = 1;
99
100	msc = SSL_mbedtls_ssl_context_from_SSL(wsi->tls.ssl);
101	mbedtls_ssl_set_session(msc, &ts->session);
102
103	/* keep our session list sorted in lru -> mru order */
104
105	lws_dll2_remove(&ts->list);
106	lws_dll2_add_tail(&ts->list, &wsi->a.vhost->tls_sessions);
107
108bail:
109	lws_vhost_unlock(wsi->a.vhost); /* } vh --------------  */
110	lws_context_unlock(wsi->a.context); /* } cx --------------  */
111}
112
113int
114lws_tls_session_is_reused(struct lws *wsi)
115{
116#if defined(LWS_WITH_CLIENT)
117	struct lws *nwsi = lws_get_network_wsi(wsi);
118
119	if (!nwsi)
120		return 0;
121
122	return nwsi->tls_session_reused;
123#else
124	return 0;
125#endif
126}
127
128static int
129lws_tls_session_destroy_dll(struct lws_dll2 *d, void *user)
130{
131	lws_tls_scm_t *ts = lws_container_of(d, lws_tls_scm_t, list);
132
133	__lws_tls_session_destroy(ts);
134
135	return 0;
136}
137
138void
139lws_tls_session_vh_destroy(struct lws_vhost *vh)
140{
141	lws_dll2_foreach_safe(&vh->tls_sessions, NULL,
142			      lws_tls_session_destroy_dll);
143}
144
145static void
146lws_tls_session_expiry_cb(lws_sorted_usec_list_t *sul)
147{
148	lws_tls_scm_t *ts = lws_container_of(sul, lws_tls_scm_t, sul_ttl);
149	struct lws_vhost *vh = lws_container_of(ts->list.owner,
150						struct lws_vhost, tls_sessions);
151
152	lws_context_lock(vh->context, __func__); /* -------------- cx { */
153	lws_vhost_lock(vh); /* -------------- vh { */
154	__lws_tls_session_destroy(ts);
155	lws_vhost_unlock(vh); /* } vh --------------  */
156	lws_context_unlock(vh->context); /* } cx --------------  */
157}
158
159/*
160 * Called after SSL_accept on the wsi
161 */
162
163int
164lws_tls_session_new_mbedtls(struct lws *wsi)
165{
166	char buf[LWS_SESSION_TAG_LEN];
167	mbedtls_ssl_context *msc;
168	struct lws_vhost *vh;
169	lws_tls_scm_t *ts;
170	size_t nl;
171#if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG)
172	const char *disposition = "reuse";
173#endif
174
175	vh = wsi->a.vhost;
176	if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
177		return 0;
178
179	if (lws_tls_session_tag_from_wsi(wsi, buf, sizeof(buf)))
180		return 0;
181
182	nl = strlen(buf);
183
184	msc = SSL_mbedtls_ssl_context_from_SSL(wsi->tls.ssl);
185
186	lws_context_lock(vh->context, __func__); /* -------------- cx { */
187	lws_vhost_lock(vh); /* -------------- vh { */
188
189	ts = __lws_tls_session_lookup_by_name(vh, buf);
190
191	if (!ts) {
192		/*
193		 * We have to make our own, new session
194		 */
195
196		if (vh->tls_sessions.count == vh->tls_session_cache_max) {
197
198			/*
199			 * We have reached the vhost's session cache limit,
200			 * prune the LRU / head
201			 */
202			ts = lws_container_of(vh->tls_sessions.head,
203					      lws_tls_scm_t, list);
204
205			lwsl_tlssess("%s: pruning oldest session (hit max %u)\n",
206				     __func__,
207				     (unsigned int)vh->tls_session_cache_max);
208
209			lws_vhost_lock(vh); /* -------------- vh { */
210			__lws_tls_session_destroy(ts);
211			lws_vhost_unlock(vh); /* } vh --------------  */
212		}
213
214		ts = lws_malloc(sizeof(*ts) + nl + 1, __func__);
215
216		if (!ts)
217			goto bail;
218
219		memset(ts, 0, sizeof(*ts));
220		memcpy(&ts[1], buf, nl + 1);
221
222		if (mbedtls_ssl_get_session(msc, &ts->session)) {
223			lws_free(ts);
224			/* no joy for whatever reason */
225			goto bail;
226		}
227
228		lws_dll2_add_tail(&ts->list, &vh->tls_sessions);
229
230		lws_sul_schedule(wsi->a.context, wsi->tsi, &ts->sul_ttl,
231				 lws_tls_session_expiry_cb,
232				 (int64_t)vh->tls.tls_session_cache_ttl *
233							 LWS_US_PER_SEC);
234
235#if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG)
236		disposition = "new";
237#endif
238	} else {
239
240		mbedtls_ssl_session_free(&ts->session);
241
242		if (mbedtls_ssl_get_session(msc, &ts->session))
243			/* no joy for whatever reason */
244			goto bail;
245
246		/* keep our session list sorted in lru -> mru order */
247
248		lws_dll2_remove(&ts->list);
249		lws_dll2_add_tail(&ts->list, &vh->tls_sessions);
250	}
251
252	lws_vhost_unlock(vh); /* } vh --------------  */
253	lws_context_unlock(vh->context); /* } cx --------------  */
254
255	lwsl_tlssess("%s: %s: %s %s, (%s:%u)\n", __func__,
256		     wsi->lc.gutag, disposition, buf, vh->name,
257		     (unsigned int)vh->tls_sessions.count);
258
259	/*
260	 * indicate we will hold on to the SSL_SESSION reference, and take
261	 * responsibility to call SSL_SESSION_free() on it ourselves
262	 */
263
264	return 1;
265
266bail:
267	lws_vhost_unlock(vh); /* } vh --------------  */
268	lws_context_unlock(vh->context); /* } cx --------------  */
269
270	return 0;
271}
272
273#if defined(LWS_TLS_SYNTHESIZE_CB)
274
275/*
276 * On openssl, there is an async cb coming when the server issues the session
277 * information on the link, so we can pick it up and update the cache at the
278 * right time.
279 *
280 * On mbedtls and some version at least of borning ssl, this cb is either not
281 * part of the tls library apis or fails to arrive.
282 */
283
284void
285lws_sess_cache_synth_cb(lws_sorted_usec_list_t *sul)
286{
287	struct lws_lws_tls *tls = lws_container_of(sul, struct lws_lws_tls,
288						   sul_cb_synth);
289	struct lws *wsi = lws_container_of(tls, struct lws, tls);
290
291	lws_tls_session_new_mbedtls(wsi);
292}
293#endif
294
295void
296lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl)
297{
298	/* Default to 1hr max recommendation from RFC5246 F.1.4 */
299	vh->tls.tls_session_cache_ttl = !ttl ? 3600 : ttl;
300}
301
302int
303lws_tls_session_dump_save(struct lws_vhost *vh, const char *host, uint16_t port,
304			  lws_tls_sess_cb_t cb_save, void *opq)
305{
306	/* there seems no serialization / deserialization helper in mbedtls */
307	lwsl_warn("%s: only supported on openssl atm\n", __func__);
308
309	return 1;
310}
311
312int
313lws_tls_session_dump_load(struct lws_vhost *vh, const char *host, uint16_t port,
314			  lws_tls_sess_cb_t cb_load, void *opq)
315{
316	/* there seems no serialization / deserialization helper in mbedtls */
317	lwsl_warn("%s: only supported on openssl atm\n", __func__);
318
319	return 1;
320}
321