diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index 7d485f4f1..37934e75a 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -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++; diff --git a/lib/core-net/wsi.c b/lib/core-net/wsi.c index 75eb8d951..db97bb664 100644 --- a/lib/core-net/wsi.c +++ b/lib/core-net/wsi.c @@ -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; diff --git a/lib/tls/openssl/openssl-client.c b/lib/tls/openssl/openssl-client.c index 299c58ff4..74335e77c 100644 --- a/lib/tls/openssl/openssl-client.c +++ b/lib/tls/openssl/openssl-client.c @@ -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 diff --git a/lib/tls/openssl/private.h b/lib/tls/openssl/private.h index 6a89d51fd..028a4b290 100644 --- a/lib/tls/openssl/private.h +++ b/lib/tls/openssl/private.h @@ -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); diff --git a/lib/tls/openssl/ssl.c b/lib/tls/openssl/ssl.c index 462d9484b..e23e185e9 100644 --- a/lib/tls/openssl/ssl.c +++ b/lib/tls/openssl/ssl.c @@ -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 diff --git a/lib/tls/private-network.h b/lib/tls/private-network.h index 2627d4728..e05f659d3 100644 --- a/lib/tls/private-network.h +++ b/lib/tls/private-network.h @@ -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 {