lib: TLS session ticket caching reworked

Described in detail in internal doc TLS-SESSIONS.md

Main points:
- use a new `ssl_peer_key` for cache lookups by connection filters
- recognize differences between TLSv1.3 and other tickets
  * TLSv1.3 tickets are single-use, cache can hold several of them for a peer
  * TLSv1.2 are reused, keep only a single one per peer
- differentiate between ticket BLOB to store (that could be persisted) and object instances
- use put/take/return pattern for cache access
- remember TLS version, ALPN protocol, time received and lifetime of ticket
- auto-expire tickets after their lifetime

Closes #15774
This commit is contained in:
Stefan Eissing 2024-12-18 13:22:35 +01:00 committed by Daniel Stenberg
parent e5e2e09a75
commit fa0ccd9f1f
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
36 changed files with 1784 additions and 780 deletions

View File

@ -57,6 +57,7 @@ INTERNALDOCS = \
internals/README.md \
internals/SPLAY.md \
internals/STRPARSE.md \
internals/TLS-SESSIONS.md \
internals/WEBSOCKET.md
EXTRA_DIST = \

View File

@ -132,3 +132,14 @@ CURLcode Curl_dyn_setlen(struct dynbuf *s, size_t len);
Sets the new shorter length of the buffer in number of bytes. Keeps the
leftmost set number of bytes, discards the rest. To instead keep the tail part
of the buffer, see `Curl_dyn_tail()`.
## `Curl_dyn_take`
```c
char *Curl_dyn_take(struct dynbuf *s, size_t *plen);
```
Transfers ownership of the internal buffer to the caller. The dynbuf
resets to its initial state. The returned pointer may be `NULL` if the
dynbuf never allocated memory. The returned length is the amount of
data written to the buffer. The actual allocated memory might be larger.

View File

@ -64,7 +64,13 @@ See also `Curl_llist_insert_next`.
## Remove a node
Remove a node again from a list by calling `Curl_llist_remove()`.
Remove a node again from a list by calling `Curl_llist_remove()`. This
will destroy the node's `elem` (e.g. calling a registered free function).
To remove a node without destroying it's `elem`, use
`Curl_node_take_elem()` which returns the `elem` pointer and
removes the node from the list. The caller then owns this pointer
and has to take care of it.
## Iterate

View File

@ -0,0 +1,168 @@
<!--
Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
SPDX-License-Identifier: curl
-->
# TLS Sessions and Tickets
The TLS protocol offers methods of "resuming" a previous "session". A
TLS "session" is a negotiated security context across a connection
(which may be via TCP or UDP or other transports.)
By "resuming", the TLS protocol means that the security context from
before can be fully or partially resurrected when the TLS client presents
the proper crypto stuff to the server. This saves on the amount of
TLS packets that need to be sent back and forth, reducing amount
of data and even latency. In the case of QUIC, resumption may send
application data without having seen any reply from the server, hence
this is named 0-RTT data.
The exact mechanism of session tickets in TLSv1.2 (and earlier) and
TLSv1.3 differs. TLSv1.2 tickets have several weaknesses (that can
be exploited by attackers) which TLSv1.3 then fixed. See
[Session Tickets in the real world](https://words.filippo.io/we-need-to-talk-about-session-tickets/)
for an insight into this topic.
These difference between TLS protocol versions are reflected in curl's
handling of session tickets. More below.
## Curl's `ssl_peer_key`
In order to find a ticket from a previous TLS session, curl
needs a name for TLS sessions that uniquely identifies the peer
it talks to.
This name has to reflect also the various TLS parameters that can
be configured in curl for a connection. We do not want to use
a ticket from an different configuration. Example: when setting
the maximum TLS version to 1.2, we do not want to reuse a ticket
we got from a TLSv1.3 session, although we are talking to the
same host.
Internally, we call this name a `ssl_peer_key`. It is a printable
string that carries hostname and port and any non-default TLS
parameters involved in the connection.
Examples:
- `curl.se:443:CA-/etc/ssl/cert.pem:IMPL-GnuTLS/3.8.7` is a peer key for
a connection to `curl.se:443` using `/etc/ssl/cert.pem` as CA
trust anchors and GnuTLS/3.8.7 as TLS backend.
- `curl.se:443:TLSVER-6-6:CA-/etc/ssl/cert.pem:IMPL-GnuTLS/3.8.7` is the
same as the previous, except it is configured to use TLSv1.2 as
min and max versions.
Different configurations produce different keys which is just what
curl needs when handling SSL session tickets.
One important thing: peer keys do not contain confidential
information. If you configure a client certificate or SRP authentication
with username/password, these will not be part of the peer key.
However, peer keys carry the hostnames you use curl for. The *do*
leak the privacy of your communication. We recommend to *not* persist
peer keys for this reason.
**Caveat**: The key may contain file names or paths. It does not
reflect the *contents* in the filesystem. If you change `/etc/ssl/cert.pem`
and reuse a previous ticket, curl might trust a server which no
longer has a root certificate in the file.
## Session Cache Access
#### Lookups
When a new connection is being established, each SSL connection filter creates
its own peer_key and calls into the cache. The cache then looks for a ticket
with exactly this peer_key. Peer keys between proxy SSL filters and SSL
filters talking through a tunnel will differ, as they talk to different
peers.
If the connection filter wants to use a client certificate or SRP
authentication, the cache will check those as well. If the cache peer
carries client cert or SRP auth, the connection filter must have
those with the same values (and vice versa).
On a match, the connection filter gets the session ticket and feeds that
to the TLS implementation which, on accepting it, will try to resume it
for a shorter handshake. In addition, the filter gets the ALPN used
before and the amount of 0-RTT data that the server announced to be
willing to accept. The filter can then decide if it wants to attempt
0-RTT or not. (The ALPN is needed to know if the server speaks the
protocol you want to send in 0-RTT. It makes no sense to send HTTP/2
requests to a server that only knows HTTP/1.1.)
#### Updates
When a new TLS session ticket is received by a filter, it adds it to the
cache using its peer_key and SSL configuration. The cache looks for
a matching entry and, should it find one, adds the ticket for this
peer.
### Put, Take and Return
when a filter accesses the session cache, it *takes*
a ticket from the cache, meaning a returned ticket is removed. The filter
then configures its TLS backend and *returns* the ticket to the cache.
The cache needs to treat tickets from TLSv1.2 and 1.3 differently.
1.2 tickets should be reused, but 1.3 tickets SHOULD NOT (RFC 8446).
The session cache will simply drop 1.3 tickets when they are returned
after use, but keep a 1.2 ticket.
When a ticket is *put* into the cache, there is also a difference. There
can be several 1.3 tickets at the same time, but only a single 1.2 ticket.
TLSv1.2 tickets replace any other. 1.3 tickets accumulate up to a max
amount.
By having a "put/take/return" we reflect the 1.3 use case nicely. Two
concurrent connections will not reuse the same ticket.
## Session Ticket Persistence
#### Privacy and Security
As mentioned above, ssl peer keys are not intended for storage in a
file system. They'll clearly show which hosts the user talked to. This
maybe "just" privacy relevant, but has security implications as an
attacker might find worthy targets among your peer keys.
Also, we do not recommend to persist TLSv1.2 tickets.
### Salted Hashes
The TLS session cache offers an alternative to storing peer keys:
it provides a salted SHA256 hash of the peer key for import and export.
#### Export
The salt is generated randomly for each peer key on export. The
SHA256 makes sure that the peer key cannot be reversed and that
a slightly different key still produces a very different result.
This means an attacker cannot just "grep" a session file for a
particular entry, e.g. if they want to know if you accessed a
specific host. They *can* however compute the SHA256 hashes for
all salts in the file and find a specific entry. But they *cannot*
find a hostname they do not know. They'd have to brute force by
guessing.
#### Import
When session tickets are imported from a file, curl only gets the
salted hashes. The tickets imported will belong to an *unknown*
peer key.
When a connection filter tries to *take* a session ticket, it will
pass its peer key. This peer key will initially not match any
tickets in the cache. The cache then checks all entries with
unknown peer keys if the passed key matches their salted hash. If
it does, the peer key is recovered and remembered at the cache
entry.
This is a performance penalty in the order of "unknown" peer keys
which will diminish over time when keys are rediscovered. Note that
this also works for putting a new ticket into the cache: when no
present entry matches, a new one with peer key is created. This
peer key will then no longer bear the cost of hash computes.

View File

@ -56,6 +56,7 @@ LIB_VTLS_CFILES = \
vtls/schannel_verify.c \
vtls/sectransp.c \
vtls/vtls.c \
vtls/vtls_scache.c \
vtls/wolfssl.c \
vtls/x509asn1.c
@ -74,6 +75,7 @@ LIB_VTLS_HFILES = \
vtls/sectransp.h \
vtls/vtls.h \
vtls/vtls_int.h \
vtls/vtls_scache.h \
vtls/wolfssl.h \
vtls/x509asn1.h

View File

@ -244,6 +244,18 @@ char *Curl_dyn_ptr(const struct dynbuf *s)
return s->bufr;
}
char *Curl_dyn_take(struct dynbuf *s, size_t *plen)
{
char *ptr = s->bufr;
DEBUGASSERT(s);
DEBUGASSERT(s->init == DYNINIT);
*plen = s->leng;
s->bufr = NULL;
s->leng = 0;
s->allc = 0;
return ptr;
}
/*
* Returns an unsigned pointer to the buffer.
*/

View File

@ -39,6 +39,7 @@
#define Curl_dyn_uptr(a) curlx_dyn_uptr(a)
#define Curl_dyn_len(a) curlx_dyn_len(a)
#define Curl_dyn_reset(a) curlx_dyn_reset(a)
#define Curl_dyn_take(a,b) curlx_dyn_take(a,b)
#define Curl_dyn_tail(a,b) curlx_dyn_tail(a,b)
#define Curl_dyn_setlen(a,b) curlx_dyn_setlen(a,b)
#define curlx_dynbuf dynbuf /* for the struct name */
@ -75,6 +76,10 @@ size_t Curl_dyn_len(const struct dynbuf *s);
/* The implementation of this function exists in mprintf.c */
int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save);
/* Take the buffer out of the dynbuf. Caller has ownership and
* dynbuf resets to initial state. */
char *Curl_dyn_take(struct dynbuf *s, size_t *plen);
/* Dynamic buffer max sizes */
#define DYN_DOH_RESPONSE 3000
#define DYN_DOH_CNAME 256

View File

@ -134,16 +134,12 @@ Curl_llist_append(struct Curl_llist *list, const void *p,
Curl_llist_insert_next(list, list->_tail, p, ne);
}
/*
* @unittest: 1300
*/
void
Curl_node_uremove(struct Curl_llist_node *e, void *user)
void *Curl_node_take_elem(struct Curl_llist_node *e)
{
void *ptr;
struct Curl_llist *list;
if(!e)
return;
return NULL;
list = e->_list;
DEBUGASSERT(list);
@ -179,8 +175,23 @@ Curl_node_uremove(struct Curl_llist_node *e, void *user)
#endif
--list->_size;
return ptr;
}
/* call the dtor() last for when it actually frees the 'e' memory itself */
/*
* @unittest: 1300
*/
void
Curl_node_uremove(struct Curl_llist_node *e, void *user)
{
struct Curl_llist *list;
void *ptr;
if(!e)
return;
list = e->_list;
DEBUGASSERT(list);
ptr = Curl_node_take_elem(e);
if(list->_dtor)
list->_dtor(user, ptr);
}

View File

@ -75,6 +75,10 @@ size_t Curl_llist_count(struct Curl_llist *list);
/* Curl_node_elem() returns the custom data from a Curl_llist_node */
void *Curl_node_elem(struct Curl_llist_node *n);
/* Remove the node from the list and return the custom data
* from a Curl_llist_node. Will NOT incoke a registered `dtor`. */
void *Curl_node_take_elem(struct Curl_llist_node *);
/* Curl_node_next() returns the next element in a list from a given
Curl_llist_node */
struct Curl_llist_node *Curl_node_next(struct Curl_llist_node *n);

View File

@ -1588,8 +1588,8 @@ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option,
data->hsts = NULL;
#endif
#ifdef USE_SSL
if(data->share->sslsession == data->state.session)
data->state.session = NULL;
if(data->share->ssl_scache == data->state.ssl_scache)
data->state.ssl_scache = NULL;
#endif
#ifdef USE_LIBPSL
if(data->psl == &data->share->psl)
@ -1632,10 +1632,8 @@ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option,
}
#endif
#ifdef USE_SSL
if(data->share->sslsession) {
data->set.general_ssl.max_ssl_sessions = data->share->max_ssl_sessions;
data->state.session = data->share->sslsession;
}
if(data->share->ssl_scache)
data->state.ssl_scache = data->share->ssl_scache;
#endif
#ifdef USE_LIBPSL
if(data->share->specifier & (1 << CURL_LOCK_DATA_PSL))

View File

