ssl: support Apple SecTrust configurations

- configure/cmake support for enabling the option
- supported in OpenSSL and GnuTLS backends
- when configured, Apple SecTrust is the default trust store
  for peer verification. When one of the CURLOPT_* for adding
  certificates is used, that default does not apply.
- add documentation of build options and SSL use

Closes #18703
This commit is contained in:
Stefan Eissing 2025-09-24 10:19:46 +02:00 committed by Daniel Stenberg
parent 9cc1ee55a4
commit eefd03c572
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
29 changed files with 1377 additions and 604 deletions

View File

@ -743,6 +743,7 @@ scp
SDK
se
SEB
SecTrust
SEK
selectable
Serv

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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-*

View File

@ -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}

View File

@ -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`

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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 \

View File

@ -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

View File

@ -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:

View File

@ -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);

View File

@ -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 {

View File

@ -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)

297
lib/vtls/apple.c Normal file
View File

@ -0,0 +1,297 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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 */

55
lib/vtls/apple.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef HEADER_CURL_VTLS_APPLE_H
#define HEADER_CURL_VTLS_APPLE_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Jan Venekamp, <jan@venekamp.net>
*
* 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 */

View File

@ -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(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;
}
}
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;
}
}
else
infof(data, "found %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;
}
}
else
infof(data, "found %d certificates in %s", rc, config->CApath);
}
if(!config->verifypeer) {
infof(data, "SSL Trust: peer verification disabled");
return CURLE_OK;
}
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);
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, " 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);
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, " 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");
/* verify_status is a bitmask of gnutls_certificate_status bits */
if(verify_status & GNUTLS_CERT_INVALID) {
#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);
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;

View File

@ -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,

File diff suppressed because it is too large Load Diff

View File

@ -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,

View File

@ -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 =
data->set.str[STRING_SSL_SIGNATURE_ALGORITHMS];
data->set.ssl.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];
#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];
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
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];
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];
sslc->primary.pinned_key =
data->set.str[STRING_SSL_PINNEDPUBLICKEY];
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
sslc->primary.username = data->set.str[STRING_TLSAUTH_USERNAME];
sslc->primary.password = data->set.str[STRING_TLSAUTH_PASSWORD];
#endif
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 */

View File

@ -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]) {

58
m4/curl-apple-sectrust.m4 Normal file
View File

@ -0,0 +1,58 @@
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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
])

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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: