]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
schannel: add CA cache support for files and memory blobs
authorAndrew Kurushin <ajax16384@gmail.com>
Fri, 3 Nov 2023 15:25:00 +0000 (18:25 +0300)
committerJay Satiro <raysatiro@yahoo.com>
Sat, 11 Nov 2023 09:11:11 +0000 (04:11 -0500)
- 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

docs/libcurl/opts/CURLOPT_CA_CACHE_TIMEOUT.3
lib/vtls/schannel.c
lib/vtls/schannel_int.h
lib/vtls/schannel_verify.c

index 36d2bf74aa6adc76cae9d24eac04442340c229cf..cbe0db4a3571181e6a9105aae5847de4b78b8200 100644 (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"
index e93aa23899ac1f5691ae39ede7f3748766a97fea..9a192c8e9672c176ae6a7dba55056225b748d53a 100644 (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 */
 };
index a128e04f6c3a737c73fe04ec69a9ed3860bd9a27..fe7450d453ea9a5082294e0275b62e0b793755a1 100644 (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 */
index a5d5c98bb7fe33f1dd0f7f1b8b103b8a16ed6c42..d9634ea46bf5fce96f31ea4653f3c893db670a7a 100644 (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)