]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
curl: add options for safe/no CA bundle search (Windows)
authorViktor Szakats <commit@vsz.me>
Sun, 18 Aug 2024 07:51:49 +0000 (09:51 +0200)
committerViktor Szakats <commit@vsz.me>
Sun, 22 Sep 2024 16:17:25 +0000 (18:17 +0200)
Add `CURL_CA_SEARCH_SAFE` build-time option to enable CA bundle search
in the `curl` tool directory. The lookup method was already used to find
`.curlrc` and `_curlrc` (on Windows). On Windows it overrides the unsafe
default `SearchPath()` method.

Enable with:
- cmake: `-DCURL_CA_SEARCH_SAFE=ON`
- autotools: `--enable-ca-search-safe`
- raw: `CPPFLAGS=-DCURL_CA_SEARCH_SAFE`

On Windows, before this patch the whole `PATH` was searched for
a CA bundle. `PATH` may contain unwanted or world-writable locations,
including the current directory. Searching them all is convenient to
pick up any CA bundle, but not secure.

The Muldersoft curl distro implements such CA search via a custom
patch for Windows:
https://github.com/lordmulder/cURL-build-win32/blob/cd652d4792c177c98b08b4309d3cac2b8dbbf9b0/patch/curl_tool_doswin.diff#L50

MSYS2/mingw-w64 distro has also been rolling a patch solving this:
https://github.com/msys2/MINGW-packages/blob/master/mingw-w64-curl/0001-Make-cURL-relocatable.patch
https://github.com/msys2/MINGW-packages/blob/master/mingw-w64-curl/pathtools.c

Also add option to fully disable Windows CA search:
- cmake: `-DCURL_DISABLE_CA_SEARCH=ON`
- autotools: `--disable-ca-search`
- raw: `CPPFLAGS=-DCURL_DISABLE_CA_SEARCH`.

Both options are considered EXPERIMENTAL, with possible incompatible
changes or even (partial) removal in the future, depending on feedback.

An alternative, secure option is to embed the CA bundle into the binary.

Safe search can be extended to other platforms if necessary or useful,
by using `_NSGetExecutablePath()` (macOS),
`/proc/self/exe` (Linux/Cygwin), or `argv[0]`.

Closes #14582

14 files changed:
.github/workflows/windows.yml
CMakeLists.txt
configure.ac
docs/CURL-DISABLE.md
docs/SSLCERTS.md
docs/cmdline-opts/cacert.md
lib/curl_config.h.cmake
src/tool_doswin.c
src/tool_doswin.h
src/tool_operate.c
src/tool_parsecfg.c
src/tool_util.c
src/tool_util.h
tests/server/disabled.c

index 7cff2fccecc7dbc5bf2e5f645af5b335130ba607..2ca7cd601ffc54adaeb91a17c48df244144b1b99 100644 (file)
@@ -500,7 +500,7 @@ jobs:
             plat: 'windows'
             type: 'Debug'
             tflags: '~1516 ~2301 ~2302 ~2303 ~2307'
-            config: '-DENABLE_DEBUG=ON -DENABLE_UNICODE=OFF -DCURL_USE_SCHANNEL=OFF -DCURL_BROTLI=ON -DCURL_ZSTD=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_LIBSSH2=ON -DCURL_USE_OPENSSL=ON'
+            config: '-DENABLE_DEBUG=ON -DENABLE_UNICODE=OFF -DCURL_USE_SCHANNEL=OFF -DCURL_BROTLI=ON -DCURL_ZSTD=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_LIBSSH2=ON -DCURL_USE_OPENSSL=ON -DCURL_CA_SEARCH_SAFE=ON'
           - name: 'boringssl-ECH'
             install: 'brotli zlib zstd libpsl nghttp2 boringssl libssh2[core,zlib]'
             arch: 'x64'
index 3853673508d6c991a042c29adaf45c7e353c58c3..9ec7dd433a3e8d06f52dc3536f5e94f906d82e40 100644 (file)
@@ -1345,6 +1345,11 @@ if(_curl_ca_bundle_supported)
   endif()
 endif()
 
+if(WIN32)
+  option(CURL_DISABLE_CA_SEARCH "Disable unsafe CA bundle search in PATH on Windows" OFF)
+  option(CURL_CA_SEARCH_SAFE "Enable safe CA bundle search (within the curl tool directory) on Windows" OFF)
+endif()
+
 # Check for header files
 if(WIN32)
   set(CURL_INCLUDES ${CURL_INCLUDES} "winsock2.h")
