]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
vtls: feature ssls-export for SSL session im-/export
authorStefan Eissing <stefan@eissing.org>
Tue, 7 Jan 2025 11:41:26 +0000 (12:41 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 8 Jan 2025 22:32:07 +0000 (23:32 +0100)
Adds the experimental feature `ssls-export` to libcurl and curl for
importing and exporting SSL sessions from/to a file.

* add functions to libcurl API
* add command line option `--ssl-sessions <filename>` to curl
* add documenation
* add support in configure
* add support in cmake
+ add pytest case

Closes #15924

48 files changed:
.github/scripts/spellcheck.words
CMakeLists.txt
configure.ac
docs/EXPERIMENTAL.md
docs/INSTALL-CMAKE.md
docs/cmdline-opts/Makefile.inc
docs/cmdline-opts/ssl-sessions.md [new file with mode: 0644]
docs/cmdline-opts/tls-earlydata.md
docs/libcurl/Makefile.inc
docs/libcurl/curl_easy_ssls_export.md [new file with mode: 0644]
docs/libcurl/curl_easy_ssls_import.md [new file with mode: 0644]
docs/libcurl/curl_global_trace.md
docs/libcurl/curl_version_info.md
docs/options-in-versions
include/curl/curl.h
lib/Makefile.inc
lib/curl_get_line.c
lib/curl_get_line.h
lib/curl_trc.c
lib/curl_trc.h
lib/easy.c
lib/libcurl.def
lib/version.c
lib/vquic/curl_ngtcp2.c
lib/vtls/gtls.c
lib/vtls/vtls.c
lib/vtls/vtls_scache.c
lib/vtls/vtls_scache.h
lib/vtls/vtls_spack.c [new file with mode: 0644]
lib/vtls/vtls_spack.h [new file with mode: 0644]
m4/curl-confopts.m4
projects/generate.bat
scripts/singleuse.pl
src/Makefile.inc
src/tool_cfgable.h
src/tool_getparam.c
src/tool_getparam.h
src/tool_libinfo.c
src/tool_libinfo.h
src/tool_listhelp.c
src/tool_operate.c
src/tool_ssls.c [new file with mode: 0644]
src/tool_ssls.h [new file with mode: 0644]
tests/data/test1135
tests/http/test_17_ssl_use.py
tests/server/Makefile.inc
tests/unit/unit3200.c
winbuild/MakefileBuild.vc

index 2e90bd0640cecb7fc6ca5dfcf29590028efa7b18..85714c771d7ed15a6c5fa60471460cdd06c38627 100644 (file)
@@ -779,6 +779,7 @@ src
 SRP
 SRWLOCK
 SSL
+SSLS
 ssl
 SSLeay
 SSLKEYLOGFILE
index 1ede5306fe2100321b4daf73b7fd551b2ab4de82..6de5cb7fe6b50274653a91c2ed1e6cd3793737cb 100644 (file)
@@ -978,6 +978,15 @@ if(USE_ECH)
   endif()
 endif()
 
+option(USE_SSLS_EXPORT "Enable SSL session export support" OFF)
+if(USE_SSLS_EXPORT)
+  if(_ssl_enabled)
+    message(STATUS "SSL export enabled.")
+  else()
+    message(FATAL_ERROR "SSL session export requires SSL enabled")
+  endif()
+endif()
+
 option(USE_NGHTTP2 "Use nghttp2 library" ON)
 if(USE_NGHTTP2)
   find_package(NGHTTP2)
@@ -2086,6 +2095,7 @@ curl_add_if("TrackMemory"   ENABLE_CURLDEBUG)
 curl_add_if("ECH"           _ssl_enabled AND HAVE_ECH)
 curl_add_if("PSL"           USE_LIBPSL)
 curl_add_if("CAcert"        CURL_CA_EMBED_SET)
+curl_add_if("SSLS-EXPORT"   _ssl_enabled AND USE_SSLS_EXPORT)
 if(_items)
   if(NOT CMAKE_VERSION VERSION_LESS 3.13)
     list(SORT _items CASE INSENSITIVE)
index ecb58a924c477d1b746f48800d2e88251e251cc6..8c05f70d95b77ab6aacc9d1ebb01740c7e397314 100644 (file)
@@ -54,6 +54,7 @@ CURL_CHECK_OPTION_ARES
 CURL_CHECK_OPTION_RT
 CURL_CHECK_OPTION_HTTPSRR
 CURL_CHECK_OPTION_ECH
+CURL_CHECK_OPTION_SSLS_EXPORT
 
 XC_CHECK_PATH_SEPARATOR
 
@@ -4946,6 +4947,26 @@ else
   CURL_DISABLE_WEBSOCKETS=1
 fi
 
+dnl *************************************************************
+dnl check whether experimental SSL Session Im-/Export is enabled
+dnl
+if test "x$want_ssls_export" != "xno"; then
+  AC_MSG_CHECKING([whether SSL session export support is available])
+
+  dnl assume NOT and look for sufficient condition
+  SSLS_EXPORT_ENABLED=0
+  SSLS_EXPORT_SUPPORT=''
+
+  if test "x$SSL_ENABLED" != "x1"; then
+    AC_MSG_ERROR([--enable-ssls-export ignored: No SSL support])
+  else
+    SSLS_EXPORT_ENABLED=1
+    AC_DEFINE(USE_SSLS_EXPORT, 1, [if SSL session export support is available])
+    AC_MSG_RESULT("SSL session im-/export enabled")
+    experimental="$experimental SSLS-EXPORT"
+  fi
+fi
+
 dnl ************************************************************
 dnl hiding of library internal symbols
 dnl
@@ -5148,6 +5169,10 @@ if test "x$OPENSSL_ENABLED" = "x1" -o -n "$SSL_ENABLED"; then
   fi
 fi
 
+if test "x$SSLS_EXPORT_ENABLED" = "x1"; then
+  SUPPORT_FEATURES="$SUPPORT_FEATURES SSLS-EXPORT"
+fi
+
 if test ${ac_cv_sizeof_curl_off_t} -gt 4; then
   if test ${ac_cv_sizeof_off_t} -gt 4 -o \
      "$curl_win32_file_api" = "win32_large_files"; then
@@ -5413,6 +5438,7 @@ AC_MSG_NOTICE([Configured to build curl/libcurl:
   HTTP2:            ${curl_h2_msg}
   HTTP3:            ${curl_h3_msg}
   ECH:              ${curl_ech_msg}
+  SSLS-EXPORT:      ${curl_ssls_export_msg}
   Protocols:        ${SUPPORT_PROTOCOLS_LOWER}
   Features:         ${SUPPORT_FEATURES}
 ])
index 00e267a96009c0d5bbcb0d8c0b8b72804de35112..a8eab659bca0ada8c1714370a1412f45d6704228 100644 (file)
@@ -63,3 +63,15 @@ Graduation requirements:
 
 - it has been given time to mature, so no earlier than April 2025 (twelve
   months after being added here)
+
+## SSL session import/export
+
+Import/Export of SSL sessions tickets in libcurl and curl command line
+option '--ssl-session <filename>' for faster TLS handshakes and use
+of TLSv1.3/QUIC Early Data (0-RTT).
+
+Graduation requirements:
+
+- the implementation is considered safe
+
+- feedback from users saying that session export works for their use cases
index 1cdde97c2cc2f3fedc3ae834fbe5cc22502c029c..33622a3b2d32ad8d22aed0a5bc0ad3e16dc2ef35 100644 (file)
@@ -191,6 +191,7 @@ assumes that CMake generates `Makefile`:
 - `USE_ECH`:                                Enable ECH support. Default: `OFF`
 - `USE_HTTPSRR`:                            Enable HTTPS RR support. Default: `OFF`
 - `USE_OPENSSL_QUIC`:                       Use OpenSSL and nghttp3 libraries for HTTP/3 support. Default: `OFF`
+- `USE_SSLS_EXPORT`:                        Enable experimental SSL session import/export. Default: `OFF`
 
 ## Disabling features
 
index 3bcffa49fca4b4f389d499861d26379df9e6d313..be6fadd26bb1cde86e59918f911191a4b84fba3d 100644 (file)
@@ -269,6 +269,7 @@ DPAGES = \
   ssl-no-revoke.md \
   ssl-reqd.md \
   ssl-revoke-best-effort.md \
+  ssl-sessions.md \
   ssl.md \
   sslv2.md \
   sslv3.md \
diff --git a/docs/cmdline-opts/ssl-sessions.md b/docs/cmdline-opts/ssl-sessions.md
new file mode 100644 (file)
index 0000000..aefc20a
--- /dev/null
@@ -0,0 +1,35 @@
+---
+c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+SPDX-License-Identifier: curl
+Long: ssl-sessions
+Arg: <filename>
+Protocols: TLS
+Help: Load/save SSL session tickets from/to this file
+Added: 8.12.0
+Category: tls
+Multi: single
+See-also:
+  - tls-earlydata
+Example:
+  - --ssl-sessions sessions.txt $URL
+---
+
+# `--ssl-sessions`
+
+Use the given file to load SSL session tickets into curl's cache before
+starting any transfers. At the end of a successful curl run, the cached
+SSL sessions tickets are save to the file, replacing any previous content.
+
+The file does not have to exist, but curl reports an error if it is
+unable to create it. Unused loaded tickets are saved again, unless they
+get replaced or purged from the cache for space reasons.
+
+Using a session file allows `--tls-earlydata` to send the first request
+in "0-RTT" mode, should an SSL session with the feature be found. Note that
+a server may not support early data. Also note that early data does
+not provide forward secrecy, e.g. is not as secure.
+
+The SSL session tickets are stored as base64 encoded text, each ticket on
+its own line. The hostnames are cryptographically salted and hashed. While
+this prevents someone to easily see the hosts you contacted, they could still
+check if a specific hostname matches one of the values.
index 8482f809ec063040bd40c59e567000f9229a74fd..de815b4956a65e0dacc1ebf6f53089695904284e 100644 (file)
@@ -10,6 +10,7 @@ Multi: boolean
 See-also:
   - tlsv1.3
   - tls-max
+  - ssl-sessions
 Example:
   - --tls-earlydata $URL
 ---
index fe990cc1ec80c21e92922ad6fba650218affba7c..0ae1a053899408c37322621d84270424d3cdbb7e 100644 (file)
@@ -41,6 +41,8 @@ man_MANS = \
  curl_easy_reset.3 \
  curl_easy_send.3 \
  curl_easy_setopt.3 \
+ curl_easy_ssls_import.3 \
+ curl_easy_ssls_export.3 \
  curl_easy_strerror.3 \
  curl_easy_unescape.3 \
  curl_easy_upkeep.3 \
diff --git a/docs/libcurl/curl_easy_ssls_export.md b/docs/libcurl/curl_easy_ssls_export.md
new file mode 100644 (file)
index 0000000..d0cb943
--- /dev/null
@@ -0,0 +1,172 @@
+---
+c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+SPDX-License-Identifier: curl
+Title: curl_easy_ssls_export
+Section: 3
+Source: libcurl
+See-also:
+  - CURLOPT_SHARE (3)
+  - curl_share_setopt (3)
+  - curl_easy_ssls_import (3)
+Protocol:
+  - All
+TLS-backend:
+  - GnuTLS
+  - OpenSSL
+  - BearSSL
+  - wolfSSL
+  - mbedTLS
+Added-in: 8.12.0
+---
+
+# NAME
+
+curl_easy_ssls_export - export SSL sessions
+
+# SYNOPSIS
+
+~~~c
+#include <curl/curl.h>
+
+typedef CURLcode curl_ssls_export_function(CURL *handle,
+                                           void *userptr,
+                                           const char *session_key,
+                                           const unsigned char *shmac,
+                                           size_t shmac_len,
+                                           const unsigned char *sdata,
+                                           size_t sdata_len,
+                                           curl_off_t valid_until,
+                                           int ietf_tls_id,
+                                           const char *alpn,
+                                           size_t earlydata_max);
+
+CURLcode curl_easy_ssls_export(CURL *handle,
+                               curl_ssls_export_function *export_fn,
+                               void *userptr);
+~~~
+
+# DESCRIPTION
+
+This function iterates over all SSL session tickets that belong to the
+easy handle and invokes the **export_fn** callback on each of them, as
+long as the callback returns **CURLE_OK**.
+
+The callback may then store this information and use curl_easy_ssls_import(3)
+in another libcurl instance to add SSL session tickets again. Reuse of
+SSL session tickets may result in faster handshakes and some connections
+might be able to send request data in the initial packets (0-RTT).
+
+From all the parameters passed to the **export_fn** only two need to be
+persisted: either **session_key** or **shamc** and always **sdata**. All
+other parameters are informative, e.g. allow the callback to act only
+on specific session tickets.
+
+Note that SSL sessions that involve a client certificate or SRP
+username/password are not exported.
+
+# Export Function Parameter
+
+## Session Key
+
+This is a printable, 0-terminated string that starts with **hostname:port**
+the session ticket is originating from and also contains all relevant
+SSL parameters used in the connection. The key also carries the name
+and version number of the TLS backend used.
+
+It is recommended to only persist **session_key** when it can be protected
+from outside access. Since the hostname appears in plain text, it would
+allow any third party to see how curl has been used for.
+
+## Salted Hash
+
+A binary blob of **shmac_len** bytes that contains a random salt and
+a cryptographic hash of the salt and **session_key**. The salt is generated
+for every session individually. Storing **shmac** is recommended when
+placing session tickets in a file, for example.
+
+A third party may brute-force known hostnames, but cannot just "grep" for
+them.
+
+## Session Data
+
+A binary blob of **sdata_len** bytes, **sdata** contains all relevant
+SSL session ticket information for a later import - apart from **session_key**
+and **shmac**.
+
+## valid_until
+
+Seconds since EPOCH (1970-01-01) until the session ticket is considered
+valid.
+
+## TLS Version
+
+The IETF assigned number for the TLS version the session ticket originates
+from. This is **0x0304** for TLSv1.3, **0x0303** for 1.2, etc. Session
+tickets from version 1.3 have better security properties, so an export
+might store only those.
+
+## ALPN
+
+The ALPN protocol that had been negotiated with the host. This may be
+**NULL** if negotiation gave no result or had not been attempted.
+
+## Early Data
+
+The maximum amount of bytes the server supports to receive in early data
+(0-RTT). This is 0 unless the server explicitly indicates support.
+
+# %PROTOCOLS%
+
+# EXAMPLE
+
+~~~c
+CURLcode my_export_cb(CURL *handle,
+                      void *userptr,
+                      const char *session_key,
+                      const unsigned char *shmac,
+                      size_t shmac_len,
+                      const unsigned char *sdata,
+                      size_t sdata_len,
+                      curl_off_t valid_until,
+                      int ietf_tls_id,
+                      const char *alpn,
+                      size_t earlydata_max)
+{
+  /* persist sdata */
+  return CURLE_OK;
+}
+
+int main(void)
+{
+  CURLSHcode sh;
+  CURLSH *share = curl_share_init();
+  CURLcode rc;
+  CURL *curl;
+
+  sh = curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
+  if(sh)
+    printf("Error: %s\n", curl_share_strerror(sh));
+
+  curl = curl_easy_init();
+  if(curl) {
+    curl_easy_setopt(curl, CURLOPT_SHARE, share);
+
+    rc = curl_easy_ssls_export(curl, my_export_cb, NULL);
+
+    /* always cleanup */
+    curl_easy_cleanup(curl);
+  }
+  curl_share_cleanup(share);
+}
+~~~
+
+# %AVAILABILITY%
+
+# RETURN VALUE
+
+This function returns a CURLcode indicating success or error.
+
+CURLE_OK (0) means everything was OK, non-zero means an error occurred, see
+libcurl-errors(3). If CURLOPT_ERRORBUFFER(3) was set with curl_easy_setopt(3)
+there can be an error message stored in the error buffer when non-zero is
+returned.
diff --git a/docs/libcurl/curl_easy_ssls_import.md b/docs/libcurl/curl_easy_ssls_import.md
new file mode 100644 (file)
index 0000000..30d0b4d
--- /dev/null
@@ -0,0 +1,84 @@
+---
+c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+SPDX-License-Identifier: curl
+Title: curl_easy_ssls_import
+Section: 3
+Source: libcurl
+See-also:
+  - CURLOPT_SHARE (3)
+  - curl_share_setopt (3)
+  - curl_easy_ssls_export (3)
+Protocol:
+  - All
+Added-in: 8.12.0
+---
+
+# NAME
+
+curl_easy_ssls_export - export SSL sessions
+
+# SYNOPSIS
+
+~~~c
+#include <curl/curl.h>
+
+CURLcode curl_easy_ssls_import(CURL *handle,
+                               const char *session_key,
+                               const unsigned char *shmac, size_t shmac_len,
+                               const unsigned char *sdata, size_t sdata_len);
+~~~
+
+# DESCRIPTION
+
+This function imports a previously exported SSL session ticket. **sdata** and
+**sdata_len** must always be provided. If **session_key** is **NULL**, then
+**shmac** and **shmac_len** must be given as received during the export.
+See curl_easy_ssls_export(3) for a description of those.
+
+Import of session tickets from other curl versions may fail due to changes
+in the handling of **shmac** or **sdata**. A session ticket which has
+already expired is silently discarded.
+
+# %PROTOCOLS%
+
+# EXAMPLE
+
+~~~c
+int main(void)
+{
+  CURLSHcode sh;
+  CURLSH *share = curl_share_init();
+  CURLcode rc;
+  CURL *curl;
+
+  sh = curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
+  if(sh)
+    printf("Error: %s\n", curl_share_strerror(sh));
+
+  curl = curl_easy_init();
+  if(curl) {
+    unsigned char *shmac, *sdata;
+    size_t hlen, slen;
+
+    curl_easy_setopt(curl, CURLOPT_SHARE, share);
+
+    /* read shmac and sdata from storage */
+    rc = curl_easy_ssls_import(curl, NULL, shmac, hlen, sdata, slen);
+
+    /* always cleanup */
+    curl_easy_cleanup(curl);
+  }
+  curl_share_cleanup(share);
+}
+~~~
+
+# %AVAILABILITY%
+
+# RETURN VALUE
+
+This function returns a CURLcode indicating success or error.
+
+CURLE_OK (0) means everything was OK, non-zero means an error occurred, see
+libcurl-errors(3). If CURLOPT_ERRORBUFFER(3) was set with curl_easy_setopt(3)
+there can be an error message stored in the error buffer when non-zero is
+returned.
index bf722ae6f93db3c17a52986376b9db684191cb32..87edb8ecf08f0f398218a3fcbca52642c92eb914 100644 (file)
@@ -105,6 +105,10 @@ Tracing of DNS-over-HTTP operations to resolve hostnames.
 
 Traces reading of upload data from the application in order to send it to the server.
 
+## `ssls`
+
+Tracing of SSL Session handling, e.g. caching/import/export.
+
 ## `smtp`
 
 Tracing of SMTP operations when this protocol is enabled in your build.
index 271e935caf7af374539feeacc1174db215bc29e6..a112058a6d1f1b83a7aaaa24b370be50fdc1e4b0 100644 (file)
@@ -300,6 +300,13 @@ GSS-API Negotiation Mechanism, defined in RFC 2478.) (added in 7.10.8)
 
 supports SSL (HTTPS/FTPS) (Added in 7.10)
 
