Coverage Report

Created: 2026-04-15 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/boringssl/crypto/x509/by_dir.cc
Line
Count
Source
1
// Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include <inttypes.h>
16
#include <string.h>
17
18
#include <openssl/buf.h>
19
#include <openssl/err.h>
20
#include <openssl/mem.h>
21
#include <openssl/x509.h>
22
23
#include "../internal.h"
24
#include "../mem_internal.h"
25
#include "internal.h"
26
27
28
using namespace bssl;
29
30
BSSL_NAMESPACE_BEGIN
31
32
typedef struct lookup_dir_hashes_st {
33
  uint32_t hash;
34
  int suffix;
35
} BY_DIR_HASH;
36
37
typedef struct lookup_dir_entry_st {
38
  Mutex lock;
39
  char *dir;
40
  int dir_type;
41
  STACK_OF(BY_DIR_HASH) *hashes;
42
} BY_DIR_ENTRY;
43
44
typedef struct lookup_dir_st {
45
  STACK_OF(BY_DIR_ENTRY) *dirs;
46
} BY_DIR;
47
48
DEFINE_NAMESPACED_STACK_OF(BY_DIR_HASH)
49
DEFINE_NAMESPACED_STACK_OF(BY_DIR_ENTRY)
50
51
BSSL_NAMESPACE_END
52
53
static int dir_ctrl(X509_LOOKUP *ctx, int cmd, const char *argp, long argl,
54
                    char **ret);
55
static int new_dir(X509_LOOKUP *lu);
56
static void free_dir(X509_LOOKUP *lu);
57
static int add_cert_dir(BY_DIR *ctx, const char *dir, int type);
58
static int get_cert_by_subject(X509_LOOKUP *xl, int type, const X509_NAME *name,
59
                               X509_OBJECT *ret);
