ssl: support Apple SecTrust configurations
No binary (build failed)
—
(log yok)
| @@ -743,6 +743,7 @@ scp | |
| SDK | |
| se | |
| SEB | |
| + | SecTrust |
| SEK | |
| selectable | |
| Serv | |
| @@ -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 | |
| @@ -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 | |
| @@ -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() | |
| @@ -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-* | |
| @@ -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} | |
| @@ -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` | |
| @@ -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 | |
| @@ -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. | |
| @@ -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 | |
| @@ -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 \ | |
| @@ -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 | |
| @@ -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: | |
| @@ -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); | |
| @@ -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 { | |
| @@ -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) | |
| @@ -0,0 +1,297 @@ | |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Daniel Stenberg, <[email protected]>, 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 <Security/Security.h> |
| + | #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 */ |
| @@ -0,0 +1,55 @@ | |
| + | #ifndef HEADER_CURL_VTLS_APPLE_H |
| + | #define HEADER_CURL_VTLS_APPLE_H |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Jan Venekamp, <[email protected]> |
| + | * |
| + | * 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 */ |
| @@ -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; | |
| @@ -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, | |
| @@ -67,6 +67,7 @@ | |
| #include "../strdup.h" | |
| #include "../strerror.h" | |
| #include "../curl_printf.h" | |
| + | #include "apple.h" |
| #include <openssl/ssl.h> | |
| #include <openssl/rand.h> | |
| @@ -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; | |
| } | |
| @@ -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, | |
| @@ -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 <Security/Security.h> |
| + | #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 */ | |
| @@ -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]) { | |
| @@ -0,0 +1,58 @@ | |
| + | #*************************************************************************** |
| + | # _ _ ____ _ |
| + | # Project ___| | | | _ \| | |
| + | # / __| | | | |_) | | |
| + | # | (__| |_| | _ <| |___ |
| + | # \___|\___/|_| \_\_____| |
| + | # |
| + | # Copyright (C) Daniel Stenberg, <[email protected]>, 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 <sys/types.h> |
| + | #include <TargetConditionals.h> |
| + | ]],[[ |
| + | #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 |
| + | |
| + | ]) |
| @@ -19,7 +19,7 @@ https | |
| insecure HTTPS without permission | |
| </name> | |
| <command> | |
| - | https://%HOSTIP:%HTTPSPORT/want/%TESTNUMBER --cacert moooo |
| + | https://%HOSTIP:%HTTPSPORT/want/%TESTNUMBER --cacert moooo --no-ca-native |
| </command> | |
| </client> | |
| @@ -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 | |
| @@ -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 | |
| @@ -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: | |