+## SSLS-EXPORT
+
+*features* mask bit: non-existent
+
+libcurl was built with SSL session import/export support
+(experimental, added in 8.12.0)
+
 ## SSPI
 
 *features* mask bit: CURL_VERSION_SSPI
index a7a11630d3274d73487376ab49ab0968258fc38d..057cbafccd9f4ff5ab9513a97668197c14e2e546 100644 (file)
 --ssl-no-revoke                      7.44.0
 --ssl-reqd                           7.20.0
 --ssl-revoke-best-effort             7.70.0
+--ssl-sessions                       8.12.0
 --sslv2 (-2)                         5.9
 --sslv3 (-3)                         5.9
 --stderr                             6.2
index 353a3b19266d26aff369a1a794aeef6fe08d5d36..84cf5f2f525abe3cdfb728631ab94e178cfd734a 100644 (file)
@@ -3232,6 +3232,50 @@ CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask);
 #define CURLPAUSE_ALL       (CURLPAUSE_RECV|CURLPAUSE_SEND)
 #define CURLPAUSE_CONT      (CURLPAUSE_RECV_CONT|CURLPAUSE_SEND_CONT)
 
+/*
+ * NAME curl_easy_ssls_import()
+ *
+ * DESCRIPTION
+ *
+ * The curl_easy_ssls_import function adds a previously exported SSL session
+ * to the SSL session cache of the easy handle (or the underlying share).
+ */
+CURL_EXTERN CURLcode curl_easy_ssls_import(CURL *handle,
+                                           const char *session_key,
+                                           const unsigned char *shmac,
+                                           size_t shmac_len,
+                                           const unsigned char *sdata,
+                                           size_t sdata_len);
+
+/* This is the curl_ssls_export_cb callback prototype. It
+ * is passed to curl_easy_ssls_export() to extract SSL sessions/tickets. */
+typedef CURLcode curl_ssls_export_cb(CURL *handle,
+                                     void *userptr,
+                                     const char *session_key,
+                                     const unsigned char *shmac,
+                                     size_t shmac_len,
+                                     const unsigned char *sdata,
+                                     size_t sdata_len,
+                                     curl_off_t valid_until,
+                                     int ietf_tls_id,
+                                     const char *alpn,
+                                     size_t earlydata_max);
+
+/*
+ * NAME curl_easy_ssls_export()
+ *
+ * DESCRIPTION
+ *
+ * The curl_easy_ssls_export function iterates over all SSL sessions stored
+ * in the easy handle (or underlying share) and invokes the passed
+ * callback.
+ *
+ */
+CURL_EXTERN CURLcode curl_easy_ssls_export(CURL *handle,
+                                           curl_ssls_export_cb *export_fn,
+                                           void *userptr);
+
+
 #ifdef  __cplusplus
 } /* end of extern "C" */
 #endif
index 12bf70e9e1afe11cd61f07ef9f2d0080ab1483e5..d2c7b6e8881e70179fa56fefd123c2fcbdae77c7 100644 (file)
@@ -57,6 +57,7 @@ LIB_VTLS_CFILES =           \
   vtls/sectransp.c          \
   vtls/vtls.c               \
   vtls/vtls_scache.c        \
+  vtls/vtls_spack.c         \
   vtls/wolfssl.c            \
   vtls/x509asn1.c
 
@@ -76,6 +77,7 @@ LIB_VTLS_HFILES =           \
   vtls/vtls.h               \
   vtls/vtls_int.h           \
   vtls/vtls_scache.h        \
+  vtls/vtls_spack.h         \
   vtls/wolfssl.h            \
   vtls/x509asn1.h
 
index 100207331d81520d6172140254b958be21617821..7ee3b65af77f123f904ac50314850325005d5e65 100644 (file)
@@ -28,7 +28,9 @@
   !defined(CURL_DISABLE_HSTS) || !defined(CURL_DISABLE_NETRC)
 
 #include "curl_get_line.h"
+#ifdef BUILDING_LIBCURL
 #include "curl_memory.h"
+#endif
 /* The last #include file should be: */
 #include "memdebug.h"
 
index 7907cde88085634677a78202dbf928eb89466776..1e3b0f0357c78f0ac0a8b7e63ff908380ab82cda 100644 (file)
 
 #include "dynbuf.h"
 
