]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
quic: use the session cache with wolfSSL as well
authorStefan Eissing <stefan@eissing.org>
Tue, 22 Oct 2024 12:13:00 +0000 (14:13 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 24 Oct 2024 12:55:23 +0000 (14:55 +0200)
Use session cache for QUIC when built with quictls or wolfSSL.

Add test_017_10 for verifying QUIC TLS session reuse when built with
quictls, gnutls or wolfssl.

Closes #15358

lib/vquic/curl_ngtcp2.c
lib/vquic/vquic-tls.c
lib/vtls/openssl.c
lib/vtls/wolfssl.c
lib/vtls/wolfssl.h
tests/http/test_02_download.py
tests/http/test_17_ssl_use.py

index bf8381e6dd57b9ed112230135b90b9f48c394f8d..4a3c00459171350e0ed392efdfc27eec56893415 100644 (file)
@@ -41,6 +41,7 @@
 #include "vtls/gtls.h"
 #elif defined(USE_WOLFSSL)
 #include <ngtcp2/ngtcp2_crypto_wolfssl.h>
+#include "vtls/wolfssl.h"
 #endif
 
 #include "urldata.h"
@@ -2160,11 +2161,11 @@ static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
 {
   ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session);
   struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL;
-  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL;
 
   (void)msg;
   (void)incoming;
-  if(when) { /* after message has been processed */
+  if(when && cf && ctx) { /* after message has been processed */
     struct Curl_easy *data = CF_DATA_CURRENT(cf);
     DEBUGASSERT(data);
     if(data) {
@@ -2184,12 +2185,32 @@ static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
 }
 #endif /* USE_GNUTLS */
 
+#ifdef USE_WOLFSSL
+static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session)
+{
+  ngtcp2_crypto_conn_ref *conn_ref = wolfSSL_get_app_data(ssl);
+  struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL;
+
+  DEBUGASSERT(cf != NULL);
+  if(cf && session) {
+    struct cf_ngtcp2_ctx *ctx = cf->ctx;
+    struct Curl_easy *data = CF_DATA_CURRENT(cf);
+    DEBUGASSERT(data);
+    if(data && ctx) {
+      (void)wssl_cache_session(cf, data, &ctx->peer, session);
+    }
+  }
+  return 0;
+}
+#endif /* USE_WOLFSSL */
+
 static CURLcode tls_ctx_setup(struct Curl_cfilter *cf,
                               struct Curl_easy *data,
                               void *user_data)
 {
   struct curl_tls_ctx *ctx = user_data;
-  (void)cf;
+  struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+
 #ifdef USE_OPENSSL
 #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
   if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ossl.ssl_ctx)
@@ -2203,29 +2224,37 @@ static CURLcode tls_ctx_setup(struct Curl_cfilter *cf,
     return CURLE_FAILED_INIT;
   }
 #endif /* !OPENSSL_IS_BORINGSSL && !OPENSSL_IS_AWSLC */
-  /* Enable the session cache because it is a prerequisite for the
-   * "new session" callback. Use the "external storage" mode to prevent
-   * OpenSSL from creating an internal session cache.
-   */
-  SSL_CTX_set_session_cache_mode(ctx->ossl.ssl_ctx,
-                                 SSL_SESS_CACHE_CLIENT |
-                                 SSL_SESS_CACHE_NO_INTERNAL);
-  SSL_CTX_sess_set_new_cb(ctx->ossl.ssl_ctx, quic_ossl_new_session_cb);
+  if(ssl_config->primary.cache_session) {
+    /* Enable the session cache because it is a prerequisite for the
+     * "new session" callback. Use the "external storage" mode to prevent
+     * OpenSSL from creating an internal session cache.
+     */
+    SSL_CTX_set_session_cache_mode(ctx->ossl.ssl_ctx,
+                                   SSL_SESS_CACHE_CLIENT |
+                                   SSL_SESS_CACHE_NO_INTERNAL);
+    SSL_CTX_sess_set_new_cb(ctx->ossl.ssl_ctx, quic_ossl_new_session_cb);
+  }
 
 #elif defined(USE_GNUTLS)
   if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls.session) != 0) {
     failf(data, "ngtcp2_crypto_gnutls_configure_client_session failed");
     return CURLE_FAILED_INIT;
   }
