Coverage Report

Created: 2026-08-28 07: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 <algorithm>
19
#include <string_view>
20
21
#include <openssl/buf.h>
22
#include <openssl/err.h>
23
#include <openssl/mem.h>
24
#include <openssl/x509.h>
25
26
#include "../internal.h"
27
#include "../mem_internal.h"
28
#include "internal.h"
29
30
31
BSSL_NAMESPACE_BEGIN
32
33
// A ByDirEntry tracks state for a single directory, notably the starting suffix
34
// for CRL lookups.
35
class ByDirEntry {
36
 public:
37
  static constexpr bool kAllowUniquePtr = true;
38
39
0
  ByDirEntry() = default;
40
41
0
  static UniquePtr<ByDirEntry> Create(int dir_type, std::string_view dir) {
42
0
    auto ret = MakeUnique<ByDirEntry>();
43
0
    ret->dir_type_ = dir_type;
44
0
    ret->dir_.reset(OPENSSL_strndup(dir.data(), dir.size()));
45
0
    if (ret->dir_ == nullptr) {
46
0
      return nullptr;
47
0
    }
48
0
    return ret;
49
0
  }
50
51
0
  int dir_type() const { return dir_type_; }
52
0
  const char *dir() const { return dir_.get(); }
53
54
0
  int GetCRLSuffix(uint32_t hash) const {
55
0
    MutexReadLock lock(&lock_);
56
0
    auto it = std::lower_bound(crl_suffixes_.begin(), crl_suffixes_.end(), hash);
57
0
    if (it == crl_suffixes_.end() || it->hash != hash) {
58
0
      return 0;
59
0
    }
60
0
    return it->suffix;
61
0
  }
62
63
0
  bool UpdateCRLSuffix(uint32_t hash, int suffix) {
64
0
    MutexWriteLock lock(&lock_);
65
0
    auto it = std::lower_bound(crl_suffixes_.begin(), crl_suffixes_.end(), hash);
66
0
    if (it != crl_suffixes_.end() && it->hash == hash) {
67
0
      it->suffix = std::max(suffix, it->suffix);
68
0
      return true;
69
0
    }
70
0
    if (!crl_suffixes_.Push(CRLSuffix{hash, suffix})) {
71
0
      return false;
72
0
    }
73
0
    std::sort(crl_suffixes_.begin(), crl_suffixes_.end());
74
0
    return true;
75
0
  }
76
77
 private:
78
  struct CRLSuffix {
79
    uint32_t hash;
80
    int suffix;
81
0
    bool operator<(uint32_t h) const { return hash < h; }
82
0
    bool operator<(const CRLSuffix &other) const { return hash < other.hash; }
83
  };
84
85
  UniquePtr<char> dir_;
86
  int dir_type_ = 0;
87
  mutable Mutex lock_;
88
  // crl_suffixes_ is kept sorted.
89
  // TODO(davidben): This should be a hash table. Insertions are O(N log N).
90
  Vector<CRLSuffix> crl_suffixes_;
91
};
92
93
struct ByDir {
94
  Vector<UniquePtr<ByDirEntry>> dirs;
95
};
96
97
static int dir_ctrl(X509_LOOKUP *ctx, int cmd, const char *argp, long argl,
98
                    char **ret);
99
static int new_dir(X509_LOOKUP *lu);
100
static void free_dir(X509_LOOKUP *lu);
101
static int add_cert_dir(ByDir *ctx, const char *dir, int type);
102
static int get_cert_by_subject(X509_LOOKUP *xl, int type, const X509_NAME *name,
103
                               X509_OBJECT *ret);