+#ifndef BUILDING_LIBCURL
+/* this renames functions so that the tool code can use the same code
+   without getting symbol collisions */
+#define Curl_get_line(a,b) curlx_get_line(a,b)
+#endif
+
 /* Curl_get_line() returns complete lines that end with a newline. */
 int Curl_get_line(struct dynbuf *buf, FILE *input);
 
index 2776d0d119f6908ce9c9a68d4f1156e5f129365a..dcb7813f6d9f26556ef56896da20dfd06e664948 100644 (file)
@@ -239,6 +239,24 @@ void Curl_trc_smtp(struct Curl_easy *data, const char *fmt, ...)
 }
 #endif /* !CURL_DISABLE_SMTP */
 
+#ifdef USE_SSL
+struct curl_trc_feat Curl_trc_feat_ssls = {
+  "SSLS",
+  CURL_LOG_LVL_NONE,
+};
+
+void Curl_trc_ssls(struct Curl_easy *data, const char *fmt, ...)
+{
+  DEBUGASSERT(!strchr(fmt, '\n'));
+  if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ssls)) {
+    va_list ap;
+    va_start(ap, fmt);
+    trc_infof(data, &Curl_trc_feat_ssls, fmt, ap);
+    va_end(ap);
+  }
+}
+#endif /* USE_SSL */
+
 #if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP)
 struct curl_trc_feat Curl_trc_feat_ws = {
   "WS",
@@ -279,6 +297,9 @@ static struct trc_feat_def trc_feats[] = {
 #ifndef CURL_DISABLE_SMTP
   { &Curl_trc_feat_smtp,      TRC_CT_PROTOCOL },
 #endif
+#ifdef USE_SSL
+  { &Curl_trc_feat_ssls,      TRC_CT_NETWORK },
+#endif
 #if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP)
   { &Curl_trc_feat_ws,        TRC_CT_PROTOCOL },
 #endif
index 67a7f4aa10ce831f0417d3a96066cea5cd302b72..c796d671d0b9fc1a5241aec1c09b2ca49058573a 100644 (file)
@@ -94,6 +94,11 @@ void Curl_failf(struct Curl_easy *data,
   do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_smtp)) \
          Curl_trc_smtp(data, __VA_ARGS__); } while(0)
 #endif /* !CURL_DISABLE_SMTP */
+#ifdef USE_SSL
+#define CURL_TRC_SSLS(data, ...) \
+  do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ssls)) \
+         Curl_trc_ssls(data, __VA_ARGS__); } while(0)
+#endif /* USE_SSL */
 #if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP)
 #define CURL_TRC_WS(data, ...)                             \
   do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ws)) \
@@ -113,6 +118,9 @@ void Curl_failf(struct Curl_easy *data,
 #ifndef CURL_DISABLE_SMTP
 #define CURL_TRC_SMTP  Curl_trc_smtp
 #endif
+#ifdef USE_SSL
+#define CURL_TRC_SSLS  Curl_trc_ssls
+#endif
 #if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP)
 #define CURL_TRC_WS    Curl_trc_ws
 #endif
@@ -167,6 +175,11 @@ extern struct curl_trc_feat Curl_trc_feat_smtp;
 void Curl_trc_smtp(struct Curl_easy *data,
                    const char *fmt, ...) CURL_PRINTF(2, 3);
 #endif
+#ifdef USE_SSL
+extern struct curl_trc_feat Curl_trc_feat_ssls;
+void Curl_trc_ssls(struct Curl_easy *data,
+                   const char *fmt, ...) CURL_PRINTF(2, 3);
+#endif
 #if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP)
 extern struct curl_trc_feat Curl_trc_feat_ws;
 void Curl_trc_ws(struct Curl_easy *data,
index ac8fab34220d9b4f1f8f9cb7bc504a5024a46cca..d1888b7fe4e295b81a2d9dccd5b5395df91bdb59 100644 (file)
@@ -48,6 +48,7 @@
 #include <curl/curl.h>
 #include "transfer.h"
 #include "vtls/vtls.h"
+#include "vtls/vtls_scache.h"
 #include "url.h"
 #include "getinfo.h"
 #include "hostip.h"
@@ -1336,3 +1337,41 @@ CURLcode curl_easy_upkeep(CURL *d)
   /* Use the common function to keep connections alive. */
   return Curl_cpool_upkeep(data);
 }
+
+CURLcode curl_easy_ssls_import(CURL *d, const char *session_key,
+                               const unsigned char *shmac, size_t shmac_len,
+                               const unsigned char *sdata, size_t sdata_len)
+{
+#ifdef USE_SSLS_EXPORT
+  struct Curl_easy *data = d;
+  if(!GOOD_EASY_HANDLE(data))
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  return Curl_ssl_session_import(data, session_key,
+                                 shmac, shmac_len, sdata, sdata_len);
+#else
+  (void)d;
+  (void)session_key;
+  (void)shmac;
+  (void)shmac_len;
+  (void)sdata;
+  (void)sdata_len;
+  return CURLE_NOT_BUILT_IN;
+#endif
+}
+
+CURLcode curl_easy_ssls_export(CURL *d,
+                               curl_ssls_export_cb *export_fn,
+                               void *userptr)
+{
+#ifdef USE_SSLS_EXPORT
+  struct Curl_easy *data = d;
+  if(!GOOD_EASY_HANDLE(data))
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  return Curl_ssl_session_export(data, export_fn, userptr);
+#else
+  (void)d;
+  (void)export_fn;
+  (void)userptr;
+  return CURLE_NOT_BUILT_IN;
+#endif
+}
index 9bf9fcc958fd952fe0bedacc2e2d0cff5f250b00..43e26f655c6f123495717849aace2b012cbdd5aa 100644 (file)
@@ -15,6 +15,8 @@ curl_easy_recv
 curl_easy_reset
 curl_easy_send
 curl_easy_setopt
+curl_easy_ssls_export
+curl_easy_ssls_import
 curl_easy_strerror
 curl_easy_unescape
 curl_easy_upkeep
index 6898b4255c8731ee4f24ecffdece49b250de0878..c5275495bcd526004cf6cbeaf536cfeb5fdda591 100644 (file)
@@ -523,6 +523,9 @@ static const struct feat features_table[] = {
 #ifdef USE_SSL
   FEATURE("SSL",         NULL,                CURL_VERSION_SSL),
 #endif
+#if defined(USE_SSLS_EXPORT)
+  FEATURE("SSLS-EXPORT", NULL,                0),
+#endif
 #ifdef USE_WINDOWS_SSPI
   FEATURE("SSPI",        NULL,                CURL_VERSION_SSPI),
 #endif
index 79535d6e0927ea16188eb7f3119fa40f081120a2..e891e7597386faf5b7df87d7bce8fc80b0418e5a 100644 (file)
@@ -476,6 +476,10 @@ static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data)
                                                     data, &ctx->peer);
   CURL_TRC_CF(data, cf, "handshake complete after %dms",
              (int)Curl_timediff(ctx->handshake_at, ctx->started_at));
+  /* In case of earlydata, where we simulate being connected, update
+   * 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) {
     int flags = gnutls_session_get_flags(ctx->tls.gtls.session);
index b87da2c82db571826e2db376553480a9ae991d7f..77b1d23493be87741c631f4464965516703bbf74 100644 (file)
@@ -751,10 +751,11 @@ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf,
 
   /* extract session ID to the allocated buffer */
   gnutls_session_get_data(session, sdata, &sdata_len);
-
-  CURL_TRC_CF(data, cf, "get session id (len=%zu, alpn=%s) and store in cache",
-              sdata_len, alpn ? alpn : "-");
   earlydata_max = gnutls_record_get_max_early_data_size(session);