index eb7e9b353dc56fcc596ef712d631a80be5ecc140..ca6e4c66e099dff1b90573bbe5a7f2ec4e7a0bb5 100644 (file)
@@ -2184,6 +2184,50 @@ fi
 
 AM_CONDITIONAL(CURL_CA_EMBED_SET, test "x$CURL_CA_EMBED" != "x")
 
+dnl ----------------------
+dnl check unsafe CA search
+dnl ----------------------
+
+if test "$curl_cv_native_windows" = "yes"; then
+  AC_MSG_CHECKING([whether to enable unsafe CA bundle search in PATH on Windows])
+  AC_ARG_ENABLE(ca-search,
+AS_HELP_STRING([--enable-ca-search],[Enable unsafe CA bundle search in PATH on Windows (default)])
+AS_HELP_STRING([--disable-ca-search],[Disable unsafe CA bundle search in PATH on Windows]),
+  [ case "$enableval" in
+    no)
+      AC_MSG_RESULT([no])
+      AC_DEFINE(CURL_DISABLE_CA_SEARCH, 1, [If unsafe CA bundle search in PATH on Windows is disabled])
+      ;;
+    *)
+      AC_MSG_RESULT([yes])
+      ;;
+    esac ],
+      AC_MSG_RESULT([yes])
+  )
+fi
+
+dnl --------------------
+dnl check safe CA search
+dnl --------------------
+
+if test "$curl_cv_native_windows" = "yes"; then
+  AC_MSG_CHECKING([whether to enable safe CA bundle search (within the curl tool directory) on Windows])
+  AC_ARG_ENABLE(ca-search-safe,
+AS_HELP_STRING([--enable-ca-search-safe],[Enable safe CA bundle search])
+AS_HELP_STRING([--disable-ca-search-safe],[Disable safe CA bundle search (default)]),
+  [ case "$enableval" in
+    yes)
+      AC_MSG_RESULT([yes])
+      AC_DEFINE(CURL_CA_SEARCH_SAFE, 1, [If safe CA bundle search is enabled])
+      ;;
+    *)
+      AC_MSG_RESULT([no])
+      ;;
+    esac ],
+      AC_MSG_RESULT([no])
+  )
+fi
+
 dnl **********************************************************************
 dnl Check for libpsl
 dnl **********************************************************************
index 32ae025fc4a146b623ad8e7ef27280d9f96ad6ba..7962832fad85df54e68b379399e2b072ee01b861 100644 (file)
@@ -42,6 +42,10 @@ Disable support for the negotiate authentication methods.
 
 Disable **AWS-SIG4** support.
 
+## `CURL_DISABLE_CA_SEARCH`
+
+Disable unsafe CA bundle search in PATH on Windows.
+
 ## `CURL_DISABLE_DICT`
 
 Disable the DICT protocol
index b6d7a6e7512eebadfd3c395c475b70a37d651233..300039c736d134b1db7f936482fc67a7c85439fe 100644 (file)
@@ -65,6 +65,9 @@ cert file named `curl-ca-bundle.crt` in these directories and in this order:
   4. Windows Directory (e.g. C:\Windows)
   5. all directories along %PATH%
 
+curl 8.11.0 added a build-time option to disable this search behavior, and
+another option to restrict search to the application's directory.
+
 ### Use the native store
 
 In several environments, in particular on Windows, you can ask curl to use the
index 1b34ce5b4d4286c0f0d59c577f956634494eefbd..00c277e2e106966450f46e4cfdccf81ac8e490a4 100644 (file)
@@ -27,10 +27,13 @@ curl recognizes the environment variable named 'CURL_CA_BUNDLE' if it is set
 and the TLS backend is not Schannel, and uses the given path as a path to a CA
 cert bundle. This option overrides that variable.
 
-The Windows version of curl automatically looks for a CA certs file named
+(Windows) curl automatically looks for a CA certs file named
 'curl-ca-bundle.crt', either in the same directory as curl.exe, or in the
 Current Working Directory, or in any folder along your PATH.
 
+curl 8.11.0 added a build-time option to disable this search behavior, and
+another option to restrict search to the application's directory.
+
 (iOS and macOS only) If curl is built against Secure Transport, then this
 option is supported for backward compatibility with other SSL engines, but it
 should not be set. If the option is not set, then curl uses the certificates
index f247272a8aa823882e52581a6823e7469a50961e..6e5fe4fcb2812996a911df9d167c14bb4e247c98 100644 (file)
 /* disables verbose strings */
 #cmakedefine CURL_DISABLE_VERBOSE_STRINGS 1
 
