blob: aa3e14cb35dcb1b818c88c09cd1fffdad5bd108e [file] [log] [blame]
Adrien Béraud612b55b2023-05-29 10:42:04 -04001/*
2 * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
3 *
Adrien Béraudcb753622023-07-17 22:32:49 -04004 * This program is free software: you can redistribute it and/or modify
Adrien Béraud612b55b2023-05-29 10:42:04 -04005 * it under the terms of the GNU General Public License as published by
Adrien Béraudcb753622023-07-17 22:32:49 -04006 * the Free Software Foundation, either version 3 of the License, or
Adrien Béraud612b55b2023-05-29 10:42:04 -04007 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Adrien Béraudcb753622023-07-17 22:32:49 -040011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Adrien Béraud612b55b2023-05-29 10:42:04 -040012 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
Adrien Béraudcb753622023-07-17 22:32:49 -040015 * along with this program. If not, see <https://www.gnu.org/licenses/>.
Adrien Béraud612b55b2023-05-29 10:42:04 -040016 */
Adrien Béraud612b55b2023-05-29 10:42:04 -040017#include "certstore.h"
18#include "security_const.h"
19
20#include "fileutils.h"
21
22#include <opendht/thread_pool.h>
23#include <opendht/logger.h>
24
25#include <gnutls/ocsp.h>
26
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040027#include <fmt/std.h>
28
Adrien Béraud612b55b2023-05-29 10:42:04 -040029#include <thread>
30#include <sstream>
31#include <fmt/format.h>
32
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040033namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040034namespace tls {
35
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040036CertificateStore::CertificateStore(const std::filesystem::path& path, std::shared_ptr<Logger> logger)
Adrien Béraud612b55b2023-05-29 10:42:04 -040037 : logger_(std::move(logger))
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040038 , certPath_(path / "certificates")
39 , crlPath_(path /"crls")
40 , ocspPath_(path /"oscp")
Adrien Béraud612b55b2023-05-29 10:42:04 -040041{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040042 fileutils::check_dir(certPath_);
43 fileutils::check_dir(crlPath_);
44 fileutils::check_dir(ocspPath_);
Adrien Béraud612b55b2023-05-29 10:42:04 -040045 loadLocalCertificates();
46}
47
48unsigned
49CertificateStore::loadLocalCertificates()
50{
51 std::lock_guard<std::mutex> l(lock_);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040052 if (logger_)
53 logger_->debug("CertificateStore: loading certificates from {}", certPath_);
Adrien Béraud612b55b2023-05-29 10:42:04 -040054
Adrien Béraud612b55b2023-05-29 10:42:04 -040055 unsigned n = 0;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040056 std::error_code ec;
57 for (const auto& crtPath : std::filesystem::directory_iterator(certPath_, ec)) {
58 const auto& path = crtPath.path();
59 auto fileName = path.filename().string();
Adrien Béraud612b55b2023-05-29 10:42:04 -040060 try {
61 auto crt = std::make_shared<crypto::Certificate>(
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040062 fileutils::loadFile(crtPath));
Adrien Béraud612b55b2023-05-29 10:42:04 -040063 auto id = crt->getId().toString();
64 auto longId = crt->getLongId().toString();
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040065 if (id != fileName && longId != fileName)
Adrien Béraud612b55b2023-05-29 10:42:04 -040066 throw std::logic_error("Certificate id mismatch");
67 while (crt) {
68 id = crt->getId().toString();
69 longId = crt->getLongId().toString();
70 certs_.emplace(std::move(id), crt);
71 certs_.emplace(std::move(longId), crt);
72 loadRevocations(*crt);
73 crt = crt->issuer;
74 ++n;
75 }
76 } catch (const std::exception& e) {
77 if (logger_)
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040078 logger_->warn("loadLocalCertificates: error loading {}: {}", path, e.what());
79 remove(path);
Adrien Béraud612b55b2023-05-29 10:42:04 -040080 }
81 }
82 if (logger_)
83 logger_->debug("CertificateStore: loaded {} local certificates.", n);
84 return n;
85}
86
87void
88CertificateStore::loadRevocations(crypto::Certificate& crt) const
89{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040090 std::error_code ec;
91 auto dir = crlPath_ / crt.getId().toString();
92 for (const auto& crl : std::filesystem::directory_iterator(dir, ec)) {
Adrien Béraud612b55b2023-05-29 10:42:04 -040093 try {
94 crt.addRevocationList(std::make_shared<crypto::RevocationList>(
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040095 fileutils::loadFile(crl)));
Adrien Béraud612b55b2023-05-29 10:42:04 -040096 } catch (const std::exception& e) {
97 if (logger_)
98 logger_->warn("Can't load revocation list: %s", e.what());
99 }
100 }
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400101
102 auto ocsp_dir = ocspPath_ / crt.getId().toString();
103 for (const auto& ocsp_filepath : std::filesystem::directory_iterator(ocsp_dir, ec)) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400104 try {
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400105 auto ocsp = ocsp_filepath.path().filename().string();
106 if (logger_) logger_->debug("Found {}", ocsp_filepath.path());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400107 auto serial = crt.getSerialNumber();
108 if (dht::toHex(serial.data(), serial.size()) != ocsp)
109 continue;
110 // Save the response
111 auto ocspBlob = fileutils::loadFile(ocsp_filepath);
112 crt.ocspResponse = std::make_shared<dht::crypto::OcspResponse>(ocspBlob.data(),
113 ocspBlob.size());
114 unsigned int status = crt.ocspResponse->getCertificateStatus();
115 if (status == GNUTLS_OCSP_CERT_GOOD) {
116 if (logger_) logger_->debug("Certificate {:s} has good OCSP status", crt.getId());
117 } else if (status == GNUTLS_OCSP_CERT_REVOKED) {
118 if (logger_) logger_->error("Certificate {:s} has revoked OCSP status", crt.getId());
119 } else if (status == GNUTLS_OCSP_CERT_UNKNOWN) {
120 if (logger_) logger_->error("Certificate {:s} has unknown OCSP status", crt.getId());
121 } else {
122 if (logger_) logger_->error("Certificate {:s} has invalid OCSP status", crt.getId());
123 }
124 } catch (const std::exception& e) {
125 if (logger_)
126 logger_->warn("Can't load OCSP revocation status: {:s}", e.what());
127 }
128 }
129}
130
131std::vector<std::string>
132CertificateStore::getPinnedCertificates() const
133{
134 std::lock_guard<std::mutex> l(lock_);
135
136 std::vector<std::string> certIds;
137 certIds.reserve(certs_.size());
138 for (const auto& crt : certs_)
139 certIds.emplace_back(crt.first);
140 return certIds;
141}
142
143std::shared_ptr<crypto::Certificate>
144CertificateStore::getCertificate(const std::string& k)
145{
146 auto getCertificate_ = [this](const std::string& k) -> std::shared_ptr<crypto::Certificate> {
147 auto cit = certs_.find(k);
148 if (cit == certs_.cend())
149 return {};
150 return cit->second;
151 };
152 std::unique_lock<std::mutex> l(lock_);
153 auto crt = getCertificate_(k);
154 // Check if certificate is complete
155 // If the certificate has been splitted, reconstruct it
156 auto top_issuer = crt;
157 while (top_issuer && top_issuer->getUID() != top_issuer->getIssuerUID()) {
158 if (top_issuer->issuer) {
159 top_issuer = top_issuer->issuer;
160 } else if (auto cert = getCertificate_(top_issuer->getIssuerUID())) {
161 top_issuer->issuer = cert;
162 top_issuer = cert;
163 } else {
164 // In this case, a certificate was not found
165 if (logger_)
166 logger_->warn("Incomplete certificate detected {:s}", k);
167 break;
168 }
169 }
170 return crt;
171}
172
173std::shared_ptr<crypto::Certificate>
174CertificateStore::getCertificateLegacy(const std::string& dataDir, const std::string& k)
175{
Adrien Béraud8b831a82023-07-21 14:13:06 -0400176 try {
177 auto oldPath = fmt::format("{}/certificates/{}", dataDir, k);
178 if (fileutils::isFile(oldPath)) {
179 auto crt = std::make_shared<crypto::Certificate>(oldPath);
180 pinCertificate(crt, true);
181 return crt;
182 }
183 } catch (const std::exception& e) {
184 if (logger_)
185 logger_->warn("Can't load certificate: {:s}", e.what());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400186 }
187 return {};
188}
189
190std::shared_ptr<crypto::Certificate>
191CertificateStore::findCertificateByName(const std::string& name, crypto::NameType type) const
192{
193 std::unique_lock<std::mutex> l(lock_);
194 for (auto& i : certs_) {
195 if (i.second->getName() == name)
196 return i.second;
197 if (type != crypto::NameType::UNKNOWN) {
198 for (const auto& alt : i.second->getAltNames())
199 if (alt.first == type and alt.second == name)
200 return i.second;
201 }
202 }
203 return {};
204}
205
206std::shared_ptr<crypto::Certificate>
207CertificateStore::findCertificateByUID(const std::string& uid) const
208{
209 std::unique_lock<std::mutex> l(lock_);
210 for (auto& i : certs_) {
211 if (i.second->getUID() == uid)
212 return i.second;
213 }
214 return {};
215}
216
217std::shared_ptr<crypto::Certificate>
218CertificateStore::findIssuer(const std::shared_ptr<crypto::Certificate>& crt) const
219{
220 std::shared_ptr<crypto::Certificate> ret {};
221 auto n = crt->getIssuerUID();
222 if (not n.empty()) {
223 if (crt->issuer and crt->issuer->getUID() == n)
224 ret = crt->issuer;
225 else
226 ret = findCertificateByUID(n);
227 }
228 if (not ret) {
229 n = crt->getIssuerName();
230 if (not n.empty())
231 ret = findCertificateByName(n);
232 }
233 if (not ret)
234 return ret;
235 unsigned verify_out = 0;
236 int err = gnutls_x509_crt_verify(crt->cert, &ret->cert, 1, 0, &verify_out);
237 if (err != GNUTLS_E_SUCCESS) {
238 if (logger_)
239 logger_->warn("gnutls_x509_crt_verify failed: {:s}", gnutls_strerror(err));
240 return {};
241 }
242 if (verify_out & GNUTLS_CERT_INVALID)
243 return {};
244 return ret;
245}
246
247static std::vector<crypto::Certificate>
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400248readCertificates(const std::filesystem::path& path, const std::string& crl_path)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400249{
250 std::vector<crypto::Certificate> ret;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400251 if (std::filesystem::is_directory(path)) {
252 for (const auto& file : std::filesystem::directory_iterator(path)) {
253 auto certs = readCertificates(file, crl_path);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400254 ret.insert(std::end(ret),
255 std::make_move_iterator(std::begin(certs)),
256 std::make_move_iterator(std::end(certs)));
257 }
258 } else {
259 try {
260 auto data = fileutils::loadFile(path);
261 const gnutls_datum_t dt {data.data(), (unsigned) data.size()};
262 gnutls_x509_crt_t* certs {nullptr};
263 unsigned cert_num {0};
264 gnutls_x509_crt_list_import2(&certs, &cert_num, &dt, GNUTLS_X509_FMT_PEM, 0);
265 for (unsigned i = 0; i < cert_num; i++)
266 ret.emplace_back(certs[i]);
267 gnutls_free(certs);
268 } catch (const std::exception& e) {
269 };
270 }
271 return ret;
272}
273
274void
275CertificateStore::pinCertificatePath(const std::string& path,
276 std::function<void(const std::vector<std::string>&)> cb)
277{
278 dht::ThreadPool::computation().run([&, path, cb]() {
279 auto certs = readCertificates(path, crlPath_);
280 std::vector<std::string> ids;
281 std::vector<std::weak_ptr<crypto::Certificate>> scerts;
282 ids.reserve(certs.size());
283 scerts.reserve(certs.size());
284 {
285 std::lock_guard<std::mutex> l(lock_);
286
287 for (auto& cert : certs) {
Adrien Béraud8b831a82023-07-21 14:13:06 -0400288 try {
289 auto shared = std::make_shared<crypto::Certificate>(std::move(cert));
290 scerts.emplace_back(shared);
291 auto e = certs_.emplace(shared->getId().toString(), shared);
292 ids.emplace_back(e.first->first);
293 e = certs_.emplace(shared->getLongId().toString(), shared);
294 ids.emplace_back(e.first->first);
295 } catch (const std::exception& e) {
296 if (logger_)
297 logger_->warn("Can't load certificate: {:s}", e.what());
298 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400299 }
300 paths_.emplace(path, std::move(scerts));
301 }
302 if (logger_) logger_->d("CertificateStore: loaded %zu certificates from %s.", certs.size(), path.c_str());
303 if (cb)
304 cb(ids);
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400305 //emitSignal<libdhtnet::ConfigurationSignal::CertificatePathPinned>(path, ids);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400306 });
307}
308
309unsigned
310CertificateStore::unpinCertificatePath(const std::string& path)
311{
312 std::lock_guard<std::mutex> l(lock_);
313
314 auto certs = paths_.find(path);
315 if (certs == std::end(paths_))
316 return 0;
317 unsigned n = 0;
318 for (const auto& wcert : certs->second) {
319 if (auto cert = wcert.lock()) {
320 certs_.erase(cert->getId().toString());
321 ++n;
322 }
323 }
324 paths_.erase(certs);
325 return n;
326}
327
328std::vector<std::string>
329CertificateStore::pinCertificate(const std::vector<uint8_t>& cert, bool local) noexcept
330{
331 try {
332 return pinCertificate(crypto::Certificate(cert), local);
333 } catch (const std::exception& e) {
334 }
335 return {};
336}
337
338std::vector<std::string>
339CertificateStore::pinCertificate(crypto::Certificate&& cert, bool local)
340{
341 return pinCertificate(std::make_shared<crypto::Certificate>(std::move(cert)), local);
342}
343
344std::vector<std::string>
345CertificateStore::pinCertificate(const std::shared_ptr<crypto::Certificate>& cert, bool local)
346{
347 bool sig {false};
348 std::vector<std::string> ids {};
349 {
350 auto c = cert;
351 std::lock_guard<std::mutex> l(lock_);
352 while (c) {
353 bool inserted;
354 auto id = c->getId().toString();
355 auto longId = c->getLongId().toString();
356 decltype(certs_)::iterator it;
357 std::tie(it, inserted) = certs_.emplace(id, c);
358 if (not inserted)
359 it->second = c;
360 std::tie(it, inserted) = certs_.emplace(longId, c);
361 if (not inserted)
362 it->second = c;
363 if (local) {
364 for (const auto& crl : c->getRevocationLists())
365 pinRevocationList(id, *crl);
366 }
367 ids.emplace_back(longId);
368 ids.emplace_back(id);
369 c = c->issuer;
370 sig |= inserted;
371 }
372 if (local) {
373 if (sig)
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400374 fileutils::saveFile(certPath_ / ids.front(), cert->getPacked());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400375 }
376 }
377 //for (const auto& id : ids)
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400378 // emitSignal<libdhtnet::ConfigurationSignal::CertificatePinned>(id);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400379 return ids;
380}
381
382bool
383CertificateStore::unpinCertificate(const std::string& id)
384{
385 std::lock_guard<std::mutex> l(lock_);
386
387 certs_.erase(id);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400388 return remove(certPath_ / id);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400389}
390
391bool
392CertificateStore::setTrustedCertificate(const std::string& id, TrustStatus status)
393{
394 if (status == TrustStatus::TRUSTED) {
395 if (auto crt = getCertificate(id)) {
396 trustedCerts_.emplace_back(crt);
397 return true;
398 }
399 } else {
400 auto tc = std::find_if(trustedCerts_.begin(),
401 trustedCerts_.end(),
402 [&](const std::shared_ptr<crypto::Certificate>& crt) {
403 return crt->getId().toString() == id;
404 });
405 if (tc != trustedCerts_.end()) {
406 trustedCerts_.erase(tc);
407 return true;
408 }
409 }
410 return false;
411}
412
413std::vector<gnutls_x509_crt_t>
414CertificateStore::getTrustedCertificates() const
415{
416 std::vector<gnutls_x509_crt_t> crts;
417 crts.reserve(trustedCerts_.size());
418 for (auto& crt : trustedCerts_)
419 crts.emplace_back(crt->getCopy());
420 return crts;
421}
422
423void
424CertificateStore::pinRevocationList(const std::string& id,
425 const std::shared_ptr<dht::crypto::RevocationList>& crl)
426{
427 try {
428 if (auto c = getCertificate(id))
429 c->addRevocationList(crl);
430 pinRevocationList(id, *crl);
431 } catch (...) {
432 if (logger_)
433 logger_->warn("Can't add revocation list");
434 }
435}
436
437void
438CertificateStore::pinRevocationList(const std::string& id, const dht::crypto::RevocationList& crl)
439{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400440 fileutils::check_dir(crlPath_ / id);
441 fileutils::saveFile(crlPath_ / id / dht::toHex(crl.getNumber()),
Adrien Béraud612b55b2023-05-29 10:42:04 -0400442 crl.getPacked());
443}
444
445void
446CertificateStore::pinOcspResponse(const dht::crypto::Certificate& cert)
447{
448 if (not cert.ocspResponse)
449 return;
450 try {
451 cert.ocspResponse->getCertificateStatus();
452 } catch (dht::crypto::CryptoException& e) {
453 if (logger_) logger_->error("Failed to read certificate status of OCSP response: {:s}", e.what());
454 return;
455 }
456 auto id = cert.getId().toString();
457 auto serial = cert.getSerialNumber();
458 auto serialhex = dht::toHex(serial);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400459 auto dir = ocspPath_ / id;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400460
461 if (auto localCert = getCertificate(id)) {
462 // Update certificate in the local store if relevant
463 if (localCert.get() != &cert && serial == localCert->getSerialNumber()) {
464 if (logger_) logger_->d("Updating OCSP for certificate %s in the local store", id.c_str());
465 localCert->ocspResponse = cert.ocspResponse;
466 }
467 }
468
469 dht::ThreadPool::io().run([l=logger_,
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400470 path = dir / serialhex,
Adrien Béraud612b55b2023-05-29 10:42:04 -0400471 dir = std::move(dir),
472 id = std::move(id),
473 serialhex = std::move(serialhex),
474 ocspResponse = cert.ocspResponse] {
475 if (l) l->d("Saving OCSP Response of device %s with serial %s", id.c_str(), serialhex.c_str());
476 std::lock_guard<std::mutex> lock(fileutils::getFileLock(path));
477 fileutils::check_dir(dir.c_str());
478 fileutils::saveFile(path, ocspResponse->pack());
479 });
480}
481
482TrustStore::PermissionStatus
483TrustStore::statusFromStr(const char* str)
484{
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400485 if (!std::strcmp(str, libdhtnet::Certificate::Status::ALLOWED))
Adrien Béraud612b55b2023-05-29 10:42:04 -0400486 return PermissionStatus::ALLOWED;
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400487 if (!std::strcmp(str, libdhtnet::Certificate::Status::BANNED))
Adrien Béraud612b55b2023-05-29 10:42:04 -0400488 return PermissionStatus::BANNED;
489 return PermissionStatus::UNDEFINED;
490}
491
492const char*
493TrustStore::statusToStr(TrustStore::PermissionStatus s)
494{
495 switch (s) {
496 case PermissionStatus::ALLOWED:
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400497 return libdhtnet::Certificate::Status::ALLOWED;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400498 case PermissionStatus::BANNED:
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400499 return libdhtnet::Certificate::Status::BANNED;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400500 case PermissionStatus::UNDEFINED:
501 default:
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400502 return libdhtnet::Certificate::Status::UNDEFINED;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400503 }
504}
505
506TrustStatus
507trustStatusFromStr(const char* str)
508{
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400509 if (!std::strcmp(str, libdhtnet::Certificate::TrustStatus::TRUSTED))
Adrien Béraud612b55b2023-05-29 10:42:04 -0400510 return TrustStatus::TRUSTED;
511 return TrustStatus::UNTRUSTED;
512}
513
514const char*
515statusToStr(TrustStatus s)
516{
517 switch (s) {
518 case TrustStatus::TRUSTED:
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400519 return libdhtnet::Certificate::TrustStatus::TRUSTED;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400520 case TrustStatus::UNTRUSTED:
521 default:
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400522 return libdhtnet::Certificate::TrustStatus::UNTRUSTED;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400523 }
524}
525
526bool
527TrustStore::addRevocationList(dht::crypto::RevocationList&& crl)
528{
529 allowed_.add(crl);
530 return true;
531}
532
533bool
534TrustStore::setCertificateStatus(const std::string& cert_id,
535 const TrustStore::PermissionStatus status)
536{
537 return setCertificateStatus(nullptr, cert_id, status, false);
538}
539
540bool
541TrustStore::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
542 const TrustStore::PermissionStatus status,
543 bool local)
544{
545 return setCertificateStatus(cert, cert->getId().toString(), status, local);
546}
547
548bool
549TrustStore::setCertificateStatus(std::shared_ptr<crypto::Certificate> cert,
550 const std::string& cert_id,
551 const TrustStore::PermissionStatus status,
552 bool local)
553{
554 if (cert)
555 certStore_.pinCertificate(cert, local);
556 std::lock_guard<std::recursive_mutex> lk(mutex_);
557 updateKnownCerts();
558 bool dirty {false};
559 if (status == PermissionStatus::UNDEFINED) {
560 unknownCertStatus_.erase(cert_id);
561 dirty = certStatus_.erase(cert_id);
562 } else {
563 bool allowed = (status == PermissionStatus::ALLOWED);
564 auto s = certStatus_.find(cert_id);
565 if (s == std::end(certStatus_)) {
566 // Certificate state is currently undefined
567 if (not cert)
568 cert = certStore_.getCertificate(cert_id);
569 if (cert) {
570 unknownCertStatus_.erase(cert_id);
571 auto& crt_status = certStatus_[cert_id];
572 if (not crt_status.first)
573 crt_status.first = cert;
574 crt_status.second.allowed = allowed;
575 setStoreCertStatus(*cert, allowed);
576 } else {
577 // Can't find certificate
578 unknownCertStatus_[cert_id].allowed = allowed;
579 }
580 } else {
581 // Certificate is already allowed or banned
582 if (s->second.second.allowed != allowed) {
583 s->second.second.allowed = allowed;
584 if (allowed) // Certificate is re-added after ban, rebuld needed
585 dirty = true;
586 else
587 allowed_.remove(*s->second.first, false);
588 }
589 }
590 }
591 if (dirty)
592 rebuildTrust();
593 return true;
594}
595
596TrustStore::PermissionStatus
597TrustStore::getCertificateStatus(const std::string& cert_id) const
598{
599 std::lock_guard<std::recursive_mutex> lk(mutex_);
600 auto s = certStatus_.find(cert_id);
601 if (s == std::end(certStatus_)) {
602 auto us = unknownCertStatus_.find(cert_id);
603 if (us == std::end(unknownCertStatus_))
604 return PermissionStatus::UNDEFINED;
605 return us->second.allowed ? PermissionStatus::ALLOWED : PermissionStatus::BANNED;
606 }
607 return s->second.second.allowed ? PermissionStatus::ALLOWED : PermissionStatus::BANNED;
608}
609
610std::vector<std::string>
611TrustStore::getCertificatesByStatus(TrustStore::PermissionStatus status) const
612{
613 std::lock_guard<std::recursive_mutex> lk(mutex_);
614 std::vector<std::string> ret;
615 for (const auto& i : certStatus_)
616 if (i.second.second.allowed == (status == TrustStore::PermissionStatus::ALLOWED))
617 ret.emplace_back(i.first);
618 for (const auto& i : unknownCertStatus_)
619 if (i.second.allowed == (status == TrustStore::PermissionStatus::ALLOWED))
620 ret.emplace_back(i.first);
621 return ret;
622}
623
624bool
625TrustStore::isAllowed(const crypto::Certificate& crt, bool allowPublic)
626{
627 // Match by certificate pinning
628 std::lock_guard<std::recursive_mutex> lk(mutex_);
629 bool allowed {allowPublic};
630 for (auto c = &crt; c; c = c->issuer.get()) {
631 auto status = getCertificateStatus(c->getId().toString()); // lock mutex_
632 if (status == PermissionStatus::ALLOWED)
633 allowed = true;
634 else if (status == PermissionStatus::BANNED)
635 return false;
636 }
637
638 // Match by certificate chain
639 updateKnownCerts();
640 auto ret = allowed_.verify(crt);
641 // Unknown issuer (only that) are accepted if allowPublic is true
642 if (not ret
643 and !(allowPublic and ret.result == (GNUTLS_CERT_INVALID | GNUTLS_CERT_SIGNER_NOT_FOUND))) {
644 if (certStore_.logger())
645 certStore_.logger()->warn("%s", ret.toString().c_str());
646 return false;
647 }
648
649 return allowed;
650}
651
652void
653TrustStore::updateKnownCerts()
654{
655 auto i = std::begin(unknownCertStatus_);
656 while (i != std::end(unknownCertStatus_)) {
657 if (auto crt = certStore_.getCertificate(i->first)) {
658 certStatus_.emplace(i->first, std::make_pair(crt, i->second));
659 setStoreCertStatus(*crt, i->second.allowed);
660 i = unknownCertStatus_.erase(i);
661 } else
662 ++i;
663 }
664}
665
666void
667TrustStore::setStoreCertStatus(const crypto::Certificate& crt, bool status)
668{
669 if (status)
670 allowed_.add(crt);
671 else
672 allowed_.remove(crt, false);
673}
674
675void
676TrustStore::rebuildTrust()
677{
678 allowed_ = {};
679 for (const auto& c : certStatus_)
680 setStoreCertStatus(*c.second.first, c.second.second.allowed);
681}
682
683} // namespace tls
Sébastien Blin464bdff2023-07-19 08:02:53 -0400684} // namespace dhtnet