+
+  CURL_TRC_CF(data, cf, "get session id (len=%zu, alpn=%s, earlymax=%zu) "
+              "and store in cache", sdata_len, alpn ? alpn : "-",
+              earlydata_max);
   if(quic_tp && quic_tp_len) {
     qtp_clone = Curl_memdup0((char *)quic_tp, quic_tp_len);
     if(!qtp_clone) {
index 02085f412ac7c8789f449dd34370ef551b50004b..1a7f362f8647b79db5895a4f57c8c942568af527 100644 (file)
@@ -667,7 +667,7 @@ CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data,
   return result;
 }
 
-/* get 32 bits of random */
+/* get length bytes of randomness */
 CURLcode Curl_ssl_random(struct Curl_easy *data,
                          unsigned char *entropy,
                          size_t length)
index 2c661a61c1dce06e4dd634ceef85bb30f33673ac..5f4ec4604b74eec9c362297fca6eb95004c0754b 100644 (file)
  *
  ***************************************************************************/
 
-/* This file is for implementing all "generic" SSL functions that all libcurl
-   internals should use. It is then responsible for calling the proper
-   "backend" function.
-
-   SSL-functions in libcurl should call functions in this source file, and not
-   to any specific SSL-layer.
-
-   Curl_ssl_ - prefix for generic ones
-
-   Note that this source code uses the functions of the configured SSL
-   backend via the global Curl_ssl instance.
-
-   "SSL/TLS Strong Encryption: An Introduction"
-   https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html
-*/
-
 #include "curl_setup.h"
 
 #ifdef USE_SSL
@@ -58,6 +42,7 @@
 #include "vtls.h" /* generic SSL protos etc */
 #include "vtls_int.h"
 #include "vtls_scache.h"
+#include "vtls_spack.h"
 
 #include "strcase.h"
 #include "url.h"
@@ -65,6 +50,7 @@
 #include "share.h"
 #include "curl_trc.h"
 #include "curl_sha256.h"
+#include "rand.h"
 #include "warnless.h"
 #include "curl_printf.h"
 #include "strdup.h"
@@ -215,18 +201,33 @@ static void cf_ssl_scache_peer_set_obj(struct Curl_ssl_scache_peer *peer,
   peer->sobj_free = sobj_free;
 }
 
-static CURLcode cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer,
-                                        const char *ssl_peer_key,
-                                        const char *clientcert,
-                                        const char *srp_username,
-                                        const char *srp_password)
+static CURLcode
+cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer,
+                        const char *ssl_peer_key,
+                        const char *clientcert,
+                        const char *srp_username,
+                        const char *srp_password,
+                        const unsigned char *salt,
+                        const unsigned char *hmac)
 {
   CURLcode result = CURLE_OUT_OF_MEMORY;
 
   DEBUGASSERT(!peer->ssl_peer_key);
-  peer->ssl_peer_key = strdup(ssl_peer_key);
-  if(!peer->ssl_peer_key)
+  if(ssl_peer_key) {
+    peer->ssl_peer_key = strdup(ssl_peer_key);
+    if(!peer->ssl_peer_key)
+      goto out;
+    peer->hmac_set = FALSE;
+  }
+  else if(salt && hmac) {
+    memcpy(peer->key_salt, salt, sizeof(peer->key_salt));
+    memcpy(peer->key_hmac, hmac, sizeof(peer->key_hmac));
+    peer->hmac_set = TRUE;
+  }
+  else {
+    result = CURLE_BAD_FUNCTION_ARGUMENT;
     goto out;
+  }
   if(clientcert) {
     peer->clientcert = strdup(clientcert);
     if(!peer->clientcert)
@@ -557,7 +558,16 @@ out:
 static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer,
                                      struct ssl_primary_config *conn_config)
 {
-  if(!Curl_safecmp(peer->clientcert, conn_config->clientcert))
+  if(!conn_config) {
+    if(peer->clientcert)
+      return FALSE;
+#ifdef USE_TLS_SRP
+    if(peer->srp_username || peer->srp_password)
+      return FALSE;
+#endif
+    return TRUE;
+  }
+  else if(!Curl_safecmp(peer->clientcert, conn_config->clientcert))
     return FALSE;
 #ifdef USE_TLS_SRP
    if(Curl_timestrcmp(peer->srp_username, conn_config->username) ||
@@ -567,21 +577,17 @@ static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer,
   return TRUE;
 }
 
-static CURLcode cf_ssl_find_peer(struct Curl_cfilter *cf,
-                                 struct Curl_easy *data,
-                                 struct Curl_ssl_scache *scache,
-                                 const char *ssl_peer_key,
-                                 struct Curl_ssl_scache_peer **ppeer)
+static CURLcode
+cf_ssl_find_peer_by_key(struct Curl_easy *data,
+                        struct Curl_ssl_scache *scache,
+                        const char *ssl_peer_key,
+                        struct ssl_primary_config *conn_config,
+                        struct Curl_ssl_scache_peer **ppeer)
 {
-  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);
   size_t i, peer_key_len = 0;
   CURLcode result = CURLE_OK;
 
   *ppeer = NULL;
-  if(!ssl_config || !ssl_config->primary.cache_session)
-    goto out;
-
   /* check for entries with known peer_key */
   for(i = 0; scache && i < scache->peer_count; i++) {
     if(scache->peers[i].ssl_peer_key &&
@@ -611,6 +617,8 @@ static CURLcode cf_ssl_find_peer(struct Curl_cfilter *cf,
         goto out;
       if(!memcmp(scache->peers[i].key_hmac, my_hmac, sizeof(my_hmac))) {
         /* remember peer_key for future lookups */
+        CURL_TRC_SSLS(data, "peer entry %zu key recovered: %s",
+                      i, ssl_peer_key);
         scache->peers[i].ssl_peer_key = strdup(ssl_peer_key);
         if(!scache->peers[i].ssl_peer_key) {
           result = CURLE_OUT_OF_MEMORY;
@@ -621,34 +629,18 @@ static CURLcode cf_ssl_find_peer(struct Curl_cfilter *cf,
       }
     }
   }
+  CURL_TRC_SSLS(data, "peer not found for %s", ssl_peer_key);
 out:
-  if(result)
-    CURL_TRC_CF(data, cf, "[SACHE] failure finding scache peer: %d", result);
   return result;
 }
 
-static CURLcode cf_ssl_add_peer(struct Curl_cfilter *cf,
-                                struct Curl_easy *data,
-                                struct Curl_ssl_scache *scache,
-                                const char *ssl_peer_key,
-                                struct Curl_ssl_scache_peer **ppeer)
+static struct Curl_ssl_scache_peer *
+cf_ssl_get_free_peer(struct Curl_ssl_scache *scache)
 {
-  struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
   struct Curl_ssl_scache_peer *peer = NULL;
   size_t i;
-  CURLcode result;
-
-  *ppeer = NULL;
-  result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
-  if(result || !scache->peer_count)
-    return result;
 
-  if(peer) {
-    *ppeer = peer;
-    return CURLE_OK;
-  }
-
-  /* not there, find empty or oldest peer */
+  /* find empty or oldest peer */
   for(i = 0; i < scache->peer_count; ++i) {
     /* free peer entry? */
     if(!scache->peers[i].ssl_peer_key && !scache->peers[i].hmac_set) {
@@ -667,39 +659,86 @@ static CURLcode cf_ssl_add_peer(struct Curl_cfilter *cf,
     }
   }
   DEBUGASSERT(peer);
-  if(!peer)
+  if(peer)
+    cf_ssl_scache_clear_peer(peer);
+  return peer;
+}
+
+static CURLcode
+cf_ssl_add_peer(struct Curl_easy *data,
+                struct Curl_ssl_scache *scache,
+                const char *ssl_peer_key,
+                struct ssl_primary_config *conn_config,
+                struct Curl_ssl_scache_peer **ppeer)
+{
+  struct Curl_ssl_scache_peer *peer = NULL;
+  CURLcode result = CURLE_OK;
+
+  *ppeer = NULL;
+  if(ssl_peer_key) {
+    result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
+                                     &peer);
+    if(result || !scache->peer_count)
+      return result;
+  }
+
+  if(peer) {
+    *ppeer = peer;
     return CURLE_OK;
-  /* clear previous peer and reinit */
-  cf_ssl_scache_clear_peer(peer);
-  result = cf_ssl_scache_peer_init(peer, ssl_peer_key,
-                                   conn_config->clientcert,
+  }
+
+  peer = cf_ssl_get_free_peer(scache);
+  if(peer) {
+    const char *ccert = conn_config ? conn_config->clientcert : NULL;
+    const char *username = NULL, *password = NULL;
 #ifdef USE_TLS_SRP
-                                   conn_config->username,
-                                   conn_config->password);
-#else
-                                   NULL, NULL);
+    username = conn_config ? conn_config->username : NULL;
+    password = conn_config ? conn_config->password : NULL;
 #endif
-  if(result)
-    goto out;
-  /* all ready */
-  *ppeer = peer;
-  result = CURLE_OK;
+    result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert,
+                                     username, password, NULL, NULL);
+    if(result)
+      goto out;
+    /* all ready */
+    *ppeer = peer;
+    result = CURLE_OK;
+  }
 
 out:
   if(result) {
     cf_ssl_scache_clear_peer(peer);
-    CURL_TRC_CF(data, cf, "[SACHE] failure adding peer: %d", result);
   }
   return result;
 }
 
-static CURLcode cf_scache_peer_add_session(struct Curl_cfilter *cf,
-                                           struct Curl_easy *data,
-                                           struct Curl_ssl_scache *scache,
-                                           const char *ssl_peer_key,
-                                           struct Curl_ssl_session *s)
+static void cf_scache_peer_add_session(struct Curl_ssl_scache_peer *peer,
+                                       struct Curl_ssl_session *s,
+                                       curl_off_t now)
+{
+  /* A session not from TLSv1.3 replaces all other. */
+  if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3) {
+    Curl_llist_destroy(&peer->sessions, NULL);
+    Curl_llist_append(&peer->sessions, s, &s->list);
+  }
+  else {
+    /* Expire existing, append, trim from head to obey max_sessions */
+    cf_scache_peer_remove_expired(peer, now);
+    cf_scache_peer_remove_non13(peer);
+    Curl_llist_append(&peer->sessions, s, &s->list);
+    while(Curl_llist_count(&peer->sessions) > peer->max_sessions) {
+      Curl_node_remove(Curl_llist_head(&peer->sessions));
+    }
+  }
+}
+
+static CURLcode cf_scache_add_session(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      struct Curl_ssl_scache *scache,
+                                      const char *ssl_peer_key,
+                                      struct Curl_ssl_session *s)
 {
   struct Curl_ssl_scache_peer *peer = NULL;
+  struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
   CURLcode result = CURLE_OUT_OF_MEMORY;
   curl_off_t now = (curl_off_t)time(NULL);
   curl_off_t max_lifetime;
@@ -719,32 +758,19 @@ static CURLcode cf_scache_peer_add_session(struct Curl_cfilter *cf,
     s->valid_until = now + max_lifetime;
 
   if(cf_scache_session_expired(s, now)) {
-    CURL_TRC_CF(data, cf, "[SCACHE] add, session already expired");
+    CURL_TRC_SSLS(data, "add, session already expired");
     Curl_ssl_session_destroy(s);
     return CURLE_OK;
   }
 
-  result = cf_ssl_add_peer(cf, data, scache, ssl_peer_key, &peer);
+  result = cf_ssl_add_peer(data, scache, ssl_peer_key, conn_config, &peer);
   if(result || !peer) {
-    CURL_TRC_CF(data, cf, "[SCACHE] unable to add scache peer: %d", result);
+    CURL_TRC_SSLS(data, "unable to add scache peer: %d", result);
     Curl_ssl_session_destroy(s);
     goto out;
   }
 
-  /* A session not from TLSv1.3 replaces all other. */
-  if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3) {
-    Curl_llist_destroy(&peer->sessions, NULL);
-    Curl_llist_append(&peer->sessions, s, &s->list);
-  }
-  else {
-    /* Expire existing, append, trim from head to obey max_sessions */
-    cf_scache_peer_remove_expired(peer, now);
-    cf_scache_peer_remove_non13(peer);
-    Curl_llist_append(&peer->sessions, s, &s->list);
-    while(Curl_llist_count(&peer->sessions) > peer->max_sessions) {
-      Curl_node_remove(Curl_llist_head(&peer->sessions));
-    }
-  }
+  cf_scache_peer_add_session(peer, s, now);
 
 out:
   if(result) {
@@ -752,12 +778,12 @@ out:
           ssl_peer_key, result);
   }
   else
-    CURL_TRC_CF(data, cf, "[SCACHE] added session for %s [proto=0x%x, "
-                "valid_secs=%" FMT_OFF_T ", alpn=%s, earlydata=%zu, "
-                "quic_tp=%s], peer has %zu sessions now",
-                ssl_peer_key, s->ietf_tls_id, s->valid_until - now, s->alpn,
-                s->earlydata_max, s->quic_tp ? "yes" : "no",
-                Curl_llist_count(&peer->sessions));
+    CURL_TRC_SSLS(data, "added session for %s [proto=0x%x, "
+                  "valid_secs=%" FMT_OFF_T ", alpn=%s, earlydata=%zu, "
+                  "quic_tp=%s], peer has %zu sessions now",
+                  ssl_peer_key, s->ietf_tls_id, s->valid_until - now,
+                  s->alpn, s->earlydata_max, s->quic_tp ? "yes" : "no",
+                  Curl_llist_count(&peer->sessions));
   return result;
 }
 
@@ -767,10 +793,16 @@ CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf,
                              struct Curl_ssl_session *s)
 {
   struct Curl_ssl_scache *scache = data->state.ssl_scache;
+  struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
   CURLcode result;
 
+  if(!ssl_config || !scache || !ssl_config->primary.cache_session) {
+    Curl_ssl_session_destroy(s);
+    return CURLE_OK;
+  }
+
   Curl_ssl_scache_lock(data);
-  result = cf_scache_peer_add_session(cf, data, scache, ssl_peer_key, s);
+  result = cf_scache_add_session(cf, data, scache, ssl_peer_key, s);
   Curl_ssl_scache_unlock(data);
   return result;
 }
@@ -794,6 +826,7 @@ CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf,
                               struct Curl_ssl_session **ps)
 {
   struct Curl_ssl_scache *scache = data->state.ssl_scache;
+  struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
   struct Curl_ssl_scache_peer *peer = NULL;
   struct Curl_llist_node *n;
   struct Curl_ssl_session *s = NULL;
@@ -804,7 +837,8 @@ CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf,
     return CURLE_OK;
 
   Curl_ssl_scache_lock(data);
-  result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
+  result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
+                                   &peer);
   if(!result && peer) {
     cf_scache_peer_remove_expired(peer, (curl_off_t)time(NULL));
     n = Curl_llist_head(&peer->sessions);
@@ -817,14 +851,14 @@ CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf,
   Curl_ssl_scache_unlock(data);
   if(s) {
     *ps = s;
-    CURL_TRC_CF(data, cf, "[SCACHE] took session for %s [proto=0x%x, "
-                "alpn=%s, earlydata=%zu, quic_tp=%s], %zu sessions remain",
-                ssl_peer_key, s->ietf_tls_id, s->alpn,
-                s->earlydata_max, s->quic_tp ? "yes" : "no",
-                Curl_llist_count(&peer->sessions));
+    CURL_TRC_SSLS(data, "took session for %s [proto=0x%x, "
+                  "alpn=%s, earlydata=%zu, quic_tp=%s], %zu sessions remain",
+                  ssl_peer_key, s->ietf_tls_id, 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);
+    CURL_TRC_SSLS(data, "no cached session for %s", ssl_peer_key);
   }
   return result;
 }
