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