]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
OpenSSL/quictls: add support for TLSv1.3 early data
authorStefan Eissing <stefan@eissing.org>
Tue, 25 Feb 2025 14:07:19 +0000 (15:07 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 3 Mar 2025 08:27:04 +0000 (09:27 +0100)
based on #16450

Adds support for TLSv1.3 early data for TCP and QUIC via ngtcp2.

Closes #16477

12 files changed:
docs/libcurl/opts/CURLOPT_SSL_OPTIONS.md
lib/vquic/curl_ngtcp2.c
lib/vquic/vquic-tls.c
lib/vtls/gtls.c
lib/vtls/openssl.c
lib/vtls/openssl.h
lib/vtls/wolfssl.c
tests/http/test_02_download.py
tests/http/test_07_upload.py
tests/http/test_08_caddy.py
tests/http/test_17_ssl_use.py
tests/http/testenv/env.py

index 737fdeb1e6474e7843cbe90554deab8a98caa8a2..a9c190902fe3d2fb68108bb210e61ca88778a968 100644 (file)
@@ -89,11 +89,13 @@ could be a privacy violation and unexpected.
 ## CURLSSLOPT_EARLYDATA
 
 Tell libcurl to try sending application data as TLS1.3 early data. This option
-is supported for GnuTLS and wolfSSL. This option works on a best effort basis,
+is supported for GnuTLS, wolfSSL, quictls and OpenSSL (but not BoringSSL
+or AWS-LC). It works on TCP and QUIC connections using ngtcp2.
+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 for GnuTLS and 8.13.0 for wolfSSL)
+(Added in 8.11.0 for GnuTLS and 8.13.0 for wolfSSL, quictls and OpenSSL)
 
 # DEFAULT
 
index 2a90167ac528dce1767ef719b6e3e9958374ebe3..20670a2ec744eeab5f4109a97e32c9727b6d7886 100644 (file)
@@ -482,6 +482,11 @@ static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data)
   if(ctx->use_earlydata)
     Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at);
   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);
+#endif
 #ifdef USE_GNUTLS
     int flags = gnutls_session_get_flags(ctx->tls.gtls.session);
     ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA);
@@ -2183,8 +2188,24 @@ static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
   ctx = cf ? cf->ctx : NULL;
   data = cf ? CF_DATA_CURRENT(cf) : NULL;
   if(cf && data && ctx) {
+    unsigned char *quic_tp = NULL;
+    size_t quic_tp_len = 0;
+#ifdef HAVE_OPENSSL_EARLYDATA
+    ngtcp2_ssize tplen;
+    uint8_t tpbuf[256];
+
+    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;
+    }
+#endif
     Curl_ossl_add_session(cf, data, ctx->peer.scache_key, ssl_sessionid,
-                          SSL_version(ssl), "h3");
+                          SSL_version(ssl), "h3", quic_tp, quic_tp_len);
     return 1;
   }
   return 0;
@@ -2355,6 +2376,9 @@ static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf,
   CURLcode result = CURLE_OK;
 
   *do_early_data = FALSE;
+#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA)
+  ctx->earlydata_max = scs->earlydata_max;
+#endif
 #ifdef USE_GNUTLS
   ctx->earlydata_max =
     gnutls_record_get_max_early_data_size(ctx->tls.gtls.session);
@@ -2366,7 +2390,8 @@ static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf,
   ctx->earlydata_max = 0;
 #endif /* WOLFSSL_EARLY_DATA */
 #endif
-#if defined(USE_GNUTLS) || defined(USE_WOLFSSL)
+#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \
+    (defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA))
   if((!ctx->earlydata_max)) {
     CURL_TRC_CF(data, cf, "SSL session does not allow earlydata");
   }
@@ -2394,7 +2419,7 @@ static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf,
       }
     }
   }
-#else /* USE_GNUTLS */
+#else /* not supported in the TLS backend */
   (void)data;
   (void)ctx;
   (void)scs;