@@ -836,15 +870,16 @@ CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf,
                                  Curl_ssl_scache_obj_dtor *sobj_free)
 {
   struct Curl_ssl_scache *scache = data->state.ssl_scache;
+  struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
   struct Curl_ssl_scache_peer *peer = NULL;
   CURLcode result;
 
   DEBUGASSERT(sobj);
   DEBUGASSERT(sobj_free);
 
-  result = cf_ssl_add_peer(cf, data, scache, ssl_peer_key, &peer);
+  result = cf_ssl_add_peer(data, scache, ssl_peer_key, conn_config, &peer);
   if(result || !peer) {
-    CURL_TRC_CF(data, cf, "[SCACHE] unable to add scache peer: %d", result);
+    CURL_TRC_SSLS(data, "unable to add scache peer: %d", result);
     goto out;
   }
 
@@ -863,6 +898,7 @@ bool Curl_ssl_scache_get_obj(struct Curl_cfilter *cf,
                              void **sobj)
 {
   struct Curl_ssl_scache *scache = data->state.ssl_scache;
+  struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
   struct Curl_ssl_scache_peer *peer = NULL;
   CURLcode result;
 
@@ -870,15 +906,16 @@ bool Curl_ssl_scache_get_obj(struct Curl_cfilter *cf,
   if(!scache)
     return FALSE;
 
-  result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
+  result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
+                                   &peer);
   if(result)
     return FALSE;
 
   if(peer)
     *sobj = peer->sobj;
 
-  CURL_TRC_CF(data, cf, "[SACHE] %s cached session for '%s'",
-              *sobj ? "Found" : "No", ssl_peer_key);
+  CURL_TRC_SSLS(data, "%s cached session for '%s'",
+                *sobj ? "Found" : "No", ssl_peer_key);
   return !!*sobj;
 }
 
@@ -887,6 +924,7 @@ void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf,
                                 const char *ssl_peer_key)
 {
   struct Curl_ssl_scache *scache = data->state.ssl_scache;
+  struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
   struct Curl_ssl_scache_peer *peer = NULL;
   CURLcode result;
 
@@ -895,10 +933,237 @@ void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf,
     return;
 
   Curl_ssl_scache_lock(data);
-  result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
+  result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
+                                   &peer);
   if(!result && peer)
     cf_ssl_scache_clear_peer(peer);
   Curl_ssl_scache_unlock(data);
 }
 
+#ifdef USE_SSLS_EXPORT
+
+#define CURL_SSL_TICKET_MAX   (16*1024)
+
+static CURLcode cf_ssl_scache_peer_set_hmac(struct Curl_ssl_scache_peer *peer)
+{
+  CURLcode result;
+
+  DEBUGASSERT(peer);
+  if(!peer->ssl_peer_key)
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+
+  result = Curl_rand(NULL, peer->key_salt, sizeof(peer->key_salt));
+  if(result)
+    return result;
+
+  result = Curl_hmacit(&Curl_HMAC_SHA256,
+                       peer->key_salt, sizeof(peer->key_salt),
+                       (const unsigned char *)peer->ssl_peer_key,
+                       strlen(peer->ssl_peer_key),
+                       peer->key_hmac);
+  if(!result)
+    peer->hmac_set = TRUE;
+  return result;
+}
+
+static CURLcode
+cf_ssl_find_peer_by_hmac(struct Curl_ssl_scache *scache,
+                         const unsigned char *salt,
+                         const unsigned char *hmac,
+                         struct Curl_ssl_scache_peer **ppeer)
+{
+  size_t i;
+  CURLcode result = CURLE_OK;
+
+  *ppeer = NULL;
+  /* look for an entry that matches salt+hmac exactly or has a known
+   * ssl_peer_key which salt+hmac's to the same. */
+  for(i = 0; scache && i < scache->peer_count; i++) {
+    struct Curl_ssl_scache_peer *peer = &scache->peers[i];
+    if(!cf_ssl_scache_match_auth(peer, NULL))
+      continue;
+    if(scache->peers[i].hmac_set &&
+       !memcmp(peer->key_salt, salt, sizeof(peer->key_salt)) &&
+       !memcmp(peer->key_hmac, hmac, sizeof(peer->key_hmac))) {
+      /* found exact match, return */
+      *ppeer = peer;
+      goto out;
+    }
+    else if(peer->ssl_peer_key) {
+      unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH];
+      /* compute hmac for the passed salt */
+      result = Curl_hmacit(&Curl_HMAC_SHA256,
+                           salt, sizeof(peer->key_salt),
+                           (const unsigned char *)peer->ssl_peer_key,
+                           strlen(peer->ssl_peer_key),
+                           my_hmac);
+      if(result)
+        goto out;
+      if(!memcmp(my_hmac, hmac, sizeof(my_hmac))) {
+        /* cryptohash match, take over salt+hmac if no set and return */
+        if(!peer->hmac_set) {
+          memcpy(peer->key_salt, salt, sizeof(peer->key_salt));
+          memcpy(peer->key_hmac, hmac, sizeof(peer->key_hmac));
+          peer->hmac_set = TRUE;
+        }
+        *ppeer = peer;
+        goto out;
+      }
+    }
+  }
+out:
+  return result;
+}
+
+CURLcode Curl_ssl_session_import(struct Curl_easy *data,
+                                 const char *ssl_peer_key,
+                                 const unsigned char *shmac, size_t shmac_len,
+                                 const unsigned char *sdata, size_t sdata_len)
+{
+  struct Curl_ssl_scache *scache = data->state.ssl_scache;
+  struct Curl_ssl_scache_peer *peer = NULL;
+  struct Curl_ssl_session *s = NULL;
+  bool locked = FALSE;
+  CURLcode r;
+
+  if(!scache) {
+    r = CURLE_BAD_FUNCTION_ARGUMENT;
+    goto out;
+  }
+  if(!ssl_peer_key && (!shmac || !shmac_len)) {
+    r = CURLE_BAD_FUNCTION_ARGUMENT;
+    goto out;
+  }
+
+  r = Curl_ssl_session_unpack(data, sdata, sdata_len, &s);
+  if(r)
+    goto out;
+
+  Curl_ssl_scache_lock(data);
+  locked = TRUE;
+
+  if(ssl_peer_key) {
+    r = cf_ssl_add_peer(data, scache, ssl_peer_key, NULL, &peer);
+    if(r)
+      goto out;
+  }
+  else if(shmac_len != (sizeof(peer->key_salt) + sizeof(peer->key_hmac))) {
+    /* Either salt+hmac was garbled by caller or is from a curl version
+     * that does things differently */
+    r = CURLE_BAD_FUNCTION_ARGUMENT;
+    goto out;
+  }
+  else {
+    const unsigned char *salt = shmac;
+    const unsigned char *hmac = shmac + sizeof(peer->key_salt);
+
+    r = cf_ssl_find_peer_by_hmac(scache, salt, hmac, &peer);
+    if(r)
+      goto out;
+    if(!peer) {
+      peer = cf_ssl_get_free_peer(scache);
+      if(peer) {
+        r = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL,
+                                    NULL, NULL, salt, hmac);
+        if(r)
+          goto out;
+      }
+    }
+  }
+
+  if(peer) {
+    cf_scache_peer_add_session(peer, s, time(NULL));
+    s = NULL; /* peer is now owner */
+    CURL_TRC_SSLS(data, "successfully imported ticket for peer %s, now "
+                  "with %zu tickets",
+                  peer->ssl_peer_key ? peer->ssl_peer_key : "without key",
+                  Curl_llist_count(&peer->sessions));
+  }
+
+out:
+  if(locked)
+    Curl_ssl_scache_unlock(data);
+  Curl_ssl_session_destroy(s);
+  return r;
+}
+
+CURLcode Curl_ssl_session_export(struct Curl_easy *data,
+                                 curl_ssls_export_cb *export_fn,
+                                 void *userptr)
+{
+  struct Curl_ssl_scache *scache = data->state.ssl_scache;
+  struct Curl_ssl_scache_peer *peer;
+  struct dynbuf sbuf, hbuf;
+  struct Curl_llist_node *n;
+  size_t i, npeers = 0, ntickets = 0;
+  curl_off_t now = time(NULL);
+  CURLcode r = CURLE_OK;
+
+  if(!export_fn)
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  if(!scache)
+    return CURLE_OK;
+
+  Curl_ssl_scache_lock(data);
+
+  Curl_dyn_init(&hbuf, (CURL_SHA256_DIGEST_LENGTH * 2) + 1);
+  Curl_dyn_init(&sbuf, CURL_SSL_TICKET_MAX);
+
+  for(i = 0; scache && i < scache->peer_count; i++) {
+    peer = &scache->peers[i];
+    if(!peer->ssl_peer_key && !peer->hmac_set)
+      continue;  /* skip free entry */
+    if(peer->clientcert || peer->srp_username || peer->srp_password)
+      continue;  /* not exporting those */
+
+    Curl_dyn_reset(&hbuf);
+    cf_scache_peer_remove_expired(peer, now);
+    n = Curl_llist_head(&peer->sessions);
+    if(n)
+      ++npeers;
+    while(n) {
+      struct Curl_ssl_session *s = Curl_node_elem(n);
+      if(!peer->hmac_set) {
+        r = cf_ssl_scache_peer_set_hmac(peer);
+        if(r)
+          goto out;
+      }
+      if(!Curl_dyn_len(&hbuf)) {
+        r = Curl_dyn_addn(&hbuf, peer->key_salt, sizeof(peer->key_salt));
+        if(r)
+          goto out;
+        r = Curl_dyn_addn(&hbuf, peer->key_hmac, sizeof(peer->key_hmac));
+        if(r)
+          goto out;
+      }
+      Curl_dyn_reset(&sbuf);
+      r = Curl_ssl_session_pack(data, s, &sbuf);
+      if(r)
+        goto out;
+
+      r = export_fn(data, userptr, peer->ssl_peer_key,
+                    Curl_dyn_uptr(&hbuf), Curl_dyn_len(&hbuf),
+                    Curl_dyn_uptr(&sbuf), Curl_dyn_len(&sbuf),
+                    s->valid_until, s->ietf_tls_id,
+                    s->alpn, s->earlydata_max);
+      if(r)
+        goto out;
+      ++ntickets;
+      n = Curl_node_next(n);
+    }
+
+  }
+  r = CURLE_OK;
+  CURL_TRC_SSLS(data, "exported %zu session tickets for %zu peers",
+                ntickets, npeers);
+
+out:
+  Curl_ssl_scache_unlock(data);
+  Curl_dyn_free(&hbuf);
+  Curl_dyn_free(&sbuf);
+  return r;
+}
+
+#endif /* USE_SSLS_EXPORT */
+
 #endif /* USE_SSL */
index 14b011241f9b9e7187b9bbca1de3ed4de4c3de22..a81f57e3936289658c212ffb0dc2426703e7061d 100644 (file)
@@ -195,6 +195,18 @@ void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf,
                                 struct Curl_easy *data,
                                 const char *ssl_peer_key);
 
+#ifdef USE_SSLS_EXPORT
+
+CURLcode Curl_ssl_session_import(struct Curl_easy *data,
+                                 const char *ssl_peer_key,
+                                 const unsigned char *shmac, size_t shmac_len,
+                                 const unsigned char *sdata, size_t sdata_len);
+
+CURLcode Curl_ssl_session_export(struct Curl_easy *data,
+                                 curl_ssls_export_cb *export_fn,
+                                 void *userptr);
+
+#endif /* USE_SSLS_EXPORT */
 
 #else /* USE_SSL */
 
