Coverage Report

Created: 2026-08-14 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/strongswan/src/libstrongswan/credentials/sets/cert_cache.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2008 Martin Willi
3
 * Copyright (C) 2016 Andreas Steffen
4
 *
5
 * Copyright (C) secunet Security Networks AG
6
 *
7
 * This program is free software; you can redistribute it and/or modify it
8
 * under the terms of the GNU General Public License as published by the
9
 * Free Software Foundation; either version 2 of the License, or (at your
10
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
11
 *
12
 * This program is distributed in the hope that it will be useful, but
13
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15
 * for more details.
16
 */
17
18
#include "cert_cache.h"
19
20
#include <time.h>
21
22
#include <library.h>
23
#include <threading/rwlock.h>
24
#include <collections/linked_list.h>
25
#include <credentials/certificates/crl.h>
26
#include <credentials/certificates/ocsp_response.h>
27
28
/** cache size, a power of 2 for fast modulo */
29
3.39M
#define CACHE_SIZE 32
30
31
/** attempts to acquire a cache lock */
32
0
#define REPLACE_TRIES 5
33
34
typedef struct private_cert_cache_t private_cert_cache_t;
35
typedef struct relation_t relation_t;
36
37
/**
38
 * A trusted relation between subject and issuer
39
 */
40
struct relation_t {
41
42
  /**
43
   * subject of this relation
44
   */
45
  certificate_t *subject;
46
47
  /**
48
   * cached certificate type
49
   */
50
  certificate_type_t type;
51
52
  /**
53
   * issuer of this relation
54
   */
55
  certificate_t *issuer;
56
57
  /**
58
   * Signature scheme and parameters used to sign this relation
59
   */
60
  signature_params_t *scheme;
61
62
  /**
63
   * Cache hits
64
   */
65
  u_int hits;
66
67
  /**
68
   * Lock for this relation
69
   */
70
  rwlock_t *lock;
71
};
72
73
/**
74
 * private data of cert_cache
75
 */
76
struct private_cert_cache_t {
77
78
  /**
79
   * public functions
80
   */
81
  cert_cache_t public;
82
83
  /**
84
   * array of trusted subject-issuer relations
85
   */
86
  relation_t relations[CACHE_SIZE];
87
};
88
89
/**
90
 * Replaces the information in the given relation using the passed data.
91
 *
92
 * Hits are not touched.
93
 *
94
 * The lock must be held.
95
 */
96
static void replace_relation(relation_t *rel, certificate_t *subject,
97
               certificate_t *issuer, signature_params_t *scheme)
98
1
{
99
1
  if (rel->subject)
100
0
  {
101
0
    rel->subject->destroy(rel->subject);
102
0
    rel->issuer->destroy(rel->issuer);
103
0
    signature_params_destroy(rel->scheme);
104
0
  }
105
1
  rel->subject = subject->get_ref(subject);
106
1
  rel->type = subject->get_type(subject);
107
1
  rel->issuer = issuer->get_ref(issuer);
108
1
  rel->scheme = signature_params_clone(scheme);
109
1
}
110
111
/**
112
 * Cache relation in a free slot/replace an other
113
 */
114
static void cache(private_cert_cache_t *this,
115
          certificate_t *subject, certificate_t *issuer,
116
          signature_params_t *scheme)
117
1
{
118
1
  relation_t *rel;
119
1
  int i, offset, try;
120
1
  u_int total_hits = 0;
121
122
  /* cache a CRL by replacing a previous CRL cache entry if present */
123
1
  if (subject->get_type(subject) == CERT_X509_CRL)
124
0
  {
125
0
    crl_t *crl, *cached_crl;
126
127
    /* cache a delta CRL ? */
128
0
    crl = (crl_t*)subject;
129
130
0
    for (i = 0; i < CACHE_SIZE; i++)
131
0
    {
132
0
      rel = &this->relations[i];
133
134
0
      if (rel->type == CERT_X509_CRL &&
135
0
        rel->lock->try_write_lock(rel->lock))
136
0
      {
137
        /* double-check having lock */
138
0
        if (rel->subject &&
139
0
          rel->subject->get_type(rel->subject) == CERT_X509_CRL &&
140
0
          rel->issuer->equals(rel->issuer, issuer))
141
0
        {
142
0
          cached_crl = (crl_t*)rel->subject;
143
144
0
          if (cached_crl->is_delta_crl(cached_crl, NULL) ==
145
0
                 crl->is_delta_crl(crl, NULL) &&
146
0
            crl_is_newer(crl, cached_crl))
147
0
          {
148
0
            replace_relation(rel, subject, issuer, scheme);
149
0
            return rel->lock->unlock(rel->lock);
150
0
          }
151
0
        }
152
0
        rel->lock->unlock(rel->lock);
153
0
      }
154
0
    }
155
0
  }
156
1
  else if (subject->get_type(subject) == CERT_X509_OCSP_RESPONSE)
157
0
  {
158
0
    ocsp_response_t *response, *cached_response;
159
0
    enumerator_t *e, *e1;
160
0
    chunk_t serial, serial_cached;
161
162
0
    response = (ocsp_response_t*)subject;
163
164
    /* we only check OCSP responses containing one single response */
165
0
    e = response->create_response_enumerator(response);
166
0
    if (e->enumerate(e, &serial, NULL, NULL, NULL) &&
167
0
       !e->enumerate(e, NULL, NULL, NULL, NULL))
168
0
    {
169
0
      for (i = 0; i < CACHE_SIZE; i++)
170
0
      {
171
0
        rel = &this->relations[i];
172
173
0
        if (rel->type == CERT_X509_OCSP_RESPONSE &&
174
0
          rel->lock->try_write_lock(rel->lock))
175
0
        {
176
          /* double-check having lock */
177
0
          if (rel->subject &&
178
0
            rel->subject->get_type(rel->subject) == CERT_X509_OCSP_RESPONSE &&
179
0
            rel->issuer->equals(rel->issuer, issuer) &&
180
0
            certificate_is_newer(subject, rel->subject))
181
0
          {
182
0
            cached_response = (ocsp_response_t*)rel->subject;
183
184
0
            e1 = cached_response->create_response_enumerator(cached_response);
185
0
            if (e1->enumerate(e1, &serial_cached, NULL, NULL, NULL) &&
186
0
               !e1->enumerate(e1, NULL, NULL, NULL, NULL) &&
187
0
              chunk_equals(serial_cached, serial))
188
0
            {
189
0
              e1->destroy(e1);
190
0
              e->destroy(e);
191
0
              replace_relation(rel, subject, issuer, scheme);
192
0
              return rel->lock->unlock(rel->lock);
193
0
            }
194
0
            e1->destroy(e1);
195
0
          }
196
0
          rel->lock->unlock(rel->lock);
197
0
        }
198
0
      }
199
0
    }
200
0
    e->destroy(e);
201
0
  }
202
203
  /* check for a unused relation slot first */
204
1
  for (i = 0; i < CACHE_SIZE; i++)
205
1
  {
206
1
    rel = &this->relations[i];
207
208
1
    if (!rel->subject && rel->lock->try_write_lock(rel->lock))
209
1
    {
210
      /* double-check having lock */
211
1
      if (!rel->subject)
212
1
      {
213
1
        replace_relation(rel, subject, issuer, scheme);
214
1
        return rel->lock->unlock(rel->lock);
215
1
      }
216
0
      rel->lock->unlock(rel->lock);
217
0
    }
218
0
    total_hits += rel->hits;
219
0
  }
220
  /* run several attempts to replace a random slot, never block. */
221
0
  for (try = 0; try < REPLACE_TRIES; try++)
222
0
  {
223
    /* replace a random relation */
224
0
    offset = random() % CACHE_SIZE;
225
0
    for (i = 0; i < CACHE_SIZE; i++)
226
0
    {
227
0
      rel = &this->relations[(i + offset) % CACHE_SIZE];
228
229
0
      if (rel->hits > total_hits / CACHE_SIZE)
230
0
      { /* skip often used slots */
231
0
        continue;
232
0
      }
233
0
      if (rel->lock->try_write_lock(rel->lock))
234
0
      {
235
0
        replace_relation(rel, subject, issuer, scheme);
236
0
        rel->hits = 0;
237
0
        return rel->lock->unlock(rel->lock);
238
0
      }
239
0
    }
240
    /* give other threads a chance to release locks */
241
0
    sched_yield();
242
0
  }
243
0
}
244
245
METHOD(cert_cache_t, issued_by, bool,
246
  private_cert_cache_t *this, certificate_t *subject, certificate_t *issuer,
247
  signature_params_t **schemep)
248
153
{
249
153
  certificate_t *cached_issuer = NULL;
250
153
  relation_t *found = NULL, *current;
251
153
  signature_params_t *scheme;
252
153
  int i;
253
254
1.04k
  for (i = 0; i < CACHE_SIZE; i++)
255
1.02k
  {
256
1.02k
    current = &this->relations[i];
257
258
1.02k
    current->lock->read_lock(current->lock);
259
1.02k
    if (current->subject)
260
152
    {
261
152
      if (issuer->equals(issuer, current->issuer))
262
125
      {
263
125
        if (subject->equals(subject, current->subject))
264
125
        {
265
125
          current->hits++;
266
125
          found = current;
267
125
          if (schemep)
268
0
          {
269
0
            *schemep = signature_params_clone(current->scheme);
270
0
          }
271
125
        }
272
0
        else if (!cached_issuer)
273
0
        {
274
0
          cached_issuer = current->issuer->get_ref(current->issuer);
275
0
        }
276
125
      }
277
152
    }
278
1.02k
    current->lock->unlock(current->lock);
279
1.02k
    if (found)
280
125
    {
281
125
      DESTROY_IF(cached_issuer);
282
125
      return TRUE;
283
125
    }
284
1.02k
  }
285
28
  if (subject->issued_by(subject, issuer, &scheme))
286
1
  {
287
1
    cache(this, subject, cached_issuer ?: issuer, scheme);
288
1
    if (schemep)
289
0
    {
290
0
      *schemep = scheme;
291
0
    }
292
1
    else
293
1
    {
294
1
      signature_params_destroy(scheme);
295
1
    }
296
1
    DESTROY_IF(cached_issuer);
297
1
    return TRUE;
298
1
  }
299
27
  DESTROY_IF(cached_issuer);
300
27
  return FALSE;
301
28
}
302
303
/**
304
 * certificate enumerator implementation
305
 */
306
typedef struct {
307
  /** implements enumerator_t interface */
308
  enumerator_t public;
309
  /** type of requested certificate */
310
  certificate_type_t cert;
311
  /** type of requested key */
312
  key_type_t key;
313
  /** ID to get a cert for */
314
  identification_t *id;
315
  /** cache */
316
  relation_t *relations;
317
  /** current position in array cache */
318
  int index;
319
  /** currently locked relation */
320
  int locked;
321
} cert_enumerator_t;
322
323
METHOD(enumerator_t, cert_enumerate, bool,
324
  cert_enumerator_t *this, va_list args)
325
294
{
326
294
  public_key_t *public;
327
294
  relation_t *rel;
328
294
  certificate_t **out;
329
330
294
  VA_ARGS_VGET(args, out);
331
332
294
  if (this->locked >= 0)
333
0
  {
334
0
    rel = &this->relations[this->locked];
335
0
    rel->lock->unlock(rel->lock);
336
0
    this->locked = -1;
337
0
  }
338
339
5.70k
  while (++this->index < CACHE_SIZE)
340
5.53k
  {
341
5.53k
    rel = &this->relations[this->index];
342
5.53k
    rel->lock->read_lock(rel->lock);
343
5.53k
    this->locked = this->index;
344
5.53k
    if (rel->subject)
345
293
    {
346
      /* CRL lookup is done using issuer/authkeyidentifier */
347
293
      if (this->key == KEY_ANY && this->id &&
348
56
        (this->cert == CERT_ANY || this->cert == CERT_X509_CRL) &&
349
0
        rel->subject->get_type(rel->subject) == CERT_X509_CRL &&
350
0
        rel->subject->has_issuer(rel->subject, this->id))
351
0
      {
352
0
        *out = rel->subject;
353
0
        return TRUE;
354
0
      }
355
293
      if ((this->cert == CERT_ANY ||
356
56
         rel->subject->get_type(rel->subject) == this->cert) &&
357
293
        (!this->id || rel->subject->has_subject(rel->subject, this->id)))
358
207
      {
359
207
        if (this->key == KEY_ANY)
360
0
        {
361
0
          *out = rel->subject;
362
0
          return TRUE;
363
0
        }
364
207
        public = rel->subject->get_public_key(rel->subject);
365
207
        if (public)
366
207
        {
367
207
          if (public->get_type(public) == this->key)
368
125
          {
369
125
            public->destroy(public);
370
125
            *out = rel->subject;
371
125
            return TRUE;
372
125
          }
373
82
          public->destroy(public);
374
82
        }
375
207
      }
376
293
    }
377
5.40k
    this->locked = -1;
378
5.40k
    rel->lock->unlock(rel->lock);
379
5.40k
  }
380
169
  return FALSE;
381
294
}
382
383
METHOD(enumerator_t, cert_enumerator_destroy, void,
384
  cert_enumerator_t *this)
385
294
{
386
294
  relation_t *rel;
387
388
294
  if (this->locked >= 0)
389
125
  {
390
125
    rel = &this->relations[this->locked];
391
125
    rel->lock->unlock(rel->lock);
392
125
  }
393
294
  free(this);
394
294
}
395
396
METHOD(credential_set_t, create_enumerator, enumerator_t*,
397
  private_cert_cache_t *this, certificate_type_t cert, key_type_t key,
398
  identification_t *id, bool trusted)
399
3.97k
{
400
3.97k
  cert_enumerator_t *enumerator;
401
402
3.97k
  if (trusted)
403
3.68k
  {
404
3.68k
    return NULL;
405
3.68k
  }
406
3.97k
  INIT(enumerator,
407
294
    .public = {
408
294
      .enumerate = enumerator_enumerate_default,
409
294
      .venumerate = _cert_enumerate,
410
294
      .destroy = _cert_enumerator_destroy,
411
294
    },
412
294
    .cert = cert,
413
294
    .key = key,
414
294
    .id = id,
415
294
    .relations = this->relations,
416
294
    .index = -1,
417
294
    .locked = -1,
418
294
  );
419
294
  return &enumerator->public;
420
3.97k
}
421
422
METHOD(cert_cache_t, flush, void,
423
  private_cert_cache_t *this, certificate_type_t type)
424
34.2k
{
425
34.2k
  relation_t *rel;
426
34.2k
  int i;
427
428
1.12M
  for (i = 0; i < CACHE_SIZE; i++)
429
1.09M
  {
430
1.09M
    rel = &this->relations[i];
431
1.09M
    if (!rel->subject)
432
1.09M
    {
433
1.09M
      continue;
434
1.09M
    }
435
    /* check with cheap read lock first */
436
0
    if (type != CERT_ANY)
437
0
    {
438
0
      rel->lock->read_lock(rel->lock);
439
0
      if (!rel->subject || type != rel->subject->get_type(rel->subject))
440
0
      {
441
0
        rel->lock->unlock(rel->lock);
442
0
        continue;
443
0
      }
444
0
      rel->lock->unlock(rel->lock);
445
0
    }
446
    /* double check in write lock */
447
0
    rel->lock->write_lock(rel->lock);
448
0
    if (rel->subject)
449
0
    {
450
0
      if (type == CERT_ANY || type == rel->subject->get_type(rel->subject))
451
0
      {
452
0
        rel->subject->destroy(rel->subject);
453
0
        rel->issuer->destroy(rel->issuer);
454
0
        signature_params_destroy(rel->scheme);
455
0
        rel->subject = NULL;
456
0
        rel->type = CERT_ANY;
457
0
        rel->issuer = NULL;
458
0
        rel->scheme = NULL;
459
0
        rel->hits = 0;
460
0
      }
461
0
    }
462
0
    rel->lock->unlock(rel->lock);
463
0
  }
464
34.2k
}
465
466
METHOD(cert_cache_t, destroy, void,
467
  private_cert_cache_t *this)
468
34.2k
{
469
34.2k
  relation_t *rel;
470
34.2k
  int i;
471
472
1.12M
  for (i = 0; i < CACHE_SIZE; i++)
473
1.09M
  {
474
1.09M
    rel = &this->relations[i];
475
1.09M
    if (rel->subject)
476
0
    {
477
0
      rel->subject->destroy(rel->subject);
478
0
      rel->issuer->destroy(rel->issuer);
479
0
      signature_params_destroy(rel->scheme);
480
0
    }
481
1.09M
    rel->lock->destroy(rel->lock);
482
1.09M
  }
483
34.2k
  free(this);
484
34.2k
}
485
486
/*
487
 * see header file
488
 */
489
cert_cache_t *cert_cache_create()
490
34.2k
{
491
34.2k
  private_cert_cache_t *this;
492
34.2k
  int i;
493
494
34.2k
  INIT(this,
495
34.2k
    .public = {
496
34.2k
      .set = {
497
34.2k
        .create_cert_enumerator = _create_enumerator,
498
34.2k
        .create_private_enumerator = (void*)return_null,
499
34.2k
        .create_shared_enumerator = (void*)return_null,
500
34.2k
        .create_cdp_enumerator = (void*)return_null,
501
34.2k
        .cache_cert = (void*)nop,
502
34.2k
      },
503
34.2k
      .issued_by = _issued_by,
504
34.2k
      .flush = _flush,
505
34.2k
      .destroy = _destroy,
506
34.2k
    },
507
34.2k
  );
508
509
1.12M
  for (i = 0; i < CACHE_SIZE; i++)
510
1.09M
  {
511
1.09M
    this->relations[i].subject = NULL;
512
1.09M
    this->relations[i].type = CERT_ANY;
513
1.09M
    this->relations[i].issuer = NULL;
514
1.09M
    this->relations[i].scheme = NULL;
515
1.09M
    this->relations[i].hits = 0;
516
1.09M
    this->relations[i].lock = rwlock_create(RWLOCK_TYPE_DEFAULT);
517
1.09M
  }
518
519
34.2k
  return &this->public;
520
34.2k
}