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_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
40 static void
__lws_tls_session_destroy(lws_tls_scm_t * ts)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
53 static lws_tls_scm_t *
__lws_tls_session_lookup_by_name(struct lws_vhost * vh,const char * name)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
73 void
lws_tls_reuse_session(struct lws * wsi)74 lws_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
108 bail:
109 lws_vhost_unlock(wsi->a.vhost); /* } vh -------------- */
110 lws_context_unlock(wsi->a.context); /* } cx -------------- */
111 }
112
113 int
lws_tls_session_is_reused(struct lws * wsi)114 lws_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
128 static int
lws_tls_session_destroy_dll(struct lws_dll2 * d,void * user)129 lws_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
138 void
lws_tls_session_vh_destroy(struct lws_vhost * vh)139 lws_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
145 static void
lws_tls_session_expiry_cb(lws_sorted_usec_list_t * sul)146 lws_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
163 int
lws_tls_session_new_mbedtls(struct lws * wsi)164 lws_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
266 bail:
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
284 void
lws_sess_cache_synth_cb(lws_sorted_usec_list_t * sul)285 lws_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
295 void
lws_tls_session_cache(struct lws_vhost * vh,uint32_t ttl)296 lws_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
302 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)303 lws_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
312 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)313 lws_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