]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
wolfssl: support CA caching
authorDaniel Stenberg <daniel@haxx.se>
Sat, 1 Jun 2024 10:24:42 +0000 (12:24 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Sat, 1 Jun 2024 21:50:36 +0000 (23:50 +0200)
As a bonus, add SSLSUPP_CA_CACHE to let TLS backends signal its support
for this so that *setopt() return error if there is no support.

Closes #13786

docs/libcurl/opts/CURLOPT_CA_CACHE_TIMEOUT.md
lib/setopt.c
lib/vquic/curl_ngtcp2.c
lib/vquic/vquic-tls.c
lib/vquic/vquic-tls.h
lib/vtls/openssl.c
lib/vtls/schannel.c
lib/vtls/vtls.c
lib/vtls/vtls.h
lib/vtls/wolfssl.c
lib/vtls/wolfssl.h

index aa479b6bec2d042b91d8b922f06f8bebc4e233ed..e9ad726446f874922293faa85113427336797aa2 100644 (file)
@@ -14,6 +14,8 @@ Protocol:
   - TLS
 TLS-backend:
   - OpenSSL
+  - Schannel
+  - wolfSSL
 ---
 
 # NAME
@@ -31,16 +33,17 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_CA_CACHE_TIMEOUT, long age);
 # 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 has to reload it.
+time any cached CA certificate store it has in memory may be kept and reused
+for new connections. Once the timeout has expired, a subsequent fetch
+requiring a CA certificate has to reload it.
 
-Building a certificate store from a CURLOPT_CAINFO(3) file is a slow
-operation so curl may cache the generated certificate store internally to speed
-up future connections.
+Building a CA certificate store from a CURLOPT_CAINFO(3) 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.
+Set the timeout 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.
 
 # DEFAULT
 
@@ -74,8 +77,8 @@ int main(void)
 
 This option was added in curl 7.87.0.
 
-This option is supported by OpenSSL and its forks (since 7.87.0) and Schannel
-(since 8.5.0).
+This option is supported by OpenSSL and its forks (since 7.87.0), Schannel
+(since 8.5.0) and wolfSSL (since 8.9.0).
 
 # RETURN VALUE
 
index e8b25454b2f9df4f426d64a4a3f6e715e9e3ecff..4e4da969fd6a14299151985343a2b1b3164a67f3 100644 (file)
@@ -203,13 +203,17 @@ 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;
+    if(Curl_ssl_supports(data, SSLSUPP_CA_CACHE)) {
+      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;
+      data->set.general_ssl.ca_cache_timeout = (int)arg;
+    }
+    else
+      return CURLE_NOT_BUILT_IN;
     break;
   case CURLOPT_DNS_USE_GLOBAL_CACHE:
     /* deprecated */
index 0d9d87f34affbbec319368ac994820d464aa234a..bac69894b82e1647da28553da709075147ced954 100644 (file)
@@ -2120,7 +2120,7 @@ static CURLcode tls_ctx_setup(struct Curl_cfilter *cf,
     return CURLE_FAILED_INIT;
   }
 #elif defined(USE_WOLFSSL)
-  if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->ssl_ctx) != 0) {
+  if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ctx) != 0) {
     failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");
     return CURLE_FAILED_INIT;
   }
@@ -2211,7 +2211,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
 #elif defined(USE_GNUTLS)
   ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls.session);
 #else
-  ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ssl);
+  ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.wssl.handle);
 #endif
 
   ngtcp2_ccerr_default(&ctx->last_error);
index aca18b457035176575d53a7710f18c7256527a70..51fffbac8f790447887f35a8eaa87c52fdd72d8c 100644 (file)
@@ -76,7 +76,7 @@ static void keylog_callback(const WOLFSSL *ssl, const char *line)
 }
 #endif
 