index 56e02799cb94fd9fcdbd5a1fb33309860010976f..ec792a65a2dd13062d7f523af8b4715d3e6e5300 100644 (file)
@@ -88,7 +88,8 @@ 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, alpns,
-                            cb_setup, cb_user_data, NULL, ssl_user_data);
+                            cb_setup, cb_user_data, NULL, ssl_user_data,
+                            session_reuse_cb);
 #elif defined(USE_GNUTLS)
   return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer, alpns,
                             cb_setup, cb_user_data, ssl_user_data,
index 698ceed546c31ee57fbda264f79aaa0420b30511..841a6d1d6550cc755e7d20d751f9aad7b5484a51 100644 (file)
@@ -1780,8 +1780,7 @@ static CURLcode gtls_send_earlydata(struct Curl_cfilter *cf,
     Curl_bufq_skip(&connssl->earlydata, (size_t)n);
   }
   /* sent everything there was */
-  infof(data, "SSL sending %" FMT_OFF_T " bytes of early data",
-        connssl->earlydata_skip);
+  infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip);
 out:
   return result;
 }
index c7d84291c75f8e9e72638179e72e1f0c2afae170..e10324ec1352406492f23d6caa2f114a714d5462 100644 (file)
@@ -62,6 +62,7 @@
 #include "strcase.h"
 #include "hostcheck.h"
 #include "multiif.h"
+#include "strdup.h"
 #include "strerror.h"
 #include "curl_printf.h"
 
@@ -2927,10 +2928,13 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
                                const char *ssl_peer_key,
                                SSL_SESSION *session,
                                int ietf_tls_id,
-                               const char *alpn)
+                               const char *alpn,
+                               unsigned char *quic_tp,
+                               size_t quic_tp_len)
 {
   const struct ssl_config_data *config;
   unsigned char *der_session_buf = NULL;
+  unsigned char *qtp_clone = NULL;
   CURLcode result = CURLE_OK;
 
   if(!cf || !data)
@@ -2941,6 +2945,7 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
     struct Curl_ssl_session *sc_session = NULL;
     size_t der_session_size;
     unsigned char *der_session_ptr;
+    size_t earlydata_max = 0;
 
     der_session_size = i2d_SSL_SESSION(session, NULL);
     if(der_session_size == 0) {
@@ -2960,11 +2965,23 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
       goto out;
     }
 
-    result = Curl_ssl_session_create(der_session_buf, der_session_size,
-                                     ietf_tls_id, alpn,
-                                     (curl_off_t)time(NULL) +
-                                     SSL_SESSION_get_timeout(session), 0,
-                                     &sc_session);
+#ifdef HAVE_OPENSSL_EARLYDATA
+    earlydata_max = SSL_SESSION_get_max_early_data(session);
+#endif
+    if(quic_tp && quic_tp_len) {
+      qtp_clone = Curl_memdup0((char *)quic_tp, quic_tp_len);
+      if(!qtp_clone) {
+        result = CURLE_OUT_OF_MEMORY;
+        goto out;
+      }
+    }
+
+    result = Curl_ssl_session_create2(der_session_buf, der_session_size,
+                                      ietf_tls_id, alpn,
+                                      (curl_off_t)time(NULL) +
+                                      SSL_SESSION_get_timeout(session),
+                                      earlydata_max, qtp_clone, quic_tp_len,
+                                      &sc_session);
     der_session_buf = NULL;  /* took ownership of sdata */
     if(!result) {
       result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session);
@@ -2987,7 +3004,8 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
     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);
+                          SSL_version(ssl), connssl->negotiated.alpn,
+                          NULL, 0);
   }
   return 0;
 }
@@ -3527,11 +3545,12 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
                             struct Curl_cfilter *cf,
                             struct Curl_easy *data,
                             struct ssl_peer *peer,
