SRP
SRWLOCK
SSL
+SSLS
ssl
SSLeay
SSLKEYLOGFILE
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)
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)
CURL_CHECK_OPTION_RT
CURL_CHECK_OPTION_HTTPSRR
CURL_CHECK_OPTION_ECH
+CURL_CHECK_OPTION_SSLS_EXPORT
XC_CHECK_PATH_SEPARATOR
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
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
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}
])
- 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
- `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
ssl-no-revoke.md \
ssl-reqd.md \
ssl-revoke-best-effort.md \
+ ssl-sessions.md \
ssl.md \
sslv2.md \
sslv3.md \
--- /dev/null
+---
+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.
See-also:
- tlsv1.3
- tls-max
+ - ssl-sessions
Example:
- --tls-earlydata $URL
---
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 \
--- /dev/null
+---
+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.
--- /dev/null
+---
+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.
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.
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
--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
#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
vtls/sectransp.c \
vtls/vtls.c \
vtls/vtls_scache.c \
+ vtls/vtls_spack.c \
vtls/wolfssl.c \
vtls/x509asn1.c
vtls/vtls.h \
vtls/vtls_int.h \
vtls/vtls_scache.h \
+ vtls/vtls_spack.h \
vtls/wolfssl.h \
vtls/x509asn1.h
!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"
#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);
}
#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",
#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
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)) \
#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
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,
#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"
/* 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
+}
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
#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
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);
/* 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) {
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)
*
***************************************************************************/
-/* 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
#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"
#include "share.h"
#include "curl_trc.h"
#include "curl_sha256.h"
+#include "rand.h"
#include "warnless.h"
#include "curl_printf.h"
#include "strdup.h"
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)
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) ||
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 &&
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;
}
}
}
+ 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) {
}
}
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;
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) {
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;
}
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;
}
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;
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);
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;
}
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;
}
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;
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;
}
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;
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 */
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 */
--- /dev/null
+/***************************************************************************
+ * _ _ ____ _
+ * 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 */
--- /dev/null
+#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 */
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
+])
+])
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
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
'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',
# 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 \
CURLX_HFILES = \
../lib/curl_ctype.h \
+ ../lib/curl_get_line.h \
../lib/curl_multibyte.h \
../lib/curl_setup.h \
../lib/dynbuf.h \
tool_progress.c \
tool_setopt.c \
tool_sleep.c \
+ tool_ssls.c \
tool_stderr.c \
tool_strdup.c \
tool_urlglob.c \
tool_setopt.h \
tool_setup.h \
tool_sleep.h \
+ tool_ssls.h \
tool_stderr.h \
tool_strdup.h \
tool_urlglob.h \
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;
{"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},
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;
C_SSL_NO_REVOKE,
C_SSL_REQD,
C_SSL_REVOKE_BEST_EFFORT,
+ C_SSL_SESSIONS,
C_SSLV2,
C_SSLV3,
C_STDERR,
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;
{"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},
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);
{" --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},
#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"
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) {
--- /dev/null
+/***************************************************************************
+ * _ _ ____ _
+ * 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;
+}
--- /dev/null
+#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 */
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
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()}'
../../lib/dynbuf.c \
../../lib/strdup.c \
../../lib/strcase.c \
+ ../../lib/curl_get_line.c \
../../lib/curl_multibyte.c
CURLX_HDRS = \
../../lib/curl_ctype.h \
../../lib/dynbuf.h \
../../lib/strdup.h \
+ ../../lib/curl_get_line.h \
../../lib/curl_multibyte.h
USEFUL = \
*
***************************************************************************/
#include "curlcheck.h"
+/* disable the curlx_get_line redefinitions for this unit test */
+#define BUILDING_LIBCURL
#include "curl_get_line.h"
#include "memdebug.h"
$(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 \
$(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