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:
parent
ad6fc6414d
commit
1af46f2f93
@ -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"
|
||||
|
||||
@ -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 */
|
||||
};
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user