mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
openssl: reuse client SSL_CTX where possible
If you have multiple vhosts with client contexts enabled, under OpenSSL each one brings in the system cert bundle. On libwebsockets.org, there are many vhosts and the waste adds up to about 9MB of heap. This patch makes a sha256 from the client context configuration, and if a suitable client context already exists on another vhost, bumps a refcount and reuses the client context. In the case client contexts are configured differently, a new one is created (and is available for reuse as well).
This commit is contained in:
parent
58ff44297c
commit
555c34b044
6 changed files with 166 additions and 11 deletions
|
@ -978,7 +978,8 @@ __lws_vhost_destroy2(struct lws_vhost *vh)
|
|||
n = 0;
|
||||
while (n < vh->count_protocols) {
|
||||
wsi.protocol = protocol;
|
||||
protocol->callback(&wsi, LWS_CALLBACK_PROTOCOL_DESTROY,
|
||||
if (protocol->callback)
|
||||
protocol->callback(&wsi, LWS_CALLBACK_PROTOCOL_DESTROY,
|
||||
NULL, NULL, 0);
|
||||
protocol++;
|
||||
n++;
|
||||
|
|
|
@ -109,7 +109,7 @@ lws_vhost_name_to_protocol(struct lws_vhost *vh, const char *name)
|
|||
int n;
|
||||
|
||||
for (n = 0; n < vh->count_protocols; n++)
|
||||
if (!strcmp(name, vh->protocols[n].name))
|
||||
if (vh->protocols[n].name && !strcmp(name, vh->protocols[n].name))
|
||||
return &vh->protocols[n];
|
||||
|
||||
return NULL;
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#include "core/private.h"
|
||||
|
||||
#include "tls/openssl/private.h"
|
||||
|
||||
/*
|
||||
* Care: many openssl apis return 1 for success. These are translated to the
|
||||
* lws convention of 0 for success.
|
||||
|
@ -364,12 +366,17 @@ lws_tls_client_create_vhost_context(struct lws_vhost *vh,
|
|||
unsigned int cert_mem_len,
|
||||
const char *private_key_filepath)
|
||||
{
|
||||
SSL_METHOD *method;
|
||||
unsigned long error;
|
||||
int n;
|
||||
struct lws_tls_client_reuse *tcr;
|
||||
const unsigned char **ca_mem_ptr;
|
||||
X509 *client_CA;
|
||||
X509_STORE *x509_store;
|
||||
unsigned long error;
|
||||
SSL_METHOD *method;
|
||||
EVP_MD_CTX *mdctx;
|
||||
unsigned int len;
|
||||
uint8_t hash[32];
|
||||
X509 *client_CA;
|
||||
char c;
|
||||
int n;
|
||||
|
||||
/* basic openssl init already happened in context init */
|
||||
|
||||
|
@ -389,7 +396,95 @@ lws_tls_client_create_vhost_context(struct lws_vhost *vh,
|
|||
(char *)vh->context->pt[0].serv_buf));
|
||||
return 1;
|
||||
}
|
||||
/* create context */
|
||||
|
||||
/*
|
||||
* OpenSSL client contexts are quite expensive, because they bring in
|
||||
* the system certificate bundle for each one. So if you have multiple
|
||||
* vhosts, each with a client context, it can add up to several
|
||||
* megabytes of heap. In the case the client contexts are configured
|
||||
* identically, they could perfectly well have shared just the one.
|
||||
*
|
||||
* For that reason, use a hash to fingerprint the context configuration
|
||||
* and prefer to reuse an existing one with the same fingerprint if
|
||||
* possible.
|
||||
*/
|
||||
|
||||
mdctx = EVP_MD_CTX_create();
|
||||
if (!mdctx)
|
||||
return 1;
|
||||
|
||||
if (EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) {
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (info->ssl_client_options_set)
|
||||
EVP_DigestUpdate(mdctx, &info->ssl_client_options_set,
|
||||
sizeof(info->ssl_client_options_set));
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x009080df) && !defined(USE_WOLFSSL)
|
||||
if (info->ssl_client_options_clear)
|
||||
EVP_DigestUpdate(mdctx, &info->ssl_client_options_clear,
|
||||
sizeof(info->ssl_client_options_clear));
|
||||
#endif
|
||||
|
||||
if (cipher_list)
|
||||
EVP_DigestUpdate(mdctx, cipher_list, strlen(cipher_list));
|
||||
|
||||
#if defined(LWS_HAVE_SSL_CTX_set_ciphersuites)
|
||||
if (info->client_tls_1_3_plus_cipher_list)
|
||||
EVP_DigestUpdate(mdctx, info->client_tls_1_3_plus_cipher_list,
|
||||
strlen(info->client_tls_1_3_plus_cipher_list));
|
||||
#endif
|
||||
|
||||
if (!lws_check_opt(vh->options, LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS)) {
|
||||
c = 1;
|
||||
EVP_DigestUpdate(mdctx, &c, 1);
|
||||
}
|
||||
|
||||
if (ca_filepath)
|
||||
EVP_DigestUpdate(mdctx, ca_filepath, strlen(ca_filepath));
|
||||
|
||||
if (cert_filepath)
|
||||
EVP_DigestUpdate(mdctx, cert_filepath, strlen(cert_filepath));
|
||||
|
||||
if (private_key_filepath)
|
||||
EVP_DigestUpdate(mdctx, private_key_filepath,
|
||||
strlen(private_key_filepath));
|
||||
if (ca_mem && ca_mem_len)
|
||||
EVP_DigestUpdate(mdctx, ca_mem, ca_mem_len);
|
||||
|
||||
if (cert_mem && cert_mem_len)
|
||||
EVP_DigestUpdate(mdctx, cert_mem, cert_mem_len);
|
||||
|
||||
len = sizeof(hash);
|
||||
EVP_DigestFinal_ex(mdctx, hash, &len);
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
|
||||
/* look for existing client context with same config already */
|
||||
|
||||
lws_start_foreach_dll_safe(struct lws_dll *, p, tp,
|
||||
vh->context->tls.cc_head.next) {
|
||||
tcr = lws_container_of(p, struct lws_tls_client_reuse, cc_list);
|
||||
|
||||
if (!memcmp(hash, tcr->hash, len)) {
|
||||
|
||||
/* it's a match */
|
||||
|
||||
tcr->refcount++;
|
||||
vh->tls.ssl_client_ctx = tcr->ssl_client_ctx;
|
||||
|
||||
lwsl_info("%s: vh %s: reusing client ctx %d: use %d\n",
|
||||
__func__, vh->name, tcr->index,
|
||||
tcr->refcount);
|
||||
|
||||
return 0;
|
||||
}
|
||||
} lws_end_foreach_dll_safe(p, tp);
|
||||
|
||||
/* no existing one the same... create new client SSL_CTX */
|
||||
|
||||
vh->tls.ssl_client_ctx = SSL_CTX_new(method);
|
||||
if (!vh->tls.ssl_client_ctx) {
|
||||
error = ERR_get_error();
|
||||
|
@ -399,6 +494,27 @@ lws_tls_client_create_vhost_context(struct lws_vhost *vh,
|
|||
return 1;
|
||||
}
|
||||
|
||||
tcr = lws_zalloc(sizeof(*tcr), "client ctx tcr");
|
||||
if (!tcr) {
|
||||
SSL_CTX_free(vh->tls.ssl_client_ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
tcr->ssl_client_ctx = vh->tls.ssl_client_ctx;
|
||||
tcr->refcount = 1;
|
||||
memcpy(tcr->hash, hash, len);
|
||||
tcr->index = vh->context->tls.count_client_contexts++;
|
||||
lws_dll_add_front(&tcr->cc_list, &vh->context->tls.cc_head);
|
||||
|
||||
lwsl_info("%s: vh %s: created new client ctx %d\n", __func__,
|
||||
vh->name, tcr->index);
|
||||
|
||||
/* bind the tcr to the client context */
|
||||
|
||||
SSL_CTX_set_ex_data(vh->tls.ssl_client_ctx,
|
||||
openssl_SSL_CTX_private_data_index,
|
||||
(char *)tcr);
|
||||
|
||||
#ifdef SSL_OP_NO_COMPRESSION
|
||||
SSL_CTX_set_options(vh->tls.ssl_client_ctx, SSL_OP_NO_COMPRESSION);
|
||||
#endif
|
||||
|
|
|
@ -21,6 +21,19 @@
|
|||
* gencrypto openssl-specific helper declarations
|
||||
*/
|
||||
|
||||
/*
|
||||
* one of these per different client context
|
||||
* cc_head is in lws_context.lws_context_tls
|
||||
*/
|
||||
|
||||
struct lws_tls_client_reuse {
|
||||
lws_tls_ctx *ssl_client_ctx;
|
||||
uint8_t hash[32];
|
||||
struct lws_dll cc_list;
|
||||
int refcount;
|
||||
int index;
|
||||
};
|
||||
|
||||
typedef int (*next_proto_cb)(SSL *, const unsigned char **out,
|
||||
unsigned char *outlen, const unsigned char *in,
|
||||
unsigned int inlen, void *arg);
|
||||
|
|
|
@ -90,6 +90,29 @@ lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx,
|
|||
lws_context_init_ssl_pem_passwd_cb);
|
||||
}
|
||||
|
||||
static void
|
||||
lws_ssl_destroy_client_ctx(struct lws_vhost *vhost)
|
||||
{
|
||||
struct lws_tls_client_reuse *tcr;
|
||||
|
||||
if (vhost->tls.user_supplied_ssl_ctx || !vhost->tls.ssl_client_ctx)
|
||||
return;
|
||||
|
||||
tcr = SSL_CTX_get_ex_data(vhost->tls.ssl_client_ctx,
|
||||
openssl_SSL_CTX_private_data_index);
|
||||
|
||||
if (--tcr->refcount)
|
||||
return;
|
||||
|
||||
SSL_CTX_free(vhost->tls.ssl_client_ctx);
|
||||
vhost->tls.ssl_client_ctx = NULL;
|
||||
|
||||
vhost->context->tls.count_client_contexts--;
|
||||
|
||||
lws_dll_remove(&tcr->cc_list);
|
||||
lws_free(tcr);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_ssl_destroy(struct lws_vhost *vhost)
|
||||
{
|
||||
|
@ -99,8 +122,8 @@ lws_ssl_destroy(struct lws_vhost *vhost)
|
|||
|
||||
if (vhost->tls.ssl_ctx)
|
||||
SSL_CTX_free(vhost->tls.ssl_ctx);
|
||||
if (!vhost->tls.user_supplied_ssl_ctx && vhost->tls.ssl_client_ctx)
|
||||
SSL_CTX_free(vhost->tls.ssl_client_ctx);
|
||||
|
||||
lws_ssl_destroy_client_ctx(vhost);
|
||||
|
||||
// after 1.1.0 no need
|
||||
#if (OPENSSL_VERSION_NUMBER < 0x10100000)
|
||||
|
@ -378,8 +401,8 @@ lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost)
|
|||
if (vhost->tls.ssl_ctx)
|
||||
SSL_CTX_free(vhost->tls.ssl_ctx);
|
||||
|
||||
if (!vhost->tls.user_supplied_ssl_ctx && vhost->tls.ssl_client_ctx)
|
||||
SSL_CTX_free(vhost->tls.ssl_client_ctx);
|
||||
lws_ssl_destroy_client_ctx(vhost);
|
||||
|
||||
#if defined(LWS_WITH_ACME)
|
||||
lws_tls_acme_sni_cert_destroy(vhost);
|
||||
#endif
|
||||
|
|
|
@ -31,6 +31,8 @@ struct lws_context_tls {
|
|||
char alpn_discovered[32];
|
||||
const char *alpn_default;
|
||||
time_t last_cert_check_s;
|
||||
struct lws_dll cc_head;
|
||||
int count_client_contexts;
|
||||
};
|
||||
|
||||
struct lws_pt_tls {
|
||||
|
|
Loading…
Add table
Reference in a new issue