+/* disables unsafe CA bundle search on Windows from the curl tool */
+#cmakedefine CURL_DISABLE_CA_SEARCH 1
+
+/* safe CA bundle search (within the curl tool directory) on Windows */
+#cmakedefine CURL_CA_SEARCH_SAFE 1
+
 /* to make a symbol visible */
 #cmakedefine CURL_EXTERN_SYMBOL ${CURL_EXTERN_SYMBOL}
 /* Ensure using CURL_EXTERN_SYMBOL is possible */
index fd9325476d5ef161a47961dd29e7ae19acc4b644..369f7e370353abb9792ca566ba809748167f099d 100644 (file)
@@ -600,6 +600,8 @@ char **__crt0_glob_function(char *arg)
 
 #ifdef _WIN32
 
+#if !defined(CURL_WINDOWS_UWP) && \
+  !defined(CURL_DISABLE_CA_SEARCH) && !defined(CURL_CA_SEARCH_SAFE)
 /* Search and set the CA cert file for Windows.
  *
  * Do not call this function if Schannel is the selected SSL backend. We allow
@@ -623,11 +625,6 @@ CURLcode FindWin32CACert(struct OperationConfig *config,
                          const TCHAR *bundle_file)
 {
   CURLcode result = CURLE_OK;
-
-#ifdef CURL_WINDOWS_UWP
-  (void)config;
-  (void)bundle_file;
-#else
   DWORD res_len;
   TCHAR buf[PATH_MAX];
   TCHAR *ptr = NULL;
@@ -644,11 +641,10 @@ CURLcode FindWin32CACert(struct OperationConfig *config,
     if(!config->cacert)
       result = CURLE_OUT_OF_MEMORY;
   }
-#endif
 
   return result;
 }
-
+#endif
 
 /* Get a list of all loaded modules with full paths.
  * Returns slist on success or NULL on error.
index f16fc33ac8d5f9dcd8c4ab1108a975f350cbcc21..b86959e9b866819c5200c9980608aa993c5b8226 100644 (file)
@@ -59,8 +59,11 @@ char **__crt0_glob_function(char *arg);
 
 #ifdef _WIN32
 
+#if !defined(CURL_WINDOWS_UWP) && \
+  !defined(CURL_DISABLE_CA_SEARCH) && !defined(CURL_CA_SEARCH_SAFE)
 CURLcode FindWin32CACert(struct OperationConfig *config,
                          const TCHAR *bundle_file);
+#endif
 struct curl_slist *GetLoadedModulePaths(void);
 CURLcode win32_init(void);
 
index 289ce588df7419078dc7ad8697c6f6cc8d9f2aa8..b49e9fd06f86496669463defc2924ec0cc554f13 100644 (file)
@@ -3083,8 +3083,18 @@ static CURLcode transfer_per_config(struct GlobalConfig *global,
       }
 
 #ifdef _WIN32
-      if(!env)
+      if(!env) {
+#if defined(CURL_CA_SEARCH_SAFE)
+        char *cacert = NULL;
+        FILE *cafile = Curl_execpath("curl-ca-bundle.crt", &cacert);
+        if(cafile) {
+          fclose(cafile);
+          config->cacert = strdup(cacert);
+        }
+#elif !defined(CURL_WINDOWS_UWP) && !defined(CURL_DISABLE_CA_SEARCH)
         result = FindWin32CACert(config, TEXT("curl-ca-bundle.crt"));
+#endif
+      }
 #endif
     }
     curl_easy_cleanup(curltls);
index 72fd67af8b171af86ca45cb1496c2be4a894f1c6..a267dccbe7a795367def91e02805aab67ebee5d2 100644 (file)
@@ -31,6 +31,7 @@
 #include "tool_findfile.h"
 #include "tool_msgs.h"
 #include "tool_parsecfg.h"
+#include "tool_util.h"
 #include "dynbuf.h"
 
 #include "memdebug.h" /* keep this as LAST include */
@@ -44,37 +45,6 @@ static const char *unslashquote(const char *line, char *param);
 #define MAX_CONFIG_LINE_LENGTH (10*1024*1024)
 static bool my_get_line(FILE *fp, struct curlx_dynbuf *, bool *error);
 