@ -30,6 +30,7 @@
#include "share.h"
#include "psl.h"
#include "vtls/vtls.h"
#include "vtls/vtls_scache.h"
#include "hsts.h"
#include "url.h"
@ -108,12 +109,8 @@ curl_share_setopt(CURLSH *sh, CURLSHoption option, ...)
case CURL_LOCK_DATA_SSL_SESSION:
#ifdef USE_SSL
if(!share->sslsession) {
share->max_ssl_sessions = 8;
share->sslsession = calloc(share->max_ssl_sessions,
sizeof(struct Curl_ssl_session));
share->sessionage = 0;
if(!share->sslsession)
if(!share->ssl_scache) {
if(Curl_ssl_scache_create(8, 2, &share->ssl_scache))
res = CURLSHE_NOMEM;
}
#else
@ -174,7 +171,10 @@ curl_share_setopt(CURLSH *sh, CURLSHoption option, ...)
case CURL_LOCK_DATA_SSL_SESSION:
#ifdef USE_SSL
Curl_safefree(share->sslsession);
if(share->ssl_scache) {
Curl_ssl_scache_destroy(share->ssl_scache);
share->ssl_scache = NULL;
}
#else
res = CURLSHE_NOT_BUILT_IN;
#endif
@ -245,11 +245,9 @@ curl_share_cleanup(CURLSH *sh)
#endif
#ifdef USE_SSL
if(share->sslsession) {
size_t i;
for(i = 0; i < share->max_ssl_sessions; i++)
Curl_ssl_kill_session(&(share->sslsession[i]));
free(share->sslsession);
if(share->ssl_scache) {
Curl_ssl_scache_destroy(share->ssl_scache);
share->ssl_scache = NULL;
}
#endif

View File

@ -31,6 +31,8 @@
#include "urldata.h"
#include "conncache.h"
struct Curl_ssl_scache;
#define CURL_GOOD_SHARE 0x7e117a1e
#define GOOD_SHARE_HANDLE(x) ((x) && (x)->magic == CURL_GOOD_SHARE)
@ -58,9 +60,7 @@ struct Curl_share {
struct hsts *hsts;
#endif
#ifdef USE_SSL
struct Curl_ssl_session *sslsession;
size_t max_ssl_sessions;
long sessionage;
struct Curl_ssl_scache *ssl_scache;
#endif
};
@ -68,4 +68,9 @@ CURLSHcode Curl_share_lock(struct Curl_easy *, curl_lock_data,
curl_lock_access);
CURLSHcode Curl_share_unlock(struct Curl_easy *, curl_lock_data);
/* convenience macro to check if this handle is using a shared SSL spool */
#define CURL_SHARE_ssl_scache(data) (data->share && \
(data->share->specifier & \
(1<<CURL_LOCK_DATA_SSL_SESSION)))
#endif /* HEADER_CURL_SHARE_H */

View File

@ -72,6 +72,7 @@
#include "url.h"
#include "getinfo.h"
#include "vtls/vtls.h"
#include "vtls/vtls_scache.h"
#include "vquic/vquic.h"
#include "select.h"
#include "multiif.h"
@ -538,7 +539,7 @@ void Curl_init_CONNECT(struct Curl_easy *data)
*/
CURLcode Curl_pretransfer(struct Curl_easy *data)
{
CURLcode result;
CURLcode result = CURLE_OK;
if(!data->state.url && !data->set.uh) {
/* we cannot do anything without URL */
@ -577,12 +578,14 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
data->state.httpreq = data->set.method;
data->state.url = data->set.str[STRING_SET_URL];
/* Init the SSL session ID cache here. We do it here since we want to do it
after the *_setopt() calls (that could specify the size of the cache) but
before any transfer takes place. */
result = Curl_ssl_initsessions(data, data->set.general_ssl.max_ssl_sessions);
if(result)
return result;
#ifdef USE_SSL
if(!data->state.ssl_scache) {
result = Curl_ssl_scache_create(data->set.general_ssl.max_ssl_sessions,
2, &data->state.ssl_scache);
if(result)
return result;
}
#endif
data->state.requests = 0;
data->state.followlocation = 0; /* reset the location-follow counter */

View File

@ -271,21 +271,7 @@ enum protection_level {
/* SSL backend-specific data; declared differently by each SSL backend */
struct ssl_backend_data;
typedef enum {
CURL_SSL_PEER_DNS,
CURL_SSL_PEER_IPV4,
CURL_SSL_PEER_IPV6
} ssl_peer_type;
struct ssl_peer {
char *hostname; /* hostname for verification */
char *dispname; /* display version of hostname */
char *sni; /* SNI version of hostname or NULL if not usable */
ssl_peer_type type; /* type of the peer information */
int port; /* port we are talking to */
int transport; /* one of TRNSPRT_* defines */
};
struct Curl_ssl_scache_entry;
struct ssl_primary_config {
char *CApath; /* certificate dir (does not work on Windows) */
@ -341,24 +327,6 @@ struct ssl_general_config {
int ca_cache_timeout; /* Certificate store cache timeout (seconds) */
};
typedef void Curl_ssl_sessionid_dtor(void *sessionid, size_t idsize);
/* information stored about one single SSL session */
struct Curl_ssl_session {
char *name; /* hostname for which this ID was used */
char *conn_to_host; /* hostname for the connection (may be NULL) */
const char *scheme; /* protocol scheme used */
char *alpn; /* APLN TLS negotiated protocol string */
void *sessionid; /* as returned from the SSL layer */
size_t idsize; /* if known, otherwise 0 */
Curl_ssl_sessionid_dtor *sessionid_free; /* free `sessionid` callback */
long age; /* just a number, the higher the more recent */
int remote_port; /* remote port */
int conn_to_port; /* remote port for the connection (may be -1) */
int transport; /* TCP or QUIC */
struct ssl_primary_config ssl_config; /* setup for this session */
};
#ifdef USE_WINDOWS_SSPI
#include "curl_sspi.h"
#endif
@ -1232,8 +1200,7 @@ struct UrlState {
curl_prot_t first_remote_protocol;
int retrycount; /* number of retries on a new connection */
struct Curl_ssl_session *session; /* array of 'max_ssl_sessions' size */
long sessionage; /* number of the most recent session */
struct Curl_ssl_scache *ssl_scache; /* TLS session pool */
int os_errno; /* filled in with errno whenever an error occurs */
long followlocation; /* redirect counter */
int requests; /* request counter: redirects + authentication retakes */

View File

@ -2131,7 +2131,8 @@ static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
ctx = cf ? cf->ctx : NULL;
data = cf ? CF_DATA_CURRENT(cf) : NULL;
if(cf && data && ctx) {
Curl_ossl_add_session(cf, data, &ctx->peer, ssl_sessionid);
Curl_ossl_add_session(cf, data, ctx->peer.scache_key, ssl_sessionid,
SSL_version(ssl), "h3");
return 1;
}
return 0;
@ -2158,7 +2159,8 @@ static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
}
switch(htype) {
case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: {
(void)Curl_gtls_update_session_id(cf, data, session, &ctx->peer, "h3");
(void)Curl_gtls_cache_session(cf, data, ctx->peer.scache_key,
session, -1, "h3");
break;
}
default:
@ -2181,7 +2183,8 @@ static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session)
struct Curl_easy *data = CF_DATA_CURRENT(cf);
DEBUGASSERT(data);
if(data && ctx) {
(void)wssl_cache_session(cf, data, &ctx->peer, session);
(void)Curl_wssl_cache_session(cf, data, ctx->peer.scache_key,
session, wolfSSL_version(ssl), "h3");
}
}
return 0;
@ -2258,10 +2261,6 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
int qfd;
DEBUGASSERT(ctx->initialized);
result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC);
if(result)
return result;
#define H3_ALPN "\x2h3\x5h3-29"
result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer,
H3_ALPN, sizeof(H3_ALPN) - 1,

View File

@ -1162,9 +1162,6 @@ static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf,
BIO_ADDR *baddr = NULL;
DEBUGASSERT(ctx->initialized);
result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC);
if(result)
goto out;
#define H3_ALPN "\x2h3"
result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer,

View File

@ -1278,10 +1278,6 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf,
if(result)
return result;
result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC);
if(result)
return result;
ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
if(!ctx->cfg) {
failf(data, "cannot create quiche config");

View File

@ -50,6 +50,7 @@
#include "multiif.h"
#include "vtls/keylog.h"
#include "vtls/vtls.h"
#include "vtls/vtls_scache.h"
#include "vquic-tls.h"
/* The last 3 #include files should be in this order */
@ -221,7 +222,7 @@ static CURLcode wssl_init_ssl(struct curl_tls_ctx *ctx,
}
if(ssl_config->primary.cache_session) {
(void)wssl_setup_session(cf, data, &ctx->wssl, peer);
(void)Curl_wssl_setup_session(cf, data, &ctx->wssl, peer->scache_key);
}
return CURLE_OK;
@ -236,11 +237,26 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
Curl_vquic_tls_ctx_setup *cb_setup,
void *cb_user_data, void *ssl_user_data)
{
char tls_id[80];
CURLcode result;
#ifdef USE_OPENSSL
Curl_ossl_version(tls_id, sizeof(tls_id));
#elif defined(USE_GNUTLS)
Curl_gtls_version(tls_id, sizeof(tls_id));
#elif defined(USE_WOLFSSL)
Curl_wssl_version(tls_id, sizeof(tls_id));
#else
#error "no TLS lib in used, should not happen"
return CURLE_FAILED_INIT;
#endif
result = Curl_ssl_peer_init(peer, cf, tls_id, TRNSPRT_QUIC);
if(result)
return result;
#ifdef USE_OPENSSL
(void)result;
return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer, TRNSPRT_QUIC,
return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer,
(const unsigned char *)alpn, alpn_len,
cb_setup, cb_user_data, NULL, ssl_user_data);
#elif defined(USE_GNUTLS)
@ -346,6 +362,9 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx,
}
#endif
/* on error, remove any session we might have in the pool */
if(result)
Curl_ssl_scache_remove_all(cf, data, peer->scache_key);
return result;
}

View File

@ -26,6 +26,7 @@
#include "curl_setup.h"
#include "bufq.h"
#include "vtls/vtls.h"
#include "vtls/openssl.h"
#if defined(USE_HTTP3) && \
@ -33,6 +34,8 @@
#include "vtls/wolfssl.h"
struct ssl_peer;
struct curl_tls_ctx {
#ifdef USE_OPENSSL
struct ossl_ctx ossl;

View File

@ -34,6 +34,7 @@
#include "inet_pton.h"
#include "vtls.h"
#include "vtls_int.h"
#include "vtls_scache.h"
#include "connect.h"
#include "select.h"
#include "multiif.h"
@ -609,20 +610,19 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf,
br_ssl_engine_set_x509(&backend->ctx.eng, &backend->x509.vtable);
if(ssl_config->primary.cache_session) {
void *sdata;
size_t slen;
struct Curl_ssl_session *sc_session = NULL;
const br_ssl_session_parameters *session;
CURL_TRC_CF(data, cf, "connect_step1, check session cache");
Curl_ssl_sessionid_lock(data);
if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, &sdata, &slen, NULL) &&
slen == sizeof(*session)) {
session = sdata;
ret = Curl_ssl_scache_take(cf, data, connssl->peer.scache_key,
&sc_session);
if(!ret && sc_session && sc_session->sdata && sc_session->sdata_len) {
session = (br_ssl_session_parameters *)(void *)sc_session->sdata;
br_ssl_engine_set_session_parameters(&backend->ctx.eng, session);
session_set = 1;
infof(data, "BearSSL: reusing session ID");
/* single use of sessions */
Curl_ssl_scache_return(cf, data, connssl->peer.scache_key, sc_session);
}
Curl_ssl_sessionid_unlock(data);
}
if(connssl->alpn) {
@ -804,12 +804,6 @@ static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf,
return ret;
}
static void bearssl_session_free(void *sessionid, size_t idsize)
{
(void)idsize;
free(sessionid);
}
static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
@ -832,17 +826,22 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
}
if(ssl_config->primary.cache_session) {
struct Curl_ssl_session *sc_session;
br_ssl_session_parameters *session;
session = malloc(sizeof(*session));
if(!session)
return CURLE_OUT_OF_MEMORY;
br_ssl_engine_get_session_parameters(&backend->ctx.eng, session);
Curl_ssl_sessionid_lock(data);
ret = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL,
session, sizeof(*session),
bearssl_session_free);
Curl_ssl_sessionid_unlock(data);
ret = Curl_ssl_session_create((unsigned char *)session, sizeof(*session),
(int)session->version,
connssl->negotiated.alpn,
0, -1, &sc_session);
if(!ret) {
ret = Curl_ssl_scache_put(cf, data, connssl->peer.scache_key,
sc_session);
/* took ownership of `sc_session` */
}
if(ret)
return ret;
}

View File

