From eefd03c572996e5de4dec4fe295ad6f103e0eefc Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 24 Sep 2025 10:19:46 +0200 Subject: [PATCH] ssl: support Apple SecTrust configurations - configure/cmake support for enabling the option - supported in OpenSSL and GnuTLS backends - when configured, Apple SecTrust is the default trust store for peer verification. When one of the CURLOPT_* for adding certificates is used, that default does not apply. - add documentation of build options and SSL use Closes #18703 --- .github/scripts/spellcheck.words | 1 + .github/workflows/configure-vs-cmake.yml | 4 +- .github/workflows/macos.yml | 9 +- CMakeLists.txt | 19 +- acinclude.m4 | 10 + configure.ac | 8 + docs/INSTALL-CMAKE.md | 1 + docs/INSTALL.md | 19 + docs/SSLCERTS.md | 54 +- docs/cmdline-opts/ca-native.md | 6 +- lib/Makefile.inc | 2 + lib/curl_config.h.cmake | 3 + lib/setopt.c | 14 +- lib/url.c | 30 - lib/urldata.h | 3 + lib/vquic/vquic-tls.c | 4 +- lib/vtls/apple.c | 297 +++++++++ lib/vtls/apple.h | 55 ++ lib/vtls/gtls.c | 370 ++++++----- lib/vtls/gtls.h | 3 +- lib/vtls/openssl.c | 784 ++++++++++++++--------- lib/vtls/openssl.h | 4 +- lib/vtls/vtls.c | 141 ++-- lib/vtls/vtls_scache.c | 2 +- m4/curl-apple-sectrust.m4 | 58 ++ tests/data/test305 | 2 +- tests/http/test_02_download.py | 2 + tests/http/test_07_upload.py | 2 + tests/http/test_17_ssl_use.py | 4 +- 29 files changed, 1342 insertions(+), 569 deletions(-) create mode 100644 lib/vtls/apple.c create mode 100644 lib/vtls/apple.h create mode 100644 m4/curl-apple-sectrust.m4 diff --git a/.github/scripts/spellcheck.words b/.github/scripts/spellcheck.words index fb4d9532e6..73e68f4884 100644 --- a/.github/scripts/spellcheck.words +++ b/.github/scripts/spellcheck.words @@ -743,6 +743,7 @@ scp SDK se SEB +SecTrust SEK selectable Serv diff --git a/.github/workflows/configure-vs-cmake.yml b/.github/workflows/configure-vs-cmake.yml index 984fccd592..b259daee51 100644 --- a/.github/workflows/configure-vs-cmake.yml +++ b/.github/workflows/configure-vs-cmake.yml @@ -92,13 +92,13 @@ jobs: run: | autoreconf -fi export PKG_CONFIG_DEBUG_SPEW=1 - mkdir bld-am && cd bld-am && ../configure --enable-static=no --with-openssl --without-libpsl --disable-ldap --with-brotli --with-zstd + mkdir bld-am && cd bld-am && ../configure --enable-static=no --with-openssl --without-libpsl --disable-ldap --with-brotli --with-zstd --with-apple-sectrust - name: 'run cmake' run: | cmake -B bld-cm -DCURL_WERROR=ON -DCURL_USE_LIBPSL=OFF -DCURL_DISABLE_LDAP=ON \ -DCMAKE_C_COMPILER_TARGET="$(uname -m | sed 's/arm64/aarch64/')-apple-darwin$(uname -r)" \ - -DCURL_USE_LIBSSH2=OFF + -DCURL_USE_LIBSSH2=OFF -DUSE_APPLE_SECTRUST=ON - name: 'configure log' run: cat bld-am/config.log 2>/dev/null || true diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f731406bfb..931abc8693 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -265,6 +265,11 @@ jobs: install: libnghttp3 libngtcp2 install_steps: pytest configure: --enable-debug --with-openssl=/opt/homebrew/opt/openssl --with-ngtcp2 + - name: 'OpenSSL SecTrust' + compiler: clang + install: libnghttp3 libngtcp2 + install_steps: pytest + configure: --enable-debug --with-openssl=/opt/homebrew/opt/openssl --with-ngtcp2 --with-apple-sectrust - name: 'OpenSSL event-based' compiler: clang configure: --enable-debug --with-openssl=/opt/homebrew/opt/openssl @@ -275,9 +280,9 @@ jobs: configure: --enable-debug --disable-ldap --with-openssl=/opt/homebrew/opt/quictls LDFLAGS=-L/opt/homebrew/opt/quictls/lib macos-version-min: '10.15' # cmake - - name: 'OpenSSL gsasl rtmp AppleIDN' + - name: 'OpenSSL gsasl rtmp AppleIDN SecTrust' install: libnghttp3 libngtcp2 gsasl rtmpdump - generate: -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_USE_GSASL=ON -DUSE_LIBRTMP=ON -DUSE_APPLE_IDN=ON -DUSE_NGTCP2=ON -DCURL_DISABLE_VERBOSE_STRINGS=ON + generate: -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_USE_GSASL=ON -DUSE_LIBRTMP=ON -DUSE_APPLE_IDN=ON -DUSE_NGTCP2=ON -DCURL_DISABLE_VERBOSE_STRINGS=ON -DUSE_APPLE_SECTRUST=ON - name: 'MultiSSL AppleIDN clang-tidy +examples' compiler: clang install: llvm brotli zstd gnutls nettle libressl krb5 mbedtls gsasl rustls-ffi rtmpdump libssh fish diff --git a/CMakeLists.txt b/CMakeLists.txt index 115eaa5f34..3b5ed10f80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -764,6 +764,23 @@ if(CURL_WINDOWS_SSPI AND NOT WINDOWS_STORE) set(USE_WINDOWS_SSPI ON) endif() +if(APPLE) + option(USE_APPLE_SECTRUST "Use Apple OS-native certificate verification" OFF) + if(USE_APPLE_SECTRUST) + find_library(COREFOUNDATION_FRAMEWORK NAMES "Security") + mark_as_advanced(COREFOUNDATION_FRAMEWORK) + if(NOT COREFOUNDATION_FRAMEWORK) + message(FATAL_ERROR "Security framework not found") + endif() + list(APPEND CURL_LIBS "-framework Security") + + set(_use_core_foundation_and_core_services ON) + message(STATUS "Apple OS-native certificate verification enabled") + endif() +else() + set(USE_APPLE_SECTRUST OFF) +endif() + if(_use_core_foundation_and_core_services) find_library(COREFOUNDATION_FRAMEWORK NAMES "CoreFoundation") mark_as_advanced(COREFOUNDATION_FRAMEWORK) @@ -1531,7 +1548,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) + if(NOT CMAKE_CROSSCOMPILING AND NOT WIN32 AND NOT USE_APPLE_SECTRUST) set(_curl_ca_bundle_autodetect TRUE) endif() else() diff --git a/acinclude.m4 b/acinclude.m4 index 7b26dfd466..517de0c4a6 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -1149,6 +1149,12 @@ AS_HELP_STRING([--without-ca-path], [Don't use a default CA path]), capath_warning=" (warning: certs not found)" check_capath="" + if test "x$APPLE_SECTRUST_ENABLED" = "x1"; then + ca_native="Apple SecTrust" + else + ca_native="no" + fi + if test "x$want_ca" != "xno" -a "x$want_ca" != "xunset" -a \ "x$want_capath" != "xno" -a "x$want_capath" != "xunset"; then dnl both given @@ -1162,6 +1168,10 @@ AS_HELP_STRING([--without-ca-path], [Don't use a default CA path]), dnl --with-ca-path given capath="$want_capath" ca="no" + elif test "x$ca_native" != "xno"; then + # native ca configured, do not look further + ca="no" + capath="no" else dnl First try auto-detecting a CA bundle, then a CA path. dnl Both auto-detections can be skipped by --without-ca-* diff --git a/configure.ac b/configure.ac index fddae6a630..5dde9f6c96 100644 --- a/configure.ac +++ b/configure.ac @@ -277,6 +277,12 @@ AS_HELP_STRING([--with-rustls=PATH],[where to look for Rustls, PATH points to th fi ]) +OPT_APPLE_SECTRUST=$curl_cv_apple +AC_ARG_WITH(apple-sectrust,dnl +AS_HELP_STRING([--with-apple-sectrust],[enable Apple OS native certificate verification]),[ + OPT_APPLE_SECTRUST=$withval + ]) + AC_PATH_PROG(PERL, perl,, $PATH:/usr/local/bin/perl:/usr/bin/:/usr/local/bin) AC_SUBST(PERL) AM_CONDITIONAL(PERL, test -n "$PERL") @@ -2016,6 +2022,7 @@ CURL_WITH_GNUTLS CURL_WITH_MBEDTLS CURL_WITH_WOLFSSL CURL_WITH_RUSTLS +CURL_WITH_APPLE_SECTRUST dnl link required libraries for USE_WIN32_CRYPTO or SCHANNEL_ENABLED if test "x$USE_WIN32_CRYPTO" = "x1" -o "x$SCHANNEL_ENABLED" = "x1"; then @@ -5588,6 +5595,7 @@ AC_MSG_NOTICE([Configured to build curl/libcurl: Verbose errors: ${curl_verbose_msg} Code coverage: ${curl_coverage_msg} SSPI: ${curl_sspi_msg} + ca native: ${ca_native} ca cert bundle: ${ca}${ca_warning} ca cert path: ${capath}${capath_warning} ca cert embed: ${CURL_CA_EMBED_msg} diff --git a/docs/INSTALL-CMAKE.md b/docs/INSTALL-CMAKE.md index 566fee7423..4c78752521 100644 --- a/docs/INSTALL-CMAKE.md +++ b/docs/INSTALL-CMAKE.md @@ -368,6 +368,7 @@ Details via CMake - `CURL_ZSTD`: Use zstd (`ON`, `OFF` or `AUTO`). Default: `AUTO` - `ENABLE_ARES`: Enable c-ares support. Default: `OFF` - `USE_APPLE_IDN`: Use Apple built-in IDN support. Default: `OFF` +- `USE_APPLE_SECTRUST`: Use Apple OS-native certificate verification. Default: `OFF` - `USE_LIBIDN2`: Use libidn2 for IDN support. Default: `ON` - `USE_LIBRTMP`: Enable librtmp from rtmpdump. Default: `OFF` - `USE_NGHTTP2`: Use nghttp2 library. Default: `ON` diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 80fcb93216..453071afd5 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -153,6 +153,25 @@ conflicting identical symbol names. When you build with multiple TLS backends, you can select the active one at runtime when curl starts up. +### Selecting TLS Trust Anchors Defaults + +Verifying a server certificate established a chain of trust that needs to +start somewhere. Those "root" certificates make the set of Trust Anchors. + +While the build system tries to find good defaults on the platform you +use, you may specify these explicitly. The following options are provided: + + - `--with-ca-bundle=FILE`: the file that libcurl loads default root + certificates from. + - `--with-ca-path=DIRECTORY`: a directory in which root certificates files + are found. + - `--with-ca-embed=FILE`: a file read *at build time* and added to `libcurl`. + - `--with-ca-fallback`: an OpenSSL specific option for delegating default + trust anchor selection to what OpenSSL thinks is best, *if* there are +no other certificates configured by the application. + - `--with-apple-sectrust`: use the system "SecTrust" service on Apple + operating systems for verification. (Added in 8.17.0) + ## MultiSSL and HTTP/3 HTTP/3 needs QUIC and QUIC needs TLS. Building libcurl with HTTP/3 and QUIC diff --git a/docs/SSLCERTS.md b/docs/SSLCERTS.md index 4efb9cf00b..de39545534 100644 --- a/docs/SSLCERTS.md +++ b/docs/SSLCERTS.md @@ -8,8 +8,10 @@ SPDX-License-Identifier: curl ## Native vs file based -If curl was built with Schannel support, then curl uses the system native CA -store for verification. All other TLS libraries use a file based CA store by +If curl was built with Schannel support, then curl uses the Windows native CA +store for verification. On Apple operating systems, it is possible to use Apple's +"SecTrust" services for certain TLS backends, details below. +All other TLS libraries use a file based CA store by default. ## Verification @@ -71,8 +73,10 @@ 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 -system's native CA store when verifying the certificate. +In several environments, in particular on Microsoft and Apple operating +systems, you can ask curl to use the system's native CA store when verifying +the certificate. Depending on how curl was built, this may already be the +default. With the curl command line tool: `--ca-native`. @@ -102,14 +106,46 @@ latest Firefox bundle. ## Native CA store -If curl was built with Schannel or was instructed to use the native CA Store, -then curl uses the certificates that are built into the OS. These are the same -certificates that appear in the Internet Options control panel (under Windows) -or Keychain Access application (under macOS). Any custom security rules for -certificates are honored. +### Windows + Schannel + +If curl was built with Schannel, then curl uses the certificates that are +built into the OS. These are the same certificates that appear in the +Internet Options control panel (under Windows). +Any custom security rules for certificates are honored. Schannel runs CRL checks on certificates unless peer verification is disabled. +### Apple + OpenSSL/GnuTLS + +When curl is built with Apple SecTrust enabled and uses an OpenSSL compatible +TLS backend or GnuTLS, the default verification is handled by that Apple +service. As in: + + curl https://example.com + +You may still provide your own certificates on the command line, such as: + + curl --cacert mycerts.pem https://example.com + +In this situation, Apple SecTrust is **not** used and verification is done +**only** with the trust anchors found in `mycerts.pem`. If you want **both** +Apple SecTrust and your own file to be considered, use: + + curl --ca-native --cacert mycerts.pem https://example.com + + +#### Other Combinations + +How well the use of native CA stores work in all other combinations depends +on the TLS backend and the OS. Many TLS backends offer functionality to access +the native CA on a range of operating systems. Some provide this only on specific +configurations. + +Specific support in curl exists for Windows and OpenSSL compatible TLS backends. +It tries to load the certificates from the Windows "CA" and "ROOT" stores for +transfers requesting the native CA. Due to Window's delayed population of those +stores, this might not always find all certificates. + ## HTTPS proxy curl can do HTTPS to the proxy separately from the connection to the server. diff --git a/docs/cmdline-opts/ca-native.md b/docs/cmdline-opts/ca-native.md index 7e833c9d15..a8e8c5e9a8 100644 --- a/docs/cmdline-opts/ca-native.md +++ b/docs/cmdline-opts/ca-native.md @@ -25,12 +25,14 @@ This option is independent of other CA certificate locations set at run time or build time. Those locations are searched in addition to the native CA store. This option works with OpenSSL and its forks (LibreSSL, BoringSSL, etc) on -Windows. (Added in 7.71.0) +Windows (Added in 7.71.0) and on Apple OS when libcurl is built with +Apple SecTrust enabled. (Added in 8.17.0) This option works with wolfSSL on Windows, Linux (Debian, Ubuntu, Gentoo, Fedora, RHEL), macOS, Android and iOS. (Added in 8.3.0) -This option works with GnuTLS. (Added in 8.5.0) +This option works with GnuTLS (Added in 8.5.0) and also uses Apple +SecTrust when libcurl is built with it. (Added in 8.17.0) This option works with rustls on Windows, macOS, Android and iOS. On Linux it is equivalent to using the Mozilla CA certificate bundle. When used with rustls diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 1447e53a1e..ff8144fb5a 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -77,6 +77,7 @@ LIB_VAUTH_HFILES = \ vauth/vauth.h LIB_VTLS_CFILES = \ + vtls/apple.c \ vtls/cipher_suite.c \ vtls/gtls.c \ vtls/hostcheck.c \ @@ -94,6 +95,7 @@ LIB_VTLS_CFILES = \ vtls/x509asn1.c LIB_VTLS_HFILES = \ + vtls/apple.h \ vtls/cipher_suite.h \ vtls/gtls.h \ vtls/hostcheck.h \ diff --git a/lib/curl_config.h.cmake b/lib/curl_config.h.cmake index bc8c1cd487..521a439eef 100644 --- a/lib/curl_config.h.cmake +++ b/lib/curl_config.h.cmake @@ -788,6 +788,9 @@ ${SIZEOF_TIME_T_CODE} /* to enable Apple IDN */ #cmakedefine USE_APPLE_IDN 1 +/* to enable Apple OS-native certificate verification */ +#cmakedefine USE_APPLE_SECTRUST 1 + /* Define to 1 if OpenSSL has the SSL_CTX_set_srp_username function. */ #cmakedefine HAVE_OPENSSL_SRP 1 diff --git a/lib/setopt.c b/lib/setopt.c index 3f628c443c..1c9ee42d41 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -2206,6 +2206,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, /* * Set CA info for SSL connection. Specify filename of the CA certificate */ + s->ssl.custom_cafile = TRUE; return Curl_setstropt(&s->str[STRING_SSL_CAFILE], ptr); #ifndef CURL_DISABLE_PROXY @@ -2214,6 +2215,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * Set CA info SSL connection for proxy. Specify filename of the * CA certificate */ + s->proxy_ssl.custom_cafile = TRUE; return Curl_setstropt(&s->str[STRING_SSL_CAFILE_PROXY], ptr); #endif @@ -2223,9 +2225,11 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * certificates which have been prepared using openssl c_rehash utility. */ #ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) + if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) { /* This does not work on Windows. */ + s->ssl.custom_capath = TRUE; return Curl_setstropt(&s->str[STRING_SSL_CAPATH], ptr); + } #endif return CURLE_NOT_BUILT_IN; #ifndef CURL_DISABLE_PROXY @@ -2235,9 +2239,11 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * CA certificates which have been prepared using openssl c_rehash utility. */ #ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) + if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) { /* This does not work on Windows. */ + s->proxy_ssl.custom_capath = TRUE; return Curl_setstropt(&s->str[STRING_SSL_CAPATH_PROXY], ptr); + } #endif return CURLE_NOT_BUILT_IN; #endif @@ -2900,8 +2906,10 @@ static CURLcode setopt_blob(struct Curl_easy *data, CURLoption option, * Specify entire PEM of the CA certificate */ #ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) + if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) { + s->ssl.custom_cablob = TRUE; return Curl_setblobopt(&s->blobs[BLOB_CAINFO], blob); + } #endif return CURLE_NOT_BUILT_IN; case CURLOPT_ISSUERCERT_BLOB: diff --git a/lib/url.c b/lib/url.c index 41a63890e6..0dface920d 100644 --- a/lib/url.c +++ b/lib/url.c @@ -435,36 +435,6 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->socks5_gssapi_nec = FALSE; #endif - /* Set the default CA cert bundle/path detected/specified at build time. - * - * If Schannel is the selected SSL backend then these locations are ignored. - * We allow setting CA location for Schannel when explicitly specified by - * the user via CURLOPT_CAINFO / --cacert. - */ - if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { -#ifdef CURL_CA_BUNDLE - result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE); - if(result) - return result; -#ifndef CURL_DISABLE_PROXY - result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY], - CURL_CA_BUNDLE); - if(result) - return result; -#endif -#endif -#ifdef CURL_CA_PATH - result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH); - if(result) - return result; -#ifndef CURL_DISABLE_PROXY - result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH); - if(result) - return result; -#endif -#endif - } - /* set default minimum TLS version */ #ifdef USE_SSL Curl_setopt_SSLVERSION(data, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT); diff --git a/lib/urldata.h b/lib/urldata.h index 3c7e634b00..d924b91194 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -285,6 +285,9 @@ struct ssl_config_data { BIT(native_ca_store); /* use the native ca store of operating system */ BIT(auto_client_cert); /* automatically locate and use a client certificate for authentication (Schannel) */ + BIT(custom_cafile); /* application has set custom CA file */ + BIT(custom_capath); /* application has set custom CA path */ + BIT(custom_cablob); /* application has set custom CA blob */ }; struct ssl_general_config { diff --git a/lib/vquic/vquic-tls.c b/lib/vquic/vquic-tls.c index 4bdd23c981..6576c5cd45 100644 --- a/lib/vquic/vquic-tls.c +++ b/lib/vquic/vquic-tls.c @@ -130,7 +130,7 @@ CURLcode Curl_vquic_tls_before_recv(struct curl_tls_ctx *ctx, { #ifdef USE_OPENSSL if(!ctx->ossl.x509_store_setup) { - CURLcode result = Curl_ssl_setup_x509_store(cf, data, ctx->ossl.ssl_ctx); + CURLcode result = Curl_ssl_setup_x509_store(cf, data, &ctx->ossl); if(result) return result; ctx->ossl.x509_store_setup = TRUE; @@ -170,7 +170,7 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, result = Curl_ossl_check_peer_cert(cf, data, &ctx->ossl, peer); #elif defined(USE_GNUTLS) if(conn_config->verifyhost) { - result = Curl_gtls_verifyserver(data, ctx->gtls.session, + result = Curl_gtls_verifyserver(cf, data, ctx->gtls.session, conn_config, &data->set.ssl, peer, data->set.str[STRING_SSL_PINNEDPUBLICKEY]); if(result) diff --git a/lib/vtls/apple.c b/lib/vtls/apple.c new file mode 100644 index 0000000000..b565c8f031 --- /dev/null +++ b/lib/vtls/apple.c @@ -0,0 +1,297 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* This file is for implementing all "generic" SSL functions that all libcurl + internals should use. It is then responsible for calling the proper + "backend" function. + + SSL-functions in libcurl should call functions in this source file, and not + to any specific SSL-layer. + + Curl_ssl_ - prefix for generic ones + + Note that this source code uses the functions of the configured SSL + backend via the global Curl_ssl instance. + + "SSL/TLS Strong Encryption: An Introduction" + https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html +*/ + +#include "../curl_setup.h" + +#include "../urldata.h" +#include "../cfilters.h" +#include "../curl_trc.h" +#include "vtls.h" +#include "apple.h" + +#if defined(USE_SSL) && defined(USE_APPLE_SECTRUST) +#include +#endif /* USE_SSL && USE_APPLE_SECTRUST */ + +/* The last #include files should be: */ +#include "../curl_memory.h" +#include "../memdebug.h" + + +#if defined(USE_SSL) && defined(USE_APPLE_SECTRUST) +#define SSL_SYSTEM_VERIFIER + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) \ + && MAC_OS_X_VERSION_MAX_ALLOWED >= 101400) \ + || (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) \ + && __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000) +#define SUPPORTS_SecTrustEvaluateWithError 1 +#endif + +#if defined(SUPPORTS_SecTrustEvaluateWithError) \ + && ((defined(MAC_OS_X_VERSION_MIN_REQUIRED) \ + && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) \ + || (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) \ + && __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)) +#define REQUIRES_SecTrustEvaluateWithError 1 +#endif + +#if defined(SUPPORTS_SecTrustEvaluateWithError) \ + && !defined(HAVE_BUILTIN_AVAILABLE) \ + && !defined(REQUIRES_SecTrustEvaluateWithError) +#undef SUPPORTS_SecTrustEvaluateWithError +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) \ + && MAC_OS_X_VERSION_MAX_ALLOWED >= 100900) \ + || (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) \ + && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) +#define SUPPORTS_SecOCSP 1 +#endif + +CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + size_t num_certs, + Curl_vtls_get_cert_der *der_cb, + void *cb_user_data, + const unsigned char *ocsp_buf, + size_t ocsp_len) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + CURLcode result = CURLE_OK; + SecTrustRef trust = NULL; + SecPolicyRef policy = NULL; + CFMutableArrayRef policies = NULL; + CFMutableArrayRef cert_array = NULL; + CFStringRef host_str = NULL; + CFErrorRef error = NULL; + OSStatus status = noErr; + CFStringRef error_ref = NULL; + char *err_desc = NULL; + size_t i; + + if(conn_config->verifyhost) { + host_str = CFStringCreateWithCString(NULL, + peer->sni ? peer->sni : peer->hostname, kCFStringEncodingUTF8); + if(!host_str) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + + policies = CFArrayCreateMutable(NULL, 2, &kCFTypeArrayCallBacks); + if(!policies) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + policy = SecPolicyCreateSSL(true, host_str); + if(!policy) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + CFArrayAppendValue(policies, policy); + CFRelease(policy); + policy = NULL; + +#if defined(HAVE_BUILTIN_AVAILABLE) && defined(SUPPORTS_SecOCSP) + { + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + if(!ssl_config->no_revoke) { + if(__builtin_available(macOS 10.9, iOS 7, tvOS 9, watchOS 2, *)) { + /* Even without this set, validation will seemingly-unavoidably fail + * for certificates that trustd already knows to be revoked. + * This policy further allows trustd to consult CRLs and OCSP data + * to determine revocation status (which it may then cache). */ + CFOptionFlags revocation_flags = kSecRevocationUseAnyAvailableMethod; +#if 0 + /* `revoke_best_effort` is off by default in libcurl. When we + * add `kSecRevocationRequirePositiveResponse` to the Apple + * Trust policies, it interprets this as it NEEDs a confirmation + * of a cert being NOT REVOKED. Which not in general available for + * certificates on the internet. + * It seems that applications using this policy are expected to PIN + * their certificate public keys or verification will fail. + * This does not seem to be what we want here. */ + if(!ssl_config->revoke_best_effort) { + revocation_flags |= kSecRevocationRequirePositiveResponse; + } +#endif + policy = SecPolicyCreateRevocation(revocation_flags); + if(!policy) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + CFArrayAppendValue(policies, policy); + } + } + } +#endif + + cert_array = CFArrayCreateMutable(NULL, num_certs, &kCFTypeArrayCallBacks); + if(!cert_array) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + for(i = 0; i < num_certs; i++) { + SecCertificateRef cert; + CFDataRef certdata; + unsigned char *der; + size_t der_len; + + result = der_cb(cf, data, cb_user_data, i, &der, &der_len); + if(result) + goto out; + + certdata = CFDataCreate(NULL, der, (CFIndex)der_len); + if(!certdata) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + cert = SecCertificateCreateWithData(NULL, certdata); + CFRelease(certdata); + if(!cert) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + CFArrayAppendValue(cert_array, cert); + CFRelease(cert); + } + + status = SecTrustCreateWithCertificates(cert_array, policies, &trust); + if(status != noErr || !trust) { + failf(data, "Apple SecTrust: failed to create validation trust"); + result = CURLE_PEER_FAILED_VERIFICATION; + goto out; + } + +#if defined(HAVE_BUILTIN_AVAILABLE) && defined(SUPPORTS_SecOCSP) + if(ocsp_len > 0) { + if(__builtin_available(macOS 10.9, iOS 7, tvOS 9, watchOS 2, *)) { + CFDataRef ocspdata = + CFDataCreate(NULL, ocsp_buf, (CFIndex)ocsp_len); + + status = SecTrustSetOCSPResponse(trust, ocspdata); + CFRelease(ocspdata); + if(status != noErr) { + failf(data, "Apple SecTrust: failed to set OCSP response: %i", + (int)status); + result = CURLE_PEER_FAILED_VERIFICATION; + goto out; + } + } + } +#else + (void)ocsp_buf; + (void)ocsp_len; +#endif + +#ifdef SUPPORTS_SecTrustEvaluateWithError +#if defined(HAVE_BUILTIN_AVAILABLE) + if(__builtin_available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *)) { +#else + if(1) { +#endif + result = SecTrustEvaluateWithError(trust, &error) ? + CURLE_OK : CURLE_PEER_FAILED_VERIFICATION; + if(error) { + CFIndex code = CFErrorGetCode(error); + error_ref = CFErrorCopyDescription(error); + + if(error_ref) { + CFIndex size = CFStringGetMaximumSizeForEncoding( + CFStringGetLength(error_ref), kCFStringEncodingUTF8); + err_desc = malloc(size + 1); + if(err_desc) { + if(!CFStringGetCString(error_ref, err_desc, size, + kCFStringEncodingUTF8)) { + free(err_desc); + err_desc = NULL; + } + } + } + infof(data, "Apple SecTrust failure %ld%s%s", code, + err_desc ? ": " : "", err_desc ? err_desc : ""); + } + } + else +#endif /* SUPPORTS_SecTrustEvaluateWithError */ + { +#ifndef REQUIRES_SecTrustEvaluateWithError + SecTrustResultType sec_result; + status = SecTrustEvaluate(trust, &sec_result); + + if(status != noErr) { + failf(data, "Apple SecTrust verification failed: error %i", (int)status); + } + else if((status == kSecTrustResultUnspecified) || + (status == kSecTrustResultProceed)) { + /* "unspecified" means system-trusted with no explicit user setting */ + result = CURLE_OK; + } +#endif /* REQUIRES_SecTrustEvaluateWithError */ + } + +out: + free(err_desc); + if(error_ref) + CFRelease(error_ref); + if(error) + CFRelease(error); + if(host_str) + CFRelease(host_str); + if(policies) + CFRelease(policies); + if(policy) + CFRelease(policy); + if(cert_array) + CFRelease(cert_array); + if(trust) + CFRelease(trust); + return result; +} + +#endif /* USE_SSL && USE_APPLE_SECTRUST */ diff --git a/lib/vtls/apple.h b/lib/vtls/apple.h new file mode 100644 index 0000000000..c965a449f1 --- /dev/null +++ b/lib/vtls/apple.h @@ -0,0 +1,55 @@ +#ifndef HEADER_CURL_VTLS_APPLE_H +#define HEADER_CURL_VTLS_APPLE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jan Venekamp, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "../curl_setup.h" + +#if defined(USE_SSL) && defined(USE_APPLE_SECTRUST) +struct Curl_cfilter; +struct Curl_easy; +struct ssl_peer; + +/* Get the DER encoded i-th certificate in the server handshake */ +typedef CURLcode Curl_vtls_get_cert_der(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data, + size_t i, + unsigned char **pder, + size_t *pder_len); + +/* Ask Apple's Security framework to verify the certificate chain + * send by the peer. On CURLE_OK it has been verified. + */ +CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + size_t num_certs, + Curl_vtls_get_cert_der *der_cb, + void *cb_user_data, + const unsigned char *ocsp_buf, + size_t ocsp_len); +#endif /* USE_SSL && USE_APPLE_SECTRUST */ + +#endif /* HEADER_CURL_VTLS_APPLE_H */ diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 2db73ca2d5..3d69ab1d1c 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -48,6 +48,7 @@ #include "vtls.h" #include "vtls_int.h" #include "vtls_scache.h" +#include "apple.h" #include "../vauth/vauth.h" #include "../parsedate.h" #include "../connect.h" /* for the connect timeout */ @@ -454,62 +455,75 @@ static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, { struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf); struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + bool creds_are_empty = TRUE; int rc; - if(config->verifypeer) { - bool imported_native_ca = FALSE; + if(!config->verifypeer) { + infof(data, "SSL Trust: peer verification disabled"); + return CURLE_OK; + } - if(ssl_config->native_ca_store) { - rc = gnutls_certificate_set_x509_system_trust(creds); - if(rc < 0) - infof(data, "error reading native ca store (%s), continuing anyway", - gnutls_strerror(rc)); - else { - infof(data, "found %d certificates in native ca store", rc); - if(rc > 0) - imported_native_ca = TRUE; - } + infof(data, "SSL Trust Anchors:"); + if(ssl_config->native_ca_store) { +#ifdef USE_APPLE_SECTRUST + infof(data, " Native: Apple SecTrust"); + creds_are_empty = FALSE; +#else + rc = gnutls_certificate_set_x509_system_trust(creds); + if(rc < 0) + infof(data, "error reading native ca store (%s), continuing anyway", + gnutls_strerror(rc)); + else { + infof(data, " Native: %d certificates from system trust", rc); + if(rc > 0) + creds_are_empty = FALSE; } +#endif + } - if(config->CAfile) { - /* set the trusted CA cert bundle file */ - gnutls_certificate_set_verify_flags(creds, - GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); - - rc = gnutls_certificate_set_x509_trust_file(creds, - config->CAfile, - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - infof(data, "error reading ca cert file %s (%s)%s", - config->CAfile, gnutls_strerror(rc), - (imported_native_ca ? ", continuing anyway" : "")); - if(!imported_native_ca) { - ssl_config->certverifyresult = rc; - return CURLE_SSL_CACERT_BADFILE; - } + if(config->CAfile) { + /* set the trusted CA cert bundle file */ + gnutls_certificate_set_verify_flags(creds, + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); + + rc = gnutls_certificate_set_x509_trust_file(creds, + config->CAfile, + GNUTLS_X509_FMT_PEM); + creds_are_empty = creds_are_empty && (rc <= 0); + if(rc < 0) { + infof(data, "error reading ca cert file %s (%s)%s", + config->CAfile, gnutls_strerror(rc), + (creds_are_empty ? "" : ", continuing anyway")); + if(creds_are_empty) { + ssl_config->certverifyresult = rc; + return CURLE_SSL_CACERT_BADFILE; } - else - infof(data, "found %d certificates in %s", rc, config->CAfile); } + else + infof(data, " CAfile: %d certificates in %s", rc, config->CAfile); + } - if(config->CApath) { - /* set the trusted CA cert directory */ - rc = gnutls_certificate_set_x509_trust_dir(creds, config->CApath, - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - infof(data, "error reading ca cert file %s (%s)%s", - config->CApath, gnutls_strerror(rc), - (imported_native_ca ? ", continuing anyway" : "")); - if(!imported_native_ca) { - ssl_config->certverifyresult = rc; - return CURLE_SSL_CACERT_BADFILE; - } + if(config->CApath) { + /* set the trusted CA cert directory */ + rc = gnutls_certificate_set_x509_trust_dir(creds, config->CApath, + GNUTLS_X509_FMT_PEM); + creds_are_empty = creds_are_empty && (rc <= 0); + if(rc < 0) { + infof(data, "error reading ca cert file %s (%s)%s", + config->CApath, gnutls_strerror(rc), + (creds_are_empty ? "" : ", continuing anyway")); + if(creds_are_empty) { + ssl_config->certverifyresult = rc; + return CURLE_SSL_CACERT_BADFILE; } - else - infof(data, "found %d certificates in %s", rc, config->CApath); } + else + infof(data, " CApath: %d certificates in %s", rc, config->CApath); } + if(creds_are_empty) + infof(data, " no trust anchors configured"); + if(config->CRLfile) { /* set the CRL list file */ rc = gnutls_certificate_set_x509_crl_file(creds, config->CRLfile, @@ -520,7 +534,7 @@ static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, return CURLE_SSL_CRL_BADFILE; } else - infof(data, "found %d CRL in %s", rc, config->CRLfile); + infof(data, " CRLfile: %d CRL in %s", rc, config->CRLfile); } return CURLE_OK; @@ -1520,31 +1534,76 @@ out: return result; } +struct gtls_cert_chain { + const gnutls_datum_t *certs; + unsigned int num_certs; +}; + +#ifdef USE_APPLE_SECTRUST +static CURLcode gtls_chain_get_der(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data, + size_t i, + unsigned char **pder, + size_t *pder_len) +{ + struct gtls_cert_chain *chain = user_data; + + (void)cf; + (void)data; + *pder_len = 0; + *pder = NULL; + + if(i >= chain->num_certs) + return CURLE_TOO_LARGE; + *pder = chain->certs[i].data; + *pder_len = (size_t)chain->certs[i].size; + return CURLE_OK; +} + +static CURLcode glts_apple_verify(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + struct gtls_cert_chain *chain, + bool *pverified) +{ + CURLcode result; + + result = Curl_vtls_apple_verify(cf, data, peer, chain->num_certs, + gtls_chain_get_der, chain, + NULL, 0); + *pverified = !result; + if(*pverified) + infof(data, " SSL certificate verified by Apple SecTrust."); + return result; +} +#endif /* USE_APPLE_SECTRUST */ + CURLcode -Curl_gtls_verifyserver(struct Curl_easy *data, +Curl_gtls_verifyserver(struct Curl_cfilter *cf, + struct Curl_easy *data, gnutls_session_t session, struct ssl_primary_config *config, struct ssl_config_data *ssl_config, struct ssl_peer *peer, const char *pinned_key) { - unsigned int cert_list_size; - const gnutls_datum_t *chainp; - unsigned int verify_status = 0; + struct gtls_cert_chain chain; gnutls_x509_crt_t x509_cert = NULL, x509_issuer = NULL; time_t certclock; int rc; CURLcode result = CURLE_OK; long * const certverifyresult = &ssl_config->certverifyresult; + (void)cf; /* This function will return the peer's raw certificate (chain) as sent by the peer. These certificates are in raw format (DER encoded for X.509). In case of a X.509 then a certificate list may be present. The first certificate in the list is the peer's certificate, following the issuer's certificate, then the issuer's issuer etc. */ - chainp = gnutls_certificate_get_peers(session, &cert_list_size); - if(!chainp) { + chain.certs = gnutls_certificate_get_peers(session, &chain.num_certs); + if(!chain.certs) { if(config->verifypeer || config->verifyhost || config->issuercert) { @@ -1567,16 +1626,16 @@ Curl_gtls_verifyserver(struct Curl_easy *data, infof(data, " common name: WARNING could not obtain"); } - if(data->set.ssl.certinfo && chainp) { + if(data->set.ssl.certinfo && chain.certs) { unsigned int i; - result = Curl_ssl_init_certinfo(data, (int)cert_list_size); + result = Curl_ssl_init_certinfo(data, (int)chain.num_certs); if(result) goto out; - for(i = 0; i < cert_list_size; i++) { - const char *beg = (const char *) chainp[i].data; - const char *end = beg + chainp[i].size; + for(i = 0; i < chain.num_certs; i++) { + const char *beg = (const char *) chain.certs[i].data; + const char *end = beg + chain.certs[i].size; result = Curl_extract_certinfo(data, (int)i, beg, end); if(result) @@ -1585,13 +1644,15 @@ Curl_gtls_verifyserver(struct Curl_easy *data, } if(config->verifypeer) { - /* This function will try to verify the peer's certificate and return its - status (trusted, invalid etc.). The value of status should be one or - more of the gnutls_certificate_status_t enumerated elements bitwise - or'd. To avoid denial of service attacks some default upper limits - regarding the certificate key size and chain size are set. To override - them use gnutls_certificate_set_verify_limits(). */ - + bool verified = FALSE; + unsigned int verify_status = 0; + /* This function will try to verify the peer's certificate and return + its status (trusted, invalid etc.). The value of status should be + one or more of the gnutls_certificate_status_t enumerated elements + bitwise or'd. To avoid denial of service attacks some default + upper limits regarding the certificate key size and chain size + are set. To override them use + gnutls_certificate_set_verify_limits(). */ rc = gnutls_certificate_verify_peers2(session, &verify_status); if(rc < 0) { failf(data, "server cert verify failed: %d", rc); @@ -1599,37 +1660,109 @@ Curl_gtls_verifyserver(struct Curl_easy *data, result = CURLE_SSL_CONNECT_ERROR; goto out; } - *certverifyresult = verify_status; + verified = !(verify_status & GNUTLS_CERT_INVALID); + if(verified) + infof(data, " SSL certificate verified by GnuTLS"); + +#ifdef USE_APPLE_SECTRUST + if(!verified && ssl_config->native_ca_store && + (verify_status & GNUTLS_CERT_SIGNER_NOT_FOUND)) { + result = glts_apple_verify(cf, data, peer, &chain, &verified); + if(result && (result != CURLE_PEER_FAILED_VERIFICATION)) + goto out; /* unexpected error */ + if(verified) { + infof(data, "SSL certificate verified via Apple SecTrust."); + *certverifyresult = 0; + } + } +#endif + + if(!verified) { + /* verify_status is a bitmask of gnutls_certificate_status bits */ + const char *cause = "certificate error, no details available"; + if(verify_status & GNUTLS_CERT_EXPIRED) + cause = "certificate has expired"; + else if(verify_status & GNUTLS_CERT_SIGNER_NOT_FOUND) + cause = "certificate signer not trusted"; + else if(verify_status & GNUTLS_CERT_INSECURE_ALGORITHM) + cause = "certificate uses insecure algorithm"; + else if(verify_status & GNUTLS_CERT_INVALID_OCSP_STATUS) + cause = "attached OCSP status response is invalid"; + failf(data, "SSL certificate verification failed: %s. (CAfile: %s " + "CRLfile: %s)", cause, + config->CAfile ? config->CAfile : "none", + ssl_config->primary.CRLfile ? + ssl_config->primary.CRLfile : "none"); + result = CURLE_PEER_FAILED_VERIFICATION; + goto out; + } + } + else + infof(data, " SSL certificate verification SKIPPED"); + + /* initialize an X.509 certificate structure. */ + gnutls_x509_crt_init(&x509_cert); + + if(chain.certs) + /* convert the given DER or PEM encoded Certificate to the native + gnutls_x509_crt_t format */ + gnutls_x509_crt_import(x509_cert, chain.certs, GNUTLS_X509_FMT_DER); + + /* Check for time-based validity */ + certclock = gnutls_x509_crt_get_expiration_time(x509_cert); - /* verify_status is a bitmask of gnutls_certificate_status bits */ - if(verify_status & GNUTLS_CERT_INVALID) { + if(certclock == (time_t)-1) { + if(config->verifypeer) { + failf(data, "server cert expiration date verify failed"); + *certverifyresult = GNUTLS_CERT_EXPIRED; + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + else + infof(data, " SSL certificate expiration date verify FAILED"); + } + else { + if(certclock < time(NULL)) { if(config->verifypeer) { - const char *cause = "certificate error, no details available"; - if(verify_status & GNUTLS_CERT_EXPIRED) - cause = "certificate has expired"; - else if(verify_status & GNUTLS_CERT_SIGNER_NOT_FOUND) - cause = "certificate signer not trusted"; - else if(verify_status & GNUTLS_CERT_INSECURE_ALGORITHM) - cause = "certificate uses insecure algorithm"; - else if(verify_status & GNUTLS_CERT_INVALID_OCSP_STATUS) - cause = "attached OCSP status response is invalid"; - failf(data, "server verification failed: %s. (CAfile: %s " - "CRLfile: %s)", cause, - config->CAfile ? config->CAfile : "none", - ssl_config->primary.CRLfile ? - ssl_config->primary.CRLfile : "none"); + failf(data, "server certificate expiration date has passed."); + *certverifyresult = GNUTLS_CERT_EXPIRED; result = CURLE_PEER_FAILED_VERIFICATION; goto out; } else - infof(data, " server certificate verification FAILED"); + infof(data, " SSL certificate expiration date FAILED"); } else - infof(data, " server certificate verification OK"); + infof(data, " SSL certificate expiration date OK"); + } + + certclock = gnutls_x509_crt_get_activation_time(x509_cert); + + if(certclock == (time_t)-1) { + if(config->verifypeer) { + failf(data, "server cert activation date verify failed"); + *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED; + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + else + infof(data, " SSL certificate activation date verify FAILED"); + } + else { + if(certclock > time(NULL)) { + if(config->verifypeer) { + failf(data, "server certificate not activated yet."); + *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED; + result = CURLE_PEER_FAILED_VERIFICATION; + goto out; + } + else + infof(data, " SSL certificate activation date FAILED"); + } + else + infof(data, " SSL certificate activation date OK"); } - else - infof(data, " server certificate verification SKIPPED"); if(config->verifystatus) { result = gtls_verify_ocsp_status(data, session); @@ -1637,15 +1770,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, goto out; } else - infof(data, " server certificate status verification SKIPPED"); - - /* initialize an X.509 certificate structure. */ - gnutls_x509_crt_init(&x509_cert); - - if(chainp) - /* convert the given DER or PEM encoded Certificate to the native - gnutls_x509_crt_t format */ - gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER); + infof(data, " SSL certificate status verification SKIPPED"); if(config->issuercert) { gnutls_datum_t issuerp; @@ -1660,7 +1785,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, result = CURLE_SSL_ISSUER_ERROR; goto out; } - infof(data, " server certificate issuer check OK (Issuer Cert: %s)", + infof(data, " SSL certificate issuer check OK (Issuer Cert: %s)", config->issuercert ? config->issuercert : "none"); } @@ -1725,61 +1850,6 @@ Curl_gtls_verifyserver(struct Curl_easy *data, if(result) goto out; - /* Check for time-based validity */ - certclock = gnutls_x509_crt_get_expiration_time(x509_cert); - - if(certclock == (time_t)-1) { - if(config->verifypeer) { - failf(data, "server cert expiration date verify failed"); - *certverifyresult = GNUTLS_CERT_EXPIRED; - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - else - infof(data, " server certificate expiration date verify FAILED"); - } - else { - if(certclock < time(NULL)) { - if(config->verifypeer) { - failf(data, "server certificate expiration date has passed."); - *certverifyresult = GNUTLS_CERT_EXPIRED; - result = CURLE_PEER_FAILED_VERIFICATION; - goto out; - } - else - infof(data, " server certificate expiration date FAILED"); - } - else - infof(data, " server certificate expiration date OK"); - } - - certclock = gnutls_x509_crt_get_activation_time(x509_cert); - - if(certclock == (time_t)-1) { - if(config->verifypeer) { - failf(data, "server cert activation date verify failed"); - *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED; - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - else - infof(data, " server certificate activation date verify FAILED"); - } - else { - if(certclock > time(NULL)) { - if(config->verifypeer) { - failf(data, "server certificate not activated yet."); - *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED; - result = CURLE_PEER_FAILED_VERIFICATION; - goto out; - } - else - infof(data, " server certificate activation date FAILED"); - } - else - infof(data, " server certificate activation date OK"); - } - if(pinned_key) { result = pkp_pin_peer_pubkey(data, x509_cert, pinned_key); if(result != CURLE_OK) { @@ -1814,7 +1884,7 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, #endif CURLcode result; - result = Curl_gtls_verifyserver(data, session, conn_config, ssl_config, + result = Curl_gtls_verifyserver(cf, data, session, conn_config, ssl_config, &connssl->peer, pinned_key); if(result) goto out; diff --git a/lib/vtls/gtls.h b/lib/vtls/gtls.h index 01f8b43ac8..afbe51eb9c 100644 --- a/lib/vtls/gtls.h +++ b/lib/vtls/gtls.h @@ -100,7 +100,8 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, struct Curl_easy *data, struct gtls_ctx *gtls); -CURLcode Curl_gtls_verifyserver(struct Curl_easy *data, +CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, + struct Curl_easy *data, gnutls_session_t session, struct ssl_primary_config *config, struct ssl_config_data *ssl_config, diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 01c0b5513c..193723b649 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -67,6 +67,7 @@ #include "../strdup.h" #include "../strerror.h" #include "../curl_printf.h" +#include "apple.h" #include #include @@ -738,7 +739,7 @@ static int ossl_bio_cf_in_read(BIO *bio, char *buf, int blen) /* Before returning server replies to the SSL instance, we need * to have setup the x509 store or verification will fail. */ if(!octx->x509_store_setup) { - r2 = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); + r2 = Curl_ssl_setup_x509_store(cf, data, octx); if(r2) { BIO_clear_retry_flags(bio); octx->io_result = r2; @@ -1816,6 +1817,7 @@ static CURLcode client_cert(struct Curl_easy *data, return CURLE_OK; } +#ifndef CURL_DISABLE_VERBOSE_STRINGS /* returns non-zero on failure */ static CURLcode x509_name_oneline(X509_NAME *a, struct dynbuf *d) { @@ -1837,6 +1839,7 @@ static CURLcode x509_name_oneline(X509_NAME *a, struct dynbuf *d) } return result; } +#endif /** * Global SSL init @@ -2392,7 +2395,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, string and we cannot match it. */ Curl_cert_hostcheck(altptr, altlen, peer->hostname, hostlen)) { matched = TRUE; - infof(data, " subjectAltName: host \"%s\" matched cert's \"%.*s\"", + infof(data, " subjectAltName: \"%s\" matches cert's \"%.*s\"", peer->dispname, (int)altlen, altptr); } break; @@ -2403,7 +2406,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, if((altlen == addrlen) && !memcmp(altptr, &addr, altlen)) { matched = TRUE; infof(data, - " subjectAltName: host \"%s\" matched cert's IP address!", + " subjectAltName: \"%s\" matches cert's IP address!", peer->dispname); } break; @@ -3169,17 +3172,17 @@ static CURLcode load_cacert_from_memory(X509_STORE *store, } #ifdef USE_WIN32_CRYPTO -static CURLcode import_windows_cert_store(struct Curl_easy *data, - const char *name, - X509_STORE *store, - bool *imported) +static CURLcode ossl_win_load_store(struct Curl_easy *data, + const char *win_store, + X509_STORE *store, + bool *padded) { CURLcode result = CURLE_OK; HCERTSTORE hStore; - *imported = FALSE; + *padded = FALSE; - hStore = CertOpenSystemStoreA(0, name); + hStore = CertOpenSystemStoreA(0, win_store); if(hStore) { PCCERT_CONTEXT pContext = NULL; /* The array of enhanced key usage OIDs will vary per certificate and @@ -3295,7 +3298,7 @@ static CURLcode import_windows_cert_store(struct Curl_easy *data, #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) infof(data, "SSL: Imported cert"); #endif - *imported = TRUE; + *padded = TRUE; } X509_free(x509); } @@ -3310,125 +3313,181 @@ static CURLcode import_windows_cert_store(struct Curl_easy *data, return result; } -#endif -static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, +static CURLcode ossl_windows_load_anchors(struct Curl_cfilter *cf, + struct Curl_easy *data, + X509_STORE *store, + bool *padded) +{ + /* Import certificates from the Windows root certificate store if + requested. + https://stackoverflow.com/questions/9507184/ + https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 + https://datatracker.ietf.org/doc/html/rfc5280 */ + const char *win_stores[] = { + "ROOT", /* Trusted Root Certification Authorities */ + "CA" /* Intermediate Certification Authorities */ + }; + size_t i; + CURLcode result = CURLE_OK; + + *padded = FALSE; + for(i = 0; i < CURL_ARRAYSIZE(win_stores); ++i) { + bool store_added = FALSE; + result = ossl_win_load_store(data, win_stores[i], store, &store_added); + if(result) + return result; + if(store_added) { + CURL_TRC_CF(data, cf, "added trust anchors from Windows %s store", + win_stores[i]); + *padded = TRUE; + } + else + infof(data, "error importing Windows %s store, continuing anyway", + win_stores[i]); + } + return result; +} + +#endif /* USE_WIN32_CRYPTO */ + +static CURLcode ossl_load_trust_anchors(struct Curl_cfilter *cf, struct Curl_easy *data, + struct ossl_ctx *octx, X509_STORE *store) { 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; - X509_LOOKUP *lookup = NULL; - 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); + (conn_config->ca_info_blob ? NULL : conn_config->CAfile); const char * const ssl_capath = conn_config->CApath; - const char * const ssl_crlfile = ssl_config->primary.CRLfile; - const bool verifypeer = conn_config->verifypeer; - bool imported_native_ca = FALSE; - bool imported_ca_info_blob = FALSE; + bool have_native_check = FALSE; - CURL_TRC_CF(data, cf, "ossl_populate_x509_store, path=%s, blob=%d", - ssl_cafile ? ssl_cafile : "none", !!ca_info_blob); - if(!store) - return CURLE_OUT_OF_MEMORY; - - if(verifypeer) { + octx->store_is_empty = TRUE; + if(ssl_config->native_ca_store) { #ifdef USE_WIN32_CRYPTO - /* Import certificates from the Windows root certificate store if - requested. - https://stackoverflow.com/questions/9507184/ - https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 - https://datatracker.ietf.org/doc/html/rfc5280 */ - if(ssl_config->native_ca_store) { - const char *storeNames[] = { - "ROOT", /* Trusted Root Certification Authorities */ - "CA" /* Intermediate Certification Authorities */ - }; - size_t i; - for(i = 0; i < CURL_ARRAYSIZE(storeNames); ++i) { - bool imported = FALSE; - result = import_windows_cert_store(data, storeNames[i], store, - &imported); - if(result) - return result; - if(imported) { - infof(data, "successfully imported Windows %s store", storeNames[i]); - imported_native_ca = TRUE; - } - else - infof(data, "error importing Windows %s store, continuing anyway", - storeNames[i]); - } + bool added = FALSE; + result = ossl_windows_load_anchors(cf, data, store, &added); + if(result) + return result; + if(added) { + infof(data, " Native: Windows System Stores ROOT+CA"); + octx->store_is_empty = FALSE; } +#elif defined(USE_APPLE_SECTRUST) + infof(data, " Native: Apple SecTrust"); + have_native_check = TRUE; #endif - if(ca_info_blob) { - result = load_cacert_from_memory(store, ca_info_blob); - if(result) { - failf(data, "error importing CA certificate blob"); - return result; - } - else { - imported_ca_info_blob = TRUE; - infof(data, "successfully imported CA certificate blob"); - } + } + + if(conn_config->ca_info_blob) { + result = load_cacert_from_memory(store, conn_config->ca_info_blob); + if(result) { + failf(data, "error adding trust anchors from certificate blob: %d", + result); + return result; } + infof(data, " CA Blob from configuration"); + octx->store_is_empty = FALSE; + } - if(ssl_cafile || ssl_capath) { + if(ssl_cafile || ssl_capath) { #ifdef HAVE_OPENSSL3 - /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ - if(ssl_cafile && !X509_STORE_load_file(store, ssl_cafile)) { - if(!imported_native_ca && !imported_ca_info_blob) { + /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ + if(ssl_cafile) { + if(!X509_STORE_load_file(store, ssl_cafile)) { + if(octx->store_is_empty && !have_native_check) { /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate file: %s", ssl_cafile); + failf(data, "error adding trust anchors from file: %s", ssl_cafile); return CURLE_SSL_CACERT_BADFILE; } else infof(data, "error setting certificate file, continuing anyway"); } - if(ssl_capath && !X509_STORE_load_path(store, ssl_capath)) { - if(!imported_native_ca && !imported_ca_info_blob) { + infof(data, " CAfile: %s", ssl_cafile); + octx->store_is_empty = FALSE; + } + if(ssl_capath) { + if(!X509_STORE_load_path(store, ssl_capath)) { + if(octx->store_is_empty && !have_native_check) { /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate path: %s", ssl_capath); + failf(data, "error adding trust anchors from path: %s", ssl_capath); return CURLE_SSL_CACERT_BADFILE; } else infof(data, "error setting certificate path, continuing anyway"); } + infof(data, " CApath: %s", ssl_capath); + octx->store_is_empty = FALSE; + } #else - /* tell OpenSSL where to find CA certificates that are used to verify the - server's certificate. */ - if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) { - if(!imported_native_ca && !imported_ca_info_blob) { - /* 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 { - infof(data, "error setting certificate verify locations," - " continuing anyway"); - } + /* tell OpenSSL where to find CA certificates that are used to verify the + server's certificate. */ + if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) { + if(octx->store_is_empty && !have_native_check) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error adding trust anchors from locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + return CURLE_SSL_CACERT_BADFILE; + } + else { + infof(data, "error setting certificate verify locations," + " continuing anyway"); } -#endif - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); } + if(ssl_cafile) + infof(data, " CAfile: %s", ssl_cafile); + if(ssl_capath) + infof(data, " CApath: %s", ssl_capath); + octx->store_is_empty = FALSE; +#endif + } #ifdef CURL_CA_FALLBACK - if(!ssl_cafile && !ssl_capath && - !imported_native_ca && !imported_ca_info_blob) { - /* verifying the peer without any CA certificates will not - work so use OpenSSL's built-in default as fallback */ - X509_STORE_set_default_paths(store); - } + if(octx->store_is_empty) { + /* verifying the peer without any CA certificates will not + work so use OpenSSL's built-in default as fallback */ + X509_STORE_set_default_paths(store); + infof(data, " OpenSSL default paths (fallback)"); + octx->store_is_empty = FALSE; + } #endif + if(octx->store_is_empty && !have_native_check) + infof(data, " no trust anchors configured"); + + return result; +} + +static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ossl_ctx *octx, + X509_STORE *store) +{ + 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; + X509_LOOKUP *lookup = NULL; + const char * const ssl_crlfile = ssl_config->primary.CRLfile; + + CURL_TRC_CF(data, cf, "configuring OpenSSL's x509 trust store"); + if(!store) + return CURLE_OUT_OF_MEMORY; + + if(!conn_config->verifypeer) { + infof(data, "SSL Trust: peer verification disabled"); + return CURLE_OK; } + infof(data, "SSL Trust Anchors:"); + result = ossl_load_trust_anchors(cf, data, octx, store); + if(result) + return result; + + /* Does not make sense to load a CRL file without peer verification */ if(ssl_crlfile) { /* tell OpenSSL where to find CRL file that is used to check certificate * revocation */ @@ -3438,33 +3497,28 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, failf(data, "error loading CRL file: %s", ssl_crlfile); return CURLE_SSL_CRL_BADFILE; } - /* Everything is fine. */ - infof(data, "successfully loaded CRL file:"); X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); - - infof(data, " CRLfile: %s", ssl_crlfile); + infof(data, " CRLfile: %s", ssl_crlfile); } - if(verifypeer) { - /* Try building a chain using issuers in the trusted store first to avoid - problems with server-sent legacy intermediates. Newer versions of - OpenSSL do alternate chain checking by default but we do not know how to - determine that in a reliable manner. - https://web.archive.org/web/20190422050538/rt.openssl.org/Ticket/Display.html?id=3621 + /* Try building a chain using issuers in the trusted store first to avoid + problems with server-sent legacy intermediates. Newer versions of + OpenSSL do alternate chain checking by default but we do not know how to + determine that in a reliable manner. + https://web.archive.org/web/20190422050538/rt.openssl.org/Ticket/Display.html?id=3621 + */ + X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST); + if(!ssl_config->no_partialchain && !ssl_crlfile) { + /* Have intermediate certificates in the trust store be treated as + trust-anchors, in the same way as self-signed root CA certificates + are. This allows users to verify servers using the intermediate cert + only, instead of needing the whole chain. + + Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we + cannot do partial chains with a CRL check. */ - X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST); - if(!ssl_config->no_partialchain && !ssl_crlfile) { - /* Have intermediate certificates in the trust store be treated as - trust-anchors, in the same way as self-signed root CA certificates - are. This allows users to verify servers using the intermediate cert - only, instead of needing the whole chain. - - Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we - cannot do partial chains with a CRL check. - */ - X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN); - } + X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN); } return result; @@ -3479,6 +3533,7 @@ struct ossl_x509_share { char *CAfile; /* CAfile path used to generate X509 store */ X509_STORE *store; /* cached X509 store or NULL if none */ struct curltime time; /* when the cached store was created */ + BIT(store_is_empty); /* no certs/paths/blobs are in the store */ }; static void oss_x509_share_free(void *key, size_t key_len, void *p) @@ -3523,13 +3578,15 @@ ossl_cached_x509_store_different(struct Curl_cfilter *cf, } static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf, - const struct Curl_easy *data) + const struct Curl_easy *data, + bool *pempty) { struct Curl_multi *multi = data->multi; struct ossl_x509_share *share; X509_STORE *store = NULL; DEBUGASSERT(multi); + *pempty = TRUE; share = multi ? Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_OSSL_X509_KEY), sizeof(MPROTO_OSSL_X509_KEY)-1) : NULL; @@ -3537,6 +3594,7 @@ static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf, !ossl_cached_x509_store_expired(data, share) && !ossl_cached_x509_store_different(cf, share)) { store = share->store; + *pempty = share->store_is_empty; } return store; @@ -3544,7 +3602,8 @@ static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf, static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, const struct Curl_easy *data, - X509_STORE *store) + X509_STORE *store, + bool is_empty) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct Curl_multi *multi = data->multi; @@ -3588,19 +3647,20 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, share->time = curlx_now(); share->store = store; + share->store_is_empty = is_empty; share->CAfile = CAfile; } } CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, - SSL_CTX *ssl_ctx) + struct ossl_ctx *octx) { 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; X509_STORE *cached_store; - bool cache_criteria_met; + bool cache_criteria_met, is_empty; /* 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 @@ -3614,16 +3674,17 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, ERR_set_mark(); - cached_store = ossl_get_cached_x509_store(cf, data); + cached_store = ossl_get_cached_x509_store(cf, data, &is_empty); if(cached_store && cache_criteria_met && X509_STORE_up_ref(cached_store)) { - SSL_CTX_set_cert_store(ssl_ctx, cached_store); + SSL_CTX_set_cert_store(octx->ssl_ctx, cached_store); + octx->store_is_empty = is_empty; } else { - X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx); + X509_STORE *store = SSL_CTX_get_cert_store(octx->ssl_ctx); - result = ossl_populate_x509_store(cf, data, store); + result = ossl_populate_x509_store(cf, data, octx, store); if(result == CURLE_OK && cache_criteria_met) { - ossl_set_cached_x509_store(cf, data, store); + ossl_set_cached_x509_store(cf, data, store, octx->store_is_empty); } } @@ -3634,15 +3695,15 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, #else /* HAVE_SSL_X509_STORE_SHARE */ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, - SSL_CTX *ssl_ctx) + struct ossl_ctx *octx) { CURLcode result; X509_STORE *store; ERR_set_mark(); - store = SSL_CTX_get_cert_store(ssl_ctx); - result = ossl_populate_x509_store(cf, data, store); + store = SSL_CTX_get_cert_store(octx->ssl_ctx); + result = ossl_populate_x509_store(cf, data, octx, store); ERR_pop_to_mark(); @@ -3900,7 +3961,6 @@ static CURLcode ossl_init_ssl(struct ossl_ctx *octx, SSL_set_connect_state(octx->ssl); - octx->server_cert = NULL; if(peer->sni) { if(!SSL_set_tlsext_host_name(octx->ssl, peer->sni)) { failf(data, "Failed set SNI"); @@ -4004,7 +4064,6 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, char * const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; const char * const ssl_cert_type = ssl_config->cert_type; - const bool verifypeer = conn_config->verifypeer; unsigned int ssl_version_min; char error_buffer[256]; @@ -4238,12 +4297,11 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, } #endif /* HAVE_OPENSSL_SRP && USE_TLS_SRP */ - /* OpenSSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(octx->ssl_ctx, - verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); + /* OpenSSL always tries to verify the peer. By setting the failure mode + * to NONE, we allow the connect to complete, regardless of the outcome. + * We then explicitly check the result and may try alternatives like + * Apple's SecTrust for verification. */ + SSL_CTX_set_verify(octx->ssl_ctx, SSL_VERIFY_NONE, NULL); /* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */ #ifdef HAVE_KEYLOG_CALLBACK @@ -4269,7 +4327,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, * we need to do the full initialization before calling it. * See: #11800 */ if(!octx->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); + result = Curl_ssl_setup_x509_store(cf, data, octx); if(result) return result; octx->x509_store_setup = TRUE; @@ -4481,7 +4539,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, if(!octx->x509_store_setup) { /* After having send off the ClientHello, we prepare the x509 * store to verify the coming certificate from the server */ - CURLcode result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); + CURLcode result = Curl_ssl_setup_x509_store(cf, data, octx); if(result) return result; octx->x509_store_setup = TRUE; @@ -4762,6 +4820,9 @@ static void infof_certstack(struct Curl_easy *data, const SSL *ssl) int num_cert_levels; int cert_level; + if(!Curl_trc_is_verbose(data)) + return; + verify_result = SSL_get_verify_result(ssl); if(verify_result != X509_V_OK) certstack = SSL_get_peer_cert_chain(ssl); @@ -4818,7 +4879,229 @@ static void infof_certstack(struct Curl_easy *data, const SSL *ssl) #define infof_certstack(data, ssl) #endif +static CURLcode ossl_check_issuer(struct Curl_cfilter *cf, + struct Curl_easy *data, + X509 *server_cert) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + X509 *issuer = NULL; + BIO *fp = NULL; + char err_buf[256]=""; + bool strict = (conn_config->verifypeer || conn_config->verifyhost); + CURLcode result = CURLE_OK; + + /* e.g. match issuer name with provided issuer certificate */ + if(conn_config->issuercert_blob) { + fp = BIO_new_mem_buf(conn_config->issuercert_blob->data, + (int)conn_config->issuercert_blob->len); + if(!fp) { + failf(data, "BIO_new_mem_buf NULL, " OSSL_PACKAGE " error %s", + ossl_strerror(ERR_get_error(), err_buf, sizeof(err_buf))); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + else if(conn_config->issuercert) { + fp = BIO_new(BIO_s_file()); + if(!fp) { + failf(data, "BIO_new return NULL, " OSSL_PACKAGE " error %s", + ossl_strerror(ERR_get_error(), err_buf, sizeof(err_buf))); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(BIO_read_filename(fp, conn_config->issuercert) <= 0) { + if(strict) + failf(data, "SSL: Unable to open issuer cert (%s)", + conn_config->issuercert); + result = CURLE_SSL_ISSUER_ERROR; + goto out; + } + } + + if(fp) { + issuer = PEM_read_bio_X509(fp, NULL, ZERO_NULL, NULL); + if(!issuer) { + if(strict) + failf(data, "SSL: Unable to read issuer cert (%s)", + conn_config->issuercert); + result = CURLE_SSL_ISSUER_ERROR; + goto out; + } + + if(X509_check_issued(issuer, server_cert) != X509_V_OK) { + if(strict) + failf(data, "SSL: Certificate issuer check failed (%s)", + conn_config->issuercert); + result = CURLE_SSL_ISSUER_ERROR; + goto out; + } + + infof(data, " SSL certificate issuer check ok (%s)", + conn_config->issuercert); + } + +out: + if(fp) + BIO_free(fp); + if(issuer) + X509_free(issuer); + return result; +} + +static CURLcode ossl_check_pinned_key(struct Curl_cfilter *cf, + struct Curl_easy *data, + X509 *server_cert) +{ + const char *ptr; + CURLcode result = CURLE_OK; + + (void)cf; +#ifndef CURL_DISABLE_PROXY + ptr = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif + if(ptr) { + result = ossl_pkp_pin_peer_pubkey(data, server_cert, ptr); + if(result) + failf(data, "SSL: public key does not match pinned public key"); + } + return result; +} + +#ifndef CURL_DISABLE_VERBOSE_STRINGS #define MAX_CERT_NAME_LENGTH 2048 +static CURLcode ossl_infof_cert(struct Curl_cfilter *cf, + struct Curl_easy *data, + X509 *server_cert) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + bool strict = (conn_config->verifypeer || conn_config->verifyhost); + BIO *mem = NULL; + struct dynbuf dname; + char err_buf[256] = ""; + char *buf; + long len; + CURLcode result = CURLE_OK; + + if(!Curl_trc_is_verbose(data)) + return CURLE_OK; + + curlx_dyn_init(&dname, MAX_CERT_NAME_LENGTH); + mem = BIO_new(BIO_s_mem()); + if(!mem) { + failf(data, "BIO_new return NULL, " OSSL_PACKAGE " error %s", + ossl_strerror(ERR_get_error(), err_buf, sizeof(err_buf))); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + infof(data, "%s certificate:", Curl_ssl_cf_is_proxy(cf) ? + "Proxy" : "Server"); + + result = x509_name_oneline(X509_get_subject_name(server_cert), &dname); + infof(data, " subject: %s", result ? "[NONE]" : curlx_dyn_ptr(&dname)); + + ASN1_TIME_print(mem, X509_get0_notBefore(server_cert)); + len = BIO_get_mem_data(mem, (char **) &buf); + infof(data, " start date: %.*s", (int)len, buf); + (void)BIO_reset(mem); + + ASN1_TIME_print(mem, X509_get0_notAfter(server_cert)); + len = BIO_get_mem_data(mem, (char **) &buf); + infof(data, " expire date: %.*s", (int)len, buf); + (void)BIO_reset(mem); + + result = x509_name_oneline(X509_get_issuer_name(server_cert), &dname); + if(result) { + if(strict) + failf(data, "SSL: could not get X509-issuer name"); + result = CURLE_PEER_FAILED_VERIFICATION; + goto out; + } + infof(data, " issuer: %s", curlx_dyn_ptr(&dname)); + +out: + BIO_free(mem); + curlx_dyn_free(&dname); + return result; +} +#endif /* ! CURL_DISABLE_VERBOSE_STRINGS */ + + +#ifdef USE_APPLE_SECTRUST +struct ossl_certs_ctx { + STACK_OF(X509) *sk; + size_t num_certs; +}; + +static CURLcode ossl_chain_get_der(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data, + size_t i, + unsigned char **pder, + size_t *pder_len) +{ + struct ossl_certs_ctx *chain = user_data; + X509 *cert; + int der_len; + + (void)cf; + (void)data; + *pder_len = 0; + *pder = NULL; + + if(i >= chain->num_certs) + return CURLE_TOO_LARGE; + cert = sk_X509_value(chain->sk, (int)i); + if(!cert) + return CURLE_FAILED_INIT; + der_len = i2d_X509(cert, pder); + if(der_len < 0) + return CURLE_FAILED_INIT; + *pder_len = (size_t)der_len; + return CURLE_OK; +} + +static CURLcode ossl_apple_verify(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ossl_ctx *octx, + struct ssl_peer *peer, + bool *pverified) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ossl_certs_ctx chain; + long ocsp_len = 0; +#ifdef HAVE_BORINGSSL_LIKE + const uint8_t *ocsp_data = NULL; +#else + unsigned char *ocsp_data = NULL; +#endif + CURLcode result; + + memset(&chain, 0, sizeof(chain)); + chain.sk = SSL_get_peer_cert_chain(octx->ssl); + chain.num_certs = chain.sk ? sk_X509_num(chain.sk) : 0; + + if(!chain.num_certs && + (conn_config->verifypeer || conn_config->verifyhost)) { + failf(data, "SSL: could not get peer certificate"); + result = CURLE_PEER_FAILED_VERIFICATION; + } + + if(conn_config->verifystatus && !octx->reused_session) + ocsp_len = (long)SSL_get_tlsext_status_ocsp_resp(octx->ssl, &ocsp_data); + + result = Curl_vtls_apple_verify(cf, data, peer, chain.num_certs, + ossl_chain_get_der, &chain, + ocsp_data, ocsp_len); + *pverified = !result; + return result; +} +#endif /* USE_APPLE_SECTRUST */ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -4829,216 +5112,93 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); CURLcode result = CURLE_OK; - long lerr; - X509 *issuer; - BIO *fp = NULL; - char error_buffer[256]=""; - const char *ptr; - BIO *mem = BIO_new(BIO_s_mem()); + long ossl_verify; bool strict = (conn_config->verifypeer || conn_config->verifyhost); - struct dynbuf dname; - - DEBUGASSERT(octx); - - curlx_dyn_init(&dname, MAX_CERT_NAME_LENGTH); - - if(!mem) { - failf(data, - "BIO_new return NULL, " OSSL_PACKAGE " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - return CURLE_OUT_OF_MEMORY; - } + X509 *server_cert; + bool verified = FALSE; if(data->set.ssl.certinfo && !octx->reused_session) { /* asked to gather certificate info. Reused sessions don't have cert chains */ result = ossl_certchain(data, octx->ssl); - if(result) { - BIO_free(mem); + if(result) return result; - } } - octx->server_cert = SSL_get1_peer_certificate(octx->ssl); - if(!octx->server_cert) { - BIO_free(mem); + server_cert = SSL_get1_peer_certificate(octx->ssl); + if(!server_cert) { if(!strict) - return CURLE_OK; + goto out; failf(data, "SSL: could not get peer certificate"); - return CURLE_PEER_FAILED_VERIFICATION; + result = CURLE_PEER_FAILED_VERIFICATION; + goto out; } - infof(data, "%s certificate:", - Curl_ssl_cf_is_proxy(cf) ? "Proxy" : "Server"); - - result = x509_name_oneline(X509_get_subject_name(octx->server_cert), - &dname); - infof(data, " subject: %s", result ? "[NONE]" : curlx_dyn_ptr(&dname)); - #ifndef CURL_DISABLE_VERBOSE_STRINGS - { - char *buf; - long len; - ASN1_TIME_print(mem, X509_get0_notBefore(octx->server_cert)); - len = BIO_get_mem_data(mem, (char **) &buf); - infof(data, " start date: %.*s", (int)len, buf); - (void)BIO_reset(mem); - - ASN1_TIME_print(mem, X509_get0_notAfter(octx->server_cert)); - len = BIO_get_mem_data(mem, (char **) &buf); - infof(data, " expire date: %.*s", (int)len, buf); - (void)BIO_reset(mem); - } + result = ossl_infof_cert(cf, data, server_cert); + if(result) + goto out; + infof_certstack(data, octx->ssl); #endif - BIO_free(mem); - if(conn_config->verifyhost) { - result = ossl_verifyhost(data, conn, peer, octx->server_cert); - if(result) { - X509_free(octx->server_cert); - octx->server_cert = NULL; - curlx_dyn_free(&dname); - return result; - } - } - - result = x509_name_oneline(X509_get_issuer_name(octx->server_cert), - &dname); - if(result) { - if(strict) - failf(data, "SSL: could not get X509-issuer name"); - result = CURLE_PEER_FAILED_VERIFICATION; + result = ossl_verifyhost(data, conn, peer, server_cert); + if(result) + goto out; } - else { - infof(data, " issuer: %s", curlx_dyn_ptr(&dname)); - curlx_dyn_free(&dname); - - /* We could do all sorts of certificate verification stuff here before - deallocating the certificate. */ - - /* e.g. match issuer name with provided issuer certificate */ - if(conn_config->issuercert || conn_config->issuercert_blob) { - if(conn_config->issuercert_blob) { - fp = BIO_new_mem_buf(conn_config->issuercert_blob->data, - (int)conn_config->issuercert_blob->len); - if(!fp) { - failf(data, - "BIO_new_mem_buf NULL, " OSSL_PACKAGE " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - X509_free(octx->server_cert); - octx->server_cert = NULL; - return CURLE_OUT_OF_MEMORY; - } - } - else { - fp = BIO_new(BIO_s_file()); - if(!fp) { - failf(data, - "BIO_new return NULL, " OSSL_PACKAGE " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - X509_free(octx->server_cert); - octx->server_cert = NULL; - return CURLE_OUT_OF_MEMORY; - } - if(BIO_read_filename(fp, conn_config->issuercert) <= 0) { - if(strict) - failf(data, "SSL: Unable to open issuer cert (%s)", - conn_config->issuercert); - BIO_free(fp); - X509_free(octx->server_cert); - octx->server_cert = NULL; - return CURLE_SSL_ISSUER_ERROR; - } - } + ossl_verify = SSL_get_verify_result(octx->ssl); + ssl_config->certverifyresult = ossl_verify; - issuer = PEM_read_bio_X509(fp, NULL, ZERO_NULL, NULL); - if(!issuer) { - if(strict) - failf(data, "SSL: Unable to read issuer cert (%s)", - conn_config->issuercert); - BIO_free(fp); - X509_free(issuer); - X509_free(octx->server_cert); - octx->server_cert = NULL; - return CURLE_SSL_ISSUER_ERROR; - } - - if(X509_check_issued(issuer, octx->server_cert) != X509_V_OK) { - if(strict) - failf(data, "SSL: Certificate issuer check failed (%s)", - conn_config->issuercert); - BIO_free(fp); - X509_free(issuer); - X509_free(octx->server_cert); - octx->server_cert = NULL; - return CURLE_SSL_ISSUER_ERROR; - } + verified = (ossl_verify == X509_V_OK); + if(verified) + infof(data, "SSL certificate verified via OpenSSL."); - infof(data, " SSL certificate issuer check ok (%s)", - conn_config->issuercert); - BIO_free(fp); - X509_free(issuer); - } - - lerr = SSL_get_verify_result(octx->ssl); - ssl_config->certverifyresult = lerr; - if(lerr != X509_V_OK) { - if(conn_config->verifypeer) { - /* We probably never reach this, because SSL_connect() will fail - and we return earlier if verifypeer is set? */ - if(strict) - failf(data, "SSL certificate verify result: %s (%ld)", - X509_verify_cert_error_string(lerr), lerr); - result = CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, " SSL certificate verify result: %s (%ld)," - " continuing anyway.", - X509_verify_cert_error_string(lerr), lerr); +#ifdef USE_APPLE_SECTRUST + if(!verified && + conn_config->verifypeer && ssl_config->native_ca_store && + (ossl_verify == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY)) { + /* we verify using Apple SecTrust *unless* OpenSSL already verified. + * This may happen if the application intercepted the OpenSSL callback + * and installed its own. */ + result = ossl_apple_verify(cf, data, octx, peer, &verified); + if(result && (result != CURLE_PEER_FAILED_VERIFICATION)) + goto out; /* unexpected error */ + if(verified) { + infof(data, "SSL certificate verified via Apple SecTrust."); + ssl_config->certverifyresult = X509_V_OK; } - else - infof(data, " SSL certificate verify ok."); } - infof_certstack(data, octx->ssl); +#endif + + if(!verified) { + /* no trust established, report the OpenSSL status */ + failf(data, "SSL certificate OpenSSL verify result: %s (%ld)", + X509_verify_cert_error_string(ossl_verify), ossl_verify); + result = CURLE_PEER_FAILED_VERIFICATION; + if(conn_config->verifypeer) + goto out; + infof(data, " SSL certificate verification failed, continuing anyway!"); + } #if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP) if(conn_config->verifystatus && !octx->reused_session) { /* do not do this after Session ID reuse */ result = verifystatus(cf, data, octx); - if(result) { - X509_free(octx->server_cert); - octx->server_cert = NULL; - return result; - } + if(result) + goto out; } #endif - if(!strict) - /* when not strict, we do not bother about the verify cert problems */ - result = CURLE_OK; - -#ifndef CURL_DISABLE_PROXY - ptr = Curl_ssl_cf_is_proxy(cf) ? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : - data->set.str[STRING_SSL_PINNEDPUBLICKEY]; -#else - ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; -#endif - if(!result && ptr) { - result = ossl_pkp_pin_peer_pubkey(data, octx->server_cert, ptr); - if(result) - failf(data, "SSL: public key does not match pinned public key"); - } + result = ossl_check_issuer(cf, data, server_cert); + if(result) + goto out; - X509_free(octx->server_cert); - octx->server_cert = NULL; + result = ossl_check_pinned_key(cf, data, server_cert); +out: + X509_free(server_cert); return result; } diff --git a/lib/vtls/openssl.h b/lib/vtls/openssl.h index e263ee2eb2..021d754a62 100644 --- a/lib/vtls/openssl.h +++ b/lib/vtls/openssl.h @@ -71,7 +71,6 @@ struct ossl_ctx { /* these ones requires specific SSL-types */ SSL_CTX* ssl_ctx; SSL* ssl; - X509* server_cert; BIO_METHOD *bio_method; CURLcode io_result; /* result of last BIO cfilter operation */ /* blocked writes need to retry with same length, remember it */ @@ -82,6 +81,7 @@ struct ossl_ctx { bool keylog_done; #endif BIT(x509_store_setup); /* x509 store has been set up */ + BIT(store_is_empty); /* no certs/paths/blobs in x509 store */ BIT(reused_session); /* session-ID was reused for this */ }; @@ -122,7 +122,7 @@ extern const struct Curl_ssl Curl_ssl_openssl; */ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, - SSL_CTX *ssl_ctx); + struct ossl_ctx *octx); CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf, struct Curl_easy *data, diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 1b1f66cc6e..ccd567cd86 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -75,9 +75,14 @@ #include "../curlx/inet_pton.h" #include "../connect.h" #include "../select.h" +#include "../setopt.h" #include "../strdup.h" #include "../rand.h" +#ifdef USE_APPLE_SECTRUST +#include +#endif /* USE_APPLE_SECTRUST */ + /* The last #include files should be: */ #include "../curl_memory.h" #include "../memdebug.h" @@ -290,62 +295,100 @@ static void free_primary_ssl_config(struct ssl_primary_config *sslc) CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) { - data->set.ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH]; - data->set.ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE]; - data->set.ssl.primary.CRLfile = data->set.str[STRING_SSL_CRLFILE]; - data->set.ssl.primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; - data->set.ssl.primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; - data->set.ssl.primary.cipher_list = - data->set.str[STRING_SSL_CIPHER_LIST]; - data->set.ssl.primary.cipher_list13 = - data->set.str[STRING_SSL_CIPHER13_LIST]; - data->set.ssl.primary.signature_algorithms = + struct ssl_config_data *sslc = &data->set.ssl; +#if defined(CURL_CA_PATH) || defined(CURL_CA_BUNDLE) + struct UserDefined *set = &data->set; + CURLcode result; +#endif + + if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { +#ifdef USE_APPLE_SECTRUST + if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) + sslc->native_ca_store = TRUE; +#endif +#ifdef CURL_CA_PATH + if(!sslc->custom_capath && !set->str[STRING_SSL_CAPATH]) { + result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH); + if(result) + return result; + } + sslc->primary.CApath = data->set.str[STRING_SSL_CAPATH]; +#endif +#ifdef CURL_CA_BUNDLE + if(!sslc->custom_cafile && !set->str[STRING_SSL_CAFILE]) { + result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE); + if(result) + return result; + } +#endif + } + sslc->primary.CAfile = data->set.str[STRING_SSL_CAFILE]; + sslc->primary.CRLfile = data->set.str[STRING_SSL_CRLFILE]; + sslc->primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; + sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; + sslc->primary.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST]; + sslc->primary.cipher_list13 = data->set.str[STRING_SSL_CIPHER13_LIST]; + sslc->primary.signature_algorithms = data->set.str[STRING_SSL_SIGNATURE_ALGORITHMS]; - data->set.ssl.primary.pinned_key = + sslc->primary.pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; - data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT]; - data->set.ssl.primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; - data->set.ssl.primary.curves = data->set.str[STRING_SSL_EC_CURVES]; + sslc->primary.cert_blob = data->set.blobs[BLOB_CERT]; + sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; + sslc->primary.curves = data->set.str[STRING_SSL_EC_CURVES]; #ifdef USE_TLS_SRP - data->set.ssl.primary.username = data->set.str[STRING_TLSAUTH_USERNAME]; - data->set.ssl.primary.password = data->set.str[STRING_TLSAUTH_PASSWORD]; + sslc->primary.username = data->set.str[STRING_TLSAUTH_USERNAME]; + sslc->primary.password = data->set.str[STRING_TLSAUTH_PASSWORD]; #endif - data->set.ssl.cert_type = data->set.str[STRING_CERT_TYPE]; - data->set.ssl.key = data->set.str[STRING_KEY]; - data->set.ssl.key_type = data->set.str[STRING_KEY_TYPE]; - data->set.ssl.key_passwd = data->set.str[STRING_KEY_PASSWD]; - data->set.ssl.primary.clientcert = data->set.str[STRING_CERT]; - data->set.ssl.key_blob = data->set.blobs[BLOB_KEY]; + sslc->cert_type = data->set.str[STRING_CERT_TYPE]; + sslc->key = data->set.str[STRING_KEY]; + sslc->key_type = data->set.str[STRING_KEY_TYPE]; + sslc->key_passwd = data->set.str[STRING_KEY_PASSWD]; + sslc->primary.clientcert = data->set.str[STRING_CERT]; + sslc->key_blob = data->set.blobs[BLOB_KEY]; #ifndef CURL_DISABLE_PROXY - data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; - data->set.proxy_ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; - data->set.proxy_ssl.primary.cipher_list = - data->set.str[STRING_SSL_CIPHER_LIST_PROXY]; - data->set.proxy_ssl.primary.cipher_list13 = - data->set.str[STRING_SSL_CIPHER13_LIST_PROXY]; - data->set.proxy_ssl.primary.pinned_key = - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]; - data->set.proxy_ssl.primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; - data->set.proxy_ssl.primary.ca_info_blob = - data->set.blobs[BLOB_CAINFO_PROXY]; - data->set.proxy_ssl.primary.issuercert = - data->set.str[STRING_SSL_ISSUERCERT_PROXY]; - data->set.proxy_ssl.primary.issuercert_blob = - data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; - data->set.proxy_ssl.primary.CRLfile = - data->set.str[STRING_SSL_CRLFILE_PROXY]; - data->set.proxy_ssl.cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; - data->set.proxy_ssl.key = data->set.str[STRING_KEY_PROXY]; - data->set.proxy_ssl.key_type = data->set.str[STRING_KEY_TYPE_PROXY]; - data->set.proxy_ssl.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; - data->set.proxy_ssl.primary.clientcert = data->set.str[STRING_CERT_PROXY]; - data->set.proxy_ssl.key_blob = data->set.blobs[BLOB_KEY_PROXY]; + sslc = &data->set.proxy_ssl; + if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { +#ifdef USE_APPLE_SECTRUST + if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) + sslc->native_ca_store = TRUE; +#endif +#ifdef CURL_CA_PATH + if(!sslc->custom_capath && !set->str[STRING_SSL_CAPATH_PROXY]) { + result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], + CURL_CA_PATH); + if(result) + return result; + } + sslc->primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; +#endif +#ifdef CURL_CA_BUNDLE + if(!sslc->custom_cafile && !set->str[STRING_SSL_CAFILE_PROXY]) { + result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY], + CURL_CA_BUNDLE); + if(result) + return result; + } +#endif + } + sslc->primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; + sslc->primary.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST_PROXY]; + sslc->primary.cipher_list13 = data->set.str[STRING_SSL_CIPHER13_LIST_PROXY]; + sslc->primary.pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]; + sslc->primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; + sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO_PROXY]; + sslc->primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT_PROXY]; + sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; + sslc->primary.CRLfile = data->set.str[STRING_SSL_CRLFILE_PROXY]; + sslc->cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; + sslc->key = data->set.str[STRING_KEY_PROXY]; + sslc->key_type = data->set.str[STRING_KEY_TYPE_PROXY]; + sslc->key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; + sslc->primary.clientcert = data->set.str[STRING_CERT_PROXY]; + sslc->key_blob = data->set.blobs[BLOB_KEY_PROXY]; #ifdef USE_TLS_SRP - data->set.proxy_ssl.primary.username = - data->set.str[STRING_TLSAUTH_USERNAME_PROXY]; - data->set.proxy_ssl.primary.password = - data->set.str[STRING_TLSAUTH_PASSWORD_PROXY]; + sslc->primary.username = data->set.str[STRING_TLSAUTH_USERNAME_PROXY]; + sslc->primary.password = data->set.str[STRING_TLSAUTH_PASSWORD_PROXY]; #endif #endif /* CURL_DISABLE_PROXY */ diff --git a/lib/vtls/vtls_scache.c b/lib/vtls/vtls_scache.c index 74b2b20cf6..16e3f0314f 100644 --- a/lib/vtls/vtls_scache.c +++ b/lib/vtls/vtls_scache.c @@ -371,7 +371,7 @@ void Curl_ssl_scache_unlock(struct Curl_easy *data) static CURLcode cf_ssl_peer_key_add_path(struct dynbuf *buf, const char *name, - char *path, + const char *path, bool *is_local) { if(path && path[0]) { diff --git a/m4/curl-apple-sectrust.m4 b/m4/curl-apple-sectrust.m4 new file mode 100644 index 0000000000..792f719d38 --- /dev/null +++ b/m4/curl-apple-sectrust.m4 @@ -0,0 +1,58 @@ +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +#*************************************************************************** + +AC_DEFUN([CURL_WITH_APPLE_SECTRUST], [ +AC_MSG_CHECKING([whether to enable Apple OS native certificate validation]) +if test "x$OPT_APPLE_SECTRUST" = xyes; then + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM([[ + #include + #include + ]],[[ + #if TARGET_OS_MAC + return 0; + #else + #error Not macOS + #endif + ]]) + ],[ + build_for_apple="yes" + ],[ + build_for_apple="no" + ]) + if test "x$build_for_apple" != "xno"; then + AC_MSG_RESULT(yes) + AC_DEFINE(USE_APPLE_SECTRUST, 1, [enable Apple OS certificate validation]) + APPLE_SECTRUST_ENABLED=1 + APPLE_SECTRUST_LDFLAGS='-framework CoreFoundation -framework CoreServices -framework Security' + LDFLAGS="$LDFLAGS $APPLE_SECTRUST_LDFLAGS" + LDFLAGSPC="$LDFLAGSPC $APPLE_SECTRUST_LDFLAGS" + else + AC_MSG_RESULT(no) + fi +else + AC_MSG_RESULT(no) +fi + +]) diff --git a/tests/data/test305 b/tests/data/test305 index 9e7dc8dcb0..2d4ffd1aab 100644 --- a/tests/data/test305 +++ b/tests/data/test305 @@ -19,7 +19,7 @@ https insecure HTTPS without permission -https://%HOSTIP:%HTTPSPORT/want/%TESTNUMBER --cacert moooo +https://%HOSTIP:%HTTPSPORT/want/%TESTNUMBER --cacert moooo --no-ca-native diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py index 89bf7e1ab1..26da1d2fee 100644 --- a/tests/http/test_02_download.py +++ b/tests/http/test_02_download.py @@ -610,6 +610,8 @@ class TestDownload: pytest.skip("h3 not supported") if proto != 'h3' and sys.platform.startswith('darwin') and env.ci_run: pytest.skip('failing on macOS CI runners') + if proto == 'h3' and sys.platform.startswith('darwin') and env.curl_uses_lib('gnutls'): + pytest.skip('h3 gnutls early data failing on macOS') count = 2 docname = 'data-10k' # we want this test to always connect to nghttpx, since it is diff --git a/tests/http/test_07_upload.py b/tests/http/test_07_upload.py index 0868a406ab..d17d3a44ad 100644 --- a/tests/http/test_07_upload.py +++ b/tests/http/test_07_upload.py @@ -693,6 +693,8 @@ class TestUpload: pytest.skip("h3 not supported") if proto != 'h3' and sys.platform.startswith('darwin') and env.ci_run: pytest.skip('failing on macOS CI runners') + if proto == 'h3' and sys.platform.startswith('darwin') and env.curl_uses_lib('gnutls'): + pytest.skip('h3 gnutls early data failing on macOS') count = 2 # we want this test to always connect to nghttpx, since it is # the only server we have that supports TLS earlydata diff --git a/tests/http/test_17_ssl_use.py b/tests/http/test_17_ssl_use.py index b346281dfc..619ecd25e6 100644 --- a/tests/http/test_17_ssl_use.py +++ b/tests/http/test_17_ssl_use.py @@ -433,9 +433,9 @@ class TestSSLUse: exp_trace = None match_trace = None if env.curl_uses_lib('openssl') or env.curl_uses_lib('quictls'): - exp_trace = r'.*SSL certificate problem: certificate has expired$' + exp_trace = r'.*SSL certificate OpenSSL verify result: certificate has expired.*$' elif env.curl_uses_lib('gnutls'): - exp_trace = r'.*server verification failed: certificate has expired\..*' + exp_trace = r'.*SSL certificate verification failed: certificate has expired\..*' elif env.curl_uses_lib('wolfssl'): exp_trace = r'.*server verification failed: certificate has expired\.$' if exp_trace is not None: -- 2.47.3