-static CURLcode curl_wssl_init_ctx(struct curl_tls_ctx *ctx,
+static CURLcode Curl_wssl_init_ctx(struct curl_tls_ctx *ctx,
                                    struct Curl_cfilter *cf,
                                    struct Curl_easy *data,
                                    Curl_vquic_tls_ctx_setup *cb_setup,
@@ -91,8 +91,8 @@ static CURLcode curl_wssl_init_ctx(struct curl_tls_ctx *ctx,
     goto out;
   }
 
-  ctx->ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
-  if(!ctx->ssl_ctx) {
+  ctx->wssl.ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
+  if(!ctx->wssl.ctx) {
     result = CURLE_OUT_OF_MEMORY;
     goto out;
   }
@@ -103,9 +103,9 @@ static CURLcode curl_wssl_init_ctx(struct curl_tls_ctx *ctx,
       goto out;
   }
 
-  wolfSSL_CTX_set_default_verify_paths(ctx->ssl_ctx);
+  wolfSSL_CTX_set_default_verify_paths(ctx->wssl.ctx);
 
-  if(wolfSSL_CTX_set_cipher_list(ctx->ssl_ctx, conn_config->cipher_list13 ?
+  if(wolfSSL_CTX_set_cipher_list(ctx->wssl.ctx, conn_config->cipher_list13 ?
                                  conn_config->cipher_list13 :
                                  QUIC_CIPHERS) != 1) {
     char error_buffer[256];
@@ -115,7 +115,7 @@ static CURLcode curl_wssl_init_ctx(struct curl_tls_ctx *ctx,
     goto out;
   }
 
-  if(wolfSSL_CTX_set1_groups_list(ctx->ssl_ctx, conn_config->curves ?
+  if(wolfSSL_CTX_set1_groups_list(ctx->wssl.ctx, conn_config->curves ?
                                   conn_config->curves :
                                   (char *)QUIC_GROUPS) != 1) {
     failf(data, "wolfSSL failed to set curves");
@@ -127,7 +127,7 @@ static CURLcode curl_wssl_init_ctx(struct curl_tls_ctx *ctx,
   Curl_tls_keylog_open();
   if(Curl_tls_keylog_enabled()) {
 #if defined(HAVE_SECRET_CALLBACK)
-    wolfSSL_CTX_set_keylog_callback(ctx->ssl_ctx, keylog_callback);
+    wolfSSL_CTX_set_keylog_callback(ctx->wssl.ctx, keylog_callback);
 #else
     failf(data, "wolfSSL was built without keylog callback");
     result = CURLE_NOT_BUILT_IN;
@@ -139,12 +139,12 @@ static CURLcode curl_wssl_init_ctx(struct curl_tls_ctx *ctx,
     const char * const ssl_cafile = conn_config->CAfile;
     const char * const ssl_capath = conn_config->CApath;
 
-    wolfSSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL);
+    wolfSSL_CTX_set_verify(ctx->wssl.ctx, SSL_VERIFY_PEER, NULL);
     if(ssl_cafile || ssl_capath) {
       /* tell wolfSSL where to find CA certificates that are used to verify
          the server's certificate. */
       int rc =
-        wolfSSL_CTX_load_verify_locations_ex(ctx->ssl_ctx, ssl_cafile,
+        wolfSSL_CTX_load_verify_locations_ex(ctx->wssl.ctx, ssl_cafile,
                                              ssl_capath,
                                              WOLFSSL_LOAD_FLAG_IGNORE_ERR);
       if(SSL_SUCCESS != rc) {
@@ -163,18 +163,18 @@ static CURLcode curl_wssl_init_ctx(struct curl_tls_ctx *ctx,
     else {
       /* verifying the peer without any CA certificates won't work so
          use wolfssl's built-in default as fallback */
-      wolfSSL_CTX_set_default_verify_paths(ctx->ssl_ctx);
+      wolfSSL_CTX_set_default_verify_paths(ctx->wssl.ctx);
     }
 #endif
   }
   else {
-    wolfSSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_NONE, NULL);
+    wolfSSL_CTX_set_verify(ctx->wssl.ctx, SSL_VERIFY_NONE, NULL);
   }
 
   /* give application a chance to interfere with SSL set up. */
   if(data->set.ssl.fsslctx) {
     Curl_set_in_callback(data, true);
-    result = (*data->set.ssl.fsslctx)(data, ctx->ssl_ctx,
+    result = (*data->set.ssl.fsslctx)(data, ctx->wssl.ctx,
                                       data->set.ssl.fsslctxp);
     Curl_set_in_callback(data, false);
     if(result) {
@@ -185,36 +185,36 @@ static CURLcode curl_wssl_init_ctx(struct curl_tls_ctx *ctx,
   result = CURLE_OK;
 
 out:
-  if(result && ctx->ssl_ctx) {
-    SSL_CTX_free(ctx->ssl_ctx);
-    ctx->ssl_ctx = NULL;
+  if(result && ctx->wssl.ctx) {
+    SSL_CTX_free(ctx->wssl.ctx);
+    ctx->wssl.ctx = NULL;
   }
   return result;
 }
 
 /** SSL callbacks ***/
 
-static CURLcode curl_wssl_init_ssl(struct curl_tls_ctx *ctx,
+static CURLcode Curl_wssl_init_ssl(struct curl_tls_ctx *ctx,
                                    struct Curl_easy *data,
                                    struct ssl_peer *peer,
                                    const char *alpn, size_t alpn_len,
                                    void *user_data)
 {
   (void)data;
-  DEBUGASSERT(!ctx->ssl);
-  DEBUGASSERT(ctx->ssl_ctx);
-  ctx->ssl = wolfSSL_new(ctx->ssl_ctx);
+  DEBUGASSERT(!ctx->wssl.handle);
+  DEBUGASSERT(ctx->wssl.ctx);
+  ctx->wssl.handle = wolfSSL_new(ctx->wssl.ctx);
 
-  wolfSSL_set_app_data(ctx->ssl, user_data);
-  wolfSSL_set_connect_state(ctx->ssl);
-  wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0);
+  wolfSSL_set_app_data(ctx->wssl.handle, user_data);
+  wolfSSL_set_connect_state(ctx->wssl.handle);
+  wolfSSL_set_quic_use_legacy_codepoint(ctx->wssl.handle, 0);
 
   if(alpn)
-    wolfSSL_set_alpn_protos(ctx->ssl, (const unsigned char *)alpn,
+    wolfSSL_set_alpn_protos(ctx->wssl.handle, (const unsigned char *)alpn,
                             (int)alpn_len);
 
   if(peer->sni) {
-    wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME,
+    wolfSSL_UseSNI(ctx->wssl.handle, WOLFSSL_SNI_HOST_NAME,
                    peer->sni, (unsigned short)strlen(peer->sni));
   }
 
@@ -243,11 +243,11 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
                             (const unsigned char *)alpn, alpn_len,
                             cb_setup, cb_user_data, ssl_user_data);
 #elif defined(USE_WOLFSSL)
-  result = curl_wssl_init_ctx(ctx, cf, data, cb_setup, cb_user_data);
+  result = Curl_wssl_init_ctx(ctx, cf, data, cb_setup, cb_user_data);
   if(result)
     return result;
 
-  return curl_wssl_init_ssl(ctx, data, peer, alpn, alpn_len, ssl_user_data);
+  return Curl_wssl_init_ssl(ctx, data, peer, alpn, alpn_len, ssl_user_data);
 #else
 #error "no TLS lib in used, should not happen"
   return CURLE_FAILED_INIT;
@@ -267,10 +267,10 @@ void Curl_vquic_tls_cleanup(struct curl_tls_ctx *ctx)
   if(ctx->gtls.session)
     gnutls_deinit(ctx->gtls.session);
 #elif defined(USE_WOLFSSL)
-  if(ctx->ssl)
-    wolfSSL_free(ctx->ssl);
-  if(ctx->ssl_ctx)
-    wolfSSL_CTX_free(ctx->ssl_ctx);
+  if(ctx->wssl.handle)
+    wolfSSL_free(ctx->wssl.handle);
+  if(ctx->wssl.ctx)
+    wolfSSL_CTX_free(ctx->wssl.ctx);
 #endif
   memset(ctx, 0, sizeof(*ctx));
 }
@@ -286,6 +286,12 @@ CURLcode Curl_vquic_tls_before_recv(struct curl_tls_ctx *ctx,
       return result;
     ctx->ossl.x509_store_setup = TRUE;
   }
+#elif defined(USE_WOLFSSL)
+  if(!ctx->wssl.x509_store_setup) {
+    CURLcode result = Curl_wssl_setup_x509_store(cf, data, &ctx->wssl);
+    if(result)
+      return result;
+  }
 #elif defined(USE_GNUTLS)
   if(!ctx->gtls.trust_setup) {
     CURLcode result = Curl_gtls_client_trust_setup(cf, data, &ctx->gtls);
@@ -325,7 +331,7 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx,
   (void)data;
   if(conn_config->verifyhost) {
     if(peer->sni) {
-      WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(ctx->ssl);
+      WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(ctx->wssl.handle);
       if(wolfSSL_X509_check_host(cert, peer->sni, strlen(peer->sni), 0, NULL)
             == WOLFSSL_FAILURE) {
         result = CURLE_PEER_FAILED_VERIFICATION;
index 1d35fd0e62b89cf62b5f4d9d5f3b1609d110209b..0ec74bfbae3acbec65d1952d39fe518e34b2ca44 100644 (file)
 #if defined(USE_HTTP3) && \
   (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL))
 
+#include "vtls/wolfssl.h"
+
 struct curl_tls_ctx {
 #ifdef USE_OPENSSL
   struct ossl_ctx ossl;
 #elif defined(USE_GNUTLS)
   struct gtls_ctx gtls;
 #elif defined(USE_WOLFSSL)
-  WOLFSSL_CTX *ssl_ctx;
-  WOLFSSL *ssl;
+  struct wolfssl_ctx wssl;
 #endif
 };
 
index c9120cd37fe71479be96fbb873d3f85ffc80bf9a..3618e7de1590f1c04fbe122e59a2f26ef97995f7 100644 (file)
@@ -2849,8 +2849,8 @@ typedef long ctx_option_t;
 #if (OPENSSL_VERSION_NUMBER < 0x10100000L) /* 1.1.0 */
 static CURLcode
 ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options,
-                                       struct Curl_cfilter *cf,
-                                       struct Curl_easy *data)
+                                    struct Curl_cfilter *cf,
+                                    struct Curl_easy *data)
 {
   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
   long ssl_version = conn_config->version;
@@ -5273,6 +5273,7 @@ const struct Curl_ssl Curl_ssl_openssl = {
 #ifdef USE_ECH
   SSLSUPP_ECH |
 #endif
+  SSLSUPP_CA_CACHE |
   SSLSUPP_HTTPS_PROXY,
 
   sizeof(struct ossl_ctx),
index b4fb3b7e5b7014702909e7d01764069e67e10929..414907b16cfaaabe50674d6f3100fab6ceb20416 100644 (file)
@@ -2910,6 +2910,7 @@ const struct Curl_ssl Curl_ssl_schannel = {
 #endif
   SSLSUPP_PINNEDPUBKEY |
   SSLSUPP_TLS13_CIPHERSUITES |
+  SSLSUPP_CA_CACHE |
   SSLSUPP_HTTPS_PROXY,
 
   sizeof(struct schannel_ssl_backend_data),
index fcf17b82435e844ac97abb995e512786706e19f7..aa190b78092ce53c309cd712335b857f081df108 100644 (file)
@@ -453,7 +453,7 @@ static bool ssl_prefs_check(struct Curl_easy *data)
 }
 
 static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data,
-                                     const struct alpn_spec *alpn)
+                                           const struct alpn_spec *alpn)
 {
   struct ssl_connect_data *ctx;
 
index ab667795858309c4d6273a5a0ba38d669652b76d..813bf2ada7b816a8c661ba90e58ffcd1d661e4d3 100644 (file)
@@ -38,6 +38,7 @@ struct Curl_ssl_session;
 #define SSLSUPP_TLS13_CIPHERSUITES (1<<5) /* supports TLS 1.3 ciphersuites */
 #define SSLSUPP_CAINFO_BLOB  (1<<6)
 #define SSLSUPP_ECH          (1<<7)
+#define SSLSUPP_CA_CACHE     (1<<8)
 
 #define ALPN_ACCEPTED "ALPN: server accepted "
 
index 4acd354ffc229d2340bd659658134dc43c99a6b8..5c8dcd96fd79ffce469829911b238ce940661b18 100644 (file)
 #undef USE_BIO_CHAIN
 #endif
 
-struct wolfssl_ssl_backend_data {
-  WOLFSSL_CTX *ctx;
-  WOLFSSL     *handle;
-  CURLcode    io_result;   /* result of last BIO cfilter operation */
-};
-
 #ifdef OPENSSL_EXTRA
 /*
  * Availability note:
@@ -290,8 +284,8 @@ static int wolfssl_bio_cf_out_write(WOLFSSL_BIO *bio,
 {
   struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio);
   struct ssl_connect_data *connssl = cf->ctx;
-  struct wolfssl_ssl_backend_data *backend =
-    (struct wolfssl_ssl_backend_data *)connssl->backend;
+  struct wolfssl_ctx *backend =
+    (struct wolfssl_ctx *)connssl->backend;
   struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nwritten;
   CURLcode result = CURLE_OK;
@@ -311,8 +305,8 @@ static int wolfssl_bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen)
 {
   struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio);
   struct ssl_connect_data *connssl = cf->ctx;
-  struct wolfssl_ssl_backend_data *backend =
-    (struct wolfssl_ssl_backend_data *)connssl->backend;
+  struct wolfssl_ctx *backend =
+    (struct wolfssl_ctx *)connssl->backend;
   struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nread;
   CURLcode result = CURLE_OK;
@@ -357,6 +351,252 @@ static void wolfssl_bio_cf_free_methods(void)
 
 #endif /* !USE_BIO_CHAIN */
 
+static CURLcode populate_x509_store(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    X509_STORE *store,
+                                    struct wolfssl_ctx *wssl)
+{
+  struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+  const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
+  const char * const ssl_cafile =
+    /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
+    (ca_info_blob ? NULL : conn_config->CAfile);
+  const char * const ssl_capath = conn_config->CApath;
+  struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+  bool imported_native_ca = false;
+
+#if !defined(NO_FILESYSTEM) && defined(WOLFSSL_SYS_CA_CERTS)
+  /* load native CA certificates */
+  if(ssl_config->native_ca_store) {
+    if(wolfSSL_CTX_load_system_CA_certs(wssl->ctx) != WOLFSSL_SUCCESS) {
+      infof(data, "error importing native CA store, continuing anyway");
+    }
+    else {
+      imported_native_ca = true;
+      infof(data, "successfully imported native CA store");
+      wssl->x509_store_setup = TRUE;
+    }
+  }
+#endif /* !NO_FILESYSTEM */
+
+  /* load certificate blob */
+  if(ca_info_blob) {
+    if(wolfSSL_CTX_load_verify_buffer(wssl->ctx, ca_info_blob->data,
+                                      ca_info_blob->len,
+                                      SSL_FILETYPE_PEM) != SSL_SUCCESS) {
+      if(imported_native_ca) {
+        infof(data, "error importing CA certificate blob, continuing anyway");
+      }
+      else {
+        failf(data, "error importing CA certificate blob");
+        return CURLE_SSL_CACERT_BADFILE;
+      }
+    }
+    else {
+      infof(data, "successfully imported CA certificate blob");
+      wssl->x509_store_setup = TRUE;
+    }
+  }
+
+#ifndef NO_FILESYSTEM
+  /* load trusted cacert from file if not blob */
+
+  CURL_TRC_CF(data, cf, "populate_x509_store, path=%s, blob=%d",
+              ssl_cafile? ssl_cafile : "none", !!ca_info_blob);
+  if(!store)
+    return CURLE_OUT_OF_MEMORY;
+
+  if((ssl_cafile || ssl_capath) && (!wssl->x509_store_setup)) {
+    int rc =
+      wolfSSL_CTX_load_verify_locations_ex(wssl->ctx,
+                                           ssl_cafile,
+                                           ssl_capath,
+                                           WOLFSSL_LOAD_FLAG_IGNORE_ERR);
+    if(SSL_SUCCESS != rc) {
+      if(conn_config->verifypeer) {
+        /* Fail if we insist on successfully verifying the server. */
+        failf(data, "error setting certificate verify locations:"
+              " CAfile: %s CApath: %s",
+              ssl_cafile ? ssl_cafile : "none",
+              ssl_capath ? ssl_capath : "none");
+        return CURLE_SSL_CACERT_BADFILE;
+      }
+      else {
+        /* Just continue with a warning if no strict certificate
+           verification is required. */
+        infof(data, "error setting certificate verify locations,"
+              " continuing anyway:");
+      }
+    }
+    else {
+      /* Everything is fine. */
+      infof(data, "successfully set certificate verify locations:");
+    }
+    infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
+    infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
+    wssl->x509_store_setup = TRUE;
+  }
+#endif
+  (void)store;
+  return CURLE_OK;
+}
+
+/* key to use at `multi->proto_hash` */
+#define MPROTO_WSSL_X509_KEY   "tls:wssl:x509:share"
+
+struct wssl_x509_share {
+  char *CAfile;         /* CAfile path used to generate X509 store */
+  WOLFSSL_X509_STORE *store; /* cached X509 store or NULL if none */
+  struct curltime time; /* when the cached store was created */
+};
+
+static void wssl_x509_share_free(void *key, size_t key_len, void *p)
+{
+  struct wssl_x509_share *share = p;
+  DEBUGASSERT(key_len == (sizeof(MPROTO_WSSL_X509_KEY)-1));
+  DEBUGASSERT(!memcmp(MPROTO_WSSL_X509_KEY, key, key_len));
+  (void)key;
+  (void)key_len;
+  if(share->store) {
+    wolfSSL_X509_STORE_free(share->store);
+  }
+  free(share->CAfile);
+  free(share);
+}
+
+static bool
+cached_x509_store_expired(const struct Curl_easy *data,
+                          const struct wssl_x509_share *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;
+
+  if(timeout_ms < 0)
+    return false;
+
+  return elapsed_ms >= timeout_ms;
+}
+
+static bool
+cached_x509_store_different(struct Curl_cfilter *cf,
+                            const struct wssl_x509_share *mb)
+{
+  struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+  if(!mb->CAfile || !conn_config->CAfile)
+    return mb->CAfile != conn_config->CAfile;
+
+  return strcmp(mb->CAfile, conn_config->CAfile);
+}
+
+static X509_STORE *get_cached_x509_store(struct Curl_cfilter *cf,
+                                         const struct Curl_easy *data)
+{
+  struct Curl_multi *multi = data->multi;
+  struct wssl_x509_share *share;
+  WOLFSSL_X509_STORE *store = NULL;
+
+  DEBUGASSERT(multi);
+  share = multi? Curl_hash_pick(&multi->proto_hash,
+                                (void *)MPROTO_WSSL_X509_KEY,
+                                sizeof(MPROTO_WSSL_X509_KEY)-1) : NULL;
+  if(share && share->store &&
+     !cached_x509_store_expired(data, share) &&
+     !cached_x509_store_different(cf, share)) {
+    store = share->store;
+  }
+
+  return store;
+}
+
+static void set_cached_x509_store(struct Curl_cfilter *cf,
+                                  const struct Curl_easy *data,
+                                  X509_STORE *store)
+{
+  struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+  struct Curl_multi *multi = data->multi;
+  struct wssl_x509_share *share;
+
+  DEBUGASSERT(multi);
+  if(!multi)
+    return;
+  share = Curl_hash_pick(&multi->proto_hash,
+                         (void *)MPROTO_WSSL_X509_KEY,
+                         sizeof(MPROTO_WSSL_X509_KEY)-1);
+
+  if(!share) {
+    share = calloc(1, sizeof(*share));
+    if(!share)
+      return;
+    if(!Curl_hash_add2(&multi->proto_hash,
+                       (void *)MPROTO_WSSL_X509_KEY,
+                       sizeof(MPROTO_WSSL_X509_KEY)-1,
+                       share, wssl_x509_share_free)) {
+      free(share);
+      return;
+    }
+  }
+
+  if(wolfSSL_X509_STORE_up_ref(store)) {
+    char *CAfile = NULL;
+
+    if(conn_config->CAfile) {
+      CAfile = strdup(conn_config->CAfile);
+      if(!CAfile) {
+        X509_STORE_free(store);
+        return;
+      }
+    }
+
+    if(share->store) {
+      X509_STORE_free(share->store);
+      free(share->CAfile);
+    }
+
+    share->time = Curl_now();
+    share->store = store;
+    share->CAfile = CAfile;
+  }
+}
+
+CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    struct wolfssl_ctx *wssl)
+{
+  struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+  struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+  CURLcode result = CURLE_OK;
+  WOLFSSL_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. */
+  cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) &&
+    conn_config->verifypeer &&
+    !conn_config->CApath &&
+    !conn_config->ca_info_blob &&
+    !ssl_config->primary.CRLfile &&
+    !ssl_config->native_ca_store;
+
+  cached_store = get_cached_x509_store(cf, data);
+  if(cached_store && cache_criteria_met
+     && wolfSSL_X509_STORE_up_ref(cached_store)) {
+    wolfSSL_CTX_set_cert_store(wssl->ctx, cached_store);
+  }
+  else {
+    X509_STORE *store = wolfSSL_CTX_get_cert_store(wssl->ctx);
+
+    result = populate_x509_store(cf, data, store, wssl);
+    if(result == CURLE_OK && cache_criteria_met) {
+      set_cached_x509_store(cf, data, store);
+    }
+  }
+
+  return result;
+}
+
 /*
  * This function loads all the client/CA certificates and CRLs. Setup the TLS
  * layer and do all necessary magic.
@@ -366,15 +606,10 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   char *ciphers, *curves;
   struct ssl_connect_data *connssl = cf->ctx;
-  struct wolfssl_ssl_backend_data *backend =
-    (struct wolfssl_ssl_backend_data *)connssl->backend;
+  struct wolfssl_ctx *backend =
+    (struct wolfssl_ctx *)connssl->backend;
   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
-  const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
   const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
-  const char * const ssl_cafile =
-    /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
-    (ca_info_blob ? NULL : conn_config->CAfile);
-  const char * const ssl_capath = conn_config->CApath;
   WOLFSSL_METHOD* req_method = NULL;
 #ifdef HAVE_LIBOQS
   word16 oqsAlg = 0;
@@ -386,8 +621,6 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
 #else
 #define use_sni(x)  Curl_nop_stmt
 #endif
-  bool imported_native_ca = false;
-  bool imported_ca_info_blob = false;
 
   DEBUGASSERT(backend);
 
@@ -524,71 +757,7 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
     }
   }
 
-#if !defined(NO_FILESYSTEM) && defined(WOLFSSL_SYS_CA_CERTS)
-  /* load native CA certificates */
-  if(ssl_config->native_ca_store) {
-    if(wolfSSL_CTX_load_system_CA_certs(backend->ctx) != WOLFSSL_SUCCESS) {
-      infof(data, "error importing native CA store, continuing anyway");
-    }
-    else {
-      imported_native_ca = true;
-      infof(data, "successfully imported native CA store");
-    }
-  }
-#endif /* !NO_FILESYSTEM */
-
-  /* load certificate blob */
-  if(ca_info_blob) {
-    if(wolfSSL_CTX_load_verify_buffer(backend->ctx, ca_info_blob->data,
-                                      ca_info_blob->len,
-                                      SSL_FILETYPE_PEM) != SSL_SUCCESS) {
-      if(imported_native_ca) {
-        infof(data, "error importing CA certificate blob, continuing anyway");
-      }
-      else {
-        failf(data, "error importing CA certificate blob");
-        return CURLE_SSL_CACERT_BADFILE;
-      }
-    }
-    else {
-      imported_ca_info_blob = true;
-      infof(data, "successfully imported CA certificate blob");
-    }
-  }
-
 #ifndef NO_FILESYSTEM
-  /* load trusted cacert from file if not blob */
-  if(ssl_cafile || ssl_capath) {
-    int rc =
-      wolfSSL_CTX_load_verify_locations_ex(backend->ctx,
-                                           ssl_cafile,
-                                           ssl_capath,
-                                           WOLFSSL_LOAD_FLAG_IGNORE_ERR);
-    if(SSL_SUCCESS != rc) {
-      if(conn_config->verifypeer && !imported_ca_info_blob &&
-         !imported_native_ca) {
-        /* Fail if we insist on successfully verifying the server. */
-        failf(data, "error setting certificate verify locations:"
-              " CAfile: %s CApath: %s",
-              ssl_cafile ? ssl_cafile : "none",
-              ssl_capath ? ssl_capath : "none");
-        return CURLE_SSL_CACERT_BADFILE;
-      }
-      else {
-        /* Just continue with a warning if no strict certificate
-           verification is required. */
-        infof(data, "error setting certificate verify locations,"
-              " continuing anyway:");
-      }
-    }
-    else {
-      /* Everything is fine. */
-      infof(data, "successfully set certificate verify locations:");
-    }
-    infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
-    infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
-  }
-
   /* Load the client certificate, and private key */
   if(ssl_config->primary.clientcert && ssl_config->key) {
     int file_type = do_file_type(ssl_config->cert_type);
@@ -839,8 +1008,8 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   int ret = -1;
   struct ssl_connect_data *connssl = cf->ctx;
-  struct wolfssl_ssl_backend_data *backend =
-    (struct wolfssl_ssl_backend_data *)connssl->backend;
+  struct wolfssl_ctx *backend =
+    (struct wolfssl_ctx *)connssl->backend;
   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
 #ifndef CURL_DISABLE_PROXY
   const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf)?
@@ -862,6 +1031,21 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
       return CURLE_SSL_CONNECT_ERROR;
   }
 
+  if(!backend->x509_store_setup) {
+    /* After having send off the ClientHello, we prepare the x509
+     * store to verify the coming certificate from the server */
+    CURLcode result;
+    struct wolfssl_ctx wssl;
+    wssl.ctx = backend->ctx;
+    wssl.handle = backend->handle;
+    wssl.io_result = CURLE_OK;
+    wssl.x509_store_setup = FALSE;
+    result = Curl_wssl_setup_x509_store(cf, data, &wssl);
+    if(result)
+      return result;
+    backend->x509_store_setup = wssl.x509_store_setup;
+  }
+
   ret = wolfSSL_connect(backend->handle);
 
 #ifdef OPENSSL_EXTRA
@@ -1070,8 +1254,8 @@ wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   CURLcode result = CURLE_OK;
   struct ssl_connect_data *connssl = cf->ctx;
-  struct wolfssl_ssl_backend_data *backend =
-    (struct wolfssl_ssl_backend_data *)connssl->backend;
+  struct wolfssl_ctx *backend =
+    (struct wolfssl_ctx *)connssl->backend;
   const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
 
   DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
@@ -1116,8 +1300,8 @@ static ssize_t wolfssl_send(struct Curl_cfilter *cf,
                             CURLcode *curlcode)
 {
   struct ssl_connect_data *connssl = cf->ctx;
-  struct wolfssl_ssl_backend_data *backend =
-    (struct wolfssl_ssl_backend_data *)connssl->backend;
+  struct wolfssl_ctx *backend =
+    (struct wolfssl_ctx *)connssl->backend;
   char error_buffer[WOLFSSL_MAX_ERROR_SZ];
   int memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
   int rc;
@@ -1158,8 +1342,8 @@ static ssize_t wolfssl_send(struct Curl_cfilter *cf,
 static void wolfssl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   struct ssl_connect_data *connssl = cf->ctx;
-  struct wolfssl_ssl_backend_data *backend =
-    (struct wolfssl_ssl_backend_data *)connssl->backend;
+  struct wolfssl_ctx *backend =
+    (struct wolfssl_ctx *)connssl->backend;
 
   (void) data;
 
@@ -1187,8 +1371,8 @@ static ssize_t wolfssl_recv(struct Curl_cfilter *cf,
                             CURLcode *curlcode)
 {
   struct ssl_connect_data *connssl = cf->ctx;
-  struct wolfssl_ssl_backend_data *backend =
-    (struct wolfssl_ssl_backend_data *)connssl->backend;
+  struct wolfssl_ctx *backend =
+    (struct wolfssl_ctx *)connssl->backend;
   char error_buffer[WOLFSSL_MAX_ERROR_SZ];
   int buffsize = (blen > (size_t)INT_MAX) ? INT_MAX : (int)blen;
   int nread;
@@ -1269,12 +1453,12 @@ static bool wolfssl_data_pending(struct Curl_cfilter *cf,
                                  const struct Curl_easy *data)
 {
   struct ssl_connect_data *ctx = cf->ctx;
-  struct wolfssl_ssl_backend_data *backend;
+  struct wolfssl_ctx *backend;
 
   (void)data;
   DEBUGASSERT(ctx && ctx->backend);
 
-  backend = (struct wolfssl_ssl_backend_data *)ctx->backend;
+  backend = (struct wolfssl_ctx *)ctx->backend;
   if(backend->handle)   /* SSL is in use */
     return (0 != wolfSSL_pending(backend->handle)) ? TRUE : FALSE;
   else
@@ -1290,13 +1474,13 @@ static int wolfssl_shutdown(struct Curl_cfilter *cf,
                             struct Curl_easy *data)
 {
   struct ssl_connect_data *ctx = cf->ctx;
-  struct wolfssl_ssl_backend_data *backend;
+  struct wolfssl_ctx *backend;
   int retval = 0;
 
   (void)data;
   DEBUGASSERT(ctx && ctx->backend);
 
-  backend = (struct wolfssl_ssl_backend_data *)ctx->backend;
+  backend = (struct wolfssl_ctx *)ctx->backend;
   if(backend->handle) {
     wolfSSL_ERR_clear_error();
     wolfSSL_free(backend->handle);
@@ -1472,8 +1656,8 @@ static CURLcode wolfssl_sha256sum(const unsigned char *tmp, /* input */
 static void *wolfssl_get_internals(struct ssl_connect_data *connssl,
                                    CURLINFO info UNUSED_PARAM)
 {
-  struct wolfssl_ssl_backend_data *backend =
-    (struct wolfssl_ssl_backend_data *)connssl->backend;
+  struct wolfssl_ctx *backend =
+    (struct wolfssl_ctx *)connssl->backend;
   (void)info;
   DEBUGASSERT(backend);
   return backend->handle;
@@ -1493,9 +1677,10 @@ const struct Curl_ssl Curl_ssl_wolfssl = {
 #ifdef USE_ECH
   SSLSUPP_ECH |
 #endif
-  SSLSUPP_SSL_CTX,
+  SSLSUPP_SSL_CTX |
+  SSLSUPP_CA_CACHE,
 
-  sizeof(struct wolfssl_ssl_backend_data),
+  sizeof(struct wolfssl_ctx),
 
   wolfssl_init,                    /* init */
   wolfssl_cleanup,                 /* cleanup */
index a5ed8480996d4784eb55d7e573a39ce74238eba0..d75bdaa1e00e10f6aaa5ca2ef987baa872d12d3b 100644 (file)
 #include "curl_setup.h"
 
 #ifdef USE_WOLFSSL
+#include <wolfssl/version.h>
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/error-ssl.h>
+
+#include "urldata.h"
 
 extern const struct Curl_ssl Curl_ssl_wolfssl;
 
+struct wolfssl_ctx {
+  WOLFSSL_CTX *ctx;
+  WOLFSSL     *handle;
+  CURLcode    io_result;   /* result of last BIO cfilter operation */
+  BIT(x509_store_setup);   /* x509 store has been set up */
+};
+
+CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    struct wolfssl_ctx *wssl);
+
 #endif /* USE_WOLFSSL */
 #endif /* HEADER_CURL_WOLFSSL_H */