-  gnutls_handshake_set_hook_function(ctx->gtls.session,
-                                     GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST,
-                                     quic_gtls_handshake_cb);
+  if(ssl_config->primary.cache_session) {
+    gnutls_handshake_set_hook_function(ctx->gtls.session,
+                                       GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST,
+                                       quic_gtls_handshake_cb);
+  }
 
 #elif defined(USE_WOLFSSL)
   if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.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);
+  }
 #endif
   return CURLE_OK;
 }
@@ -2305,8 +2334,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
   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);
-#else
+#elif defined(USE_WOLFSSL)
   ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.wssl.handle);
+#else
+  #error "ngtcp2 TLS backend not defined"
 #endif
 
   ngtcp2_ccerr_default(&ctx->last_error);
index 1caa83828af133dfcab9281cec0dc322f5b862c6..5afe23ee22f6a0ab647b28bc6e4febe12d703bff 100644 (file)
@@ -195,12 +195,14 @@ out:
 /** SSL callbacks ***/
 
 static CURLcode Curl_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)
 {
-  (void)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);
@@ -218,6 +220,10 @@ static CURLcode Curl_wssl_init_ssl(struct curl_tls_ctx *ctx,
                    peer->sni, (unsigned short)strlen(peer->sni));
   }
 
+  if(ssl_config->primary.cache_session) {
+    (void)wssl_setup_session(cf, data, &ctx->wssl, peer);
+  }
+
   return CURLE_OK;
 }
 #endif /* defined(USE_WOLFSSL) */
@@ -247,7 +253,8 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
   if(result)
     return result;
 
-  return Curl_wssl_init_ssl(ctx, data, peer, alpn, alpn_len, ssl_user_data);
+  return Curl_wssl_init_ssl(ctx, cf, data, peer, alpn, alpn_len,
+                            ssl_user_data);
 #else
 #error "no TLS lib in used, should not happen"
   return CURLE_FAILED_INIT;
index bb84cb85ad9dc066fb9ea222190c10f9f6cdc304..f0c5622496a203cdc5aae53666f0d8e81a8b4bad 100644 (file)
@@ -3974,7 +3974,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
 #endif
 
   octx->reused_session = FALSE;
