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

@ -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)