openssl+sectrust: fix session reuse

When TLS is verified via Apple SecTrust, openssl internal state is a
verification faiure (that is how it works, we use sectrust when openssl
fails to verify and natice ca store is enabled).

OpenSSL stores this verification status inside its TLS session objects.
On reuse, we see an unverified session and do not reuse it for a
verified connect attempt. While this is a performance penalty for most
connections, it *fails* on ftps:// transfers where servers expect
session reuse on DATA connections.

Fix this by remembering that TLS and a session was verified by sectrust
and allow reuse of such sessions for new connect attempts that also use
sectrust.

Closes #22235
This commit is contained in:
Stefan Eissing 2026-07-01 10:54:21 +02:00 committed by Daniel Stenberg
parent 417189f360
commit 75c2c881af
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
5 changed files with 47 additions and 24 deletions

View file

@ -649,8 +649,8 @@ static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
quic_tp_len = (size_t)tplen;
}
#endif
Curl_ossl_add_session(cf, data, ctx->ssl_peer.scache_key, ssl_sessionid,
SSL_version(ssl), "h3", quic_tp, quic_tp_len);
Curl_ossl_add_session(cf, data, &ctx->tls.ossl, ctx->ssl_peer.scache_key,
ssl_sessionid, "h3", quic_tp, quic_tp_len);
}
return 0;
}

View file

