diff --git a/CMakeLists-implied-options.txt b/CMakeLists-implied-options.txt index 0a5a5d252..e90d2f443 100644 --- a/CMakeLists-implied-options.txt +++ b/CMakeLists-implied-options.txt @@ -396,8 +396,8 @@ if (LWS_WITH_PLUGINS AND NOT LWS_WITH_SHARED) endif() if (LWS_WITH_TLS_SESSIONS) - if (NOT LWS_WITH_NETWORK OR NOT LWS_WITH_CLIENT OR LWS_WITH_MBEDTLS) - message("TLS_SESSIONS support only covers client on openssl atm, disabling") + if (NOT LWS_WITH_NETWORK OR NOT LWS_WITH_CLIENT) + message("TLS_SESSIONS support requires client, disabling") set(LWS_WITH_TLS_SESSIONS OFF) endif() endif() diff --git a/lib/core/context.c b/lib/core/context.c index 3eb30b3a3..3ca945f56 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -320,6 +320,9 @@ static const char * const opts_str = #if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) "SSPROX " #endif +#if defined(LWS_WITH_MBEDTLS) + "MbedTLS " +#endif #if defined(LWS_WITH_SYS_ASYNC_DNS) "ASYNC_DNS " #endif diff --git a/lib/tls/CMakeLists.txt b/lib/tls/CMakeLists.txt index 9302cbe42..58b1c3d99 100644 --- a/lib/tls/CMakeLists.txt +++ b/lib/tls/CMakeLists.txt @@ -122,6 +122,10 @@ if (LWS_WITH_SSL) list(APPEND SOURCES tls/mbedtls/mbedtls-ssl.c) endif() + if (LWS_WITH_TLS_SESSIONS) + list(APPEND SOURCES + tls/mbedtls/mbedtls-session.c) + endif() if (LWS_WITH_GENCRYPTO) list(APPEND SOURCES tls/mbedtls/lws-genhash.c diff --git a/lib/tls/mbedtls/mbedtls-client.c b/lib/tls/mbedtls/mbedtls-client.c index cd9a585b8..8f315f75d 100644 --- a/lib/tls/mbedtls/mbedtls-client.c +++ b/lib/tls/mbedtls/mbedtls-client.c @@ -23,6 +23,7 @@ */ #include "private-lib-core.h" +#include "private-lib-tls-mbedtls.h" static int OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) @@ -66,6 +67,11 @@ lws_ssl_client_bio_create(struct lws *wsi) return -1; } +#if defined(LWS_WITH_TLS_SESSIONS) + if (!(wsi->a.vhost->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)) + lws_tls_reuse_session(wsi); +#endif + if (wsi->a.vhost->tls.ssl_info_event_mask) SSL_set_info_callback(wsi->tls.ssl, lws_ssl_info_callback); @@ -195,6 +201,9 @@ lws_tls_client_connect(struct lws *wsi, char *errbuf, size_t elen) if (n == 1) { SSL_get0_alpn_selected(wsi->tls.ssl, &prot, &len); lws_role_call_alpn_negotiated(wsi, (const char *)prot); +#if defined(LWS_WITH_TLS_SESSIONS) + lws_tls_session_new_mbedtls(wsi); +#endif lwsl_info("client connect OK\n"); return LWS_SSL_CAPABLE_DONE; } @@ -317,6 +326,12 @@ lws_tls_client_create_vhost_context(struct lws_vhost *vh, unsigned long error; int n; +#if defined(LWS_WITH_TLS_SESSIONS) + vh->tls_session_cache_max = info->tls_session_cache_max ? + info->tls_session_cache_max : 10; + lws_tls_session_cache(vh, info->tls_session_timeout); +#endif + if (!method) { error = (unsigned long)ERR_get_error(); lwsl_err("problem creating ssl method %lu: %s\n", diff --git a/lib/tls/mbedtls/mbedtls-session.c b/lib/tls/mbedtls/mbedtls-session.c new file mode 100644 index 000000000..d0c70bfae --- /dev/null +++ b/lib/tls/mbedtls/mbedtls-session.c @@ -0,0 +1,277 @@ +/* + * 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" + +typedef struct lws_tls_session_cache_mbedtls { + lws_dll2_t list; + + mbedtls_ssl_session session; + lws_sorted_usec_list_t sul_ttl; + + /* name is overallocated here */ +} lws_tls_scm_t; + +#define lwsl_tlssess lwsl_notice + +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) +{ + lwsl_tlssess("%s: %s (%u)\n", __func__, (const char *)&ts[1], + (unsigned int)(ts->list.owner->count - 1)); + + lws_sul_cancel(&ts->sul_ttl); + mbedtls_ssl_session_free(&ts->session); + lws_dll2_remove(&ts->list); /* vh lock */ + + lws_free(ts); +} + +static lws_tls_scm_t * +__lws_tls_session_lookup_by_name(struct lws_vhost *vh, const char *name) +{ + lws_start_foreach_dll(struct lws_dll2 *, p, + lws_dll2_get_head(&vh->tls_sessions)) { + lws_tls_scm_t *ts = lws_container_of(p, lws_tls_scm_t, list); + const char *ts_name = (const char *)&ts[1]; + + if (!strcmp(name, ts_name)) + return ts; + + } lws_end_foreach_dll(p); + + return NULL; +} + +/* + * If possible, reuse an existing, cached session + */ + +void +lws_tls_reuse_session(struct lws *wsi) +{ + char buf[16 + 48 + 1 + 8 + 1]; + mbedtls_ssl_context *msc; + lws_tls_scm_t *ts; + + if (!wsi->a.vhost || + wsi->a.vhost->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) + return; + + 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 (!ts) { + lwsl_tlssess("%s: no existing session for %s\n", __func__, buf); + goto bail; + } + + lwsl_tlssess("%s: %s\n", __func__, (const char *)&ts[1]); + wsi->tls_session_reused = 1; + + msc = SSL_mbedtls_ssl_context_from_SSL(wsi->tls.ssl); + mbedtls_ssl_set_session(msc, &ts->session); + + /* keep our session list sorted in lru -> mru order */ + + lws_dll2_remove(&ts->list); + lws_dll2_add_tail(&ts->list, &wsi->a.vhost->tls_sessions); + +bail: + lws_vhost_unlock(wsi->a.vhost); /* } vh -------------- */ + lws_context_unlock(wsi->a.context); /* } cx -------------- */ +} + +static int +lws_tls_session_destroy_dll(struct lws_dll2 *d, void *user) +{ + lws_tls_scm_t *ts = lws_container_of(d, lws_tls_scm_t, list); + + __lws_tls_session_destroy(ts); + + return 0; +} + +void +lws_tls_session_vh_destroy(struct lws_vhost *vh) +{ + lws_dll2_foreach_safe(&vh->tls_sessions, NULL, + lws_tls_session_destroy_dll); +} + +static void +lws_tls_session_expiry_cb(lws_sorted_usec_list_t *sul) +{ + lws_tls_scm_t *ts = lws_container_of(sul, lws_tls_scm_t, sul_ttl); + struct lws_vhost *vh = lws_container_of(ts->list.owner, + struct lws_vhost, tls_sessions); + + lws_context_lock(vh->context, __func__); /* -------------- cx { */ + lws_vhost_lock(vh); /* -------------- vh { */ + __lws_tls_session_destroy(ts); + lws_vhost_unlock(vh); /* } vh -------------- */ + lws_context_unlock(vh->context); /* } cx -------------- */ +} + +/* + * Called after SSL_accept on the wsi + */ + +int +lws_tls_session_new_mbedtls(struct lws *wsi) +{ + char buf[16 + 48 + 1 + 8 + 1]; + mbedtls_ssl_context *msc; + struct lws_vhost *vh; + lws_tls_scm_t *ts; + size_t nl; +#if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG) + const char *disposition = "reuse"; +#endif + + vh = wsi->a.vhost; + if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) + return 0; + + lws_tls_session_name_from_wsi(wsi, buf, sizeof(buf)); + nl = strlen(buf); + + msc = SSL_mbedtls_ssl_context_from_SSL(wsi->tls.ssl); + + lws_context_lock(vh->context, __func__); /* -------------- cx { */ + lws_vhost_lock(vh); /* -------------- vh { */ + + ts = __lws_tls_session_lookup_by_name(vh, buf); + + 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_scm_t, list); + + lwsl_tlssess("%s: pruning oldest session (hit max %u)\n", + __func__, + (unsigned int)vh->tls_session_cache_max); + + 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) + goto bail; + + memset(ts, 0, sizeof(*ts)); + memcpy(&ts[1], buf, nl + 1); + + if (mbedtls_ssl_get_session(msc, &ts->session)) + /* no joy for whatever reason */ + goto bail; + + lws_dll2_add_tail(&ts->list, &vh->tls_sessions); + + lws_sul_schedule(wsi->a.context, wsi->tsi, &ts->sul_ttl, + lws_tls_session_expiry_cb, + (int64_t)vh->tls.tls_session_cache_ttl * + LWS_US_PER_SEC); + +#if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG) + disposition = "new"; +#endif + } else { + + mbedtls_ssl_session_free(&ts->session); + + if (mbedtls_ssl_get_session(msc, &ts->session)) + /* no joy for whatever reason */ + goto bail; + + /* keep our session list sorted in lru -> mru order */ + + lws_dll2_remove(&ts->list); + lws_dll2_add_tail(&ts->list, &vh->tls_sessions); + } + + lws_vhost_unlock(vh); /* } vh -------------- */ + lws_context_unlock(vh->context); /* } cx -------------- */ + + lwsl_tlssess("%s: %s: %s %s, (%s:%u)\n", __func__, + wsi->lc.gutag, disposition, buf, vh->name, + (unsigned int)vh->tls_sessions.count); + + /* + * indicate we will hold on to the SSL_SESSION reference, and take + * responsibility to call SSL_SESSION_free() on it ourselves + */ + + return 1; + +bail: + lws_vhost_unlock(vh); /* } vh -------------- */ + lws_context_unlock(vh->context); /* } cx -------------- */ + + return 0; +} + +void +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; +} diff --git a/lib/tls/mbedtls/private-lib-tls-mbedtls.h b/lib/tls/mbedtls/private-lib-tls-mbedtls.h index b6b4e6bc4..56ceee00e 100644 --- a/lib/tls/mbedtls/private-lib-tls-mbedtls.h +++ b/lib/tls/mbedtls/private-lib-tls-mbedtls.h @@ -36,3 +36,6 @@ lws_gencrypto_mbedtls_hash_to_MD_TYPE(enum lws_genhash_types hash_type); int lws_gencrypto_mbedtls_rngf(void *context, unsigned char *buf, size_t len); + +int +lws_tls_session_new_mbedtls(struct lws *wsi); diff --git a/lib/tls/mbedtls/wrapper/include/openssl/ssl.h b/lib/tls/mbedtls/wrapper/include/openssl/ssl.h index f01e8c355..e6baba5f3 100755 --- a/lib/tls/mbedtls/wrapper/include/openssl/ssl.h +++ b/lib/tls/mbedtls/wrapper/include/openssl/ssl.h @@ -51,6 +51,8 @@ SSL *SSL_SSL_from_mbedtls_ssl_context(mbedtls_ssl_context *msc); + mbedtls_ssl_context *SSL_mbedtls_ssl_context_from_SSL(SSL *ssl); + /** * @brief create a SSL context * diff --git a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c index de160a880..c73ba084a 100755 --- a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c +++ b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c @@ -885,6 +885,13 @@ SSL *SSL_SSL_from_mbedtls_ssl_context(mbedtls_ssl_context *msc) return ssl_pm->owner; } +mbedtls_ssl_context *SSL_mbedtls_ssl_context_from_SSL(SSL *ssl) +{ + struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm; + + return &ssl_pm->ssl; +} + #include "ssl_cert.h" void SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx) diff --git a/lib/tls/openssl/private-lib-tls-openssl.h b/lib/tls/openssl/private-lib-tls-openssl.h index ea745b28a..004d596ae 100644 --- a/lib/tls/openssl/private-lib-tls-openssl.h +++ b/lib/tls/openssl/private-lib-tls-openssl.h @@ -58,11 +58,5 @@ lws_gencrypto_openssl_hash_to_EVP_MD(enum lws_genhash_types hash_type); int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen); #endif -void -lws_tls_reuse_session(struct lws *wsi); - -void -lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl); - #endif diff --git a/lib/tls/private-lib-tls.h b/lib/tls/private-lib-tls.h index ea7e06abc..c95be4b86 100644 --- a/lib/tls/private-lib-tls.h +++ b/lib/tls/private-lib-tls.h @@ -190,6 +190,11 @@ int lws_genec_confirm_curve_allowed_by_tls_id(const char *allowed, int id, struct lws_jwk *jwk); +void +lws_tls_reuse_session(struct lws *wsi); + +void +lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl); #else /* ! WITH_TLS */ diff --git a/lib/tls/private-network.h b/lib/tls/private-network.h index 8c6cdf36f..26ff5b400 100644 --- a/lib/tls/private-network.h +++ b/lib/tls/private-network.h @@ -66,6 +66,10 @@ struct lws_vhost_tls { int allow_non_ssl_on_ssl_port; int ssl_info_event_mask; +#if defined(LWS_WITH_MBEDTLS) + uint32_t tls_session_cache_ttl; +#endif + unsigned int user_supplied_ssl_ctx:1; unsigned int skipped_certs:1; };