diff --git a/lib/vtls/vtls_spack.c b/lib/vtls/vtls_spack.c
new file mode 100644 (file)
index 0000000..6dec8e5
--- /dev/null
@@ -0,0 +1,345 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_SSLS_EXPORT
+
+#include "urldata.h"
+#include "curl_trc.h"
+#include "vtls_scache.h"
+#include "vtls_spack.h"
+#include "strdup.h"
+
+/* The last #include files should be: */
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#ifdef _MSC_VER
+#if _MSC_VER >= 1600
+#include <stdint.h>
+#else
+typedef unsigned char uint8_t;
+typedef unsigned __int16 uint16_t;
+typedef unsigned __int32 uint32_t;
+typedef unsigned __int64 uint64_t;
+#endif
+#endif /* _MSC_VER */
+
+#ifndef UINT16_MAX
+#define UINT16_MAX    0xffff
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX    0xffffffff
+#endif
+
+#define CURL_SPACK_VERSION       0x01
+#define CURL_SPACK_IETF_ID       0x02
+#define CURL_SPACK_VALID_UNTIL   0x03
+#define CURL_SPACK_TICKET        0x04
+#define CURL_SPACK_ALPN          0x05
+#define CURL_SPACK_EARLYDATA     0x06
+#define CURL_SPACK_QUICTP        0x07
+
+static CURLcode spack_enc8(struct dynbuf *buf, uint8_t b)
+{
+  return Curl_dyn_addn(buf, &b, 1);
+}
+
+static CURLcode
+spack_dec8(uint8_t *val, const uint8_t **src, const uint8_t *end)
+{
+  if(end - *src < 1)
+    return CURLE_READ_ERROR;
+  *val = **src;
+  *src += 1;
+  return CURLE_OK;
+}
+
+static CURLcode spack_enc16(struct dynbuf *buf, uint16_t val)
+{
+  uint8_t nval[2];
+  nval[0] = (uint8_t)(val >> 8);
+  nval[1] = (uint8_t)val;
+  return Curl_dyn_addn(buf, nval, sizeof(nval));
+}
+
+static CURLcode
+spack_dec16(uint16_t *val, const uint8_t **src, const uint8_t *end)
+{
+  if(end - *src < 2)
+    return CURLE_READ_ERROR;
+  *val = (uint16_t)((*src)[0] << 8 | (*src)[1]);
+  *src += 2;
+  return CURLE_OK;
+}
+
+static CURLcode spack_enc32(struct dynbuf *buf, uint32_t val)
+{
+  uint8_t nval[4];
+  nval[0] = (uint8_t)(val >> 24);
+  nval[1] = (uint8_t)(val >> 16);
+  nval[2] = (uint8_t)(val >> 8);
+  nval[3] = (uint8_t)val;
+  return Curl_dyn_addn(buf, nval, sizeof(nval));
+}
+
+static CURLcode
+spack_dec32(uint32_t *val, const uint8_t **src, const uint8_t *end)
+{
+  if(end - *src < 4)
+    return CURLE_READ_ERROR;
+  *val = (uint32_t)(*src)[0] << 24 | (uint32_t)(*src)[1] << 16 |
+         (uint32_t)(*src)[2] << 8 | (*src)[3];
+  *src += 4;
+  return CURLE_OK;
+}
+
+static CURLcode spack_enc64(struct dynbuf *buf, uint64_t val)
+{
+  uint8_t nval[8];
+  nval[0] = (uint8_t)(val >> 56);
+  nval[1] = (uint8_t)(val >> 48);
+  nval[2] = (uint8_t)(val >> 40);
+  nval[3] = (uint8_t)(val >> 32);                  \
+  nval[4] = (uint8_t)(val >> 24);
+  nval[5] = (uint8_t)(val >> 16);
+  nval[6] = (uint8_t)(val >> 8);
+  nval[7] = (uint8_t)val;
+  return Curl_dyn_addn(buf, nval, sizeof(nval));
+}
+
+static CURLcode
+spack_dec64(uint64_t *val, const uint8_t **src, const uint8_t *end)
+{
+  if(end - *src < 8)
+    return CURLE_READ_ERROR;
+  *val = (uint64_t)(*src)[0] << 56 | (uint64_t)(*src)[1] << 48 |
+         (uint64_t)(*src)[2] << 40 | (uint64_t)(*src)[3] << 32 |
+         (uint64_t)(*src)[4] << 24 | (uint64_t)(*src)[5] << 16 |
+         (uint64_t)(*src)[6] << 8 | (*src)[7];
+  *src += 8;
+  return CURLE_OK;
+}
+
+static CURLcode spack_encstr16(struct dynbuf *buf, const char *s)
+{
+  size_t slen = strlen(s);
+  CURLcode r;
+  if(slen > UINT16_MAX)
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  r = spack_enc16(buf, (uint16_t)slen);
+  if(!r) {
+    r = Curl_dyn_addn(buf, s, slen);
+  }
+  return r;
+}
+
+static CURLcode
+spack_decstr16(char **val, const uint8_t **src, const uint8_t *end)
+{
+  uint16_t slen;
+  CURLcode r;
+
+  *val = NULL;
+  r = spack_dec16(&slen, src, end);
+  if(r)
+    return r;
+  if(end - *src < slen)
+    return CURLE_READ_ERROR;
+  *val = Curl_memdup0((const char *)(*src), slen);
+  *src += slen;
+  return *val ? CURLE_OK : CURLE_OUT_OF_MEMORY;
+}
+
+static CURLcode spack_encdata16(struct dynbuf *buf,
+                                const uint8_t *data, size_t data_len)
+{
+  CURLcode r;
+  if(data_len > UINT16_MAX)
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  r = spack_enc16(buf, (uint16_t)data_len);
+  if(!r) {
+    r = Curl_dyn_addn(buf, data, data_len);
+  }
+  return r;
+}
+
+static CURLcode
+spack_decdata16(uint8_t **val, size_t *val_len,
+                const uint8_t **src, const uint8_t *end)
+{
+  uint16_t data_len;
+  CURLcode r;
+
+  *val = NULL;
+  r = spack_dec16(&data_len, src, end);
+  if(r)
+    return r;
+  if(end - *src < data_len)
+    return CURLE_READ_ERROR;
+  *val = Curl_memdup0((const char *)(*src), data_len);
+  *val_len = data_len;
+  *src += data_len;
+  return *val ? CURLE_OK : CURLE_OUT_OF_MEMORY;
+}
+
+CURLcode Curl_ssl_session_pack(struct Curl_easy *data,
+                               struct Curl_ssl_session *s,
+                               struct dynbuf *buf)
+{
+  CURLcode r;
+  DEBUGASSERT(s->sdata);
+  DEBUGASSERT(s->sdata_len);
+
+  if(s->valid_until < 0)
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+
+  r = spack_enc8(buf, CURL_SPACK_VERSION);
+  if(!r)
+    r = spack_enc8(buf, CURL_SPACK_TICKET);
+  if(!r)
+    r = spack_encdata16(buf, s->sdata, s->sdata_len);
+  if(!r)
+    r = spack_enc8(buf, CURL_SPACK_IETF_ID);
+  if(!r)
+    r = spack_enc16(buf, (uint16_t)s->ietf_tls_id);
+  if(!r)
+    r = spack_enc8(buf, CURL_SPACK_VALID_UNTIL);
+  if(!r)
+    r = spack_enc64(buf, (uint64_t)s->valid_until);
+  if(!r && s->alpn) {
+    r = spack_enc8(buf, CURL_SPACK_ALPN);
+    if(!r)
+      r = spack_encstr16(buf, s->alpn);
+  }
+  if(!r && s->earlydata_max) {
+    if(s->earlydata_max > UINT32_MAX)
+      r = CURLE_BAD_FUNCTION_ARGUMENT;
+    if(!r)
+      r = spack_enc8(buf, CURL_SPACK_EARLYDATA);
+    if(!r)
+      r = spack_enc32(buf, (uint32_t)s->earlydata_max);
+  }
+  if(!r && s->quic_tp && s->quic_tp_len) {
+    r = spack_enc8(buf, CURL_SPACK_QUICTP);
+    if(!r)
+      r = spack_encdata16(buf, s->quic_tp, s->quic_tp_len);
+  }
+
+  if(r)
+    CURL_TRC_SSLS(data, "error packing data: %d", r);
+  return r;
+}
+
+CURLcode Curl_ssl_session_unpack(struct Curl_easy *data,
+                                 const unsigned char *buf, size_t buflen,
+                                 struct Curl_ssl_session **ps)
+{
+  struct Curl_ssl_session *s = NULL;
+  const unsigned char *end = buf + buflen;
+  uint8_t val8, *pval8;
+  uint16_t val16;
+  uint32_t val32;
+  uint64_t val64;
+  CURLcode r;
+
+  DEBUGASSERT(buf);
+  DEBUGASSERT(buflen);
+  *ps = NULL;
+
+  r = spack_dec8(&val8, &buf, end);
+  if(r)
+    goto out;
+  if(val8 != CURL_SPACK_VERSION) {
+    r = CURLE_READ_ERROR;
+    goto out;
+  }
+
+  s = calloc(1, sizeof(*s));
+  if(!s) {
+    r = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  while(buf < end) {
+    r = spack_dec8(&val8, &buf, end);
+    if(r)
+      goto out;
+
+    switch(val8) {
+    case CURL_SPACK_ALPN:
+      r = spack_decstr16(&s->alpn, &buf, end);
+      if(r)
+        goto out;
+      break;
+    case CURL_SPACK_EARLYDATA:
+      r = spack_dec32(&val32, &buf, end);
+      if(r)
+        goto out;
+      s->earlydata_max = val32;
+      break;
+    case CURL_SPACK_IETF_ID:
+      r = spack_dec16(&val16, &buf, end);
+      if(r)
+        goto out;
+      s->ietf_tls_id = val16;
+      break;
+    case CURL_SPACK_QUICTP: {
+      r = spack_decdata16(&pval8, &s->quic_tp_len, &buf, end);
+      if(r)
+        goto out;
+      s->quic_tp = pval8;
+      break;
+    }
+    case CURL_SPACK_TICKET: {
+      r = spack_decdata16(&pval8, &s->sdata_len, &buf, end);
+      if(r)
+        goto out;
+      s->sdata = pval8;
+      break;
+    }
+    case CURL_SPACK_VALID_UNTIL:
+      r = spack_dec64(&val64, &buf, end);
+      if(r)
+        goto out;
+      s->valid_until = (curl_off_t)val64;
+      break;
+    default:  /* unknown tag */
+      r = CURLE_READ_ERROR;
+      goto out;
+    }
+  }
+
+out:
+  if(r) {
+    CURL_TRC_SSLS(data, "error unpacking data: %d", r);
+    Curl_ssl_session_destroy(s);
+  }
+  else
+    *ps = s;
+  return r;
+}
+
+#endif /* USE_SSLS_EXPORT */
diff --git a/lib/vtls/vtls_spack.h b/lib/vtls/vtls_spack.h
new file mode 100644 (file)
index 0000000..8905d7f
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef HEADER_CURL_VTLS_SPACK_H
+#define HEADER_CURL_VTLS_SPACK_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_SSLS_EXPORT
+
+struct dynbuf;
+struct Curl_ssl_session;
+
+CURLcode Curl_ssl_session_pack(struct Curl_easy *data,
+                               struct Curl_ssl_session *s,
+                               struct dynbuf *buf);
+
+CURLcode Curl_ssl_session_unpack(struct Curl_easy *data,
+                                 const unsigned char *buf, size_t buflen,
+                                 struct Curl_ssl_session **ps);
+
+#endif /* USE_SSLS_EXPORT */
+
+#endif /* HEADER_CURL_VTLS_SPACK_H */
index f11a320eb93ec0e7bdbcaa7ca28584274446ad71..f7b99dffccf68a367b42d2b87e6099d3f65fb536 100644 (file)
@@ -653,3 +653,41 @@ AS_HELP_STRING([--disable-ech],[Disable ECH support]),
   esac
 ])
 ])