-                            const struct alpn_spec *alpns,
+                            const struct alpn_spec *alpns_requested,
                             Curl_ossl_ctx_setup_cb *cb_setup,
                             void *cb_user_data,
                             Curl_ossl_new_session_cb *cb_new_session,
-                            void *ssl_user_data)
+                            void *ssl_user_data,
+                            Curl_ossl_init_session_reuse_cb *sess_reuse_cb)
 {
   CURLcode result = CURLE_OK;
   const char *ciphers;
@@ -3545,12 +3564,14 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
   const char * const ssl_cert_type = ssl_config->cert_type;
   const bool verifypeer = conn_config->verifypeer;
   char error_buffer[256];
+  struct alpn_spec alpns;
 
   /* Make funny stuff to get random input */
   result = ossl_seed(data);
   if(result)
     return result;
 
+  Curl_alpn_copy(&alpns, alpns_requested);
   ssl_config->certverifyresult = !X509_V_OK;
 
   switch(peer->transport) {
@@ -3722,22 +3743,6 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
   SSL_CTX_set_mode(octx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
 #endif
 
-#ifdef HAS_ALPN_OPENSSL
-  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
-
   if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
     if(!result &&
        !cert_stuff(data, octx->ssl_ctx,
@@ -4020,12 +4025,12 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
 
   octx->reused_session = FALSE;
   if(ssl_config->primary.cache_session) {
-    struct Curl_ssl_session *sc_session = NULL;
+    struct Curl_ssl_session *scs = NULL;
 
-    result = Curl_ssl_scache_take(cf, data, peer->scache_key, &sc_session);
-    if(!result && sc_session && sc_session->sdata && sc_session->sdata_len) {
-      const unsigned char *der_sessionid = sc_session->sdata;
-      size_t der_sessionid_size = sc_session->sdata_len;
+    result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs);
+    if(!result && scs && scs->sdata && scs->sdata_len) {
+      const unsigned char *der_sessionid = scs->sdata;
+      size_t der_sessionid_size = scs->sdata_len;
       SSL_SESSION *ssl_session = NULL;
 
       /* If OpenSSL does not accept the session from the cache, this
@@ -4040,8 +4045,29 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
                               sizeof(error_buffer)));
         }
         else {
-          infof(data, "SSL reusing session");
+          infof(data, "SSL reusing session with ALPN '%s'",
+                scs->alpn ? scs->alpn : "-");
           octx->reused_session = TRUE;
+#ifdef HAVE_OPENSSL_EARLYDATA
+          if(ssl_config->earlydata && scs->alpn &&
+             SSL_SESSION_get_max_early_data(ssl_session) &&
+             !cf->conn->connect_only &&
+             (SSL_version(octx->ssl) == TLS1_3_VERSION)) {
+            bool do_early_data = FALSE;
+            if(sess_reuse_cb) {
+              result = sess_reuse_cb(cf, data, &alpns, scs, &do_early_data);
+              if(result)
+                return result;
+            }
+            if(do_early_data) {
+              /* We only try the ALPN protocol the session used before,
+               * otherwise we might send early data for the wrong protocol */
+              Curl_alpn_restrict_to(&alpns, scs->alpn);
+            }
+          }
+#else
+          (void)sess_reuse_cb;
+#endif
         }
         SSL_SESSION_free(ssl_session);
       }
@@ -4049,12 +4075,58 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
         infof(data, "SSL session not accepted by OpenSSL, continuing without");
       }
     }
-    Curl_ssl_scache_return(cf, data, peer->scache_key, sc_session);
+    Curl_ssl_scache_return(cf, data, peer->scache_key, scs);
+  }
+
+#ifdef HAS_ALPN_OPENSSL
+  if(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_set_alpn_protos(octx->ssl, proto.data, (int)proto.len)) {
+      failf(data, "Error setting ALPN");
+      return CURLE_SSL_CONNECT_ERROR;
+    }
   }
+#endif
 
   return CURLE_OK;
 }
 
