Coverage Report

Created: 2026-07-30 07:03

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