-  if(ssl_config->primary.cache_session && transport == TRNSPRT_TCP) {
+  if(ssl_config->primary.cache_session) {
     Curl_ssl_sessionid_lock(data);
     if(!Curl_ssl_getsessionid(cf, data, peer, (void **)&der_sessionid,
       &der_sessionid_size, NULL)) {
index c77b18afc7752866ecbbf0160fc320b0f10541ad..c636e4788ef55e47c3023813ca31e6f9da01495a 100644 (file)
@@ -370,13 +370,60 @@ static void wolfssl_bio_cf_free_methods(void)
 
 #endif /* !USE_BIO_CHAIN */
 
-static void wolfssl_session_free(void *sessionid, size_t idsize)
+static void wolfssl_session_free(void *sdata, size_t slen)
 {
-  (void)idsize;
-  wolfSSL_SESSION_free(sessionid);
+  (void)slen;
+  free(sdata);
 }
 
-static int wolfssl_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session)
+CURLcode wssl_cache_session(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            struct ssl_peer *peer,
+                            WOLFSSL_SESSION *session)
+{
+  CURLcode result = CURLE_OK;
+  unsigned char *sdata = NULL;
+  unsigned int slen;
+
+  if(!session)
+    goto out;
+
+  slen = wolfSSL_i2d_SSL_SESSION(session, NULL);
+  if(slen <= 0) {
+    CURL_TRC_CF(data, cf, "fail to assess session length: %u", slen);
+    result = CURLE_FAILED_INIT;
+    goto out;
+  }
+  sdata = calloc(1, slen);
+  if(!sdata) {
+    failf(data, "unable to allocate session buffer of %u bytes", slen);
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  slen = wolfSSL_i2d_SSL_SESSION(session, &sdata);
+  if(slen <= 0) {
+    CURL_TRC_CF(data, cf, "fail to serialize session: %u", slen);
+    result = CURLE_FAILED_INIT;
+    goto out;
+  }
+
+  Curl_ssl_sessionid_lock(data);
+  result = Curl_ssl_set_sessionid(cf, data, peer, NULL,
+                                  sdata, slen, wolfssl_session_free);
+  Curl_ssl_sessionid_unlock(data);
+  if(result)
+    failf(data, "failed to add new ssl session to cache (%d)", result);
+  else {
+    CURL_TRC_CF(data, cf, "added new session to cache");
+    sdata = NULL;
+  }
+
+out:
+  free(sdata);
+  return 0;
+}
+
+static int wssl_vtls_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session)
 {
   struct Curl_cfilter *cf;
 
@@ -388,18 +435,44 @@ static int wolfssl_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session)
     DEBUGASSERT(connssl);
     DEBUGASSERT(data);
     if(connssl && data) {
-      CURLcode result;
-      Curl_ssl_sessionid_lock(data);
-      result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL,
-                                      session, 0, wolfssl_session_free);
-      Curl_ssl_sessionid_unlock(data);
-      if(result)
-        failf(data, "failed to add new ssl session to cache (%d)", result);
+      (void)wssl_cache_session(cf, data, &connssl->peer, session);
+    }
+  }
+  return 0;
+}
+
+CURLcode wssl_setup_session(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            struct wolfssl_ctx *wss,
+                            struct ssl_peer *peer)
+{
+  void *psdata;
+  const unsigned char *sdata = NULL;
+  size_t slen = 0;
+  CURLcode result = CURLE_OK;
+
+  Curl_ssl_sessionid_lock(data);
+  if(!Curl_ssl_getsessionid(cf, data, peer, &psdata, &slen, NULL)) {
+    WOLFSSL_SESSION *session;
+    sdata = psdata;
+    session = wolfSSL_d2i_SSL_SESSION(NULL, &sdata, (long)slen);
+    if(session) {
+      int ret = wolfSSL_set_session(wss->handle, session);
+      if(ret != WOLFSSL_SUCCESS) {
+        Curl_ssl_delsessionid(data, psdata);
+        infof(data, "previous session not accepted (%d), "
+              "removing from cache", ret);
+      }
       else
-        CURL_TRC_CF(data, cf, "added new session to cache");
+        infof(data, "SSL reusing session ID");
+      wolfSSL_SESSION_free(session);
+    }
+    else {
+      failf(data, "could not decode previous session");
     }
   }
-  return 1;
+  Curl_ssl_sessionid_unlock(data);
+  return result;
 }
 
 static CURLcode populate_x509_store(struct Curl_cfilter *cf,
@@ -1087,24 +1160,11 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
 
   /* Check if there is a cached ID we can/should use here! */
   if(ssl_config->primary.cache_session) {
-    void *ssl_sessionid = NULL;
-
+    /* Set session from cache if there is one */
+    (void)wssl_setup_session(cf, data, backend, &connssl->peer);
     /* Register to get notified when a new session is received */
     wolfSSL_set_app_data(backend->handle, cf);
-    wolfSSL_CTX_sess_set_new_cb(backend->ctx, wolfssl_new_session_cb);
-
-    Curl_ssl_sessionid_lock(data);
-    if(!Curl_ssl_getsessionid(cf, data, &connssl->peer,
-                              &ssl_sessionid, NULL, NULL)) {
-      /* we got a session id, use it! */
-      if(!SSL_set_session(backend->handle, ssl_sessionid)) {
-        Curl_ssl_delsessionid(data, ssl_sessionid);
-        infof(data, "cannot use session ID, going on without");
-      }
-      else
-        infof(data, "SSL reusing session ID");
-    }
-    Curl_ssl_sessionid_unlock(data);
+    wolfSSL_CTX_sess_set_new_cb(backend->ctx, wssl_vtls_new_session_cb);
   }
 
 #ifdef USE_ECH
index 318d8b4ab3308b4f5fd32530db9cded9319945da..e39fa88adcd2b57a473397b9432b2ae7d403a2e7 100644 (file)
@@ -48,5 +48,16 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf,
                                     struct Curl_easy *data,
                                     struct wolfssl_ctx *wssl);
 