-#ifdef _WIN32
-static FILE *execpath(const char *filename, char **pathp)
-{
-  static char filebuffer[512];
-  /* Get the filename of our executable. GetModuleFileName is already declared
-   * via inclusions done in setup header file. We assume that we are using
-   * the ASCII version here.
-   */
-  unsigned long len = GetModuleFileNameA(0, filebuffer, sizeof(filebuffer));
-  if(len > 0 && len < sizeof(filebuffer)) {
-    /* We got a valid filename - get the directory part */
-    char *lastdirchar = strrchr(filebuffer, '\\');
-    if(lastdirchar) {
-      size_t remaining;
-      *lastdirchar = 0;
-      /* If we have enough space, build the RC filename */
-      remaining = sizeof(filebuffer) - strlen(filebuffer);
-      if(strlen(filename) < remaining - 1) {
-        FILE *f;
-        msnprintf(lastdirchar, remaining, "%s%s", DIR_CHAR, filename);
-        *pathp = filebuffer;
-        f = fopen(filebuffer, FOPEN_READTEXT);
-        return f;
-      }
-    }
-  }
-
-  return NULL;
-}
-#endif
-
 
 /* return 0 on everything-is-fine, and non-zero otherwise */
 int parseconfig(const char *filename, struct GlobalConfig *global)
@@ -100,9 +70,9 @@ int parseconfig(const char *filename, struct GlobalConfig *global)
     else {
       char *fullp;
       /* check for .curlrc then _curlrc in the dir of the executable */
-      file = execpath(".curlrc", &fullp);
+      file = Curl_execpath(".curlrc", &fullp);
       if(!file)
-        file = execpath("_curlrc", &fullp);
+        file = Curl_execpath("_curlrc", &fullp);
       if(file)
         /* this is the filename we read from */
         filename = fullp;
index b185799da53966fe840226464d8a7e3551a0eda4..e657dacf0c71e2425bd6c03dd37c2cb71c64f3de 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "tool_util.h"
 
+#include "curlx.h"
 #include "memdebug.h" /* keep this as LAST include */
 
 #if defined(_WIN32)
@@ -188,3 +189,33 @@ int tool_ftruncate64(int fd, curl_off_t where)
 }
 
 #endif /* USE_TOOL_FTRUNCATE */
+
+#ifdef _WIN32
+FILE *Curl_execpath(const char *filename, char **pathp)
+{
+  static char filebuffer[512];
+  unsigned long len;
+  /* Get the filename of our executable. GetModuleFileName is already declared
+   * via inclusions done in setup header file. We assume that we are using
+   * the ASCII version here.
+   */
+  len = GetModuleFileNameA(0, filebuffer, sizeof(filebuffer));
+  if(len > 0 && len < sizeof(filebuffer)) {
+    /* We got a valid filename - get the directory part */
+    char *lastdirchar = strrchr(filebuffer, DIR_CHAR[0]);
+    if(lastdirchar) {
+      size_t remaining;
+      *lastdirchar = 0;
+      /* If we have enough space, build the RC filename */
+      remaining = sizeof(filebuffer) - strlen(filebuffer);
+      if(strlen(filename) < remaining - 1) {
+        msnprintf(lastdirchar, remaining, "%s%s", DIR_CHAR, filename);
+        *pathp = filebuffer;
+        return fopen(filebuffer, FOPEN_READTEXT);
+      }
+    }
+  }
+
+  return NULL;
+}
+#endif
index d68867265c9c0147948f3042ac84dc696737dbf0..9fec7e8737e11135b93f56b973bb94f659f85dec 100644 (file)
@@ -39,4 +39,8 @@ long tvdiff(struct timeval t1, struct timeval t2);
 int struplocompare(const char *p1, const char *p2);
 int struplocompare4sort(const void *p1, const void *p2);
 
+#ifdef _WIN32
+FILE *Curl_execpath(const char *filename, char **pathp);
+#endif
+
 #endif /* HEADER_CURL_TOOL_UTIL_H */
index fe500137d4914541c179c5633b1362cc57918ee8..057ab36fc615d0cfceefdc9271fed78077df4ebb 100644 (file)
@@ -103,6 +103,15 @@ static const char *disabled[]={
 #endif
 #ifndef CURL_HAVE_SHA512_256
   "sha512-256",
+#endif
+#ifdef _WIN32
+#if defined(CURL_WINDOWS_UWP) || \
+  defined(CURL_DISABLE_CA_SEARCH) || defined(CURL_CA_SEARCH_SAFE)
+  "win32-ca-searchpath",
+#endif
+#ifndef CURL_CA_SEARCH_SAFE
+  "win32-ca-search-safe",
+#endif
 #endif
   NULL
 };