+static CURLcode ossl_on_session_reuse(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      struct alpn_spec *alpns,
+                                      struct Curl_ssl_session *scs,
+                                      bool *do_early_data)
+{
+  struct ssl_connect_data *connssl = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  *do_early_data = FALSE;
+  connssl->earlydata_max = scs->earlydata_max;
+  if(!connssl->earlydata_max) {
+    CURL_TRC_CF(data, cf, "SSL session does not allow earlydata");
+  }
+  else if(!Curl_alpn_contains_proto(alpns, 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_await;
+    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;
+}
+
 static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
                                    struct Curl_easy *data)
 {
@@ -4068,7 +4140,8 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
 
   result = Curl_ossl_ctx_init(octx, cf, data, &connssl->peer,
                               connssl->alpn, NULL, NULL,
-                              ossl_new_session_cb, cf);
+                              ossl_new_session_cb, cf,
+                              ossl_on_session_reuse);
   if(result)
     return result;
 
@@ -4094,7 +4167,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
 #endif
 
 #ifdef HAS_ALPN_OPENSSL
-  if(connssl->alpn) {
+  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);
@@ -4209,27 +4282,25 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf,
     if(SSL_ERROR_WANT_READ == detail) {
       CURL_TRC_CF(data, cf, "SSL_connect() -> want recv");
       connssl->io_need = CURL_SSL_IO_NEED_RECV;
-      return CURLE_OK;
+      return CURLE_AGAIN;
     }
     if(SSL_ERROR_WANT_WRITE == detail) {
       CURL_TRC_CF(data, cf, "SSL_connect() -> want send");
       connssl->io_need = CURL_SSL_IO_NEED_SEND;
-      return CURLE_OK;
+      return CURLE_AGAIN;
     }
 #ifdef SSL_ERROR_WANT_ASYNC
     if(SSL_ERROR_WANT_ASYNC == detail) {
       CURL_TRC_CF(data, cf, "SSL_connect() -> want async");
       connssl->io_need = CURL_SSL_IO_NEED_RECV;
-      connssl->connecting_state = ssl_connect_2;
-      return CURLE_OK;
+      return CURLE_AGAIN;
     }
 #endif
 #ifdef SSL_ERROR_WANT_RETRY_VERIFY
     if(SSL_ERROR_WANT_RETRY_VERIFY == detail) {
       CURL_TRC_CF(data, cf, "SSL_connect() -> want retry_verify");
       connssl->io_need = CURL_SSL_IO_NEED_RECV;
-      connssl->connecting_state = ssl_connect_2;
-      return CURLE_OK;
+      return CURLE_AGAIN;
     }
 #endif
     else {
@@ -4787,15 +4858,92 @@ static CURLcode ossl_connect_step3(struct Curl_cfilter *cf,
    */
 
   result = Curl_oss_check_peer_cert(cf, data, octx, &connssl->peer);
-  if(!result)
-    connssl->connecting_state = ssl_connect_done;
-  else
+  if(result)
     /* on error, remove sessions we might have in the pool */
     Curl_ssl_scache_remove_all(cf, data, connssl->peer.scache_key);
 
   return result;
 }
 
+#ifdef HAVE_OPENSSL_EARLYDATA
+static CURLcode ossl_send_earlydata(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data)
+{
+  struct ssl_connect_data *connssl = cf->ctx;
+  struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
+  CURLcode result = CURLE_OK;
+  const unsigned char *buf;
+  size_t blen, nwritten;
+  int rc;
+
+  DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sending);
+  octx->io_result = CURLE_OK;
+  while(Curl_bufq_peek(&connssl->earlydata, &buf, &blen)) {
+    nwritten = 0;
+    rc = SSL_write_early_data(octx->ssl, buf, blen, &nwritten);
+    CURL_TRC_CF(data, cf, "SSL_write_early_data(len=%zu) -> %d, %zu",
+                blen, rc, nwritten);
+    if(rc <= 0) {
+      long sslerror;
+      char error_buffer[256];
+      int err = SSL_get_error(octx->ssl, rc);
+
+      switch(err) {
+      case SSL_ERROR_WANT_READ:
+        connssl->io_need = CURL_SSL_IO_NEED_RECV;
+        result = CURLE_AGAIN;
+        goto out;
+      case SSL_ERROR_WANT_WRITE:
+        connssl->io_need = CURL_SSL_IO_NEED_SEND;
+        result = CURLE_AGAIN;
+        goto out;
+      case SSL_ERROR_SYSCALL: {
+        int sockerr = SOCKERRNO;
+
+        if(octx->io_result == CURLE_AGAIN) {
+          result = CURLE_AGAIN;
+          goto out;
+        }
+        sslerror = ERR_get_error();
+        if(sslerror)
+          ossl_strerror(sslerror, error_buffer, sizeof(error_buffer));
+        else if(sockerr)
+          Curl_strerror(sockerr, error_buffer, sizeof(error_buffer));
+        else
+          msnprintf(error_buffer, sizeof(error_buffer), "%s",
+                    SSL_ERROR_to_str(err));
+
+        failf(data, OSSL_PACKAGE " SSL_write:early_data: %s, errno %d",
+              error_buffer, sockerr);
+        result = CURLE_SEND_ERROR;
+        goto out;
+      }
+      case SSL_ERROR_SSL: {
+        /*  A failure in the SSL library occurred, usually a protocol error.
+            The OpenSSL error queue contains more information on the error. */
+        sslerror = ERR_get_error();
+        failf(data, "SSL_write_early_data() error: %s",
+              ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)));
+        result = CURLE_SEND_ERROR;
+        goto out;
+      }
+      default:
+        /* a true error */
+        failf(data, OSSL_PACKAGE " SSL_write_early_data: %s, errno %d",
+              SSL_ERROR_to_str(err), SOCKERRNO);
+        result = CURLE_SEND_ERROR;
+        goto out;
+      }
+    }
+    Curl_bufq_skip(&connssl->earlydata, nwritten);
+  }
+  /* sent everything there was */
+  infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip);
+out:
+  return result;
+}
+#endif /* HAVE_OPENSSL_EARLYDATA */
+
 static CURLcode ossl_connect(struct Curl_cfilter *cf,
                              struct Curl_easy *data,
                              bool *done)