60
static const X509_LOOKUP_METHOD x509_dir_lookup = {
61
    new_dir,              // new
62
    free_dir,             // free
63
    dir_ctrl,             // ctrl
64
    get_cert_by_subject,  // get_by_subject
65
};
66
67
0
const X509_LOOKUP_METHOD *X509_LOOKUP_hash_dir() { return &x509_dir_lookup; }
68
69
static int dir_ctrl(X509_LOOKUP *ctx, int cmd, const char *argp, long argl,
70
0
                    char **retp) {
71
0
  int ret = 0;
72
0
  char *dir = nullptr;
73
74
0
  BY_DIR *ld = reinterpret_cast<BY_DIR *>(ctx->method_data);
75
76
0
  switch (cmd) {
77
0
    case X509_L_ADD_DIR:
78
0
      if (argl == X509_FILETYPE_DEFAULT) {
79
0
        dir = (char *)getenv(X509_get_default_cert_dir_env());
80
0
        if (dir) {
81
0
          ret = add_cert_dir(ld, dir, X509_FILETYPE_PEM);
82
0
        } else {
83
0
          ret =
84
0
              add_cert_dir(ld, X509_get_default_cert_dir(), X509_FILETYPE_PEM);
85
0
        }
86
0
        if (!ret) {
87
0
          OPENSSL_PUT_ERROR(X509, X509_R_LOADING_CERT_DIR);
88
0
        }
89
0
      } else {
90
0
        ret = add_cert_dir(ld, argp, (int)argl);
91
0
      }
92
0
      break;
93
0
  }
94
0
  return ret;
95
0
}
96
97
0
static int new_dir(X509_LOOKUP *lu) {
98
0
  BY_DIR *a;
99
100
0
  if ((a = New<BY_DIR>()) == nullptr) {
101
0
    return 0;
102
0
  }
103
0
  a->dirs = nullptr;
104
0
  lu->method_data = a;
105
0
  return 1;
106
0
}
107
108
0
static void by_dir_hash_free(BY_DIR_HASH *hash) { Delete(hash); }
109
110
static int by_dir_hash_cmp(const BY_DIR_HASH *const *a,
111
0
                           const BY_DIR_HASH *const *b) {
112
0
  if ((*a)->hash > (*b)->hash) {
113
0
    return 1;
114
0
  }
115
0
  if ((*a)->hash < (*b)->hash) {
116
0
    return -1;
117
0
  }
118
0
  return 0;
119
0
}
120
121
0
static void by_dir_entry_free(BY_DIR_ENTRY *ent) {
122
0
  if (ent != nullptr) {
123
0
    Delete(ent->dir);
124
0
    sk_BY_DIR_HASH_pop_free(ent->hashes, by_dir_hash_free);
125
0
    Delete(ent);
126
0
  }
127
0
}
128
129
0
static void free_dir(X509_LOOKUP *lu) {
130
0
  BY_DIR *a = reinterpret_cast<BY_DIR *>(lu->method_data);
131
0
  if (a != nullptr) {
132
0
    sk_BY_DIR_ENTRY_pop_free(a->dirs, by_dir_entry_free);
133
0
    Delete(a);
134
0
  }
135
0
}
136
137
#if defined(OPENSSL_WINDOWS)
138
#define DIR_HASH_SEPARATOR ';'
139
#else
140
0
#define DIR_HASH_SEPARATOR ':'
141
#endif
142
143
0
static int add_cert_dir(BY_DIR *ctx, const char *dir, int type) {
144
0
  size_t j, len;
145
0
  const char *s, *ss, *p;
146
147
0
  if (dir == nullptr || !*dir) {
148
0
    OPENSSL_PUT_ERROR(X509, X509_R_INVALID_DIRECTORY);
149
0
    return 0;
150
0
  }
151
152
0
  s = dir;
153
0
  p = s;
154
0
  do {
155
0
    if (*p == DIR_HASH_SEPARATOR || *p == '\0') {
156
0
      BY_DIR_ENTRY *ent;
157
0
      ss = s;
158
0
      s = p + 1;
159
0
      len = p - ss;
160
0
      if (len == 0) {
161
0
        continue;
162
0
      }
163
0
      for (j = 0; j < sk_BY_DIR_ENTRY_num(ctx->dirs); j++) {
164
0
        ent = sk_BY_DIR_ENTRY_value(ctx->dirs, j);
165
0
        if (strlen(ent->dir) == len && strncmp(ent->dir, ss, len) == 0) {
166
0
          break;
167
0
        }
168
0
      }
169
0
      if (j < sk_BY_DIR_ENTRY_num(ctx->dirs)) {
170
0
        continue;
171
0
      }
172
0
      if (ctx->dirs == nullptr) {
173
0
        ctx->dirs = sk_BY_DIR_ENTRY_new_null();
174
0
        if (!ctx->dirs) {
175
0
          return 0;
176
0
        }
177
0
      }
178
0
      ent = New<BY_DIR_ENTRY>();
179
0
      if (!ent) {
180
0
        return 0;
181
0
      }
182
0
      ent->dir_type = type;
183
0
      ent->hashes = sk_BY_DIR_HASH_new(by_dir_hash_cmp);
184
0
      ent->dir = OPENSSL_strndup(ss, len);
185
0
      if (ent->dir == nullptr || ent->hashes == nullptr ||
186
0
          !sk_BY_DIR_ENTRY_push(ctx->dirs, ent)) {
187
0
        by_dir_entry_free(ent);
188
0
        return 0;
189
0
      }
190
0
    }
191
0
  } while (*p++ != '\0');
192
0
  return 1;
193
0
}
194
195
static int get_cert_by_subject(X509_LOOKUP *xl, int type, const X509_NAME *name,
196
0
                               X509_OBJECT *ret) {
197
0
  UniquePtr<X509> lookup_cert;
198
0
  UniquePtr<X509_CRL> lookup_crl;
199
0
  int ok = 0;
200
0
  size_t i;
201
0
  int k;
202
0
  uint32_t h;
203
0
  uint32_t hash_array[2];
204
0
  int hash_index;
205
0
  char *b = nullptr;
206
0
  X509_OBJECT stmp, *tmp;
207
0
  const char *postfix = "";
208
209
0
  if (name == nullptr) {
210
0
    return 0;
211
0
  }
212
213
0
  stmp.type = type;
214
0
  BY_DIR *ctx = reinterpret_cast<BY_DIR *>(xl->method_data);
215
0
  if (type == X509_LU_X509) {
216
0
    lookup_cert.reset(X509_new());
217
0
    if (lookup_cert == nullptr ||
218
0
        !X509_set_subject_name(lookup_cert.get(), name)) {
219
0
      return 0;
220
0
    }
221
0
    stmp.data.x509 = lookup_cert.get();
222
0
    postfix = "";
223
0
  } else if (type == X509_LU_CRL) {
224
0
    lookup_crl.reset(X509_CRL_new());
225
0
    if (lookup_crl == nullptr ||
226
0
        !X509_CRL_set_issuer_name(lookup_crl.get(), name)) {
227
0
      return 0;
228
0
    }
229
0
    stmp.data.crl = lookup_crl.get();
230
0
    postfix = "r";
231
0
  } else {
232
0
    OPENSSL_PUT_ERROR(X509, X509_R_WRONG_LOOKUP_TYPE);
233
0
    goto finish;
234
0
  }
235
236
0
  hash_array[0] = X509_NAME_hash(name);
237
0
  hash_array[1] = X509_NAME_hash_old(name);
238
0
  for (hash_index = 0; hash_index < 2; ++hash_index) {
239
0
    h = hash_array[hash_index];
240
0
    for (i = 0; i < sk_BY_DIR_ENTRY_num(ctx->dirs); i++) {
241
0
      BY_DIR_ENTRY *ent;
242
0
      size_t idx;
243
0
      BY_DIR_HASH htmp, *hent;
244
0
      ent = sk_BY_DIR_ENTRY_value(ctx->dirs, i);
245
0
      if (type == X509_LU_CRL && ent->hashes) {
246
0
        htmp.hash = h;
247
0
        MutexReadLock lock(&ent->lock);
248
0
        if (sk_BY_DIR_HASH_find(ent->hashes, &idx, &htmp)) {
249
0
          hent = sk_BY_DIR_HASH_value(ent->hashes, idx);
250
0
          k = hent->suffix;
251
0
        } else {
252
0
          hent = nullptr;
253
0
          k = 0;
254
0
        }
255
0
      } else {
256
0
        k = 0;
257
0
        hent = nullptr;
258
0
      }
259
0
      for (;;) {
260
0
        OPENSSL_free(b);
261
0
        if (OPENSSL_asprintf(&b, "%s/%08" PRIx32 ".%s%d", ent->dir, h, postfix,
262
0
                             k) == -1) {
263
0
          OPENSSL_PUT_ERROR(X509, ERR_R_BUF_LIB);
264
0
          b = nullptr;
265
0
          goto finish;
266
0
        }
267
0
        if (type == X509_LU_X509) {
268
0
          if ((X509_load_cert_file(xl, b, ent->dir_type)) == 0) {
269
            // Don't expose the lower level error, All of these boil
270
            // down to "we could not find a CA".
271
0
            ERR_clear_error();
272
0
            break;
273
0
          }
274
0
        } else if (type == X509_LU_CRL) {
275
0
          if ((X509_load_crl_file(xl, b, ent->dir_type)) == 0) {
276
            // Don't expose the lower level error, All of these boil
277
            // down to "we could not find a CRL".
278
0
            ERR_clear_error();
279
0
            break;
280
0
          }
281
0
        }
282
        // The lack of a CA or CRL will be caught higher up
283
0
        k++;
284
0
      }
285
286
      // we have added it to the cache so now pull it out again
287
0
      auto *store_impl = FromOpaque(xl->store_ctx);
288
0
      store_impl->objs_lock.LockWrite();
289
0
      tmp = nullptr;
290
0
      sk_X509_OBJECT_sort(store_impl->objs.get());
291
0
      if (sk_X509_OBJECT_find(store_impl->objs.get(), &idx, &stmp)) {
292
0
        tmp = sk_X509_OBJECT_value(store_impl->objs.get(), idx);
293
0
      }
294
0
      store_impl->objs_lock.UnlockWrite();
295
296
      // If a CRL, update the last file suffix added for this
297
298
0
      if (type == X509_LU_CRL) {
299
0
        ent->lock.LockWrite();
300
        // Look for entry again in case another thread added an entry
301
        // first.
302
0
        if (!hent) {
303
0
          htmp.hash = h;
304
0
          sk_BY_DIR_HASH_sort(ent->hashes);
305
0
          if (sk_BY_DIR_HASH_find(ent->hashes, &idx, &htmp)) {
306
0
            hent = sk_BY_DIR_HASH_value(ent->hashes, idx);
307
0
          }
308
0
        }
309
0
        if (!hent) {
310
0
          hent = New<BY_DIR_HASH>();
311
0
          if (hent == nullptr) {
312
0
            ent->lock.UnlockWrite();
313
0
            ok = 0;
314
0
            goto finish;
315
0
          }
316
0
          hent->hash = h;
317
0
          hent->suffix = k;
318
0
          if (!sk_BY_DIR_HASH_push(ent->hashes, hent)) {
319
0
            ent->lock.UnlockWrite();
320
0
            Delete(hent);
321
0
            ok = 0;
322
0
            goto finish;
323
0
          }
324
0
          sk_BY_DIR_HASH_sort(ent->hashes);
325
0
        } else if (hent->suffix < k) {
326
0
          hent->suffix = k;
327
0
        }
328
329
0
        ent->lock.UnlockWrite();
330
0
      }
331
332
0
      if (tmp != nullptr) {
333
0
        ok = 1;
334
0
        ret->type = tmp->type;
335
0
        OPENSSL_memcpy(&ret->data, &tmp->data, sizeof(ret->data));
336
337
        // Clear any errors that might have been raised processing empty
338
        // or malformed files.
339
0
        ERR_clear_error();
340
341
        // If we were going to up the reference count, we would need
342
        // to do it on a perl 'type' basis
343
0
        goto finish;
344
0
      }
345
0
    }
346
0
  }
347
0
finish:
348
0
  OPENSSL_free(b);
349
0
  return ok;
350
0
}
351
352
0
int X509_LOOKUP_add_dir(X509_LOOKUP *lookup, const char *name, int type) {
353
0
  return X509_LOOKUP_ctrl(lookup, X509_L_ADD_DIR, name, type, nullptr);
354
0
}