104
static const X509_LOOKUP_METHOD x509_dir_lookup = {
105
    new_dir,              // new
106
    free_dir,             // free
107
    dir_ctrl,             // ctrl
108
    get_cert_by_subject,  // get_by_subject
109
};
110
111
static int dir_ctrl(X509_LOOKUP *ctx, int cmd, const char *argp, long argl,
112
0
                    char **retp) {
113
0
  ByDir *ld = reinterpret_cast<ByDir *>(ctx->method_data);
114
0
  switch (cmd) {
115
0
    case X509_L_ADD_DIR:
116
0
      if (argl == X509_FILETYPE_DEFAULT) {
117
0
        const char *dir = getenv(X509_get_default_cert_dir_env());
118
0
        if (!add_cert_dir(ld, dir ? dir : X509_get_default_cert_dir(),
119
0
                          X509_FILETYPE_PEM)) {
120
0
          OPENSSL_PUT_ERROR(X509, X509_R_LOADING_CERT_DIR);
121
0
          return 0;
122
0
        }
123
0
        return 1;
124
0
      }
125
0
      return add_cert_dir(ld, argp, (int)argl);
126
0
  }
127
0
  return 0;
128
0
}
129
130
0
static int new_dir(X509_LOOKUP *lu) {
131
0
  ByDir *a = New<ByDir>();
132
0
  if (a == nullptr) {
133
0
    return 0;
134
0
  }
135
0
  lu->method_data = a;
136
0
  return 1;
137
0
}
138
139
0
static void free_dir(X509_LOOKUP *lu) {
140
0
  Delete(reinterpret_cast<ByDir *>(lu->method_data));
141
0
}
142
143
#if defined(OPENSSL_WINDOWS)
144
#define DIR_HASH_SEPARATOR ';'
145
#else
146
0
#define DIR_HASH_SEPARATOR ':'
147
#endif
148
149
0
static int add_cert_dir(ByDir *ctx, const char *inp, int type) {
150
0
  if (inp == nullptr || !*inp) {
151
0
    OPENSSL_PUT_ERROR(X509, X509_R_INVALID_DIRECTORY);
152
0
    return 0;
153
0
  }
154
155
0
  std::string_view rest = inp;
156
0
  do {
157
    // Split by `DIR_HASH_SEPARATOR`.
158
0
    size_t sep = rest.find(DIR_HASH_SEPARATOR);
159
0
    std::string_view dir;
160
0
    if (sep == std::string_view::npos) {
161
0
      dir = rest;
162
0
      rest = std::string_view();
163
0
    } else {
164
0
      dir = rest.substr(0, sep);
165
0
      rest = rest.substr(sep + 1);
166
0
    }
167
0
    if (dir.empty()) {
168
0
      continue;
169
0
    }
170
    // Ignore duplicates.
171
0
    if (std::any_of(ctx->dirs.begin(), ctx->dirs.end(),
172
0
                    [&](const auto &ent) { return ent->dir() == dir; })) {
173
0
      continue;
174
0
    }
175
0
    auto ent = ByDirEntry::Create(type, dir);
176
0
    if (ent == nullptr || !ctx->dirs.Push(std::move(ent))) {
177
0
      return 0;
178
0
    }
179
0
  } while (!rest.empty());
180
0
  return 1;
181
0
}
182
183
static int get_cert_by_subject(X509_LOOKUP *xl, int type, const X509_NAME *name,
184
0
                               X509_OBJECT *ret) {
185
0
  if (name == nullptr) {
186
0
    return 0;
187
0
  }
188
189
  // Set up an `X509_OBJECT` to compare against.
190
0
  UniquePtr<X509> lookup_cert;
191
0
  UniquePtr<X509_CRL> lookup_crl;
192
0
  X509_OBJECT stmp;
193
0
  const char *postfix = "";
194
0
  stmp.type = type;
195
0
  ByDir *ctx = reinterpret_cast<ByDir *>(xl->method_data);
196
0
  if (type == X509_LU_X509) {
197
0
    lookup_cert.reset(X509_new());
198
0
    if (lookup_cert == nullptr ||
199
0
        !X509_set_subject_name(lookup_cert.get(), name)) {
200
0
      return 0;
201
0
    }
202
0
    stmp.data.x509 = lookup_cert.get();
203
0
    postfix = "";
204
0
  } else if (type == X509_LU_CRL) {
205
0
    lookup_crl.reset(X509_CRL_new());
206
0
    if (lookup_crl == nullptr ||
207
0
        !X509_CRL_set_issuer_name(lookup_crl.get(), name)) {
208
0
      return 0;
209
0
    }
210
0
    stmp.data.crl = lookup_crl.get();
211
0
    postfix = "r";
212
0
  } else {
213
0
    OPENSSL_PUT_ERROR(X509, X509_R_WRONG_LOOKUP_TYPE);
214
0
    return 0;
215
0
  }
216
217
  // Try both new and old hashes.
218
0
  const uint32_t hashes[] = {X509_NAME_hash(name), X509_NAME_hash_old(name)};
219
0
  for (uint32_t hash : hashes) {
220
0
    for (UniquePtr<ByDirEntry> &ent : ctx->dirs) {
221
      // If a CRL, start from the previously saved suffix. Updated CRLs are
222
      // expected to be added until new filenames.
223
      // TODO(crbug.com/42290566): Is this what we want?
224
0
      int suffix = 0;
225
0
      if (type == X509_LU_CRL) {
226
0
        suffix = ent->GetCRLSuffix(hash);
227
0
      }
228
229
      // The directory format handles hash collections by incrementing a suffix
230
      // on the file name. Load every suffix into the cache.
231
0
      for (;;) {
232
0
        char *path = nullptr;
233
0
        if (OPENSSL_asprintf(&path, "%s/%08" PRIx32 ".%s%d", ent->dir(), hash,
234
0
                             postfix, suffix) == -1) {
235
0
          OPENSSL_PUT_ERROR(X509, ERR_R_BUF_LIB);
236
0
          return 0;
237
0
        }
238
0
        UniquePtr<char> free_path(path);
239
0
        if (type == X509_LU_X509) {
240
0
          if ((X509_load_cert_file(xl, path, ent->dir_type())) == 0) {
241
            // Don't expose the lower level error, All of these boil down to "we
242
            // could not find a CA".
243
0
            ERR_clear_error();
244
0
            break;
245
0
          }
246
0
        } else if (type == X509_LU_CRL) {
247
0
          if ((X509_load_crl_file(xl, path, ent->dir_type())) == 0) {
248
            // Don't expose the lower level error, All of these boil down to "we
249
            // could not find a CRL".
250
0
            ERR_clear_error();
251
0
            break;
252
0
          }
253
0
        }
254
        // The lack of a CA or CRL will be caught higher up.
255
0
        suffix++;
256
0
      }
257
258
      // We have added it to the cache so now pull it out again.
259
0
      auto *store_impl = FromOpaque(xl->store_ctx);
260
0
      store_impl->objs_lock.LockWrite();
261
0
      const X509_OBJECT *found = nullptr;
262
0
      sk_X509_OBJECT_sort(store_impl->objs.get());
263
0
      size_t idx;
264
0
      if (sk_X509_OBJECT_find(store_impl->objs.get(), &idx, &stmp)) {
265
0
        found = sk_X509_OBJECT_value(store_impl->objs.get(), idx);
266
0
      }
267
0
      store_impl->objs_lock.UnlockWrite();
268
269
      // If a CRL, store the last suffix we saw, to skip already loaded files
270
      // next time.
271
      // TODO(crbug.com/42290566): Is this what we want?
272
0
      if (type == X509_LU_CRL && !ent->UpdateCRLSuffix(hash, suffix)) {
273
0
        return 0;
274
0
      }
275
276
0
      if (found != nullptr) {
277
        // Clear any errors that might have been raised processing empty or
278
        // malformed files.
279
0
        ERR_clear_error();
280
281
        // TODO(crbug.com/42290561): This should manage the reference counts
282
        // correctly but does not.
283
0
        ret->type = found->type;
284
0
        OPENSSL_memcpy(&ret->data, &found->data, sizeof(ret->data));
285
0
        return 1;
286
0
      }
287
0
    }
288
0
  }
289
290
0
  return 0;
291
0
}
292
293
BSSL_NAMESPACE_END
294
295
0
const X509_LOOKUP_METHOD *X509_LOOKUP_hash_dir() {
296
0
  return &bssl::x509_dir_lookup;
297
0
}
298
299
0
int X509_LOOKUP_add_dir(X509_LOOKUP *lookup, const char *name, int type) {
300
0
  return X509_LOOKUP_ctrl(lookup, X509_L_ADD_DIR, name, type, nullptr);
301
0
}