ngtcp2+openssL: fix early data

Openssl needs an additional call into its API at just the right time to
*really* enable early data use. Add checks after handshake if early data
was really successful, more stringent than we used before.

test_02_32: enable early data test wiht more backends and platforms to
see how CI now copes with it.

Fixes #22649
Closes #22657
Reported-by: Rarylson Freitas
This commit is contained in:
Stefan Eissing 2026-08-24 12:46:30 +02:00 committed by Daniel Stenberg
parent 5267ed859d
commit d1702a24b3
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
3 changed files with 31 additions and 16 deletions

View file

@ -103,7 +103,6 @@ BoringSSL). It works on TCP and QUIC connections using ngtcp2.
This option works on a best effort basis,
in cases when it was not possible to send early data the request is resent
normally post-handshake.
This option does not work when using QUIC.
(Added in 8.11.0 for GnuTLS and 8.13.0 for OpenSSL, quictls and wolfSSL)
# DEFAULT

View file

@ -274,15 +274,24 @@ static int cb_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data)
}
if(ctx->use_earlydata) {
#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA)
ctx->earlydata_accepted =
(SSL_get_early_data_status(ctx->tls.ossl.ssl) !=
SSL_EARLY_DATA_REJECTED);
/* Check for bug that OpenSSL did not even send the early data. */
if(SSL_get_early_data_status(ctx->tls.ossl.ssl) == SSL_EARLY_DATA_NOT_SENT)
CURL_TRC_CF(data, cf, "OpenSSL did not send early data");
#endif
#ifdef USE_GNUTLS
#if NGTCP2_VERSION_NUM >= 0x011700
ctx->earlydata_accepted =
!ngtcp2_conn_get_tls_early_data_rejected2(ctx->qconn);
#else /* older NGTCP2 */
#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA)
int ossl_early_status = SSL_get_early_data_status(ctx->tls.ossl.ssl);
if(ossl_early_status == SSL_EARLY_DATA_NOT_SENT)
CURL_TRC_CF(data, cf, "OpenSSL did not send early data");
ctx->earlydata_accepted = (ossl_early_status == SSL_EARLY_DATA_ACCEPTED);
#elif defined(USE_GNUTLS)
int flags = gnutls_session_get_flags(ctx->tls.gtls.session);
ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA);
#endif
#ifdef USE_WOLFSSL
#elif defined(USE_WOLFSSL)
#ifdef WOLFSSL_EARLY_DATA
ctx->earlydata_accepted =
(wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) !=
@ -291,7 +300,8 @@ static int cb_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data)
DEBUGASSERT(0); /* should not come here if ED is disabled. */
ctx->earlydata_accepted = FALSE;
#endif /* WOLFSSL_EARLY_DATA */
#endif
#endif /* OPENSSL or GNUTLS or WOLFSSL */
#endif /* older NGTCP2 */
CURL_TRC_CF(data, cf, "server did%s accept %zu bytes of early data",
ctx->earlydata_accepted ? "" : " not", ctx->earlydata_skip);
Curl_pgrsEarlyData(data, ctx->earlydata_accepted ?
@ -1068,6 +1078,18 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
#error "ngtcp2 TLS backend not defined"
#endif
#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) && \
defined(OPENSSL_QUIC_API2)
/* We need to tell OpenSSL to *really* use Early Data for QUIC and
* this only works *after* ngtcp2 has tweaked all SSL parameters,
* otherwise OpenSSL does not accept it. */
if(ctx->use_earlydata &&
!SSL_set_quic_tls_early_data_enabled(ctx->tls.ossl.ssl, 1)) {
CURL_TRC_CF(data, cf, "OpenSSL refused to use early data");
ctx->use_earlydata = FALSE;
cf->connected = FALSE;
}
#endif
ngtcp2_ccerr_default(&ctx->last_error);
return CURLE_OK;

View file

@ -526,7 +526,7 @@ class TestDownload:
assert r.total_connects <= 3, r.dump_logs()
# nghttpx is the only server we have that supports TLS early data
@pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx")
@pytest.mark.skipif(condition=not Env.have_h3_server(), reason="no nghttpx with QUIC")
@pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug")
@pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings")
@pytest.mark.parametrize("proto", Env.http_protos())
@ -535,12 +535,6 @@ class TestDownload:
pytest.skip('TLS earlydata not implemented')
if proto == 'h3' and not env.curl_can_h3_early_data():
pytest.skip("h3 early data 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 env.curl_uses_lib('wolfssl'):
pytest.skip('h3 wolfssl early data failing')
if proto == 'h3' and env.curl_uses_lib('gnutls'):
pytest.skip('h3 gnutls early data failing')
count = 2
docname = 'data-10k'
# we want this test to always connect to nghttpx, since it is
@ -571,7 +565,7 @@ class TestDownload:
if m:
earlydata[int(m.group(1))] = int(m.group(2))
continue
if re.match(r'\[1-1] \* SSL reusing session.*', line):
if re.match(r'\[1-1] \* (\[1-1] )?SSL reusing session.*', line):
reused_session = True
assert reused_session, 'session was not reused for 2nd transfer'
assert earlydata[0] == 0, f'{earlydata}'