@ -47,6 +47,7 @@
#include "gtls.h"
#include "vtls.h"
#include "vtls_int.h"
#include "vtls_scache.h"
#include "vauth/vauth.h"
#include "parsedate.h"
#include "connect.h" /* for the connect timeout */
@ -714,21 +715,17 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf,
return CURLE_OK;
}
static void gtls_sessionid_free(void *sessionid, size_t idsize)
{
(void)idsize;
free(sessionid);
}
CURLcode Curl_gtls_update_session_id(struct Curl_cfilter *cf,
struct Curl_easy *data,
gnutls_session_t session,
struct ssl_peer *peer,
const char *alpn)
CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
gnutls_session_t session,
int lifetime_secs,
const char *alpn)
{
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
void *connect_sessionid;
size_t connect_idsize = 0;
struct Curl_ssl_session *sc_session;
unsigned char *sdata;
size_t sdata_len = 0;
CURLcode result = CURLE_OK;
if(!ssl_config->primary.cache_session)
@ -740,35 +737,57 @@ CURLcode Curl_gtls_update_session_id(struct Curl_cfilter *cf,
detect that. */
/* get the session ID data size */
gnutls_session_get_data(session, NULL, &connect_idsize);
if(!connect_idsize) /* gnutls does this for some version combinations */
gnutls_session_get_data(session, NULL, &sdata_len);
if(!sdata_len) /* gnutls does this for some version combinations */
return CURLE_OK;
connect_sessionid = malloc(connect_idsize); /* get a buffer for it */
if(!connect_sessionid)
sdata = malloc(sdata_len); /* get a buffer for it */
if(!sdata)
return CURLE_OUT_OF_MEMORY;
/* extract session ID to the allocated buffer */
gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
gnutls_session_get_data(session, sdata, &sdata_len);
CURL_TRC_CF(data, cf, "get session id (len=%zu, alpn=%s) and store in cache",
connect_idsize, alpn ? alpn : "-");
Curl_ssl_sessionid_lock(data);
/* store this session id, takes ownership */
result = Curl_ssl_set_sessionid(cf, data, peer, alpn,
connect_sessionid, connect_idsize,
gtls_sessionid_free);
Curl_ssl_sessionid_unlock(data);
sdata_len, alpn ? alpn : "-");
result = Curl_ssl_session_create(sdata, sdata_len,
Curl_glts_get_ietf_proto(session),
alpn, 0, lifetime_secs,
&sc_session);
/* call took ownership of `sdata`*/
if(!result) {
result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session);
/* took ownership of `sc_session` */
}
return result;
}
int Curl_glts_get_ietf_proto(gnutls_session_t session)
{
switch(gnutls_protocol_get_version(session)) {
case GNUTLS_SSL3:
return CURL_IETF_PROTO_SSL3;
case GNUTLS_TLS1_0:
return CURL_IETF_PROTO_TLS1;
case GNUTLS_TLS1_1:
return CURL_IETF_PROTO_TLS1_1;
case GNUTLS_TLS1_2:
return CURL_IETF_PROTO_TLS1_2;
case GNUTLS_TLS1_3:
return CURL_IETF_PROTO_TLS1_3;
default:
return CURL_IETF_PROTO_UNKNOWN;
}
}
static CURLcode cf_gtls_update_session_id(struct Curl_cfilter *cf,
struct Curl_easy *data,
gnutls_session_t session)
{
struct ssl_connect_data *connssl = cf->ctx;
return Curl_gtls_update_session_id(cf, data, session, &connssl->peer,
connssl->alpn_negotiated);
return Curl_gtls_cache_session(cf, data, connssl->peer.scache_key,
session, -1,
connssl->negotiated.alpn);
}
static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
@ -1058,9 +1077,11 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
{
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);
struct Curl_ssl_session *scs = NULL;
gnutls_datum_t gtls_alpns[5];
size_t gtls_alpns_count = 0;
CURLcode result;
int rc;
DEBUGASSERT(gctx);
@ -1085,29 +1106,23 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
/* This might be a reconnect, so we check for a session ID in the cache
to speed up things */
if(conn_config->cache_session) {
void *ssl_sessionid;
size_t ssl_idsize;
char *session_alpn;
Curl_ssl_sessionid_lock(data);
if(!Curl_ssl_getsessionid(cf, data, peer,
&ssl_sessionid, &ssl_idsize, &session_alpn)) {
/* we got a session id, use it! */
int rc;
result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs);
if(result)
goto out;
rc = gnutls_session_set_data(gctx->session, ssl_sessionid, ssl_idsize);
if(rc < 0)
infof(data, "SSL failed to set session ID");
if(scs && scs->sdata && scs->sdata_len) {
/* we got a cached session, use it! */
rc = gnutls_session_set_data(gctx->session, scs->sdata, scs->sdata_len);
if(rc < 0) {
infof(data, "SSL session not accepted by GnuTLS, continuing without");
}
else {
infof(data, "SSL reusing session ID (size=%zu, alpn=%s)",
ssl_idsize, session_alpn ? session_alpn : "-");
#ifdef DEBUGBUILD
if((ssl_config->earlydata || !!getenv("CURL_USE_EARLYDATA")) &&
#else
infof(data, "SSL reusing session with ALPN '%s'",
scs->alpn ? scs->alpn : "-");
if(ssl_config->earlydata &&
#endif
!cf->conn->connect_only && connssl &&
(gnutls_protocol_get_version(gctx->session) == GNUTLS_TLS1_3) &&
Curl_alpn_contains_proto(connssl->alpn, session_alpn)) {
Curl_alpn_contains_proto(connssl->alpn, scs->alpn)) {
connssl->earlydata_max =
gnutls_record_get_max_early_data_size(gctx->session);
if((!connssl->earlydata_max ||
@ -1118,24 +1133,32 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
else {
CURL_TRC_CF(data, cf, "TLS session allows %zu earlydata bytes, "
"reusing ALPN '%s'",
connssl->earlydata_max, session_alpn);
connssl->earlydata_max, scs->alpn);
connssl->earlydata_state = ssl_earlydata_use;
connssl->state = ssl_connection_deferred;
result = Curl_alpn_set_negotiated(cf, data, connssl,
(const unsigned char *)session_alpn,
session_alpn ? strlen(session_alpn) : 0);
(const unsigned char *)scs->alpn,
scs->alpn ? strlen(scs->alpn) : 0);
if(result)
return result;
goto out;
/* We only try the ALPN protocol the session used before,
* otherwise we might send early data for the wrong protocol */
gtls_alpns[0].data = (unsigned char *)session_alpn;
gtls_alpns[0].size = (unsigned)strlen(session_alpn);
gtls_alpns_count = 1;
gtls_alpns[0].data = (unsigned char *)scs->alpn;
gtls_alpns[0].size = (unsigned)strlen(scs->alpn);
if(gnutls_alpn_set_protocols(gctx->session,
gtls_alpns, 1,
GNUTLS_ALPN_MANDATORY)) {
failf(data, "failed setting ALPN");
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
/* don't set again below */
gtls_alpns_count = 0;
alpn = NULL;
}
}
}
}
Curl_ssl_sessionid_unlock(data);
}
/* convert the ALPN string from our arguments to a list of strings that
@ -1143,19 +1166,21 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
* to the server. nice. */
if(!gtls_alpns_count && alpn && alpn_len) {
size_t i, alen = alpn_len;
unsigned char *s = (unsigned char *)alpn;
unsigned char *salpn = (unsigned char *)alpn;
unsigned char slen;
for(i = 0; (i < ARRAYSIZE(gtls_alpns)) && alen; ++i) {
slen = s[0];
slen = salpn[0];
if(slen >= alen)
return CURLE_FAILED_INIT;
gtls_alpns[i].data = s + 1;
gtls_alpns[i].data = salpn + 1;
gtls_alpns[i].size = slen;
s += slen + 1;
salpn += slen + 1;
alen -= (size_t)slen + 1;
}
if(alen) /* not all alpn chars used, wrong format or too many */
return CURLE_FAILED_INIT;
if(alen) { /* not all alpn chars used, wrong format or too many */
result = CURLE_FAILED_INIT;
goto out;
}
gtls_alpns_count = i;
}
@ -1164,10 +1189,12 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
gtls_alpns, (unsigned int)gtls_alpns_count,
GNUTLS_ALPN_MANDATORY)) {
failf(data, "failed setting ALPN");
return CURLE_SSL_CONNECT_ERROR;
result = CURLE_SSL_CONNECT_ERROR;
}
return CURLE_OK;
out:
Curl_ssl_scache_return(cf, data, peer->scache_key, scs);
return result;
}
static CURLcode
@ -1197,7 +1224,8 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
}
result = Curl_gtls_ctx_init(&backend->gtls, cf, data, &connssl->peer,
proto.data, proto.len, connssl, NULL, NULL, cf);
proto.data, proto.len,
connssl, NULL, NULL, cf);
if(result)
return result;
@ -2213,7 +2241,7 @@ out:
return ret;
}
static size_t gtls_version(char *buffer, size_t size)
size_t Curl_gtls_version(char *buffer, size_t size)
{
return msnprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL));
}
@ -2268,7 +2296,7 @@ const struct Curl_ssl Curl_ssl_gnutls = {
gtls_init, /* init */
gtls_cleanup, /* cleanup */
gtls_version, /* version */
Curl_gtls_version, /* version */
gtls_shutdown, /* shutdown */
gtls_data_pending, /* data_pending */
gtls_random, /* random */

View File

@ -47,6 +47,8 @@ struct ssl_config_data;
struct ssl_peer;
struct ssl_connect_data;
int Curl_glts_get_ietf_proto(gnutls_session_t session);
struct gtls_shared_creds {
gnutls_certificate_credentials_t creds;
char *CAfile; /* CAfile path used to generate X509 store */
@ -70,6 +72,8 @@ struct gtls_ctx {
BIT(sent_shutdown);
};
size_t Curl_gtls_version(char *buffer, size_t size);
typedef CURLcode Curl_gtls_ctx_setup_cb(struct Curl_cfilter *cf,
struct Curl_easy *data,
void *user_data);
@ -96,11 +100,12 @@ CURLcode Curl_gtls_verifyserver(struct Curl_easy *data,
const char *pinned_key);
/* Extract TLS session and place in cache, if configured. */
CURLcode Curl_gtls_update_session_id(struct Curl_cfilter *cf,
struct Curl_easy *data,
gnutls_session_t session,
struct ssl_peer *peer,
const char *alpn);
CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
gnutls_session_t session,
int lifetime_secs,
const char *alpn);
extern const struct Curl_ssl Curl_ssl_gnutls;

View File

@ -64,6 +64,7 @@
#include "mbedtls.h"
#include "vtls.h"
#include "vtls_int.h"
#include "vtls_scache.h"
#include "x509asn1.h"
#include "parsedate.h"
#include "connect.h" /* for the connect timeout */
@ -875,29 +876,30 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
/* Check if there is a cached ID we can/should use here! */
if(ssl_config->primary.cache_session) {
void *sdata = NULL;
size_t slen = 0;
struct Curl_ssl_session *sc_session = NULL;
CURLcode result;
Curl_ssl_sessionid_lock(data);
if(!Curl_ssl_getsessionid(cf, data, &connssl->peer,
&sdata, &slen, NULL) && slen) {
result = Curl_ssl_scache_take(cf, data, connssl->peer.scache_key,
&sc_session);
if(!result && sc_session && sc_session->sdata && sc_session->sdata_len) {
mbedtls_ssl_session session;
mbedtls_ssl_session_init(&session);
ret = mbedtls_ssl_session_load(&session, sdata, slen);
ret = mbedtls_ssl_session_load(&session, sc_session->sdata,
sc_session->sdata_len);
if(ret) {
failf(data, "error loading cached session: -0x%x", -ret);
failf(data, "SSL session error loading: -0x%x", -ret);
}
else {
ret = mbedtls_ssl_set_session(&backend->ssl, &session);
if(ret)
failf(data, "error setting session: -0x%x", -ret);
failf(data, "SSL session error setting: -0x%x", -ret);
else
infof(data, "SSL reusing session ID");
}
mbedtls_ssl_session_free(&session);
}
Curl_ssl_sessionid_unlock(data);
Curl_ssl_scache_return(cf, data, connssl->peer.scache_key, sc_session);
}
mbedtls_ssl_conf_ca_chain(&backend->config,
@ -1115,12 +1117,6 @@ pinnedpubkey_error:
return CURLE_OK;
}
static void mbedtls_session_free(void *session, size_t slen)
{
(void)slen;
free(session);
}
static CURLcode
mbed_new_session(struct Curl_cfilter *cf, struct Curl_easy *data)
{
@ -1128,48 +1124,64 @@ mbed_new_session(struct Curl_cfilter *cf, struct Curl_easy *data)
struct mbed_ssl_backend_data *backend =
(struct mbed_ssl_backend_data *)connssl->backend;
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
mbedtls_ssl_session session;
bool msession_alloced = FALSE;
struct Curl_ssl_session *sc_session = NULL;
unsigned char *sdata = NULL;
size_t slen = 0;
int ietf_tls_id;
CURLcode result = CURLE_OK;
int ret;
DEBUGASSERT(backend);
if(ssl_config->primary.cache_session) {
int ret;
mbedtls_ssl_session session;
unsigned char *sdata = NULL;
size_t slen = 0;
if(!ssl_config->primary.cache_session)
return CURLE_OK;
mbedtls_ssl_session_init(&session);
ret = mbedtls_ssl_get_session(&backend->ssl, &session);
if(ret) {
if(ret != MBEDTLS_ERR_SSL_ALLOC_FAILED)
mbedtls_ssl_session_free(&session);
failf(data, "mbedtls_ssl_get_session returned -0x%x", -ret);
return CURLE_SSL_CONNECT_ERROR;
}
mbedtls_ssl_session_save(&session, NULL, 0, &slen);
if(!slen) {
failf(data, "failed to serialize session: length is 0");
}
else {
sdata = malloc(slen);
if(sdata) {
ret = mbedtls_ssl_session_save(&session, sdata, slen, &slen);
if(ret) {
failf(data, "failed to serialize session: -0x%x", -ret);
}
else {
Curl_ssl_sessionid_lock(data);
result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL,
sdata, slen, mbedtls_session_free);
Curl_ssl_sessionid_unlock(data);
if(!result)
sdata = NULL;
}
}
}
mbedtls_ssl_session_free(&session);
free(sdata);
mbedtls_ssl_session_init(&session);
ret = mbedtls_ssl_get_session(&backend->ssl, &session);
msession_alloced = (ret != MBEDTLS_ERR_SSL_ALLOC_FAILED);
if(ret) {
failf(data, "mbedtls_ssl_get_session returned -0x%x", -ret);
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
mbedtls_ssl_session_save(&session, NULL, 0, &slen);
if(!slen) {
failf(data, "failed to serialize session: length is 0");
goto out;
}
sdata = malloc(slen);
if(!sdata) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
ret = mbedtls_ssl_session_save(&session, sdata, slen, &slen);
if(ret) {
failf(data, "failed to serialize session: -0x%x", -ret);
goto out;
}
#if MBEDTLS_VERSION_NUMBER >= 0x03020000
ietf_tls_id = mbedtls_ssl_get_version_number(&backend->ssl);
#else
ietf_tls_id = CURL_IETF_PROTO_UNKNOWN;
#endif
result = Curl_ssl_session_create(sdata, slen,
ietf_tls_id,
connssl->negotiated.alpn, 0, -1,
&sc_session);
sdata = NULL; /* call took ownership */
if(!result)
result = Curl_ssl_scache_put(cf, data, connssl->peer.scache_key,
sc_session);
out:
if(msession_alloced)
mbedtls_ssl_session_free(&session);
free(sdata);
return result;
}

View File

@ -56,6 +56,7 @@
#include "select.h"
#include "vtls.h"
#include "vtls_int.h"
#include "vtls_scache.h"
#include "vauth/vauth.h"
#include "keylog.h"
#include "strcase.h"
@ -960,8 +961,6 @@ static const char *SSL_ERROR_to_str(int err)
}
}
static size_t ossl_version(char *buffer, size_t size);
/* Return error string for last OpenSSL error
*/
static char *ossl_strerror(unsigned long error, char *buf, size_t size)
@ -970,7 +969,7 @@ static char *ossl_strerror(unsigned long error, char *buf, size_t size)
DEBUGASSERT(size);
*buf = '\0';
len = ossl_version(buf, size);
len = Curl_ossl_version(buf, size);
DEBUGASSERT(len < (size - 2));
if(len < (size - 2)) {
buf += len;
@ -2013,13 +2012,6 @@ static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
}
}
static void ossl_session_free(void *sessionid, size_t idsize)
{
/* free the ID */
(void)idsize;
free(sessionid);
}
/*
* This function is called when the 'data' struct is going away. Close
* down everything and free all resources!
@ -2873,20 +2865,23 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options,
CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
const struct ssl_peer *peer,
SSL_SESSION *session)
const char *ssl_peer_key,
SSL_SESSION *session,
int ietf_tls_id,
const char *alpn)
{
const struct ssl_config_data *config;
unsigned char *der_session_buf = NULL;
CURLcode result = CURLE_OK;
size_t der_session_size;
unsigned char *der_session_buf;
unsigned char *der_session_ptr;
if(!cf || !data)
goto out;
config = Curl_ssl_cf_get_config(cf, data);
if(config->primary.cache_session) {
struct Curl_ssl_session *sc_session = NULL;
size_t der_session_size;
unsigned char *der_session_ptr;
der_session_size = i2d_SSL_SESSION(session, NULL);
if(der_session_size == 0) {
@ -2903,17 +2898,22 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
der_session_size = i2d_SSL_SESSION(session, &der_session_ptr);
if(der_session_size == 0) {
result = CURLE_OUT_OF_MEMORY;
free(der_session_buf);
goto out;
}
Curl_ssl_sessionid_lock(data);
result = Curl_ssl_set_sessionid(cf, data, peer, NULL, der_session_buf,
der_session_size, ossl_session_free);
Curl_ssl_sessionid_unlock(data);
result = Curl_ssl_session_create(der_session_buf, der_session_size,
ietf_tls_id, alpn, 0,
SSL_SESSION_get_timeout(session),
&sc_session);
der_session_buf = NULL; /* took ownership of sdata */
if(!result) {
result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session);
/* took ownership of `sc_session` */
}
}
out:
free(der_session_buf);
return result;
}
@ -2929,7 +2929,9 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
cf = (struct Curl_cfilter*) SSL_get_app_data(ssl);
connssl = cf ? cf->ctx : NULL;
data = connssl ? CF_DATA_CURRENT(cf) : NULL;
Curl_ossl_add_session(cf, data, &connssl->peer, ssl_sessionid);
if(data && connssl)
Curl_ossl_add_session(cf, data, connssl->peer.scache_key, ssl_sessionid,
SSL_version(ssl), connssl->negotiated.alpn);
return 0;
}
@ -3468,7 +3470,6 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
int transport, /* TCP or QUIC */
const unsigned char *alpn, size_t alpn_len,
Curl_ossl_ctx_setup_cb *cb_setup,
void *cb_user_data,
@ -3479,9 +3480,6 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
const char *ciphers;
SSL_METHOD_QUAL SSL_METHOD *req_method = NULL;
ctx_option_t ctx_options = 0;
SSL_SESSION *ssl_session = NULL;
const unsigned char *der_sessionid = NULL;
size_t der_sessionid_size = 0;
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);
const long int ssl_version_min = conn_config->version;
@ -3498,7 +3496,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
ssl_config->certverifyresult = !X509_V_OK;
switch(transport) {
switch(peer->transport) {
case TRNSPRT_TCP:
/* check to see if we have been told to use an explicit SSL/TLS version */
switch(ssl_version_min) {
@ -3542,7 +3540,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
#endif
break;
default:
failf(data, "unsupported transport %d in SSL init", transport);
failf(data, "unsupported transport %d in SSL init", peer->transport);
return CURLE_SSL_CONNECT_ERROR;
}
@ -3965,32 +3963,36 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
octx->reused_session = FALSE;
if(ssl_config->primary.cache_session) {
Curl_ssl_sessionid_lock(data);
if(!Curl_ssl_getsessionid(cf, data, peer, (void **)&der_sessionid,
&der_sessionid_size, NULL)) {
/* we got a session id, use it! */
struct Curl_ssl_session *sc_session = NULL;
result = Curl_ssl_scache_take(cf, data, peer->scache_key, &sc_session);
if(!result && sc_session && sc_session->sdata && sc_session->sdata_len) {
const unsigned char *der_sessionid = sc_session->sdata;
size_t der_sessionid_size = sc_session->sdata_len;
SSL_SESSION *ssl_session = NULL;
/* If OpenSSL does not accept the session from the cache, this
* is not an error. We just continue without it. */
ssl_session = d2i_SSL_SESSION(NULL, &der_sessionid,
(long)der_sessionid_size);
(long)der_sessionid_size);
if(ssl_session) {
if(!SSL_set_session(octx->ssl, ssl_session)) {
Curl_ssl_sessionid_unlock(data);
SSL_SESSION_free(ssl_session);
failf(data, "SSL: SSL_set_session failed: %s",
infof(data, "SSL: SSL_set_session not accepted, "
"continuing without: %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
return CURLE_SSL_CONNECT_ERROR;
}
else {
infof(data, "SSL reusing session");
octx->reused_session = TRUE;
}
SSL_SESSION_free(ssl_session);
/* Informational message */
infof(data, "SSL reusing session ID");
octx->reused_session = TRUE;
}
else {
Curl_ssl_sessionid_unlock(data);
return CURLE_SSL_CONNECT_ERROR;
infof(data, "SSL session not accepted by OpenSSL, continuing without");
}
}
Curl_ssl_sessionid_unlock(data);
Curl_ssl_scache_return(cf, data, peer->scache_key, sc_session);
}
return CURLE_OK;
@ -4018,7 +4020,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
}
#endif
result = Curl_ossl_ctx_init(octx, cf, data, &connssl->peer, TRNSPRT_TCP,
result = Curl_ossl_ctx_init(octx, cf, data, &connssl->peer,
proto.data, proto.len, NULL, NULL,
ossl_new_session_cb, cf);
if(result)
@ -4693,21 +4695,6 @@ CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf,
/* do not do this after Session ID reuse */
result = verifystatus(cf, data, octx);
if(result) {
/* when verifystatus failed, remove the session id from the cache again
if present */
if(!Curl_ssl_cf_is_proxy(cf)) {
void *old_ssl_sessionid = NULL;
bool incache;
Curl_ssl_sessionid_lock(data);
incache = !(Curl_ssl_getsessionid(cf, data, peer,
&old_ssl_sessionid, NULL, NULL));
if(incache) {
infof(data, "Remove session ID again from cache");
Curl_ssl_delsessionid(data, old_ssl_sessionid);
}
Curl_ssl_sessionid_unlock(data);
}
X509_free(octx->server_cert);
octx->server_cert = NULL;
return result;
@ -4757,6 +4744,9 @@ static CURLcode ossl_connect_step3(struct Curl_cfilter *cf,
result = Curl_oss_check_peer_cert(cf, data, octx, &connssl->peer);
if(!result)
connssl->connecting_state = ssl_connect_done;
else
/* on error, remove sessions we might have in the pool */
Curl_ssl_scache_remove_all(cf, data, connssl->peer.scache_key);
return result;
}
@ -5172,7 +5162,7 @@ static CURLcode ossl_get_channel_binding(struct Curl_easy *data, int sockindex,
#endif
}
static size_t ossl_version(char *buffer, size_t size)
size_t Curl_ossl_version(char *buffer, size_t size)
{
#ifdef LIBRESSL_VERSION_NUMBER
#ifdef HAVE_OPENSSL_VERSION
@ -5336,7 +5326,7 @@ const struct Curl_ssl Curl_ssl_openssl = {
ossl_init, /* init */
ossl_cleanup, /* cleanup */
ossl_version, /* version */
Curl_ossl_version, /* version */
ossl_shutdown, /* shutdown */
ossl_data_pending, /* data_pending */
ossl_random, /* random */

View File

@ -36,6 +36,8 @@
#include "urldata.h"
struct ssl_peer;
/* Struct to hold a Curl OpenSSL instance */
struct ossl_ctx {
/* these ones requires specific SSL-types */
@ -53,6 +55,8 @@ struct ossl_ctx {
BIT(reused_session); /* session-ID was reused for this */
};
size_t Curl_ossl_version(char *buffer, size_t size);
typedef CURLcode Curl_ossl_ctx_setup_cb(struct Curl_cfilter *cf,
struct Curl_easy *data,
void *user_data);
@ -63,7 +67,6 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
int transport, /* TCP or QUIC */
const unsigned char *alpn, size_t alpn_len,
Curl_ossl_ctx_setup_cb *cb_setup,
void *cb_user_data,
@ -94,8 +97,10 @@ CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf,
*/
CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
const struct ssl_peer *peer,
SSL_SESSION *ssl_sessionid);
const char *ssl_peer_key,
SSL_SESSION *ssl_sessionid,
int ietf_tls_id,
const char *alpn);
/*
* Get the server cert, verify it and show it, etc., only call failf() if

View File

@ -41,6 +41,7 @@
#include "schannel_int.h"
#include "vtls.h"
#include "vtls_int.h"
#include "vtls_scache.h"
#include "strcase.h"
#include "sendf.h"
#include "connect.h" /* for the connect timeout */
@ -954,9 +955,9 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
/* check for an existing reusable credential handle */
if(ssl_config->primary.cache_session) {
Curl_ssl_sessionid_lock(data);
if(!Curl_ssl_getsessionid(cf, data, &connssl->peer,
(void **)&old_cred, NULL, NULL)) {
Curl_ssl_scache_lock(data);
if(Curl_ssl_scache_get_obj(cf, data, connssl->peer.scache_key,
(void **)&old_cred)) {
backend->cred = old_cred;
DEBUGF(infof(data, "schannel: reusing existing credential handle"));
@ -966,7 +967,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
"schannel: incremented credential handle refcount = %d",
backend->cred->refcount));
}
Curl_ssl_sessionid_unlock(data);
Curl_ssl_scache_unlock(data);
}
if(!backend->cred) {
@ -1501,12 +1502,11 @@ add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, bool reverse_order,
return args->result == CURLE_OK;
}
static void schannel_session_free(void *sessionid, size_t idsize)
static void schannel_session_free(void *sessionid)
{
/* this is expected to be called under sessionid lock */
struct Curl_schannel_cred *cred = sessionid;
(void)idsize;
if(cred) {
cred->refcount--;
if(cred->refcount == 0) {
@ -1599,14 +1599,12 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
/* save the current session data for possible reuse */
if(ssl_config->primary.cache_session) {
Curl_ssl_sessionid_lock(data);
Curl_ssl_scache_lock(data);
/* Up ref count since call takes ownership */
backend->cred->refcount++;
result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL,
backend->cred,
sizeof(struct Curl_schannel_cred),
schannel_session_free);
Curl_ssl_sessionid_unlock(data);
result = Curl_ssl_scache_add_obj(cf, data, connssl->peer.scache_key,
backend->cred, schannel_session_free);
Curl_ssl_scache_unlock(data);
if(result)
return result;
}
@ -2445,9 +2443,9 @@ static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data)
/* free SSPI Schannel API credential handle */
if(backend->cred) {
Curl_ssl_sessionid_lock(data);
schannel_session_free(backend->cred, 0);
Curl_ssl_sessionid_unlock(data);
Curl_ssl_scache_lock(data);
schannel_session_free(backend->cred);
Curl_ssl_scache_unlock(data);
backend->cred = NULL;
}

View File

@ -38,6 +38,7 @@
#include "multiif.h"
#include "strcase.h"
#include "x509asn1.h"
#include "vtls_scache.h"
#include "strerror.h"
#include "cipher_suite.h"
@ -1020,7 +1021,7 @@ failed:
return ret;
}
static void sectransp_session_free(void *sessionid, size_t idsize)
static void sectransp_session_free(void *sessionid)
{
/* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a
cached session ID inside the Security framework. There is a private
@ -1028,7 +1029,6 @@ static void sectransp_session_free(void *sessionid, size_t idsize)
got your application rejected from the App Store due to the use of a
private API, so the best we can do is free up our own char array that we
created way back in sectransp_connect_step1... */
(void)idsize;
Curl_safefree(sessionid);
}
@ -1337,19 +1337,19 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf,
char *ssl_sessionid;
size_t ssl_sessionid_len;
Curl_ssl_sessionid_lock(data);
if(!Curl_ssl_getsessionid(cf, data, &connssl->peer,
(void **)&ssl_sessionid, &ssl_sessionid_len,
NULL)) {
Curl_ssl_scache_lock(data);
if(Curl_ssl_scache_get_obj(cf, data, connssl->peer.scache_key,
(void **)&ssl_sessionid)) {
/* we got a session id, use it! */
err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len);
Curl_ssl_sessionid_unlock(data);
err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid,
strlen(ssl_sessionid));
Curl_ssl_scache_unlock(data);
if(err != noErr) {
failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err);
return CURLE_SSL_CONNECT_ERROR;
}
/* Informational message */
infof(data, "SSL reusing session ID");
else
infof(data, "SSL reusing session ID");
}
/* If there is not one, then let's make one up! This has to be done prior
to starting the handshake. */
@ -1363,15 +1363,17 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf,
err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len);
if(err != noErr) {
Curl_ssl_sessionid_unlock(data);
Curl_ssl_scache_unlock(data);
failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err);
return CURLE_SSL_CONNECT_ERROR;
}
result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL,
ssl_sessionid, ssl_sessionid_len,
/* This is all a bit weird, as we have not handshaked yet.
* I hope this backend will go away soon. */
result = Curl_ssl_scache_add_obj(cf, data, connssl->peer.scache_key,
(void *)ssl_sessionid,
sectransp_session_free);
Curl_ssl_sessionid_unlock(data);
Curl_ssl_scache_unlock(data);
if(result)
return result;
}

