From 1730407b74f41cfd33f189348be54d0504b7c291 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 14 Aug 2025 00:48:00 +0200 Subject: [PATCH] windows: add build option to use the native CA store With the same semantics as Apple SecTrust, in both libcurl and the curl tool, when using non-Schannel TLS backends. In practice it means that it makes TLS work without manually or implicitly configuring a CA bundle `.crt` file, such as `curl-ca-bundle.crt`. To enable: - autotools: `--enable-ca-native` - cmake: `-DCURL_CA_NATIVE=ON` - CPPFLAGS: `-DCURL_CA_NATIVE` When enabled: - enables `CURLSSLOPT_NATIVE_CA` (libcurl) / `--ca-native` and `--proxy-ca-native` (curl tool) options by default. - unsafe search for an on-disk CA bundle gets disabled by default. Equivalent to `--disable-ca-search` with autotools, `-DCURL_DISABLE_CA_SEARCH=ON` with CMake. - build-time detection of CA bundle and CA path gets disabled. As with Apple SecTrust. This was already the default for Windows. - native CA can be disabled at run-time with the `--no-ca-native` and/or `--no-proxy-ca-native` command-line options. Rationale: This build option: - has a repeat and active interest from packagers and users. - helps integrating curl with Windows for those who need this. - it also applies to macOS: #17525 Shipped in curl 8.17.0. - makes it trivial to use custom certs configured on the OS. - frees applications/packagers/users from the task of securely distributing, and keeping up-to-date, a CA bundle. - frees potentially many curl tool from configuring a CA bundle manually to access HTTPS (and other TLS) URLs. This is traditionally difficult on Windows because there is no concept of a universal, protected, non-world-writable, location on the file system to securely store a CA bundle. - allows using modern features regardless of Windows version. Some of these features are not supported with Schannel (e.g. HTTP/3, ECH) on any Windows version. - is necessary for HTTP/3 builds, where bootstrapping a CA bundle is not possible with Schannel, because MultiSSL is not an option, and HTTP/3 is not supported with Schannel. Ref: #16181 (previous attempt) Ref: https://github.com/curl/curl/discussions/9348 Ref: https://github.com/curl/curl/issues/9350 Ref: https://github.com/curl/curl/pull/13111 Ref: https://github.com/microsoft/vcpkg/pull/46459#issuecomment-3162068701 Ref: 22652a5a4cb6a4cc1c0f4ff3ebc4f9768f6663cd #14582 Ref: eefd03c572996e5de4dec4fe295ad6f103e0eefc #18703 Closes #18279 --- .github/workflows/windows.yml | 3 +- CMakeLists.txt | 14 ++++++-- acinclude.m4 | 2 ++ configure.ac | 55 +++++++++++++++++++++++++++---- docs/INSTALL-CMAKE.md | 4 ++- docs/libcurl/curl_version_info.md | 7 ++++ lib/curl_config-cmake.h.in | 3 ++ lib/version.c | 4 +++ lib/vtls/vtls.c | 4 +-- 9 files changed, 83 insertions(+), 13 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 65f4190e91..16abc5ae17 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -200,7 +200,7 @@ jobs: - { build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '' , config: '--with-openssl', install: 'openssl-devel libssh2-devel', name: 'default R' } # MinGW - { build: 'autotools', sys: 'mingw64' , env: 'x86_64' , tflags: 'skiprun' , config: '--enable-debug --with-openssl --disable-threaded-resolver --disable-curldebug --enable-static --without-zlib', install: 'mingw-w64-x86_64-openssl mingw-w64-x86_64-libssh2', name: 'default' } - - { build: 'autotools', sys: 'mingw64' , env: 'x86_64' , tflags: '' , config: '--enable-debug --with-openssl --enable-windows-unicode --enable-ares --with-openssl-quic --enable-static --disable-shared', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-openssl mingw-w64-x86_64-nghttp3 mingw-w64-x86_64-libssh2', name: 'c-ares U' } + - { build: 'autotools', sys: 'mingw64' , env: 'x86_64' , tflags: '' , config: '--enable-debug --with-openssl --enable-windows-unicode --enable-ares --with-openssl-quic --enable-static --disable-shared --enable-ca-native', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-openssl mingw-w64-x86_64-nghttp3 mingw-w64-x86_64-libssh2', name: 'c-ares U' } - { build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: '--min=1650', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-libssh2', type: 'Debug', name: 'schannel c-ares U' } # MinGW torture - { build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: '-t --shallow=13 --min=700 1 to 950' , config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-libssh2', type: 'Debug', name: 'schannel U torture 1' } @@ -764,6 +764,7 @@ jobs: -DNGTCP2_INCLUDE_DIR=/ucrt64/include -DNGTCP2_LIBRARY=/ucrt64/lib/libngtcp2.dll.a -DNGTCP2_CRYPTO_OSSL_LIBRARY=/ucrt64/lib/libngtcp2_crypto_ossl.dll.a + -DCURL_CA_NATIVE=ON - name: 'schannel U' install-vcpkg: 'zlib libssh2[core,zlib]' diff --git a/CMakeLists.txt b/CMakeLists.txt index c3da479065..32bc5c326f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1325,6 +1325,13 @@ endif() # # CA handling # +option(CURL_CA_NATIVE "Use native CA store" OFF) +if(CURL_CA_NATIVE) + set(_curl_disable_ca_search_default ON) +else() + set(_curl_disable_ca_search_default OFF) +endif() + if(_curl_ca_bundle_supported) set(_ca_opt_desc "Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.") @@ -1347,7 +1354,7 @@ if(_curl_ca_bundle_supported) unset(CURL_CA_BUNDLE CACHE) elseif(CURL_CA_BUNDLE STREQUAL "auto") unset(CURL_CA_BUNDLE CACHE) - if(NOT CMAKE_CROSSCOMPILING AND NOT WIN32 AND NOT USE_APPLE_SECTRUST) + if(NOT CMAKE_CROSSCOMPILING AND NOT WIN32 AND NOT USE_APPLE_SECTRUST AND NOT CURL_CA_NATIVE) set(_curl_ca_bundle_autodetect TRUE) endif() else() @@ -1361,7 +1368,7 @@ if(_curl_ca_bundle_supported) unset(CURL_CA_PATH CACHE) elseif(CURL_CA_PATH STREQUAL "auto") unset(CURL_CA_PATH CACHE) - if(NOT CMAKE_CROSSCOMPILING AND NOT WIN32 AND NOT USE_APPLE_SECTRUST) + if(NOT CMAKE_CROSSCOMPILING AND NOT WIN32 AND NOT USE_APPLE_SECTRUST AND NOT CURL_CA_NATIVE) set(_curl_ca_path_autodetect TRUE) endif() else() @@ -1418,7 +1425,7 @@ if(_curl_ca_bundle_supported) endif() if(WIN32) - option(CURL_DISABLE_CA_SEARCH "Disable unsafe CA bundle search in PATH on Windows" OFF) + option(CURL_DISABLE_CA_SEARCH "Disable unsafe CA bundle search in PATH on Windows" ${_curl_disable_ca_search_default}) option(CURL_CA_SEARCH_SAFE "Enable safe CA bundle search (within the curl tool directory) on Windows" OFF) endif() @@ -1998,6 +2005,7 @@ curl_add_if("PSL" USE_LIBPSL) curl_add_if("CAcert" CURL_CA_EMBED_SET) curl_add_if("SSLS-EXPORT" _ssl_enabled AND USE_SSLS_EXPORT) curl_add_if("AppleSecTrust" USE_APPLE_SECTRUST AND _ssl_enabled AND (USE_OPENSSL OR USE_GNUTLS)) +curl_add_if("NativeCA" NOT USE_APPLE_SECTRUST AND _ssl_enabled AND CURL_CA_NATIVE) if(_items) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) list(SORT _items CASE INSENSITIVE) diff --git a/acinclude.m4 b/acinclude.m4 index 775102033c..638b04adf6 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -1151,6 +1151,8 @@ AS_HELP_STRING([--without-ca-path], [Do not use a default CA path]), if test "$APPLE_SECTRUST_ENABLED" = "1"; then ca_native="Apple SecTrust" + elif test "$ca_native_opt" = "1"; then + ca_native="yes" else ca_native="no" fi diff --git a/configure.ac b/configure.ac index 24b64f66b8..5135cdda54 100644 --- a/configure.ac +++ b/configure.ac @@ -2093,6 +2093,28 @@ elif test "$VALID_DEFAULT_SSL_BACKEND" = "yes"; then AC_DEFINE_UNQUOTED([CURL_DEFAULT_SSL_BACKEND], ["$DEFAULT_SSL_BACKEND"], [Default SSL backend]) fi +dnl --------------------- +dnl check native CA store +dnl --------------------- + +ca_native_opt=0 +AC_MSG_CHECKING([whether to use native CA store]) +AC_ARG_ENABLE(ca-native, +AS_HELP_STRING([--enable-ca-native],[Enable native CA store]) +AS_HELP_STRING([--disable-ca-native],[Disable native CA store (default)]), +[ case "$enableval" in + yes) + AC_MSG_RESULT([yes]) + AC_DEFINE(CURL_CA_NATIVE, 1, [If native CA store is enabled]) + ca_native_opt=1 + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac ], + AC_MSG_RESULT([no]) +) + dnl ********************************************************************** dnl Check for the CA bundle dnl ********************************************************************** @@ -2109,21 +2131,38 @@ dnl check unsafe CA search dnl ---------------------- if test "$curl_cv_native_windows" = "yes"; then + ca_search=1 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([--enable-ca-search],[Enable unsafe CA bundle search in PATH on Windows]) 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]) + ca_search=0 + ;; + yes) + AC_MSG_RESULT([yes]) ;; *) - AC_MSG_RESULT([yes]) + if test "$ca_native_opt" = "1"; then + AC_MSG_RESULT([no]) + ca_search=0 + else + AC_MSG_RESULT([yes]) + fi ;; esac ], - AC_MSG_RESULT([yes]) + if test "$ca_native_opt" = "1"; then + AC_MSG_RESULT([no]) + ca_search=0 + else + AC_MSG_RESULT([yes]) + fi ) + if test "$ca_search" = "0"; then + AC_DEFINE(CURL_DISABLE_CA_SEARCH, 1, [If unsafe CA bundle search in PATH on Windows is disabled]) + fi fi dnl -------------------- @@ -5286,8 +5325,12 @@ if test "$OPENSSL_ENABLED" = "1" || test -n "$SSL_ENABLED"; then fi fi -if test "$APPLE_SECTRUST_ENABLED" = "1"; then - SUPPORT_FEATURES="$SUPPORT_FEATURES AppleSecTrust" +if test -n "$SSL_ENABLED"; then + if test "$APPLE_SECTRUST_ENABLED" = "1"; then + SUPPORT_FEATURES="$SUPPORT_FEATURES AppleSecTrust" + elif test "$ca_native_opt" = "1"; then + SUPPORT_FEATURES="$SUPPORT_FEATURES NativeCA" + fi fi if test "$want_httpsrr" != "no"; then diff --git a/docs/INSTALL-CMAKE.md b/docs/INSTALL-CMAKE.md index 0023adb27f..8ff3f30146 100644 --- a/docs/INSTALL-CMAKE.md +++ b/docs/INSTALL-CMAKE.md @@ -260,6 +260,8 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CURL_CA_BUNDLE`: Absolute path to the CA bundle. Set `none` to disable or `auto` for auto-detection. Default: `auto` - `CURL_CA_EMBED`: Absolute path to the CA bundle to embed in the curl tool. Default: (disabled) - `CURL_CA_FALLBACK`: Use built-in CA store of OpenSSL. Default: `OFF` +- `CURL_CA_NATIVE`: Use native CA store. Default: `OFF` + Supported by GnuTLS, OpenSSL (including forks) on Windows, wolfSSL. - `CURL_CA_PATH`: Absolute path to a directory containing CA certificates stored individually. Set `none` to disable or `auto` for auto-detection. Default: `auto` - `CURL_CA_SEARCH_SAFE`: Enable safe CA bundle search (within the curl tool directory) on Windows. Default: `OFF` @@ -283,7 +285,7 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CURL_DISABLE_BASIC_AUTH`: Disable Basic authentication. Default: `OFF` - `CURL_DISABLE_BEARER_AUTH`: Disable Bearer authentication. Default: `OFF` - `CURL_DISABLE_BINDLOCAL`: Disable local binding support. Default: `OFF` -- `CURL_DISABLE_CA_SEARCH`: Disable unsafe CA bundle search in PATH on Windows. Default: `OFF` +- `CURL_DISABLE_CA_SEARCH`: Disable unsafe CA bundle search in PATH on Windows. Default: `OFF` (turns to `ON`, when `CURL_CA_NATIVE=ON`) - `CURL_DISABLE_COOKIES`: Disable cookies support. Default: `OFF` - `CURL_DISABLE_DICT`: Disable DICT. Default: `OFF` - `CURL_DISABLE_DIGEST_AUTH`: Disable Digest authentication. Default: `OFF` diff --git a/docs/libcurl/curl_version_info.md b/docs/libcurl/curl_version_info.md index b9c4d4748b..98b9f2bc78 100644 --- a/docs/libcurl/curl_version_info.md +++ b/docs/libcurl/curl_version_info.md @@ -286,6 +286,13 @@ supports HTTP deflate using libz libcurl was built with multiple SSL backends. For details, see curl_global_sslset(3). +## `NativeCA` + +*features* mask bit: non-existent + +libcurl was built to enable native CA store, to verify server certificates +(Added in 8.19.0). + ## `NTLM` *features* mask bit: CURL_VERSION_NTLM diff --git a/lib/curl_config-cmake.h.in b/lib/curl_config-cmake.h.in index 3889e7183f..dbbe7e353a 100644 --- a/lib/curl_config-cmake.h.in +++ b/lib/curl_config-cmake.h.in @@ -34,6 +34,9 @@ /* Default SSL backend */ #cmakedefine CURL_DEFAULT_SSL_BACKEND "${CURL_DEFAULT_SSL_BACKEND}" +/* Use native CA store */ +#cmakedefine CURL_CA_NATIVE 1 + /* disables alt-svc */ #cmakedefine CURL_DISABLE_ALTSVC 1 diff --git a/lib/version.c b/lib/version.c index 697d9ea96d..83e519810b 100644 --- a/lib/version.c +++ b/lib/version.c @@ -513,9 +513,13 @@ static const struct feat features_table[] = { #ifdef USE_LIBPSL FEATURE("PSL", NULL, CURL_VERSION_PSL), #endif +#ifdef USE_SSL #ifdef USE_APPLE_SECTRUST FEATURE("AppleSecTrust", NULL, 0), +#elif defined(CURL_CA_NATIVE) + FEATURE("NativeCA", NULL, 0), #endif +#endif /* USE_SSL */ #ifdef USE_SPNEGO FEATURE("SPNEGO", NULL, CURL_VERSION_SPNEGO), #endif diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 6e336b0b8d..aef5183087 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -295,7 +295,7 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) #endif if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { -#ifdef USE_APPLE_SECTRUST +#if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) sslc->native_ca_store = TRUE; #endif @@ -341,7 +341,7 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) #ifndef CURL_DISABLE_PROXY sslc = &data->set.proxy_ssl; if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { -#ifdef USE_APPLE_SECTRUST +#if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) sslc->native_ca_store = TRUE; #endif