mirror of
https://github.com/curl/curl.git
synced 2025-09-01 09:55:00 +03:00
QUIC: 0RTT for gnutls via CURLSSLOPT_EARLYDATA
When a QUIC TLS session announced early data support and 'CURLSSLOPT_EARLYDATA' is set for the transfer, send initial request and body (up to the 128k we buffer) as 0RTT when curl is built with ngtcp2+gnutls. QUIC 0RTT needs not only the TLS session but the QUIC transport paramters as well. Store those and the earlydata max value together with the session in the cache. Add test case for h3 use of this. Enable quic early data in nghttpx for testing. Closes #15667
This commit is contained in:
parent
b399a98d2d
commit
68bd759c2b
|
@ -66,6 +66,7 @@
|
|||
#include "vquic-tls.h"
|
||||
#include "vtls/keylog.h"
|
||||
#include "vtls/vtls.h"
|
||||
#include "vtls/vtls_scache.h"
|
||||
#include "curl_ngtcp2.h"
|
||||
|
||||
#include "warnless.h"
|
||||
|
@ -137,8 +138,16 @@ struct cf_ngtcp2_ctx {
|
|||
uint64_t max_idle_ms; /* max idle time for QUIC connection */
|
||||
uint64_t used_bidi_streams; /* bidi streams we have opened */
|
||||
uint64_t max_bidi_streams; /* max bidi streams we can open */
|
||||
size_t earlydata_max; /* max amount of early data supported by
|
||||
server on session reuse */
|
||||
size_t earlydata_skip; /* sending bytes to skip when earlydata
|
||||
* is accepted by peer */
|
||||
CURLcode tls_vrfy_result; /* result of TLS peer verification */
|
||||
int qlogfd;
|
||||
BIT(initialized);
|
||||
BIT(tls_handshake_complete); /* TLS handshake is done */
|
||||
BIT(use_earlydata); /* Using 0RTT data */
|
||||
BIT(earlydata_accepted); /* 0RTT was acceptd by server */
|
||||
BIT(shutdown_started); /* queued shutdown packets */
|
||||
};
|
||||
|
||||
|
@ -442,12 +451,42 @@ static void quic_settings(struct cf_ngtcp2_ctx *ctx,
|
|||
}
|
||||
}
|
||||
|
||||
static CURLcode init_ngh3_conn(struct Curl_cfilter *cf);
|
||||
static CURLcode init_ngh3_conn(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data);
|
||||
|
||||
static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data)
|
||||
static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data)
|
||||
{
|
||||
(void)user_data;
|
||||
struct Curl_cfilter *cf = user_data;
|
||||
struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL;
|
||||
struct Curl_easy *data;
|
||||
|
||||
(void)tconn;
|
||||
DEBUGASSERT(ctx);
|
||||
data = CF_DATA_CURRENT(cf);
|
||||
DEBUGASSERT(data);
|
||||
if(!ctx || !data)
|
||||
return NGHTTP3_ERR_CALLBACK_FAILURE;
|
||||
|
||||
ctx->handshake_at = Curl_now();
|
||||
ctx->tls_handshake_complete = TRUE;
|
||||
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
|
||||
cf->conn->httpversion = 30;
|
||||
|
||||
ctx->tls_vrfy_result = Curl_vquic_tls_verify_peer(&ctx->tls, cf,
|
||||
data, &ctx->peer);
|
||||
CURL_TRC_CF(data, cf, "handshake complete after %dms",
|
||||
(int)Curl_timediff(ctx->handshake_at, ctx->started_at));
|
||||
#ifdef USE_GNUTLS
|
||||
if(ctx->use_earlydata) {
|
||||
int flags = gnutls_session_get_flags(ctx->tls.gtls.session);
|
||||
ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA);
|
||||
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 ?
|
||||
(curl_off_t)ctx->earlydata_skip :
|
||||
-(curl_off_t)ctx->earlydata_skip);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -717,16 +756,19 @@ static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_encryption_level level,
|
|||
void *user_data)
|
||||
{
|
||||
struct Curl_cfilter *cf = user_data;
|
||||
struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL;
|
||||
struct Curl_easy *data = CF_DATA_CURRENT(cf);
|
||||
(void)tconn;
|
||||
|
||||
if(level != NGTCP2_ENCRYPTION_LEVEL_1RTT) {
|
||||
if(level != NGTCP2_ENCRYPTION_LEVEL_1RTT)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(init_ngh3_conn(cf) != CURLE_OK) {
|
||||
return NGTCP2_ERR_CALLBACK_FAILURE;
|
||||
DEBUGASSERT(ctx);
|
||||
DEBUGASSERT(data);
|
||||
if(ctx && data && !ctx->h3conn) {
|
||||
if(init_ngh3_conn(cf, data))
|
||||
return NGTCP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -739,7 +781,7 @@ static ngtcp2_callbacks ng_callbacks = {
|
|||
ngtcp2_crypto_client_initial_cb,
|
||||
NULL, /* recv_client_initial */
|
||||
ngtcp2_crypto_recv_crypto_data_cb,
|
||||
cb_handshake_completed,
|
||||
cf_ngtcp2_handshake_completed,
|
||||
NULL, /* recv_version_negotiation */
|
||||
ngtcp2_crypto_encrypt_cb,
|
||||
ngtcp2_crypto_decrypt_cb,
|
||||
|
@ -1128,14 +1170,15 @@ static nghttp3_callbacks ngh3_callbacks = {
|
|||
NULL /* recv_settings */
|
||||
};
|
||||
|
||||
static CURLcode init_ngh3_conn(struct Curl_cfilter *cf)
|
||||
static CURLcode init_ngh3_conn(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct cf_ngtcp2_ctx *ctx = cf->ctx;
|
||||
CURLcode result;
|
||||
int rc;
|
||||
int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id;
|
||||
int rc;
|
||||
|
||||
if(ngtcp2_conn_get_streams_uni_left(ctx->qconn) < 3) {
|
||||
failf(data, "QUIC connection lacks 3 uni streams to run HTTP/3");
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
|
@ -1147,45 +1190,47 @@ static CURLcode init_ngh3_conn(struct Curl_cfilter *cf)
|
|||
nghttp3_mem_default(),
|
||||
cf);
|
||||
if(rc) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto fail;
|
||||
failf(data, "error creating nghttp3 connection instance");
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL);
|
||||
if(rc) {
|
||||
result = CURLE_QUIC_CONNECT_ERROR;
|
||||
goto fail;
|
||||
failf(data, "error creating HTTP/3 control stream: %s",
|
||||
ngtcp2_strerror(rc));
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
rc = nghttp3_conn_bind_control_stream(ctx->h3conn, ctrl_stream_id);
|
||||
if(rc) {
|
||||
result = CURLE_QUIC_CONNECT_ERROR;
|
||||
goto fail;
|
||||
failf(data, "error binding HTTP/3 control stream: %s",
|
||||
ngtcp2_strerror(rc));
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_enc_stream_id, NULL);
|
||||
if(rc) {
|
||||
result = CURLE_QUIC_CONNECT_ERROR;
|
||||
goto fail;
|
||||
failf(data, "error creating HTTP/3 qpack encoding stream: %s",
|
||||
ngtcp2_strerror(rc));
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_dec_stream_id, NULL);
|
||||
if(rc) {
|
||||
result = CURLE_QUIC_CONNECT_ERROR;
|
||||
goto fail;
|
||||
failf(data, "error creating HTTP/3 qpack decoding stream: %s",
|
||||
ngtcp2_strerror(rc));
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
rc = nghttp3_conn_bind_qpack_streams(ctx->h3conn, qpack_enc_stream_id,
|
||||
qpack_dec_stream_id);
|
||||
if(rc) {
|
||||
result = CURLE_QUIC_CONNECT_ERROR;
|
||||
goto fail;
|
||||
failf(data, "error binding HTTP/3 qpack streams: %s",
|
||||
ngtcp2_strerror(rc));
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
return CURLE_OK;
|
||||
fail:
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
|
||||
|
@ -1236,6 +1281,10 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|||
DEBUGASSERT(ctx->h3conn);
|
||||
*err = CURLE_OK;
|
||||
|
||||
/* handshake verification failed in callback, do not recv anything */
|
||||
if(ctx->tls_vrfy_result)
|
||||
return ctx->tls_vrfy_result;
|
||||
|
||||
pktx_init(&pktx, cf, data);
|
||||
|
||||
if(!stream || ctx->shutdown_started) {
|
||||
|
@ -1533,6 +1582,10 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|||
pktx_init(&pktx, cf, data);
|
||||
*err = CURLE_OK;
|
||||
|
||||
/* handshake verification failed in callback, do not send anything */
|
||||
if(ctx->tls_vrfy_result)
|
||||
return ctx->tls_vrfy_result;
|
||||
|
||||
(void)eos; /* TODO: use for stream EOF and block handling */
|
||||
result = cf_progress_ingress(cf, data, &pktx);
|
||||
if(result) {
|
||||
|
@ -1594,6 +1647,9 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|||
(void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id);
|
||||
}
|
||||
|
||||
if(sent > 0 && !ctx->tls_handshake_complete && ctx->use_earlydata)
|
||||
ctx->earlydata_skip += sent;
|
||||
|
||||
result = cf_progress_egress(cf, data, &pktx);
|
||||
if(result) {
|
||||
*err = result;
|
||||
|
@ -1612,17 +1668,6 @@ out:
|
|||
return sent;
|
||||
}
|
||||
|
||||
static CURLcode qng_verify_peer(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct cf_ngtcp2_ctx *ctx = cf->ctx;
|
||||
|
||||
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
|
||||
cf->conn->httpversion = 30;
|
||||
|
||||
return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer);
|
||||
}
|
||||
|
||||
static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen,
|
||||
struct sockaddr_storage *remote_addr,
|
||||
socklen_t remote_addrlen, int ecn,
|
||||
|
@ -2135,6 +2180,24 @@ static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
|
|||
#endif /* USE_OPENSSL */
|
||||
|
||||
#ifdef USE_GNUTLS
|
||||
|
||||
static const char *gtls_hs_msg_name(int mtype)
|
||||
{
|
||||
switch(mtype) {
|
||||
case 1: return "ClientHello";
|
||||
case 2: return "ServerHello";
|
||||
case 4: return "SessionTicket";
|
||||
case 8: return "EncryptedExtensions";
|
||||
case 11: return "Certificate";
|
||||
case 13: return "CertificateRequest";
|
||||
case 15: return "CertificateVerify";
|
||||
case 20: return "Finished";
|
||||
case 24: return "KeyUpdate";
|
||||
case 254: return "MessageHash";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
|
||||
unsigned when, unsigned int incoming,
|
||||
const gnutls_datum_t *msg)
|
||||
|
@ -2148,14 +2211,28 @@ static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
|
|||
if(when && cf && ctx) { /* after message has been processed */
|
||||
struct Curl_easy *data = CF_DATA_CURRENT(cf);
|
||||
DEBUGASSERT(data);
|
||||
if(data) {
|
||||
CURL_TRC_CF(data, cf, "handshake: %s message type %d",
|
||||
incoming ? "incoming" : "outgoing", htype);
|
||||
}
|
||||
if(!data)
|
||||
return 0;
|
||||
CURL_TRC_CF(data, cf, "SSL message: %s %s [%d]",
|
||||
incoming ? "<-" : "->", gtls_hs_msg_name(htype), htype);
|
||||
switch(htype) {
|
||||
case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: {
|
||||
ngtcp2_ssize tplen;
|
||||
uint8_t tpbuf[256];
|
||||
unsigned char *quic_tp = NULL;
|
||||
size_t quic_tp_len = 0;
|
||||
|
||||
tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf,
|
||||
sizeof(tpbuf));
|
||||
if(tplen < 0)
|
||||
CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s",
|
||||
ngtcp2_strerror((int)tplen));
|
||||
else {
|
||||
quic_tp = (unsigned char *)tpbuf;
|
||||
quic_tp_len = (size_t)tplen;
|
||||
}
|
||||
(void)Curl_gtls_cache_session(cf, data, ctx->peer.scache_key,
|
||||
session, -1, "h3");
|
||||
session, -1, "h3", quic_tp, quic_tp_len);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -2186,9 +2263,9 @@ static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session)
|
|||
}
|
||||
#endif /* USE_WOLFSSL */
|
||||
|
||||
static CURLcode tls_ctx_setup(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
void *user_data)
|
||||
static CURLcode cf_ngtcp2_tls_ctx_setup(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
void *user_data)
|
||||
{
|
||||
struct curl_tls_ctx *ctx = user_data;
|
||||
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
|
||||
|
@ -2241,6 +2318,53 @@ static CURLcode tls_ctx_setup(struct Curl_cfilter *cf,
|
|||
return CURLE_OK;
|
||||
}
|
||||
|
||||
static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct Curl_ssl_session *scs,
|
||||
bool *do_early_data)
|
||||
{
|
||||
struct cf_ngtcp2_ctx *ctx = cf->ctx;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
*do_early_data = FALSE;
|
||||
#ifdef USE_GNUTLS
|
||||
ctx->earlydata_max =
|
||||
gnutls_record_get_max_early_data_size(ctx->tls.gtls.session);
|
||||
if((!ctx->earlydata_max)) {
|
||||
CURL_TRC_CF(data, cf, "SSL session does not allow earlydata");
|
||||
}
|
||||
else if(strcmp("h3", scs->alpn)) {
|
||||
CURL_TRC_CF(data, cf, "SSL session from different ALPN, no early data");
|
||||
}
|
||||
else if(!scs->quic_tp || !scs->quic_tp_len) {
|
||||
CURL_TRC_CF(data, cf, "no 0RTT transport parameters, no early data, ");
|
||||
}
|
||||
else {
|
||||
int rv;
|
||||
rv = ngtcp2_conn_decode_and_set_0rtt_transport_params(
|
||||
ctx->qconn, (uint8_t *)scs->quic_tp, scs->quic_tp_len);
|
||||
if(rv)
|
||||
CURL_TRC_CF(data, cf, "no early data, failed to set 0RTT transport "
|
||||
"parameters: %s", ngtcp2_strerror(rv));
|
||||
else {
|
||||
infof(data, "SSL session allows %zu bytes of early data, "
|
||||
"reusing ALPN '%s'", ctx->earlydata_max, scs->alpn);
|
||||
result = init_ngh3_conn(cf, data);
|
||||
if(!result) {
|
||||
ctx->use_earlydata = TRUE;
|
||||
cf->connected = TRUE;
|
||||
*do_early_data = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else /* USE_GNUTLS */
|
||||
(void)data;
|
||||
(void)ctx;
|
||||
(void)scs;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Might be called twice for happy eyeballs.
|
||||
*/
|
||||
|
@ -2256,17 +2380,6 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
|
|||
int qfd;
|
||||
|
||||
DEBUGASSERT(ctx->initialized);
|
||||
#define H3_ALPN "\x2h3\x5h3-29"
|
||||
result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer,
|
||||
H3_ALPN, sizeof(H3_ALPN) - 1,
|
||||
tls_ctx_setup, &ctx->tls, &ctx->conn_ref);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
SSL_set_quic_use_legacy_codepoint(ctx->tls.ossl.ssl, 0);
|
||||
#endif
|
||||
|
||||
ctx->dcid.datalen = NGTCP2_MAX_CIDLEN;
|
||||
result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN);
|
||||
if(result)
|
||||
|
@ -2308,7 +2421,17 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
|
|||
if(rc)
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
|
||||
#define H3_ALPN "\x2h3\x5h3-29"
|
||||
result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer,
|
||||
H3_ALPN, sizeof(H3_ALPN) - 1,
|
||||
cf_ngtcp2_tls_ctx_setup, &ctx->tls,
|
||||
&ctx->conn_ref,
|
||||
cf_ngtcp2_on_session_reuse);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
SSL_set_quic_use_legacy_codepoint(ctx->tls.ossl.ssl, 0);
|
||||
ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ossl.ssl);
|
||||
#elif defined(USE_GNUTLS)
|
||||
ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls.session);
|
||||
|
@ -2359,6 +2482,11 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf,
|
|||
result = cf_connect_start(cf, data, &pktx);
|
||||
if(result)
|
||||
goto out;
|
||||
if(cf->connected) {
|
||||
cf->conn->alpn = CURL_HTTP_VERSION_3;
|
||||
*done = TRUE;
|
||||
goto out;
|
||||
}
|
||||
result = cf_progress_egress(cf, data, &pktx);
|
||||
/* we do not expect to be able to recv anything yet */
|
||||
goto out;
|
||||
|
@ -2373,10 +2501,7 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf,
|
|||
goto out;
|
||||
|
||||
if(ngtcp2_conn_get_handshake_completed(ctx->qconn)) {
|
||||
ctx->handshake_at = now;
|
||||
CURL_TRC_CF(data, cf, "handshake complete after %dms",
|
||||
(int)Curl_timediff(now, ctx->started_at));
|
||||
result = qng_verify_peer(cf, data);
|
||||
result = ctx->tls_vrfy_result;
|
||||
if(!result) {
|
||||
CURL_TRC_CF(data, cf, "peer verified");
|
||||
cf->connected = TRUE;
|
||||
|
|
|
@ -1166,7 +1166,7 @@ static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf,
|
|||
#define H3_ALPN "\x2h3"
|
||||
result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer,
|
||||
H3_ALPN, sizeof(H3_ALPN) - 1,
|
||||
NULL, NULL, NULL);
|
||||
NULL, NULL, NULL, NULL);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
|
|
|
@ -1309,7 +1309,7 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf,
|
|||
result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer,
|
||||
QUICHE_H3_APPLICATION_PROTOCOL,
|
||||
sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1,
|
||||
NULL, NULL, cf);
|
||||
NULL, NULL, cf, NULL);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
|
|
|
@ -235,7 +235,8 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
|
|||
struct ssl_peer *peer,
|
||||
const char *alpn, size_t alpn_len,
|
||||
Curl_vquic_tls_ctx_setup *cb_setup,
|
||||
void *cb_user_data, void *ssl_user_data)
|
||||
void *cb_user_data, void *ssl_user_data,
|
||||
Curl_vquic_session_reuse_cb *session_reuse_cb)
|
||||
{
|
||||
char tls_id[80];
|
||||
CURLcode result;
|
||||
|
@ -250,6 +251,7 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
|
|||
#error "no TLS lib in used, should not happen"
|
||||
return CURLE_FAILED_INIT;
|
||||
#endif
|
||||
(void)session_reuse_cb;
|
||||
result = Curl_ssl_peer_init(peer, cf, tls_id, TRNSPRT_QUIC);
|
||||
if(result)
|
||||
return result;
|
||||
|
@ -260,15 +262,16 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
|
|||
(const unsigned char *)alpn, alpn_len,
|
||||
cb_setup, cb_user_data, NULL, ssl_user_data);
|
||||
#elif defined(USE_GNUTLS)
|
||||
(void)result;
|
||||
return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer,
|
||||
(const unsigned char *)alpn, alpn_len, NULL,
|
||||
cb_setup, cb_user_data, ssl_user_data);
|
||||
(const unsigned char *)alpn, alpn_len,
|
||||
cb_setup, cb_user_data, ssl_user_data,
|
||||
session_reuse_cb);
|
||||
#elif defined(USE_WOLFSSL)
|
||||
result = wssl_init_ctx(ctx, cf, data, cb_setup, cb_user_data);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
(void)session_reuse_cb;
|
||||
return wssl_init_ssl(ctx, cf, data, peer, alpn, alpn_len, ssl_user_data);
|
||||
#else
|
||||
#error "no TLS lib in used, should not happen"
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "vtls/wolfssl.h"
|
||||
|
||||
struct ssl_peer;
|
||||
struct Curl_ssl_session;
|
||||
|
||||
struct curl_tls_ctx {
|
||||
#ifdef USE_OPENSSL
|
||||
|
@ -57,6 +58,11 @@ typedef CURLcode Curl_vquic_tls_ctx_setup(struct Curl_cfilter *cf,
|
|||
struct Curl_easy *data,
|
||||
void *cb_user_data);
|
||||
|
||||
typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct Curl_ssl_session *scs,
|
||||
bool *do_early_data);
|
||||
|
||||
/**
|
||||
* Initialize the QUIC TLS instances based of the SSL configurations
|
||||
* for the connection filter, transfer and peer.
|
||||
|
@ -68,8 +74,9 @@ typedef CURLcode Curl_vquic_tls_ctx_setup(struct Curl_cfilter *cf,
|
|||
* may be NULL
|
||||
* @param alpn_len the overall number of bytes in `alpn`
|
||||
* @param cb_setup optional callback for early TLS config
|
||||
± @param cb_user_data user_data param for callback
|
||||
* @param cb_user_data user_data param for callback
|
||||
* @param ssl_user_data optional pointer to set in TLS application context
|
||||
* @param session_reuse_cb callback to handle session reuse, signal early data
|
||||
*/
|
||||
CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
|
||||
struct Curl_cfilter *cf,
|
||||
|
@ -78,7 +85,8 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
|
|||
const char *alpn, size_t alpn_len,
|
||||
Curl_vquic_tls_ctx_setup *cb_setup,
|
||||
void *cb_user_data,
|
||||
void *ssl_user_data);
|
||||
void *ssl_user_data,
|
||||
Curl_vquic_session_reuse_cb *session_reuse_cb);
|
||||
|
||||
/**
|
||||
* Cleanup all data that has been initialized.
|
||||
|
|
|
@ -836,7 +836,7 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
|
|||
ret = Curl_ssl_session_create((unsigned char *)session, sizeof(*session),
|
||||
(int)session->version,
|
||||
connssl->negotiated.alpn,
|
||||
0, -1, &sc_session);
|
||||
0, -1, 0, &sc_session);
|
||||
if(!ret) {
|
||||
ret = Curl_ssl_scache_put(cf, data, connssl->peer.scache_key,
|
||||
sc_session);
|
||||
|
|
160
lib/vtls/gtls.c
160
lib/vtls/gtls.c
|
@ -54,6 +54,7 @@
|
|||
#include "progress.h"
|
||||
#include "select.h"
|
||||
#include "strcase.h"
|
||||
#include "strdup.h"
|
||||
#include "warnless.h"
|
||||
#include "x509asn1.h"
|
||||
#include "multiif.h"
|
||||
|
@ -720,12 +721,15 @@ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf,
|
|||
const char *ssl_peer_key,
|
||||
gnutls_session_t session,
|
||||
int lifetime_secs,
|
||||
const char *alpn)
|
||||
const char *alpn,
|
||||
unsigned char *quic_tp,
|
||||
size_t quic_tp_len)
|
||||
{
|
||||
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
|
||||
struct Curl_ssl_session *sc_session;
|
||||
unsigned char *sdata;
|
||||
unsigned char *sdata, *qtp_clone = NULL;
|
||||
size_t sdata_len = 0;
|
||||
size_t earlydata_max = 0;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
if(!ssl_config->primary.cache_session)
|
||||
|
@ -750,11 +754,21 @@ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf,
|
|||
|
||||
CURL_TRC_CF(data, cf, "get session id (len=%zu, alpn=%s) and store in cache",
|
||||
sdata_len, alpn ? alpn : "-");
|
||||
result = Curl_ssl_session_create(sdata, sdata_len,
|
||||
Curl_glts_get_ietf_proto(session),
|
||||
alpn, 0, lifetime_secs,
|
||||
&sc_session);
|
||||
/* call took ownership of `sdata`*/
|
||||
earlydata_max = gnutls_record_get_max_early_data_size(session);
|
||||
if(quic_tp && quic_tp_len) {
|
||||
qtp_clone = Curl_memdup0((char *)quic_tp, quic_tp_len);
|
||||
if(!qtp_clone) {
|
||||
free(sdata);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
result = Curl_ssl_session_create2(sdata, sdata_len,
|
||||
Curl_glts_get_ietf_proto(session),
|
||||
alpn, 0, lifetime_secs, earlydata_max,
|
||||
qtp_clone, quic_tp_len,
|
||||
&sc_session);
|
||||
/* call took ownership of `sdata` and `qtp_clone` */
|
||||
if(!result) {
|
||||
result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session);
|
||||
/* took ownership of `sc_session` */
|
||||
|
@ -787,7 +801,7 @@ static CURLcode cf_gtls_update_session_id(struct Curl_cfilter *cf,
|
|||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
return Curl_gtls_cache_session(cf, data, connssl->peer.scache_key,
|
||||
session, -1,
|
||||
connssl->negotiated.alpn);
|
||||
connssl->negotiated.alpn, NULL, 0);
|
||||
}
|
||||
|
||||
static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
|
||||
|
@ -819,6 +833,7 @@ static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
|
|||
static CURLcode gtls_client_init(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct ssl_peer *peer,
|
||||
size_t earlydata_max,
|
||||
struct gtls_ctx *gtls)
|
||||
{
|
||||
struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf);
|
||||
|
@ -872,6 +887,14 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf,
|
|||
|
||||
/* Initialize TLS session as a client */
|
||||
init_flags = GNUTLS_CLIENT;
|
||||
if(peer->transport == TRNSPRT_QUIC && earlydata_max > 0)
|
||||
init_flags |= GNUTLS_ENABLE_EARLY_DATA | GNUTLS_NO_END_OF_EARLY_DATA;
|
||||
else if(earlydata_max > 0 && earlydata_max != 0xFFFFFFFFUL)
|
||||
/* See https://gitlab.com/gnutls/gnutls/-/issues/1619
|
||||
* We cannot differentiate between a session announcing no earldata
|
||||
* and one announcing 0xFFFFFFFFUL. On TCP+TLS, this is unlikely, but
|
||||
* on QUIC this is common. */
|
||||
init_flags |= GNUTLS_ENABLE_EARLY_DATA;
|
||||
|
||||
#if defined(GNUTLS_FORCE_CLIENT_CERT)
|
||||
init_flags |= GNUTLS_FORCE_CLIENT_CERT;
|
||||
|
@ -893,6 +916,8 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf,
|
|||
init_flags |= GNUTLS_NO_STATUS_REQUEST;
|
||||
#endif
|
||||
|
||||
CURL_TRC_CF(data, cf, "gnutls_init(flags=%x), earlydata=%zu",
|
||||
init_flags, earlydata_max);
|
||||
rc = gnutls_init(>ls->session, init_flags);
|
||||
if(rc != GNUTLS_E_SUCCESS) {
|
||||
failf(data, "gnutls_init() failed: %d", rc);
|
||||
|
@ -1065,46 +1090,62 @@ static int keylog_callback(gnutls_session_t session, const char *label,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static CURLcode gtls_on_session_reuse(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct Curl_ssl_session *scs,
|
||||
bool *do_early_data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct gtls_ssl_backend_data *backend =
|
||||
(struct gtls_ssl_backend_data *)connssl->backend;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
*do_early_data = FALSE;
|
||||
connssl->earlydata_max =
|
||||
gnutls_record_get_max_early_data_size(backend->gtls.session);
|
||||
if((!connssl->earlydata_max || connssl->earlydata_max == 0xFFFFFFFFUL)) {
|
||||
/* Seems to be GnuTLS way to signal no EarlyData in session */
|
||||
CURL_TRC_CF(data, cf, "SSL session does not allow earlydata");
|
||||
}
|
||||
else if(!Curl_alpn_contains_proto(connssl->alpn, scs->alpn)) {
|
||||
CURL_TRC_CF(data, cf, "SSL session has different ALPN, no early data");
|
||||
}
|
||||
else {
|
||||
infof(data, "SSL session allows %zu bytes of early data, "
|
||||
"reusing ALPN '%s'", connssl->earlydata_max, scs->alpn);
|
||||
connssl->earlydata_state = ssl_earlydata_use;
|
||||
connssl->state = ssl_connection_deferred;
|
||||
result = Curl_alpn_set_negotiated(cf, data, connssl,
|
||||
(const unsigned char *)scs->alpn,
|
||||
scs->alpn ? strlen(scs->alpn) : 0);
|
||||
*do_early_data = !result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
|
||||
struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct ssl_peer *peer,
|
||||
const unsigned char *alpn, size_t alpn_len,
|
||||
struct ssl_connect_data *connssl,
|
||||
Curl_gtls_ctx_setup_cb *cb_setup,
|
||||
void *cb_user_data,
|
||||
void *ssl_user_data)
|
||||
void *ssl_user_data,
|
||||
Curl_gtls_init_session_reuse_cb *sess_reuse_cb)
|
||||
{
|
||||
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
|
||||
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
|
||||
struct Curl_ssl_session *scs = NULL;
|
||||
gnutls_datum_t gtls_alpns[5];
|
||||
size_t gtls_alpns_count = 0;
|
||||
bool gtls_session_setup = FALSE;
|
||||
CURLcode result;
|
||||
int rc;
|
||||
|
||||
DEBUGASSERT(gctx);
|
||||
|
||||
result = gtls_client_init(cf, data, peer, gctx);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
gnutls_session_set_ptr(gctx->session, ssl_user_data);
|
||||
|
||||
if(cb_setup) {
|
||||
result = cb_setup(cf, data, cb_user_data);
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Open the file if a TLS or QUIC backend has not done this before. */
|
||||
Curl_tls_keylog_open();
|
||||
if(Curl_tls_keylog_enabled()) {
|
||||
gnutls_session_set_keylog_function(gctx->session, keylog_callback);
|
||||
}
|
||||
|
||||
/* This might be a reconnect, so we check for a session ID in the cache
|
||||
to speed up things */
|
||||
to speed up things. We need to do this before constructing the gnutls
|
||||
session since we need to set flags depending on the kind of reuse. */
|
||||
if(conn_config->cache_session) {
|
||||
result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs);
|
||||
if(result)
|
||||
|
@ -1112,35 +1153,28 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
|
|||
|
||||
if(scs && scs->sdata && scs->sdata_len) {
|
||||
/* we got a cached session, use it! */
|
||||
|
||||
result = gtls_client_init(cf, data, peer, scs->earlydata_max, gctx);
|
||||
if(result)
|
||||
goto out;
|
||||
gtls_session_setup = TRUE;
|
||||
|
||||
rc = gnutls_session_set_data(gctx->session, scs->sdata, scs->sdata_len);
|
||||
if(rc < 0) {
|
||||
if(rc < 0)
|
||||
infof(data, "SSL session not accepted by GnuTLS, continuing without");
|
||||
}
|
||||
else {
|
||||
infof(data, "SSL reusing session with ALPN '%s'",
|
||||
scs->alpn ? scs->alpn : "-");
|
||||
if(ssl_config->earlydata &&
|
||||
!cf->conn->connect_only && connssl &&
|
||||
(gnutls_protocol_get_version(gctx->session) == GNUTLS_TLS1_3) &&
|
||||
Curl_alpn_contains_proto(connssl->alpn, scs->alpn)) {
|
||||
connssl->earlydata_max =
|
||||
gnutls_record_get_max_early_data_size(gctx->session);
|
||||
if((!connssl->earlydata_max ||
|
||||
connssl->earlydata_max == 0xFFFFFFFFUL)) {
|
||||
/* Seems to be GnuTLS way to signal no EarlyData in session */
|
||||
CURL_TRC_CF(data, cf, "TLS session does not allow earlydata");
|
||||
}
|
||||
else {
|
||||
CURL_TRC_CF(data, cf, "TLS session allows %zu earlydata bytes, "
|
||||
"reusing ALPN '%s'",
|
||||
connssl->earlydata_max, scs->alpn);
|
||||
connssl->earlydata_state = ssl_earlydata_use;
|
||||
connssl->state = ssl_connection_deferred;
|
||||
result = Curl_alpn_set_negotiated(cf, data, connssl,
|
||||
(const unsigned char *)scs->alpn,
|
||||
scs->alpn ? strlen(scs->alpn) : 0);
|
||||
!cf->conn->connect_only &&
|
||||
(gnutls_protocol_get_version(gctx->session) == GNUTLS_TLS1_3)) {
|
||||
bool do_early_data = FALSE;
|
||||
if(sess_reuse_cb) {
|
||||
result = sess_reuse_cb(cf, data, scs, &do_early_data);
|
||||
if(result)
|
||||
goto out;
|
||||
goto out;
|
||||
}
|
||||
if(do_early_data) {
|
||||
/* We only try the ALPN protocol the session used before,
|
||||
* otherwise we might send early data for the wrong protocol */
|
||||
gtls_alpns[0].data = (unsigned char *)scs->alpn;
|
||||
|
@ -1161,6 +1195,26 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
|
|||
}
|
||||
}
|
||||
|
||||
if(!gtls_session_setup) {
|
||||
result = gtls_client_init(cf, data, peer, 0, gctx);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
|
||||
gnutls_session_set_ptr(gctx->session, ssl_user_data);
|
||||
|
||||
if(cb_setup) {
|
||||
result = cb_setup(cf, data, cb_user_data);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Open the file if a TLS or QUIC backend has not done this before. */
|
||||
Curl_tls_keylog_open();
|
||||
if(Curl_tls_keylog_enabled()) {
|
||||
gnutls_session_set_keylog_function(gctx->session, keylog_callback);
|
||||
}
|
||||
|
||||
/* convert the ALPN string from our arguments to a list of strings that
|
||||
* gnutls wants and will convert internally back to this string for sending
|
||||
* to the server. nice. */
|
||||
|
@ -1225,7 +1279,7 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
|
|||
|
||||
result = Curl_gtls_ctx_init(&backend->gtls, cf, data, &connssl->peer,
|
||||
proto.data, proto.len,
|
||||
connssl, NULL, NULL, cf);
|
||||
NULL, NULL, cf, gtls_on_session_reuse);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ struct ssl_primary_config;
|
|||
struct ssl_config_data;
|
||||
struct ssl_peer;
|
||||
struct ssl_connect_data;
|
||||
struct Curl_ssl_session;
|
||||
|
||||
int Curl_glts_get_ietf_proto(gnutls_session_t session);
|
||||
|
||||
|
@ -78,15 +79,20 @@ typedef CURLcode Curl_gtls_ctx_setup_cb(struct Curl_cfilter *cf,
|
|||
struct Curl_easy *data,
|
||||
void *user_data);
|
||||
|
||||
typedef CURLcode Curl_gtls_init_session_reuse_cb(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct Curl_ssl_session *scs,
|
||||
bool *do_early_data);
|
||||
|
||||
CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
|
||||
struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct ssl_peer *peer,
|
||||
const unsigned char *alpn, size_t alpn_len,
|
||||
struct ssl_connect_data *connssl,
|
||||
Curl_gtls_ctx_setup_cb *cb_setup,
|
||||
void *cb_user_data,
|
||||
void *ssl_user_data);
|
||||
void *ssl_user_data,
|
||||
Curl_gtls_init_session_reuse_cb *sess_reuse_cb);
|
||||
|
||||
CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
|
@ -105,7 +111,9 @@ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf,
|
|||
const char *ssl_peer_key,
|
||||
gnutls_session_t session,
|
||||
int lifetime_secs,
|
||||
const char *alpn);
|
||||
const char *alpn,
|
||||
unsigned char *quic_tp,
|
||||
size_t quic_tp_len);
|
||||
|
||||
extern const struct Curl_ssl Curl_ssl_gnutls;
|
||||
|
||||
|
|
|
@ -1171,7 +1171,7 @@ mbed_new_session(struct Curl_cfilter *cf, struct Curl_easy *data)
|
|||
#endif
|
||||
result = Curl_ssl_session_create(sdata, slen,
|
||||
ietf_tls_id,
|
||||
connssl->negotiated.alpn, 0, -1,
|
||||
connssl->negotiated.alpn, 0, -1, 0,
|
||||
&sc_session);
|
||||
sdata = NULL; /* call took ownership */
|
||||
if(!result)
|
||||
|
|
|
@ -2903,7 +2903,7 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
|
|||
|
||||
result = Curl_ssl_session_create(der_session_buf, der_session_size,
|
||||
ietf_tls_id, alpn, 0,
|
||||
SSL_SESSION_get_timeout(session),
|
||||
SSL_SESSION_get_timeout(session), 0,
|
||||
&sc_session);
|
||||
der_session_buf = NULL; /* took ownership of sdata */
|
||||
if(!result) {
|
||||
|
|
|
@ -103,6 +103,11 @@ static void cf_ssl_scache_clear_session(struct Curl_ssl_session *s)
|
|||
s->sdata = NULL;
|
||||
}
|
||||
s->sdata_len = 0;
|
||||
if(s->quic_tp) {
|
||||
free((void *)s->quic_tp);
|
||||
s->quic_tp = NULL;
|
||||
}
|
||||
s->quic_tp_len = 0;
|
||||
s->ietf_tls_id = 0;
|
||||
s->time_received = 0;
|
||||
s->lifetime_secs = 0;
|
||||
|
@ -120,7 +125,21 @@ CURLcode
|
|||
Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len,
|
||||
int ietf_tls_id, const char *alpn,
|
||||
curl_off_t time_received, long lifetime_secs,
|
||||
size_t earlydata_max,
|
||||
struct Curl_ssl_session **psession)
|
||||
{
|
||||
return Curl_ssl_session_create2(sdata, sdata_len, ietf_tls_id, alpn,
|
||||
time_received, lifetime_secs,
|
||||
earlydata_max, NULL, 0, psession);
|
||||
}
|
||||
|
||||
CURLcode
|
||||
Curl_ssl_session_create2(unsigned char *sdata, size_t sdata_len,
|
||||
int ietf_tls_id, const char *alpn,
|
||||
curl_off_t time_received, long lifetime_secs,
|
||||
size_t earlydata_max,
|
||||
unsigned char *quic_tp, size_t quic_tp_len,
|
||||
struct Curl_ssl_session **psession)
|
||||
{
|
||||
struct Curl_ssl_session *s;
|
||||
|
||||
|
@ -133,6 +152,7 @@ Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len,
|
|||
s = calloc(1, sizeof(*s));
|
||||
if(!s) {
|
||||
free(sdata);
|
||||
free(quic_tp);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
|
@ -147,8 +167,11 @@ Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len,
|
|||
lifetime_secs = CURL_SCACHE_MAX_12_LIFETIME_SEC;
|
||||
|
||||
s->lifetime_secs = (int)lifetime_secs;
|
||||
s->earlydata_max = earlydata_max;
|
||||
s->sdata = sdata;
|
||||
s->sdata_len = sdata_len;
|
||||
s->quic_tp = quic_tp;
|
||||
s->quic_tp_len = quic_tp_len;
|
||||
if(alpn) {
|
||||
s->alpn = strdup(alpn);
|
||||
if(!s->alpn) {
|
||||
|
@ -738,8 +761,10 @@ out:
|
|||
}
|
||||
else
|
||||
CURL_TRC_CF(data, cf, "[SCACHE] added session for %s [proto=0x%x, "
|
||||
"lifetime=%d, alpn=%s], peer has %zu sessions now",
|
||||
"lifetime=%d, alpn=%s, earlydata=%zu, quic_tp=%s], "
|
||||
"peer has %zu sessions now",
|
||||
ssl_peer_key, s->ietf_tls_id, s->lifetime_secs, s->alpn,
|
||||
s->earlydata_max, s->quic_tp ? "yes" : "no",
|
||||
Curl_llist_count(&peer->sessions));
|
||||
return result;
|
||||
}
|
||||
|
@ -779,6 +804,7 @@ CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf,
|
|||
struct Curl_ssl_scache *scache = data->state.ssl_scache;
|
||||
struct Curl_ssl_scache_peer *peer = NULL;
|
||||
struct Curl_llist_node *n;
|
||||
struct Curl_ssl_session *s = NULL;
|
||||
CURLcode result;
|
||||
|
||||
*ps = NULL;
|
||||
|
@ -791,15 +817,24 @@ CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf,
|
|||
cf_scache_peer_remove_expired(peer, (curl_off_t)time(NULL));
|
||||
n = Curl_llist_head(&peer->sessions);
|
||||
if(n) {
|
||||
*ps = Curl_node_take_elem(n);
|
||||
s = Curl_node_take_elem(n);
|
||||
(scache->age)++; /* increase general age */
|
||||
peer->age = scache->age; /* set this as used in this age */
|
||||
}
|
||||
}
|
||||
Curl_ssl_scache_unlock(data);
|
||||
|
||||
CURL_TRC_CF(data, cf, "[SCACHE] %s cached session for '%s'",
|
||||
*ps ? "Found" : "No", ssl_peer_key);
|
||||
if(s) {
|
||||
*ps = s;
|
||||
CURL_TRC_CF(data, cf, "[SCACHE] took session for %s [proto=0x%x, "
|
||||
"lifetime=%d, alpn=%s, earlydata=%zu, quic_tp=%s], "
|
||||
"%zu sessions remain",
|
||||
ssl_peer_key, s->ietf_tls_id, s->lifetime_secs, s->alpn,
|
||||
s->earlydata_max, s->quic_tp ? "yes" : "no",
|
||||
Curl_llist_count(&peer->sessions));
|
||||
}
|
||||
else {
|
||||
CURL_TRC_CF(data, cf, "[SCACHE] no cached session for %s", ssl_peer_key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -122,6 +122,9 @@ struct Curl_ssl_session {
|
|||
int lifetime_secs; /* ticket lifetime (-1 unknown) */
|
||||
int ietf_tls_id; /* TLS protocol identifier negotiated */
|
||||
char *alpn; /* APLN TLS negotiated protocol string */
|
||||
size_t earlydata_max; /* max 0-RTT data supported by peer */
|
||||
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 */
|
||||
};
|
||||
|
||||
|
@ -142,8 +145,19 @@ CURLcode
|
|||
Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len,
|
||||
int ietf_tls_id, const char *alpn,
|
||||
curl_off_t time_received, long lifetime_secs,
|
||||
size_t earlydata_max,
|
||||
struct Curl_ssl_session **psession);
|
||||
|
||||
/* Variation of session creation with quic transport parameter bytes,
|
||||
* Takes ownership of `quic_tp` regardless of return code. */
|
||||
CURLcode
|
||||
Curl_ssl_session_create2(unsigned char *sdata, size_t sdata_len,
|
||||
int ietf_tls_id, const char *alpn,
|
||||
curl_off_t time_received, long lifetime_secs,
|
||||
size_t earlydata_max,
|
||||
unsigned char *quic_tp, size_t quic_tp_len,
|
||||
struct Curl_ssl_session **psession);
|
||||
|
||||
/* Destroy a `session` instance. Can be called with NULL.
|
||||
* Does NOT need locking. */
|
||||
void Curl_ssl_session_destroy(struct Curl_ssl_session *s);
|
||||
|
|
|
@ -432,7 +432,7 @@ CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf,
|
|||
|
||||
result = Curl_ssl_session_create(sdata, sdata_len,
|
||||
ietf_tls_id, alpn, 0,
|
||||
wolfSSL_SESSION_get_timeout(session),
|
||||
wolfSSL_SESSION_get_timeout(session), 0,
|
||||
&sc_session);
|
||||
sdata = NULL; /* took ownership of sdata */
|
||||
if(!result) {
|
||||
|
|
|
@ -631,8 +631,7 @@ class TestDownload:
|
|||
elif proto == 'h2':
|
||||
assert earlydata[1] == 107, f'{earlydata}'
|
||||
elif proto == 'h3':
|
||||
# not implemented
|
||||
assert earlydata[1] == 0, f'{earlydata}'
|
||||
assert earlydata[1] == 67, f'{earlydata}'
|
||||
|
||||
@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
|
||||
@pytest.mark.parametrize("max_host_conns", [0, 1, 5])
|
||||
|
|
|
@ -697,7 +697,10 @@ class TestUpload:
|
|||
['http/1.1', 32*1024, 16384], # headers+body, limited by server max
|
||||
['h2', 10*1024, 10378], # headers+body
|
||||
['h2', 32*1024, 16384], # headers+body, limited by server max
|
||||
['h3', 1024, 0], # earlydata not supported
|
||||
['h3', 1024, 1126], # headers+body (app data)
|
||||
['h3', 1024 * 1024, 131177], # headers+body (long app data). The 0RTT
|
||||
# size is limited by our sendbuf size
|
||||
# of 128K.
|
||||
])
|
||||
def test_07_70_put_earlydata(self, env: Env, httpd, nghttpx, proto, upload_size, exp_early):
|
||||
if not env.curl_uses_lib('gnutls'):
|
||||
|
@ -727,7 +730,7 @@ class TestUpload:
|
|||
self.check_downloads(client, [f"{upload_size}"], count)
|
||||
earlydata = {}
|
||||
for line in r.trace_lines:
|
||||
m = re.match(r'^\[t-(\d+)] EarlyData: (\d+)', line)
|
||||
m = re.match(r'^\[t-(\d+)] EarlyData: (-?\d+)', line)
|
||||
if m:
|
||||
earlydata[int(m.group(1))] = int(m.group(2))
|
||||
assert earlydata[0] == 0, f'{earlydata}'
|
||||
|
|
|
@ -210,7 +210,7 @@ class TestCaddy:
|
|||
respdata = open(curl.response_file(i)).readlines()
|
||||
assert respdata == exp_data
|
||||
|
||||
@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
|
||||
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
|
||||
def test_08_08_earlydata(self, env: Env, httpd, caddy, proto):
|
||||
count = 2
|
||||
docname = 'data10k.data'
|
||||
|
@ -230,12 +230,15 @@ class TestCaddy:
|
|||
self.check_downloads(client, srcfile, count)
|
||||
earlydata = {}
|
||||
for line in r.trace_lines:
|
||||
m = re.match(r'^\[t-(\d+)] EarlyData: (\d+)', line)
|
||||
m = re.match(r'^\[t-(\d+)] EarlyData: (-?\d+)', line)
|
||||
if m:
|
||||
earlydata[int(m.group(1))] = int(m.group(2))
|
||||
# Caddy does not support early data
|
||||
assert earlydata[0] == 0, f'{earlydata}'
|
||||
assert earlydata[1] == 0, f'{earlydata}'
|
||||
if proto == 'h3':
|
||||
assert earlydata[1] == 71, f'{earlydata}'
|
||||
else:
|
||||
# Caddy does not support early data on TCP
|
||||
assert earlydata[1] == 0, f'{earlydata}'
|
||||
|
||||
def check_downloads(self, client, srcfile: str, count: int,
|
||||
complete: bool = True):
|
||||
|
|
|
@ -205,6 +205,7 @@ class NghttpxQuic(Nghttpx):
|
|||
args = [
|
||||
self._cmd,
|
||||
f'--frontend=*,{self.env.h3_port};quic',
|
||||
'--frontend-quic-early-data',
|
||||
f'--frontend=*,{self.env.nghttpx_https_port};tls',
|
||||
f'--backend=127.0.0.1,{self.env.https_port};{self.env.domain1};sni={self.env.domain1};proto=h2;tls',
|
||||
f'--backend=127.0.0.1,{self.env.http_port}',
|
||||
|
|
Loading…
Reference in New Issue
Block a user