From: Michael Drake Date: Wed, 28 Sep 2022 14:51:44 +0000 (+0100) Subject: openssl: reduce CA certificate bundle reparsing by caching X-Git-Tag: curl-7_87_0~192 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3c16697ebd796f799227be293e8689aec5f8190d;p=thirdparty%2Fcurl.git openssl: reduce CA certificate bundle reparsing by caching Closes #9620 --- diff --git a/lib/multi.c b/lib/multi.c index 51acba73ac..09965de839 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -2770,6 +2770,11 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) wakeup_close(multi->wakeup_pair[1]); #endif #endif + +#ifdef USE_SSL + Curl_free_multi_ssl_backend_data(multi->ssl_backend_data); +#endif + free(multi); return CURLM_OK; diff --git a/lib/multihandle.h b/lib/multihandle.h index a997784ea3..5a83656d51 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -79,6 +79,10 @@ typedef enum { /* value for MAXIMUM CONCURRENT STREAMS upper limit */ #define INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1) +/* Curl_multi SSL backend-specific data; declared differently by each SSL + backend */ +struct multi_ssl_backend_data; + /* This is the struct known as CURLM on the outside */ struct Curl_multi { /* First a simple identifier to easier detect if a user mix up @@ -118,6 +122,10 @@ struct Curl_multi { times of all currently set timers */ struct Curl_tree *timetree; +#if defined(USE_SSL) + struct multi_ssl_backend_data *ssl_backend_data; +#endif + /* 'sockhash' is the lookup hash for socket descriptor => easy handles (note the pluralis form, there can be more than one easy handle waiting on the same actual socket) */ diff --git a/lib/urldata.h b/lib/urldata.h index fc79851765..bc8dfd6238 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1531,7 +1531,7 @@ struct UrlState { * Character pointer fields point to dynamic storage, unless otherwise stated. */ -struct Curl_multi; /* declared and used only in multi.c */ +struct Curl_multi; /* declared in multihandle.c */ /* * This enumeration MUST not use conditional directives (#ifdefs), new diff --git a/lib/vtls/bearssl.c b/lib/vtls/bearssl.c index 1221ce8c84..e66dd8469c 100644 --- a/lib/vtls/bearssl.c +++ b/lib/vtls/bearssl.c @@ -1208,7 +1208,8 @@ const struct Curl_ssl Curl_ssl_bearssl = { Curl_none_false_start, /* false_start */ bearssl_sha256sum, /* sha256sum */ NULL, /* associate_connection */ - NULL /* disassociate_connection */ + NULL, /* disassociate_connection */ + NULL /* free_multi_ssl_backend_data */ }; #endif /* USE_BEARSSL */ diff --git a/lib/vtls/gskit.c b/lib/vtls/gskit.c index 52d2eaec3d..719314f684 100644 --- a/lib/vtls/gskit.c +++ b/lib/vtls/gskit.c @@ -1323,7 +1323,8 @@ const struct Curl_ssl Curl_ssl_gskit = { Curl_none_false_start, /* false_start */ NULL, /* sha256sum */ NULL, /* associate_connection */ - NULL /* disassociate_connection */ + NULL, /* disassociate_connection */ + NULL /* free_multi_ssl_backend_data */ }; #endif /* USE_GSKIT */ diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index cf3dbc5238..68e3fe299d 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -1699,7 +1699,8 @@ const struct Curl_ssl Curl_ssl_gnutls = { Curl_none_false_start, /* false_start */ gtls_sha256sum, /* sha256sum */ NULL, /* associate_connection */ - NULL /* disassociate_connection */ + NULL, /* disassociate_connection */ + NULL /* free_multi_ssl_backend_data */ }; #endif /* USE_GNUTLS */ diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index fbde8976eb..a5250289d8 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -1267,7 +1267,8 @@ const struct Curl_ssl Curl_ssl_mbedtls = { Curl_none_false_start, /* false_start */ mbedtls_sha256sum, /* sha256sum */ NULL, /* associate_connection */ - NULL /* disassociate_connection */ + NULL, /* disassociate_connection */ + NULL /* free_multi_ssl_backend_data */ }; #endif /* USE_MBEDTLS */ diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c index b08808c433..55d777d69b 100644 --- a/lib/vtls/nss.c +++ b/lib/vtls/nss.c @@ -2530,7 +2530,8 @@ const struct Curl_ssl Curl_ssl_nss = { nss_false_start, /* false_start */ nss_sha256sum, /* sha256sum */ NULL, /* associate_connection */ - NULL /* disassociate_connection */ + NULL, /* disassociate_connection */ + NULL /* free_multi_ssl_backend_data */ }; #endif /* USE_NSS */ diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 29ff13f1c6..4b2aa55340 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -259,6 +259,15 @@ #define HAVE_OPENSSL_VERSION #endif +/* + * Whether the OpenSSL version has the API needed to support sharing an + * X509_STORE between connections. The API is: + * * `X509_STORE_up_ref` -- Introduced: OpenSSL 1.1.0. + */ +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* OpenSSL >= 1.1.0 */ +#define HAVE_SSL_X509_STORE_SHARE +#endif + struct ssl_backend_data { struct Curl_easy *logger; /* transfer handle to pass trace logs to, only using sockindex 0 */ @@ -272,6 +281,14 @@ struct ssl_backend_data { #endif }; +#if defined(HAVE_SSL_X509_STORE_SHARE) +struct multi_ssl_backend_data { + char *CAfile; /* CAfile path used to generate X509 store */ + X509_STORE *store; /* cached X509 store or NULL if none */ + struct curltime time; /* when the cached store was created */ +}; +#endif /* HAVE_SSL_X509_STORE_SHARE */ + #define push_certinfo(_label, _num) \ do { \ long info_len = BIO_get_mem_data(mem, &ptr); \ @@ -2830,7 +2847,7 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) return res; } -static CURLcode load_cacert_from_memory(SSL_CTX *ctx, +static CURLcode load_cacert_from_memory(X509_STORE *store, const struct curl_blob *ca_info_blob) { /* these need to be freed at the end */ @@ -2839,16 +2856,11 @@ static CURLcode load_cacert_from_memory(SSL_CTX *ctx, /* everything else is just a reference */ int i, count = 0; - X509_STORE *cts = NULL; X509_INFO *itmp = NULL; if(ca_info_blob->len > (size_t)INT_MAX) return CURLE_SSL_CACERT_BADFILE; - cts = SSL_CTX_get_cert_store(ctx); - if(!cts) - return CURLE_OUT_OF_MEMORY; - cbio = BIO_new_mem_buf(ca_info_blob->data, (int)ca_info_blob->len); if(!cbio) return CURLE_OUT_OF_MEMORY; @@ -2863,7 +2875,7 @@ static CURLcode load_cacert_from_memory(SSL_CTX *ctx, for(i = 0; i < (int)sk_X509_INFO_num(inf); ++i) { itmp = sk_X509_INFO_value(inf, i); if(itmp->x509) { - if(X509_STORE_add_cert(cts, itmp->x509)) { + if(X509_STORE_add_cert(store, itmp->x509)) { ++count; } else { @@ -2873,7 +2885,7 @@ static CURLcode load_cacert_from_memory(SSL_CTX *ctx, } } if(itmp->crl) { - if(X509_STORE_add_crl(cts, itmp->crl)) { + if(X509_STORE_add_crl(store, itmp->crl)) { ++count; } else { @@ -2891,161 +2903,530 @@ static CURLcode load_cacert_from_memory(SSL_CTX *ctx, return (count > 0 ? CURLE_OK : CURLE_SSL_CACERT_BADFILE); } -static CURLcode ossl_connect_step1(struct Curl_easy *data, - struct connectdata *conn, int sockindex) +static CURLcode populate_x509_store(struct Curl_easy *data, + struct connectdata *conn, + X509_STORE *store) { CURLcode result = CURLE_OK; - char *ciphers; - SSL_METHOD_QUAL SSL_METHOD *req_method = NULL; X509_LOOKUP *lookup = NULL; - curl_socket_t sockfd = conn->sock[sockindex]; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - ctx_option_t ctx_options = 0; - void *ssl_sessionid = NULL; - -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - bool sni; - const char * const hostname = SSL_HOST_NAME(); - -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif -#endif - const long int ssl_version = SSL_CONN_CONFIG(version); -#ifdef USE_OPENSSL_SRP - const enum CURL_TLSAUTH ssl_authtype = SSL_SET_OPTION(primary.authtype); -#endif - char * const ssl_cert = SSL_SET_OPTION(primary.clientcert); - const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob); const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob); - const char * const ssl_cert_type = SSL_SET_OPTION(cert_type); const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ (ca_info_blob ? NULL : SSL_CONN_CONFIG(CAfile)); const char * const ssl_capath = SSL_CONN_CONFIG(CApath); - const bool verifypeer = SSL_CONN_CONFIG(verifypeer); const char * const ssl_crlfile = SSL_SET_OPTION(primary.CRLfile); - char error_buffer[256]; - struct ssl_backend_data *backend = connssl->backend; + const bool verifypeer = SSL_CONN_CONFIG(verifypeer); bool imported_native_ca = false; - DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); - DEBUGASSERT(backend); - - /* Make funny stuff to get random input */ - result = ossl_seed(data); - if(result) - return result; + if(!store) + return CURLE_OUT_OF_MEMORY; - SSL_SET_OPTION_LVALUE(certverifyresult) = !X509_V_OK; +#if defined(USE_WIN32_CRYPTO) + /* Import certificates from the Windows root certificate store if requested. + https://stackoverflow.com/questions/9507184/ + https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 + https://datatracker.ietf.org/doc/html/rfc5280 */ + if((SSL_CONN_CONFIG(verifypeer) || SSL_CONN_CONFIG(verifyhost)) && + (SSL_SET_OPTION(native_ca_store))) { + HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT")); - /* check to see if we've been told to use an explicit SSL/TLS version */ + if(hStore) { + PCCERT_CONTEXT pContext = NULL; + /* The array of enhanced key usage OIDs will vary per certificate and is + declared outside of the loop so that rather than malloc/free each + iteration we can grow it with realloc, when necessary. */ + CERT_ENHKEY_USAGE *enhkey_usage = NULL; + DWORD enhkey_usage_size = 0; - switch(ssl_version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - /* it will be handled later with the context options */ -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) - req_method = TLS_client_method(); -#else - req_method = SSLv23_client_method(); + /* This loop makes a best effort to import all valid certificates from + the MS root store. If a certificate cannot be imported it is skipped. + 'result' is used to store only hard-fail conditions (such as out of + memory) that cause an early break. */ + result = CURLE_OK; + for(;;) { + X509 *x509; + FILETIME now; + BYTE key_usage[2]; + DWORD req_size; + const unsigned char *encoded_cert; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + char cert_name[256]; #endif - use_sni(TRUE); - break; - case CURL_SSLVERSION_SSLv2: - failf(data, "No SSLv2 support"); - return CURLE_NOT_BUILT_IN; - case CURL_SSLVERSION_SSLv3: - failf(data, "No SSLv3 support"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - DEBUGASSERT(!backend->ctx); - backend->ctx = SSL_CTX_new(req_method); - if(!backend->ctx) { - failf(data, "SSL: couldn't create a context: %s", - ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer))); - return CURLE_OUT_OF_MEMORY; - } + pContext = CertEnumCertificatesInStore(hStore, pContext); + if(!pContext) + break; -#ifdef SSL_MODE_RELEASE_BUFFERS - SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS); +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, + NULL, cert_name, sizeof(cert_name))) { + strcpy(cert_name, "Unknown"); + } + infof(data, "SSL: Checking cert \"%s\"", cert_name); #endif -#ifdef SSL_CTRL_SET_MSG_CALLBACK - if(data->set.fdebug && data->set.verbose) { - /* the SSL trace callback is only used for verbose logging */ - SSL_CTX_set_msg_callback(backend->ctx, ossl_trace); - SSL_CTX_set_msg_callback_arg(backend->ctx, conn); - set_logger(conn, data); - } -#endif + encoded_cert = (const unsigned char *)pContext->pbCertEncoded; + if(!encoded_cert) + continue; - /* OpenSSL contains code to work around lots of bugs and flaws in various - SSL-implementations. SSL_CTX_set_options() is used to enabled those - work-arounds. The man page for this option states that SSL_OP_ALL enables - all the work-arounds and that "It is usually safe to use SSL_OP_ALL to - enable the bug workaround options if compatibility with somewhat broken - implementations is desired." + GetSystemTimeAsFileTime(&now); + if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 || + CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0) + continue; - The "-no_ticket" option was introduced in OpenSSL 0.9.8j. It's a flag to - disable "rfc4507bis session ticket support". rfc4507bis was later turned - into the proper RFC5077: https://datatracker.ietf.org/doc/html/rfc5077 + /* If key usage exists check for signing attribute */ + if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType, + pContext->pCertInfo, + key_usage, sizeof(key_usage))) { + if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE)) + continue; + } + else if(GetLastError()) + continue; - The enabled extension concerns the session management. I wonder how often - libcurl stops a connection and then resumes a TLS session. Also, sending - the session data is some overhead. I suggest that you just use your - proposed patch (which explicitly disables TICKET). + /* If enhanced key usage exists check for server auth attribute. + * + * Note "In a Microsoft environment, a certificate might also have EKU + * extended properties that specify valid uses for the certificate." + * The call below checks both, and behavior varies depending on what is + * found. For more details see CertGetEnhancedKeyUsage doc. + */ + if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) { + if(req_size && req_size > enhkey_usage_size) { + void *tmp = realloc(enhkey_usage, req_size); - If someone writes an application with libcurl and OpenSSL who wants to - enable the feature, one can do this in the SSL callback. + if(!tmp) { + failf(data, "SSL: Out of memory allocating for OID list"); + result = CURLE_OUT_OF_MEMORY; + break; + } - SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper - interoperability with web server Netscape Enterprise Server 2.0.1 which - was released back in 1996. + enhkey_usage = (CERT_ENHKEY_USAGE *)tmp; + enhkey_usage_size = req_size; + } - Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has - become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate - CVE-2010-4180 when using previous OpenSSL versions we no longer enable - this option regardless of OpenSSL version and SSL_OP_ALL definition. + if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) { + if(!enhkey_usage->cUsageIdentifier) { + /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is + good for all uses. If it returns zero, the certificate has no + valid uses." */ + if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND) + continue; + } + else { + DWORD i; + bool found = false; - OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability - (https://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to - SSL_OP_ALL that _disables_ that work-around despite the fact that - SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to - keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit - must not be set. - */ + for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) { + if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */, + enhkey_usage->rgpszUsageIdentifier[i])) { + found = true; + break; + } + } - ctx_options = SSL_OP_ALL; + if(!found) + continue; + } + } + else + continue; + } + else + continue; -#ifdef SSL_OP_NO_TICKET - ctx_options |= SSL_OP_NO_TICKET; -#endif + x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if(!x509) + continue; -#ifdef SSL_OP_NO_COMPRESSION - ctx_options |= SSL_OP_NO_COMPRESSION; + /* Try to import the certificate. This may fail for legitimate reasons + such as duplicate certificate, which is allowed by MS but not + OpenSSL. */ + if(X509_STORE_add_cert(store, x509) == 1) { +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + infof(data, "SSL: Imported cert \"%s\"", cert_name); #endif + imported_native_ca = true; + } + X509_free(x509); + } -#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG - /* mitigate CVE-2010-4180 */ - ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; + free(enhkey_usage); + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + if(result) + return result; + } + if(imported_native_ca) + infof(data, "successfully imported Windows CA store"); + else + infof(data, "error importing Windows CA store, continuing anyway"); + } #endif -#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS - /* unless the user explicitly asks to allow the protocol vulnerability we - use the work-around */ - if(!SSL_SET_OPTION(enable_beast)) + if(ca_info_blob) { + result = load_cacert_from_memory(store, ca_info_blob); + if(result) { + if(result == CURLE_OUT_OF_MEMORY || + (verifypeer && !imported_native_ca)) { + failf(data, "error importing CA certificate blob"); + return result; + } + /* Only warn if no certificate verification is required. */ + infof(data, "error importing CA certificate blob, continuing anyway"); + } + } + + if(verifypeer && !imported_native_ca && (ssl_cafile || ssl_capath)) { +#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) + /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ + if(ssl_cafile && + !X509_STORE_load_file(store, ssl_cafile)) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate file: %s", ssl_cafile); + return CURLE_SSL_CACERT_BADFILE; + } + if(ssl_capath && + !X509_STORE_load_path(store, ssl_capath)) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate path: %s", ssl_capath); + return CURLE_SSL_CACERT_BADFILE; + } +#else + /* tell OpenSSL where to find CA certificates that are used to verify the + server's certificate. */ + if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate verify locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + return CURLE_SSL_CACERT_BADFILE; + } +#endif + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + } + +#ifdef CURL_CA_FALLBACK + if(verifypeer && + !ca_info_blob && !ssl_cafile && !ssl_capath && !imported_native_ca) { + /* verifying the peer without any CA certificates won't + work so use openssl's built-in default as fallback */ + X509_STORE_set_default_paths(store); + } +#endif + + if(ssl_crlfile) { + /* tell OpenSSL where to find CRL file that is used to check certificate + * revocation */ + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + if(!lookup || + (!X509_load_crl_file(lookup, ssl_crlfile, X509_FILETYPE_PEM)) ) { + failf(data, "error loading CRL file: %s", ssl_crlfile); + return CURLE_SSL_CRL_BADFILE; + } + /* Everything is fine. */ + infof(data, "successfully loaded CRL file:"); + X509_STORE_set_flags(store, + X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); + + infof(data, " CRLfile: %s", ssl_crlfile); + } + + if(verifypeer) { + /* Try building a chain using issuers in the trusted store first to avoid + problems with server-sent legacy intermediates. Newer versions of + OpenSSL do alternate chain checking by default but we do not know how to + determine that in a reliable manner. + https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest + */ +#if defined(X509_V_FLAG_TRUSTED_FIRST) + X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST); +#endif +#ifdef X509_V_FLAG_PARTIAL_CHAIN + if(!SSL_SET_OPTION(no_partialchain) && !ssl_crlfile) { + /* Have intermediate certificates in the trust store be treated as + trust-anchors, in the same way as self-signed root CA certificates + are. This allows users to verify servers using the intermediate cert + only, instead of needing the whole chain. + + Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we + cannot do partial chains with a CRL check. + */ + X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN); + } +#endif + } + + return result; +} + +#if defined(HAVE_SSL_X509_STORE_SHARE) +#define X509_STORE_EXPIRY_MS (24 * 60 * 60 * 1000) /* 24 hours */ +static bool cached_x509_store_expired(const struct multi_ssl_backend_data *mb) +{ + struct curltime now = Curl_now(); + + return Curl_timediff(now, mb->time) >= X509_STORE_EXPIRY_MS; +} + +static bool cached_x509_store_different( + const struct multi_ssl_backend_data *mb, + const struct connectdata *conn) +{ + if(!mb->CAfile || !SSL_CONN_CONFIG(CAfile)) + return mb->CAfile != SSL_CONN_CONFIG(CAfile); + + return strcmp(mb->CAfile, SSL_CONN_CONFIG(CAfile)); +} + +static X509_STORE *get_cached_x509_store(const struct Curl_easy *data, + const struct connectdata *conn) +{ + struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + X509_STORE *store = NULL; + + if(multi && + multi->ssl_backend_data && + multi->ssl_backend_data->store && + !cached_x509_store_expired(multi->ssl_backend_data) && + !cached_x509_store_different(multi->ssl_backend_data, conn)) { + store = multi->ssl_backend_data->store; + } + + return store; +} + +static void set_cached_x509_store(const struct Curl_easy *data, + const struct connectdata *conn, + X509_STORE *store) +{ + struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + struct multi_ssl_backend_data *mbackend; + + if(!multi) + return; + + if(!multi->ssl_backend_data) { + multi->ssl_backend_data = calloc(1, sizeof(struct multi_ssl_backend_data)); + if(!multi->ssl_backend_data) + return; + } + + mbackend = multi->ssl_backend_data; + + if(X509_STORE_up_ref(store)) { + char *CAfile = NULL; + + if(SSL_CONN_CONFIG(CAfile)) { + CAfile = strdup(SSL_CONN_CONFIG(CAfile)); + if(!CAfile) { + X509_STORE_free(store); + return; + } + } + + if(mbackend->store) { + X509_STORE_free(mbackend->store); + free(mbackend->CAfile); + } + + mbackend->time = Curl_now(); + mbackend->store = store; + mbackend->CAfile = CAfile; + } +} + +static CURLcode set_up_x509_store(struct Curl_easy *data, + struct connectdata *conn, + struct ssl_backend_data *backend) +{ + CURLcode result = CURLE_OK; + X509_STORE *cached_store = get_cached_x509_store(data, conn); + + /* Consider the X509 store cacheable if it comes exclusively from a CAfile, + or no source is provided and we are falling back to openssl's built-in + default. */ + bool cache_criteria_met = SSL_CONN_CONFIG(verifypeer) && + !SSL_CONN_CONFIG(CApath) && + !SSL_CONN_CONFIG(ca_info_blob) && + !SSL_SET_OPTION(primary.CRLfile) && + !SSL_SET_OPTION(native_ca_store); + + if(cached_store && cache_criteria_met && X509_STORE_up_ref(cached_store)) { + SSL_CTX_set_cert_store(backend->ctx, cached_store); + } + else { + X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx); + + result = populate_x509_store(data, conn, store); + if(result == CURLE_OK && cache_criteria_met) { + set_cached_x509_store(data, conn, store); + } + } + + return result; +} +#else /* HAVE_SSL_X509_STORE_SHARE */ +static CURLcode set_up_x509_store(struct Curl_easy *data, + struct connectdata *conn, + struct ssl_backend_data *backend) +{ + X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx); + + return populate_x509_store(data, conn, store); +} +#endif /* HAVE_SSL_X509_STORE_SHARE */ + +static CURLcode ossl_connect_step1(struct Curl_easy *data, + struct connectdata *conn, int sockindex) +{ + CURLcode result = CURLE_OK; + char *ciphers; + SSL_METHOD_QUAL SSL_METHOD *req_method = NULL; + curl_socket_t sockfd = conn->sock[sockindex]; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + ctx_option_t ctx_options = 0; + void *ssl_sessionid = NULL; + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + bool sni; + const char * const hostname = SSL_HOST_NAME(); + +#ifdef ENABLE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif +#endif + const long int ssl_version = SSL_CONN_CONFIG(version); +#ifdef USE_OPENSSL_SRP + const enum CURL_TLSAUTH ssl_authtype = SSL_SET_OPTION(primary.authtype); +#endif + char * const ssl_cert = SSL_SET_OPTION(primary.clientcert); + const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob); + const char * const ssl_cert_type = SSL_SET_OPTION(cert_type); + const bool verifypeer = SSL_CONN_CONFIG(verifypeer); + char error_buffer[256]; + struct ssl_backend_data *backend = connssl->backend; + + DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + DEBUGASSERT(backend); + + /* Make funny stuff to get random input */ + result = ossl_seed(data); + if(result) + return result; + + SSL_SET_OPTION_LVALUE(certverifyresult) = !X509_V_OK; + + /* check to see if we've been told to use an explicit SSL/TLS version */ + + switch(ssl_version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + case CURL_SSLVERSION_TLSv1_3: + /* it will be handled later with the context options */ +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + req_method = TLS_client_method(); +#else + req_method = SSLv23_client_method(); +#endif + use_sni(TRUE); + break; + case CURL_SSLVERSION_SSLv2: + failf(data, "No SSLv2 support"); + return CURLE_NOT_BUILT_IN; + case CURL_SSLVERSION_SSLv3: + failf(data, "No SSLv3 support"); + return CURLE_NOT_BUILT_IN; + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + + DEBUGASSERT(!backend->ctx); + backend->ctx = SSL_CTX_new(req_method); + + if(!backend->ctx) { + failf(data, "SSL: couldn't create a context: %s", + ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer))); + return CURLE_OUT_OF_MEMORY; + } + +#ifdef SSL_MODE_RELEASE_BUFFERS + SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS); +#endif + +#ifdef SSL_CTRL_SET_MSG_CALLBACK + if(data->set.fdebug && data->set.verbose) { + /* the SSL trace callback is only used for verbose logging */ + SSL_CTX_set_msg_callback(backend->ctx, ossl_trace); + SSL_CTX_set_msg_callback_arg(backend->ctx, conn); + set_logger(conn, data); + } +#endif + + /* OpenSSL contains code to work around lots of bugs and flaws in various + SSL-implementations. SSL_CTX_set_options() is used to enabled those + work-arounds. The man page for this option states that SSL_OP_ALL enables + all the work-arounds and that "It is usually safe to use SSL_OP_ALL to + enable the bug workaround options if compatibility with somewhat broken + implementations is desired." + + The "-no_ticket" option was introduced in OpenSSL 0.9.8j. It's a flag to + disable "rfc4507bis session ticket support". rfc4507bis was later turned + into the proper RFC5077: https://datatracker.ietf.org/doc/html/rfc5077 + + The enabled extension concerns the session management. I wonder how often + libcurl stops a connection and then resumes a TLS session. Also, sending + the session data is some overhead. I suggest that you just use your + proposed patch (which explicitly disables TICKET). + + If someone writes an application with libcurl and OpenSSL who wants to + enable the feature, one can do this in the SSL callback. + + SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper + interoperability with web server Netscape Enterprise Server 2.0.1 which + was released back in 1996. + + Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has + become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate + CVE-2010-4180 when using previous OpenSSL versions we no longer enable + this option regardless of OpenSSL version and SSL_OP_ALL definition. + + OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability + (https://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to + SSL_OP_ALL that _disables_ that work-around despite the fact that + SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to + keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit + must not be set. + */ + + ctx_options = SSL_OP_ALL; + +#ifdef SSL_OP_NO_TICKET + ctx_options |= SSL_OP_NO_TICKET; +#endif + +#ifdef SSL_OP_NO_COMPRESSION + ctx_options |= SSL_OP_NO_COMPRESSION; +#endif + +#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG + /* mitigate CVE-2010-4180 */ + ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; +#endif + +#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS + /* unless the user explicitly asks to allow the protocol vulnerability we + use the work-around */ + if(!SSL_SET_OPTION(enable_beast)) ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; #endif @@ -3196,249 +3577,9 @@ static CURLcode ossl_connect_step1(struct Curl_easy *data, } #endif - -#if defined(USE_WIN32_CRYPTO) - /* Import certificates from the Windows root certificate store if requested. - https://stackoverflow.com/questions/9507184/ - https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 - https://datatracker.ietf.org/doc/html/rfc5280 */ - if((SSL_CONN_CONFIG(verifypeer) || SSL_CONN_CONFIG(verifyhost)) && - (SSL_SET_OPTION(native_ca_store))) { - X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx); - HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT")); - - if(hStore) { - PCCERT_CONTEXT pContext = NULL; - /* The array of enhanced key usage OIDs will vary per certificate and is - declared outside of the loop so that rather than malloc/free each - iteration we can grow it with realloc, when necessary. */ - CERT_ENHKEY_USAGE *enhkey_usage = NULL; - DWORD enhkey_usage_size = 0; - - /* This loop makes a best effort to import all valid certificates from - the MS root store. If a certificate cannot be imported it is skipped. - 'result' is used to store only hard-fail conditions (such as out of - memory) that cause an early break. */ - result = CURLE_OK; - for(;;) { - X509 *x509; - FILETIME now; - BYTE key_usage[2]; - DWORD req_size; - const unsigned char *encoded_cert; -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - char cert_name[256]; -#endif - - pContext = CertEnumCertificatesInStore(hStore, pContext); - if(!pContext) - break; - -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, - NULL, cert_name, sizeof(cert_name))) { - strcpy(cert_name, "Unknown"); - } - infof(data, "SSL: Checking cert \"%s\"", cert_name); -#endif - - encoded_cert = (const unsigned char *)pContext->pbCertEncoded; - if(!encoded_cert) - continue; - - GetSystemTimeAsFileTime(&now); - if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 || - CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0) - continue; - - /* If key usage exists check for signing attribute */ - if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType, - pContext->pCertInfo, - key_usage, sizeof(key_usage))) { - if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE)) - continue; - } - else if(GetLastError()) - continue; - - /* If enhanced key usage exists check for server auth attribute. - * - * Note "In a Microsoft environment, a certificate might also have EKU - * extended properties that specify valid uses for the certificate." - * The call below checks both, and behavior varies depending on what is - * found. For more details see CertGetEnhancedKeyUsage doc. - */ - if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) { - if(req_size && req_size > enhkey_usage_size) { - void *tmp = realloc(enhkey_usage, req_size); - - if(!tmp) { - failf(data, "SSL: Out of memory allocating for OID list"); - result = CURLE_OUT_OF_MEMORY; - break; - } - - enhkey_usage = (CERT_ENHKEY_USAGE *)tmp; - enhkey_usage_size = req_size; - } - - if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) { - if(!enhkey_usage->cUsageIdentifier) { - /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is - good for all uses. If it returns zero, the certificate has no - valid uses." */ - if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND) - continue; - } - else { - DWORD i; - bool found = false; - - for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) { - if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */, - enhkey_usage->rgpszUsageIdentifier[i])) { - found = true; - break; - } - } - - if(!found) - continue; - } - } - else - continue; - } - else - continue; - - x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); - if(!x509) - continue; - - /* Try to import the certificate. This may fail for legitimate reasons - such as duplicate certificate, which is allowed by MS but not - OpenSSL. */ - if(X509_STORE_add_cert(store, x509) == 1) { -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - infof(data, "SSL: Imported cert \"%s\"", cert_name); -#endif - imported_native_ca = true; - } - X509_free(x509); - } - - free(enhkey_usage); - CertFreeCertificateContext(pContext); - CertCloseStore(hStore, 0); - - if(result) - return result; - } - if(imported_native_ca) - infof(data, "successfully imported Windows CA store"); - else - infof(data, "error importing Windows CA store, continuing anyway"); - } -#endif - - if(ca_info_blob) { - result = load_cacert_from_memory(backend->ctx, ca_info_blob); - if(result) { - if(result == CURLE_OUT_OF_MEMORY || - (verifypeer && !imported_native_ca)) { - failf(data, "error importing CA certificate blob"); - return result; - } - /* Only warn if no certificate verification is required. */ - infof(data, "error importing CA certificate blob, continuing anyway"); - } - } - - if(verifypeer && !imported_native_ca && (ssl_cafile || ssl_capath)) { -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) - /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ - if(ssl_cafile && - !SSL_CTX_load_verify_file(backend->ctx, ssl_cafile)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate file: %s", ssl_cafile); - return CURLE_SSL_CACERT_BADFILE; - } - if(ssl_capath && - !SSL_CTX_load_verify_dir(backend->ctx, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate path: %s", ssl_capath); - return CURLE_SSL_CACERT_BADFILE; - } -#else - /* tell OpenSSL where to find CA certificates that are used to verify the - server's certificate. */ - if(!SSL_CTX_load_verify_locations(backend->ctx, ssl_cafile, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - return CURLE_SSL_CACERT_BADFILE; - } -#endif - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); - } - -#ifdef CURL_CA_FALLBACK - if(verifypeer && - !ca_info_blob && !ssl_cafile && !ssl_capath && !imported_native_ca) { - /* verifying the peer without any CA certificates won't - work so use openssl's built-in default as fallback */ - SSL_CTX_set_default_verify_paths(backend->ctx); - } -#endif - - if(ssl_crlfile) { - /* tell OpenSSL where to find CRL file that is used to check certificate - * revocation */ - lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(backend->ctx), - X509_LOOKUP_file()); - if(!lookup || - (!X509_load_crl_file(lookup, ssl_crlfile, X509_FILETYPE_PEM)) ) { - failf(data, "error loading CRL file: %s", ssl_crlfile); - return CURLE_SSL_CRL_BADFILE; - } - /* Everything is fine. */ - infof(data, "successfully loaded CRL file:"); - X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx), - X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); - - infof(data, " CRLfile: %s", ssl_crlfile); - } - - if(verifypeer) { - /* Try building a chain using issuers in the trusted store first to avoid - problems with server-sent legacy intermediates. Newer versions of - OpenSSL do alternate chain checking by default but we do not know how to - determine that in a reliable manner. - https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest - */ -#if defined(X509_V_FLAG_TRUSTED_FIRST) - X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx), - X509_V_FLAG_TRUSTED_FIRST); -#endif -#ifdef X509_V_FLAG_PARTIAL_CHAIN - if(!SSL_SET_OPTION(no_partialchain) && !ssl_crlfile) { - /* Have intermediate certificates in the trust store be treated as - trust-anchors, in the same way as self-signed root CA certificates - are. This allows users to verify servers using the intermediate cert - only, instead of needing the whole chain. - - Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we - cannot do partial chains with a CRL check. - */ - X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx), - X509_V_FLAG_PARTIAL_CHAIN); - } -#endif - } + result = set_up_x509_store(data, conn, backend); + if(result) + return result; /* OpenSSL always tries to verify the peer, this only says whether it should * fail to connect if the verification fails, or if it should continue @@ -4571,6 +4712,20 @@ static void ossl_disassociate_connection(struct Curl_easy *data, } } +static void ossl_free_multi_ssl_backend_data( + struct multi_ssl_backend_data *mbackend) +{ +#if defined(HAVE_SSL_X509_STORE_SHARE) + if(mbackend->store) { + X509_STORE_free(mbackend->store); + } + free(mbackend->CAfile); + free(mbackend); +#else /* HAVE_SSL_X509_STORE_SHARE */ + (void)mbackend; +#endif /* HAVE_SSL_X509_STORE_SHARE */ +} + const struct Curl_ssl Curl_ssl_openssl = { { CURLSSLBACKEND_OPENSSL, "openssl" }, /* info */ @@ -4611,7 +4766,8 @@ const struct Curl_ssl Curl_ssl_openssl = { NULL, /* sha256sum */ #endif ossl_associate_connection, /* associate_connection */ - ossl_disassociate_connection /* disassociate_connection */ + ossl_disassociate_connection, /* disassociate_connection */ + ossl_free_multi_ssl_backend_data /* free_multi_ssl_backend_data */ }; #endif /* USE_OPENSSL */ diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 77a49f1ab4..45433bb85f 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -630,7 +630,8 @@ const struct Curl_ssl Curl_ssl_rustls = { Curl_none_false_start, /* false_start */ NULL, /* sha256sum */ NULL, /* associate_connection */ - NULL /* disassociate_connection */ + NULL, /* disassociate_connection */ + NULL /* free_multi_ssl_backend_data */ }; #endif /* USE_RUSTLS */ diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index d5c22446e4..ed9746f531 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -2809,7 +2809,8 @@ const struct Curl_ssl Curl_ssl_schannel = { Curl_none_false_start, /* false_start */ schannel_sha256sum, /* sha256sum */ NULL, /* associate_connection */ - NULL /* disassociate_connection */ + NULL, /* disassociate_connection */ + NULL /* free_multi_ssl_backend_data */ }; #endif /* USE_SCHANNEL */ diff --git a/lib/vtls/sectransp.c b/lib/vtls/sectransp.c index 26f04ceecd..ffabf12769 100644 --- a/lib/vtls/sectransp.c +++ b/lib/vtls/sectransp.c @@ -3529,7 +3529,8 @@ const struct Curl_ssl Curl_ssl_sectransp = { sectransp_false_start, /* false_start */ sectransp_sha256sum, /* sha256sum */ NULL, /* associate_connection */ - NULL /* disassociate_connection */ + NULL, /* disassociate_connection */ + NULL /* free_multi_ssl_backend_data */ }; #ifdef __clang__ diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index cbe03f5898..468cce202a 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -655,6 +655,12 @@ void Curl_ssl_detach_conn(struct Curl_easy *data, } } +void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend) +{ + if(Curl_ssl->free_multi_ssl_backend_data && mbackend) + Curl_ssl->free_multi_ssl_backend_data(mbackend); +} + void Curl_ssl_close_all(struct Curl_easy *data) { /* kill the session ID cache if not shared */ @@ -1310,7 +1316,8 @@ static const struct Curl_ssl Curl_ssl_multi = { Curl_none_false_start, /* false_start */ NULL, /* sha256sum */ NULL, /* associate_connection */ - NULL /* disassociate_connection */ + NULL, /* disassociate_connection */ + NULL /* free_multi_ssl_backend_data */ }; const struct Curl_ssl *Curl_ssl = diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h index 50c53b3fbd..1ab90c09ed 100644 --- a/lib/vtls/vtls.h +++ b/lib/vtls/vtls.h @@ -47,6 +47,10 @@ struct ssl_connect_data; #define VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR \ ALPN_ACCEPTED "%.*s" +/* Curl_multi SSL backend-specific data; declared differently by each SSL + backend */ +struct multi_ssl_backend_data; + struct Curl_ssl { /* * This *must* be the first entry to allow returning the list of available @@ -102,6 +106,8 @@ struct Curl_ssl { struct connectdata *conn, int sockindex); void (*disassociate_connection)(struct Curl_easy *data, int sockindex); + + void (*free_multi_ssl_backend_data)(struct multi_ssl_backend_data *mbackend); }; #ifdef USE_SSL @@ -311,6 +317,8 @@ void Curl_ssl_associate_conn(struct Curl_easy *data, void Curl_ssl_detach_conn(struct Curl_easy *data, struct connectdata *conn); +void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend); + #define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */ #else /* if not USE_SSL */ diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 594c39a324..bc2a3c03f7 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -1241,7 +1241,8 @@ const struct Curl_ssl Curl_ssl_wolfssl = { Curl_none_false_start, /* false_start */ wolfssl_sha256sum, /* sha256sum */ NULL, /* associate_connection */ - NULL /* disassociate_connection */ + NULL, /* disassociate_connection */ + NULL /* free_multi_ssl_backend_data */ }; #endif