diff --git a/READMEs/README.tls-sessions.md b/READMEs/README.tls-sessions.md new file mode 100644 index 000000000..be2249553 --- /dev/null +++ b/READMEs/README.tls-sessions.md @@ -0,0 +1,105 @@ +# Using TLS Session resumption + +Lws supports clientside session caching and session resumption on both mbedtls +and openssl-type tls library backends, to accellerate connection re- +establishment. + +## Background + +TLS specifies logical "sessions" that get "established" on both sides when the +tls tunnel is negotiated... these are the object that gets validated by the +certificate PKI. They each have a server-unique "Session ID" of up to 32 bytes +each. + +Normally the default is that there is a new session negotiated per connection, +so multiple connections to the same endpoint each negotiate fresh sessions from +scratch. + +However tls servers typically maintain a cache of recent sessions, and where +both the server and client still have a copy of a previously-negotiated session +around, support the client explicitly requesting additional connections binding +to the old session by asking for it by its Session ID at negotiation time. + +### Re-use of validated sessions + +The advantage is that the timeconsuming key exchange part of the negotiation can +be skipped, and a connection-specific AES key agreed at both sides just by +hashing on the secret held in the session object at each side. This allows new +tunnels to be established much faster after the first, while the session from +the first is still valid and available at both sides. + +Both the server and client may apply their own lifetime restriction to their +copy of the session, the first side to expire it will cause a new session to be +forced at the next reuse attempt. Lifetimes above 24h are not recommended by +RFC5246. + +### Multiple concurrent use of validated sessions + +In addition, the session's scope is any connection to the server that knows the +original session ID, because individual new AES keys are hashed from the session +secret, multiple connections to the same endpoint can take advantage of a single +valid session object. + +### Difference from Session Tickets + +TLS also supports sessions as bearer tokens, but these are generally considered +as degrading security. Lws doesn't support Session Tickets, just reuse by +Session IDs. + +## Support in lws + +Server-side TLS generally has session caching enabled by default. For client +side, lws now enables `LWS_WITH_TLS_SESSIONS` at cmake by default, which adds +a configurable tls session cache that is automatically kept updated with a +MRU-sorted list of established sessions. + +It's also possible to serialize sessions and save and load them, but this has to +be treated with caution. + +Filling, expiring and consulting the session cache for client connections is +performed automatically. + +### Session namespacing in lws + +Internally sessions are referred to by a vhostname.hostname.port tuple. + +### Configuring the clientside cache + +Session caches in lws exist in and are bound to the vhost. Different vhosts may +provide different authentication (eg, client certs) to the same endpoint that +another connection should not be able to take advantage of. + +The max size of this cache can be set at `.tls_session_cache_max` in the vhost +creation info struct, if left at 0 then a default of 10 is applied. + +The Time-To-Live policy for sessions at the client can be set in seconds at +`.tls_session_timeout`, by default whatever the tls library thinks it should be, +perhaps 300s. + +You can disable session caching for a particular vhost by adding the vhost +option flag `LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE` to `.options` at +vhost creation time. + +### Session saving and loading + +Trying to make sessions really persistent is supported but requires extra +caution. RFC5246 says + + Applications that may be run in relatively insecure environments should not + write session IDs to stable storage. + +The issue is that while in process memory the session object is relatively +secure compared to ensitive secrets and tls library data already in process +memory. + +But when serialized to, eg, some external, unencrypted medium, the accessibility +of what is basically a secret able to decrypt tls connections can become a +security hazard. It's left to the user to take any necessary steps to secure +sessions stored that way. + +For openssl, Public APIs are provided in `libwebsockets/lws-tls-sessions.h` to +serialize any session in the cache associated with a vhost/host/port tuple, and +to preload any available session into a vhost session cache by describing the +endpoint hostname and port. + +The session saving and loading apis aren't supported for mbedtls yet. \ No newline at end of file diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index a1574c60b..a7fcfca91 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -91,6 +91,7 @@ #cmakedefine LWS_HAVE_SSL_set_alpn_protos #cmakedefine LWS_HAVE_SSL_SET_INFO_CALLBACK #cmakedefine LWS_HAVE_SSL_SESSION_set_time +#cmakedefine LWS_HAVE_SSL_SESSION_up_ref #cmakedefine LWS_HAVE__STAT32I64 #cmakedefine LWS_HAVE_STDINT_H #cmakedefine LWS_HAVE_SYS_CAPABILITY_H diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 6c989168f..721516172 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -629,6 +629,8 @@ struct lws; #if defined(LWS_WITH_TLS) +#include + #if defined(LWS_WITH_MBEDTLS) #include #include diff --git a/include/libwebsockets/lws-tls-sessions.h b/include/libwebsockets/lws-tls-sessions.h new file mode 100644 index 000000000..e0b409e6f --- /dev/null +++ b/include/libwebsockets/lws-tls-sessions.h @@ -0,0 +1,81 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2021 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/*! \defgroup tls_sessions TLS Session Management + + APIs related to managing TLS Sessions +*/ +//@{ + + +#define LWS_SESSION_TAG_LEN 96 + +struct lws_tls_session_dump +{ + char tag[LWS_SESSION_TAG_LEN]; + void *blob; + void *opaque; + size_t blob_len; +}; + +typedef int (*lws_tls_sess_cb_t)(struct lws_context *cx, + struct lws_tls_session_dump *info); + +/** + * lws_tls_session_dump_save() - serialize a tls session via a callback + * + * \param vh: the vhost to load into the session cache + * \param host: the name of the host the session relates to + * \param port: the port the session connects to on the host + * \param cb_save: the callback to perform the saving of the session blob + * \param opq: an opaque pointer passed into the callback + * + * If a session matching the vhost/host/port exists in the vhost's session + * cache, serialize it via the provided callback. + * + * \p opq is passed to the callback without being used by lws at all. + */ +LWS_VISIBLE LWS_EXTERN 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); + +/** + * lws_tls_session_dump_load() - deserialize a tls session via a callback + * + * \param vh: the vhost to load into the session cache + * \param host: the name of the host the session relates to + * \param port: the port the session connects to on the host + * \param cb_load: the callback to retreive the session blob from + * \param opq: an opaque pointer passed into the callback + * + * Try to preload a session described by the first three parameters into the + * client session cache, from the given callback. + * + * \p opq is passed to the callback without being used by lws at all. + */ +LWS_VISIBLE LWS_EXTERN 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); + +///@} diff --git a/lib/roles/http/client/client-http.c b/lib/roles/http/client/client-http.c index 1561aa7d6..925e119b7 100644 --- a/lib/roles/http/client/client-http.c +++ b/lib/roles/http/client/client-http.c @@ -577,7 +577,7 @@ lws_client_interpret_server_handshake(struct lws *wsi) char *p = NULL, *q, *simp; char new_path[300]; - lws_free_set_NULL(wsi->stash); + // lws_free_set_NULL(wsi->stash); #if defined(LWS_WITH_CONMON) wsi->conmon.ciu_txn_resp = (lws_conmon_interval_us_t) diff --git a/lib/tls/CMakeLists.txt b/lib/tls/CMakeLists.txt index 58b1c3d99..497634c5d 100644 --- a/lib/tls/CMakeLists.txt +++ b/lib/tls/CMakeLists.txt @@ -113,7 +113,11 @@ if (LWS_WITH_SSL) list(APPEND SOURCES tls/tls-network.c) endif() - + if (LWS_WITH_TLS_SESSIONS) + list(APPEND SOURCES + tls/tls-sessions.c) + endif() + if (LWS_WITH_MBEDTLS) list(APPEND SOURCES tls/mbedtls/mbedtls-tls.c @@ -315,6 +319,8 @@ CHECK_FUNCTION_EXISTS(${VARIA}HMAC_CTX_new LWS_HAVE_HMAC_CTX_new PARENT_SCOPE) CHECK_SYMBOL_EXISTS(${VARIA}SSL_CTX_set_ciphersuites LWS_HAVE_SSL_CTX_set_ciphersuites PARENT_SCOPE) CHECK_FUNCTION_EXISTS(${VARIA}EVP_PKEY_new_raw_private_key LWS_HAVE_EVP_PKEY_new_raw_private_key PARENT_SCOPE) CHECK_FUNCTION_EXISTS(${VARIA}SSL_SESSION_set_time LWS_HAVE_SSL_SESSION_set_time PARENT_SCOPE) +CHECK_SYMBOL_EXISTS(${VARIA}SSL_SESSION_up_ref LWS_HAVE_SSL_SESSION_up_ref PARENT_SCOPE) + # deprecated in openssl v3 CHECK_FUNCTION_EXISTS(${VARIA}EC_KEY_new_by_curve_name LWS_HAVE_EC_KEY_new_by_curve_name PARENT_SCOPE) diff --git a/lib/tls/mbedtls/mbedtls-session.c b/lib/tls/mbedtls/mbedtls-session.c index d0c70bfae..1450e34f5 100644 --- a/lib/tls/mbedtls/mbedtls-session.c +++ b/lib/tls/mbedtls/mbedtls-session.c @@ -33,29 +33,9 @@ typedef struct lws_tls_session_cache_mbedtls { /* name is overallocated here */ } lws_tls_scm_t; -#define lwsl_tlssess lwsl_notice +#define lwsl_tlssess lwsl_info -static int -lws_tls_session_name_from_wsi(struct lws *wsi, char *buf, size_t len) -{ - size_t n; - /* - * We have to include the vhost name in the session tag, since - * different vhosts may make connections to the same endpoint using - * different client certs. - */ - - n = (size_t)lws_snprintf(buf, len, "%s.", wsi->a.vhost->name); - - buf += n; - len = len - n; - - lws_sa46_write_numeric_address(&wsi->sa46_peer, buf, len - 8); - lws_snprintf(buf + strlen(buf), 8, ":%u", wsi->c_port); - - return 0; -} static void __lws_tls_session_destroy(lws_tls_scm_t *ts) @@ -93,7 +73,7 @@ __lws_tls_session_lookup_by_name(struct lws_vhost *vh, const char *name) void lws_tls_reuse_session(struct lws *wsi) { - char buf[16 + 48 + 1 + 8 + 1]; + char buf[LWS_SESSION_TAG_LEN]; mbedtls_ssl_context *msc; lws_tls_scm_t *ts; @@ -104,7 +84,9 @@ lws_tls_reuse_session(struct lws *wsi) lws_context_lock(wsi->a.context, __func__); /* -------------- cx { */ lws_vhost_lock(wsi->a.vhost); /* -------------- vh { */ - lws_tls_session_name_from_wsi(wsi, buf, sizeof(buf)); + if (lws_tls_session_tag_from_wsi(wsi, buf, sizeof(buf))) + goto bail; + ts = __lws_tls_session_lookup_by_name(wsi->a.vhost, buf); if (!ts) { @@ -166,7 +148,7 @@ lws_tls_session_expiry_cb(lws_sorted_usec_list_t *sul) int lws_tls_session_new_mbedtls(struct lws *wsi) { - char buf[16 + 48 + 1 + 8 + 1]; + char buf[LWS_SESSION_TAG_LEN]; mbedtls_ssl_context *msc; struct lws_vhost *vh; lws_tls_scm_t *ts; @@ -179,7 +161,9 @@ lws_tls_session_new_mbedtls(struct lws *wsi) if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) return 0; - lws_tls_session_name_from_wsi(wsi, buf, sizeof(buf)); + if (lws_tls_session_tag_from_wsi(wsi, buf, sizeof(buf))) + return 0; + nl = strlen(buf); msc = SSL_mbedtls_ssl_context_from_SSL(wsi->tls.ssl); @@ -275,3 +259,23 @@ lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl) /* Default to 1hr max recommendation from RFC5246 F.1.4 */ vh->tls.tls_session_cache_ttl = !ttl ? 3600 : ttl; } + +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) +{ + /* there seems no serialization / deserialization helper in mbedtls */ + lwsl_warn("%s: only supported on openssl atm\n", __func__); + + return 1; +} + +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) +{ + /* there seems no serialization / deserialization helper in mbedtls */ + lwsl_warn("%s: only supported on openssl atm\n", __func__); + + return 1; +} diff --git a/lib/tls/openssl/openssl-session.c b/lib/tls/openssl/openssl-session.c index 7d250b0bd..985ae98c0 100644 --- a/lib/tls/openssl/openssl-session.c +++ b/lib/tls/openssl/openssl-session.c @@ -35,28 +35,6 @@ typedef struct lws_tls_session_cache_openssl { #define lwsl_tlssess lwsl_info -static int -lws_tls_session_name_from_wsi(struct lws *wsi, char *buf, size_t len) -{ - size_t n; - - /* - * We have to include the vhost name in the session tag, since - * different vhosts may make connections to the same endpoint using - * different client certs. - */ - - n = (size_t)lws_snprintf(buf, len, "%s.", wsi->a.vhost->name); - - buf += n; - len = len - n; - - lws_sa46_write_numeric_address(&wsi->sa46_peer, buf, len - 8); - lws_snprintf(buf + strlen(buf), 8, ":%u", wsi->c_port); - - return 0; -} - static void __lws_tls_session_destroy(lws_tls_sco_t *ts) { @@ -92,7 +70,7 @@ __lws_tls_session_lookup_by_name(struct lws_vhost *vh, const char *name) void lws_tls_reuse_session(struct lws *wsi) { - char buf[16 + INET6_ADDRSTRLEN + 1 + 8 + 1]; + char tag[LWS_SESSION_TAG_LEN]; lws_tls_sco_t *ts; if (!wsi->a.vhost || @@ -102,11 +80,12 @@ lws_tls_reuse_session(struct lws *wsi) lws_context_lock(wsi->a.context, __func__); /* -------------- cx { */ lws_vhost_lock(wsi->a.vhost); /* -------------- vh { */ - lws_tls_session_name_from_wsi(wsi, buf, sizeof(buf)); - ts = __lws_tls_session_lookup_by_name(wsi->a.vhost, buf); + if (lws_tls_session_tag_from_wsi(wsi, tag, sizeof(tag))) + goto bail; + ts = __lws_tls_session_lookup_by_name(wsi->a.vhost, tag); if (!ts) { - lwsl_tlssess("%s: no existing session for %s\n", __func__, buf); + lwsl_tlssess("%s: no existing session for %s\n", __func__, tag); goto bail; } @@ -142,15 +121,52 @@ lws_tls_session_vh_destroy(struct lws_vhost *vh) lws_tls_session_destroy_dll); } +static lws_tls_sco_t * +lws_tls_session_add_entry(struct lws_vhost *vh, const char *tag) +{ + lws_tls_sco_t *ts; + size_t nl = strlen(tag); + + if (vh->tls_sessions.count == (vh->tls_session_cache_max ? + vh->tls_session_cache_max : 10)) { + + /* + * We have reached the vhost's session cache limit, + * prune the LRU / head + */ + ts = lws_container_of(vh->tls_sessions.head, + lws_tls_sco_t, list); + + if (ts) { /* centos 7 ... */ + lwsl_tlssess("%s: pruning oldest session\n", __func__); + + lws_vhost_lock(vh); /* -------------- vh { */ + __lws_tls_session_destroy(ts); + lws_vhost_unlock(vh); /* } vh -------------- */ + } + } + + ts = lws_malloc(sizeof(*ts) + nl + 1, __func__); + + if (!ts) + return NULL; + + memset(ts, 0, sizeof(*ts)); + memcpy(&ts[1], tag, nl + 1); + + lws_dll2_add_tail(&ts->list, &vh->tls_sessions); + + return ts; +} + static int lws_tls_session_new_cb(SSL *ssl, SSL_SESSION *sess) { struct lws *wsi = (struct lws *)SSL_get_ex_data(ssl, openssl_websocket_private_data_index); - char buf[16 + INET6_ADDRSTRLEN + 1 + 8 + 1]; + char tag[LWS_SESSION_TAG_LEN]; struct lws_vhost *vh; lws_tls_sco_t *ts; - size_t nl; #if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG) const char *disposition = "reuse"; long ttl; @@ -163,12 +179,12 @@ lws_tls_session_new_cb(SSL *ssl, SSL_SESSION *sess) } vh = wsi->a.vhost; - lws_tls_session_name_from_wsi(wsi, buf, sizeof(buf)); - nl = strlen(buf); - if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) return 0; + if (lws_tls_session_tag_from_wsi(wsi, tag, sizeof(tag))) + return 0; + #if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG) /* api return is long, although we only support setting * default (300s) or max uint32_t */ @@ -178,37 +194,14 @@ lws_tls_session_new_cb(SSL *ssl, SSL_SESSION *sess) lws_context_lock(vh->context, __func__); /* -------------- cx { */ lws_vhost_lock(vh); /* -------------- vh { */ - ts = __lws_tls_session_lookup_by_name(vh, buf); + ts = __lws_tls_session_lookup_by_name(vh, tag); if (!ts) { - /* - * We have to make our own, new session - */ - - if (vh->tls_sessions.count == vh->tls_session_cache_max) { - - /* - * We have reached the vhost's session cache limit, - * prune the LRU / head - */ - ts = lws_container_of(vh->tls_sessions.head, - lws_tls_sco_t, list); - - lwsl_tlssess("%s: pruning oldest session\n", __func__); - - __lws_tls_session_destroy(ts); - } - - ts = lws_malloc(sizeof(*ts) + nl + 1, __func__); + ts = lws_tls_session_add_entry(vh, tag); if (!ts) goto bail; - memset(ts, 0, sizeof(*ts)); - memcpy(&ts[1], buf, nl + 1); - - lws_dll2_add_tail(&ts->list, &vh->tls_sessions); - #if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG) disposition = "new"; #endif @@ -237,7 +230,7 @@ lws_tls_session_new_cb(SSL *ssl, SSL_SESSION *sess) lws_context_unlock(vh->context); /* } cx -------------- */ lwsl_tlssess("%s: %p: %s: %s %s, ttl %lds (%s:%u)\n", __func__, - sess, wsi->lc.gutag, disposition, buf, ttl, vh->name, + sess, wsi->lc.gutag, disposition, tag, ttl, vh->name, vh->tls_sessions.count); /* @@ -278,3 +271,134 @@ lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl) SSL_CTX_set_timeout(vh->tls.ssl_client_ctx, (long)ttl); #endif } + +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) +{ + struct lws_tls_session_dump d; + lws_tls_sco_t *ts; + int ret = 1, bl; + void *v; + + if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) + return 1; + + lws_tls_session_tag_discrete(vh->name, host, port, d.tag, sizeof(d.tag)); + + lws_context_lock(vh->context, __func__); /* -------------- cx { */ + lws_vhost_lock(vh); /* -------------- vh { */ + + ts = __lws_tls_session_lookup_by_name(vh, d.tag); + if (!ts) + goto bail; + + /* We have a ref on the session, exit via bail to clean it... */ + + bl = i2d_SSL_SESSION(ts->session, NULL); + if (!bl) + goto bail; + + d.blob_len = (size_t)bl; + v = d.blob = lws_malloc(d.blob_len, __func__); + + if (d.blob) { + + /* this advances d.blob by the blob size ;-) */ + i2d_SSL_SESSION(ts->session, (uint8_t **)&d.blob); + + d.opaque = opq; + d.blob = v; + if (cb_save(vh->context, &d)) + lwsl_notice("%s: save failed\n", __func__); + else + ret = 0; + + lws_free(v); + } + +bail: + lws_vhost_unlock(vh); /* } vh -------------- */ + lws_context_unlock(vh->context); /* } cx -------------- */ + + return ret; +} + +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) +{ + struct lws_tls_session_dump d; + lws_tls_sco_t *ts; + SSL_SESSION *sess; + void *v; + + if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) + return 1; + + d.opaque = opq; + lws_tls_session_tag_discrete(vh->name, host, port, d.tag, sizeof(d.tag)); + + lws_context_lock(vh->context, __func__); /* -------------- cx { */ + lws_vhost_lock(vh); /* -------------- vh { */ + + ts = __lws_tls_session_lookup_by_name(vh, d.tag); + + if (ts) { + /* + * Since we are getting this out of cold storage, we should + * not replace any existing session since it is likely newer + */ + lwsl_notice("%s: session already exists for %s\n", __func__, + d.tag); + goto bail1; + } + + if (cb_load(vh->context, &d)) { + lwsl_warn("%s: load failed\n", __func__); + + goto bail1; + } + + /* the callback has allocated the blob and set d.blob / d.blob_len */ + + v = d.blob; + /* this advances d.blob by the blob size ;-) */ + sess = d2i_SSL_SESSION(NULL, (const uint8_t **)&d.blob, + (long)d.blob_len); + free(v); /* user code will have used malloc() */ + if (!sess) { + lwsl_warn("%s: d2i_SSL_SESSION failed\n", __func__); + goto bail; + } + + lws_vhost_lock(vh); /* -------------- vh { */ + ts = lws_tls_session_add_entry(vh, d.tag); + lws_vhost_unlock(vh); /* } vh -------------- */ + + if (!ts) { + lwsl_warn("%s: unable to add cache entry\n", __func__); + goto bail; + } + +#if defined(LWS_HAVE_SSL_SESSION_up_ref) + SSL_SESSION_up_ref(sess); +#else + lwsl_err("%s: openssl is too old\n", __func__); +#endif + lwsl_tlssess("%s: session loaded OK\n", __func__); + + lws_vhost_unlock(vh); /* } vh -------------- */ + lws_context_unlock(vh->context); /* } cx -------------- */ + + return 0; + +bail: + SSL_SESSION_free(sess); +bail1: + + lws_vhost_unlock(vh); /* } vh -------------- */ + lws_context_unlock(vh->context); /* } cx -------------- */ + + return 1; +} diff --git a/lib/tls/private-lib-tls.h b/lib/tls/private-lib-tls.h index c95be4b86..3244c4aed 100644 --- a/lib/tls/private-lib-tls.h +++ b/lib/tls/private-lib-tls.h @@ -116,7 +116,6 @@ enum lws_tls_extant { LWS_TLS_EXTANT_ALTERNATIVE }; - #if defined(LWS_WITH_TLS) int @@ -196,6 +195,38 @@ lws_tls_reuse_session(struct lws *wsi); void lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl); +int +lws_tls_session_name_from_wsi(struct lws *wsi, char *buf, size_t len); + +/** + * lws_tls_session_name_discrete() - form an lws session tag name from pieces + * + * \param vhname: name of the vhost + * \param host: name of the host we are connecting to, like warmcat.com + * \param port: the port we connected to + * \param buf: the destination buffer for the tag + * \param len: the max available size of the destination buffer + * + * Creates a tag string representing a specific host, for use with serializing + * sessions made with the host. + */ +void +lws_tls_session_tag_discrete(const char *vhname, const char *host, + uint16_t port, char *buf, size_t len); + +/** + * lws_tls_session_name_from_wsi() - form an lws session tag name from a client wsi + * + * \param wsi: the wsi whose vhost, host and port we should use for the tag + * \param buf: the destination buffer for the tag + * \param len: the max available size of the destination buffer + * + * Creates a tag string representing a specific host, for use with serializing + * sessions made with the host. + */ +int +lws_tls_session_tag_from_wsi(struct lws *wsi, char *buf, size_t len); + #else /* ! WITH_TLS */ #define lws_tls_restrict_borrow(xxx) (0) diff --git a/lib/tls/tls-sessions.c b/lib/tls/tls-sessions.c new file mode 100644 index 000000000..706e2da74 --- /dev/null +++ b/lib/tls/tls-sessions.c @@ -0,0 +1,66 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2021 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "private-lib-core.h" + +void +lws_tls_session_tag_discrete(const char *vhname, const char *host, + uint16_t port, char *buf, size_t len) +{ + /* + * We have to include the vhost name in the session tag, since + * different vhosts may make connections to the same endpoint using + * different client certs. + */ + + lws_snprintf(buf, len, "%s_%s_%u", vhname, host, port); +} + +int +lws_tls_session_tag_from_wsi(struct lws *wsi, char *buf, size_t len) +{ + const char *host; + + if (!wsi) + return 1; + + if (!wsi->stash) { + lwsl_warn("%s: wsi has no stash\n", __func__); + return 1; + } + + host = wsi->stash->cis[CIS_HOST]; + if (!host) + host = wsi->stash->cis[CIS_ADDRESS]; + + if (!host) + return 1; + + lws_tls_session_tag_discrete(wsi->a.vhost->name, host, wsi->c_port, + buf, len); + + return 0; +} + + diff --git a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c index f0029b853..f2a247fb7 100644 --- a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c +++ b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c @@ -39,6 +39,11 @@ #include #include #include +#if !defined(WIN32) +#include +#include +#include +#endif #define COUNT 8 @@ -46,7 +51,8 @@ struct cliuser { int index; }; -static int completed, failed, numbered, stagger_idx, posting, count = COUNT, reuse; +static int completed, failed, numbered, stagger_idx, posting, count = COUNT, + reuse; static lws_sorted_usec_list_t sul_stagger; static struct lws_client_connect_info i; static struct lws *client_wsi[COUNT]; @@ -59,6 +65,100 @@ struct pss { char body_part; }; +#if defined(LWS_WITH_TLS_SESSIONS) && !defined(LWS_WITH_MBEDTLS) && !defined(WIN32) + +/* this should work OK on win32, but not adapted for non-posix file apis */ + +static int +sess_save_cb(struct lws_context *cx, struct lws_tls_session_dump *info) +{ + char path[128]; + int fd, n; + + lws_snprintf(path, sizeof(path), "%s/lws_tls_sess_%s", (const char *)info->opaque, + info->tag); + fd = open(path, LWS_O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) { + lwsl_warn("%s: cannot open %s\n", __func__, path); + return 1; + } + + n = (int)write(fd, info->blob, info->blob_len); + + close(fd); + + return n != (int)info->blob_len; +} + +static int +sess_load_cb(struct lws_context *cx, struct lws_tls_session_dump *info) +{ + struct stat sta; + char path[128]; + int fd, n; + + lws_snprintf(path, sizeof(path), "%s/lws_tls_sess_%s", (const char *)info->opaque, + info->tag); + fd = open(path, LWS_O_RDONLY); + if (fd < 0) + return 1; + + if (fstat(fd, &sta) || !sta.st_size) + goto bail; + + info->blob = malloc((size_t)sta.st_size); + /* caller will free this */ + if (!info->blob) + goto bail; + + info->blob_len = (size_t)sta.st_size; + + n = (int)read(fd, info->blob, info->blob_len); + close(fd); + + return n != (int)info->blob_len; + +bail: + close(fd); + + return 1; +} +#endif + +#if defined(LWS_WITH_CONMON) +void +dump_conmon_data(struct lws *wsi) +{ + const struct addrinfo *ai; + struct lws_conmon cm; + char ads[48]; + + lws_conmon_wsi_take(wsi, &cm); + + lws_sa46_write_numeric_address(&cm.peer46, ads, sizeof(ads)); + lwsl_notice("%s: peer %s, dns: %uus, sockconn: %uus, tls: %uus, txn_resp: %uus\n", + __func__, ads, + (unsigned int)cm.ciu_dns, + (unsigned int)cm.ciu_sockconn, + (unsigned int)cm.ciu_tls, + (unsigned int)cm.ciu_txn_resp); + + ai = cm.dns_results_copy; + while (ai) { + lws_sa46_write_numeric_address((lws_sockaddr46 *)ai->ai_addr, ads, sizeof(ads)); + lwsl_notice("%s: DNS %s\n", __func__, ads); + ai = ai->ai_next; + } + + /* + * This destroys the DNS list in the lws_conmon that we took + * responsibility for when we used lws_conmon_wsi_take() + */ + + lws_conmon_release(&cm); +} +#endif + static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) @@ -73,8 +173,20 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: lwsl_user("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: idx: %d, resp %u: tls-session-reuse: %d\n", idx, lws_http_client_http_response(wsi), lws_tls_session_is_reused(wsi)); + if (lws_tls_session_is_reused(wsi)) reuse++; +#if defined(LWS_WITH_TLS_SESSIONS) && !defined(LWS_WITH_MBEDTLS) && !defined(WIN32) + else + /* + * Attempt to store any new session into + * external storage + */ + if (lws_tls_session_dump_save(lws_get_vhost_by_name(context, "default"), + i.host, (uint16_t)i.port, + sess_save_cb, "/tmp")) + lwsl_warn("%s: session save failed\n", __func__); +#endif break; /* because we are protocols[0] ... */ @@ -83,6 +195,11 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, in ? (char *)in : "(null)"); client_wsi[idx] = NULL; failed++; + +#if defined(LWS_WITH_CONMON) + dump_conmon_data(wsi); +#endif + goto finished; /* chunks of chunked content, with header removed */ @@ -92,6 +209,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, return 0; /* don't passthru */ case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: + /* * Tell lws we are going to send the body next... */ @@ -123,6 +241,11 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_CLOSED_CLIENT_HTTP: lwsl_info("%s: closed: %s\n", __func__, lws_wsi_tag(client_wsi[idx])); + +#if defined(LWS_WITH_CONMON) + dump_conmon_data(wsi); +#endif + if (client_wsi[idx]) { /* * If it completed normally, it will have been set to @@ -459,6 +582,11 @@ int main(int argc, const char **argv) #endif } +#if defined(LWS_WITH_CONMON) + if (lws_cmdline_option(argc, argv, "--conmon")) + i.ssl_connection |= LCCSCF_CONMON; +#endif + /* force h1 even if h2 available */ if (lws_cmdline_option(argc, argv, "--h1")) i.alpn = "http/1.1"; @@ -501,6 +629,15 @@ int main(int argc, const char **argv) i.origin = i.address; i.protocol = protocols[0].name; +#if defined(LWS_WITH_TLS_SESSIONS) && !defined(LWS_WITH_MBEDTLS) && !defined(WIN32) + /* + * Attempt to preload a session from external storage + */ + if (lws_tls_session_dump_load(lws_get_vhost_by_name(context, "default"), + i.host, (uint16_t)i.port, sess_load_cb, "/tmp")) + lwsl_warn("%s: session load failed\n", __func__); +#endif + if (!staggered) /* * just pile on all the connections at once, testing the