• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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 lwsl_tlssess lwsl_info
37 
38 static void
__lws_tls_session_destroy(lws_tls_sco_t * ts)39 __lws_tls_session_destroy(lws_tls_sco_t *ts)
40 {
41 	lwsl_tlssess("%s: %s (%u)\n", __func__, (const char *)&ts[1],
42 				     ts->list.owner->count - 1);
43 
44 	lws_sul_cancel(&ts->sul_ttl);
45 	SSL_SESSION_free(ts->session);
46 	lws_dll2_remove(&ts->list);		/* vh lock */
47 
48 	lws_free(ts);
49 }
50 
51 static lws_tls_sco_t *
__lws_tls_session_lookup_by_name(struct lws_vhost * vh,const char * name)52 __lws_tls_session_lookup_by_name(struct lws_vhost *vh, const char *name)
53 {
54 	lws_start_foreach_dll(struct lws_dll2 *, p,
55 			      lws_dll2_get_head(&vh->tls_sessions)) {
56 		lws_tls_sco_t *ts = lws_container_of(p, lws_tls_sco_t, list);
57 		const char *ts_name = (const char *)&ts[1];
58 
59 		if (!strcmp(name, ts_name))
60 			return ts;
61 
62 	} lws_end_foreach_dll(p);
63 
64 	return NULL;
65 }
66 
67 /*
68  * If possible, reuse an existing, cached session
69  */
70 
71 void
lws_tls_reuse_session(struct lws * wsi)72 lws_tls_reuse_session(struct lws *wsi)
73 {
74 	char tag[LWS_SESSION_TAG_LEN];
75 	lws_tls_sco_t *ts;
76 
77 	if (!wsi->a.vhost ||
78 	    wsi->a.vhost->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
79 		return;
80 
81 	lws_context_lock(wsi->a.context, __func__); /* -------------- cx { */
82 	lws_vhost_lock(wsi->a.vhost); /* -------------- vh { */
83 
84 	if (lws_tls_session_tag_from_wsi(wsi, tag, sizeof(tag)))
85 		goto bail;
86 	ts = __lws_tls_session_lookup_by_name(wsi->a.vhost, tag);
87 
88 	if (!ts) {
89 		lwsl_tlssess("%s: no existing session for %s\n", __func__, tag);
90 		goto bail;
91 	}
92 
93 	lwsl_tlssess("%s: %s\n", __func__, (const char *)&ts[1]);
94 
95 	if (!SSL_set_session(wsi->tls.ssl, ts->session)) {
96 		lwsl_err("%s: session not set for %s\n", __func__, tag);
97 		goto bail;
98 	}
99 
100 #if !defined(USE_WOLFSSL)
101 	/* extend session lifetime */
102 	SSL_SESSION_set_time(ts->session,
103 #if defined(OPENSSL_IS_BORINGSSL)
104 			(unsigned long)
105 #else
106 			(long)
107 #endif
108 			time(NULL));
109 #endif
110 
111 	/* keep our session list sorted in lru -> mru order */
112 
113 	lws_dll2_remove(&ts->list);
114 	lws_dll2_add_tail(&ts->list, &wsi->a.vhost->tls_sessions);
115 
116 bail:
117 	lws_vhost_unlock(wsi->a.vhost); /* } vh --------------  */
118 	lws_context_unlock(wsi->a.context); /* } cx --------------  */
119 }
120 
121 int
lws_tls_session_is_reused(struct lws * wsi)122 lws_tls_session_is_reused(struct lws *wsi)
123 {
124 #if defined(LWS_WITH_CLIENT)
125 	struct lws *nwsi = lws_get_network_wsi(wsi);
126 
127 	if (!nwsi || !nwsi->tls.ssl)
128 		return 0;
129 
130        return (int)SSL_session_reused(nwsi->tls.ssl);
131 #else
132        return 0;
133 #endif
134 }
135 
136 static int
lws_tls_session_destroy_dll(struct lws_dll2 * d,void * user)137 lws_tls_session_destroy_dll(struct lws_dll2 *d, void *user)
138 {
139 	lws_tls_sco_t *ts = lws_container_of(d, lws_tls_sco_t, list);
140 
141 	__lws_tls_session_destroy(ts);
142 
143 	return 0;
144 }
145 
146 void
lws_tls_session_vh_destroy(struct lws_vhost * vh)147 lws_tls_session_vh_destroy(struct lws_vhost *vh)
148 {
149 	lws_dll2_foreach_safe(&vh->tls_sessions, NULL,
150 			      lws_tls_session_destroy_dll);
151 }
152 
153 static void
lws_tls_session_expiry_cb(lws_sorted_usec_list_t * sul)154 lws_tls_session_expiry_cb(lws_sorted_usec_list_t *sul)
155 {
156 	lws_tls_sco_t *ts = lws_container_of(sul, lws_tls_sco_t, sul_ttl);
157 	struct lws_vhost *vh = lws_container_of(ts->list.owner,
158 						struct lws_vhost, tls_sessions);
159 
160 	lws_context_lock(vh->context, __func__); /* -------------- cx { */
161 	lws_vhost_lock(vh); /* -------------- vh { */
162 	__lws_tls_session_destroy(ts);
163 	lws_vhost_unlock(vh); /* } vh --------------  */
164 	lws_context_unlock(vh->context); /* } cx --------------  */
165 }
166 
167 static lws_tls_sco_t *
lws_tls_session_add_entry(struct lws_vhost * vh,const char * tag)168 lws_tls_session_add_entry(struct lws_vhost *vh, const char *tag)
169 {
170 	lws_tls_sco_t *ts;
171 	size_t nl = strlen(tag);
172 
173 	if (vh->tls_sessions.count == (vh->tls_session_cache_max ?
174 				      vh->tls_session_cache_max : 10)) {
175 
176 		/*
177 		 * We have reached the vhost's session cache limit,
178 		 * prune the LRU / head
179 		 */
180 		ts = lws_container_of(vh->tls_sessions.head,
181 				      lws_tls_sco_t, list);
182 
183 		if (ts) { /* centos 7 ... */
184 			lwsl_tlssess("%s: pruning oldest session\n", __func__);
185 
186 			lws_vhost_lock(vh); /* -------------- vh { */
187 			__lws_tls_session_destroy(ts);
188 			lws_vhost_unlock(vh); /* } vh --------------  */
189 		}
190 	}
191 
192 	ts = lws_malloc(sizeof(*ts) + nl + 1, __func__);
193 
194 	if (!ts)
195 		return NULL;
196 
197 	memset(ts, 0, sizeof(*ts));
198 	memcpy(&ts[1], tag, nl + 1);
199 
200 	lws_dll2_add_tail(&ts->list, &vh->tls_sessions);
201 
202 	return ts;
203 }
204 
205 static int
lws_tls_session_new_cb(SSL * ssl,SSL_SESSION * sess)206 lws_tls_session_new_cb(SSL *ssl, SSL_SESSION *sess)
207 {
208 	struct lws *wsi = (struct lws *)SSL_get_ex_data(ssl,
209 					openssl_websocket_private_data_index);
210 	char tag[LWS_SESSION_TAG_LEN];
211 	struct lws_vhost *vh;
212 	lws_tls_sco_t *ts;
213 	long ttl;
214 #if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG)
215 	const char *disposition = "reuse";
216 #endif
217 
218 	if (!wsi) {
219 		lwsl_warn("%s: can't get wsi from ssl privdata\n", __func__);
220 
221 		return 0;
222 	}
223 
224 	vh = wsi->a.vhost;
225 	if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
226 		return 0;
227 
228 	if (lws_tls_session_tag_from_wsi(wsi, tag, sizeof(tag)))
229 		return 0;
230 
231 	/* api return is long, although we only support setting
232 	 * default (300s) or max uint32_t */
233 	ttl = SSL_SESSION_get_timeout(sess);
234 
235 	lws_context_lock(vh->context, __func__); /* -------------- cx { */
236 	lws_vhost_lock(vh); /* -------------- vh { */
237 
238 	ts = __lws_tls_session_lookup_by_name(vh, tag);
239 
240 	if (!ts) {
241 		ts = lws_tls_session_add_entry(vh, tag);
242 
243 		if (!ts)
244 			goto bail;
245 
246 		lws_sul_schedule(wsi->a.context, wsi->tsi, &ts->sul_ttl,
247 				 lws_tls_session_expiry_cb,
248 				 ttl * LWS_US_PER_SEC);
249 
250 #if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG)
251 		disposition = "new";
252 #endif
253 
254 		/*
255 		 * We don't have to do a SSL_SESSION_up_ref() here, because
256 		 * we will return from this callback indicating that we kept the
257 		 * ref
258 		 */
259 	} else {
260 		/*
261 		 * Give up our refcount on the session we are about to replace
262 		 * with a newer one
263 		 */
264 		SSL_SESSION_free(ts->session);
265 
266 		/* keep our session list sorted in lru -> mru order */
267 
268 		lws_dll2_remove(&ts->list);
269 		lws_dll2_add_tail(&ts->list, &vh->tls_sessions);
270 	}
271 
272 	ts->session = sess;
273 
274 	lws_vhost_unlock(vh); /* } vh --------------  */
275 	lws_context_unlock(vh->context); /* } cx --------------  */
276 
277 	lwsl_tlssess("%s: %p: %s: %s %s, ttl %lds (%s:%u)\n", __func__,
278 		     sess, wsi->lc.gutag, disposition, tag, ttl, vh->name,
279 		     vh->tls_sessions.count);
280 
281 	/*
282 	 * indicate we will hold on to the SSL_SESSION reference, and take
283 	 * responsibility to call SSL_SESSION_free() on it ourselves
284 	 */
285 
286 	return 1;
287 
288 bail:
289 	lws_vhost_unlock(vh); /* } vh --------------  */
290 	lws_context_unlock(vh->context); /* } cx --------------  */
291 
292 	return 0;
293 }
294 
295 #if defined(LWS_TLS_SYNTHESIZE_CB)
296 
297 /*
298  * On openssl, there is an async cb coming when the server issues the session
299  * information on the link, so we can pick it up and update the cache at the
300  * right time.
301  *
302  * On mbedtls and some version at least of borning ssl, this cb is either not
303  * part of the tls library apis or fails to arrive.
304  *
305  * This synthetic cb is called instead for those build cases, scheduled for
306  * +500ms after the tls negotiation completed.
307  */
308 
309 void
lws_sess_cache_synth_cb(lws_sorted_usec_list_t * sul)310 lws_sess_cache_synth_cb(lws_sorted_usec_list_t *sul)
311 {
312 	struct lws_lws_tls *tls = lws_container_of(sul, struct lws_lws_tls,
313 						   sul_cb_synth);
314 	struct lws *wsi = lws_container_of(tls, struct lws, tls);
315 	SSL_SESSION *sess;
316 
317 	if (lws_tls_session_is_reused(wsi))
318 		return;
319 
320 	sess = SSL_get1_session(tls->ssl);
321 	if (!sess)
322 		return;
323 
324 	if (!SSL_SESSION_is_resumable(sess) || /* not worth caching, or... */
325 	    !lws_tls_session_new_cb(tls->ssl, sess)) { /* ...cb didn't keep it */
326 		/*
327 		 * For now the policy if no session message after the wait,
328 		 * is just let it be.  Typically the session info is sent
329 		 * early.
330 		 */
331 		SSL_SESSION_free(sess);
332 	}
333 }
334 #endif
335 
336 void
lws_tls_session_cache(struct lws_vhost * vh,uint32_t ttl)337 lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl)
338 {
339 	long cmode;
340 
341 	if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
342 		return;
343 
344 	cmode = SSL_CTX_get_session_cache_mode(vh->tls.ssl_client_ctx);
345 
346 	SSL_CTX_set_session_cache_mode(vh->tls.ssl_client_ctx,
347 				       (int)(cmode | SSL_SESS_CACHE_CLIENT));
348 
349 	SSL_CTX_sess_set_new_cb(vh->tls.ssl_client_ctx, lws_tls_session_new_cb);
350 
351 	if (!ttl)
352 		return;
353 
354 #if defined(OPENSSL_IS_BORINGSSL)
355 	SSL_CTX_set_timeout(vh->tls.ssl_client_ctx, ttl);
356 #else
357 	SSL_CTX_set_timeout(vh->tls.ssl_client_ctx, (long)ttl);
358 #endif
359 }
360 
361 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)362 lws_tls_session_dump_save(struct lws_vhost *vh, const char *host, uint16_t port,
363 			  lws_tls_sess_cb_t cb_save, void *opq)
364 {
365 	struct lws_tls_session_dump d;
366 	lws_tls_sco_t *ts;
367 	int ret = 1, bl;
368 	void *v;
369 
370 	if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
371 		return 1;
372 
373 	lws_tls_session_tag_discrete(vh->name, host, port, d.tag, sizeof(d.tag));
374 
375 	lws_context_lock(vh->context, __func__); /* -------------- cx { */
376 	lws_vhost_lock(vh); /* -------------- vh { */
377 
378 	ts = __lws_tls_session_lookup_by_name(vh, d.tag);
379 	if (!ts)
380 		goto bail;
381 
382 	/* We have a ref on the session, exit via bail to clean it... */
383 
384 	bl = i2d_SSL_SESSION(ts->session, NULL);
385 	if (!bl)
386 		goto bail;
387 
388 	d.blob_len = (size_t)bl;
389 	v = d.blob = lws_malloc(d.blob_len, __func__);
390 
391 	if (d.blob) {
392 
393 		/* this advances d.blob by the blob size ;-) */
394 		i2d_SSL_SESSION(ts->session, (uint8_t **)&d.blob);
395 
396 		d.opaque = opq;
397 		d.blob = v;
398 		if (cb_save(vh->context, &d))
399 			lwsl_notice("%s: save failed\n", __func__);
400 		else
401 			ret = 0;
402 
403 		lws_free(v);
404 	}
405 
406 bail:
407 	lws_vhost_unlock(vh); /* } vh --------------  */
408 	lws_context_unlock(vh->context); /* } cx --------------  */
409 
410 	return ret;
411 }
412 
413 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)414 lws_tls_session_dump_load(struct lws_vhost *vh, const char *host, uint16_t port,
415 			  lws_tls_sess_cb_t cb_load, void *opq)
416 {
417 	struct lws_tls_session_dump d;
418 	lws_tls_sco_t *ts;
419 	SSL_SESSION *sess = NULL; /* allow it to "bail" early */
420 	void *v;
421 
422 	if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
423 		return 1;
424 
425 	d.opaque = opq;
426 	lws_tls_session_tag_discrete(vh->name, host, port, d.tag, sizeof(d.tag));
427 
428 	lws_context_lock(vh->context, __func__); /* -------------- cx { */
429 	lws_vhost_lock(vh); /* -------------- vh { */
430 
431 	ts = __lws_tls_session_lookup_by_name(vh, d.tag);
432 
433 	if (ts) {
434 		/*
435 		 * Since we are getting this out of cold storage, we should
436 		 * not replace any existing session since it is likely newer
437 		 */
438 		lwsl_notice("%s: session already exists for %s\n", __func__,
439 				d.tag);
440 		goto bail1;
441 	}
442 
443 	if (cb_load(vh->context, &d)) {
444 		lwsl_warn("%s: load failed\n", __func__);
445 
446 		goto bail1;
447 	}
448 
449 	/* the callback has allocated the blob and set d.blob / d.blob_len */
450 
451 	v = d.blob;
452 	/* this advances d.blob by the blob size ;-) */
453 	sess = d2i_SSL_SESSION(NULL, (const uint8_t **)&d.blob,
454 							(long)d.blob_len);
455 	free(v); /* user code will have used malloc() */
456 	if (!sess) {
457 		lwsl_warn("%s: d2i_SSL_SESSION failed\n", __func__);
458 		goto bail;
459 	}
460 
461 	lws_vhost_lock(vh); /* -------------- vh { */
462 	ts = lws_tls_session_add_entry(vh, d.tag);
463 	lws_vhost_unlock(vh); /* } vh --------------  */
464 
465 	if (!ts) {
466 		lwsl_warn("%s: unable to add cache entry\n", __func__);
467 		goto bail;
468 	}
469 
470 	ts->session = sess;
471 	lwsl_tlssess("%s: session loaded OK\n", __func__);
472 
473 	lws_vhost_unlock(vh); /* } vh --------------  */
474 	lws_context_unlock(vh->context); /* } cx --------------  */
475 
476 	return 0;
477 
478 bail:
479 	SSL_SESSION_free(sess);
480 bail1:
481 
482 	lws_vhost_unlock(vh); /* } vh --------------  */
483 	lws_context_unlock(vh->context); /* } cx --------------  */
484 
485 	return 1;
486 }
487