/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 | } |