+
+dnl CURL_CHECK_OPTION_SSLS_EXPORT
+dnl -----------------------------------------------------
+dnl Verify whether configure has been invoked with option
+dnl --enable-ssl-session-export or --disable-ssl-session-export, and set
+dnl shell variable want_ech as appropriate.
+
+AC_DEFUN([CURL_CHECK_OPTION_SSLS_EXPORT], [
+  AC_MSG_CHECKING([whether to enable SSL session export support])
+  OPT_SSLS_EXPORT="default"
+  AC_ARG_ENABLE(ssls-export,
+AS_HELP_STRING([--enable-ssls-export],
+               [Enable SSL session export support])
+AS_HELP_STRING([--disable-ssls-export],
+               [Disable SSL session export support]),
+  OPT_SSLS_EXPORT=$enableval)
+  case "$OPT_SSLS_EXPORT" in
+    no)
+      dnl --disable-ssls-export option used
+      want_ssls_export="no"
+      curl_ssls_export_msg="no      (--enable-ssls-export)"
+      AC_MSG_RESULT([no])
+      ;;
+    default)
+      dnl configure option not specified
+      want_ssls_export="no"
+      curl_ssls_export_msg="no      (--enable-ssls-export)"
+      AC_MSG_RESULT([no])
+      ;;
+    *)
+      dnl --enable-ssls-export option used
+      want_ssls_export="yes"
+      curl_ssls_export_msg="enabled (--disable-ssls-export)"
+      AC_MSG_RESULT([yes])
+      ;;
+  esac
+])
+])
index 8145883f38c394908628cdde1ed0f9d53808d746..d217848acf3f4804acbf50695a9ed87eb94a574e 100644 (file)
@@ -164,6 +164,7 @@ rem
       call :element %1 lib "timediff.c" %3
       call :element %1 lib "nonblock.c" %3
       call :element %1 lib "warnless.c" %3
+      call :element %1 lib "curl_get_line.c" %3
       call :element %1 lib "curl_multibyte.c" %3
       call :element %1 lib "version_win32.c" %3
       call :element %1 lib "dynbuf.c" %3
@@ -176,6 +177,7 @@ rem
       call :element %1 lib "nonblock.h" %3
       call :element %1 lib "warnless.h" %3
       call :element %1 lib "curl_ctype.h" %3
+      call :element %1 lib "curl_get_line.h" %3
       call :element %1 lib "curl_multibyte.h" %3
       call :element %1 lib "version_win32.h" %3
       call :element %1 lib "dynbuf.h" %3
