1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

tls-sessions: serialization

This commit is contained in:
Andy Green 2021-04-01 22:31:13 +01:00
parent e2f1dd1c82
commit 7451702722
11 changed files with 643 additions and 86 deletions

View file

@ -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.

View file

@ -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

View file

@ -629,6 +629,8 @@ struct lws;
#if defined(LWS_WITH_TLS)
#include <libwebsockets/lws-tls-sessions.h>
#if defined(LWS_WITH_MBEDTLS)
#include <mbedtls/md5.h>
#include <mbedtls/sha1.h>

View file

@ -0,0 +1,81 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
*
* 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);
///@}

View file

@ -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)

View file

@ -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)

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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)

66
lib/tls/tls-sessions.c Normal file
View file

@ -0,0 +1,66 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
*
* 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;
}

View file

@ -39,6 +39,11 @@
#include <signal.h>
#include <assert.h>
#include <time.h>
#if !defined(WIN32)
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#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