xref: /curl/lib/vtls/vtls_scache.c (revision 68bd759c)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 
25 /* This file is for implementing all "generic" SSL functions that all libcurl
26    internals should use. It is then responsible for calling the proper
27    "backend" function.
28 
29    SSL-functions in libcurl should call functions in this source file, and not
30    to any specific SSL-layer.
31 
32    Curl_ssl_ - prefix for generic ones
33 
34    Note that this source code uses the functions of the configured SSL
35    backend via the global Curl_ssl instance.
36 
37    "SSL/TLS Strong Encryption: An Introduction"
38    https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html
39 */
40 
41 #include "curl_setup.h"
42 
43 #ifdef USE_SSL
44 
45 #ifdef HAVE_SYS_TYPES_H
46 #include <sys/types.h>
47 #endif
48 #ifdef HAVE_SYS_STAT_H
49 #include <sys/stat.h>
50 #endif
51 #ifdef HAVE_FCNTL_H
52 #include <fcntl.h>
53 #endif
54 
55 #include "urldata.h"
56 #include "cfilters.h"
57 
58 #include "vtls.h" /* generic SSL protos etc */
59 #include "vtls_int.h"
60 #include "vtls_scache.h"
61 
62 #include "strcase.h"
63 #include "url.h"
64 #include "llist.h"
65 #include "share.h"
66 #include "curl_trc.h"
67 #include "curl_sha256.h"
68 #include "warnless.h"
69 #include "curl_printf.h"
70 #include "strdup.h"
71 
72 /* The last #include files should be: */
73 #include "curl_memory.h"
74 #include "memdebug.h"
75 
76 /* a peer+tls-config we cache sessions for */
77 struct Curl_ssl_scache_peer {
78   char *ssl_peer_key;      /* id for peer + relevant TLS configuration */
79   char *clientcert;
80   char *srp_username;
81   char *srp_password;
82   struct Curl_llist sessions;
83   void *sobj;              /* object instance or NULL */
84   Curl_ssl_scache_obj_dtor *sobj_free; /* free `sobj` callback */
85   unsigned char key_salt[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */
86   unsigned char key_hmac[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */
87   size_t max_sessions;
88   long age;                /* just a number, the higher the more recent */
89   BIT(hmac_set);           /* if key_salt and key_hmac are present */
90 };
91 
92 struct Curl_ssl_scache {
93   struct Curl_ssl_scache_peer *peers;
94   size_t peer_count;
95   int default_lifetime_secs;
96   long age;
97 };
98 
cf_ssl_scache_clear_session(struct Curl_ssl_session * s)99 static void cf_ssl_scache_clear_session(struct Curl_ssl_session *s)
100 {
101   if(s->sdata) {
102     free((void *)s->sdata);
103     s->sdata = NULL;
104   }
105   s->sdata_len = 0;
106   if(s->quic_tp) {
107     free((void *)s->quic_tp);
108     s->quic_tp = NULL;
109   }
110   s->quic_tp_len = 0;
111   s->ietf_tls_id = 0;
112   s->time_received = 0;
113   s->lifetime_secs = 0;
114   Curl_safefree(s->alpn);
115 }
116 
cf_ssl_scache_sesssion_ldestroy(void * udata,void * s)117 static void cf_ssl_scache_sesssion_ldestroy(void *udata, void *s)
118 {
119   (void)udata;
120   cf_ssl_scache_clear_session(s);
121   free(s);
122 }
123 
124 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,size_t earlydata_max,struct Curl_ssl_session ** psession)125 Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len,
126                         int ietf_tls_id, const char *alpn,
127                         curl_off_t time_received, long lifetime_secs,
128                         size_t earlydata_max,
129                         struct Curl_ssl_session **psession)
130 {
131   return Curl_ssl_session_create2(sdata, sdata_len, ietf_tls_id, alpn,
132                                   time_received, lifetime_secs,
133                                   earlydata_max, NULL, 0, psession);
134 }
135 
136 CURLcode
Curl_ssl_session_create2(unsigned char * sdata,size_t sdata_len,int ietf_tls_id,const char * alpn,curl_off_t time_received,long lifetime_secs,size_t earlydata_max,unsigned char * quic_tp,size_t quic_tp_len,struct Curl_ssl_session ** psession)137 Curl_ssl_session_create2(unsigned char *sdata, size_t sdata_len,
138                          int ietf_tls_id, const char *alpn,
139                          curl_off_t time_received, long lifetime_secs,
140                          size_t earlydata_max,
141                          unsigned char *quic_tp, size_t quic_tp_len,
142                          struct Curl_ssl_session **psession)
143 {
144   struct Curl_ssl_session *s;
145 
146   if(!sdata || !sdata_len) {
147     free(sdata);
148     return CURLE_BAD_FUNCTION_ARGUMENT;
149   }
150 
151   *psession = NULL;
152   s = calloc(1, sizeof(*s));
153   if(!s) {
154     free(sdata);
155     free(quic_tp);
156     return CURLE_OUT_OF_MEMORY;
157   }
158 
159   s->ietf_tls_id = ietf_tls_id;
160   s->time_received = time_received;
161   if(lifetime_secs < 0)
162     lifetime_secs = -1; /* unknown */
163   else if((s->ietf_tls_id == CURL_IETF_PROTO_TLS1_3) &&
164           (lifetime_secs > CURL_SCACHE_MAX_13_LIFETIME_SEC))
165     lifetime_secs = CURL_SCACHE_MAX_13_LIFETIME_SEC;
166   else if(lifetime_secs > CURL_SCACHE_MAX_12_LIFETIME_SEC)
167     lifetime_secs = CURL_SCACHE_MAX_12_LIFETIME_SEC;
168 
169   s->lifetime_secs = (int)lifetime_secs;
170   s->earlydata_max = earlydata_max;
171   s->sdata = sdata;
172   s->sdata_len = sdata_len;
173   s->quic_tp = quic_tp;
174   s->quic_tp_len = quic_tp_len;
175   if(alpn) {
176     s->alpn = strdup(alpn);
177     if(!s->alpn) {
178       cf_ssl_scache_sesssion_ldestroy(NULL, s);
179       return CURLE_OUT_OF_MEMORY;
180     }
181   }
182   *psession = s;
183   return CURLE_OK;
184 }
185 
Curl_ssl_session_destroy(struct Curl_ssl_session * s)186 void Curl_ssl_session_destroy(struct Curl_ssl_session *s)
187 {
188   if(s) {
189     /* if in the list, the list destructor takes care of it */
190     if(Curl_node_llist(&s->list))
191       Curl_node_remove(&s->list);
192     else {
193       cf_ssl_scache_sesssion_ldestroy(NULL, s);
194     }
195   }
196 }
197 
cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer * peer)198 static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer)
199 {
200   Curl_llist_destroy(&peer->sessions, NULL);
201   if(peer->sobj) {
202     DEBUGASSERT(peer->sobj_free);
203     if(peer->sobj_free)
204       peer->sobj_free(peer->sobj);
205     peer->sobj = NULL;
206   }
207   peer->sobj_free = NULL;
208   Curl_safefree(peer->clientcert);
209 #ifdef USE_TLS_SRP
210   Curl_safefree(peer->srp_username);
211   Curl_safefree(peer->srp_password);
212 #endif
213   Curl_safefree(peer->ssl_peer_key);
214   peer->age = 0;
215   peer->hmac_set = FALSE;
216 }
217 
cf_ssl_scache_peer_set_obj(struct Curl_ssl_scache_peer * peer,void * sobj,Curl_ssl_scache_obj_dtor * sobj_free)218 static void cf_ssl_scache_peer_set_obj(struct Curl_ssl_scache_peer *peer,
219                                        void *sobj,
220                                        Curl_ssl_scache_obj_dtor *sobj_free)
221 {
222   DEBUGASSERT(peer);
223   if(peer->sobj_free) {
224     peer->sobj_free(peer->sobj);
225   }
226   peer->sobj = sobj;
227   peer->sobj_free = sobj_free;
228 }
229 
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)230 static CURLcode cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer,
231                                         const char *ssl_peer_key,
232                                         const char *clientcert,
233                                         const char *srp_username,
234                                         const char *srp_password)
235 {
236   CURLcode result = CURLE_OUT_OF_MEMORY;
237 
238   DEBUGASSERT(!peer->ssl_peer_key);
239   peer->ssl_peer_key = strdup(ssl_peer_key);
240   if(!peer->ssl_peer_key)
241     goto out;
242   if(clientcert) {
243     peer->clientcert = strdup(clientcert);
244     if(!peer->clientcert)
245       goto out;
246   }
247   if(srp_username) {
248     peer->srp_username = strdup(srp_username);
249     if(!peer->srp_username)
250       goto out;
251   }
252   if(srp_password) {
253     peer->srp_password = strdup(srp_password);
254     if(!peer->srp_password)
255       goto out;
256   }
257   result = CURLE_OK;
258 out:
259   if(result)
260     cf_ssl_scache_clear_peer(peer);
261   return result;
262 }
263 
cf_scache_session_remove(struct Curl_ssl_scache_peer * peer,struct Curl_ssl_session * s)264 static void cf_scache_session_remove(struct Curl_ssl_scache_peer *peer,
265                                      struct Curl_ssl_session *s)
266 {
267   (void)peer;
268   DEBUGASSERT(Curl_node_llist(&s->list) == &peer->sessions);
269   Curl_ssl_session_destroy(s);
270 }
271 
cf_scache_session_expired(struct Curl_ssl_session * s,curl_off_t now)272 static bool cf_scache_session_expired(struct Curl_ssl_session *s,
273                                       curl_off_t now)
274 {
275   return (s->lifetime_secs > 0 &&
276          (s->time_received + s->lifetime_secs) < now);
277 }
278 
cf_scache_peer_remove_expired(struct Curl_ssl_scache_peer * peer,curl_off_t now)279 static void cf_scache_peer_remove_expired(struct Curl_ssl_scache_peer *peer,
280                                           curl_off_t now)
281 {
282   struct Curl_llist_node *n = Curl_llist_head(&peer->sessions);
283   while(n) {
284     struct Curl_ssl_session *s = Curl_node_elem(n);
285     n = Curl_node_next(n);
286     if(cf_scache_session_expired(s, now))
287       cf_scache_session_remove(peer, s);
288   }
289 }
290 
cf_scache_peer_remove_non13(struct Curl_ssl_scache_peer * peer)291 static void cf_scache_peer_remove_non13(struct Curl_ssl_scache_peer *peer)
292 {
293   struct Curl_llist_node *n = Curl_llist_head(&peer->sessions);
294   while(n) {
295     struct Curl_ssl_session *s = Curl_node_elem(n);
296     n = Curl_node_next(n);
297     if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3)
298       cf_scache_session_remove(peer, s);
299   }
300 }
301 
Curl_ssl_scache_create(size_t max_peers,size_t max_sessions_per_peer,struct Curl_ssl_scache ** pscache)302 CURLcode Curl_ssl_scache_create(size_t max_peers,
303                                 size_t max_sessions_per_peer,
304                                 struct Curl_ssl_scache **pscache)
305 {
306   struct Curl_ssl_scache *scache;
307   struct Curl_ssl_scache_peer *peers;
308   size_t i;
309 
310   *pscache = NULL;
311   peers = calloc(max_peers, sizeof(*peers));
312   if(!peers)
313     return CURLE_OUT_OF_MEMORY;
314 
315   scache = calloc(1, sizeof(*scache));
316   if(!scache) {
317     free(peers);
318     return CURLE_OUT_OF_MEMORY;
319   }
320 
321   scache->default_lifetime_secs = (24*60*60); /* 1 day */
322   scache->peer_count = max_peers;
323   scache->peers = peers;
324   scache->age = 1;
325   for(i = 0; i < scache->peer_count; ++i) {
326     scache->peers[i].max_sessions = max_sessions_per_peer;
327     Curl_llist_init(&scache->peers[i].sessions,
328                     cf_ssl_scache_sesssion_ldestroy);
329   }
330 
331   *pscache = scache;
332   return CURLE_OK;
333 }
334 
Curl_ssl_scache_destroy(struct Curl_ssl_scache * scache)335 void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache)
336 {
337   if(scache) {
338     size_t i;
339     for(i = 0; i < scache->peer_count; ++i) {
340       cf_ssl_scache_clear_peer(&scache->peers[i]);
341     }
342     free(scache->peers);
343     free(scache);
344   }
345 }
346 
347 /* Lock shared SSL session data */
Curl_ssl_scache_lock(struct Curl_easy * data)348 void Curl_ssl_scache_lock(struct Curl_easy *data)
349 {
350   if(CURL_SHARE_ssl_scache(data))
351     Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE);
352 }
353 
354 /* Unlock shared SSL session data */
Curl_ssl_scache_unlock(struct Curl_easy * data)355 void Curl_ssl_scache_unlock(struct Curl_easy *data)
356 {
357   if(CURL_SHARE_ssl_scache(data))
358     Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
359 }
360 
cf_ssl_peer_key_add_path(struct dynbuf * buf,const char * name,char * path)361 static CURLcode cf_ssl_peer_key_add_path(struct dynbuf *buf,
362                                           const char *name,
363                                           char *path)
364 {
365   if(path && path[0]) {
366     /* We try to add absolute paths, so that the session key can stay
367      * valid when used in another process with different CWD. However,
368      * when a path does not exist, this does not work. Then, we add
369      * the path as is. */
370 #ifdef _WIN32
371     char abspath[_MAX_PATH];
372     if(_fullpath(abspath, path, _MAX_PATH))
373       return Curl_dyn_addf(buf, ":%s-%s", name, abspath);
374 #else
375     if(path[0] != '/') {
376       char *abspath = realpath(path, NULL);
377       if(abspath) {
378         CURLcode r = Curl_dyn_addf(buf, ":%s-%s", name, abspath);
379         (free)(abspath); /* allocated by libc, free without memdebug */
380         return r;
381       }
382     }
383 #endif
384     return Curl_dyn_addf(buf, ":%s-%s", name, path);
385   }
386   return CURLE_OK;
387 }
388 
cf_ssl_peer_key_add_hash(struct dynbuf * buf,const char * name,struct curl_blob * blob)389 static CURLcode cf_ssl_peer_key_add_hash(struct dynbuf *buf,
390                                           const char *name,
391                                           struct curl_blob *blob)
392 {
393   CURLcode r = CURLE_OK;
394   if(blob && blob->len) {
395     unsigned char hash[CURL_SHA256_DIGEST_LENGTH];
396     size_t i;
397 
398     r = Curl_dyn_addf(buf, ":%s-", name);
399     if(r)
400       goto out;
401     r = Curl_sha256it(hash, blob->data, blob->len);
402     if(r)
403       goto out;
404     for(i = 0; i < CURL_SHA256_DIGEST_LENGTH; ++i) {
405       r = Curl_dyn_addf(buf, "%02x", hash[i]);
406       if(r)
407         goto out;
408     }
409   }
410 out:
411   return r;
412 }
413 
Curl_ssl_peer_key_make(struct Curl_cfilter * cf,const struct ssl_peer * peer,const char * tls_id,char ** ppeer_key)414 CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf,
415                                 const struct ssl_peer *peer,
416                                 const char *tls_id,
417                                 char **ppeer_key)
418 {
419   struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf);
420   struct dynbuf buf;
421   size_t key_len;
422   CURLcode r;
423 
424   *ppeer_key = NULL;
425   Curl_dyn_init(&buf, 10 * 1024);
426 
427   r = Curl_dyn_addf(&buf, "%s:%d", peer->hostname, peer->port);
428   if(r)
429     goto out;
430 
431   switch(peer->transport) {
432   case TRNSPRT_TCP:
433     break;
434   case TRNSPRT_UDP:
435     r = Curl_dyn_add(&buf, ":UDP");
436     break;
437   case TRNSPRT_QUIC:
438     r = Curl_dyn_add(&buf, ":QUIC");
439     break;
440   case TRNSPRT_UNIX:
441     r = Curl_dyn_add(&buf, ":UNIX");
442     break;
443   default:
444     r = Curl_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport);
445     break;
446   }
447   if(r)
448     goto out;
449 
450   if(!ssl->verifypeer) {
451     r = Curl_dyn_add(&buf, ":NO-VRFY-PEER");
452     if(r)
453       goto out;
454   }
455   if(!ssl->verifyhost) {
456     r = Curl_dyn_add(&buf, ":NO-VRFY-HOST");
457     if(r)
458       goto out;
459   }
460   if(ssl->verifystatus) {
461     r = Curl_dyn_add(&buf, ":VRFY-STATUS");
462     if(r)
463       goto out;
464   }
465   if(!ssl->verifypeer || !ssl->verifyhost) {
466     if(cf->conn->bits.conn_to_host) {
467       r = Curl_dyn_addf(&buf, ":CHOST-%s", cf->conn->conn_to_host.name);
468       if(r)
469         goto out;
470     }
471     if(cf->conn->bits.conn_to_port) {
472       r = Curl_dyn_addf(&buf, ":CPORT-%d", cf->conn->conn_to_port);
473       if(r)
474         goto out;
475     }
476   }
477 
478   if(ssl->version || ssl->version_max) {
479     r = Curl_dyn_addf(&buf, ":TLSVER-%d-%d", ssl->version,
480                       (ssl->version_max >> 16));
481     if(r)
482       goto out;
483   }
484   if(ssl->ssl_options) {
485     r = Curl_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options);
486     if(r)
487       goto out;
488   }
489   if(ssl->cipher_list) {
490     r = Curl_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list);
491     if(r)
492       goto out;
493   }
494   if(ssl->cipher_list13) {
495     r = Curl_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13);
496     if(r)
497       goto out;
498   }
499   if(ssl->curves) {
500     r = Curl_dyn_addf(&buf, ":CURVES-%s", ssl->curves);
501     if(r)
502       goto out;
503   }
504   if(ssl->verifypeer) {
505     r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile);
506     if(r)
507       goto out;
508     r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath);
509     if(r)
510       goto out;
511     r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile);
512     if(r)
513       goto out;
514     r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert);
515     if(r)
516       goto out;
517     if(ssl->cert_blob) {
518       r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob);
519       if(r)
520         goto out;
521     }
522     if(ssl->ca_info_blob) {
523       r = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob);
524       if(r)
525         goto out;
526     }
527     if(ssl->issuercert_blob) {
528       r = cf_ssl_peer_key_add_hash(&buf, "IssuerBlob", ssl->issuercert_blob);
529       if(r)
530         goto out;
531     }
532   }
533   if(ssl->pinned_key && ssl->pinned_key[0]) {
534     r = Curl_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key);
535     if(r)
536       goto out;
537   }
538 
539   if(ssl->clientcert && ssl->clientcert[0]) {
540     r = Curl_dyn_add(&buf, ":CCERT");
541     if(r)
542       goto out;
543   }
544 #ifdef USE_TLS_SRP
545   if(ssl->username || ssl->password) {
546     r = Curl_dyn_add(&buf, ":SRP-AUTH");
547     if(r)
548       goto out;
549   }
550 #endif
551 
552   if(!tls_id || !tls_id[0]) {
553     r = CURLE_FAILED_INIT;
554     goto out;
555   }
556   r = Curl_dyn_addf(&buf, ":IMPL-%s", tls_id);
557   if(r)
558     goto out;
559 
560   *ppeer_key = Curl_dyn_take(&buf, &key_len);
561   /* we just added printable char, and dynbuf always 0 terminates,
562    * no need to track length */
563 
564 
565 out:
566   Curl_dyn_free(&buf);
567   return r;
568 }
569 
cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer * peer,struct ssl_primary_config * conn_config)570 static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer,
571                                      struct ssl_primary_config *conn_config)
572 {
573   if(!Curl_safecmp(peer->clientcert, conn_config->clientcert))
574     return FALSE;
575 #ifdef USE_TLS_SRP
576    if(Curl_timestrcmp(peer->srp_username, conn_config->username) ||
577       Curl_timestrcmp(peer->srp_password, conn_config->password))
578      return FALSE;
579 #endif
580   return TRUE;
581 }
582 
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)583 static CURLcode cf_ssl_find_peer(struct Curl_cfilter *cf,
584                                  struct Curl_easy *data,
585                                  struct Curl_ssl_scache *scache,
586                                  const char *ssl_peer_key,
587                                  struct Curl_ssl_scache_peer **ppeer)
588 {
589   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
590   struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
591   size_t i, peer_key_len = 0;
592   CURLcode result = CURLE_OK;
593 
594   *ppeer = NULL;
595   if(!ssl_config || !ssl_config->primary.cache_session)
596     goto out;
597 
598   /* check for entries with known peer_key */
599   for(i = 0; scache && i < scache->peer_count; i++) {
600     if(scache->peers[i].ssl_peer_key &&
601        strcasecompare(ssl_peer_key, scache->peers[i].ssl_peer_key) &&
602        cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) {
603       /* yes, we have a cached session for this! */
604       *ppeer = &scache->peers[i];
605       goto out;
606     }
607   }
608   /* check for entries with HMAC set but no known peer_key */
609   for(i = 0; scache && i < scache->peer_count; i++) {
610     if(!scache->peers[i].ssl_peer_key &&
611        scache->peers[i].hmac_set &&
612        cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) {
613       /* possible entry with unknown peer_key, check hmac */
614       unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH];
615       if(!peer_key_len) /* we are lazy */
616         peer_key_len = strlen(ssl_peer_key);
617       result = Curl_hmacit(&Curl_HMAC_SHA256,
618                            scache->peers[i].key_salt,
619                            sizeof(scache->peers[i].key_salt),
620                            (const unsigned char *)ssl_peer_key,
621                            peer_key_len,
622                            my_hmac);
623       if(result)
624         goto out;
625       if(!memcmp(scache->peers[i].key_hmac, my_hmac, sizeof(my_hmac))) {
626         /* remember peer_key for future lookups */
627         scache->peers[i].ssl_peer_key = strdup(ssl_peer_key);
628         if(!scache->peers[i].ssl_peer_key) {
629           result = CURLE_OUT_OF_MEMORY;
630           goto out;
631         }
632         *ppeer = &scache->peers[i];
633         goto out;
634       }
635     }
636   }
637 out:
638   if(result)
639     CURL_TRC_CF(data, cf, "[SACHE] failure finding scache peer: %d", result);
640   return result;
641 }
642 
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)643 static CURLcode cf_ssl_add_peer(struct Curl_cfilter *cf,
644                                 struct Curl_easy *data,
645                                 struct Curl_ssl_scache *scache,
646                                 const char *ssl_peer_key,
647                                 struct Curl_ssl_scache_peer **ppeer)
648 {
649   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
650   struct Curl_ssl_scache_peer *peer = NULL;
651   size_t i;
652   CURLcode result;
653 
654   *ppeer = NULL;
655   result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
656   if(result || !scache->peer_count)
657     return result;
658 
659   if(peer) {
660     *ppeer = peer;
661     return CURLE_OK;
662   }
663 
664   /* not there, find empty or oldest peer */
665   for(i = 0; i < scache->peer_count; ++i) {
666     /* free peer entry? */
667     if(!scache->peers[i].ssl_peer_key && !scache->peers[i].hmac_set) {
668       peer = &scache->peers[i];
669       break;
670     }
671     /* peer without sessions and obj */
672     if(!scache->peers[i].sobj &&
673        !Curl_llist_count(&scache->peers[i].sessions)) {
674       peer = &scache->peers[i];
675       break;
676     }
677     /* remember "oldest" peer */
678     if(!peer || (scache->peers[i].age < peer->age)) {
679       peer = &scache->peers[i];
680     }
681   }
682   DEBUGASSERT(peer);
683   if(!peer)
684     return CURLE_OK;
685   /* clear previous peer and reinit */
686   cf_ssl_scache_clear_peer(peer);
687   result = cf_ssl_scache_peer_init(peer, ssl_peer_key,
688                                    conn_config->clientcert,
689 #ifdef USE_TLS_SRP
690                                    conn_config->username,
691                                    conn_config->password);
692 #else
693                                    NULL, NULL);
694 #endif
695   if(result)
696     goto out;
697   /* all ready */
698   *ppeer = peer;
699   result = CURLE_OK;
700 
701 out:
702   if(result) {
703     cf_ssl_scache_clear_peer(peer);
704     CURL_TRC_CF(data, cf, "[SACHE] failure adding peer: %d", result);
705   }
706   return result;
707 }
708 
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)709 static CURLcode cf_scache_peer_add_session(struct Curl_cfilter *cf,
710                                            struct Curl_easy *data,
711                                            struct Curl_ssl_scache *scache,
712                                            const char *ssl_peer_key,
713                                            struct Curl_ssl_session *s)
714 {
715   struct Curl_ssl_scache_peer *peer = NULL;
716   CURLcode result = CURLE_OUT_OF_MEMORY;
717   curl_off_t now = (curl_off_t)time(NULL);
718 
719   if(!scache || !scache->peer_count) {
720     Curl_ssl_session_destroy(s);
721     return CURLE_OK;
722   }
723 
724   if(!s->time_received)
725     s->time_received = now;
726   if(s->lifetime_secs < 0)
727     s->lifetime_secs = scache->default_lifetime_secs;
728 
729   if(cf_scache_session_expired(s, now)) {
730     CURL_TRC_CF(data, cf, "[SCACHE] add, session already expired");
731     Curl_ssl_session_destroy(s);
732     return CURLE_OK;
733   }
734 
735   result = cf_ssl_add_peer(cf, data, scache, ssl_peer_key, &peer);
736   if(result || !peer) {
737     CURL_TRC_CF(data, cf, "[SCACHE] unable to add scache peer: %d", result);
738     Curl_ssl_session_destroy(s);
739     goto out;
740   }
741 
742   /* A session not from TLSv1.3 replaces all other. */
743   if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3) {
744     Curl_llist_destroy(&peer->sessions, NULL);
745     Curl_llist_append(&peer->sessions, s, &s->list);
746   }
747   else {
748     /* Expire existing, append, trim from head to obey max_sessions */
749     cf_scache_peer_remove_expired(peer, now);
750     cf_scache_peer_remove_non13(peer);
751     Curl_llist_append(&peer->sessions, s, &s->list);
752     while(Curl_llist_count(&peer->sessions) > peer->max_sessions) {
753       Curl_node_remove(Curl_llist_head(&peer->sessions));
754     }
755   }
756 
757 out:
758   if(result) {
759     failf(data, "[SCACHE] failed to add session for %s, error=%d",
760           ssl_peer_key, result);
761   }
762   else
763     CURL_TRC_CF(data, cf, "[SCACHE] added session for %s [proto=0x%x, "
764                 "lifetime=%d, alpn=%s, earlydata=%zu, quic_tp=%s], "
765                 "peer has %zu sessions now",
766                 ssl_peer_key, s->ietf_tls_id, s->lifetime_secs, s->alpn,
767                 s->earlydata_max, s->quic_tp ? "yes" : "no",
768                 Curl_llist_count(&peer->sessions));
769   return result;
770 }
771 
Curl_ssl_scache_put(struct Curl_cfilter * cf,struct Curl_easy * data,const char * ssl_peer_key,struct Curl_ssl_session * s)772 CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf,
773                              struct Curl_easy *data,
774                              const char *ssl_peer_key,
775                              struct Curl_ssl_session *s)
776 {
777   struct Curl_ssl_scache *scache = data->state.ssl_scache;
778   CURLcode result;
779 
780   Curl_ssl_scache_lock(data);
781   result = cf_scache_peer_add_session(cf, data, scache, ssl_peer_key, s);
782   Curl_ssl_scache_unlock(data);
783   return result;
784 }
785 
Curl_ssl_scache_return(struct Curl_cfilter * cf,struct Curl_easy * data,const char * ssl_peer_key,struct Curl_ssl_session * s)786 void Curl_ssl_scache_return(struct Curl_cfilter *cf,
787                            struct Curl_easy *data,
788                            const char *ssl_peer_key,
789                            struct Curl_ssl_session *s)
790 {
791   /* See RFC 8446 C.4:
792    * "Clients SHOULD NOT reuse a ticket for multiple connections." */
793   if(s && s->ietf_tls_id < 0x304)
794     (void)Curl_ssl_scache_put(cf, data, ssl_peer_key, s);
795   else
796     Curl_ssl_session_destroy(s);
797 }
798 
Curl_ssl_scache_take(struct Curl_cfilter * cf,struct Curl_easy * data,const char * ssl_peer_key,struct Curl_ssl_session ** ps)799 CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf,
800                               struct Curl_easy *data,
801                               const char *ssl_peer_key,
802                               struct Curl_ssl_session **ps)
803 {
804   struct Curl_ssl_scache *scache = data->state.ssl_scache;
805   struct Curl_ssl_scache_peer *peer = NULL;
806   struct Curl_llist_node *n;
807   struct Curl_ssl_session *s = NULL;
808   CURLcode result;
809 
810   *ps = NULL;
811   if(!scache)
812     return CURLE_OK;
813 
814   Curl_ssl_scache_lock(data);
815   result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
816   if(!result && peer) {
817     cf_scache_peer_remove_expired(peer, (curl_off_t)time(NULL));
818     n = Curl_llist_head(&peer->sessions);
819     if(n) {
820       s = Curl_node_take_elem(n);
821       (scache->age)++;            /* increase general age */
822       peer->age = scache->age; /* set this as used in this age */
823     }
824   }
825   Curl_ssl_scache_unlock(data);
826   if(s) {
827     *ps = s;
828     CURL_TRC_CF(data, cf, "[SCACHE] took session for %s [proto=0x%x, "
829                 "lifetime=%d, alpn=%s, earlydata=%zu, quic_tp=%s], "
830                 "%zu sessions remain",
831                 ssl_peer_key, s->ietf_tls_id, s->lifetime_secs, s->alpn,
832                 s->earlydata_max, s->quic_tp ? "yes" : "no",
833                 Curl_llist_count(&peer->sessions));
834   }
835   else {
836     CURL_TRC_CF(data, cf, "[SCACHE] no cached session for %s", ssl_peer_key);
837   }
838   return result;
839 }
840 
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)841 CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf,
842                                  struct Curl_easy *data,
843                                  const char *ssl_peer_key,
844                                  void *sobj,
845                                  Curl_ssl_scache_obj_dtor *sobj_free)
846 {
847   struct Curl_ssl_scache *scache = data->state.ssl_scache;
848   struct Curl_ssl_scache_peer *peer = NULL;
849   CURLcode result;
850 
851   DEBUGASSERT(sobj);
852   DEBUGASSERT(sobj_free);
853 
854   result = cf_ssl_add_peer(cf, data, scache, ssl_peer_key, &peer);
855   if(result || !peer) {
856     CURL_TRC_CF(data, cf, "[SCACHE] unable to add scache peer: %d", result);
857     goto out;
858   }
859 
860   cf_ssl_scache_peer_set_obj(peer, sobj, sobj_free);
861   sobj = NULL;  /* peer took ownership */
862 
863 out:
864   if(sobj && sobj_free)
865     sobj_free(sobj);
866   return result;
867 }
868 
Curl_ssl_scache_get_obj(struct Curl_cfilter * cf,struct Curl_easy * data,const char * ssl_peer_key,void ** sobj)869 bool Curl_ssl_scache_get_obj(struct Curl_cfilter *cf,
870                              struct Curl_easy *data,
871                              const char *ssl_peer_key,
872                              void **sobj)
873 {
874   struct Curl_ssl_scache *scache = data->state.ssl_scache;
875   struct Curl_ssl_scache_peer *peer = NULL;
876   CURLcode result;
877 
878   *sobj = NULL;
879   if(!scache)
880     return FALSE;
881 
882   result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
883   if(result)
884     return FALSE;
885 
886   if(peer)
887     *sobj = peer->sobj;
888 
889   CURL_TRC_CF(data, cf, "[SACHE] %s cached session for '%s'",
890               *sobj ? "Found" : "No", ssl_peer_key);
891   return !!*sobj;
892 }
893 
Curl_ssl_scache_remove_all(struct Curl_cfilter * cf,struct Curl_easy * data,const char * ssl_peer_key)894 void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf,
895                                 struct Curl_easy *data,
896                                 const char *ssl_peer_key)
897 {
898   struct Curl_ssl_scache *scache = data->state.ssl_scache;
899   struct Curl_ssl_scache_peer *peer = NULL;
900   CURLcode result;
901 
902   (void)cf;
903   if(!scache)
904     return;
905 
906   Curl_ssl_scache_lock(data);
907   result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
908   if(!result && peer)
909     cf_ssl_scache_clear_peer(peer);
910   Curl_ssl_scache_unlock(data);
911 }
912 
913 #endif /* USE_SSL */
914