TLS: TLSv1.3 earlydata support for curl

Based on #14135, implement TLSv1.3 earlydata support for the curl
command line, libcurl and its implementation in GnuTLS.

If a known TLS session announces early data support, and the feature is
enabled *and* it is not a "connect-only" transfer, delay the TLS
handshake until the first request is being sent.

- Add --tls-earldata as new boolean command line option for curl.
- Add CURLSSLOPT_EARLYDATA to libcurl to enable use of the feature.
- Add CURLINFO_EARLYDATA_SENT_T to libcurl, reporting the amount of
  bytes sent and accepted/rejected by the server.

Implementation details:
- store the ALPN protocol selected at the SSL session.
- When reusing the session and enabling earlydata, use exactly
  that ALPN protocol for negoptiation with the server. When the
  sessions ALPN does not match the connections ALPN, earlydata
  will not be enabled.
- Check that the server selected the correct ALPN protocol for
  an earlydata connect. If the server does not confirm or reports
  something different, the connect fails.
- HTTP/2: delay sending the initial SETTINGS frames during connect,
  if not connect-only.

Verification:
- add test_02_32 to verify earlydata GET with nghttpx.
- add test_07_70 to verify earlydata PUT with nghttpx.
- add support in 'hx-download', 'hx-upload' clients for the feature

Assisted-by: ad-chaos on github
Closes #15211
This commit is contained in:
Stefan Eissing 2024-10-09 14:46:32 +02:00 committed by Daniel Stenberg
parent d0377f5a86
commit 962097b8dd
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
40 changed files with 899 additions and 134 deletions

View file

@ -789,8 +789,11 @@ static ssize_t send_callback(nghttp2_session *h2,
(void)flags;
DEBUGASSERT(data);
nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen,
nw_out_writer, cf, &result);
if(!cf->connected)
nwritten = Curl_bufq_write(&ctx->outbufq, buf, blen, &result);
else
nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen,
nw_out_writer, cf, &result);
if(nwritten < 0) {
if(result == CURLE_AGAIN) {
ctx->nw_out_blocked = 1;
@ -1898,6 +1901,11 @@ out:
nghttp2_strerror(rv), rv);
return CURLE_SEND_ERROR;
}
/* Defer flushing during the connect phase so that the SETTINGS and
* other initial frames are sent together with the first request.
* Unless we are 'connect_only' where the request will never come. */
if(!cf->connected && !cf->conn->connect_only)
return CURLE_OK;
return nw_out_flush(cf, data);
}
@ -2439,6 +2447,7 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf,
struct cf_h2_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
struct cf_call_data save;
bool first_time = FALSE;
if(cf->connected) {
*done = TRUE;
@ -2460,11 +2469,14 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf,
result = cf_h2_ctx_open(cf, data);
if(result)
goto out;
first_time = TRUE;
}
result = h2_progress_ingress(cf, data, H2_CHUNK_SIZE);
if(result)
goto out;
if(!first_time) {
result = h2_progress_ingress(cf, data, H2_CHUNK_SIZE);
if(result)
goto out;
}
/* Send out our SETTINGS and ACKs and such. If that blocks, we
* have it buffered and can count this filter as being connected */