From 75c2c881af3201bc35d582eb4304e9b8ce3436ea Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 1 Jul 2026 10:54:21 +0200 Subject: [PATCH] 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 --- lib/vquic/cf-ngtcp2-cmn.c | 4 +-- lib/vtls/openssl.c | 53 +++++++++++++++++++++++---------------- lib/vtls/openssl.h | 6 ++++- lib/vtls/vtls_scache.h | 1 + lib/vtls/vtls_spack.c | 7 ++++++ 5 files changed, 47 insertions(+), 24 deletions(-) diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c index 419c52009a..a86960c91c 100644 --- a/lib/vquic/cf-ngtcp2-cmn.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -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; } diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index ed401fe0d8..cd8d43523a 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -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 */ diff --git a/lib/vtls/openssl.h b/lib/vtls/openssl.h index 53ab60528f..1daeef50df 100644 --- a/lib/vtls/openssl.h +++ b/lib/vtls/openssl.h @@ -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); diff --git a/lib/vtls/vtls_scache.h b/lib/vtls/vtls_scache.h index effb1d8f96..fc4eb9ef51 100644 --- a/lib/vtls/vtls_scache.h +++ b/lib/vtls/vtls_scache.h @@ -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. diff --git a/lib/vtls/vtls_spack.c b/lib/vtls/vtls_spack.c index 9cbdecc901..e572e923f5 100644 --- a/lib/vtls/vtls_spack.c +++ b/lib/vtls/vtls_spack.c @@ -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;