curl.h: add CURLOPT_CA_CACHE_TIMEOUT option

Adds a new option to control the maximum time that a cached
certificate store may be retained for.

Currently only the OpenSSL backend implements support for
caching certificate stores.

Closes #9620
This commit is contained in:
Michael Drake 2022-10-12 12:12:08 +01:00 committed by Daniel Stenberg
parent 3c16697ebd
commit 1fdca35ddd
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
10 changed files with 116 additions and 11 deletions

View File

@ -629,6 +629,8 @@ Path to proxy CA cert bundle. See \fICURLOPT_PROXY_CAPATH(3)\fP
Certificate Revocation List. See \fICURLOPT_CRLFILE(3)\fP
.IP CURLOPT_PROXY_CRLFILE
Proxy Certificate Revocation List. See \fICURLOPT_PROXY_CRLFILE(3)\fP
.IP CURLOPT_CA_CACHE_TIMEOUT
Timeout for CA cache. See \fICURLOPT_CA_CACHE_TIMEOUT(3)\fP
.IP CURLOPT_CERTINFO
Extract certificate info. See \fICURLOPT_CERTINFO(3)\fP
.IP CURLOPT_PINNEDPUBLICKEY

View File

@ -0,0 +1,76 @@
.\" **************************************************************************
.\" * _ _ ____ _
.\" * Project ___| | | | _ \| |
.\" * / __| | | | |_) | |
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
.\" * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
.\" *
.\" * This software is licensed as described in the file COPYING, which
.\" * you should have received as part of this distribution. The terms
.\" * are also available at https://curl.se/docs/copyright.html.
.\" *
.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
.\" * copies of the Software, and permit persons to whom the Software is
.\" * furnished to do so, under the terms of the COPYING file.
.\" *
.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
.\" * KIND, either express or implied.
.\" *
.\" * SPDX-License-Identifier: curl
.\" *
.\" **************************************************************************
.\"
.TH CURLOPT_CA_CACHE_TIMEOUT 3 "21 Dec 2022" "libcurl 7.87.0" "curl_easy_setopt options"
.SH NAME
CURLOPT_CA_CACHE_TIMEOUT \- life-time for cached certificate stores
.SH SYNOPSIS
.nf
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_CA_CACHE_TIMEOUT, long age);
.fi
.SH DESCRIPTION
Pass a long, this sets the timeout in seconds. This tells libcurl the maximum
time any cached certificate store it has in memory may be kept and reused for
new connections. Once the timeout has expired, a subsequent fetch requiring a
certificate store will have to build a new one.
Building a certificate store from a \fICURLOPT_CAINFO\fP file is a slow
operation so curl may cache the generated certificate store internally to speed
up future connections.
Set to zero to completely disable caching, or set to -1 to retain the cached
store remain forever. By default, libcurl caches this info for 24 hours.
.SH DEFAULT
86400 (24 hours)
.SH PROTOCOLS
All
.SH EXAMPLE
.nf
CURL *curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/foo.bin");
/* only reuse certificate stores for a short time */
curl_easy_setopt(curl, CURLOPT_CA_CACHE_TIMEOUT, 60L);
ret = curl_easy_perform(curl);
/* in this second request, the cache will not be used if more than
sixty seconds have passed since the previous connection */
ret = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
.fi
.SH AVAILABILITY
This option was added in curl 7.87.0.
Currently the only SSL backend to implement this certificate store caching
functionality is the OpenSSL (and forks) backend.
.SH RETURN VALUE
Returns CURLE_OK
.SH "SEE ALSO"
.BR CURLOPT_CAINFO "(3), "

View File

@ -122,6 +122,7 @@ man_MANS = \
CURLOPT_CAINFO.3 \
CURLOPT_CAINFO_BLOB.3 \
CURLOPT_CAPATH.3 \
CURLOPT_CA_CACHE_TIMEOUT.3 \
CURLOPT_CERTINFO.3 \
CURLOPT_CHUNK_BGN_FUNCTION.3 \
CURLOPT_CHUNK_DATA.3 \

View File

@ -565,6 +565,7 @@ CURLOPT_BUFFERSIZE 7.10
CURLOPT_CAINFO 7.4.2
CURLOPT_CAINFO_BLOB 7.77.0
CURLOPT_CAPATH 7.9.8
CURLOPT_CA_CACHE_TIMEOUT 7.87.0
CURLOPT_CERTINFO 7.19.1
CURLOPT_CHUNK_BGN_FUNCTION 7.21.0
CURLOPT_CHUNK_DATA 7.21.0