@ -2632,9 +2632,9 @@ static CURLcode ossl_set_ssl_version_min_max(struct Curl_cfilter *cf,
CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ossl_ctx *octx,
const char *ssl_peer_key,
SSL_SESSION *session,
int ietf_tls_id,
const char *alpn,
unsigned char *quic_tp,
size_t quic_tp_len)
@ -2651,6 +2651,7 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
size_t der_session_size;
unsigned char *der_session_ptr;
size_t earlydata_max = 0;
int ietf_tls_id = SSL_version(octx->ssl);
der_session_size = i2d_SSL_SESSION(session, NULL);
if(der_session_size == 0) {
@ -2688,6 +2689,9 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
earlydata_max, qtp_clone, quic_tp_len,
&sc_session);
der_session_buf = NULL; /* took ownership of sdata */
#ifdef USE_APPLE_SECTRUST
sc_session->sectrust_verified = octx->sectrust_verified;
#endif
if(!result) {
result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session);
/* took ownership of `sc_session` */
@ -2708,8 +2712,9 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
if(cf) {
struct Curl_easy *data = CF_DATA_CURRENT(cf);
struct ssl_connect_data *connssl = cf->ctx;
Curl_ossl_add_session(cf, data, connssl->peer.scache_key, ssl_sessionid,
SSL_version(ssl), connssl->negotiated.alpn, NULL, 0);
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
Curl_ossl_add_session(cf, data, octx, connssl->peer.scache_key,
ssl_sessionid, connssl->negotiated.alpn, NULL, 0);
}
return 0;
}
@ -3353,7 +3358,12 @@ static CURLcode ossl_init_session_and_alpns(
}
else {
if(conn_cfg->verifypeer &&
(SSL_get_verify_result(octx->ssl) != X509_V_OK)) {
(SSL_get_verify_result(octx->ssl) != X509_V_OK)
#ifdef USE_APPLE_SECTRUST
/* if sectrust is used and verified the session before */
&& (!ssl_config->native_ca_store || !scs->sectrust_verified)
#endif
) {
/* Session was from unverified connection, cannot reuse here */
SSL_set_session(octx->ssl, NULL);
infof(data, "SSL session not peer verified, not reusing");
@ -3362,6 +3372,9 @@ static CURLcode ossl_init_session_and_alpns(
infof(data, "SSL reusing session with ALPN '%s'",
scs->alpn ? scs->alpn : "-");
octx->reused_session = TRUE;
#ifdef USE_APPLE_SECTRUST
octx->sectrust_session = scs->sectrust_verified;
#endif
infof(data, "SSL verify result: %lx",
(unsigned long)SSL_get_verify_result(octx->ssl));
#ifdef HAVE_OPENSSL_EARLYDATA
@ -4671,13 +4684,13 @@ static CURLcode ossl_chain_get_der(struct Curl_cfilter *cf,
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_peer *peer)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct ossl_certs_ctx chain;
CURLcode result;
octx->sectrust_verified = FALSE;
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;
@ -4689,8 +4702,11 @@ static CURLcode ossl_apple_verify(struct Curl_cfilter *cf,
result = CURLE_PEER_FAILED_VERIFICATION;
}
else {
/* when session was reused, there is no peer cert chain */
*pverified = FALSE;
/* When session was reused, there is no peer cert chain.
* We trust it if it came from a SecTrust verified TLS. */
CURL_TRC_CF(data, cf, "session reused, sectrust_session=%d",
octx->sectrust_session);
octx->sectrust_verified = (bool)octx->sectrust_session;
return CURLE_OK;
}
}
@ -4717,12 +4733,12 @@ static CURLcode ossl_apple_verify(struct Curl_cfilter *cf,
if(!result && ocsp_missing && conn_config->verifystatus &&
!octx->reused_session) {
/* verified, but OCSP stapling is required and server sent none */
*pverified = TRUE;
octx->sectrust_verified = TRUE;
failf(data, "No OCSP response received");
return CURLE_SSL_INVALIDCERTSTATUS;
}
}
*pverified = !result;
octx->sectrust_verified = !result;
return result;
}
#endif /* USE_APPLE_SECTRUST */
@ -4739,9 +4755,6 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf,
long ossl_verify;
X509 *server_cert;
bool verified = FALSE;
#if !defined(OPENSSL_NO_OCSP) && defined(USE_APPLE_SECTRUST)
bool sectrust_verified = FALSE;
#endif
if(data->set.ssl.certinfo && !octx->reused_session) {
/* asked to gather certificate info. Reused sessions do not have cert
@ -4789,15 +4802,13 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf,
/* 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);
result = ossl_apple_verify(cf, data, octx, peer);
if(result && (result != CURLE_PEER_FAILED_VERIFICATION))
goto out; /* unexpected error */
if(verified) {
if(octx->sectrust_verified) {
infof(data, "SSL certificate verified via Apple SecTrust.");
ssl_config->certverifyresult = X509_V_OK;
#ifndef OPENSSL_NO_OCSP
sectrust_verified = TRUE;
#endif
verified = TRUE;
}
}
#endif
@ -4816,9 +4827,9 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf,
#ifndef OPENSSL_NO_OCSP
if(conn_config->verifystatus &&
#ifdef USE_APPLE_SECTRUST
!sectrust_verified && /* already verified via apple sectrust, cannot
* verifystate via OpenSSL in that case as it
* does not have the trust anchors */
!octx->sectrust_verified && /* already verified via sectrust, cannot
* verifystate via OpenSSL in that case as it
* does not have the trust anchors */
#endif
!octx->reused_session) {
/* do not do this after Session ID reuse */

View file

@ -129,6 +129,10 @@ struct ossl_ctx {
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 */
#ifdef USE_APPLE_SECTRUST
BIT(sectrust_verified); /* peer was verified by sectrust */
BIT(sectrust_session); /* session from sectrust verified peer */
#endif
};
size_t Curl_ossl_version(char *buffer, size_t size);
@ -182,9 +186,9 @@ CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf,
*/
CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ossl_ctx *octx,
const char *ssl_peer_key,
SSL_SESSION *session,
int ietf_tls_id,
const char *alpn,
unsigned char *quic_tp,
size_t quic_tp_len);

View file

@ -131,6 +131,7 @@ struct Curl_ssl_session {
const unsigned char *quic_tp; /* Optional QUIC transport param bytes */
size_t quic_tp_len; /* number of bytes in quic_tp */
struct Curl_llist_node list; /* internal storage handling */
BIT(sectrust_verified); /* session comes from sectrust verified TLS */
};
/* Create a `session` instance. Does NOT need locking.

View file

@ -38,6 +38,7 @@
#define CURL_SPACK_ALPN 0x05
#define CURL_SPACK_EARLYDATA 0x06
#define CURL_SPACK_QUICTP 0x07
#define CURL_SPACK_SECTRUST 0x08
static CURLcode spack_enc8(struct dynbuf *buf, uint8_t b)
{
@ -218,6 +219,9 @@ CURLcode Curl_ssl_session_pack(struct Curl_easy *data,
if(!result)
result = spack_enc32(buf, (uint32_t)s->earlydata_max);
}
if(!result && s->sectrust_verified) {
result = spack_enc8(buf, CURL_SPACK_SECTRUST);
}
if(!result && s->quic_tp && s->quic_tp_len) {
result = spack_enc8(buf, CURL_SPACK_QUICTP);
if(!result)
@ -303,6 +307,9 @@ CURLcode Curl_ssl_session_unpack(struct Curl_easy *data,
goto out;
s->valid_until = (curl_off_t)val64;
break;
case CURL_SPACK_SECTRUST:
s->sectrust_verified = TRUE;
break;
default: /* unknown tag */
result = CURLE_READ_ERROR;
goto out;