+CURLcode wssl_setup_session(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            struct wolfssl_ctx *wss,
+                            struct ssl_peer *peer);
+
+CURLcode wssl_cache_session(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            struct ssl_peer *peer,
+                            WOLFSSL_SESSION *session);
+
+
 #endif /* USE_WOLFSSL */
 #endif /* HEADER_CURL_WOLFSSL_H */
index 149919625eb07d36cdb3fbb3bf713604c127886e..5bcbb8c43903d8cafee50f502af925ded12672e2 100644 (file)
@@ -168,8 +168,6 @@ class TestDownload:
     @pytest.mark.parametrize("proto", ['http/1.1'])
     def test_02_07b_download_reuse(self, env: Env,
                                    httpd, nghttpx, repeat, proto):
-        if env.curl_uses_lib('wolfssl'):
-            pytest.skip("wolfssl session reuse borked")
         count = 6
         curl = CurlClient(env=env)
         urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]'
index eeccbd0af440a147543ff276a5a73c3021eafd9d..197a5b4f92582bf0668b50bfe1b8bcef41925b19 100644 (file)
 import json
 import logging
 import os
+import re
 import pytest
 
-from testenv import Env, CurlClient
+from testenv import Env, CurlClient, LocalClient
 
 
 log = logging.getLogger(__name__)
@@ -38,7 +39,8 @@ log = logging.getLogger(__name__)
 class TestSSLUse:
 
     @pytest.fixture(autouse=True, scope='class')
-    def _class_scope(self, env, nghttpx):
+    def _class_scope(self, env, httpd, nghttpx):
+        env.make_data_file(indir=httpd.docs_dir, fname="data-10k", fsize=10*1024)
         if env.have_h3():
             nghttpx.start_if_needed()
 
@@ -118,8 +120,6 @@ class TestSSLUse:
     def test_17_04_double_dot(self, env: Env, proto):
         if proto == 'h3' and not env.have_h3():
             pytest.skip("h3 not supported")
-        if proto == 'h3' and env.curl_uses_lib('wolfssl'):
-            pytest.skip("wolfSSL HTTP/3 peer verification does not properly check")
         curl = CurlClient(env=env)
         domain = f'{env.domain1}..'
         url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'
@@ -313,3 +313,31 @@ class TestSSLUse:
             assert r.json['SSL_PROTOCOL'] == tls_proto, r.dump_logs()
         else:
             assert r.exit_code != 0, f'extra_args={extra_args}\n{r.dump_logs()}'
+
+    def test_17_10_h3_session_reuse(self, env: Env, httpd, nghttpx):
+        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'):
+            pytest.skip("QUIC session reuse not implemented")
+        count = 2
+        docname = 'data-10k'
+        url = f'https://localhost:{env.https_port}/{docname}'
+        client = LocalClient(name='hx-download', env=env)
+        if not client.exists():
+            pytest.skip(f'example client not built: {client.name}')
+        r = client.run(args=[
+             '-n', f'{count}',
+             '-f',  # forbid reuse of connections
+             '-r', f'{env.domain1}:{env.port_for("h3")}:127.0.0.1',
+             '-V', 'h3', url
+        ])
+        r.check_exit_code(0)
+        # check that TLS session was reused as expected
+        reused_session = False
+        for line in r.trace_lines:
+            m = re.match(r'\[1-1] \* SSL reusing session.*', line)
+            if m:
+                reused_session = True
+        assert reused_session, f'{r}\n{r.dump_logs()}'