View File

@ -55,6 +55,7 @@
#include "vtls.h" /* generic SSL protos etc */
#include "vtls_int.h"
#include "vtls_scache.h"
#include "openssl.h" /* OpenSSL versions */
#include "gtls.h" /* GnuTLS versions */
@ -74,6 +75,7 @@
#include "multiif.h"
#include "timeval.h"
#include "curl_md5.h"
#include "curl_sha256.h"
#include "warnless.h"
#include "curl_base64.h"
#include "curl_printf.h"
@ -88,11 +90,6 @@
#include "memdebug.h"
/* convenience macro to check if this handle is using a shared SSL session */
#define SSLSESSION_SHARED(data) (data->share && \
(data->share->specifier & \
(1<<CURL_LOCK_DATA_SSL_SESSION)))
#define CLONE_STRING(var) \
do { \
if(source->var) { \
@ -465,9 +462,10 @@ static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data,
if(!ctx)
return NULL;
ctx->ssl_impl = Curl_ssl;
ctx->alpn = alpn;
Curl_bufq_init2(&ctx->earlydata, CURL_SSL_EARLY_MAX, 1, BUFQ_OPT_NO_SPARES);
ctx->backend = calloc(1, Curl_ssl->sizeof_ssl_backend_data);
ctx->backend = calloc(1, ctx->ssl_impl->sizeof_ssl_backend_data);
if(!ctx->backend) {
free(ctx);
return NULL;
@ -478,7 +476,7 @@ static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data,
static void cf_ctx_free(struct ssl_connect_data *ctx)
{
if(ctx) {
Curl_safefree(ctx->alpn_negotiated);
Curl_safefree(ctx->negotiated.alpn);
Curl_bufq_free(&ctx->earlydata);
free(ctx->backend);
free(ctx);
@ -496,7 +494,7 @@ static CURLcode ssl_connect(struct Curl_cfilter *cf, struct Curl_easy *data)
/* mark this is being ssl-enabled from here on. */
connssl->state = ssl_connection_negotiating;
result = Curl_ssl->connect_blocking(cf, data);
result = connssl->ssl_impl->connect_blocking(cf, data);
if(!result) {
DEBUGASSERT(connssl->state == ssl_connection_complete);
@ -509,275 +507,13 @@ static CURLcode
ssl_connect_nonblocking(struct Curl_cfilter *cf, struct Curl_easy *data,
bool *done)
{
struct ssl_connect_data *connssl = cf->ctx;
if(!ssl_prefs_check(data))
return CURLE_SSL_CONNECT_ERROR;
/* mark this is being ssl requested from here on. */
return Curl_ssl->connect_nonblocking(cf, data, done);
}
/*
* Lock shared SSL session data
*/
void Curl_ssl_sessionid_lock(struct Curl_easy *data)
{
if(SSLSESSION_SHARED(data))
Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE);
}
/*
* Unlock shared SSL session data
*/
void Curl_ssl_sessionid_unlock(struct Curl_easy *data)
{
if(SSLSESSION_SHARED(data))
Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
}
/*
* Check if there is a session ID for the given connection in the cache, and if
* there is one suitable, it is provided. Returns TRUE when no entry matched.
*/
bool Curl_ssl_getsessionid(struct Curl_cfilter *cf,
struct Curl_easy *data,
const struct ssl_peer *peer,
void **ssl_sessionid,
size_t *idsize, /* set 0 if unknown */
char **palpn)
{
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);
struct Curl_ssl_session *check;
size_t i;
long *general_age;
bool no_match = TRUE;
*ssl_sessionid = NULL;
if(palpn)
*palpn = NULL;
if(!ssl_config)
return TRUE;
DEBUGASSERT(ssl_config->primary.cache_session);
if(!ssl_config->primary.cache_session || !data->state.session)
/* session ID reuse is disabled or the session cache has not been
setup */
return TRUE;
/* Lock if shared */
if(SSLSESSION_SHARED(data))
general_age = &data->share->sessionage;
else
general_age = &data->state.sessionage;
for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) {
check = &data->state.session[i];
if(!check->sessionid)
/* not session ID means blank entry */
continue;
if(strcasecompare(peer->hostname, check->name) &&
((!cf->conn->bits.conn_to_host && !check->conn_to_host) ||
(cf->conn->bits.conn_to_host && check->conn_to_host &&
strcasecompare(cf->conn->conn_to_host.name, check->conn_to_host))) &&
((!cf->conn->bits.conn_to_port && check->conn_to_port == -1) ||
(cf->conn->bits.conn_to_port && check->conn_to_port != -1 &&
cf->conn->conn_to_port == check->conn_to_port)) &&
(peer->port == check->remote_port) &&
(peer->transport == check->transport) &&
strcasecompare(cf->conn->handler->scheme, check->scheme) &&
match_ssl_primary_config(data, conn_config, &check->ssl_config)) {
/* yes, we have a session ID! */
(*general_age)++; /* increase general age */
check->age = *general_age; /* set this as used in this age */
*ssl_sessionid = check->sessionid;
if(idsize)
*idsize = check->idsize;
if(palpn)
*palpn = check->alpn;
no_match = FALSE;
break;
}
}
CURL_TRC_CF(data, cf, "%s cached session ID for %s://%s:%d",
no_match ? "No" : "Found",
cf->conn->handler->scheme, peer->hostname, peer->port);
return no_match;
}
/*
* Kill a single session ID entry in the cache.
*/
void Curl_ssl_kill_session(struct Curl_ssl_session *session)
{
if(session->sessionid) {
/* defensive check */
/* free the ID the SSL-layer specific way */
session->sessionid_free(session->sessionid, session->idsize);
session->sessionid = NULL;
session->sessionid_free = NULL;
session->age = 0; /* fresh */
free_primary_ssl_config(&session->ssl_config);
Curl_safefree(session->name);
Curl_safefree(session->conn_to_host);
Curl_safefree(session->alpn);
}
}
/*
* Delete the given session ID from the cache.
*/
void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid)
{
size_t i;
for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) {
struct Curl_ssl_session *check = &data->state.session[i];
if(check->sessionid == ssl_sessionid) {
Curl_ssl_kill_session(check);
break;
}
}
}
CURLcode Curl_ssl_set_sessionid(struct Curl_cfilter *cf,
struct Curl_easy *data,
const struct ssl_peer *peer,
const char *alpn,
void *ssl_sessionid,
size_t idsize,
Curl_ssl_sessionid_dtor *sessionid_free_cb)
{
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
size_t i;
struct Curl_ssl_session *store;
long oldest_age;
char *clone_host = NULL;
char *clone_conn_to_host = NULL;
char *clone_alpn = NULL;
int conn_to_port;
long *general_age;
void *old_sessionid;
size_t old_size;
CURLcode result = CURLE_OUT_OF_MEMORY;
DEBUGASSERT(ssl_sessionid);
DEBUGASSERT(sessionid_free_cb);
if(!data->state.session) {
sessionid_free_cb(ssl_sessionid, idsize);
return CURLE_OK;
}
if(!Curl_ssl_getsessionid(cf, data, peer, &old_sessionid, &old_size, NULL)) {
if((old_size == idsize) &&
((old_sessionid == ssl_sessionid) ||
(idsize && !memcmp(old_sessionid, ssl_sessionid, idsize)))) {
/* the very same */
sessionid_free_cb(ssl_sessionid, idsize);
return CURLE_OK;
}
Curl_ssl_delsessionid(data, old_sessionid);
}
store = &data->state.session[0];
oldest_age = data->state.session[0].age; /* zero if unused */
DEBUGASSERT(ssl_config->primary.cache_session);
(void)ssl_config;
clone_host = strdup(peer->hostname);
if(!clone_host)
goto out;
if(cf->conn->bits.conn_to_host) {
clone_conn_to_host = strdup(cf->conn->conn_to_host.name);
if(!clone_conn_to_host)
goto out;
}
clone_alpn = alpn ? strdup(alpn) : NULL;
if(alpn && !clone_alpn)
goto out;
if(cf->conn->bits.conn_to_port)
conn_to_port = cf->conn->conn_to_port;
else
conn_to_port = -1;
/* Now we should add the session ID and the hostname to the cache, (remove
the oldest if necessary) */
/* If using shared SSL session, lock! */
if(SSLSESSION_SHARED(data)) {
general_age = &data->share->sessionage;
}
else {
general_age = &data->state.sessionage;
}
/* find an empty slot for us, or find the oldest */
for(i = 1; (i < data->set.general_ssl.max_ssl_sessions) &&
data->state.session[i].sessionid; i++) {
if(data->state.session[i].age < oldest_age) {
oldest_age = data->state.session[i].age;
store = &data->state.session[i];
}
}
if(i == data->set.general_ssl.max_ssl_sessions)
/* cache is full, we must "kill" the oldest entry! */
Curl_ssl_kill_session(store);
else
store = &data->state.session[i]; /* use this slot */
/* now init the session struct wisely */
if(!clone_ssl_primary_config(conn_config, &store->ssl_config)) {
free_primary_ssl_config(&store->ssl_config);
store->sessionid = NULL; /* let caller free sessionid */
goto out;
}
store->sessionid = ssl_sessionid;
store->idsize = idsize;
store->sessionid_free = sessionid_free_cb;
store->age = *general_age; /* set current age */
/* free it if there is one already present */
free(store->name);
free(store->conn_to_host);
store->name = clone_host; /* clone hostname */
clone_host = NULL;
store->conn_to_host = clone_conn_to_host; /* clone connect to hostname */
clone_conn_to_host = NULL;
store->conn_to_port = conn_to_port; /* connect to port number */
store->alpn = clone_alpn;
clone_alpn = NULL;
/* port number */
store->remote_port = peer->port;
store->scheme = cf->conn->handler->scheme;
store->transport = peer->transport;
result = CURLE_OK;
out:
free(clone_host);
free(clone_conn_to_host);
free(clone_alpn);
if(result) {
failf(data, "Failed to add Session ID to cache for %s://%s:%d [%s]",
store->scheme, store->name, store->remote_port,
Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server");
sessionid_free_cb(ssl_sessionid, idsize);
return result;
}
CURL_TRC_CF(data, cf, "Added Session ID to cache for %s://%s:%d [%s]",
store->scheme, store->name, store->remote_port,
Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server");
return CURLE_OK;
return connssl->ssl_impl->connect_nonblocking(cf, data, done);
}
CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex,
@ -791,14 +527,9 @@ CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex,
void Curl_ssl_close_all(struct Curl_easy *data)
{
/* kill the session ID cache if not shared */
if(data->state.session && !SSLSESSION_SHARED(data)) {
size_t i;
for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++)
/* the single-killer function handles empty table slots */
Curl_ssl_kill_session(&data->state.session[i]);
/* free the cache data */
Curl_safefree(data->state.session);
if(data->state.ssl_scache && !CURL_SHARE_ssl_scache(data)) {
Curl_ssl_scache_destroy(data->state.ssl_scache);
data->state.ssl_scache = NULL;
}
if(Curl_ssl->close_all)
@ -853,29 +584,6 @@ struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data)
return NULL;
}
/*
* This sets up a session ID cache to the specified size. Make sure this code
* is agnostic to what underlying SSL technology we use.
*/
CURLcode Curl_ssl_initsessions(struct Curl_easy *data, size_t amount)
{
struct Curl_ssl_session *session;
if(data->state.session)
/* this is just a precaution to prevent multiple inits */
return CURLE_OK;
session = calloc(amount, sizeof(struct Curl_ssl_session));
if(!session)
return CURLE_OUT_OF_MEMORY;
/* store the info in the SSL section */
data->set.general_ssl.max_ssl_sessions = amount;
data->state.session = session;
data->state.sessionage = 1; /* this is brand new */
return CURLE_OK;
}
static size_t multissl_version(char *buffer, size_t size);
void Curl_ssl_version(char *buffer, size_t size)
@ -1494,11 +1202,12 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
void Curl_ssl_peer_cleanup(struct ssl_peer *peer)
{
Curl_safefree(peer->sni);
if(peer->dispname != peer->hostname)
free(peer->dispname);
free(peer->sni);
free(peer->hostname);
peer->hostname = peer->sni = peer->dispname = NULL;
peer->dispname = NULL;
Curl_safefree(peer->hostname);
Curl_safefree(peer->scache_key);
peer->type = CURL_SSL_PEER_DNS;
}
@ -1506,7 +1215,7 @@ static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct ssl_connect_data *connssl = cf->ctx;
if(connssl) {
Curl_ssl->close(cf, data);
connssl->ssl_impl->close(cf, data);
connssl->state = ssl_connection_none;
Curl_ssl_peer_cleanup(&connssl->peer);
}
@ -1532,7 +1241,9 @@ static ssl_peer_type get_peer_type(const char *hostname)
return CURL_SSL_PEER_DNS;
}
CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, struct Curl_cfilter *cf,
CURLcode Curl_ssl_peer_init(struct ssl_peer *peer,
struct Curl_cfilter *cf,
const char *tls_id,
int transport)
{
const char *ehostname, *edispname;
@ -1594,7 +1305,8 @@ CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, struct Curl_cfilter *cf,
peer->sni[len] = 0;
}
}
result = CURLE_OK;
result = Curl_ssl_peer_key_make(cf, peer, tls_id, &peer->scache_key);
out:
if(result)
@ -1657,7 +1369,9 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf,
*done = FALSE;
if(!connssl->peer.hostname) {
result = Curl_ssl_peer_init(&connssl->peer, cf, TRNSPRT_TCP);
char tls_id[80];
connssl->ssl_impl->version(tls_id, sizeof(tls_id) - 1);
result = Curl_ssl_peer_init(&connssl->peer, cf, tls_id, TRNSPRT_TCP);
if(result)
goto out;
}
@ -1686,11 +1400,13 @@ out:
static bool ssl_cf_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
struct ssl_connect_data *connssl = cf->ctx;
struct cf_call_data save;
bool result;
CF_DATA_SAVE(save, cf, data);
if(Curl_ssl->data_pending && Curl_ssl->data_pending(cf, data))
if(connssl->ssl_impl->data_pending &&
connssl->ssl_impl->data_pending(cf, data))
result = TRUE;
else
result = cf->next->cft->has_data_pending(cf->next, data);
@ -1702,6 +1418,7 @@ static ssize_t ssl_cf_send(struct Curl_cfilter *cf,
struct Curl_easy *data, const void *buf, size_t len,
bool eos, CURLcode *err)
{
struct ssl_connect_data *connssl = cf->ctx;
struct cf_call_data save;
ssize_t nwritten = 0;
@ -1710,7 +1427,7 @@ static ssize_t ssl_cf_send(struct Curl_cfilter *cf,
*err = CURLE_OK;
if(len > 0) {
CF_DATA_SAVE(save, cf, data);
nwritten = Curl_ssl->send_plain(cf, data, buf, len, err);
nwritten = connssl->ssl_impl->send_plain(cf, data, buf, len, err);
CF_DATA_RESTORE(cf, save);
}
return nwritten;
@ -1720,12 +1437,13 @@ static ssize_t ssl_cf_recv(struct Curl_cfilter *cf,
struct Curl_easy *data, char *buf, size_t len,
CURLcode *err)
{
struct ssl_connect_data *connssl = cf->ctx;
struct cf_call_data save;
ssize_t nread;
CF_DATA_SAVE(save, cf, data);
*err = CURLE_OK;
nread = Curl_ssl->recv_plain(cf, data, buf, len, err);
nread = connssl->ssl_impl->recv_plain(cf, data, buf, len, err);
if(nread > 0) {
DEBUGASSERT((size_t)nread <= len);
}
@ -1743,6 +1461,7 @@ static CURLcode ssl_cf_shutdown(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
{
struct ssl_connect_data *connssl = cf->ctx;
CURLcode result = CURLE_OK;
*done = TRUE;
@ -1750,7 +1469,7 @@ static CURLcode ssl_cf_shutdown(struct Curl_cfilter *cf,
struct cf_call_data save;
CF_DATA_SAVE(save, cf, data);
result = Curl_ssl->shut_down(cf, data, TRUE, done);
result = connssl->ssl_impl->shut_down(cf, data, TRUE, done);
CURL_TRC_CF(data, cf, "cf_shutdown -> %d, done=%d", result, *done);
CF_DATA_RESTORE(cf, save);
cf->shutdown = (result || *done);
@ -1762,10 +1481,11 @@ static void ssl_cf_adjust_pollset(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct easy_pollset *ps)
{
struct ssl_connect_data *connssl = cf->ctx;
struct cf_call_data save;
CF_DATA_SAVE(save, cf, data);
Curl_ssl->adjust_pollset(cf, data, ps);
connssl->ssl_impl->adjust_pollset(cf, data, ps);
CF_DATA_RESTORE(cf, save);
}
@ -1971,9 +1691,10 @@ void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex,
/* get first SSL filter in chain, if any is present */
cf = get_ssl_filter(data->conn->cfilter[sockindex]);
if(cf) {
struct ssl_connect_data *connssl = cf->ctx;
struct cf_call_data save;
CF_DATA_SAVE(save, cf, data);
result = Curl_ssl->get_internals(cf->ctx, info);
result = connssl->ssl_impl->get_internals(cf->ctx, info);
CF_DATA_RESTORE(cf, save);
}
}
@ -2006,7 +1727,7 @@ static CURLcode vtls_shutdown_blocking(struct Curl_cfilter *cf,
return CURLE_OPERATION_TIMEDOUT;
}
result = Curl_ssl->shut_down(cf, data, send_shutdown, done);
result = connssl->ssl_impl->shut_down(cf, data, send_shutdown, done);
if(result ||*done)
goto out;
@ -2158,28 +1879,28 @@ CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf,
#endif
;
if(connssl->alpn_negotiated) {
if(connssl->negotiated.alpn) {
/* When we ask for a specific ALPN protocol, we need the confirmation
* of it by the server, as we have installed protocol handler and
* connection filter chain for exactly this protocol. */
if(!proto_len) {
failf(data, "ALPN: asked for '%s' from previous session, "
"but server did not confirm it. Refusing to continue.",
connssl->alpn_negotiated);
connssl->negotiated.alpn);
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
else if((strlen(connssl->alpn_negotiated) != proto_len) ||
memcmp(connssl->alpn_negotiated, proto, proto_len)) {
else if((strlen(connssl->negotiated.alpn) != proto_len) ||
memcmp(connssl->negotiated.alpn, proto, proto_len)) {
failf(data, "ALPN: asked for '%s' from previous session, but server "
"selected '%.*s'. Refusing to continue.",
connssl->alpn_negotiated, (int)proto_len, proto);
connssl->negotiated.alpn, (int)proto_len, proto);
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
/* ALPN is exactly what we asked for, done. */
infof(data, "ALPN: server confirmed to use '%s'",
connssl->alpn_negotiated);
connssl->negotiated.alpn);
goto out;
}
@ -2190,11 +1911,11 @@ CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf,
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
connssl->alpn_negotiated = malloc(proto_len + 1);
if(!connssl->alpn_negotiated)
connssl->negotiated.alpn = malloc(proto_len + 1);
if(!connssl->negotiated.alpn)
return CURLE_OUT_OF_MEMORY;
memcpy(connssl->alpn_negotiated, proto, proto_len);
connssl->alpn_negotiated[proto_len] = 0;
memcpy(connssl->negotiated.alpn, proto, proto_len);
connssl->negotiated.alpn[proto_len] = 0;
}
if(proto && proto_len) {

View File

@ -28,7 +28,9 @@
struct connectdata;
struct ssl_config_data;
struct ssl_primary_config;
struct Curl_ssl_session;
struct Curl_cfilter;
struct Curl_easy;
struct dynbuf;
#define SSLSUPP_CA_PATH (1<<0) /* supports CAPATH */
#define SSLSUPP_CERTINFO (1<<1) /* supports CURLOPT_CERTINFO */
@ -63,9 +65,31 @@ struct Curl_ssl_session;
#define VTLS_INFOF_ALPN_DEFERRED \
"ALPN: deferred handshake for early data using '%.*s'."
/* Curl_multi SSL backend-specific data; declared differently by each SSL
backend */
struct Curl_cfilter;
/* IETF defined version numbers used in TLS protocol negotiation */
#define CURL_IETF_PROTO_UNKNOWN 0x0
#define CURL_IETF_PROTO_SSL3 0x0300
#define CURL_IETF_PROTO_TLS1 0x0301
#define CURL_IETF_PROTO_TLS1_1 0x0302
#define CURL_IETF_PROTO_TLS1_2 0x0303
#define CURL_IETF_PROTO_TLS1_3 0x0304
#define CURL_IETF_PROTO_DTLS1 0xFEFF
#define CURL_IETF_PROTO_DTLS1_2 0xFEFD
typedef enum {
CURL_SSL_PEER_DNS,
CURL_SSL_PEER_IPV4,
CURL_SSL_PEER_IPV6
} ssl_peer_type;
struct ssl_peer {
char *hostname; /* hostname for verification */
char *dispname; /* display version of hostname */
char *sni; /* SNI version of hostname or NULL if not usable */
char *scache_key; /* for lookups in session cache */
ssl_peer_type type; /* type of the peer information */
int port; /* port we are talking to */
int transport; /* one of TRNSPRT_* defines */
};
CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
const curl_ssl_backend ***avail);
@ -121,7 +145,9 @@ void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy);
* Init SSL peer information for filter. Can be called repeatedly.
*/
CURLcode Curl_ssl_peer_init(struct ssl_peer *peer,
struct Curl_cfilter *cf, int transport);
struct Curl_cfilter *cf,
const char *tls_id,
int transport);
/**
* Free all allocated data and reset peer information.
*/
@ -138,8 +164,6 @@ CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine);
CURLcode Curl_ssl_set_engine_default(struct Curl_easy *data);
struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data);
/* init the SSL session ID cache */
CURLcode Curl_ssl_initsessions(struct Curl_easy *, size_t);
void Curl_ssl_version(char *buffer, size_t size);
/* Certificate information list handling. */
@ -155,33 +179,6 @@ CURLcode Curl_ssl_push_certinfo(struct Curl_easy *data, int certnum,
/* Functions to be used by SSL library adaptation functions */
/* Lock session cache mutex.
* Call this before calling other Curl_ssl_*session* functions
* Caller should unlock this mutex as soon as possible, as it may block
* other SSL connection from making progress.
* The purpose of explicitly locking SSL session cache data is to allow
* individual SSL engines to manage session lifetime in their specific way.
*/
void Curl_ssl_sessionid_lock(struct Curl_easy *data);
/* Unlock session cache mutex */
void Curl_ssl_sessionid_unlock(struct Curl_easy *data);
/* Kill a single session ID entry in the cache
* Sessionid mutex must be locked (see Curl_ssl_sessionid_lock).
* This will call engine-specific curlssl_session_free function, which must
* take sessionid object ownership from sessionid cache
* (e.g. decrement refcount).
*/
void Curl_ssl_kill_session(struct Curl_ssl_session *session);
/* delete a session from the cache
* Sessionid mutex must be locked (see Curl_ssl_sessionid_lock).
* This will call engine-specific curlssl_session_free function, which must
* take sessionid object ownership from sessionid cache
* (e.g. decrement refcount).
*/
void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid);
/* get N random bytes into the buffer */
CURLcode Curl_ssl_random(struct Curl_easy *data, unsigned char *buffer,
size_t length);
@ -273,9 +270,7 @@ extern struct Curl_cftype Curl_cft_ssl_proxy;
#define Curl_ssl_set_engine(x,y) CURLE_NOT_BUILT_IN
#define Curl_ssl_set_engine_default(x) CURLE_NOT_BUILT_IN
#define Curl_ssl_engines_list(x) NULL
#define Curl_ssl_initsessions(x,y) CURLE_OK
#define Curl_ssl_free_certinfo(x) Curl_nop_stmt
#define Curl_ssl_kill_session(x) Curl_nop_stmt
#define Curl_ssl_random(x,y,z) ((void)x, CURLE_NOT_BUILT_IN)
#define Curl_ssl_cert_status_request() FALSE
#define Curl_ssl_false_start() FALSE

View File

@ -26,9 +26,11 @@
#include "curl_setup.h"
#include "cfilters.h"
#include "urldata.h"
#include "vtls.h"
#ifdef USE_SSL
struct Curl_ssl;
struct ssl_connect_data;
/* see https://www.iana.org/assignments/tls-extensiontype-values/ */
@ -103,12 +105,15 @@ typedef enum {
/* Information in each SSL cfilter context: cf->ctx */
struct ssl_connect_data {
struct ssl_peer peer;
const struct Curl_ssl *ssl_impl; /* TLS backend for this filter */
struct ssl_peer peer; /* peer the filter talks to */
const struct alpn_spec *alpn; /* ALPN to use or NULL for none */
void *backend; /* vtls backend specific props */
struct cf_call_data call_data; /* data handle used in current call */
struct curltime handshake_done; /* time when handshake finished */
char *alpn_negotiated; /* negotiated ALPN value or NULL */
struct {
char *alpn; /* ALPN value or NULL */
} negotiated;
struct bufq earlydata; /* earlydata to be send to peer */
size_t earlydata_max; /* max earlydata allowed by peer */
size_t earlydata_skip; /* sending bytes to skip when earlydata
@ -193,43 +198,6 @@ void Curl_ssl_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data,
*/
bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf);
/* extract a session ID
* Sessionid mutex must be locked (see Curl_ssl_sessionid_lock).
* Caller must make sure that the ownership of returned sessionid object
* is properly taken (e.g. its refcount is incremented
* under sessionid mutex).
* @param cf the connection filter wanting to use it
* @param data the transfer involved
* @param peer the peer the filter wants to talk to
* @param sessionid on return the TLS session
* @param idsize on return the size of the TLS session data
* @param palpn on return the ALPN string used by the session,
* set to NULL when not interested
*/
bool Curl_ssl_getsessionid(struct Curl_cfilter *cf,
struct Curl_easy *data,
const struct ssl_peer *peer,
void **ssl_sessionid,
size_t *idsize, /* set 0 if unknown */
char **palpn);
/* Set a TLS session ID for `peer`. Replaces an existing session ID if
* not already the same.
* Sessionid mutex must be locked (see Curl_ssl_sessionid_lock).
* Call takes ownership of `ssl_sessionid`, using `sessionid_free_cb`
* to deallocate it. Is called in all outcomes, either right away or
* later when the session cache is cleaned up.
* Caller must ensure that it has properly shared ownership of this sessionid
* object with cache (e.g. incrementing refcount on success)
*/
CURLcode Curl_ssl_set_sessionid(struct Curl_cfilter *cf,
struct Curl_easy *data,
const struct ssl_peer *peer,
const char *alpn,
void *sessionid,
size_t sessionid_size,
Curl_ssl_sessionid_dtor *sessionid_free_cb);
#endif /* USE_SSL */
#endif /* HEADER_CURL_VTLS_INT_H */

876
lib/vtls/vtls_scache.c Normal file
View File

@ -0,0 +1,876 @@
/***************************************************************************
* _ _ ____ _
* 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
*
***************************************************************************/
/* 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
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include "urldata.h"
#include "cfilters.h"
#include "vtls.h" /* generic SSL protos etc */
#include "vtls_int.h"
#include "vtls_scache.h"
#include "strcase.h"
#include "url.h"
#include "llist.h"
#include "share.h"
#include "curl_trc.h"
#include "curl_sha256.h"
#include "warnless.h"
#include "curl_printf.h"
#include "strdup.h"
/* The last #include files should be: */
#include "curl_memory.h"
#include "memdebug.h"
/* a peer+tls-config we cache sessions for */
struct Curl_ssl_scache_peer {
char *ssl_peer_key; /* id for peer + relevant TLS configuration */
char *clientcert;
char *srp_username;
char *srp_password;
struct Curl_llist sessions;
void *sobj; /* object instance or NULL */
Curl_ssl_scache_obj_dtor *sobj_free; /* free `sobj` callback */
unsigned char key_salt[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */
unsigned char key_hmac[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */
size_t max_sessions;
long age; /* just a number, the higher the more recent */
BIT(hmac_set); /* if key_salt and key_hmac are present */
};
struct Curl_ssl_scache {
struct Curl_ssl_scache_peer *peers;
size_t peer_count;
int default_lifetime_secs;
long age;
};
static void cf_ssl_scache_clear_session(struct Curl_ssl_session *s)
{
if(s->sdata) {
free((void *)s->sdata);
s->sdata = NULL;
}
s->sdata_len = 0;
s->ietf_tls_id = 0;
s->time_received = 0;
s->lifetime_secs = 0;
Curl_safefree(s->alpn);
}
static void cf_ssl_scache_sesssion_ldestroy(void *udata, void *s)
{
(void)udata;
cf_ssl_scache_clear_session(s);
free(s);
}
CURLcode
Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len,
int ietf_tls_id, const char *alpn,
curl_off_t time_received, long lifetime_secs,
struct Curl_ssl_session **psession)
{
struct Curl_ssl_session *s;
if(!sdata || !sdata_len) {
free(sdata);
return CURLE_BAD_FUNCTION_ARGUMENT;
}
*psession = NULL;
s = calloc(1, sizeof(*s));
if(!s) {
free(sdata);
return CURLE_OUT_OF_MEMORY;
}
s->ietf_tls_id = ietf_tls_id;
s->time_received = time_received;
if(lifetime_secs < 0)
lifetime_secs = -1; /* unknown */
else if((s->ietf_tls_id == CURL_IETF_PROTO_TLS1_3) &&
(lifetime_secs > CURL_SCACHE_MAX_13_LIFETIME_SEC))
lifetime_secs = CURL_SCACHE_MAX_13_LIFETIME_SEC;
else if(lifetime_secs > CURL_SCACHE_MAX_12_LIFETIME_SEC)
lifetime_secs = CURL_SCACHE_MAX_12_LIFETIME_SEC;
s->lifetime_secs = (int)lifetime_secs;
s->sdata = sdata;
s->sdata_len = sdata_len;
if(alpn) {
s->alpn = strdup(alpn);
if(!s->alpn) {
cf_ssl_scache_sesssion_ldestroy(NULL, s);
return CURLE_OUT_OF_MEMORY;
}
}
*psession = s;
return CURLE_OK;
}
void Curl_ssl_session_destroy(struct Curl_ssl_session *s)
{
if(s) {
/* if in the list, the list destructor takes care of it */
if(Curl_node_llist(&s->list))
Curl_node_remove(&s->list);
else {
cf_ssl_scache_sesssion_ldestroy(NULL, s);
}
}
}
static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer)
{
Curl_llist_destroy(&peer->sessions, NULL);
if(peer->sobj) {
DEBUGASSERT(peer->sobj_free);
if(peer->sobj_free)
peer->sobj_free(peer->sobj);
peer->sobj = NULL;
}
peer->sobj_free = NULL;
Curl_safefree(peer->clientcert);
#ifdef USE_TLS_SRP
Curl_safefree(peer->srp_username);
Curl_safefree(peer->srp_password);
#endif
Curl_safefree(peer->ssl_peer_key);
peer->age = 0;
peer->hmac_set = FALSE;
}
static void cf_ssl_scache_peer_set_obj(struct Curl_ssl_scache_peer *peer,
void *sobj,
Curl_ssl_scache_obj_dtor *sobj_free)
{
DEBUGASSERT(peer);
if(peer->sobj_free) {
peer->sobj_free(peer->sobj);
}
peer->sobj = sobj;
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)
{
CURLcode result = CURLE_OUT_OF_MEMORY;
DEBUGASSERT(!peer->ssl_peer_key);
peer->ssl_peer_key = strdup(ssl_peer_key);
if(!peer->ssl_peer_key)
goto out;
if(clientcert) {
peer->clientcert = strdup(clientcert);
if(!peer->clientcert)
goto out;
}
if(srp_username) {
peer->srp_username = strdup(srp_username);
if(!peer->srp_username)
goto out;
}
if(srp_password) {
peer->srp_password = strdup(srp_password);
if(!peer->srp_password)
goto out;
}
result = CURLE_OK;
out:
if(result)
cf_ssl_scache_clear_peer(peer);
return result;
}
static void cf_scache_session_remove(struct Curl_ssl_scache_peer *peer,
struct Curl_ssl_session *s)
{
(void)peer;
DEBUGASSERT(Curl_node_llist(&s->list) == &peer->sessions);
Curl_ssl_session_destroy(s);
}
static bool cf_scache_session_expired(struct Curl_ssl_session *s,
curl_off_t now)
{
return (s->lifetime_secs > 0 &&
(s->time_received + s->lifetime_secs) < now);
}
static void cf_scache_peer_remove_expired(struct Curl_ssl_scache_peer *peer,
curl_off_t now)
{
struct Curl_llist_node *n = Curl_llist_head(&peer->sessions);
while(n) {
struct Curl_ssl_session *s = Curl_node_elem(n);
n = Curl_node_next(n);
if(cf_scache_session_expired(s, now))
cf_scache_session_remove(peer, s);
}
}
static void cf_scache_peer_remove_non13(struct Curl_ssl_scache_peer *peer)
{
struct Curl_llist_node *n = Curl_llist_head(&peer->sessions);
while(n) {
struct Curl_ssl_session *s = Curl_node_elem(n);
n = Curl_node_next(n);
if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3)
cf_scache_session_remove(peer, s);
}
}
CURLcode Curl_ssl_scache_create(size_t max_peers,
size_t max_sessions_per_peer,
struct Curl_ssl_scache **pscache)
{
struct Curl_ssl_scache *scache;
struct Curl_ssl_scache_peer *peers;
size_t i;
*pscache = NULL;
peers = calloc(max_peers, sizeof(*peers));
if(!peers)
return CURLE_OUT_OF_MEMORY;
scache = calloc(1, sizeof(*scache));
if(!scache) {
free(peers);
return CURLE_OUT_OF_MEMORY;
}
scache->default_lifetime_secs = (24*60*60); /* 1 day */
scache->peer_count = max_peers;
scache->peers = peers;
scache->age = 1;
for(i = 0; i < scache->peer_count; ++i) {
scache->peers[i].max_sessions = max_sessions_per_peer;
Curl_llist_init(&scache->peers[i].sessions,
cf_ssl_scache_sesssion_ldestroy);
}
*pscache = scache;
return CURLE_OK;
}
void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache)
{
if(scache) {
size_t i;
for(i = 0; i < scache->peer_count; ++i) {
cf_ssl_scache_clear_peer(&scache->peers[i]);
}
free(scache->peers);
free(scache);
}
}
/* Lock shared SSL session data */
void Curl_ssl_scache_lock(struct Curl_easy *data)
{
if(CURL_SHARE_ssl_scache(data))
Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE);
}
/* Unlock shared SSL session data */
void Curl_ssl_scache_unlock(struct Curl_easy *data)
{
if(CURL_SHARE_ssl_scache(data))
Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
}
static CURLcode cf_ssl_peer_key_add_path(struct dynbuf *buf,
const char *name,
char *path)
{
if(path && path[0]) {
/* We try to add absolute paths, so that the session key can stay
* valid when used in another process with different CWD. However,
* when a path does not exist, this does not work. Then, we add
* the path as is. */
#ifdef _WIN32
char abspath[_MAX_PATH];
if(_fullpath(abspath, path, _MAX_PATH))
return Curl_dyn_addf(buf, ":%s-%s", name, abspath);
#else
if(path[0] != '/') {
char *abspath = realpath(path, NULL);
if(abspath) {
CURLcode r = Curl_dyn_addf(buf, ":%s-%s", name, abspath);
(free)(abspath); /* allocated by libc, free without memdebug */
return r;
}
}
#endif
return Curl_dyn_addf(buf, ":%s-%s", name, path);
}
return CURLE_OK;
}
static CURLcode cf_ssl_peer_key_add_hash(struct dynbuf *buf,
const char *name,
struct curl_blob *blob)
{
CURLcode r = CURLE_OK;
if(blob && blob->len) {
unsigned char hash[CURL_SHA256_DIGEST_LENGTH];
size_t i;
r = Curl_dyn_addf(buf, ":%s-", name);
if(r)
goto out;
r = Curl_sha256it(hash, blob->data, blob->len);
if(r)
goto out;
for(i = 0; i < CURL_SHA256_DIGEST_LENGTH; ++i) {
r = Curl_dyn_addf(buf, "%02x", hash[i]);
if(r)
goto out;
}
}
out:
return r;
}
CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf,
const struct ssl_peer *peer,
const char *tls_id,
char **ppeer_key)
{
struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf);
struct dynbuf buf;
size_t key_len;
CURLcode r;
*ppeer_key = NULL;
Curl_dyn_init(&buf, 10 * 1024);
r = Curl_dyn_addf(&buf, "%s:%d", peer->hostname, peer->port);
if(r)
goto out;
switch(peer->transport) {
case TRNSPRT_TCP:
break;
case TRNSPRT_UDP:
r = Curl_dyn_add(&buf, ":UDP");
break;
case TRNSPRT_QUIC:
r = Curl_dyn_add(&buf, ":QUIC");
break;
case TRNSPRT_UNIX:
r = Curl_dyn_add(&buf, ":UNIX");
break;
default:
r = Curl_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport);
break;
}
if(r)
goto out;
if(!ssl->verifypeer) {
r = Curl_dyn_add(&buf, ":NO-VRFY-PEER");
if(r)
goto out;
}
if(!ssl->verifyhost) {
r = Curl_dyn_add(&buf, ":NO-VRFY-HOST");
if(r)
goto out;
}
if(ssl->verifystatus) {
r = Curl_dyn_add(&buf, ":VRFY-STATUS");
if(r)
goto out;
}
if(!ssl->verifypeer || !ssl->verifyhost) {
if(cf->conn->bits.conn_to_host) {
r = Curl_dyn_addf(&buf, ":CHOST-%s", cf->conn->conn_to_host.name);
if(r)
goto out;
}
if(cf->conn->bits.conn_to_port) {
r = Curl_dyn_addf(&buf, ":CPORT-%d", cf->conn->conn_to_port);
if(r)
goto out;
}
}
if(ssl->version || ssl->version_max) {
r = Curl_dyn_addf(&buf, ":TLSVER-%d-%d", ssl->version,
(ssl->version_max >> 16));
if(r)
goto out;
}
if(ssl->ssl_options) {
r = Curl_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options);
if(r)
goto out;
}
if(ssl->cipher_list) {
r = Curl_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list);
if(r)
goto out;
}
if(ssl->cipher_list13) {
r = Curl_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13);
if(r)
goto out;
}
if(ssl->curves) {
r = Curl_dyn_addf(&buf, ":CURVES-%s", ssl->curves);
if(r)
goto out;
}
if(ssl->verifypeer) {
r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile);
if(r)
goto out;
r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath);
if(r)
goto out;
r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile);
if(r)
goto out;
r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert);
if(r)
goto out;
if(ssl->cert_blob) {
r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob);
if(r)
goto out;
}
if(ssl->ca_info_blob) {
r = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob);
if(r)
goto out;
}
if(ssl->issuercert_blob) {
r = cf_ssl_peer_key_add_hash(&buf, "IssuerBlob", ssl->issuercert_blob);
if(r)
goto out;
}
}
if(ssl->pinned_key && ssl->pinned_key[0]) {
r = Curl_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key);
if(r)
goto out;
}
if(ssl->clientcert && ssl->clientcert[0]) {
r = Curl_dyn_add(&buf, ":CCERT");
if(r)
goto out;
}
#ifdef USE_TLS_SRP
if(ssl->username || ssl->password) {
r = Curl_dyn_add(&buf, ":SRP-AUTH");
if(r)
goto out;
}
#endif
if(!tls_id || !tls_id[0]) {
r = CURLE_FAILED_INIT;
goto out;
}
r = Curl_dyn_addf(&buf, ":IMPL-%s", tls_id);
if(r)
goto out;
*ppeer_key = Curl_dyn_take(&buf, &key_len);
/* we just added printable char, and dynbuf always 0 terminates,
* no need to track length */
out:
Curl_dyn_free(&buf);
return r;
}
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))
return FALSE;
#ifdef USE_TLS_SRP
if(Curl_timestrcmp(peer->srp_username, conn_config->username) ||
Curl_timestrcmp(peer->srp_password, conn_config->password))
return FALSE;
#endif
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)
{
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 &&
strcasecompare(ssl_peer_key, scache->peers[i].ssl_peer_key) &&
cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) {
/* yes, we have a cached session for this! */
*ppeer = &scache->peers[i];
goto out;
}
}
/* check for entries with HMAC set but no known peer_key */
for(i = 0; scache && i < scache->peer_count; i++) {
if(!scache->peers[i].ssl_peer_key &&
scache->peers[i].hmac_set &&
cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) {
/* possible entry with unknown peer_key, check hmac */
unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH];
if(!peer_key_len) /* we are lazy */
peer_key_len = strlen(ssl_peer_key);
(void)Curl_hmacit(&Curl_HMAC_SHA256,
scache->peers[i].key_salt,
sizeof(scache->peers[i].key_salt),
(const unsigned char *)ssl_peer_key,
peer_key_len,
my_hmac);
if(!memcmp(scache->peers[i].key_hmac, my_hmac, sizeof(my_hmac))) {
/* remember peer_key for future lookups */
scache->peers[i].ssl_peer_key = strdup(ssl_peer_key);
if(!scache->peers[i].ssl_peer_key) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
*ppeer = &scache->peers[i];
goto out;
}
}
}
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)
{
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 */
for(i = 0; i < scache->peer_count; ++i) {
/* free peer entry? */
if(!scache->peers[i].ssl_peer_key && !scache->peers[i].hmac_set) {
peer = &scache->peers[i];
break;
}
/* peer without sessions and obj */
if(!scache->peers[i].sobj &&
!Curl_llist_count(&scache->peers[i].sessions)) {
peer = &scache->peers[i];
break;
}
/* remember "oldest" peer */
if(!peer || (scache->peers[i].age < peer->age)) {
peer = &scache->peers[i];
}
}
DEBUGASSERT(peer);
if(!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,
#ifdef USE_TLS_SRP
conn_config->username,
conn_config->password);
#else
NULL, NULL);
#endif
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)
{
struct Curl_ssl_scache_peer *peer = NULL;
CURLcode result = CURLE_OUT_OF_MEMORY;
curl_off_t now = (curl_off_t)time(NULL);
if(!scache || !scache->peer_count) {
Curl_ssl_session_destroy(s);
return CURLE_OK;
}
if(!s->time_received)
s->time_received = now;
if(s->lifetime_secs < 0)
s->lifetime_secs = scache->default_lifetime_secs;
if(cf_scache_session_expired(s, now)) {
CURL_TRC_CF(data, cf, "[SCACHE] add, session already expired");
Curl_ssl_session_destroy(s);
return CURLE_OK;
}
result = cf_ssl_add_peer(cf, data, scache, ssl_peer_key, &peer);
if(result || !peer) {
CURL_TRC_CF(data, cf, "[SCACHE] 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));
}
}
out:
if(result) {
failf(data, "[SCACHE] failed to add session for %s, error=%d",
ssl_peer_key, result);
}
else
CURL_TRC_CF(data, cf, "[SCACHE] added session for %s [proto=0x%x, "
"lifetime=%d, alpn=%s], peer has %zu sessions now",
ssl_peer_key, s->ietf_tls_id, s->lifetime_secs, s->alpn,
Curl_llist_count(&peer->sessions));
return result;
}
CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
struct Curl_ssl_session *s)
{
struct Curl_ssl_scache *scache = data->state.ssl_scache;
CURLcode result;
Curl_ssl_scache_lock(data);
result = cf_scache_peer_add_session(cf, data, scache, ssl_peer_key, s);
Curl_ssl_scache_unlock(data);
return result;
}
void Curl_ssl_scache_return(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
struct Curl_ssl_session *s)
{
/* See RFC 8446 C.4:
* "Clients SHOULD NOT reuse a ticket for multiple connections." */
if(s && s->ietf_tls_id < 0x304)
(void)Curl_ssl_scache_put(cf, data, ssl_peer_key, s);
else
Curl_ssl_session_destroy(s);
}
CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
struct Curl_ssl_session **ps)
{
struct Curl_ssl_scache *scache = data->state.ssl_scache;
struct Curl_ssl_scache_peer *peer = NULL;
struct Curl_llist_node *n;
CURLcode result;
*ps = NULL;
if(!scache)
return CURLE_OK;
Curl_ssl_scache_lock(data);
result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
if(!result && peer) {
cf_scache_peer_remove_expired(peer, (curl_off_t)time(NULL));
n = Curl_llist_head(&peer->sessions);
if(n) {
*ps = Curl_node_take_elem(n);
(scache->age)++; /* increase general age */
peer->age = scache->age; /* set this as used in this age */
}
}
Curl_ssl_scache_unlock(data);
CURL_TRC_CF(data, cf, "[SCACHE] %s cached session for '%s'",
*ps ? "Found" : "No", ssl_peer_key);
return result;
}
CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
void *sobj,
Curl_ssl_scache_obj_dtor *sobj_free)
{
struct Curl_ssl_scache *scache = data->state.ssl_scache;
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);
if(result || !peer) {
CURL_TRC_CF(data, cf, "[SCACHE] unable to add scache peer: %d", result);
goto out;
}
cf_ssl_scache_peer_set_obj(peer, sobj, sobj_free);
sobj = NULL; /* peer took ownership */
out:
if(sobj && sobj_free)
sobj_free(sobj);
return result;
}
bool Curl_ssl_scache_get_obj(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
void **sobj)
{
struct Curl_ssl_scache *scache = data->state.ssl_scache;
struct Curl_ssl_scache_peer *peer = NULL;
CURLcode result;
*sobj = NULL;
if(!scache)
return FALSE;
result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &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);
return !!*sobj;
}
void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key)
{
struct Curl_ssl_scache *scache = data->state.ssl_scache;
struct Curl_ssl_scache_peer *peer = NULL;
CURLcode result;
(void)cf;
if(!scache)
return;
Curl_ssl_scache_lock(data);
result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
if(!result && peer)
cf_ssl_scache_clear_peer(peer);
Curl_ssl_scache_unlock(data);
}
#endif /* USE_SSL */