index 93a8fd7272f2428ff1f94246a529641f48d8d364..06cf8b56e046d437bddc0239bb53d9f832bfe4a3 100755 (executable)
@@ -61,6 +61,8 @@ my %api = (
     'curl_easy_reset' => 'API',
     'curl_easy_send' => 'API',
     'curl_easy_setopt' => 'API',
+    'curl_easy_ssls_export' => 'API',
+    'curl_easy_ssls_import' => 'API',
     'curl_easy_strerror' => 'API',
     'curl_easy_unescape' => 'API',
     'curl_easy_upkeep' => 'API',
index cbf6d81c42c7d78bac7731fd7eb0e6cc766e35f0..28275fee93bd8b04d9433c74ccb567c65a7863bf 100644 (file)
 # libcurl sources to include in curltool lib we use for test binaries
 CURLTOOL_LIBCURL_CFILES = \
   ../lib/base64.c \
-  ../lib/dynbuf.c
+  ../lib/dynbuf.c \
+  ../lib/curl_get_line.c
 
 # libcurl has sources that provide functions named curlx_* that are not part of
 # the official API, but we reuse the code here to avoid duplication.
 CURLX_CFILES = \
   ../lib/base64.c \
+  ../lib/curl_get_line.c \
   ../lib/curl_multibyte.c \
   ../lib/dynbuf.c \
   ../lib/nonblock.c \
@@ -48,6 +50,7 @@ CURLX_CFILES = \
 
 CURLX_HFILES = \
   ../lib/curl_ctype.h \
+  ../lib/curl_get_line.h \
   ../lib/curl_multibyte.h \
   ../lib/curl_setup.h \
   ../lib/dynbuf.h \
@@ -92,6 +95,7 @@ CURL_CFILES = \
   tool_progress.c \
   tool_setopt.c \
   tool_sleep.c \
+  tool_ssls.c \
   tool_stderr.c \
   tool_strdup.c \
   tool_urlglob.c \
@@ -139,6 +143,7 @@ CURL_HFILES = \
   tool_setopt.h \
   tool_setup.h \
   tool_sleep.h \
+  tool_ssls.h \
   tool_stderr.h \
   tool_strdup.h \
   tool_urlglob.h \
index 62f0e58cc430da169b3cd696b063b931cc2d653f..e1799f8833ed520600fe1044cb66b0f7f373edcb 100644 (file)
@@ -330,6 +330,7 @@ struct GlobalConfig {
   bool styled_output;             /* enable fancy output style detection */
   long ms_per_transfer;           /* start next transfer after (at least) this
                                      many milliseconds */
+  char *ssl_sessions;             /* file to load/save SSL session tickets */
 #ifdef DEBUGBUILD
   bool test_duphandle;
   bool test_event_based;
index 81dbbb883521549e51733d15041cc90b455c7b3b..5eb37945193a5a81cc12dd91674d20860adb5df4 100644 (file)
@@ -300,6 +300,7 @@ static const struct LongShort aliases[]= {
   {"ssl-no-revoke",              ARG_BOOL, ' ', C_SSL_NO_REVOKE},
   {"ssl-reqd",                   ARG_BOOL, ' ', C_SSL_REQD},
   {"ssl-revoke-best-effort",     ARG_BOOL, ' ', C_SSL_REVOKE_BEST_EFFORT},
+  {"ssl-sessions",               ARG_FILE, ' ', C_SSL_SESSIONS},
   {"sslv2",                      ARG_NONE, '2', C_SSLV2},
   {"sslv3",                      ARG_NONE, '3', C_SSLV3},
   {"stderr",                     ARG_FILE, ' ', C_STDERR},
@@ -2470,6 +2471,12 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
       if(feature_ssl)
         config->ssl_revoke_best_effort = TRUE;
       break;
+    case C_SSL_SESSIONS: /* --ssl-sessions */
+      if(feature_ssls_export)
+        err = getstr(&global->ssl_sessions, nextarg, DENY_BLANK);
+      else
+        err = PARAM_LIBCURL_DOESNT_SUPPORT;
+      break;
     case C_TCP_FASTOPEN: /* --tcp-fastopen */
       config->tcp_fastopen = TRUE;
       break;
index 90708e001c0ebee1eb974b91ca9b10591a1c415d..af083d06c7b25e2ce5bc801317acbf8ed21626f4 100644 (file)
@@ -259,6 +259,7 @@ typedef enum {
   C_SSL_NO_REVOKE,
   C_SSL_REQD,
   C_SSL_REVOKE_BEST_EFFORT,
+  C_SSL_SESSIONS,
   C_SSLV2,
   C_SSLV3,
   C_STDERR,
index f6053e84003ee9a39813f89e8a65594bb1dc376c..ff3117a0382c5a70f22636ba357997d959828e79 100644 (file)
@@ -84,6 +84,7 @@ bool feature_ssl = FALSE;
 bool feature_tls_srp = FALSE;
 bool feature_zstd = FALSE;
 bool feature_ech = FALSE;
+bool feature_ssls_export = FALSE;
 
 static struct feature_name_presentp {
   const char   *feature_name;
@@ -115,6 +116,7 @@ static struct feature_name_presentp {
   {"SPNEGO",         &feature_spnego,     CURL_VERSION_SPNEGO},
   {"SSL",            &feature_ssl,        CURL_VERSION_SSL},
   {"SSPI",           NULL,                CURL_VERSION_SSPI},
+  {"SSLS-EXPORT",    &feature_ssls_export, 0},
   {"threadsafe",     NULL,                CURL_VERSION_THREADSAFE},
   {"TLS-SRP",        &feature_tls_srp,    CURL_VERSION_TLSAUTH_SRP},
   {"TrackMemory",    NULL,                CURL_VERSION_CURLDEBUG},
index 0d176699e81d4613a654e85594fd8b30f11f8354..003ed0140c8ac5d0527d2d8364192f826edf0a6b 100644 (file)
@@ -62,6 +62,7 @@ extern bool feature_ssl;
 extern bool feature_tls_srp;
 extern bool feature_zstd;
 extern bool feature_ech;
+extern bool feature_ssls_export;
 
 CURLcode get_libcurl_info(void);
 const char *proto_token(const char *proto);
index 3a4b096d1d82640239b349ece40662df113686f1..70b5e378172372d2af48d7cbeff110a889ea2ba3 100644 (file)
@@ -715,6 +715,9 @@ const struct helptxt helptext[] = {
   {"    --ssl-revoke-best-effort",
    "Ignore missing cert CRL dist points",
    CURLHELP_TLS},
+  {"    --ssl-sessions <filename>",
+   "Load/save SSL session tickets from/to this file",
+   CURLHELP_TLS},
   {"-2, --sslv2",
    "SSLv2",
    CURLHELP_DEPRECATED},
index 1bba71f829ea9f647a4d46bde144f00f587f060a..da807ab8578bc0889a64bdbc386960e8b8b652fb 100644 (file)
@@ -87,6 +87,7 @@
 #include "tool_parsecfg.h"
 #include "tool_setopt.h"
 #include "tool_sleep.h"
+#include "tool_ssls.h"
 #include "tool_urlglob.h"
 #include "tool_util.h"
 #include "tool_writeout.h"
@@ -3232,18 +3233,31 @@ CURLcode operate(struct GlobalConfig *global, int argc, argv_item_t argv[])
           curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
           curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
 
-          /* Get the required arguments for each operation */
-          do {
-            result = get_args(operation, count++);
+          if(global->ssl_sessions && feature_ssls_export)
+            result = tool_ssls_load(global, global->first, share,
+                                    global->ssl_sessions);
 
-            operation = operation->next;
-          } while(!result && operation);
+          if(!result) {
+            /* Get the required arguments for each operation */
+            do {
+              result = get_args(operation, count++);
 
-          /* Set the current operation pointer */
-          global->current = global->first;
+              operation = operation->next;
+            } while(!result && operation);
 
-          /* now run! */
-          result = run_all_transfers(global, share, result);
+            /* Set the current operation pointer */
+            global->current = global->first;
+
+            /* now run! */
+            result = run_all_transfers(global, share, result);
+
+            if(global->ssl_sessions && feature_ssls_export) {
+              CURLcode r2 = tool_ssls_save(global, global->first, share,
+                                           global->ssl_sessions);
+              if(r2 && !result)
+                result = r2;
+            }
+          }
 
           curl_share_cleanup(share);
           if(global->libcurl) {
diff --git a/src/tool_ssls.c b/src/tool_ssls.c
new file mode 100644 (file)
index 0000000..48b20de
--- /dev/null
@@ -0,0 +1,222 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "tool_setup.h"
+
+#include "curlx.h"
+#include "tool_cfgable.h"
+#include "tool_cb_dbg.h"
+#include "tool_msgs.h"
+#include "tool_setopt.h"
+#include "tool_ssls.h"
+#include "dynbuf.h"
+#include "curl_base64.h"
+#include "curl_get_line.h"
+
+/* The maximum line length for an ecoded session ticket */
+#define MAX_SSLS_LINE (64 * 1024)
+
+
+static CURLcode tool_ssls_easy(struct GlobalConfig *global,
+                               struct OperationConfig *config,
+                               CURLSH *share, CURL **peasy)
+{
+  CURLcode result = CURLE_OK;
+
+  *peasy = curl_easy_init();
+  if(!*peasy)
+    return CURLE_OUT_OF_MEMORY;
+
+  result = curl_easy_setopt(*peasy, CURLOPT_SHARE, share);
+  if(global->tracetype != TRACE_NONE) {
+    my_setopt(*peasy, CURLOPT_DEBUGFUNCTION, tool_debug_cb);
+    my_setopt(*peasy, CURLOPT_DEBUGDATA, config);
+    my_setopt(*peasy, CURLOPT_VERBOSE, 1L);
+  }
+  return result;
+}
+
+CURLcode tool_ssls_load(struct GlobalConfig *global,
+                        struct OperationConfig *config,
+                        CURLSH *share, const char *filename)
+{
+  FILE *fp;
+  CURL *easy = NULL;
+  struct dynbuf buf;
+  unsigned char *shmac = NULL, *sdata = NULL;
+  char *c, *line, *end;
+  size_t shmac_len, sdata_len;
+  CURLcode r = CURLE_OK;
+  int i, imported;
+
+  curlx_dyn_init(&buf, MAX_SSLS_LINE);
+  fp = fopen(filename, FOPEN_READTEXT);
+  if(!fp) { /* ok if it does not exist */
+    notef(global, "SSL session file does not exist (yet?): %s", filename);
+    goto out;
+  }
+
+  r = tool_ssls_easy(global, config, share, &easy);
+  if(r)
+    goto out;
+
+  i = imported = 0;
+  while(Curl_get_line(&buf, fp)) {
+    ++i;
+    curl_free(shmac);
+    curl_free(sdata);
+    line = Curl_dyn_ptr(&buf);
+    while(*line && ISBLANK(*line))
+      line++;
+    if(*line == '#')
+      /* skip commented lines */
+      continue;
+
+    c = memchr(line, ':', strlen(line));
+    if(!c) {
+      warnf(global, "unrecognized line %d in ssl session file %s",
+            i, filename);
+      continue;
+    }
+    *c = '\0';
+    r = curlx_base64_decode(line, &shmac, &shmac_len);
+    if(r) {
+      warnf(global, "invalid shmax base64 encoding in line %d", i);
+      continue;
+    }
+    line = c + 1;
+    end = line + strlen(line) - 1;
+    while((end > line) && (*end == '\n' || *end == '\r' || ISBLANK(*line))) {
+      *end = '\0';
+      --end;
+    }
+    r = curlx_base64_decode(line, &sdata, &sdata_len);
+    if(r) {
+      warnf(global, "invalid sdata base64 encoding in line %d: %s", i, line);
+      continue;
+    }
+
+    r = curl_easy_ssls_import(easy, NULL, shmac, shmac_len, sdata, sdata_len);
+    if(r) {
+      warnf(global, "import of session from line %d rejected(%d)", i, r);
+      continue;
+    }
+    ++imported;
+  }
+  r = CURLE_OK;
+
+out:
+  if(easy)
+    curl_easy_cleanup(easy);
+  if(fp)
+    fclose(fp);
+  curlx_dyn_free(&buf);
+  curl_free(shmac);
+  curl_free(sdata);
+  return r;
+}
+
+struct tool_ssls_ctx {
+  struct GlobalConfig *global;
+  FILE *fp;
+  int exported;
+};
+
+static CURLcode tool_ssls_exp(CURL *easy, void *userptr,
+                              const char *session_key,
+                              const unsigned char *shmac, size_t shmac_len,
+                              const unsigned char *sdata, size_t sdata_len,
+                              curl_off_t valid_until, int ietf_tls_id,
+                              const char *alpn, size_t earlydata_max)
+{
+  struct tool_ssls_ctx *ctx = userptr;
+  char *enc = NULL;
+  size_t enc_len;
+  CURLcode r;
+
+  (void)easy;
+  (void)valid_until;
+  (void)ietf_tls_id;
+  (void)alpn;
+  (void)earlydata_max;
+  if(!ctx->exported)
+    fputs("# Your SSL session cache. https://curl.se/docs/ssl-sessions.html\n"
+        "# This file was generated by libcurl! Edit at your own risk.\n",
+        ctx->fp);
+
+  r = curlx_base64_encode((const char *)shmac, shmac_len, &enc, &enc_len);
+  if(r)
+    goto out;
+  r = CURLE_WRITE_ERROR;
+  if(enc_len != fwrite(enc, 1, enc_len, ctx->fp))
+    goto out;
+  if(EOF == fputc(':', ctx->fp))
+    goto out;
+  curl_free(enc);
+  r = curlx_base64_encode((const char *)sdata, sdata_len, &enc, &enc_len);
+  if(r)
+    goto out;
+  r = CURLE_WRITE_ERROR;
+  if(enc_len != fwrite(enc, 1, enc_len, ctx->fp))
+    goto out;
+  if(EOF == fputc('\n', ctx->fp))
+    goto out;
+  r = CURLE_OK;
+  ctx->exported++;
+out:
+  if(r)
+    warnf(ctx->global, "Warning: error saving SSL session for '%s': %d",
+          session_key, r);
+  curl_free(enc);
+  return r;
+}
+
+CURLcode tool_ssls_save(struct GlobalConfig *global,
+                        struct OperationConfig *config,
+                        CURLSH *share, const char *filename)
+{
+  struct tool_ssls_ctx ctx;
+  CURL *easy = NULL;
+  CURLcode r = CURLE_OK;
+
+  ctx.global = global;
+  ctx.exported = 0;
+  ctx.fp = fopen(filename, FOPEN_WRITETEXT);
+  if(!ctx.fp) {
+    warnf(global, "Warning: Failed to create SSL session file %s", filename);
+    goto out;
+  }
+
+  r = tool_ssls_easy(global, config, share, &easy);
+  if(r)
+    goto out;
+
+  r = curl_easy_ssls_export(easy, tool_ssls_exp, &ctx);
+
+out:
+  if(easy)
+    curl_easy_cleanup(easy);
+  if(ctx.fp)
+    fclose(ctx.fp);
+  return r;
+}
diff --git a/src/tool_ssls.h b/src/tool_ssls.h
new file mode 100644 (file)
index 0000000..b8a6a5e
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef HEADER_CURL_TOOL_SSLS_H
+#define HEADER_CURL_TOOL_SSLS_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "tool_setup.h"
+#include "tool_operate.h"
+
+
+CURLcode tool_ssls_load(struct GlobalConfig *global,
+                        struct OperationConfig *config,
+                        CURLSH *share, const char *filename);
+CURLcode tool_ssls_save(struct GlobalConfig *global,
+                        struct OperationConfig *config,
+                        CURLSH *share, const char *filename);
+
+#endif /* HEADER_CURL_TOOL_SSLS_H */
index e1e74752ad8b1f603ab9321d985ad85735663b88..070d8dff4caabc44bd0e74da16973d25da6b3545 100644 (file)
@@ -67,6 +67,8 @@ curl_version_info
 curl_easy_strerror
 curl_share_strerror
 curl_easy_pause
+curl_easy_ssls_import
+curl_easy_ssls_export
 curl_easy_init
 curl_easy_setopt
 curl_easy_perform
index 883d4a5f6f9e54a313a24b27563c5918eaa03556..dfea92ef9604a4da43f7bd203baa3df9ef8000bf 100644 (file)
@@ -390,3 +390,37 @@ class TestSSLUse:
                     match_trace = line
                     break
             assert match_trace, f'Did not find "{exp_trace}" in trace\n{r.dump_logs()}'
+
+    @pytest.mark.skipif(condition=not Env.curl_has_feature('SSLS-EXPORT'),
+                        reason='curl lacks SSL session export support')
+    def test_17_15_session_export(self, env: Env, httpd):
+        proto = 'http/1.1'
+        if env.curl_uses_lib('libressl'):
+           pytest.skip('Libressl resumption does not work inTLSv1.3')
+        if env.curl_uses_lib('rustls-ffi'):
+            pytest.skip('rustsls does not expose sessions')
+        if env.curl_uses_lib('bearssl'):
+            pytest.skip('BearSSL does not support TLSv1.3')
+        if env.curl_uses_lib('mbedtls') and \
+           not env.curl_lib_version_at_least('mbedtls', '3.6.0'):
+            pytest.skip('mbedtls TLSv1.3 session resume not working before 3.6.0')
+        run_env = os.environ.copy()
+        run_env['CURL_DEBUG'] = 'ssl,scache'
+        # clean session file first, then reuse
+        session_file = os.path.join(env.gen_dir, 'test_17_15.sessions')
+        if os.path.exists(session_file):
+            return os.remove(session_file)
+        xargs = ['--tls-max', '1.3', '--tlsv1.3', '--ssl-sessions', session_file]
+        curl = CurlClient(env=env, run_env=run_env)
+        # tell the server to close the connection after each request
+        url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
+        r = curl.http_get(url=url, alpn_proto=proto, extra_args=xargs)
+        assert r.exit_code == 0, f'{r}'
+        assert r.json['HTTPS'] == 'on', f'{r.json}'
+        assert r.json['SSL_SESSION_RESUMED'] == 'Initial', f'{r.json}\n{r.dump_logs()}'
+        # ok, run again, sessions should be imported
+        run_dir2 = os.path.join(env.gen_dir, 'curl2')
+        curl = CurlClient(env=env, run_env=run_env, run_dir=run_dir2)
+        r = curl.http_get(url=url, alpn_proto=proto, extra_args=xargs)
+        assert r.exit_code == 0, f'{r}'
+        assert r.json['SSL_SESSION_RESUMED'] == 'Resumed', f'{r.json}\n{r.dump_logs()}'
index 575a4d121c7c72b363e7bdb415949bf58689cb8f..bcd8cae5e8a4900645036b9a32775e6c26d240fc 100644 (file)
@@ -34,6 +34,7 @@ CURLX_SRCS = \
  ../../lib/dynbuf.c \
  ../../lib/strdup.c \
  ../../lib/strcase.c \
+ ../../lib/curl_get_line.c \
  ../../lib/curl_multibyte.c
 
 CURLX_HDRS = \
@@ -45,6 +46,7 @@ CURLX_HDRS = \
  ../../lib/curl_ctype.h \
  ../../lib/dynbuf.h \
  ../../lib/strdup.h \
+ ../../lib/curl_get_line.h \
  ../../lib/curl_multibyte.h
 
 USEFUL = \
index 92e179fc79ccfdfe803deb10c618d23b25f74158..03d37a555ca4b48f24f149b22673b2da29adc3d2 100644 (file)
@@ -22,6 +22,8 @@
  *
  ***************************************************************************/
 #include "curlcheck.h"
+/* disable the curlx_get_line redefinitions for this unit test */
+#define BUILDING_LIBCURL
 #include "curl_get_line.h"
 #include "memdebug.h"
 
index aee03d5640d767540452709a752121b428ae8daa..41fe4d3dc9709e790feb5d4ff93fe0b5d7ebeebd 100644 (file)
@@ -688,6 +688,7 @@ CURL_FROM_LIBCURL=$(CURL_DIROBJ)\tool_hugehelp.obj \
  $(CURL_DIROBJ)\nonblock.obj \
  $(CURL_DIROBJ)\strtoofft.obj \
  $(CURL_DIROBJ)\warnless.obj \
+ $(CURL_DIROBJ)\curl_get_line.obj \
  $(CURL_DIROBJ)\curl_multibyte.obj \
  $(CURL_DIROBJ)\version_win32.obj \
  $(CURL_DIROBJ)\dynbuf.obj \
@@ -708,6 +709,8 @@ $(CURL_DIROBJ)\strtoofft.obj: ../lib/strtoofft.c
        $(CURL_CC) $(CURL_CFLAGS) /Fo"$@" ../lib/strtoofft.c
 $(CURL_DIROBJ)\warnless.obj: ../lib/warnless.c
        $(CURL_CC) $(CURL_CFLAGS) /Fo"$@" ../lib/warnless.c
+$(CURL_DIROBJ)\curl_get_line.obj: ../lib/curl_get_line.c
+       $(CURL_CC) $(CURL_CFLAGS) /Fo"$@" ../lib/curl_get_line.c
 $(CURL_DIROBJ)\curl_multibyte.obj: ../lib/curl_multibyte.c
        $(CURL_CC) $(CURL_CFLAGS) /Fo"$@" ../lib/curl_multibyte.c
 $(CURL_DIROBJ)\version_win32.obj: ../lib/version_win32.c