]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
lib: TLS session ticket caching reworked
authorStefan Eissing <stefan@eissing.org>
Wed, 18 Dec 2024 12:22:35 +0000 (13:22 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 20 Dec 2024 13:59:23 +0000 (14:59 +0100)
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

36 files changed:
docs/Makefile.am
docs/internals/DYNBUF.md
docs/internals/LLIST.md
docs/internals/TLS-SESSIONS.md [new file with mode: 0644]
lib/Makefile.inc
lib/dynbuf.c
lib/dynbuf.h
lib/llist.c
lib/llist.h
lib/setopt.c
lib/share.c
lib/share.h
lib/transfer.c
lib/urldata.h
lib/vquic/curl_ngtcp2.c
lib/vquic/curl_osslq.c
lib/vquic/curl_quiche.c
lib/vquic/vquic-tls.c
lib/vquic/vquic-tls.h
lib/vtls/bearssl.c
lib/vtls/gtls.c
lib/vtls/gtls.h
lib/vtls/mbedtls.c
lib/vtls/openssl.c
lib/vtls/openssl.h
lib/vtls/schannel.c
lib/vtls/sectransp.c
lib/vtls/vtls.c
lib/vtls/vtls.h
lib/vtls/vtls_int.h
lib/vtls/vtls_scache.c [new file with mode: 0644]
lib/vtls/vtls_scache.h [new file with mode: 0644]
lib/vtls/wolfssl.c
lib/vtls/wolfssl.h
tests/http/test_02_download.py
tests/http/test_14_auth.py

index 8320184970b8eaaf6bbca46d16ff1c02de7b499f..02c4dcc017d4594588c4797adbc7bfc4aa9abe2a 100644 (file)
@@ -57,6 +57,7 @@ INTERNALDOCS =                                  \
  internals/README.md                            \
  internals/SPLAY.md                             \
  internals/STRPARSE.md                          \
+ internals/TLS-SESSIONS.md                      \
  internals/WEBSOCKET.md
 
 EXTRA_DIST =                                    \
index 01fe332ab688ea8ce43c9176771d66768212c8b6..610ec577de5c43e99d010d817bcf282a7cd20d2e 100644 (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.
index ee9a89badb857164d6f4b16f00a9f91dc1bc954a..c1069ce70eb2c7744d714ecb4ee354b935dfcabd 100644 (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
 
diff --git a/docs/internals/TLS-SESSIONS.md b/docs/internals/TLS-SESSIONS.md
new file mode 100644 (file)
index 0000000..36fecfc
--- /dev/null
@@ -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.
index 8529246160226fdca79d30763a018d0e93f545a1..e22094a9267281c30f455d88c3dc423f7104ff0c 100644 (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
 
index eab07efbf050098e3d8832da6bedc627fdf6cad5..15164265f480d998359f542faa8051ce99b09c50 100644 (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.
  */
index 7dbaab886e2eb9a5ac4fa6388b094debeb74096c..154b54cd9c29358b006af885c2899b41c31bf668 100644 (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
index e5c65fb90e4bbb3f0c2a66b8628d78c9106883b1..8edbab243143d70ef6a7c565a7d2c6d4cea656b0 100644 (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;
+}
+
+/*
+ * @unittest: 1300
+ */
+void
+Curl_node_uremove(struct Curl_llist_node *e, void *user)
+{
+  struct Curl_llist *list;
+  void *ptr;
+  if(!e)
+    return;
 
-  /* call the dtor() last for when it actually frees the 'e' memory itself */
+  list = e->_list;
+  DEBUGASSERT(list);
+  ptr = Curl_node_take_elem(e);
   if(list->_dtor)
     list->_dtor(user, ptr);
 }
index 26581869a3f662cf9cd112d0852fe863cd7a69bf..597c0e00a33fd1d2399bab0336b62fc48e149980 100644 (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);
index d9e0b3d0a57448154366ed4abf2fcbe77185a1ca..5b7b4af7b3b28d620a6f39be741ac91af2b8c1f0 100644 (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))
index 6cdba18ec40a05165b597bc098b500935aa898f1..8194aa864976d041324dae8086f9f6e770f3e110 100644 (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
 
index 124f7049f198160a3a511d42b595a979d1432a37..d0cdb1b2680b4c14a235093c8c86427ab2402d02 100644 (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 */
index d7d3d16f3eeed42c842abec35bba48bcb5265da0..92370065e3696c9d0b2a1c0a060e02d945380272 100644 (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 */
index 704fb7a1d221367e72212b4e4cdda72c980649ad..3f2ee4302eb5ccab2e1965c7bee0560145ad8ad6 100644 (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 */
index 37009abdc16004e31b03648a96be7cd9020a5c89..80b19d973b904fd003c2591979a09d963a1338e0 100644 (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,
index e5f737f8f0ab12bc6a46bd8f50742da3a67699b2..d9e5e1da3ae1c2c7a496029092336b8b07ac5848 100644 (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,
index 304849892ce84f8a43c9912cadaf7d4275aa7b35..0c294e2bfcd1dbc83af5e523fe81477ac5724009 100644 (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");
index 580e5707a8deb69cfe5d6b06b6d1d40fbd1ca98c..e9fda86481ecb5d40209f575f310bf480ed2c79f 100644 (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;
 }
 
index 969acad41efcab7dce3cc85b67ea1eac3748a10b..8ee6904a549d122e480572d101b30a4c29391aea 100644 (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;
index 55d57b9d5462599e9e9e6343876695d8128dc63d..acd158f027050bded938ea4441ae436e12c482e7 100644 (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;
   }
index 0bc14275c76a17a29815bc6ed8fc3efcef8c817a..451bc92cbe4ebc4b6f6db15ef57452d107f911ce 100644 (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;
-
-      rc = gnutls_session_set_data(gctx->session, ssl_sessionid, ssl_idsize);
-      if(rc < 0)
-        infof(data, "SSL failed to set session ID");
+    result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs);
+    if(result)
+      goto out;
+
+    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 */
index 4f9c540ed27aaa66a52ba7d83793ec372e5081f3..1b7c098cf0020e1d59307c8c07dd68903b936419 100644 (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;
 
index f30613197c5875c4befb356258d1e78f8a2c7286..1739084c9ac7d1eadfdb812a9b67fa4a46a1d423 100644 (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_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");
-    }
-    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_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;
 }
 
index 5b4a4158c4885b135c75d8cc4e511c8646bb58e9..babb88fd97d07051313cb32cff210fd97cf5df46 100644 (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 */
index 7aba947d18263dd97e793f61865694ed6c893062..383bb782191c24e7db32ecba470ac20ae81c9441 100644 (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
index a7298c2de640dd02616401c97c7aa99bea25c97d..e8ce9b7df32efc2d7658275982e43aead5b4ec19 100644 (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;
   }
 
index e93f90923b8124f65ecb70c50f271a14223e301c..a0f6dccc28dea6b92ca387af1284f91788e80675 100644 (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;
     }
index 0347da86983cad671ff740e68274a3d51088d7a3..02085f412ac7c8789f449dd34370ef551b50004b 100644 (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"
 #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) {
index 059a13c9b88254d6d6261c6bd9ee992c986db3f4..b751c3743c78f3d690ce666e95d2fc86fe9e77e2 100644 (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
index 0d5e9b090d69b5e685143db1c445f89811ab23b1..3a5611dfe43a784def41be1d865d63dae777c4dd 100644 (file)
 #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 */
diff --git a/lib/vtls/vtls_scache.c b/lib/vtls/vtls_scache.c
new file mode 100644 (file)
index 0000000..4ba054e
--- /dev/null
@@ -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 */
diff --git a/lib/vtls/vtls_scache.h b/lib/vtls/vtls_scache.h
new file mode 100644 (file)
index 0000000..a3e3cac
--- /dev/null
@@ -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 */
index 3e62d91456d3aa77c73d4bdc24946757fcdf05b0..800aa2ecb4c58e48101b6d4926e3c86da2b88f6a 100644 (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 */
index dc2967d69c7480bc95e5135db25ed15338f3ba2d..57935b60c714883708cb0460c521043b0c8313f5 100644 (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 */
index e7910719b3ebdcddfa9cbc6d6028201c82ecb332..b2acaca7b62aebbb889df8cb1571c56229bcdd49 100644 (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
index 8fa6767b0d17985b5533a3e5824a90726a6c93d3..57d63aea28ea78ab59c04f863c5dcebe1f4316a3 100644 (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()}'