vtls: feature ssls-export for SSL session im-/export
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
This commit is contained in:
parent
8a1ee2b47d
commit
515a21f350
1
.github/scripts/spellcheck.words
vendored
1
.github/scripts/spellcheck.words
vendored
@ -779,6 +779,7 @@ src
|
||||
SRP
|
||||
SRWLOCK
|
||||
SSL
|
||||
SSLS
|
||||
ssl
|
||||
SSLeay
|
||||
SSLKEYLOGFILE
|
||||
|
||||
@ -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)
|
||||
|
||||
26
configure.ac
26
configure.ac
@ -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}
|
||||
])
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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 \
|
||||
|
||||
35
docs/cmdline-opts/ssl-sessions.md
Normal file
35
docs/cmdline-opts/ssl-sessions.md
Normal file
@ -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.
|
||||
@ -10,6 +10,7 @@ Multi: boolean
|
||||
See-also:
|
||||
- tlsv1.3
|
||||
- tls-max
|
||||
- ssl-sessions
|
||||
Example:
|
||||
- --tls-earlydata $URL
|
||||
---
|
||||
|
||||
@ -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 \
|
||||
|
||||
172
docs/libcurl/curl_easy_ssls_export.md
Normal file
172
docs/libcurl/curl_easy_ssls_export.md
Normal file
@ -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.
|
||||
84
docs/libcurl/curl_easy_ssls_import.md
Normal file
84
docs/libcurl/curl_easy_ssls_import.md
Normal file
@ -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.
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -235,6 +235,7 @@
|
||||
--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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -26,6 +26,12 @@
|
||||
|
||||
#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);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
39
lib/easy.c
39
lib/easy.c
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -22,22 +22,6 @@
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
/* 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 */
|
||||
|
||||
@ -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 */
|
||||
|
||||
|
||||
345
lib/vtls/vtls_spack.c
Normal file
345
lib/vtls/vtls_spack.c
Normal file
@ -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 */
|
||||
43
lib/vtls/vtls_spack.h
Normal file
43
lib/vtls/vtls_spack.h
Normal file
@ -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 */
|
||||
@ -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
|
||||
])
|
||||
])
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -32,12 +32,14 @@
|
||||
# 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 \
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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) {
|
||||
|
||||
222
src/tool_ssls.c
Normal file
222
src/tool_ssls.c
Normal file
@ -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;
|
||||
}
|
||||
37
src/tool_ssls.h
Normal file
37
src/tool_ssls.h
Normal file
@ -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 */
|
||||
@ -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
|
||||
|
||||
@ -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()}'
|
||||
|
||||
@ -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 = \
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user