schannel: add CA cache support for files and memory blobs

- Support CA bundle and blob caching.

Cache timeout is 24 hours or can be set via CURLOPT_CA_CACHE_TIMEOUT.

Closes https://github.com/curl/curl/pull/12261
This commit is contained in:
Andrew Kurushin 2023-11-03 18:25:00 +03:00 committed by Jay Satiro
parent ad6fc6414d
commit 1af46f2f93
4 changed files with 204 additions and 26 deletions

View File

@ -68,8 +68,8 @@ if(curl) {
.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.
This option is supported by OpenSSL and its forks (since 7.87.0) and Schannel
(since 8.5.0).
.SH RETURN VALUE
Returns CURLE_OK
.SH "SEE ALSO"

View File

@ -2754,6 +2754,151 @@ static void *schannel_get_internals(struct ssl_connect_data *connssl,
return &backend->ctxt->ctxt_handle;
}
HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi;
const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
struct schannel_multi_ssl_backend_data *mbackend;
const struct ssl_general_config *cfg = &data->set.general_ssl;
timediff_t timeout_ms;
timediff_t elapsed_ms;
struct curltime now;
unsigned char info_blob_digest[CURL_SHA256_DIGEST_LENGTH];
DEBUGASSERT(multi);
if(!multi || !multi->ssl_backend_data) {
return NULL;
}
mbackend = (struct schannel_multi_ssl_backend_data *)multi->ssl_backend_data;
if(!mbackend->cert_store) {
return NULL;
}
/* zero ca_cache_timeout completely disables caching */
if(!cfg->ca_cache_timeout) {
return NULL;
}
/* check for cache timeout by using the cached_x509_store_expired timediff
calculation pattern from openssl.c.
negative timeout means retain forever. */
timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000;
if(timeout_ms >= 0) {
now = Curl_now();
elapsed_ms = Curl_timediff(now, mbackend->time);
if(elapsed_ms >= timeout_ms) {
return NULL;
}
}
if(ca_info_blob) {
if(!mbackend->CAinfo_blob_digest) {
return NULL;
}
if(mbackend->CAinfo_blob_size != ca_info_blob->len) {
return NULL;
}
schannel_sha256sum((const unsigned char *)ca_info_blob->data,
ca_info_blob->len,
info_blob_digest,
CURL_SHA256_DIGEST_LENGTH);
if(memcmp(mbackend->CAinfo_blob_digest,
info_blob_digest,
CURL_SHA256_DIGEST_LENGTH)) {
return NULL;
}
}
else {
if(!conn_config->CAfile || !mbackend->CAfile ||
strcmp(mbackend->CAfile, conn_config->CAfile)) {
return NULL;
}
}
return mbackend->cert_store;
}
bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf,
const struct Curl_easy *data,
HCERTSTORE cert_store)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi;
const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
struct schannel_multi_ssl_backend_data *mbackend;
unsigned char *CAinfo_blob_digest = NULL;
size_t CAinfo_blob_size = 0;
char *CAfile = NULL;
DEBUGASSERT(multi);
if(!multi) {
return false;
}
if(!multi->ssl_backend_data) {
multi->ssl_backend_data =
calloc(1, sizeof(struct schannel_multi_ssl_backend_data));
if(!multi->ssl_backend_data) {
return false;
}
}
mbackend = (struct schannel_multi_ssl_backend_data *)multi->ssl_backend_data;
if(ca_info_blob) {
CAinfo_blob_digest = malloc(CURL_SHA256_DIGEST_LENGTH);
if(!CAinfo_blob_digest) {
return false;
}
schannel_sha256sum((const unsigned char *)ca_info_blob->data,
ca_info_blob->len,
CAinfo_blob_digest,
CURL_SHA256_DIGEST_LENGTH);
CAinfo_blob_size = ca_info_blob->len;
}
else {
if(conn_config->CAfile) {
CAfile = strdup(conn_config->CAfile);
if(!CAfile) {
return false;
}
}
}
/* free old cache data */
if(mbackend->cert_store) {
CertCloseStore(mbackend->cert_store, 0);
}
free(mbackend->CAinfo_blob_digest);
free(mbackend->CAfile);
mbackend->time = Curl_now();
mbackend->cert_store = cert_store;
mbackend->CAinfo_blob_digest = CAinfo_blob_digest;
mbackend->CAinfo_blob_size = CAinfo_blob_size;
mbackend->CAfile = CAfile;
return true;
}
static void schannel_free_multi_ssl_backend_data(
struct multi_ssl_backend_data *msbd)
{
struct schannel_multi_ssl_backend_data *mbackend =
(struct schannel_multi_ssl_backend_data*)msbd;
if(mbackend->cert_store) {
CertCloseStore(mbackend->cert_store, 0);
}
free(mbackend->CAinfo_blob_digest);
free(mbackend->CAfile);
free(mbackend);
}
const struct Curl_ssl Curl_ssl_schannel = {
{ CURLSSLBACKEND_SCHANNEL, "schannel" }, /* info */
@ -2789,7 +2934,7 @@ const struct Curl_ssl Curl_ssl_schannel = {
schannel_sha256sum, /* sha256sum */
NULL, /* associate_connection */
NULL, /* disassociate_connection */
NULL, /* free_multi_ssl_backend_data */
schannel_free_multi_ssl_backend_data, /* free_multi_ssl_backend_data */
schannel_recv, /* recv decrypted data */
schannel_send, /* send data to encrypt */
};

View File

@ -149,5 +149,22 @@ struct schannel_ssl_backend_data {
#endif
};
struct schannel_multi_ssl_backend_data {
unsigned char *CAinfo_blob_digest; /* CA info blob digest */
size_t CAinfo_blob_size; /* CA info blob size */
char *CAfile; /* CAfile path used to generate
certificate store */
HCERTSTORE cert_store; /* cached certificate store or
NULL if none */
struct curltime time; /* when the cached store was created */
};
HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf,
const struct Curl_easy *data);
bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf,
const struct Curl_easy *data,
HCERTSTORE cert_store);
#endif /* USE_SCHANNEL */
#endif /* HEADER_CURL_SCHANNEL_INT_H */

