blob: cea117af8965a689f8ccaf2616ef5323701a85f7 [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
Amnab31c3742023-08-28 13:58:31 -040027#if __has_include(<fmt/std.h>)
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040028#include <fmt/std.h>
Amnab31c3742023-08-28 13:58:31 -040029#else
30#include <fmt/ostream.h>
31#endif
Adrien Béraud612b55b2023-05-29 10:42:04 -040032#include <thread>
33#include <sstream>
34#include <fmt/format.h>
35
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040036namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040037namespace tls {
38
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040039CertificateStore::CertificateStore(const std::filesystem::path& path, std::shared_ptr<Logger> logger)
Adrien Béraud612b55b2023-05-29 10:42:04 -040040 : logger_(std::move(logger))
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040041 , certPath_(path / "certificates")
42 , crlPath_(path /"crls")
43 , ocspPath_(path /"oscp")
Adrien Béraud612b55b2023-05-29 10:42:04 -040044{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040045 fileutils::check_dir(certPath_);
46 fileutils::check_dir(crlPath_);
47 fileutils::check_dir(ocspPath_);
Adrien Béraud612b55b2023-05-29 10:42:04 -040048 loadLocalCertificates();
49}
50
51unsigned
52CertificateStore::loadLocalCertificates()
53{
54 std::lock_guard<std::mutex> l(lock_);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040055 if (logger_)
56 logger_->debug("CertificateStore: loading certificates from {}", certPath_);
Adrien Béraud612b55b2023-05-29 10:42:04 -040057
Adrien Béraud612b55b2023-05-29 10:42:04 -040058 unsigned n = 0;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040059 std::error_code ec;
60 for (const auto& crtPath : std::filesystem::directory_iterator(certPath_, ec)) {
61 const auto& path = crtPath.path();
62 auto fileName = path.filename().string();
Adrien Béraud612b55b2023-05-29 10:42:04 -040063 try {
64 auto crt = std::make_shared<crypto::Certificate>(
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040065 fileutils::loadFile(crtPath));
Adrien Béraud612b55b2023-05-29 10:42:04 -040066 auto id = crt->getId().toString();
67 auto longId = crt->getLongId().toString();
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040068 if (id != fileName && longId != fileName)
Adrien Béraud612b55b2023-05-29 10:42:04 -040069 throw std::logic_error("Certificate id mismatch");
70 while (crt) {
71 id = crt->getId().toString();
72 longId = crt->getLongId().toString();
73 certs_.emplace(std::move(id), crt);
74 certs_.emplace(std::move(longId), crt);
75 loadRevocations(*crt);
76 crt = crt->issuer;
77 ++n;
78 }
79 } catch (const std::exception& e) {
80 if (logger_)
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040081 logger_->warn("loadLocalCertificates: error loading {}: {}", path, e.what());
82 remove(path);
Adrien Béraud612b55b2023-05-29 10:42:04 -040083 }
84 }
85 if (logger_)
86 logger_->debug("CertificateStore: loaded {} local certificates.", n);
87 return n;
88}
89
90void
91CertificateStore::loadRevocations(crypto::Certificate& crt) const
92{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040093 std::error_code ec;
94 auto dir = crlPath_ / crt.getId().toString();
95 for (const auto& crl : std::filesystem::directory_iterator(dir, ec)) {
Adrien Béraud612b55b2023-05-29 10:42:04 -040096 try {
97 crt.addRevocationList(std::make_shared<crypto::RevocationList>(
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040098 fileutils::loadFile(crl)));
Adrien Béraud612b55b2023-05-29 10:42:04 -040099 } catch (const std::exception& e) {
100 if (logger_)
101 logger_->warn("Can't load revocation list: %s", e.what());
102 }
103 }
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400104
105 auto ocsp_dir = ocspPath_ / crt.getId().toString();
106 for (const auto& ocsp_filepath : std::filesystem::directory_iterator(ocsp_dir, ec)) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400107 try {
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400108 auto ocsp = ocsp_filepath.path().filename().string();
109 if (logger_) logger_->debug("Found {}", ocsp_filepath.path());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400110 auto serial = crt.getSerialNumber();
111 if (dht::toHex(serial.data(), serial.size()) != ocsp)
112 continue;
113 // Save the response
114 auto ocspBlob = fileutils::loadFile(ocsp_filepath);
115 crt.ocspResponse = std::make_shared<dht::crypto::OcspResponse>(ocspBlob.data(),
116 ocspBlob.size());
117 unsigned int status = crt.ocspResponse->getCertificateStatus();
118 if (status == GNUTLS_OCSP_CERT_GOOD) {
119 if (logger_) logger_->debug("Certificate {:s} has good OCSP status", crt.getId());
120 } else if (status == GNUTLS_OCSP_CERT_REVOKED) {
121 if (logger_) logger_->error("Certificate {:s} has revoked OCSP status", crt.getId());
122 } else if (status == GNUTLS_OCSP_CERT_UNKNOWN) {
123 if (logger_) logger_->error("Certificate {:s} has unknown OCSP status", crt.getId());
124 } else {
125 if (logger_) logger_->error("Certificate {:s} has invalid OCSP status", crt.getId());
126 }
127 } catch (const std::exception& e) {
128 if (logger_)
129 logger_->warn("Can't load OCSP revocation status: {:s}", e.what());
130 }
131 }
132}
133
134std::vector<std::string>
135CertificateStore::getPinnedCertificates() const
136{
137 std::lock_guard<std::mutex> l(lock_);
138
139 std::vector<std::string> certIds;
140 certIds.reserve(certs_.size());
141 for (const auto& crt : certs_)
142 certIds.emplace_back(crt.first);
143 return certIds;
144}
145
146std::shared_ptr<crypto::Certificate>
147CertificateStore::getCertificate(const std::string& k)
148{
149 auto getCertificate_ = [this](const std::string& k) -> std::shared_ptr<crypto::Certificate> {
150 auto cit = certs_.find(k);
Adrien Béraud036612b2023-12-08 14:19:50 -0500151 return cit != certs_.cend() ? cit->second : std::shared_ptr<crypto::Certificate>{};
Adrien Béraud612b55b2023-05-29 10:42:04 -0400152 };
153 std::unique_lock<std::mutex> l(lock_);
154 auto crt = getCertificate_(k);
155 // Check if certificate is complete
156 // If the certificate has been splitted, reconstruct it
157 auto top_issuer = crt;
158 while (top_issuer && top_issuer->getUID() != top_issuer->getIssuerUID()) {
159 if (top_issuer->issuer) {
160 top_issuer = top_issuer->issuer;
161 } else if (auto cert = getCertificate_(top_issuer->getIssuerUID())) {
162 top_issuer->issuer = cert;
163 top_issuer = cert;
164 } else {
165 // In this case, a certificate was not found
166 if (logger_)
167 logger_->warn("Incomplete certificate detected {:s}", k);
168 break;
169 }
170 }
171 return crt;
172}
173
174std::shared_ptr<crypto::Certificate>
175CertificateStore::getCertificateLegacy(const std::string& dataDir, const std::string& k)
176{
Adrien Béraud8b831a82023-07-21 14:13:06 -0400177 try {
178 auto oldPath = fmt::format("{}/certificates/{}", dataDir, k);
179 if (fileutils::isFile(oldPath)) {
180 auto crt = std::make_shared<crypto::Certificate>(oldPath);
181 pinCertificate(crt, true);
182 return crt;
183 }
184 } catch (const std::exception& e) {
185 if (logger_)
186 logger_->warn("Can't load certificate: {:s}", e.what());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400187 }
188 return {};
189}
190
191std::shared_ptr<crypto::Certificate>
192CertificateStore::findCertificateByName(const std::string& name, crypto::NameType type) const
193{
194 std::unique_lock<std::mutex> l(lock_);
195 for (auto& i : certs_) {
196 if (i.second->getName() == name)
197 return i.second;
198 if (type != crypto::NameType::UNKNOWN) {
199 for (const auto& alt : i.second->getAltNames())
200 if (alt.first == type and alt.second == name)
201 return i.second;
202 }
203 }
204 return {};
205}
206
207std::shared_ptr<crypto::Certificate>
208CertificateStore::findCertificateByUID(const std::string& uid) const
209{
210 std::unique_lock<std::mutex> l(lock_);
211 for (auto& i : certs_) {
212 if (i.second->getUID() == uid)
213 return i.second;
214 }
215 return {};
216}
217
218std::shared_ptr<crypto::Certificate>
219CertificateStore::findIssuer(const std::shared_ptr<crypto::Certificate>& crt) const
220{
221 std::shared_ptr<crypto::Certificate> ret {};
222 auto n = crt->getIssuerUID();
223 if (not n.empty()) {
224 if (crt->issuer and crt->issuer->getUID() == n)
225 ret = crt->issuer;
226 else
227 ret = findCertificateByUID(n);
228 }
229 if (not ret) {
230 n = crt->getIssuerName();
231 if (not n.empty())
232 ret = findCertificateByName(n);
233 }
234 if (not ret)
235 return ret;
236 unsigned verify_out = 0;
237 int err = gnutls_x509_crt_verify(crt->cert, &ret->cert, 1, 0, &verify_out);
238 if (err != GNUTLS_E_SUCCESS) {
239 if (logger_)
240 logger_->warn("gnutls_x509_crt_verify failed: {:s}", gnutls_strerror(err));
241 return {};
242 }
243 if (verify_out & GNUTLS_CERT_INVALID)
244 return {};
245 return ret;
246}
247
248static std::vector<crypto::Certificate>
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400249readCertificates(const std::filesystem::path& path, const std::string& crl_path)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400250{
251 std::vector<crypto::Certificate> ret;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400252 if (std::filesystem::is_directory(path)) {
Adrien Béraud67a24fc2023-10-13 12:04:59 -0400253 std::error_code ec;
254 for (const auto& file : std::filesystem::directory_iterator(path, ec)) {
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400255 auto certs = readCertificates(file, crl_path);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400256 ret.insert(std::end(ret),
257 std::make_move_iterator(std::begin(certs)),
258 std::make_move_iterator(std::end(certs)));
259 }
260 } else {
261 try {
262 auto data = fileutils::loadFile(path);
263 const gnutls_datum_t dt {data.data(), (unsigned) data.size()};
264 gnutls_x509_crt_t* certs {nullptr};
265 unsigned cert_num {0};
266 gnutls_x509_crt_list_import2(&certs, &cert_num, &dt, GNUTLS_X509_FMT_PEM, 0);
267 for (unsigned i = 0; i < cert_num; i++)
268 ret.emplace_back(certs[i]);
269 gnutls_free(certs);
270 } catch (const std::exception& e) {
271 };
272 }
273 return ret;
274}
275
276void
277CertificateStore::pinCertificatePath(const std::string& path,
278 std::function<void(const std::vector<std::string>&)> cb)
279{
280 dht::ThreadPool::computation().run([&, path, cb]() {
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300281 auto certs = readCertificates(path, crlPath_.string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400282 std::vector<std::string> ids;
283 std::vector<std::weak_ptr<crypto::Certificate>> scerts;
284 ids.reserve(certs.size());
285 scerts.reserve(certs.size());
286 {
287 std::lock_guard<std::mutex> l(lock_);
288
289 for (auto& cert : certs) {
Adrien Béraud8b831a82023-07-21 14:13:06 -0400290 try {
291 auto shared = std::make_shared<crypto::Certificate>(std::move(cert));
292 scerts.emplace_back(shared);
293 auto e = certs_.emplace(shared->getId().toString(), shared);
294 ids.emplace_back(e.first->first);
295 e = certs_.emplace(shared->getLongId().toString(), shared);
296 ids.emplace_back(e.first->first);
297 } catch (const std::exception& e) {
298 if (logger_)
299 logger_->warn("Can't load certificate: {:s}", e.what());
300 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400301 }
302 paths_.emplace(path, std::move(scerts));
303 }
304 if (logger_) logger_->d("CertificateStore: loaded %zu certificates from %s.", certs.size(), path.c_str());
305 if (cb)
306 cb(ids);
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400307 //emitSignal<libdhtnet::ConfigurationSignal::CertificatePathPinned>(path, ids);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400308 });
309}
310
311unsigned
312CertificateStore::unpinCertificatePath(const std::string& path)
313{
314 std::lock_guard<std::mutex> l(lock_);
315
316 auto certs = paths_.find(path);
317 if (certs == std::end(paths_))
318 return 0;
319 unsigned n = 0;
320 for (const auto& wcert : certs->second) {
321 if (auto cert = wcert.lock()) {
322 certs_.erase(cert->getId().toString());
323 ++n;
324 }
325 }
326 paths_.erase(certs);
327 return n;
328}
329
330std::vector<std::string>
331CertificateStore::pinCertificate(const std::vector<uint8_t>& cert, bool local) noexcept
332{
333 try {
334 return pinCertificate(crypto::Certificate(cert), local);
335 } catch (const std::exception& e) {
336 }
337 return {};
338}
339
340std::vector<std::string>
341CertificateStore::pinCertificate(crypto::Certificate&& cert, bool local)
342{
343 return pinCertificate(std::make_shared<crypto::Certificate>(std::move(cert)), local);
344}
345
346std::vector<std::string>
347CertificateStore::pinCertificate(const std::shared_ptr<crypto::Certificate>& cert, bool local)
348{
349 bool sig {false};
350 std::vector<std::string> ids {};
351 {
352 auto c = cert;
353 std::lock_guard<std::mutex> l(lock_);
354 while (c) {
355 bool inserted;
356 auto id = c->getId().toString();
357 auto longId = c->getLongId().toString();
358 decltype(certs_)::iterator it;
359 std::tie(it, inserted) = certs_.emplace(id, c);
360 if (not inserted)
361 it->second = c;
362 std::tie(it, inserted) = certs_.emplace(longId, c);
363 if (not inserted)
364 it->second = c;
365 if (local) {
366 for (const auto& crl : c->getRevocationLists())
367 pinRevocationList(id, *crl);
368 }
369 ids.emplace_back(longId);
370 ids.emplace_back(id);
371 c = c->issuer;
372 sig |= inserted;
373 }
374 if (local) {
375 if (sig)
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400376 fileutils::saveFile(certPath_ / ids.front(), cert->getPacked());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400377 }
378 }
379 //for (const auto& id : ids)
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400380 // emitSignal<libdhtnet::ConfigurationSignal::CertificatePinned>(id);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400381 return ids;
382}
383
384bool
385CertificateStore::unpinCertificate(const std::string& id)
386{
387 std::lock_guard<std::mutex> l(lock_);
388
389 certs_.erase(id);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400390 return remove(certPath_ / id);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400391}
392
393bool
394CertificateStore::setTrustedCertificate(const std::string& id, TrustStatus status)
395{
396 if (status == TrustStatus::TRUSTED) {
397 if (auto crt = getCertificate(id)) {
398 trustedCerts_.emplace_back(crt);
399 return true;
400 }
401 } else {
402 auto tc = std::find_if(trustedCerts_.begin(),
403 trustedCerts_.end(),
404 [&](const std::shared_ptr<crypto::Certificate>& crt) {
405 return crt->getId().toString() == id;
406 });
407 if (tc != trustedCerts_.end()) {
408 trustedCerts_.erase(tc);
409 return true;
410 }
411 }
412 return false;
413}
414
415std::vector<gnutls_x509_crt_t>
416CertificateStore::getTrustedCertificates() const
417{
418 std::vector<gnutls_x509_crt_t> crts;
419 crts.reserve(trustedCerts_.size());
420 for (auto& crt : trustedCerts_)
421 crts.emplace_back(crt->getCopy());
422 return crts;
423}
424
425void
426CertificateStore::pinRevocationList(const std::string& id,
427 const std::shared_ptr<dht::crypto::RevocationList>& crl)
428{
429 try {
430 if (auto c = getCertificate(id))
431 c->addRevocationList(crl);
432 pinRevocationList(id, *crl);
433 } catch (...) {
434 if (logger_)
435 logger_->warn("Can't add revocation list");
436 }
437}
438
439void
440CertificateStore::pinRevocationList(const std::string& id, const dht::crypto::RevocationList& crl)
441{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400442 fileutils::check_dir(crlPath_ / id);
443 fileutils::saveFile(crlPath_ / id / dht::toHex(crl.getNumber()),
Adrien Béraud612b55b2023-05-29 10:42:04 -0400444 crl.getPacked());
445}
446
447void
448CertificateStore::pinOcspResponse(const dht::crypto::Certificate& cert)
449{
450 if (not cert.ocspResponse)
451 return;
452 try {
453 cert.ocspResponse->getCertificateStatus();
454 } catch (dht::crypto::CryptoException& e) {
455 if (logger_) logger_->error("Failed to read certificate status of OCSP response: {:s}", e.what());
456 return;
457 }
458 auto id = cert.getId().toString();
459 auto serial = cert.getSerialNumber();
460 auto serialhex = dht::toHex(serial);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400461 auto dir = ocspPath_ / id;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400462
463 if (auto localCert = getCertificate(id)) {
464 // Update certificate in the local store if relevant
465 if (localCert.get() != &cert && serial == localCert->getSerialNumber()) {
466 if (logger_) logger_->d("Updating OCSP for certificate %s in the local store", id.c_str());
467 localCert->ocspResponse = cert.ocspResponse;
468 }
469 }
470
471 dht::ThreadPool::io().run([l=logger_,
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400472 path = dir / serialhex,
Adrien Béraud612b55b2023-05-29 10:42:04 -0400473 dir = std::move(dir),
474 id = std::move(id),
475 serialhex = std::move(serialhex),
476 ocspResponse = cert.ocspResponse] {
477 if (l) l->d("Saving OCSP Response of device %s with serial %s", id.c_str(), serialhex.c_str());
478 std::lock_guard<std::mutex> lock(fileutils::getFileLock(path));
479 fileutils::check_dir(dir.c_str());
480 fileutils::saveFile(path, ocspResponse->pack());
481 });
482}
483
484TrustStore::PermissionStatus
485TrustStore::statusFromStr(const char* str)
486{
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400487 if (!std::strcmp(str, libdhtnet::Certificate::Status::ALLOWED))
Adrien Béraud612b55b2023-05-29 10:42:04 -0400488 return PermissionStatus::ALLOWED;
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400489 if (!std::strcmp(str, libdhtnet::Certificate::Status::BANNED))
Adrien Béraud612b55b2023-05-29 10:42:04 -0400490 return PermissionStatus::BANNED;
491 return PermissionStatus::UNDEFINED;
492}
493
494const char*
495TrustStore::statusToStr(TrustStore::PermissionStatus s)
496{
497 switch (s) {
498 case PermissionStatus::ALLOWED:
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400499 return libdhtnet::Certificate::Status::ALLOWED;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400500 case PermissionStatus::BANNED:
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400501 return libdhtnet::Certificate::Status::BANNED;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400502 case PermissionStatus::UNDEFINED:
503 default:
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400504 return libdhtnet::Certificate::Status::UNDEFINED;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400505 }
506}
507
508TrustStatus
509trustStatusFromStr(const char* str)
510{
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400511 if (!std::strcmp(str, libdhtnet::Certificate::TrustStatus::TRUSTED))
Adrien Béraud612b55b2023-05-29 10:42:04 -0400512 return TrustStatus::TRUSTED;
513 return TrustStatus::UNTRUSTED;
514}
515
516const char*
517statusToStr(TrustStatus s)
518{
519 switch (s) {
520 case TrustStatus::TRUSTED:
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400521 return libdhtnet::Certificate::TrustStatus::TRUSTED;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400522 case TrustStatus::UNTRUSTED:
523 default:
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400524 return libdhtnet::Certificate::TrustStatus::UNTRUSTED;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400525 }
526}
527
528bool
529TrustStore::addRevocationList(dht::crypto::RevocationList&& crl)
530{
531 allowed_.add(crl);
532 return true;
533}
534
535bool
536TrustStore::setCertificateStatus(const std::string& cert_id,
537 const TrustStore::PermissionStatus status)
538{
539 return setCertificateStatus(nullptr, cert_id, status, false);
540}
541
542bool
543TrustStore::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
544 const TrustStore::PermissionStatus status,
545 bool local)
546{
547 return setCertificateStatus(cert, cert->getId().toString(), status, local);
548}
549
550bool
551TrustStore::setCertificateStatus(std::shared_ptr<crypto::Certificate> cert,
552 const std::string& cert_id,
553 const TrustStore::PermissionStatus status,
554 bool local)
555{
556 if (cert)
557 certStore_.pinCertificate(cert, local);
558 std::lock_guard<std::recursive_mutex> lk(mutex_);
559 updateKnownCerts();
560 bool dirty {false};
561 if (status == PermissionStatus::UNDEFINED) {
562 unknownCertStatus_.erase(cert_id);
563 dirty = certStatus_.erase(cert_id);
564 } else {
565 bool allowed = (status == PermissionStatus::ALLOWED);
566 auto s = certStatus_.find(cert_id);
567 if (s == std::end(certStatus_)) {
568 // Certificate state is currently undefined
569 if (not cert)
570 cert = certStore_.getCertificate(cert_id);
571 if (cert) {
572 unknownCertStatus_.erase(cert_id);
573 auto& crt_status = certStatus_[cert_id];
574 if (not crt_status.first)
575 crt_status.first = cert;
576 crt_status.second.allowed = allowed;
577 setStoreCertStatus(*cert, allowed);
578 } else {
579 // Can't find certificate
580 unknownCertStatus_[cert_id].allowed = allowed;
581 }
582 } else {
583 // Certificate is already allowed or banned
584 if (s->second.second.allowed != allowed) {
585 s->second.second.allowed = allowed;
586 if (allowed) // Certificate is re-added after ban, rebuld needed
587 dirty = true;
588 else
589 allowed_.remove(*s->second.first, false);
590 }
591 }
592 }
593 if (dirty)
594 rebuildTrust();
595 return true;
596}
597
598TrustStore::PermissionStatus
599TrustStore::getCertificateStatus(const std::string& cert_id) const
600{
601 std::lock_guard<std::recursive_mutex> lk(mutex_);
Sébastien Blin57928252023-08-08 14:22:03 -0400602 auto cert = certStore_.getCertificate(cert_id);
603 if (!cert)
604 return PermissionStatus::UNDEFINED;
Adrien Béraudd8fb8d92023-12-07 10:08:38 -0500605 auto allowed = false;
606 auto found = false;
Sébastien Blin57928252023-08-08 14:22:03 -0400607 while (cert) {
608 auto s = certStatus_.find(cert->getId().toString());
609 if (s != std::end(certStatus_)) {
610 if (!found) {
Adrien Béraudd8fb8d92023-12-07 10:08:38 -0500611 found = true;
612 allowed = true; // we need to find at least a certificate
Sébastien Blin57928252023-08-08 14:22:03 -0400613 }
614 allowed &= s->second.second.allowed;
615 if (!allowed)
616 return PermissionStatus::BANNED;
617 } else {
618 auto us = unknownCertStatus_.find(cert->getId().toString());
619 if (us != std::end(unknownCertStatus_)) {
620 if (!found) {
Adrien Béraudd8fb8d92023-12-07 10:08:38 -0500621 found = true;
622 allowed = true; // we need to find at least a certificate
Sébastien Blin57928252023-08-08 14:22:03 -0400623 }
Adrien Béraudd8fb8d92023-12-07 10:08:38 -0500624 allowed &= us->second.allowed;
625 if (!allowed)
Sébastien Blin57928252023-08-08 14:22:03 -0400626 return PermissionStatus::BANNED;
627 }
628 }
629 if (cert->getUID() == cert->getIssuerUID())
630 break;
631 cert = cert->issuer? cert->issuer : certStore_.getCertificate(cert->getIssuerUID());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400632 }
Sébastien Blin57928252023-08-08 14:22:03 -0400633
Adrien Béraud036612b2023-12-08 14:19:50 -0500634 return allowed ? PermissionStatus::ALLOWED : PermissionStatus::UNDEFINED;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400635}
636
637std::vector<std::string>
638TrustStore::getCertificatesByStatus(TrustStore::PermissionStatus status) const
639{
640 std::lock_guard<std::recursive_mutex> lk(mutex_);
641 std::vector<std::string> ret;
642 for (const auto& i : certStatus_)
643 if (i.second.second.allowed == (status == TrustStore::PermissionStatus::ALLOWED))
644 ret.emplace_back(i.first);
645 for (const auto& i : unknownCertStatus_)
646 if (i.second.allowed == (status == TrustStore::PermissionStatus::ALLOWED))
647 ret.emplace_back(i.first);
648 return ret;
649}
650
651bool
652TrustStore::isAllowed(const crypto::Certificate& crt, bool allowPublic)
653{
654 // Match by certificate pinning
655 std::lock_guard<std::recursive_mutex> lk(mutex_);
656 bool allowed {allowPublic};
657 for (auto c = &crt; c; c = c->issuer.get()) {
658 auto status = getCertificateStatus(c->getId().toString()); // lock mutex_
659 if (status == PermissionStatus::ALLOWED)
660 allowed = true;
661 else if (status == PermissionStatus::BANNED)
662 return false;
663 }
664
665 // Match by certificate chain
666 updateKnownCerts();
667 auto ret = allowed_.verify(crt);
668 // Unknown issuer (only that) are accepted if allowPublic is true
669 if (not ret
670 and !(allowPublic and ret.result == (GNUTLS_CERT_INVALID | GNUTLS_CERT_SIGNER_NOT_FOUND))) {
671 if (certStore_.logger())
672 certStore_.logger()->warn("%s", ret.toString().c_str());
673 return false;
674 }
675
676 return allowed;
677}
678
679void
680TrustStore::updateKnownCerts()
681{
682 auto i = std::begin(unknownCertStatus_);
683 while (i != std::end(unknownCertStatus_)) {
684 if (auto crt = certStore_.getCertificate(i->first)) {
685 certStatus_.emplace(i->first, std::make_pair(crt, i->second));
686 setStoreCertStatus(*crt, i->second.allowed);
687 i = unknownCertStatus_.erase(i);
688 } else
689 ++i;
690 }
691}
692
693void
694TrustStore::setStoreCertStatus(const crypto::Certificate& crt, bool status)
695{
696 if (status)
697 allowed_.add(crt);
698 else
699 allowed_.remove(crt, false);
700}
701
702void
703TrustStore::rebuildTrust()
704{
705 allowed_ = {};
706 for (const auto& c : certStatus_)
707 setStoreCertStatus(*c.second.first, c.second.second.allowed);
708}
709
710} // namespace tls
Sébastien Blin464bdff2023-07-19 08:02:53 -0400711} // namespace dhtnet