View File

@ -2157,6 +2157,9 @@ typedef enum {
/* websockets options */
CURLOPT(CURLOPT_WS_OPTIONS, CURLOPTTYPE_LONG, 320),
/* CA cache timeout */
CURLOPT(CURLOPT_CA_CACHE_TIMEOUT, CURLOPTTYPE_LONG, 321),
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;

View File

@ -42,6 +42,7 @@ struct curl_easyoption Curl_easyopts[] = {
{"CAINFO", CURLOPT_CAINFO, CURLOT_STRING, 0},
{"CAINFO_BLOB", CURLOPT_CAINFO_BLOB, CURLOT_BLOB, 0},
{"CAPATH", CURLOPT_CAPATH, CURLOT_STRING, 0},
{"CA_CACHE_TIMEOUT", CURLOPT_CA_CACHE_TIMEOUT, CURLOT_LONG, 0},
{"CERTINFO", CURLOPT_CERTINFO, CURLOT_LONG, 0},
{"CHUNK_BGN_FUNCTION", CURLOPT_CHUNK_BGN_FUNCTION, CURLOT_FUNCTION, 0},
{"CHUNK_DATA", CURLOPT_CHUNK_DATA, CURLOT_CBPTR, 0},
@ -368,6 +369,6 @@ struct curl_easyoption Curl_easyopts[] = {
*/
int Curl_easyopts_check(void)
{
return ((CURLOPT_LASTENTRY%10000) != (320 + 1));
return ((CURLOPT_LASTENTRY%10000) != (321 + 1));
}
#endif

View File

@ -204,6 +204,15 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
data->set.dns_cache_timeout = (int)arg;
break;
case CURLOPT_CA_CACHE_TIMEOUT:
arg = va_arg(param, long);
if(arg < -1)
return CURLE_BAD_FUNCTION_ARGUMENT;
else if(arg > INT_MAX)
arg = INT_MAX;
data->set.general_ssl.ca_cache_timeout = (int)arg;
break;
case CURLOPT_DNS_USE_GLOBAL_CACHE:
/* deprecated */
break;

View File

@ -539,6 +539,8 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
/* Set the default size of the SSL session ID cache */
set->general_ssl.max_ssl_sessions = 5;
/* Timeout every 24 hours by default */
set->general_ssl.ca_cache_timeout = 24 * 60 * 60;
set->proxyport = 0;
set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */

View File

@ -313,6 +313,7 @@ struct ssl_config_data {
struct ssl_general_config {
size_t max_ssl_sessions; /* SSL session id cache size */
int ca_cache_timeout; /* Certificate store cache timeout (seconds) */
};
/* information stored about one single SSL session */

View File

@ -3164,12 +3164,18 @@ static CURLcode populate_x509_store(struct Curl_easy *data,
}
#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)
static bool cached_x509_store_expired(const struct Curl_easy *data,
const struct multi_ssl_backend_data *mb)
{
const struct ssl_general_config *cfg = &data->set.general_ssl;
struct curltime now = Curl_now();
timediff_t elapsed_ms = Curl_timediff(now, mb->time);
timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000;
return Curl_timediff(now, mb->time) >= X509_STORE_EXPIRY_MS;
if(timeout_ms < 0)
return false;
return elapsed_ms >= timeout_ms;
}
static bool cached_x509_store_different(
@ -3191,7 +3197,7 @@ static X509_STORE *get_cached_x509_store(const struct Curl_easy *data,
if(multi &&
multi->ssl_backend_data &&
multi->ssl_backend_data->store &&
!cached_x509_store_expired(multi->ssl_backend_data) &&
!cached_x509_store_expired(data, multi->ssl_backend_data) &&
!cached_x509_store_different(multi->ssl_backend_data, conn)) {
store = multi->ssl_backend_data->store;
}
@ -3244,17 +3250,20 @@ static CURLcode set_up_x509_store(struct Curl_easy *data,
struct ssl_backend_data *backend)
{
CURLcode result = CURLE_OK;
X509_STORE *cached_store = get_cached_x509_store(data, conn);
X509_STORE *cached_store;
bool cache_criteria_met;
/* 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);
cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) &&
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);
cached_store = get_cached_x509_store(data, conn);
if(cached_store && cache_criteria_met && X509_STORE_up_ref(cached_store)) {
SSL_CTX_set_cert_store(backend->ctx, cached_store);
}