Update Files
This commit is contained in:
491
Kinc/Sources/kinc/libs/tls/openssl/openssl-session.c
Normal file
491
Kinc/Sources/kinc/libs/tls/openssl/openssl-session.c
Normal file
@ -0,0 +1,491 @@
|
||||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010 - 2022 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"
|
||||
|
||||
typedef struct lws_tls_session_cache_openssl {
|
||||
lws_dll2_t list;
|
||||
|
||||
SSL_SESSION *session;
|
||||
lws_sorted_usec_list_t sul_ttl;
|
||||
|
||||
/* name is overallocated here */
|
||||
} lws_tls_sco_t;
|
||||
|
||||
#define tlssess_loglevel LLL_INFO
|
||||
#if (_LWS_ENABLED_LOGS & tlssess_loglevel)
|
||||
#define lwsl_tlssess(...) _lws_log(tlssess_loglevel, __VA_ARGS__)
|
||||
#else
|
||||
#define lwsl_tlssess(...)
|
||||
#endif
|
||||
|
||||
static void
|
||||
__lws_tls_session_destroy(lws_tls_sco_t *ts)
|
||||
{
|
||||
lwsl_tlssess("%s: %s (%u)\n", __func__, (const char *)&ts[1],
|
||||
ts->list.owner->count - 1);
|
||||
|
||||
lws_sul_cancel(&ts->sul_ttl);
|
||||
SSL_SESSION_free(ts->session);
|
||||
lws_dll2_remove(&ts->list); /* vh lock */
|
||||
|
||||
lws_free(ts);
|
||||
}
|
||||
|
||||
static lws_tls_sco_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_sco_t *ts = lws_container_of(p, lws_tls_sco_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 tag[LWS_SESSION_TAG_LEN];
|
||||
lws_tls_sco_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 { */
|
||||
|
||||
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__, tag);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
lwsl_tlssess("%s: %s\n", __func__, (const char *)&ts[1]);
|
||||
|
||||
if (!SSL_set_session(wsi->tls.ssl, ts->session)) {
|
||||
lwsl_err("%s: session not set for %s\n", __func__, tag);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
#if !defined(USE_WOLFSSL)
|
||||
/* extend session lifetime */
|
||||
SSL_SESSION_set_time(ts->session,
|
||||
#if defined(OPENSSL_IS_BORINGSSL)
|
||||
(unsigned long)
|
||||
#else
|
||||
(long)
|
||||
#endif
|
||||
time(NULL));
|
||||
#endif
|
||||
|
||||
/* 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 -------------- */
|
||||
}
|
||||
|
||||
int
|
||||
lws_tls_session_is_reused(struct lws *wsi)
|
||||
{
|
||||
#if defined(LWS_WITH_CLIENT)
|
||||
struct lws *nwsi = lws_get_network_wsi(wsi);
|
||||
|
||||
if (!nwsi || !nwsi->tls.ssl)
|
||||
return 0;
|
||||
|
||||
return (int)SSL_session_reused(nwsi->tls.ssl);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
lws_tls_session_destroy_dll(struct lws_dll2 *d, void *user)
|
||||
{
|
||||
lws_tls_sco_t *ts = lws_container_of(d, lws_tls_sco_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_sco_t *ts = lws_container_of(sul, lws_tls_sco_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 -------------- */
|
||||
}
|
||||
|
||||
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 tag[LWS_SESSION_TAG_LEN];
|
||||
struct lws_vhost *vh;
|
||||
lws_tls_sco_t *ts;
|
||||
long ttl;
|
||||
#if (_LWS_ENABLED_LOGS & tlssess_loglevel)
|
||||
const char *disposition = "reuse";
|
||||
#endif
|
||||
|
||||
if (!wsi) {
|
||||
lwsl_warn("%s: can't get wsi from ssl privdata\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
vh = wsi->a.vhost;
|
||||
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;
|
||||
|
||||
/* api return is long, although we only support setting
|
||||
* default (300s) or max uint32_t */
|
||||
ttl = SSL_SESSION_get_timeout(sess);
|
||||
|
||||
lws_context_lock(vh->context, __func__); /* -------------- cx { */
|
||||
lws_vhost_lock(vh); /* -------------- vh { */
|
||||
|
||||
ts = __lws_tls_session_lookup_by_name(vh, tag);
|
||||
|
||||
if (!ts) {
|
||||
ts = lws_tls_session_add_entry(vh, tag);
|
||||
|
||||
if (!ts)
|
||||
goto bail;
|
||||
|
||||
lws_sul_schedule(wsi->a.context, wsi->tsi, &ts->sul_ttl,
|
||||
lws_tls_session_expiry_cb,
|
||||
ttl * LWS_US_PER_SEC);
|
||||
|
||||
#if (_LWS_ENABLED_LOGS & tlssess_loglevel)
|
||||
disposition = "new";
|
||||
#endif
|
||||
|
||||
/*
|
||||
* We don't have to do a SSL_SESSION_up_ref() here, because
|
||||
* we will return from this callback indicating that we kept the
|
||||
* ref
|
||||
*/
|
||||
} else {
|
||||
/*
|
||||
* Give up our refcount on the session we are about to replace
|
||||
* with a newer one
|
||||
*/
|
||||
SSL_SESSION_free(ts->session);
|
||||
|
||||
/* keep our session list sorted in lru -> mru order */
|
||||
|
||||
lws_dll2_remove(&ts->list);
|
||||
lws_dll2_add_tail(&ts->list, &vh->tls_sessions);
|
||||
}
|
||||
|
||||
ts->session = sess;
|
||||
|
||||
lws_vhost_unlock(vh); /* } vh -------------- */
|
||||
lws_context_unlock(vh->context); /* } cx -------------- */
|
||||
|
||||
lwsl_tlssess("%s: %p: %s: %s %s, ttl %lds (%s:%u)\n", __func__,
|
||||
sess, wsi->lc.gutag, disposition, tag, ttl, vh->name,
|
||||
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;
|
||||
}
|
||||
|
||||
#if defined(LWS_TLS_SYNTHESIZE_CB)
|
||||
|
||||
/*
|
||||
* On openssl, there is an async cb coming when the server issues the session
|
||||
* information on the link, so we can pick it up and update the cache at the
|
||||
* right time.
|
||||
*
|
||||
* On mbedtls and some version at least of borning ssl, this cb is either not
|
||||
* part of the tls library apis or fails to arrive.
|
||||
*
|
||||
* This synthetic cb is called instead for those build cases, scheduled for
|
||||
* +500ms after the tls negotiation completed.
|
||||
*/
|
||||
|
||||
void
|
||||
lws_sess_cache_synth_cb(lws_sorted_usec_list_t *sul)
|
||||
{
|
||||
struct lws_lws_tls *tls = lws_container_of(sul, struct lws_lws_tls,
|
||||
sul_cb_synth);
|
||||
struct lws *wsi = lws_container_of(tls, struct lws, tls);
|
||||
SSL_SESSION *sess;
|
||||
|
||||
if (lws_tls_session_is_reused(wsi))
|
||||
return;
|
||||
|
||||
sess = SSL_get1_session(tls->ssl);
|
||||
if (!sess)
|
||||
return;
|
||||
|
||||
if (!SSL_SESSION_is_resumable(sess) || /* not worth caching, or... */
|
||||
!lws_tls_session_new_cb(tls->ssl, sess)) { /* ...cb didn't keep it */
|
||||
/*
|
||||
* For now the policy if no session message after the wait,
|
||||
* is just let it be. Typically the session info is sent
|
||||
* early.
|
||||
*/
|
||||
SSL_SESSION_free(sess);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl)
|
||||
{
|
||||
long cmode;
|
||||
|
||||
if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)
|
||||
return;
|
||||
|
||||
cmode = SSL_CTX_get_session_cache_mode(vh->tls.ssl_client_ctx);
|
||||
|
||||
SSL_CTX_set_session_cache_mode(vh->tls.ssl_client_ctx,
|
||||
(int)(cmode | SSL_SESS_CACHE_CLIENT));
|
||||
|
||||
SSL_CTX_sess_set_new_cb(vh->tls.ssl_client_ctx, lws_tls_session_new_cb);
|
||||
|
||||
if (!ttl)
|
||||
return;
|
||||
|
||||
#if defined(OPENSSL_IS_BORINGSSL)
|
||||
SSL_CTX_set_timeout(vh->tls.ssl_client_ctx, ttl);
|
||||
#else
|
||||
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 = NULL; /* allow it to "bail" early */
|
||||
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;
|
||||
}
|
||||
|
||||
ts->session = sess;
|
||||
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;
|
||||
}
|
Reference in New Issue
Block a user