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 #include "curl_setup.h"
26
27 #ifdef USE_SSL
28
29 #ifdef HAVE_SYS_TYPES_H
30 #include <sys/types.h>
31 #endif
32 #ifdef HAVE_SYS_STAT_H
33 #include <sys/stat.h>
34 #endif
35 #ifdef HAVE_FCNTL_H
36 #include <fcntl.h>
37 #endif
38
39 #include "urldata.h"
40 #include "cfilters.h"
41
42 #include "vtls.h" /* generic SSL protos etc */
43 #include "vtls_int.h"
44 #include "vtls_scache.h"
45 #include "vtls_spack.h"
46
47 #include "strcase.h"
48 #include "url.h"
49 #include "llist.h"
50 #include "share.h"
51 #include "curl_trc.h"
52 #include "curl_sha256.h"
53 #include "rand.h"
54 #include "warnless.h"
55 #include "curl_printf.h"
56 #include "strdup.h"
57
58 /* The last #include files should be: */
59 #include "curl_memory.h"
60 #include "memdebug.h"
61
62 /* a peer+tls-config we cache sessions for */
63 struct Curl_ssl_scache_peer {
64 char *ssl_peer_key; /* id for peer + relevant TLS configuration */
65 char *clientcert;
66 char *srp_username;
67 char *srp_password;
68 struct Curl_llist sessions;
69 void *sobj; /* object instance or NULL */
70 Curl_ssl_scache_obj_dtor *sobj_free; /* free `sobj` callback */
71 unsigned char key_salt[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */
72 unsigned char key_hmac[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */
73 size_t max_sessions;
74 long age; /* just a number, the higher the more recent */
75 BIT(hmac_set); /* if key_salt and key_hmac are present */
76 };
77
78 struct Curl_ssl_scache {
79 struct Curl_ssl_scache_peer *peers;
80 size_t peer_count;
81 int default_lifetime_secs;
82 long age;
83 };
84
cf_ssl_scache_clear_session(struct Curl_ssl_session * s)85 static void cf_ssl_scache_clear_session(struct Curl_ssl_session *s)
86 {
87 if(s->sdata) {
88 free((void *)s->sdata);
89 s->sdata = NULL;
90 }
91 s->sdata_len = 0;
92 if(s->quic_tp) {
93 free((void *)s->quic_tp);
94 s->quic_tp = NULL;
95 }
96 s->quic_tp_len = 0;
97 s->ietf_tls_id = 0;
98 s->valid_until = 0;
99 Curl_safefree(s->alpn);
100 }
101
cf_ssl_scache_sesssion_ldestroy(void * udata,void * s)102 static void cf_ssl_scache_sesssion_ldestroy(void *udata, void *s)
103 {
104 (void)udata;
105 cf_ssl_scache_clear_session(s);
106 free(s);
107 }
108
109 CURLcode
Curl_ssl_session_create(unsigned char * sdata,size_t sdata_len,int ietf_tls_id,const char * alpn,curl_off_t valid_until,size_t earlydata_max,struct Curl_ssl_session ** psession)110 Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len,
111 int ietf_tls_id, const char *alpn,
112 curl_off_t valid_until, size_t earlydata_max,
113 struct Curl_ssl_session **psession)
114 {
115 return Curl_ssl_session_create2(sdata, sdata_len, ietf_tls_id, alpn,
116 valid_until, earlydata_max,
117 NULL, 0, psession);
118 }
119
120 CURLcode
Curl_ssl_session_create2(unsigned char * sdata,size_t sdata_len,int ietf_tls_id,const char * alpn,curl_off_t valid_until,size_t earlydata_max,unsigned char * quic_tp,size_t quic_tp_len,struct Curl_ssl_session ** psession)121 Curl_ssl_session_create2(unsigned char *sdata, size_t sdata_len,
122 int ietf_tls_id, const char *alpn,
123 curl_off_t valid_until, size_t earlydata_max,
124 unsigned char *quic_tp, size_t quic_tp_len,
125 struct Curl_ssl_session **psession)
126 {
127 struct Curl_ssl_session *s;
128
129 if(!sdata || !sdata_len) {
130 free(sdata);
131 return CURLE_BAD_FUNCTION_ARGUMENT;
132 }
133
134 *psession = NULL;
135 s = calloc(1, sizeof(*s));
136 if(!s) {
137 free(sdata);
138 free(quic_tp);
139 return CURLE_OUT_OF_MEMORY;
140 }
141
142 s->ietf_tls_id = ietf_tls_id;
143 s->valid_until = valid_until;
144 s->earlydata_max = earlydata_max;
145 s->sdata = sdata;
146 s->sdata_len = sdata_len;
147 s->quic_tp = quic_tp;
148 s->quic_tp_len = quic_tp_len;
149 if(alpn) {
150 s->alpn = strdup(alpn);
151 if(!s->alpn) {
152 cf_ssl_scache_sesssion_ldestroy(NULL, s);
153 return CURLE_OUT_OF_MEMORY;
154 }
155 }
156 *psession = s;
157 return CURLE_OK;
158 }
159
Curl_ssl_session_destroy(struct Curl_ssl_session * s)160 void Curl_ssl_session_destroy(struct Curl_ssl_session *s)
161 {
162 if(s) {
163 /* if in the list, the list destructor takes care of it */
164 if(Curl_node_llist(&s->list))
165 Curl_node_remove(&s->list);
166 else {
167 cf_ssl_scache_sesssion_ldestroy(NULL, s);
168 }
169 }
170 }
171
cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer * peer)172 static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer)
173 {
174 Curl_llist_destroy(&peer->sessions, NULL);
175 if(peer->sobj) {
176 DEBUGASSERT(peer->sobj_free);
177 if(peer->sobj_free)
178 peer->sobj_free(peer->sobj);
179 peer->sobj = NULL;
180 }
181 peer->sobj_free = NULL;
182 Curl_safefree(peer->clientcert);
183 #ifdef USE_TLS_SRP
184 Curl_safefree(peer->srp_username);
185 Curl_safefree(peer->srp_password);
186 #endif
187 Curl_safefree(peer->ssl_peer_key);
188 peer->age = 0;
189 peer->hmac_set = FALSE;
190 }
191
cf_ssl_scache_peer_set_obj(struct Curl_ssl_scache_peer * peer,void * sobj,Curl_ssl_scache_obj_dtor * sobj_free)192 static void cf_ssl_scache_peer_set_obj(struct Curl_ssl_scache_peer *peer,
193 void *sobj,
194 Curl_ssl_scache_obj_dtor *sobj_free)
195 {
196 DEBUGASSERT(peer);
197 if(peer->sobj_free) {
198 peer->sobj_free(peer->sobj);
199 }
200 peer->sobj = sobj;
201 peer->sobj_free = sobj_free;
202 }
203
204 static CURLcode
cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer * peer,const char * ssl_peer_key,const char * clientcert,const char * srp_username,const char * srp_password,const unsigned char * salt,const unsigned char * hmac)205 cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer,
206 const char *ssl_peer_key,
207 const char *clientcert,
208 const char *srp_username,
209 const char *srp_password,
210 const unsigned char *salt,
211 const unsigned char *hmac)
212 {
213 CURLcode result = CURLE_OUT_OF_MEMORY;
214
215 DEBUGASSERT(!peer->ssl_peer_key);
216 if(ssl_peer_key) {
217 peer->ssl_peer_key = strdup(ssl_peer_key);
218 if(!peer->ssl_peer_key)
219 goto out;
220 peer->hmac_set = FALSE;
221 }
222 else if(salt && hmac) {
223 memcpy(peer->key_salt, salt, sizeof(peer->key_salt));
224 memcpy(peer->key_hmac, hmac, sizeof(peer->key_hmac));
225 peer->hmac_set = TRUE;
226 }
227 else {
228 result = CURLE_BAD_FUNCTION_ARGUMENT;
229 goto out;
230 }
231 if(clientcert) {
232 peer->clientcert = strdup(clientcert);
233 if(!peer->clientcert)
234 goto out;
235 }
236 if(srp_username) {
237 peer->srp_username = strdup(srp_username);
238 if(!peer->srp_username)
239 goto out;
240 }
241 if(srp_password) {
242 peer->srp_password = strdup(srp_password);
243 if(!peer->srp_password)
244 goto out;
245 }
246 result = CURLE_OK;
247 out:
248 if(result)
249 cf_ssl_scache_clear_peer(peer);
250 return result;
251 }
252
cf_scache_session_remove(struct Curl_ssl_scache_peer * peer,struct Curl_ssl_session * s)253 static void cf_scache_session_remove(struct Curl_ssl_scache_peer *peer,
254 struct Curl_ssl_session *s)
255 {
256 (void)peer;
257 DEBUGASSERT(Curl_node_llist(&s->list) == &peer->sessions);
258 Curl_ssl_session_destroy(s);
259 }
260
cf_scache_session_expired(struct Curl_ssl_session * s,curl_off_t now)261 static bool cf_scache_session_expired(struct Curl_ssl_session *s,
262 curl_off_t now)
263 {
264 return (s->valid_until > 0) && (s->valid_until < now);
265 }
266
cf_scache_peer_remove_expired(struct Curl_ssl_scache_peer * peer,curl_off_t now)267 static void cf_scache_peer_remove_expired(struct Curl_ssl_scache_peer *peer,
268 curl_off_t now)
269 {
270 struct Curl_llist_node *n = Curl_llist_head(&peer->sessions);
271 while(n) {
272 struct Curl_ssl_session *s = Curl_node_elem(n);
273 n = Curl_node_next(n);
274 if(cf_scache_session_expired(s, now))
275 cf_scache_session_remove(peer, s);
276 }
277 }
278
cf_scache_peer_remove_non13(struct Curl_ssl_scache_peer * peer)279 static void cf_scache_peer_remove_non13(struct Curl_ssl_scache_peer *peer)
280 {
281 struct Curl_llist_node *n = Curl_llist_head(&peer->sessions);
282 while(n) {
283 struct Curl_ssl_session *s = Curl_node_elem(n);
284 n = Curl_node_next(n);
285 if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3)
286 cf_scache_session_remove(peer, s);
287 }
288 }
289
Curl_ssl_scache_create(size_t max_peers,size_t max_sessions_per_peer,struct Curl_ssl_scache ** pscache)290 CURLcode Curl_ssl_scache_create(size_t max_peers,
291 size_t max_sessions_per_peer,
292 struct Curl_ssl_scache **pscache)
293 {
294 struct Curl_ssl_scache *scache;
295 struct Curl_ssl_scache_peer *peers;
296 size_t i;
297
298 *pscache = NULL;
299 peers = calloc(max_peers, sizeof(*peers));
300 if(!peers)
301 return CURLE_OUT_OF_MEMORY;
302
303 scache = calloc(1, sizeof(*scache));
304 if(!scache) {
305 free(peers);
306 return CURLE_OUT_OF_MEMORY;
307 }
308
309 scache->default_lifetime_secs = (24*60*60); /* 1 day */
310 scache->peer_count = max_peers;
311 scache->peers = peers;
312 scache->age = 1;
313 for(i = 0; i < scache->peer_count; ++i) {
314 scache->peers[i].max_sessions = max_sessions_per_peer;
315 Curl_llist_init(&scache->peers[i].sessions,
316 cf_ssl_scache_sesssion_ldestroy);
317 }
318
319 *pscache = scache;
320 return CURLE_OK;
321 }
322
Curl_ssl_scache_destroy(struct Curl_ssl_scache * scache)323 void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache)
324 {
325 if(scache) {
326 size_t i;
327 for(i = 0; i < scache->peer_count; ++i) {
328 cf_ssl_scache_clear_peer(&scache->peers[i]);
329 }
330 free(scache->peers);
331 free(scache);
332 }
333 }
334
335 /* Lock shared SSL session data */
Curl_ssl_scache_lock(struct Curl_easy * data)336 void Curl_ssl_scache_lock(struct Curl_easy *data)
337 {
338 if(CURL_SHARE_ssl_scache(data))
339 Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE);
340 }
341
342 /* Unlock shared SSL session data */
Curl_ssl_scache_unlock(struct Curl_easy * data)343 void Curl_ssl_scache_unlock(struct Curl_easy *data)
344 {
345 if(CURL_SHARE_ssl_scache(data))
346 Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
347 }
348
cf_ssl_peer_key_add_path(struct dynbuf * buf,const char * name,char * path)349 static CURLcode cf_ssl_peer_key_add_path(struct dynbuf *buf,
350 const char *name,
351 char *path)
352 {
353 if(path && path[0]) {
354 /* We try to add absolute paths, so that the session key can stay
355 * valid when used in another process with different CWD. However,
356 * when a path does not exist, this does not work. Then, we add
357 * the path as is. */
358 #ifdef _WIN32
359 char abspath[_MAX_PATH];
360 if(_fullpath(abspath, path, _MAX_PATH))
361 return Curl_dyn_addf(buf, ":%s-%s", name, abspath);
362 #else
363 if(path[0] != '/') {
364 char *abspath = realpath(path, NULL);
365 if(abspath) {
366 CURLcode r = Curl_dyn_addf(buf, ":%s-%s", name, abspath);
367 (free)(abspath); /* allocated by libc, free without memdebug */
368 return r;
369 }
370 }
371 #endif
372 return Curl_dyn_addf(buf, ":%s-%s", name, path);
373 }
374 return CURLE_OK;
375 }
376
cf_ssl_peer_key_add_hash(struct dynbuf * buf,const char * name,struct curl_blob * blob)377 static CURLcode cf_ssl_peer_key_add_hash(struct dynbuf *buf,
378 const char *name,
379 struct curl_blob *blob)
380 {
381 CURLcode r = CURLE_OK;
382 if(blob && blob->len) {
383 unsigned char hash[CURL_SHA256_DIGEST_LENGTH];
384 size_t i;
385
386 r = Curl_dyn_addf(buf, ":%s-", name);
387 if(r)
388 goto out;
389 r = Curl_sha256it(hash, blob->data, blob->len);
390 if(r)
391 goto out;
392 for(i = 0; i < CURL_SHA256_DIGEST_LENGTH; ++i) {
393 r = Curl_dyn_addf(buf, "%02x", hash[i]);
394 if(r)
395 goto out;
396 }
397 }
398 out:
399 return r;
400 }
401
Curl_ssl_peer_key_make(struct Curl_cfilter * cf,const struct ssl_peer * peer,const char * tls_id,char ** ppeer_key)402 CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf,
403 const struct ssl_peer *peer,
404 const char *tls_id,
405 char **ppeer_key)
406 {
407 struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf);
408 struct dynbuf buf;
409 size_t key_len;
410 CURLcode r;
411
412 *ppeer_key = NULL;
413 Curl_dyn_init(&buf, 10 * 1024);
414
415 r = Curl_dyn_addf(&buf, "%s:%d", peer->hostname, peer->port);
416 if(r)
417 goto out;
418
419 switch(peer->transport) {
420 case TRNSPRT_TCP:
421 break;
422 case TRNSPRT_UDP:
423 r = Curl_dyn_add(&buf, ":UDP");
424 break;
425 case TRNSPRT_QUIC:
426 r = Curl_dyn_add(&buf, ":QUIC");
427 break;
428 case TRNSPRT_UNIX:
429 r = Curl_dyn_add(&buf, ":UNIX");
430 break;
431 default:
432 r = Curl_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport);
433 break;
434 }
435 if(r)
436 goto out;
437
438 if(!ssl->verifypeer) {
439 r = Curl_dyn_add(&buf, ":NO-VRFY-PEER");
440 if(r)
441 goto out;
442 }
443 if(!ssl->verifyhost) {
444 r = Curl_dyn_add(&buf, ":NO-VRFY-HOST");
445 if(r)
446 goto out;
447 }
448 if(ssl->verifystatus) {
449 r = Curl_dyn_add(&buf, ":VRFY-STATUS");
450 if(r)
451 goto out;
452 }
453 if(!ssl->verifypeer || !ssl->verifyhost) {
454 if(cf->conn->bits.conn_to_host) {
455 r = Curl_dyn_addf(&buf, ":CHOST-%s", cf->conn->conn_to_host.name);
456 if(r)
457 goto out;
458 }
459 if(cf->conn->bits.conn_to_port) {
460 r = Curl_dyn_addf(&buf, ":CPORT-%d", cf->conn->conn_to_port);
461 if(r)
462 goto out;
463 }
464 }
465
466 if(ssl->version || ssl->version_max) {
467 r = Curl_dyn_addf(&buf, ":TLSVER-%d-%d", ssl->version,
468 (ssl->version_max >> 16));
469 if(r)
470 goto out;
471 }
472 if(ssl->ssl_options) {
473 r = Curl_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options);
474 if(r)
475 goto out;
476 }
477 if(ssl->cipher_list) {
478 r = Curl_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list);
479 if(r)
480 goto out;
481 }
482 if(ssl->cipher_list13) {
483 r = Curl_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13);
484 if(r)
485 goto out;
486 }
487 if(ssl->curves) {
488 r = Curl_dyn_addf(&buf, ":CURVES-%s", ssl->curves);
489 if(r)
490 goto out;
491 }
492 if(ssl->verifypeer) {
493 r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile);
494 if(r)
495 goto out;
496 r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath);
497 if(r)
498 goto out;
499 r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile);
500 if(r)
501 goto out;
502 r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert);
503 if(r)
504 goto out;
505 if(ssl->cert_blob) {
506 r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob);
507 if(r)
508 goto out;
509 }
510 if(ssl->ca_info_blob) {
511 r = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob);
512 if(r)
513 goto out;
514 }
515 if(ssl->issuercert_blob) {
516 r = cf_ssl_peer_key_add_hash(&buf, "IssuerBlob", ssl->issuercert_blob);
517 if(r)
518 goto out;
519 }
520 }
521 if(ssl->pinned_key && ssl->pinned_key[0]) {
522 r = Curl_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key);
523 if(r)
524 goto out;
525 }
526
527 if(ssl->clientcert && ssl->clientcert[0]) {
528 r = Curl_dyn_add(&buf, ":CCERT");
529 if(r)
530 goto out;
531 }
532 #ifdef USE_TLS_SRP
533 if(ssl->username || ssl->password) {
534 r = Curl_dyn_add(&buf, ":SRP-AUTH");
535 if(r)
536 goto out;
537 }
538 #endif
539
540 if(!tls_id || !tls_id[0]) {
541 r = CURLE_FAILED_INIT;
542 goto out;
543 }
544 r = Curl_dyn_addf(&buf, ":IMPL-%s", tls_id);
545 if(r)
546 goto out;
547
548 *ppeer_key = Curl_dyn_take(&buf, &key_len);
549 /* we just added printable char, and dynbuf always 0 terminates,
550 * no need to track length */
551
552
553 out:
554 Curl_dyn_free(&buf);
555 return r;
556 }
557
cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer * peer,struct ssl_primary_config * conn_config)558 static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer,
559 struct ssl_primary_config *conn_config)
560 {
561 if(!conn_config) {
562 if(peer->clientcert)
563 return FALSE;
564 #ifdef USE_TLS_SRP
565 if(peer->srp_username || peer->srp_password)
566 return FALSE;
567 #endif
568 return TRUE;
569 }
570 else if(!Curl_safecmp(peer->clientcert, conn_config->clientcert))
571 return FALSE;
572 #ifdef USE_TLS_SRP
573 if(Curl_timestrcmp(peer->srp_username, conn_config->username) ||
574 Curl_timestrcmp(peer->srp_password, conn_config->password))
575 return FALSE;
576 #endif
577 return TRUE;
578 }
579
580 static CURLcode
cf_ssl_find_peer_by_key(struct Curl_easy * data,struct Curl_ssl_scache * scache,const char * ssl_peer_key,struct ssl_primary_config * conn_config,struct Curl_ssl_scache_peer ** ppeer)581 cf_ssl_find_peer_by_key(struct Curl_easy *data,
582 struct Curl_ssl_scache *scache,
583 const char *ssl_peer_key,
584 struct ssl_primary_config *conn_config,
585 struct Curl_ssl_scache_peer **ppeer)
586 {
587 size_t i, peer_key_len = 0;
588 CURLcode result = CURLE_OK;
589
590 *ppeer = NULL;
591 /* check for entries with known peer_key */
592 for(i = 0; scache && i < scache->peer_count; i++) {
593 if(scache->peers[i].ssl_peer_key &&
594 strcasecompare(ssl_peer_key, scache->peers[i].ssl_peer_key) &&
595 cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) {
596 /* yes, we have a cached session for this! */
597 *ppeer = &scache->peers[i];
598 goto out;
599 }
600 }
601 /* check for entries with HMAC set but no known peer_key */
602 for(i = 0; scache && i < scache->peer_count; i++) {
603 if(!scache->peers[i].ssl_peer_key &&
604 scache->peers[i].hmac_set &&
605 cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) {
606 /* possible entry with unknown peer_key, check hmac */
607 unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH];
608 if(!peer_key_len) /* we are lazy */
609 peer_key_len = strlen(ssl_peer_key);
610 result = Curl_hmacit(&Curl_HMAC_SHA256,
611 scache->peers[i].key_salt,
612 sizeof(scache->peers[i].key_salt),
613 (const unsigned char *)ssl_peer_key,
614 peer_key_len,
615 my_hmac);
616 if(result)
617 goto out;
618 if(!memcmp(scache->peers[i].key_hmac, my_hmac, sizeof(my_hmac))) {
619 /* remember peer_key for future lookups */
620 CURL_TRC_SSLS(data, "peer entry %zu key recovered: %s",
621 i, ssl_peer_key);
622 scache->peers[i].ssl_peer_key = strdup(ssl_peer_key);
623 if(!scache->peers[i].ssl_peer_key) {
624 result = CURLE_OUT_OF_MEMORY;
625 goto out;
626 }
627 *ppeer = &scache->peers[i];
628 goto out;
629 }
630 }
631 }
632 CURL_TRC_SSLS(data, "peer not found for %s", ssl_peer_key);
633 out:
634 return result;
635 }
636
637 static struct Curl_ssl_scache_peer *
cf_ssl_get_free_peer(struct Curl_ssl_scache * scache)638 cf_ssl_get_free_peer(struct Curl_ssl_scache *scache)
639 {
640 struct Curl_ssl_scache_peer *peer = NULL;
641 size_t i;
642
643 /* find empty or oldest peer */
644 for(i = 0; i < scache->peer_count; ++i) {
645 /* free peer entry? */
646 if(!scache->peers[i].ssl_peer_key && !scache->peers[i].hmac_set) {
647 peer = &scache->peers[i];
648 break;
649 }
650 /* peer without sessions and obj */
651 if(!scache->peers[i].sobj &&
652 !Curl_llist_count(&scache->peers[i].sessions)) {
653 peer = &scache->peers[i];
654 break;
655 }
656 /* remember "oldest" peer */
657 if(!peer || (scache->peers[i].age < peer->age)) {
658 peer = &scache->peers[i];
659 }
660 }
661 DEBUGASSERT(peer);
662 if(peer)
663 cf_ssl_scache_clear_peer(peer);
664 return peer;
665 }
666
667 static CURLcode
cf_ssl_add_peer(struct Curl_easy * data,struct Curl_ssl_scache * scache,const char * ssl_peer_key,struct ssl_primary_config * conn_config,struct Curl_ssl_scache_peer ** ppeer)668 cf_ssl_add_peer(struct Curl_easy *data,
669 struct Curl_ssl_scache *scache,
670 const char *ssl_peer_key,
671 struct ssl_primary_config *conn_config,
672 struct Curl_ssl_scache_peer **ppeer)
673 {
674 struct Curl_ssl_scache_peer *peer = NULL;
675 CURLcode result = CURLE_OK;
676
677 *ppeer = NULL;
678 if(ssl_peer_key) {
679 result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
680 &peer);
681 if(result || !scache->peer_count)
682 return result;
683 }
684
685 if(peer) {
686 *ppeer = peer;
687 return CURLE_OK;
688 }
689
690 peer = cf_ssl_get_free_peer(scache);
691 if(peer) {
692 const char *ccert = conn_config ? conn_config->clientcert : NULL;
693 const char *username = NULL, *password = NULL;
694 #ifdef USE_TLS_SRP
695 username = conn_config ? conn_config->username : NULL;
696 password = conn_config ? conn_config->password : NULL;
697 #endif
698 result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert,
699 username, password, NULL, NULL);
700 if(result)
701 goto out;
702 /* all ready */
703 *ppeer = peer;
704 result = CURLE_OK;
705 }
706
707 out:
708 if(result) {
709 cf_ssl_scache_clear_peer(peer);
710 }
711 return result;
712 }
713
cf_scache_peer_add_session(struct Curl_ssl_scache_peer * peer,struct Curl_ssl_session * s,curl_off_t now)714 static void cf_scache_peer_add_session(struct Curl_ssl_scache_peer *peer,
715 struct Curl_ssl_session *s,
716 curl_off_t now)
717 {
718 /* A session not from TLSv1.3 replaces all other. */
719 if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3) {
720 Curl_llist_destroy(&peer->sessions, NULL);
721 Curl_llist_append(&peer->sessions, s, &s->list);
722 }
723 else {
724 /* Expire existing, append, trim from head to obey max_sessions */
725 cf_scache_peer_remove_expired(peer, now);
726 cf_scache_peer_remove_non13(peer);
727 Curl_llist_append(&peer->sessions, s, &s->list);
728 while(Curl_llist_count(&peer->sessions) > peer->max_sessions) {
729 Curl_node_remove(Curl_llist_head(&peer->sessions));
730 }
731 }
732 }
733
cf_scache_add_session(struct Curl_cfilter * cf,struct Curl_easy * data,struct Curl_ssl_scache * scache,const char * ssl_peer_key,struct Curl_ssl_session * s)734 static CURLcode cf_scache_add_session(struct Curl_cfilter *cf,
735 struct Curl_easy *data,
736 struct Curl_ssl_scache *scache,
737 const char *ssl_peer_key,
738 struct Curl_ssl_session *s)
739 {
740 struct Curl_ssl_scache_peer *peer = NULL;
741 struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
742 CURLcode result = CURLE_OUT_OF_MEMORY;
743 curl_off_t now = (curl_off_t)time(NULL);
744 curl_off_t max_lifetime;
745
746 if(!scache || !scache->peer_count) {
747 Curl_ssl_session_destroy(s);
748 return CURLE_OK;
749 }
750
751 if(s->valid_until <= 0)
752 s->valid_until = now + scache->default_lifetime_secs;
753
754 max_lifetime = (s->ietf_tls_id == CURL_IETF_PROTO_TLS1_3) ?
755 CURL_SCACHE_MAX_13_LIFETIME_SEC :
756 CURL_SCACHE_MAX_12_LIFETIME_SEC;
757 if(s->valid_until > (now + max_lifetime))
758 s->valid_until = now + max_lifetime;
759
760 if(cf_scache_session_expired(s, now)) {
761 CURL_TRC_SSLS(data, "add, session already expired");
762 Curl_ssl_session_destroy(s);
763 return CURLE_OK;
764 }
765
766 result = cf_ssl_add_peer(data, scache, ssl_peer_key, conn_config, &peer);
767 if(result || !peer) {
768 CURL_TRC_SSLS(data, "unable to add scache peer: %d", result);
769 Curl_ssl_session_destroy(s);
770 goto out;
771 }
772
773 cf_scache_peer_add_session(peer, s, now);
774
775 out:
776 if(result) {
777 failf(data, "[SCACHE] failed to add session for %s, error=%d",
778 ssl_peer_key, result);
779 }
780 else
781 CURL_TRC_SSLS(data, "added session for %s [proto=0x%x, "
782 "valid_secs=%" FMT_OFF_T ", alpn=%s, earlydata=%zu, "
783 "quic_tp=%s], peer has %zu sessions now",
784 ssl_peer_key, s->ietf_tls_id, s->valid_until - now,
785 s->alpn, s->earlydata_max, s->quic_tp ? "yes" : "no",
786 peer ? Curl_llist_count(&peer->sessions) : 0);
787 return result;
788 }
789
Curl_ssl_scache_put(struct Curl_cfilter * cf,struct Curl_easy * data,const char * ssl_peer_key,struct Curl_ssl_session * s)790 CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf,
791 struct Curl_easy *data,
792 const char *ssl_peer_key,
793 struct Curl_ssl_session *s)
794 {
795 struct Curl_ssl_scache *scache = data->state.ssl_scache;
796 struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
797 CURLcode result;
798 DEBUGASSERT(ssl_config);
799
800 if(!scache || !ssl_config->primary.cache_session) {
801 Curl_ssl_session_destroy(s);
802 return CURLE_OK;
803 }
804
805 Curl_ssl_scache_lock(data);
806 result = cf_scache_add_session(cf, data, scache, ssl_peer_key, s);
807 Curl_ssl_scache_unlock(data);
808 return result;
809 }
810
Curl_ssl_scache_return(struct Curl_cfilter * cf,struct Curl_easy * data,const char * ssl_peer_key,struct Curl_ssl_session * s)811 void Curl_ssl_scache_return(struct Curl_cfilter *cf,
812 struct Curl_easy *data,
813 const char *ssl_peer_key,
814 struct Curl_ssl_session *s)
815 {
816 /* See RFC 8446 C.4:
817 * "Clients SHOULD NOT reuse a ticket for multiple connections." */
818 if(s && s->ietf_tls_id < 0x304)
819 (void)Curl_ssl_scache_put(cf, data, ssl_peer_key, s);
820 else
821 Curl_ssl_session_destroy(s);
822 }
823
Curl_ssl_scache_take(struct Curl_cfilter * cf,struct Curl_easy * data,const char * ssl_peer_key,struct Curl_ssl_session ** ps)824 CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf,
825 struct Curl_easy *data,
826 const char *ssl_peer_key,
827 struct Curl_ssl_session **ps)
828 {
829 struct Curl_ssl_scache *scache = data->state.ssl_scache;
830 struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
831 struct Curl_ssl_scache_peer *peer = NULL;
832 struct Curl_llist_node *n;
833 struct Curl_ssl_session *s = NULL;
834 CURLcode result;
835
836 *ps = NULL;
837 if(!scache)
838 return CURLE_OK;
839
840 Curl_ssl_scache_lock(data);
841 result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
842 &peer);
843 if(!result && peer) {
844 cf_scache_peer_remove_expired(peer, (curl_off_t)time(NULL));
845 n = Curl_llist_head(&peer->sessions);
846 if(n) {
847 s = Curl_node_take_elem(n);
848 (scache->age)++; /* increase general age */
849 peer->age = scache->age; /* set this as used in this age */
850 }
851 }
852 Curl_ssl_scache_unlock(data);
853 if(s) {
854 *ps = s;
855 CURL_TRC_SSLS(data, "took session for %s [proto=0x%x, "
856 "alpn=%s, earlydata=%zu, quic_tp=%s], %zu sessions remain",
857 ssl_peer_key, s->ietf_tls_id, s->alpn,
858 s->earlydata_max, s->quic_tp ? "yes" : "no",
859 Curl_llist_count(&peer->sessions));
860 }
861 else {
862 CURL_TRC_SSLS(data, "no cached session for %s", ssl_peer_key);
863 }
864 return result;
865 }
866
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)867 CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf,
868 struct Curl_easy *data,
869 const char *ssl_peer_key,
870 void *sobj,
871 Curl_ssl_scache_obj_dtor *sobj_free)
872 {
873 struct Curl_ssl_scache *scache = data->state.ssl_scache;
874 struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
875 struct Curl_ssl_scache_peer *peer = NULL;
876 CURLcode result;
877
878 DEBUGASSERT(sobj);
879 DEBUGASSERT(sobj_free);
880
881 result = cf_ssl_add_peer(data, scache, ssl_peer_key, conn_config, &peer);
882 if(result || !peer) {
883 CURL_TRC_SSLS(data, "unable to add scache peer: %d", result);
884 goto out;
885 }
886
887 cf_ssl_scache_peer_set_obj(peer, sobj, sobj_free);
888 sobj = NULL; /* peer took ownership */
889
890 out:
891 if(sobj && sobj_free)
892 sobj_free(sobj);
893 return result;
894 }
895
Curl_ssl_scache_get_obj(struct Curl_cfilter * cf,struct Curl_easy * data,const char * ssl_peer_key,void ** sobj)896 bool Curl_ssl_scache_get_obj(struct Curl_cfilter *cf,
897 struct Curl_easy *data,
898 const char *ssl_peer_key,
899 void **sobj)
900 {
901 struct Curl_ssl_scache *scache = data->state.ssl_scache;
902 struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
903 struct Curl_ssl_scache_peer *peer = NULL;
904 CURLcode result;
905
906 *sobj = NULL;
907 if(!scache)
908 return FALSE;
909
910 result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
911 &peer);
912 if(result)
913 return FALSE;
914
915 if(peer)
916 *sobj = peer->sobj;
917
918 CURL_TRC_SSLS(data, "%s cached session for '%s'",
919 *sobj ? "Found" : "No", ssl_peer_key);
920 return !!*sobj;
921 }
922
Curl_ssl_scache_remove_all(struct Curl_cfilter * cf,struct Curl_easy * data,const char * ssl_peer_key)923 void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf,
924 struct Curl_easy *data,
925 const char *ssl_peer_key)
926 {
927 struct Curl_ssl_scache *scache = data->state.ssl_scache;
928 struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
929 struct Curl_ssl_scache_peer *peer = NULL;
930 CURLcode result;
931
932 (void)cf;
933 if(!scache)
934 return;
935
936 Curl_ssl_scache_lock(data);
937 result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
938 &peer);
939 if(!result && peer)
940 cf_ssl_scache_clear_peer(peer);
941 Curl_ssl_scache_unlock(data);
942 }
943
944 #ifdef USE_SSLS_EXPORT
945
946 #define CURL_SSL_TICKET_MAX (16*1024)
947
cf_ssl_scache_peer_set_hmac(struct Curl_ssl_scache_peer * peer)948 static CURLcode cf_ssl_scache_peer_set_hmac(struct Curl_ssl_scache_peer *peer)
949 {
950 CURLcode result;
951
952 DEBUGASSERT(peer);
953 if(!peer->ssl_peer_key)
954 return CURLE_BAD_FUNCTION_ARGUMENT;
955
956 result = Curl_rand(NULL, peer->key_salt, sizeof(peer->key_salt));
957 if(result)
958 return result;
959
960 result = Curl_hmacit(&Curl_HMAC_SHA256,
961 peer->key_salt, sizeof(peer->key_salt),
962 (const unsigned char *)peer->ssl_peer_key,
963 strlen(peer->ssl_peer_key),
964 peer->key_hmac);
965 if(!result)
966 peer->hmac_set = TRUE;
967 return result;
968 }
969
970 static CURLcode
cf_ssl_find_peer_by_hmac(struct Curl_ssl_scache * scache,const unsigned char * salt,const unsigned char * hmac,struct Curl_ssl_scache_peer ** ppeer)971 cf_ssl_find_peer_by_hmac(struct Curl_ssl_scache *scache,
972 const unsigned char *salt,
973 const unsigned char *hmac,
974 struct Curl_ssl_scache_peer **ppeer)
975 {
976 size_t i;
977 CURLcode result = CURLE_OK;
978
979 *ppeer = NULL;
980 /* look for an entry that matches salt+hmac exactly or has a known
981 * ssl_peer_key which salt+hmac's to the same. */
982 for(i = 0; scache && i < scache->peer_count; i++) {
983 struct Curl_ssl_scache_peer *peer = &scache->peers[i];
984 if(!cf_ssl_scache_match_auth(peer, NULL))
985 continue;
986 if(scache->peers[i].hmac_set &&
987 !memcmp(peer->key_salt, salt, sizeof(peer->key_salt)) &&
988 !memcmp(peer->key_hmac, hmac, sizeof(peer->key_hmac))) {
989 /* found exact match, return */
990 *ppeer = peer;
991 goto out;
992 }
993 else if(peer->ssl_peer_key) {
994 unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH];
995 /* compute hmac for the passed salt */
996 result = Curl_hmacit(&Curl_HMAC_SHA256,
997 salt, sizeof(peer->key_salt),
998 (const unsigned char *)peer->ssl_peer_key,
999 strlen(peer->ssl_peer_key),
1000 my_hmac);
1001 if(result)
1002 goto out;
1003 if(!memcmp(my_hmac, hmac, sizeof(my_hmac))) {
1004 /* cryptohash match, take over salt+hmac if no set and return */
1005 if(!peer->hmac_set) {
1006 memcpy(peer->key_salt, salt, sizeof(peer->key_salt));
1007 memcpy(peer->key_hmac, hmac, sizeof(peer->key_hmac));
1008 peer->hmac_set = TRUE;
1009 }
1010 *ppeer = peer;
1011 goto out;
1012 }
1013 }
1014 }
1015 out:
1016 return result;
1017 }
1018
Curl_ssl_session_import(struct Curl_easy * data,const char * ssl_peer_key,const unsigned char * shmac,size_t shmac_len,const unsigned char * sdata,size_t sdata_len)1019 CURLcode Curl_ssl_session_import(struct Curl_easy *data,
1020 const char *ssl_peer_key,
1021 const unsigned char *shmac, size_t shmac_len,
1022 const unsigned char *sdata, size_t sdata_len)
1023 {
1024 struct Curl_ssl_scache *scache = data->state.ssl_scache;
1025 struct Curl_ssl_scache_peer *peer = NULL;
1026 struct Curl_ssl_session *s = NULL;
1027 bool locked = FALSE;
1028 CURLcode r;
1029
1030 if(!scache) {
1031 r = CURLE_BAD_FUNCTION_ARGUMENT;
1032 goto out;
1033 }
1034 if(!ssl_peer_key && (!shmac || !shmac_len)) {
1035 r = CURLE_BAD_FUNCTION_ARGUMENT;
1036 goto out;
1037 }
1038
1039 r = Curl_ssl_session_unpack(data, sdata, sdata_len, &s);
1040 if(r)
1041 goto out;
1042
1043 Curl_ssl_scache_lock(data);
1044 locked = TRUE;
1045
1046 if(ssl_peer_key) {
1047 r = cf_ssl_add_peer(data, scache, ssl_peer_key, NULL, &peer);
1048 if(r)
1049 goto out;
1050 }
1051 else if(shmac_len != (sizeof(peer->key_salt) + sizeof(peer->key_hmac))) {
1052 /* Either salt+hmac was garbled by caller or is from a curl version
1053 * that does things differently */
1054 r = CURLE_BAD_FUNCTION_ARGUMENT;
1055 goto out;
1056 }
1057 else {
1058 const unsigned char *salt = shmac;
1059 const unsigned char *hmac = shmac + sizeof(peer->key_salt);
1060
1061 r = cf_ssl_find_peer_by_hmac(scache, salt, hmac, &peer);
1062 if(r)
1063 goto out;
1064 if(!peer) {
1065 peer = cf_ssl_get_free_peer(scache);
1066 if(peer) {
1067 r = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL,
1068 NULL, NULL, salt, hmac);
1069 if(r)
1070 goto out;
1071 }
1072 }
1073 }
1074
1075 if(peer) {
1076 cf_scache_peer_add_session(peer, s, time(NULL));
1077 s = NULL; /* peer is now owner */
1078 CURL_TRC_SSLS(data, "successfully imported ticket for peer %s, now "
1079 "with %zu tickets",
1080 peer->ssl_peer_key ? peer->ssl_peer_key : "without key",
1081 Curl_llist_count(&peer->sessions));
1082 }
1083
1084 out:
1085 if(locked)
1086 Curl_ssl_scache_unlock(data);
1087 Curl_ssl_session_destroy(s);
1088 return r;
1089 }
1090
Curl_ssl_session_export(struct Curl_easy * data,curl_ssls_export_cb * export_fn,void * userptr)1091 CURLcode Curl_ssl_session_export(struct Curl_easy *data,
1092 curl_ssls_export_cb *export_fn,
1093 void *userptr)
1094 {
1095 struct Curl_ssl_scache *scache = data->state.ssl_scache;
1096 struct Curl_ssl_scache_peer *peer;
1097 struct dynbuf sbuf, hbuf;
1098 struct Curl_llist_node *n;
1099 size_t i, npeers = 0, ntickets = 0;
1100 curl_off_t now = time(NULL);
1101 CURLcode r = CURLE_OK;
1102
1103 if(!export_fn)
1104 return CURLE_BAD_FUNCTION_ARGUMENT;
1105 if(!scache)
1106 return CURLE_OK;
1107
1108 Curl_ssl_scache_lock(data);
1109
1110 Curl_dyn_init(&hbuf, (CURL_SHA256_DIGEST_LENGTH * 2) + 1);
1111 Curl_dyn_init(&sbuf, CURL_SSL_TICKET_MAX);
1112
1113 for(i = 0; scache && i < scache->peer_count; i++) {
1114 peer = &scache->peers[i];
1115 if(!peer->ssl_peer_key && !peer->hmac_set)
1116 continue; /* skip free entry */
1117 if(peer->clientcert || peer->srp_username || peer->srp_password)
1118 continue; /* not exporting those */
1119
1120 Curl_dyn_reset(&hbuf);
1121 cf_scache_peer_remove_expired(peer, now);
1122 n = Curl_llist_head(&peer->sessions);
1123 if(n)
1124 ++npeers;
1125 while(n) {
1126 struct Curl_ssl_session *s = Curl_node_elem(n);
1127 if(!peer->hmac_set) {
1128 r = cf_ssl_scache_peer_set_hmac(peer);
1129 if(r)
1130 goto out;
1131 }
1132 if(!Curl_dyn_len(&hbuf)) {
1133 r = Curl_dyn_addn(&hbuf, peer->key_salt, sizeof(peer->key_salt));
1134 if(r)
1135 goto out;
1136 r = Curl_dyn_addn(&hbuf, peer->key_hmac, sizeof(peer->key_hmac));
1137 if(r)
1138 goto out;
1139 }
1140 Curl_dyn_reset(&sbuf);
1141 r = Curl_ssl_session_pack(data, s, &sbuf);
1142 if(r)
1143 goto out;
1144
1145 r = export_fn(data, userptr, peer->ssl_peer_key,
1146 Curl_dyn_uptr(&hbuf), Curl_dyn_len(&hbuf),
1147 Curl_dyn_uptr(&sbuf), Curl_dyn_len(&sbuf),
1148 s->valid_until, s->ietf_tls_id,
1149 s->alpn, s->earlydata_max);
1150 if(r)
1151 goto out;
1152 ++ntickets;
1153 n = Curl_node_next(n);
1154 }
1155
1156 }
1157 r = CURLE_OK;
1158 CURL_TRC_SSLS(data, "exported %zu session tickets for %zu peers",
1159 ntickets, npeers);
1160
1161 out:
1162 Curl_ssl_scache_unlock(data);
1163 Curl_dyn_free(&hbuf);
1164 Curl_dyn_free(&sbuf);
1165 return r;
1166 }
1167
1168 #endif /* USE_SSLS_EXPORT */
1169
1170 #endif /* USE_SSL */
1171