wolfssl: tls early data support
Enable TLS Early Data for wolfSSL: - merge WOLFSSL_CTX and WOLFSSL setup from ngtcp2 with the general implemenation in wolfssl.c - enable for QUIC via ngtcp2 - give Curl_vquic_tls_init() a `struct alpn_spec` like used for the TCP case. Adapt gnutls and other users. - enable pytest test cases for early data with wolfSSL and while this messes up wolfssl.c anyway, do - rename all struct/functions with prefix 'wolfssl_' to 'wssl_' to not pollute that name prefix - rename `ctx/handle` to `ssl_ctx/ssl`, as used in openssl case Closes #16167
This commit is contained in:
parent
efec626ebb
commit
edd573d980
@ -89,11 +89,11 @@ could be a privacy violation and unexpected.
|
||||
## CURLSSLOPT_EARLYDATA
|
||||
|
||||
Tell libcurl to try sending application data as TLS1.3 early data. This option
|
||||
is only supported for GnuTLS. This option works on a best effort basis,
|
||||
is supported for GnuTLS and wolfSSL. This option works on a best effort basis,
|
||||
in cases when it wasn't 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)
|
||||
(Added in 8.11.0 for GnuTLS and 8.13.0 for wolfSSL)
|
||||
|
||||
# DEFAULT
|
||||
|
||||
|
||||
@ -481,17 +481,27 @@ static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data)
|
||||
* the handshake time when we really did connect */
|
||||
if(ctx->use_earlydata)
|
||||
Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at);
|
||||
#ifdef USE_GNUTLS
|
||||
if(ctx->use_earlydata) {
|
||||
#ifdef USE_GNUTLS
|
||||
int flags = gnutls_session_get_flags(ctx->tls.gtls.session);
|
||||
ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA);
|
||||
#endif
|
||||
#ifdef USE_WOLFSSL
|
||||
#ifdef WOLFSSL_EARLY_DATA
|
||||
ctx->earlydata_accepted =
|
||||
(wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) !=
|
||||
WOLFSSL_EARLY_DATA_REJECTED);
|
||||
#else
|
||||
DEBUGASSERT(0); /* should not come here if ED is disabled. */
|
||||
ctx->earlydata_accepted = FALSE;
|
||||
#endif /* WOLFSSL_EARLY_DATA */
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
|
||||
@ -2257,8 +2267,23 @@ static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session)
|
||||
struct Curl_easy *data = CF_DATA_CURRENT(cf);
|
||||
DEBUGASSERT(data);
|
||||
if(data && ctx) {
|
||||
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_wssl_cache_session(cf, data, ctx->peer.scache_key,
|
||||
session, wolfSSL_version(ssl), "h3");
|
||||
session, wolfSSL_version(ssl),
|
||||
"h3", quic_tp, quic_tp_len);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@ -2308,13 +2333,13 @@ static CURLcode cf_ngtcp2_tls_ctx_setup(struct Curl_cfilter *cf,
|
||||
}
|
||||
|
||||
#elif defined(USE_WOLFSSL)
|
||||
if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ctx) != 0) {
|
||||
if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ssl_ctx) != 0) {
|
||||
failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");
|
||||
return CURLE_FAILED_INIT;
|
||||
}
|
||||
if(ssl_config->primary.cache_session) {
|
||||
/* Register to get notified when a new session is received */
|
||||
wolfSSL_CTX_sess_set_new_cb(ctx->wssl.ctx, wssl_quic_new_session_cb);
|
||||
wolfSSL_CTX_sess_set_new_cb(ctx->wssl.ssl_ctx, wssl_quic_new_session_cb);
|
||||
}
|
||||
#endif
|
||||
return CURLE_OK;
|
||||
@ -2322,6 +2347,7 @@ static CURLcode cf_ngtcp2_tls_ctx_setup(struct Curl_cfilter *cf,
|
||||
|
||||
static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct alpn_spec *alpns,
|
||||
struct Curl_ssl_session *scs,
|
||||
bool *do_early_data)
|
||||
{
|
||||
@ -2332,10 +2358,19 @@ static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf,
|
||||
#ifdef USE_GNUTLS
|
||||
ctx->earlydata_max =
|
||||
gnutls_record_get_max_early_data_size(ctx->tls.gtls.session);
|
||||
#endif
|
||||
#ifdef USE_WOLFSSL
|
||||
#ifdef WOLFSSL_EARLY_DATA
|
||||
ctx->earlydata_max = scs->earlydata_max;
|
||||
#else
|
||||
ctx->earlydata_max = 0;
|
||||
#endif /* WOLFSSL_EARLY_DATA */
|
||||
#endif
|
||||
#if defined(USE_GNUTLS) || defined(USE_WOLFSSL)
|
||||
if((!ctx->earlydata_max)) {
|
||||
CURL_TRC_CF(data, cf, "SSL session does not allow earlydata");
|
||||
}
|
||||
else if(strcmp("h3", scs->alpn)) {
|
||||
else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) {
|
||||
CURL_TRC_CF(data, cf, "SSL session from different ALPN, no early data");
|
||||
}
|
||||
else if(!scs->quic_tp || !scs->quic_tp_len) {
|
||||
@ -2363,6 +2398,7 @@ static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf,
|
||||
(void)data;
|
||||
(void)ctx;
|
||||
(void)scs;
|
||||
(void)alpns;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
@ -2380,6 +2416,9 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
|
||||
CURLcode result;
|
||||
const struct Curl_sockaddr_ex *sockaddr = NULL;
|
||||
int qfd;
|
||||
static const struct alpn_spec ALPN_SPEC_H3 = {
|
||||
{ "h3", "h3-29" }, 2
|
||||
};
|
||||
|
||||
DEBUGASSERT(ctx->initialized);
|
||||
ctx->dcid.datalen = NGTCP2_MAX_CIDLEN;
|
||||
@ -2423,9 +2462,7 @@ 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,
|
||||
result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, &ALPN_SPEC_H3,
|
||||
cf_ngtcp2_tls_ctx_setup, &ctx->tls,
|
||||
&ctx->conn_ref,
|
||||
cf_ngtcp2_on_session_reuse);
|
||||
@ -2438,7 +2475,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
|
||||
#elif defined(USE_GNUTLS)
|
||||
ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls.session);
|
||||
#elif defined(USE_WOLFSSL)
|
||||
ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.wssl.handle);
|
||||
ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.wssl.ssl);
|
||||
#else
|
||||
#error "ngtcp2 TLS backend not defined"
|
||||
#endif
|
||||
|
||||
@ -1163,13 +1163,15 @@ static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf,
|
||||
const struct Curl_sockaddr_ex *peer_addr = NULL;
|
||||
BIO *bio = NULL;
|
||||
BIO_ADDR *baddr = NULL;
|
||||
static const struct alpn_spec ALPN_SPEC_H3 = {
|
||||
{ "h3" }, 1
|
||||
};
|
||||
|
||||
DEBUGASSERT(ctx->initialized);
|
||||
|
||||
#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);
|
||||
&ALPN_SPEC_H3, NULL, NULL, NULL, NULL);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
|
||||
@ -1267,6 +1267,9 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf,
|
||||
int rv;
|
||||
CURLcode result;
|
||||
const struct Curl_sockaddr_ex *sockaddr;
|
||||
static const struct alpn_spec ALPN_SPEC_H3 = {
|
||||
{ "h3" }, 1
|
||||
};
|
||||
|
||||
DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD);
|
||||
DEBUGASSERT(ctx->initialized);
|
||||
@ -1304,9 +1307,7 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf,
|
||||
- 1);
|
||||
|
||||
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);
|
||||
&ALPN_SPEC_H3, NULL, NULL, cf, NULL);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
|
||||
@ -58,178 +58,11 @@
|
||||
#include "curl_memory.h"
|
||||
#include "memdebug.h"
|
||||
|
||||
#if defined(USE_WOLFSSL)
|
||||
|
||||
#define QUIC_CIPHERS \
|
||||
"TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \
|
||||
"POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
|
||||
#define QUIC_GROUPS "P-256:P-384:P-521"
|
||||
|
||||
#if defined(HAVE_SECRET_CALLBACK)
|
||||
static void keylog_callback(const WOLFSSL *ssl, const char *line)
|
||||
{
|
||||
(void)ssl;
|
||||
Curl_tls_keylog_write_line(line);
|
||||
}
|
||||
#endif
|
||||
|
||||
static CURLcode wssl_init_ctx(struct curl_tls_ctx *ctx,
|
||||
struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
Curl_vquic_tls_ctx_setup *cb_setup,
|
||||
void *cb_user_data)
|
||||
{
|
||||
struct ssl_primary_config *conn_config;
|
||||
CURLcode result = CURLE_FAILED_INIT;
|
||||
|
||||
conn_config = Curl_ssl_cf_get_primary_config(cf);
|
||||
if(!conn_config) {
|
||||
result = CURLE_FAILED_INIT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ctx->wssl.ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
|
||||
if(!ctx->wssl.ctx) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if(cb_setup) {
|
||||
result = cb_setup(cf, data, cb_user_data);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
|
||||
wolfSSL_CTX_set_default_verify_paths(ctx->wssl.ctx);
|
||||
|
||||
if(wolfSSL_CTX_set_cipher_list(ctx->wssl.ctx, conn_config->cipher_list13 ?
|
||||
conn_config->cipher_list13 :
|
||||
QUIC_CIPHERS) != 1) {
|
||||
char error_buffer[256];
|
||||
ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer));
|
||||
failf(data, "wolfSSL failed to set ciphers: %s", error_buffer);
|
||||
result = CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if(wolfSSL_CTX_set1_groups_list(ctx->wssl.ctx, conn_config->curves ?
|
||||
conn_config->curves :
|
||||
(char *)QUIC_GROUPS) != 1) {
|
||||
failf(data, "wolfSSL failed to set curves");
|
||||
result = CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
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()) {
|
||||
#if defined(HAVE_SECRET_CALLBACK)
|
||||
wolfSSL_CTX_set_keylog_callback(ctx->wssl.ctx, keylog_callback);
|
||||
#else
|
||||
failf(data, "wolfSSL was built without keylog callback");
|
||||
result = CURLE_NOT_BUILT_IN;
|
||||
goto out;
|
||||
#endif
|
||||
}
|
||||
|
||||
if(conn_config->verifypeer) {
|
||||
const char * const ssl_cafile = conn_config->CAfile;
|
||||
const char * const ssl_capath = conn_config->CApath;
|
||||
|
||||
wolfSSL_CTX_set_verify(ctx->wssl.ctx, SSL_VERIFY_PEER, NULL);
|
||||
if(ssl_cafile || ssl_capath) {
|
||||
/* tell wolfSSL where to find CA certificates that are used to verify
|
||||
the server's certificate. */
|
||||
int rc =
|
||||
wolfSSL_CTX_load_verify_locations_ex(ctx->wssl.ctx, ssl_cafile,
|
||||
ssl_capath,
|
||||
WOLFSSL_LOAD_FLAG_IGNORE_ERR);
|
||||
if(SSL_SUCCESS != rc) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate verify locations:"
|
||||
" CAfile: %s CApath: %s",
|
||||
ssl_cafile ? ssl_cafile : "none",
|
||||
ssl_capath ? ssl_capath : "none");
|
||||
result = CURLE_SSL_CACERT_BADFILE;
|
||||
goto out;
|
||||
}
|
||||
infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
|
||||
infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
|
||||
}
|
||||
#ifdef CURL_CA_FALLBACK
|
||||
else {
|
||||
/* verifying the peer without any CA certificates will not work so
|
||||
use wolfSSL's built-in default as fallback */
|
||||
wolfSSL_CTX_set_default_verify_paths(ctx->wssl.ctx);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
wolfSSL_CTX_set_verify(ctx->wssl.ctx, SSL_VERIFY_NONE, NULL);
|
||||
}
|
||||
|
||||
/* give application a chance to interfere with SSL set up. */
|
||||
if(data->set.ssl.fsslctx) {
|
||||
Curl_set_in_callback(data, TRUE);
|
||||
result = (*data->set.ssl.fsslctx)(data, ctx->wssl.ctx,
|
||||
data->set.ssl.fsslctxp);
|
||||
Curl_set_in_callback(data, FALSE);
|
||||
if(result) {
|
||||
failf(data, "error signaled by ssl ctx callback");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
result = CURLE_OK;
|
||||
|
||||
out:
|
||||
if(result && ctx->wssl.ctx) {
|
||||
SSL_CTX_free(ctx->wssl.ctx);
|
||||
ctx->wssl.ctx = NULL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** SSL callbacks ***/
|
||||
|
||||
static CURLcode wssl_init_ssl(struct curl_tls_ctx *ctx,
|
||||
struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct ssl_peer *peer,
|
||||
const char *alpn, size_t alpn_len,
|
||||
void *user_data)
|
||||
{
|
||||
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
|
||||
|
||||
DEBUGASSERT(!ctx->wssl.handle);
|
||||
DEBUGASSERT(ctx->wssl.ctx);
|
||||
ctx->wssl.handle = wolfSSL_new(ctx->wssl.ctx);
|
||||
|
||||
wolfSSL_set_app_data(ctx->wssl.handle, user_data);
|
||||
wolfSSL_set_connect_state(ctx->wssl.handle);
|
||||
wolfSSL_set_quic_use_legacy_codepoint(ctx->wssl.handle, 0);
|
||||
|
||||
if(alpn)
|
||||
wolfSSL_set_alpn_protos(ctx->wssl.handle, (const unsigned char *)alpn,
|
||||
(unsigned int)alpn_len);
|
||||
|
||||
if(peer->sni) {
|
||||
wolfSSL_UseSNI(ctx->wssl.handle, WOLFSSL_SNI_HOST_NAME,
|
||||
peer->sni, (unsigned short)strlen(peer->sni));
|
||||
}
|
||||
|
||||
if(ssl_config->primary.cache_session) {
|
||||
(void)Curl_wssl_setup_session(cf, data, &ctx->wssl, peer->scache_key);
|
||||
}
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
#endif /* defined(USE_WOLFSSL) */
|
||||
|
||||
CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
|
||||
struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct ssl_peer *peer,
|
||||
const char *alpn, size_t alpn_len,
|
||||
const struct alpn_spec *alpns,
|
||||
Curl_vquic_tls_ctx_setup *cb_setup,
|
||||
void *cb_user_data, void *ssl_user_data,
|
||||
Curl_vquic_session_reuse_cb *session_reuse_cb)
|
||||
@ -254,21 +87,16 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
(void)result;
|
||||
return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer,
|
||||
(const unsigned char *)alpn, alpn_len,
|
||||
return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer, alpns,
|
||||
cb_setup, cb_user_data, NULL, ssl_user_data);
|
||||
#elif defined(USE_GNUTLS)
|
||||
return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer,
|
||||
(const unsigned char *)alpn, alpn_len,
|
||||
return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer, alpns,
|
||||
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);
|
||||
return Curl_wssl_ctx_init(&ctx->wssl, cf, data, peer, alpns,
|
||||
cb_setup, cb_user_data,
|
||||
ssl_user_data, session_reuse_cb);
|
||||
#else
|
||||
#error "no TLS lib in used, should not happen"
|
||||
return CURLE_FAILED_INIT;
|
||||
@ -287,10 +115,10 @@ void Curl_vquic_tls_cleanup(struct curl_tls_ctx *ctx)
|
||||
gnutls_deinit(ctx->gtls.session);
|
||||
Curl_gtls_shared_creds_free(&ctx->gtls.shared_creds);
|
||||
#elif defined(USE_WOLFSSL)
|
||||
if(ctx->wssl.handle)
|
||||
wolfSSL_free(ctx->wssl.handle);
|
||||
if(ctx->wssl.ctx)
|
||||
wolfSSL_CTX_free(ctx->wssl.ctx);
|
||||
if(ctx->wssl.ssl)
|
||||
wolfSSL_free(ctx->wssl.ssl);
|
||||
if(ctx->wssl.ssl_ctx)
|
||||
wolfSSL_CTX_free(ctx->wssl.ssl_ctx);
|
||||
#endif
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
}
|
||||
@ -351,7 +179,7 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx,
|
||||
(void)data;
|
||||
if(conn_config->verifyhost) {
|
||||
if(peer->sni) {
|
||||
WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(ctx->wssl.handle);
|
||||
WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(ctx->wssl.ssl);
|
||||
if(wolfSSL_X509_check_host(cert, peer->sni, strlen(peer->sni), 0, NULL)
|
||||
== WOLFSSL_FAILURE) {
|
||||
result = CURLE_PEER_FAILED_VERIFICATION;
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
#include "curl_setup.h"
|
||||
#include "bufq.h"
|
||||
#include "vtls/vtls.h"
|
||||
#include "vtls/vtls_int.h"
|
||||
#include "vtls/openssl.h"
|
||||
|
||||
#if defined(USE_HTTP3) && \
|
||||
@ -43,7 +44,7 @@ struct curl_tls_ctx {
|
||||
#elif defined(USE_GNUTLS)
|
||||
struct gtls_ctx gtls;
|
||||
#elif defined(USE_WOLFSSL)
|
||||
struct wolfssl_ctx wssl;
|
||||
struct wssl_ctx wssl;
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -60,6 +61,7 @@ typedef CURLcode Curl_vquic_tls_ctx_setup(struct Curl_cfilter *cf,
|
||||
|
||||
typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct alpn_spec *alpns,
|
||||
struct Curl_ssl_session *scs,
|
||||
bool *do_early_data);
|
||||
|
||||
@ -70,9 +72,7 @@ typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf,
|
||||
* @param cf the connection filter involved
|
||||
* @param data the transfer involved
|
||||
* @param peer the peer that will be connected to
|
||||
* @param alpn the ALPN string in protocol format ((len+bytes+)+),
|
||||
* may be NULL
|
||||
* @param alpn_len the overall number of bytes in `alpn`
|
||||
* @param alpns the ALPN specifications to negotiate, may be NULL
|
||||
* @param cb_setup optional callback for early TLS config
|
||||
* @param cb_user_data user_data param for callback
|
||||
* @param ssl_user_data optional pointer to set in TLS application context
|
||||
@ -82,7 +82,7 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
|
||||
struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct ssl_peer *peer,
|
||||
const char *alpn, size_t alpn_len,
|
||||
const struct alpn_spec *alpns,
|
||||
Curl_vquic_tls_ctx_setup *cb_setup,
|
||||
void *cb_user_data,
|
||||
void *ssl_user_data,
|
||||
|
||||
@ -1042,6 +1042,7 @@ static int keylog_callback(gnutls_session_t session, const char *label,
|
||||
|
||||
static CURLcode gtls_on_session_reuse(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct alpn_spec *alpns,
|
||||
struct Curl_ssl_session *scs,
|
||||
bool *do_early_data)
|
||||
{
|
||||
@ -1057,7 +1058,7 @@ static CURLcode gtls_on_session_reuse(struct Curl_cfilter *cf,
|
||||
/* 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)) {
|
||||
else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) {
|
||||
CURL_TRC_CF(data, cf, "SSL session has different ALPN, no early data");
|
||||
}
|
||||
else {
|
||||
@ -1077,7 +1078,7 @@ 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,
|
||||
const struct alpn_spec *alpns_requested,
|
||||
Curl_gtls_ctx_setup_cb *cb_setup,
|
||||
void *cb_user_data,
|
||||
void *ssl_user_data,
|
||||
@ -1086,13 +1087,16 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
|
||||
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];
|
||||
gnutls_datum_t gtls_alpns[ALPN_ENTRIES_MAX];
|
||||
size_t gtls_alpns_count = 0;
|
||||
bool gtls_session_setup = FALSE;
|
||||
struct alpn_spec alpns;
|
||||
CURLcode result;
|
||||
int rc;
|
||||
|
||||
DEBUGASSERT(gctx);
|
||||
Curl_alpn_copy(&alpns, alpns_requested);
|
||||
|
||||
/* This might be a reconnect, so we check for a session ID in the cache
|
||||
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. */
|
||||
@ -1101,7 +1105,8 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
if(scs && scs->sdata && scs->sdata_len) {
|
||||
if(scs && scs->sdata && scs->sdata_len &&
|
||||
(!scs->alpn || Curl_alpn_contains_proto(&alpns, scs->alpn))) {
|
||||
/* we got a cached session, use it! */
|
||||
|
||||
result = gtls_client_init(cf, data, peer, scs->earlydata_max, gctx);
|
||||
@ -1115,30 +1120,19 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
|
||||
else {
|
||||
infof(data, "SSL reusing session with ALPN '%s'",
|
||||
scs->alpn ? scs->alpn : "-");
|
||||
if(ssl_config->earlydata &&
|
||||
if(ssl_config->earlydata && scs->alpn &&
|
||||
!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);
|
||||
result = sess_reuse_cb(cf, data, &alpns, scs, &do_early_data);
|
||||
if(result)
|
||||
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;
|
||||
gtls_alpns[0].size = (unsigned)strlen(scs->alpn);
|
||||
if(gnutls_alpn_set_protocols(gctx->session,
|
||||
gtls_alpns, 1,
|
||||
GNUTLS_ALPN_MANDATORY)) {
|
||||
failf(data, "failed setting ALPN");
|
||||
result = CURLE_SSL_CONNECT_ERROR;
|
||||
goto out;
|
||||
}
|
||||
/* don't set again below */
|
||||
gtls_alpns_count = 0;
|
||||
alpn = NULL;
|
||||
Curl_alpn_restrict_to(&alpns, scs->alpn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1168,24 +1162,14 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
|
||||
/* 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. */
|
||||
if(!gtls_alpns_count && alpn && alpn_len) {
|
||||
size_t i, alen = alpn_len;
|
||||
unsigned char *salpn = (unsigned char *)alpn;
|
||||
unsigned char slen;
|
||||
for(i = 0; (i < CURL_ARRAYSIZE(gtls_alpns)) && alen; ++i) {
|
||||
slen = salpn[0];
|
||||
if(slen >= alen)
|
||||
return CURLE_FAILED_INIT;
|
||||
gtls_alpns[i].data = salpn + 1;
|
||||
gtls_alpns[i].size = slen;
|
||||
salpn += slen + 1;
|
||||
alen -= (size_t)slen + 1;
|
||||
if(!gtls_alpns_count && alpns.count) {
|
||||
size_t i;
|
||||
DEBUGASSERT(CURL_ARRAYSIZE(gtls_alpns) >= alpns.count);
|
||||
for(i = 0; i < alpns.count; ++i) {
|
||||
gtls_alpns[i].data = (unsigned char *)alpns.entries[i];
|
||||
gtls_alpns[i].size = (unsigned int)strlen(alpns.entries[i]);
|
||||
}
|
||||
if(alen) { /* not all alpn chars used, wrong format or too many */
|
||||
result = CURLE_FAILED_INIT;
|
||||
goto out;
|
||||
}
|
||||
gtls_alpns_count = i;
|
||||
gtls_alpns_count = alpns.count;
|
||||
}
|
||||
|
||||
if(gtls_alpns_count &&
|
||||
@ -1207,7 +1191,6 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct gtls_ssl_backend_data *backend =
|
||||
(struct gtls_ssl_backend_data *)connssl->backend;
|
||||
struct alpn_proto_buf proto;
|
||||
CURLcode result;
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
@ -1217,22 +1200,15 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
same connection */
|
||||
return CURLE_OK;
|
||||
|
||||
memset(&proto, 0, sizeof(proto));
|
||||
if(connssl->alpn) {
|
||||
result = Curl_alpn_to_proto_buf(&proto, connssl->alpn);
|
||||
if(result) {
|
||||
failf(data, "Error determining ALPN");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
result = Curl_gtls_ctx_init(&backend->gtls, cf, data, &connssl->peer,
|
||||
proto.data, proto.len,
|
||||
NULL, NULL, cf, gtls_on_session_reuse);
|
||||
connssl->alpn, NULL, NULL, cf,
|
||||
gtls_on_session_reuse);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
if(connssl->alpn && (connssl->state != ssl_connection_deferred)) {
|
||||
struct alpn_proto_buf proto;
|
||||
memset(&proto, 0, sizeof(proto));
|
||||
Curl_alpn_to_proto_str(&proto, connssl->alpn);
|
||||
infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@
|
||||
|
||||
struct Curl_easy;
|
||||
struct Curl_cfilter;
|
||||
struct alpn_spec;
|
||||
struct ssl_primary_config;
|
||||
struct ssl_config_data;
|
||||
struct ssl_peer;
|
||||
@ -81,6 +82,7 @@ typedef CURLcode Curl_gtls_ctx_setup_cb(struct Curl_cfilter *cf,
|
||||
|
||||
typedef CURLcode Curl_gtls_init_session_reuse_cb(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct alpn_spec *alpns,
|
||||
struct Curl_ssl_session *scs,
|
||||
bool *do_early_data);
|
||||
|
||||
@ -88,7 +90,7 @@ 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,
|
||||
const struct alpn_spec *alpns,
|
||||
Curl_gtls_ctx_setup_cb *cb_setup,
|
||||
void *cb_user_data,
|
||||
void *ssl_user_data,
|
||||
|
||||
@ -3527,7 +3527,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
|
||||
struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct ssl_peer *peer,
|
||||
const unsigned char *alpn, size_t alpn_len,
|
||||
const struct alpn_spec *alpns,
|
||||
Curl_ossl_ctx_setup_cb *cb_setup,
|
||||
void *cb_user_data,
|
||||
Curl_ossl_new_session_cb *cb_new_session,
|
||||
@ -3722,14 +3722,21 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
|
||||
SSL_CTX_set_mode(octx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
#endif
|
||||
|
||||
if(alpn && alpn_len) {
|
||||
#ifdef HAS_ALPN_OPENSSL
|
||||
if(SSL_CTX_set_alpn_protos(octx->ssl_ctx, alpn, (int)alpn_len)) {
|
||||
if(alpns && alpns->count) {
|
||||
struct alpn_proto_buf proto;
|
||||
memset(&proto, 0, sizeof(proto));
|
||||
result = Curl_alpn_to_proto_buf(&proto, alpns);
|
||||
if(result) {
|
||||
failf(data, "Error determining ALPN");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
if(SSL_CTX_set_alpn_protos(octx->ssl_ctx, proto.data, (int)proto.len)) {
|
||||
failf(data, "Error setting ALPN");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
|
||||
if(!result &&
|
||||
@ -4053,25 +4060,14 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
|
||||
struct alpn_proto_buf proto;
|
||||
BIO *bio;
|
||||
CURLcode result;
|
||||
|
||||
DEBUGASSERT(ssl_connect_1 == connssl->connecting_state);
|
||||
DEBUGASSERT(octx);
|
||||
memset(&proto, 0, sizeof(proto));
|
||||
#ifdef HAS_ALPN_OPENSSL
|
||||
if(connssl->alpn) {
|
||||
result = Curl_alpn_to_proto_buf(&proto, connssl->alpn);
|
||||
if(result) {
|
||||
failf(data, "Error determining ALPN");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
result = Curl_ossl_ctx_init(octx, cf, data, &connssl->peer,
|
||||
proto.data, proto.len, NULL, NULL,
|
||||
connssl->alpn, NULL, NULL,
|
||||
ossl_new_session_cb, cf);
|
||||
if(result)
|
||||
return result;
|
||||
@ -4099,6 +4095,8 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
|
||||
|
||||
#ifdef HAS_ALPN_OPENSSL
|
||||
if(connssl->alpn) {
|
||||
struct alpn_proto_buf proto;
|
||||
memset(&proto, 0, sizeof(proto));
|
||||
Curl_alpn_to_proto_str(&proto, connssl->alpn);
|
||||
infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
|
||||
}
|
||||
|
||||
@ -49,6 +49,7 @@
|
||||
#define HAVE_KEYLOG_CALLBACK
|
||||
#endif
|
||||
|
||||
struct alpn_spec;
|
||||
struct ssl_peer;
|
||||
|
||||
/* Struct to hold a curl OpenSSL instance */
|
||||
@ -80,7 +81,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
|
||||
struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct ssl_peer *peer,
|
||||
const unsigned char *alpn, size_t alpn_len,
|
||||
const struct alpn_spec *alpns,
|
||||
Curl_ossl_ctx_setup_cb *cb_setup,
|
||||
void *cb_user_data,
|
||||
Curl_ossl_new_session_cb *cb_new_session,
|
||||
|
||||
@ -1831,6 +1831,24 @@ bool Curl_alpn_contains_proto(const struct alpn_spec *spec,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void Curl_alpn_restrict_to(struct alpn_spec *spec, const char *proto)
|
||||
{
|
||||
size_t plen = strlen(proto);
|
||||
DEBUGASSERT(plen < sizeof(spec->entries[0]));
|
||||
if(plen < sizeof(spec->entries[0])) {
|
||||
memcpy(spec->entries[0], proto, plen + 1);
|
||||
spec->count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Curl_alpn_copy(struct alpn_spec *dest, const struct alpn_spec *src)
|
||||
{
|
||||
if(src)
|
||||
memcpy(dest, src, sizeof(*dest));
|
||||
else
|
||||
memset(dest, 0, sizeof(*dest));
|
||||
}
|
||||
|
||||
CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct ssl_connect_data *connssl,
|
||||
|
||||
@ -49,7 +49,7 @@ struct ssl_connect_data;
|
||||
#define ALPN_PROTO_BUF_MAX (ALPN_ENTRIES_MAX * (ALPN_NAME_MAX + 1))
|
||||
|
||||
struct alpn_spec {
|
||||
const char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX];
|
||||
char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX];
|
||||
size_t count; /* number of entries */
|
||||
};
|
||||
|
||||
@ -62,6 +62,8 @@ CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf,
|
||||
const struct alpn_spec *spec);
|
||||
CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf,
|
||||
const struct alpn_spec *spec);
|
||||
void Curl_alpn_restrict_to(struct alpn_spec *spec, const char *proto);
|
||||
void Curl_alpn_copy(struct alpn_spec *dest, const struct alpn_spec *src);
|
||||
|
||||
CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
|
||||
1457
lib/vtls/wolfssl.c
1457
lib/vtls/wolfssl.c
File diff suppressed because it is too large
Load Diff
@ -29,19 +29,21 @@
|
||||
|
||||
#include "urldata.h"
|
||||
|
||||
struct alpn_spec;
|
||||
struct ssl_peer;
|
||||
struct Curl_ssl_session;
|
||||
|
||||
struct WOLFSSL;
|
||||
typedef struct WOLFSSL WOLFSSL;
|
||||
struct WOLFSSL_CTX;
|
||||
typedef struct WOLFSSL_CTX WOLFSSL_CTX;
|
||||
struct WOLFSSL_SESSION;
|
||||
typedef struct WOLFSSL_SESSION WOLFSSL_SESSION;
|
||||
|
||||
extern const struct Curl_ssl Curl_ssl_wolfssl;
|
||||
|
||||
struct wolfssl_ctx {
|
||||
WOLFSSL_CTX *ctx;
|
||||
WOLFSSL *handle;
|
||||
struct wssl_ctx {
|
||||
struct WOLFSSL_CTX *ssl_ctx;
|
||||
struct WOLFSSL *ssl;
|
||||
CURLcode io_result; /* result of last BIO cfilter operation */
|
||||
CURLcode hs_result; /* result of handshake */
|
||||
int io_send_blocked_len; /* length of last BIO write that EAGAINed */
|
||||
BIT(x509_store_setup); /* x509 store has been set up */
|
||||
BIT(shutting_down); /* TLS is being shut down */
|
||||
@ -49,21 +51,43 @@ struct wolfssl_ctx {
|
||||
|
||||
size_t Curl_wssl_version(char *buffer, size_t size);
|
||||
|
||||
typedef CURLcode Curl_wssl_ctx_setup_cb(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
void *user_data);
|
||||
|
||||
typedef CURLcode Curl_wssl_init_session_reuse_cb(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct alpn_spec *alpns,
|
||||
struct Curl_ssl_session *scs,
|
||||
bool *do_early_data);
|
||||
|
||||
CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx,
|
||||
struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct ssl_peer *peer,
|
||||
const struct alpn_spec *alpns,
|
||||
Curl_wssl_ctx_setup_cb *cb_setup,
|
||||
void *cb_user_data,
|
||||
void *ssl_user_data,
|
||||
Curl_wssl_init_session_reuse_cb *sess_reuse_cb);
|
||||
|
||||
CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct wolfssl_ctx *wssl);
|
||||
struct wssl_ctx *wssl);
|
||||
|
||||
CURLcode Curl_wssl_setup_session(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct wolfssl_ctx *wss,
|
||||
struct wssl_ctx *wss,
|
||||
const char *ssl_peer_key);
|
||||
|
||||
CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
const char *ssl_peer_key,
|
||||
WOLFSSL_SESSION *session,
|
||||
struct WOLFSSL_SESSION *session,
|
||||
int ietf_tls_id,
|
||||
const char *alpn);
|
||||
const char *alpn,
|
||||
unsigned char *quic_tp,
|
||||
size_t quic_tp_len);
|
||||
|
||||
|
||||
#endif /* USE_WOLFSSL */
|
||||
|
||||
@ -577,8 +577,8 @@ class TestDownload:
|
||||
@pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx")
|
||||
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
|
||||
def test_02_32_earlydata(self, env: Env, httpd, nghttpx, proto):
|
||||
if not env.curl_uses_lib('gnutls'):
|
||||
pytest.skip('TLS earlydata only implemented in GnuTLS')
|
||||
if not env.curl_uses_lib('gnutls') and not env.curl_uses_lib('wolfssl'):
|
||||
pytest.skip('TLS earlydata only implemented in GnuTLS/wolfSSL')
|
||||
if proto == 'h3' and not env.have_h3():
|
||||
pytest.skip("h3 not supported")
|
||||
count = 2
|
||||
|
||||
@ -703,8 +703,8 @@ class TestUpload:
|
||||
# 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'):
|
||||
pytest.skip('TLS earlydata only implemented in GnuTLS')
|
||||
if not env.curl_uses_lib('gnutls') and not env.curl_uses_lib('wolfssl'):
|
||||
pytest.skip('TLS earlydata only implemented in GnuTLS/wolfSSL')
|
||||
if proto == 'h3' and not env.have_h3():
|
||||
pytest.skip("h3 not supported")
|
||||
count = 2
|
||||
|
||||
@ -207,8 +207,8 @@ class TestCaddy:
|
||||
|
||||
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
|
||||
def test_08_08_earlydata(self, env: Env, httpd, caddy, proto):
|
||||
if not env.curl_uses_lib('gnutls'):
|
||||
pytest.skip('TLS earlydata only implemented in GnuTLS')
|
||||
if not env.curl_uses_lib('gnutls') and not env.curl_uses_lib('wolfssl'):
|
||||
pytest.skip('TLS earlydata only implemented in GnuTLS/wolfSSL')
|
||||
if proto == 'h3' and not env.have_h3():
|
||||
pytest.skip("h3 not supported")
|
||||
count = 2
|
||||
|
||||
@ -57,6 +57,7 @@ class TestShutdown:
|
||||
def test_19_01_check_tcp_rst(self, env: Env, httpd, proto):
|
||||
if env.ci_run:
|
||||
pytest.skip("seems not to work in CI")
|
||||
# timing critical, disable trace overrides
|
||||
run_env = os.environ.copy()
|
||||
if 'CURL_DEBUG' in run_env:
|
||||
del run_env['CURL_DEBUG']
|
||||
|
||||
Loading…
Reference in New Issue
Block a user