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

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