196
lib/vtls/vtls_scache.h Normal file
View File

@ -0,0 +1,196 @@
#ifndef HEADER_CURL_VTLS_SCACHE_H
#define HEADER_CURL_VTLS_SCACHE_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"
#include "cfilters.h"
#include "urldata.h"
#ifdef USE_SSL
struct Curl_cfilter;
struct Curl_easy;
struct Curl_ssl_scache;
struct Curl_ssl_session;
struct ssl_peer;
/* RFC 8446 (TLSv1.3) restrict lifetime to one week max, for
* other, less secure versions, we restrict it to a day */
#define CURL_SCACHE_MAX_13_LIFETIME_SEC (60*60*24*7)
#define CURL_SCACHE_MAX_12_LIFETIME_SEC (60*60*24)
/* Create a session cache for up to max_peers endpoints with a total
* of up to max_sessions SSL sessions per peer */
CURLcode Curl_ssl_scache_create(size_t max_peers,
size_t max_sessions_per_peer,
struct Curl_ssl_scache **pscache);
void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache);
/* Create a key from peer and TLS configuration information that is
* unique for how the connection filter wants to establish a TLS
* connection to the peer.
* If the filter is a TLS proxy filter, it will use the proxy relevant
* information.
* @param cf the connection filter wanting to use it
* @param peer the peer the filter wants to talk to
* @param tls_id identifier of TLS implementation for sessions. Should
* include full version if session data from other versions
* is to be avoided.
* @param ppeer_key on successful return, the key generated
*/
CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf,
const struct ssl_peer *peer,
const char *tls_id,
char **ppeer_key);
/* Lock session cache mutex.
* Call this before calling other Curl_ssl_*session* functions
* Caller should unlock this mutex as soon as possible, as it may block
* other SSL connection from making progress.
* The purpose of explicitly locking SSL session cache data is to allow
* individual SSL engines to manage session lifetime in their specific way.
*/
void Curl_ssl_scache_lock(struct Curl_easy *data);
/* Unlock session cache mutex */
void Curl_ssl_scache_unlock(struct Curl_easy *data);
/* Get TLS session object from the cache for the ssl_peer_ey.
* scache mutex must be locked (see Curl_ssl_scache_lock).
* Caller must make sure that the ownership of returned session object
* is properly taken (e.g. its refcount is incremented
* under scache mutex).
* @param cf the connection filter wanting to use it
* @param data the transfer involved
* @param ssl_peer_key the key for lookup
* @param sobj on return, the object for the peer key or NULL
*/
bool Curl_ssl_scache_get_obj(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
void **sobj);
typedef void Curl_ssl_scache_obj_dtor(void *sobj);
/* Add a TLS session related object to the cache.
* Replaces an existing object with the same peer_key.
* scache mutex must be locked (see Curl_ssl_scache_lock).
* Call takes ownership of `sobj`, using `sobj_dtor_cb`
* to deallocate it. Is called in all outcomes, either right away or
* later when the session cache is cleaned up.
* Caller must ensure that it has properly shared ownership of `sobj`
* with cache (e.g. incrementing refcount on success)
* @param cf the connection filter wanting to use it
* @param data the transfer involved
* @param ssl_peer_key the key for lookup
* @param sobj the TLS session object
* @param sobj_free_cb callback to free the session objectt
*/
CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
void *sobj,
Curl_ssl_scache_obj_dtor *sobj_dtor_cb);
/* All about a SSL session ticket */
struct Curl_ssl_session {
const unsigned char *sdata; /* session ticket data, plain bytes */
size_t sdata_len; /* number of bytes in sdata */
curl_off_t time_received; /* seconds since EPOCH ticket was received */
int lifetime_secs; /* ticket lifetime (-1 unknown) */
int ietf_tls_id; /* TLS protocol identifier negotiated */
char *alpn; /* APLN TLS negotiated protocol string */
struct Curl_llist_node list; /* internal storage handling */
};
/* Create a `session` instance. Does NOT need locking.
* Takes ownership of `sdata` and `sobj` regardless of return code.
* @param sdata bytes of SSL session data or NULL (sobj then required)
* @param sdata_len amount of session data bytes
* @param ietf_tls_id IETF protocol version, e.g. 0x304 for TLSv1.3
* @param alpn ALPN protocol selected or NULL
* @param time_received seconds since EPOCH session was received, pass 0
* to have the value set to time of call
* @param lifetime_secs seconds of announced lifetime, <0 if unknown.
* values longer than 1 week will be capped as
* required by RFC 8446
* @param psession on return the scached session instance created
*/
CURLcode
Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len,
int ietf_tls_id, const char *alpn,
curl_off_t time_received, long lifetime_secs,
struct Curl_ssl_session **psession);
/* Destroy a `session` instance. Can be called with NULL.
* Does NOT need locking. */
void Curl_ssl_session_destroy(struct Curl_ssl_session *s);
/* Put the scache session into the cache. Does NOT need locking.
* Call takes ownership of `s` in all outcomes.
* @param cf the connection filter wanting to use it
* @param data the transfer involved
* @param ssl_peer_key the key for lookup
* @param s the scache session object
*/
CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
struct Curl_ssl_session *s);
/* Take a matching scache session from the cache. Does NOT need locking.
* @param cf the connection filter wanting to use it
* @param data the transfer involved
* @param ssl_peer_key the key for lookup
* @param s on return, the scache session object or NULL
*/
CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
struct Curl_ssl_session **ps);
/* Return a taken scache session to the cache. Does NOT need locking.
* Depending on TLS version and other criteria, it may cache it again
* or destroy it. Maybe called with a NULL session.
*/
void Curl_ssl_scache_return(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
struct Curl_ssl_session *s);
/* Remove all sessions and obj for the peer_key. Does NOT need locking. */
void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key);
#else /* USE_SSL */
#define Curl_ssl_scache_create(x,y) CURLE_OK
#define Curl_ssl_scache_destroy(x) CURLE_OK
#endif /* USE_SSL (else) */
#endif /* HEADER_CURL_VTLS_SCACHE_H */