@@ -4813,29 +4961,63 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf,
   connssl->io_need = CURL_SSL_IO_NEED_NONE;
 
   if(ssl_connect_1 == connssl->connecting_state) {
+    CURL_TRC_CF(data, cf, "ossl_connect, step1");
     result = ossl_connect_step1(cf, data);
     if(result)
       goto out;
   }
 
   if(ssl_connect_2 == connssl->connecting_state) {
+    CURL_TRC_CF(data, cf, "ossl_connect, step2");
+#ifdef HAVE_OPENSSL_EARLYDATA
+    if(connssl->earlydata_state == ssl_earlydata_await) {
+      goto out;
+    }
+    else if(connssl->earlydata_state == ssl_earlydata_sending) {
+      result = ossl_send_earlydata(cf, data);
+      if(result)
+        goto out;
+      connssl->earlydata_state = ssl_earlydata_sent;
+    }
+#endif
+    DEBUGASSERT((connssl->earlydata_state == ssl_earlydata_none) ||
+                (connssl->earlydata_state == ssl_earlydata_sent));
+
     result = ossl_connect_step2(cf, data);
     if(result)
       goto out;
   }
 
   if(ssl_connect_3 == connssl->connecting_state) {
+    CURL_TRC_CF(data, cf, "ossl_connect, step3");
     result = ossl_connect_step3(cf, data);
     if(result)
       goto out;
+    connssl->connecting_state = ssl_connect_done;
+#ifdef HAVE_OPENSSL_EARLYDATA
+    if(connssl->earlydata_state > ssl_earlydata_none) {
+      struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
+      /* We should be in this state by now */
+      DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sent);
+      connssl->earlydata_state =
+        (SSL_get_early_data_status(octx->ssl) == SSL_EARLY_DATA_ACCEPTED) ?
+        ssl_earlydata_accepted : ssl_earlydata_rejected;
+    }
+#endif
   }
 
   if(ssl_connect_done == connssl->connecting_state) {
+    CURL_TRC_CF(data, cf, "ossl_connect, done");
     connssl->state = ssl_connection_complete;
-    *done = TRUE;
   }
 
 out:
+  if(result == CURLE_AGAIN) {
+    *done = FALSE;
+    return CURLE_OK;
+  }
+  *done = ((connssl->state == ssl_connection_complete) ||
+           (connssl->state == ssl_connection_deferred));
   return result;
 }
 
index b5c4e4aa469e64cddee333c3f4270709b3c45eaa..bc27c2291b80a08c4e3217f1199f3013daff50d5 100644 (file)
 #define HAVE_KEYLOG_CALLBACK
 #endif
 
+/* Check for OpenSSL 1.1.1 which has early data support. */
+#undef HAVE_OPENSSL_EARLYDATA
+#if OPENSSL_VERSION_NUMBER >= 0x10100010L && defined(TLS1_3_VERSION) && \
+    !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC)
+#define HAVE_OPENSSL_EARLYDATA
+#endif
+
 struct alpn_spec;
 struct ssl_peer;
+struct Curl_ssl_session;
 
 /* Struct to hold a curl OpenSSL instance */
 struct ossl_ctx {
@@ -76,6 +84,11 @@ typedef CURLcode Curl_ossl_ctx_setup_cb(struct Curl_cfilter *cf,
                                         void *user_data);
 
 typedef int Curl_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid);
+typedef CURLcode Curl_ossl_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_ossl_ctx_init(struct ossl_ctx *octx,
                             struct Curl_cfilter *cf,
@@ -85,7 +98,8 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
                             Curl_ossl_ctx_setup_cb *cb_setup,
                             void *cb_user_data,
                             Curl_ossl_new_session_cb *cb_new_session,
-                            void *ssl_user_data);
+                            void *ssl_user_data,
+                            Curl_ossl_init_session_reuse_cb *sess_reuse_cb);
 
 #if (OPENSSL_VERSION_NUMBER < 0x30000000L)
 #define SSL_get1_peer_certificate SSL_get_peer_certificate
@@ -114,7 +128,9 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
                                const char *ssl_peer_key,
                                SSL_SESSION *ssl_sessionid,
                                int ietf_tls_id,