View File

@ -600,6 +600,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
const CERT_CHAIN_CONTEXT *pChainContext = NULL;
HCERTCHAINENGINE cert_chain_engine = NULL;
HCERTSTORE trust_store = NULL;
HCERTSTORE own_trust_store = NULL;
DEBUGASSERT(BACKEND);
@ -630,31 +631,46 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
result = CURLE_SSL_CACERT_BADFILE;
}
else {
/* Open the certificate store */
trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY,
0,
(HCRYPTPROV)NULL,
CERT_STORE_CREATE_NEW_FLAG,
NULL);
if(!trust_store) {
char buffer[STRERROR_LEN];
failf(data, "schannel: failed to create certificate store: %s",
Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
result = CURLE_SSL_CACERT_BADFILE;
/* try cache */
trust_store = Curl_schannel_get_cached_cert_store(cf, data);
if(trust_store) {
infof(data, "schannel: reusing certificate store from cache");
}
else {
const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
if(ca_info_blob) {
result = add_certs_data_to_store(trust_store,
(const char *)ca_info_blob->data,
ca_info_blob->len,
"(memory blob)",
data);
/* Open the certificate store */
trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY,
0,
(HCRYPTPROV)NULL,
CERT_STORE_CREATE_NEW_FLAG,
NULL);
if(!trust_store) {
char buffer[STRERROR_LEN];
failf(data, "schannel: failed to create certificate store: %s",
Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
result = CURLE_SSL_CACERT_BADFILE;
}
else {
result = add_certs_file_to_store(trust_store,
conn_config->CAfile,
data);
const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
own_trust_store = trust_store;
if(ca_info_blob) {
result = add_certs_data_to_store(trust_store,
(const char *)ca_info_blob->data,
ca_info_blob->len,
"(memory blob)",
data);
}
else {
result = add_certs_file_to_store(trust_store,
conn_config->CAfile,
data);
}
if(result == CURLE_OK) {
if(Curl_schannel_set_cached_cert_store(cf, data, trust_store)) {
own_trust_store = NULL;
}
}
}
}
}
@ -754,8 +770,8 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
CertFreeCertificateChainEngine(cert_chain_engine);
}
if(trust_store) {
CertCloseStore(trust_store, 0);
if(own_trust_store) {
CertCloseStore(own_trust_store, 0);
}
if(pChainContext)