View File

@ -60,6 +60,7 @@
#include "inet_pton.h"
#include "vtls.h"
#include "vtls_int.h"
#include "vtls_scache.h"
#include "keylog.h"
#include "parsedate.h"
#include "connect.h" /* for the connect timeout */
@ -395,57 +396,53 @@ static void wolfssl_bio_cf_free_methods(void)
#endif /* !USE_BIO_CHAIN */
static void wolfssl_session_free(void *sdata, size_t slen)
{
(void)slen;
free(sdata);
}
CURLcode wssl_cache_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
WOLFSSL_SESSION *session)
CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
WOLFSSL_SESSION *session,
int ietf_tls_id,
const char *alpn)
{
CURLcode result = CURLE_OK;
struct Curl_ssl_session *sc_session = NULL;
unsigned char *sdata = NULL;
unsigned int slen;
unsigned int sdata_len;
if(!session)
goto out;
slen = wolfSSL_i2d_SSL_SESSION(session, NULL);
if(slen <= 0) {
CURL_TRC_CF(data, cf, "fail to assess session length: %u", slen);
sdata_len = wolfSSL_i2d_SSL_SESSION(session, NULL);
if(sdata_len <= 0) {
CURL_TRC_CF(data, cf, "fail to assess session length: %u", sdata_len);
result = CURLE_FAILED_INIT;
goto out;
}
sdata = calloc(1, slen);
sdata = calloc(1, sdata_len);
if(!sdata) {
failf(data, "unable to allocate session buffer of %u bytes", slen);
failf(data, "unable to allocate session buffer of %u bytes", sdata_len);
result = CURLE_OUT_OF_MEMORY;
goto out;
}
slen = wolfSSL_i2d_SSL_SESSION(session, &sdata);
if(slen <= 0) {
CURL_TRC_CF(data, cf, "fail to serialize session: %u", slen);
sdata_len = wolfSSL_i2d_SSL_SESSION(session, &sdata);
if(sdata_len <= 0) {
CURL_TRC_CF(data, cf, "fail to serialize session: %u", sdata_len);
result = CURLE_FAILED_INIT;
goto out;
}
Curl_ssl_sessionid_lock(data);
result = Curl_ssl_set_sessionid(cf, data, peer, NULL,
sdata, slen, wolfssl_session_free);
Curl_ssl_sessionid_unlock(data);
if(result)
failf(data, "failed to add new ssl session to cache (%d)", result);
else {
CURL_TRC_CF(data, cf, "added new session to cache");
sdata = NULL;
result = Curl_ssl_session_create(sdata, sdata_len,
ietf_tls_id, alpn, 0,
wolfSSL_SESSION_get_timeout(session),
&sc_session);
sdata = NULL; /* took ownership of sdata */
if(!result) {
result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session);
/* took ownership of `sc_session` */
}
out:
free(sdata);
return 0;
return result;
}
static int wssl_vtls_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session)
@ -460,32 +457,35 @@ static int wssl_vtls_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session)
DEBUGASSERT(connssl);
DEBUGASSERT(data);
if(connssl && data) {
(void)wssl_cache_session(cf, data, &connssl->peer, session);
(void)Curl_wssl_cache_session(cf, data, connssl->peer.scache_key,
session, wolfSSL_version(ssl),
connssl->negotiated.alpn);
}
}
return 0;
}
CURLcode wssl_setup_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct wolfssl_ctx *wss,
struct ssl_peer *peer)
CURLcode Curl_wssl_setup_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct wolfssl_ctx *wss,
const char *ssl_peer_key)
{
void *psdata;
const unsigned char *sdata = NULL;
size_t slen = 0;
CURLcode result = CURLE_OK;
struct Curl_ssl_session *sc_session = NULL;
CURLcode result;
Curl_ssl_sessionid_lock(data);
if(!Curl_ssl_getsessionid(cf, data, peer, &psdata, &slen, NULL)) {
result = Curl_ssl_scache_take(cf, data, ssl_peer_key, &sc_session);
if(!result && sc_session && sc_session->sdata && sc_session->sdata_len) {
WOLFSSL_SESSION *session;
sdata = psdata;
session = wolfSSL_d2i_SSL_SESSION(NULL, &sdata, (long)slen);
/* wolfSSL changes the passed pointer for whatever reasons, yikes */
const unsigned char *sdata = sc_session->sdata;
session = wolfSSL_d2i_SSL_SESSION(NULL, &sdata,
(long)sc_session->sdata_len);
if(session) {
int ret = wolfSSL_set_session(wss->handle, session);
if(ret != WOLFSSL_SUCCESS) {
Curl_ssl_delsessionid(data, psdata);
infof(data, "previous session not accepted (%d), "
Curl_ssl_session_destroy(sc_session);
sc_session = NULL;
infof(data, "cached session not accepted (%d), "
"removing from cache", ret);
}
else
@ -496,7 +496,7 @@ CURLcode wssl_setup_session(struct Curl_cfilter *cf,
failf(data, "could not decode previous session");
}
}
Curl_ssl_sessionid_unlock(data);
Curl_ssl_scache_return(cf, data, ssl_peer_key, sc_session);
return result;
}
@ -1188,7 +1188,7 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
/* Check if there is a cached ID we can/should use here! */
if(ssl_config->primary.cache_session) {
/* Set session from cache if there is one */
(void)wssl_setup_session(cf, data, backend, &connssl->peer);
(void)Curl_wssl_setup_session(cf, data, backend, connssl->peer.scache_key);
/* Register to get notified when a new session is received */
wolfSSL_set_app_data(backend->handle, cf);
wolfSSL_CTX_sess_set_new_cb(backend->ctx, wssl_vtls_new_session_cb);
@ -1792,7 +1792,7 @@ static ssize_t wolfssl_recv(struct Curl_cfilter *cf,
}
static size_t wolfssl_version(char *buffer, size_t size)
size_t Curl_wssl_version(char *buffer, size_t size)
{
#if LIBWOLFSSL_VERSION_HEX >= 0x03006000
return msnprintf(buffer, size, "wolfSSL/%s", wolfSSL_lib_version());
@ -2030,7 +2030,7 @@ const struct Curl_ssl Curl_ssl_wolfssl = {
wolfssl_init, /* init */
wolfssl_cleanup, /* cleanup */
wolfssl_version, /* version */
Curl_wssl_version, /* version */
wolfssl_shutdown, /* shutdown */
wolfssl_data_pending, /* data_pending */
wolfssl_random, /* random */

View File

@ -47,19 +47,23 @@ struct wolfssl_ctx {
BIT(shutting_down); /* TLS is being shut down */
};
size_t Curl_wssl_version(char *buffer, size_t size);
CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct wolfssl_ctx *wssl);
CURLcode wssl_setup_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct wolfssl_ctx *wss,
struct ssl_peer *peer);
CURLcode Curl_wssl_setup_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct wolfssl_ctx *wss,
const char *ssl_peer_key);
CURLcode wssl_cache_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
WOLFSSL_SESSION *session);
CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
WOLFSSL_SESSION *session,
int ietf_tls_id,
const char *alpn);
#endif /* USE_WOLFSSL */

View File

@ -617,7 +617,7 @@ class TestDownload:
earlydata = {}
reused_session = False
for line in r.trace_lines:
m = re.match(r'^\[t-(\d+)] EarlyData: (\d+)', line)
m = re.match(r'^\[t-(\d+)] EarlyData: (-?\d+)', line)
if m:
earlydata[int(m.group(1))] = int(m.group(2))
continue

View File

@ -134,4 +134,4 @@ class TestAuth:
# Depending on protocol, we might have an error sending or
# the server might shutdown the connection and we see the error
# on receiving
assert r.exit_code in [55, 56], f'{self.dump_logs()}'
assert r.exit_code in [55, 56], f'{r.dump_logs()}'