-                               const char *alpn);
+                               const char *alpn,
+                               unsigned char *quic_tp,
+                               size_t quic_tp_len);
 
 /*
  * Get the server cert, verify it and show it, etc., only call failf() if
index 5d909b645d53032db48f1bb81c7aeee3d0317e32..fca5a9f4fc8519e3dd66951d1d28a16f3cb1c0e7 100644 (file)
@@ -1635,8 +1635,7 @@ static CURLcode wssl_send_earlydata(struct Curl_cfilter *cf,
   connssl->earlydata_state = ssl_earlydata_sent;
   if(!Curl_ssl_cf_is_proxy(cf))
     Curl_pgrsEarlyData(data, (curl_off_t)connssl->earlydata_skip);
-  infof(data, "SSL sending %" FMT_OFF_T " bytes of early data",
-        connssl->earlydata_skip);
+  infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip);
 out:
   return result;
 }
index fcc76000dd426aab639201020b80d3a644727dae..fa2c002091ede898aed1e05689067c3ffecefac1 100644 (file)
@@ -577,9 +577,10 @@ 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') and not env.curl_uses_lib('wolfssl'):
-            pytest.skip('TLS earlydata only implemented in GnuTLS/wolfSSL')
-        if proto == 'h3' and not env.have_h3():
+        if not env.curl_can_early_data():
+            pytest.skip('TLS earlydata not implemented')
+        if proto == 'h3' and \
+           (not env.have_h3() or not env.curl_can_h3_early_data()):
             pytest.skip("h3 not supported")
         count = 2
         docname = 'data-10k'
index 241887193a7fc4e8d058e58dc3d7e61ddbf67926..53e5a00a35d2b22dc56348ca2b566d9e24c39aa2 100644 (file)
@@ -703,9 +703,10 @@ 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') and not env.curl_uses_lib('wolfssl'):
-            pytest.skip('TLS earlydata only implemented in GnuTLS/wolfSSL')
-        if proto == 'h3' and not env.have_h3():
+        if not env.curl_can_early_data():
+            pytest.skip('TLS earlydata not implemented')
+        if proto == 'h3' and \
+           (not env.have_h3() or not env.curl_can_h3_early_data()):
             pytest.skip("h3 not supported")
         count = 2
         # we want this test to always connect to nghttpx, since it is
index a2ee55d876f1310a2bc0df3ca240f1a5602491b7..cd16e0d954d46e08498f05015165631ab0fedc1f 100644 (file)
@@ -207,9 +207,10 @@ 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') and not env.curl_uses_lib('wolfssl'):
-            pytest.skip('TLS earlydata only implemented in GnuTLS/wolfSSL')
-        if proto == 'h3' and not env.have_h3():
+        if not env.curl_can_early_data():
+            pytest.skip('TLS earlydata not implemented')
+        if proto == 'h3' and \
+           (not env.have_h3() or not env.curl_can_h3_early_data()):
             pytest.skip("h3 not supported")
         count = 2
         docname = 'data10k.data'
index fc58ae837005ec2cfcf12d10ff345029d1f7c644..427f3b88ba48cacef61e7089564879c837a297a2 100644 (file)
@@ -252,8 +252,8 @@ class TestSSLUse:
         if proto == 'h3' and not env.have_h3():
             pytest.skip("h3 not supported")
         if not env.curl_uses_lib('openssl') and \
-            not env.curl_uses_lib('gnutls') and \
-            not env.curl_uses_lib('quictls'):
+           not env.curl_uses_lib('gnutls') and \
+           not env.curl_uses_lib('quictls'):
             pytest.skip("TLS library does not support --cert-status")
         curl = CurlClient(env=env)
         domain = 'localhost'
@@ -318,8 +318,8 @@ class TestSSLUse:
         if not env.have_h3():
             pytest.skip("h3 not supported")
         if not env.curl_uses_lib('quictls') and \
-            not env.curl_uses_lib('gnutls') and \
-            not env.curl_uses_lib('wolfssl'):
+           not env.curl_uses_lib('gnutls') and \
+           not env.curl_uses_lib('wolfssl'):
             pytest.skip("QUIC session reuse not implemented")
         count = 2
         docname = 'data-10k'
index 3b13a5c6bfe42789ed6851bbb05a57893a5912f5..a4ab7a9801770b083b741744776ef13d5e4c2cb2 100644 (file)
@@ -382,6 +382,18 @@ class Env:
     def curl_is_debug() -> bool:
         return Env.CONFIG.curl_is_debug
 
+    @staticmethod
+    def curl_can_early_data() -> bool:
+        return Env.curl_uses_lib('gnutls') or \
+            Env.curl_uses_lib('wolfssl') or \
+            Env.curl_uses_lib('quictls') or \
+            Env.curl_uses_lib('openssl')
+
+    @staticmethod
+    def curl_can_h3_early_data() -> bool:
+        return Env.curl_can_early_data() and \
+            Env.curl_uses_lib('ngtcp2')
+
     @staticmethod
     def have_h3() -> bool:
         return Env.have_h3_curl() and Env.have_h3_server()