Line data Source code
1 : /*
2 : * Copyright (C) 2004-2024 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (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
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #include "account_manager.h"
18 : #include "accountarchive.h"
19 : #include "jamiaccount.h"
20 : #include "base64.h"
21 : #include "jami/account_const.h"
22 : #include "account_schema.h"
23 : #include "archiver.h"
24 : #include "manager.h"
25 :
26 : #include "libdevcrypto/Common.h"
27 :
28 : #include <opendht/thread_pool.h>
29 : #include <opendht/crypto.h>
30 :
31 : #include <exception>
32 : #include <future>
33 : #include <fstream>
34 : #include <gnutls/ocsp.h>
35 :
36 : namespace jami {
37 :
38 : AccountManager::CertRequest
39 783 : AccountManager::buildRequest(PrivateKey fDeviceKey)
40 : {
41 783 : return dht::ThreadPool::computation().get<std::unique_ptr<dht::crypto::CertificateRequest>>(
42 783 : [fDeviceKey = std::move(fDeviceKey)] {
43 783 : auto request = std::make_unique<dht::crypto::CertificateRequest>();
44 783 : request->setName("Jami device");
45 783 : const auto& deviceKey = fDeviceKey.get();
46 783 : request->setUID(deviceKey->getPublicKey().getId().toString());
47 783 : request->sign(*deviceKey);
48 783 : return request;
49 783 : });
50 : }
51 :
52 799 : AccountManager::~AccountManager() {
53 799 : if (dht_)
54 688 : dht_->join();
55 799 : }
56 :
57 : void
58 392 : AccountManager::onSyncData(DeviceSync&& sync, bool checkDevice)
59 : {
60 392 : auto sync_date = clock::time_point(clock::duration(sync.date));
61 392 : if (checkDevice) {
62 : // If the DHT is used, we need to check the device here
63 17 : if (not info_->contacts->syncDevice(sync.owner->getLongId(), sync_date)) {
64 0 : return;
65 : }
66 : }
67 :
68 : // Sync known devices
69 1176 : JAMI_DEBUG("[Contacts] received device sync data ({:d} devices, {:d} contacts)",
70 : sync.devices_known.size() + sync.devices.size(),
71 : sync.peers.size());
72 392 : for (const auto& d : sync.devices_known) {
73 0 : findCertificate(d.first, [this, d](const std::shared_ptr<dht::crypto::Certificate>& crt) {
74 0 : if (not crt)
75 0 : return;
76 : // std::lock_guard lock(deviceListMutex_);
77 0 : foundAccountDevice(crt, d.second);
78 : });
79 : }
80 818 : for (const auto& d : sync.devices) {
81 426 : findCertificate(d.second.sha1,
82 852 : [this, d](const std::shared_ptr<dht::crypto::Certificate>& crt) {
83 426 : if (not crt || crt->getLongId() != d.first)
84 0 : return;
85 : // std::lock_guard lock(deviceListMutex_);
86 426 : foundAccountDevice(crt, d.second.name);
87 : });
88 : }
89 : // saveKnownDevices();
90 :
91 : // Sync contacts
92 459 : for (const auto& peer : sync.peers) {
93 67 : info_->contacts->updateContact(peer.first, peer.second);
94 : }
95 392 : info_->contacts->saveContacts();
96 :
97 : // Sync trust requests
98 403 : for (const auto& tr : sync.trust_requests)
99 22 : info_->contacts->onTrustRequest(tr.first,
100 11 : tr.second.device,
101 11 : tr.second.received,
102 : false,
103 11 : tr.second.conversationId,
104 : {});
105 : }
106 :
107 : dht::crypto::Identity
108 799 : AccountManager::loadIdentity(const std::string& accountId,
109 : const std::string& crt_path,
110 : const std::string& key_path,
111 : const std::string& key_pwd) const
112 : {
113 : // Return to avoid unnecessary log if certificate or key is missing. Example case: when
114 : // importing an account when the certificate has not been unpacked from the archive.
115 799 : if (crt_path.empty() or key_path.empty())
116 779 : return {};
117 :
118 60 : JAMI_DEBUG("Loading certificate from '{}' and key from '{}' at {}",
119 : crt_path,
120 : key_path,
121 : path_);
122 : try {
123 40 : dht::crypto::Certificate dht_cert(fileutils::loadFile(crt_path, path_));
124 40 : dht::crypto::PrivateKey dht_key(fileutils::loadFile(key_path, path_), key_pwd);
125 20 : auto crt_id = dht_cert.getLongId();
126 20 : if (!crt_id or crt_id != dht_key.getPublicKey().getLongId()) {
127 0 : JAMI_ERR("Device certificate not matching public key!");
128 0 : return {};
129 : }
130 20 : auto& issuer = dht_cert.issuer;
131 20 : if (not issuer) {
132 0 : JAMI_ERROR("Device certificate {:s} has no issuer", dht_cert.getId().toString());
133 0 : return {};
134 : }
135 : // load revocation lists for device authority (account certificate).
136 20 : Manager::instance().certStore(accountId).loadRevocations(*issuer);
137 :
138 40 : return {std::make_shared<dht::crypto::PrivateKey>(std::move(dht_key)),
139 20 : std::make_shared<dht::crypto::Certificate>(std::move(dht_cert))};
140 20 : } catch (const std::exception& e) {
141 0 : JAMI_ERR("Error loading identity: %s", e.what());
142 0 : }
143 0 : return {};
144 : }
145 :
146 : std::shared_ptr<dht::Value>
147 16 : AccountManager::parseAnnounce(const std::string& announceBase64,
148 : const std::string& accountId,
149 : const std::string& deviceSha1)
150 : {
151 16 : auto announce_val = std::make_shared<dht::Value>();
152 : try {
153 16 : auto announce = base64::decode(announceBase64);
154 16 : msgpack::object_handle announce_msg = msgpack::unpack((const char*) announce.data(),
155 32 : announce.size());
156 16 : announce_val->msgpack_unpack(announce_msg.get());
157 16 : if (not announce_val->checkSignature()) {
158 0 : JAMI_ERR("[Auth] announce signature check failed");
159 0 : return {};
160 : }
161 16 : DeviceAnnouncement da;
162 16 : da.unpackValue(*announce_val);
163 16 : if (da.from.toString() != accountId or da.dev.toString() != deviceSha1) {
164 0 : JAMI_ERR("[Auth] device ID mismatch in announce");
165 0 : return {};
166 : }
167 16 : } catch (const std::exception& e) {
168 0 : JAMI_ERR("[Auth] unable to read announce: %s", e.what());
169 0 : return {};
170 0 : }
171 16 : return announce_val;
172 16 : }
173 :
174 : const AccountInfo*
175 799 : AccountManager::useIdentity(const std::string& accountId,
176 : const dht::crypto::Identity& identity,
177 : const std::string& receipt,
178 : const std::vector<uint8_t>& receiptSignature,
179 : const std::string& username,
180 : const OnChangeCallback& onChange)
181 : {
182 799 : accountId_ = accountId;
183 799 : if (receipt.empty() or receiptSignature.empty())
184 783 : return nullptr;
185 :
186 16 : if (not identity.first or not identity.second) {
187 0 : JAMI_ERR("[Auth] no identity provided");
188 0 : return nullptr;
189 : }
190 :
191 16 : auto accountCertificate = identity.second->issuer;
192 16 : if (not accountCertificate) {
193 0 : JAMI_ERR("[Auth] device certificate must be issued by the account certificate");
194 0 : return nullptr;
195 : }
196 :
197 : // match certificate chain
198 16 : auto contactList = std::make_unique<ContactList>(accountId, accountCertificate, path_, onChange);
199 16 : auto result = contactList->isValidAccountDevice(*identity.second);
200 16 : if (not result) {
201 0 : JAMI_ERR("[Auth] unable to use identity: device certificate chain is unable to be verified: %s",
202 : result.toString().c_str());
203 0 : return nullptr;
204 : }
205 :
206 16 : auto pk = accountCertificate->getSharedPublicKey();
207 16 : JAMI_DBG("[Auth] checking device receipt for %s", pk->getId().toString().c_str());
208 16 : if (!pk->checkSignature({receipt.begin(), receipt.end()}, receiptSignature)) {
209 0 : JAMI_ERR("[Auth] device receipt signature check failed");
210 0 : return nullptr;
211 : }
212 :
213 16 : auto root = announceFromReceipt(receipt);
214 16 : if (!root.isMember("announce")) {
215 0 : JAMI_ERR() << this << " device receipt parsing error";
216 0 : return nullptr;
217 : }
218 :
219 16 : auto dev_id = root["dev"].asString();
220 16 : if (dev_id != identity.second->getId().toString()) {
221 0 : JAMI_ERR("[Auth] device ID mismatch between receipt and certificate");
222 0 : return nullptr;
223 : }
224 16 : auto id = root["id"].asString();
225 16 : if (id != pk->getId().toString()) {
226 0 : JAMI_ERR("[Auth] account ID mismatch between receipt and certificate");
227 0 : return nullptr;
228 : }
229 :
230 16 : auto devicePk = identity.first->getSharedPublicKey();
231 16 : if (!devicePk) {
232 0 : JAMI_ERR("[Auth] No device pk found");
233 0 : return nullptr;
234 : }
235 :
236 32 : auto announce = parseAnnounce(root["announce"].asString(), id, devicePk->getId().toString());
237 16 : if (not announce) {
238 0 : return nullptr;
239 : }
240 :
241 16 : onChange_ = std::move(onChange);
242 :
243 16 : auto info = std::make_unique<AccountInfo>();
244 16 : info->identity = identity;
245 16 : info->contacts = std::move(contactList);
246 16 : info->contacts->load();
247 16 : info->accountId = id;
248 16 : info->devicePk = std::move(devicePk);
249 16 : info->deviceId = info->devicePk->getLongId().toString();
250 16 : info->announce = std::move(announce);
251 16 : info->ethAccount = root["eth"].asString();
252 16 : info->username = username;
253 16 : info_ = std::move(info);
254 :
255 16 : JAMI_DBG("[Auth] Device %s receipt checked successfully for account %s",
256 : info_->deviceId.c_str(),
257 : id.c_str());
258 16 : return info_.get();
259 16 : }
260 :
261 : void
262 0 : AccountManager::reloadContacts()
263 : {
264 0 : if (info_) {
265 0 : info_->contacts->load();
266 : }
267 0 : }
268 :
269 : Json::Value
270 16 : AccountManager::announceFromReceipt(const std::string& receipt)
271 : {
272 16 : Json::Value root;
273 16 : Json::CharReaderBuilder rbuilder;
274 16 : auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
275 16 : if (!reader->parse(&receipt[0], &receipt[receipt.size()], &root, nullptr)) {
276 0 : JAMI_ERR() << this << " device receipt parsing error";
277 : }
278 32 : return root;
279 16 : }
280 :
281 : void
282 683 : AccountManager::startSync(const OnNewDeviceCb& cb, const OnDeviceAnnouncedCb& dcb, bool publishPresence)
283 : {
284 : // Put device announcement
285 683 : if (info_->announce) {
286 683 : auto h = dht::InfoHash(info_->accountId);
287 683 : if (publishPresence) {
288 2049 : dht_->put(
289 : h,
290 683 : info_->announce,
291 683 : [dcb = std::move(dcb), h](bool ok) {
292 683 : if (ok)
293 2016 : JAMI_DEBUG("device announced at {}", h.toString());
294 : // We do not care about the status, it's a permanent put, if this fail,
295 : // this means the DHT is disconnected but the put will be retried when connected.
296 683 : if (dcb)
297 683 : dcb();
298 683 : },
299 : {},
300 : true);
301 : }
302 683 : for (const auto& crl : info_->identity.second->issuer->getRevocationLists())
303 683 : dht_->put(h, crl, dht::DoneCallback {}, {}, true);
304 683 : dht_->listen<DeviceAnnouncement>(h, [this, cb = std::move(cb)](DeviceAnnouncement&& dev) {
305 760 : findCertificate(dev.dev,
306 760 : [this, cb](const std::shared_ptr<dht::crypto::Certificate>& crt) {
307 760 : foundAccountDevice(crt);
308 760 : if (cb)
309 760 : cb(crt);
310 760 : });
311 760 : return true;
312 : });
313 683 : dht_->listen<dht::crypto::RevocationList>(h, [this](dht::crypto::RevocationList&& crl) {
314 3 : if (crl.isSignedBy(*info_->identity.second->issuer)) {
315 9 : JAMI_DEBUG("found CRL for account.");
316 3 : certStore()
317 3 : .pinRevocationList(info_->accountId,
318 6 : std::make_shared<dht::crypto::RevocationList>(
319 3 : std::move(crl)));
320 : }
321 3 : return true;
322 : });
323 683 : syncDevices();
324 : } else {
325 0 : JAMI_WARNING("Unable to announce device: no announcement...");
326 : }
327 :
328 683 : auto inboxKey = dht::InfoHash::get("inbox:" + info_->devicePk->getId().toString());
329 683 : dht_->listen<dht::TrustRequest>(inboxKey, [this](dht::TrustRequest&& v) {
330 117 : if (v.service != DHT_TYPE_NS)
331 0 : return true;
332 :
333 : // allowPublic always true for trust requests (only forbidden if banned)
334 234 : onPeerMessage(
335 117 : *v.owner,
336 : true,
337 116 : [this, v](const std::shared_ptr<dht::crypto::Certificate>&,
338 254 : dht::InfoHash peer_account) mutable {
339 348 : JAMI_WARNING("Got trust request (confirm: {}) from: {} / {}. ConversationId: {}",
340 : v.confirm,
341 : peer_account.toString(),
342 : v.from.toString(),
343 : v.conversationId);
344 116 : if (info_)
345 232 : if (info_->contacts->onTrustRequest(peer_account,
346 116 : v.owner,
347 : time(nullptr),
348 116 : v.confirm,
349 116 : v.conversationId,
350 116 : std::move(v.payload))) {
351 11 : if (v.confirm) // No need to send a confirmation as already accepted here
352 4 : return;
353 11 : auto conversationId = v.conversationId;
354 : // Check if there was an old active conversation.
355 11 : auto details = info_->contacts->getContactDetails(peer_account);
356 11 : auto oldConvIt = details.find(libjami::Account::TrustRequest::CONVERSATIONID);
357 11 : if (oldConvIt != details.end() && oldConvIt->second != "") {
358 11 : if (conversationId == oldConvIt->second) {
359 : // Here, it's possible that we already have accepted the conversation
360 : // but contact were offline and sync failed.
361 : // So, retrigger the callback so upper layer will clone conversation if needed
362 : // instead of getting stuck in sync.
363 4 : info_->contacts->acceptConversation(conversationId, v.owner->getLongId().toString());
364 4 : return;
365 : }
366 7 : conversationId = oldConvIt->second;
367 21 : JAMI_WARNING("Accept with old convId: {}", conversationId);
368 : }
369 7 : sendTrustRequestConfirm(peer_account, conversationId);
370 15 : }
371 : });
372 117 : return true;
373 : });
374 683 : }
375 :
376 : const std::map<dht::PkId, KnownDevice>&
377 2048 : AccountManager::getKnownDevices() const
378 : {
379 2048 : return info_->contacts->getKnownDevices();
380 : }
381 :
382 : bool
383 1203 : AccountManager::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt,
384 : const std::string& name,
385 : const time_point& last_sync)
386 : {
387 1203 : return info_->contacts->foundAccountDevice(crt, name, last_sync);
388 : }
389 :
390 : void
391 20 : AccountManager::setAccountDeviceName(const std::string& name)
392 : {
393 20 : if (info_)
394 16 : info_->contacts->setAccountDeviceName(DeviceId(info_->deviceId), name);
395 20 : }
396 :
397 : std::string
398 783 : AccountManager::getAccountDeviceName() const
399 : {
400 783 : if (info_)
401 1566 : return info_->contacts->getAccountDeviceName(DeviceId(info_->deviceId));
402 0 : return {};
403 : }
404 :
405 : bool
406 878 : AccountManager::foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt,
407 : dht::InfoHash& account_id)
408 : {
409 878 : if (not crt)
410 0 : return false;
411 :
412 878 : auto top_issuer = crt;
413 2634 : while (top_issuer->issuer)
414 1756 : top_issuer = top_issuer->issuer;
415 :
416 : // Device certificate is unable to be self-signed
417 878 : if (top_issuer == crt) {
418 0 : JAMI_WARN("Found invalid peer device: %s", crt->getLongId().toString().c_str());
419 0 : return false;
420 : }
421 :
422 : // Check peer certificate chain
423 : // Trust store with top issuer as the only CA
424 878 : dht::crypto::TrustList peer_trust;
425 878 : peer_trust.add(*top_issuer);
426 878 : if (not peer_trust.verify(*crt)) {
427 0 : JAMI_WARN("Found invalid peer device: %s", crt->getLongId().toString().c_str());
428 0 : return false;
429 : }
430 :
431 : // Check cached OCSP response
432 878 : if (crt->ocspResponse and crt->ocspResponse->getCertificateStatus() != GNUTLS_OCSP_CERT_GOOD) {
433 0 : JAMI_ERR("Certificate %s is disabled by cached OCSP response", crt->getLongId().to_c_str());
434 0 : return false;
435 : }
436 :
437 878 : account_id = crt->issuer->getId();
438 878 : JAMI_WARN("Found peer device: %s account:%s CA:%s",
439 : crt->getLongId().toString().c_str(),
440 : account_id.toString().c_str(),
441 : top_issuer->getId().toString().c_str());
442 878 : return true;
443 878 : }
444 :
445 : void
446 117 : AccountManager::onPeerMessage(const dht::crypto::PublicKey& peer_device,
447 : bool allowPublic,
448 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>& crt,
449 : const dht::InfoHash& peer_account)>&& cb)
450 : {
451 : // quick check in case we already explicilty banned this device
452 117 : auto trustStatus = getCertificateStatus(peer_device.toString());
453 117 : if (trustStatus == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
454 0 : JAMI_WARN("[Auth] Discarding message from banned device %s", peer_device.toString().c_str());
455 0 : return;
456 : }
457 :
458 117 : findCertificate(peer_device.getId(),
459 117 : [this, cb = std::move(cb), allowPublic](
460 117 : const std::shared_ptr<dht::crypto::Certificate>& cert) {
461 117 : dht::InfoHash peer_account_id;
462 117 : if (onPeerCertificate(cert, allowPublic, peer_account_id)) {
463 116 : cb(cert, peer_account_id);
464 : }
465 117 : });
466 : }
467 :
468 : bool
469 878 : AccountManager::onPeerCertificate(const std::shared_ptr<dht::crypto::Certificate>& cert,
470 : bool allowPublic,
471 : dht::InfoHash& account_id)
472 : {
473 878 : dht::InfoHash peer_account_id;
474 878 : if (not foundPeerDevice(cert, peer_account_id)) {
475 0 : JAMI_WARN("[Auth] Discarding message from invalid peer certificate");
476 0 : return false;
477 : }
478 :
479 878 : if (not isAllowed(*cert, allowPublic)) {
480 7 : JAMI_WARN("[Auth] Discarding message from unauthorized peer %s.",
481 : peer_account_id.toString().c_str());
482 7 : return false;
483 : }
484 :
485 871 : account_id = peer_account_id;
486 871 : return true;
487 : }
488 :
489 : bool
490 66 : AccountManager::addContact(const dht::InfoHash& uri, bool confirmed, const std::string& conversationId)
491 : {
492 198 : JAMI_WARNING("AccountManager::addContact {}", confirmed);
493 66 : if (not info_) {
494 0 : JAMI_ERROR("addContact(): account not loaded");
495 0 : return false;
496 : }
497 66 : if (info_->contacts->addContact(uri, confirmed, conversationId)) {
498 66 : syncDevices();
499 66 : return true;
500 : }
501 0 : return false;
502 : }
503 :
504 : void
505 20 : AccountManager::removeContact(const std::string& uri, bool banned)
506 : {
507 20 : dht::InfoHash h(uri);
508 20 : if (not h) {
509 0 : JAMI_ERROR("removeContact: invalid contact URI");
510 0 : return;
511 : }
512 20 : if (not info_) {
513 0 : JAMI_ERROR("addContact(): account not loaded");
514 0 : return;
515 : }
516 20 : if (info_->contacts->removeContact(h, banned))
517 20 : syncDevices();
518 : }
519 :
520 : void
521 0 : AccountManager::removeContactConversation(const std::string& uri)
522 : {
523 0 : dht::InfoHash h(uri);
524 0 : if (not h) {
525 0 : JAMI_ERR("removeContact: invalid contact URI");
526 0 : return;
527 : }
528 0 : if (not info_) {
529 0 : JAMI_ERR("addContact(): account not loaded");
530 0 : return;
531 : }
532 0 : if (info_->contacts->removeContactConversation(h))
533 0 : syncDevices();
534 : }
535 :
536 : void
537 11 : AccountManager::updateContactConversation(const std::string& uri, const std::string& convId)
538 : {
539 11 : dht::InfoHash h(uri);
540 11 : if (not h) {
541 0 : JAMI_ERR("removeContact: invalid contact URI");
542 0 : return;
543 : }
544 11 : if (not info_) {
545 0 : JAMI_ERR("addContact(): account not loaded");
546 0 : return;
547 : }
548 11 : info_->contacts->updateConversation(h, convId);
549 : // Also decline trust request if there is one
550 11 : auto req = info_->contacts->getTrustRequest(h);
551 22 : if (req.find(libjami::Account::TrustRequest::CONVERSATIONID) != req.end()
552 22 : && req.at(libjami::Account::TrustRequest::CONVERSATIONID) == convId) {
553 0 : discardTrustRequest(uri);
554 : }
555 11 : syncDevices();
556 11 : }
557 :
558 : std::vector<std::map<std::string, std::string>>
559 690 : AccountManager::getContacts(bool includeRemoved) const
560 : {
561 690 : if (not info_) {
562 0 : JAMI_ERR("getContacts(): account not loaded");
563 0 : return {};
564 : }
565 690 : const auto& contacts = info_->contacts->getContacts();
566 690 : std::vector<std::map<std::string, std::string>> ret;
567 690 : ret.reserve(contacts.size());
568 :
569 698 : for (const auto& c : contacts) {
570 8 : if (!c.second.isActive() && !includeRemoved && !c.second.isBanned())
571 0 : continue;
572 8 : auto details = c.second.toMap();
573 8 : if (not details.empty()) {
574 8 : details["id"] = c.first.toString();
575 8 : ret.emplace_back(std::move(details));
576 : }
577 8 : }
578 690 : return ret;
579 690 : }
580 :
581 : /** Obtain details about one account contact in serializable form. */
582 : std::map<std::string, std::string>
583 855 : AccountManager::getContactDetails(const std::string& uri) const
584 : {
585 855 : if (!info_) {
586 0 : JAMI_ERR("getContactDetails(): account not loaded");
587 0 : return {};
588 : }
589 855 : dht::InfoHash h(uri);
590 855 : if (not h) {
591 0 : JAMI_ERR("getContactDetails: invalid contact URI");
592 0 : return {};
593 : }
594 855 : return info_->contacts->getContactDetails(h);
595 : }
596 :
597 : bool
598 17807 : AccountManager::findCertificate(
599 : const dht::InfoHash& h,
600 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
601 : {
602 35618 : if (auto cert = certStore().getCertificate(h.toString())) {
603 16617 : if (cb)
604 16613 : cb(cert);
605 1194 : } else if (dht_) {
606 2388 : dht_->findCertificate(h,
607 1194 : [cb = std::move(cb), this](
608 2222 : const std::shared_ptr<dht::crypto::Certificate>& crt) {
609 1194 : if (crt && info_) {
610 1111 : certStore().pinCertificate(crt);
611 : }
612 1194 : if (cb)
613 1112 : cb(crt);
614 1194 : });
615 17808 : }
616 17808 : return true;
617 : }
618 :
619 : bool
620 764 : AccountManager::findCertificate(
621 : const dht::PkId& id, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
622 : {
623 1528 : if (auto cert = certStore().getCertificate(id.toString())) {
624 763 : if (cb)
625 763 : cb(cert);
626 4 : } else if (auto cert = certStore().getCertificateLegacy(fileutils::get_data_dir().string(),
627 3 : id.toString())) {
628 0 : if (cb)
629 0 : cb(cert);
630 1 : } else if (cb)
631 765 : cb(nullptr);
632 764 : return true;
633 : }
634 :
635 : bool
636 89 : AccountManager::setCertificateStatus(const std::string& cert_id,
637 : dhtnet::tls::TrustStore::PermissionStatus status)
638 : {
639 89 : return info_ and info_->contacts->setCertificateStatus(cert_id, status);
640 : }
641 :
642 : bool
643 0 : AccountManager::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
644 : dhtnet::tls::TrustStore::PermissionStatus status,
645 : bool local)
646 : {
647 0 : return info_ and info_->contacts->setCertificateStatus(cert, status, local);
648 : }
649 :
650 : std::vector<std::string>
651 0 : AccountManager::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
652 : {
653 0 : return info_ ? info_->contacts->getCertificatesByStatus(status) : std::vector<std::string> {};
654 : }
655 :
656 : dhtnet::tls::TrustStore::PermissionStatus
657 7046 : AccountManager::getCertificateStatus(const std::string& cert_id) const
658 : {
659 7046 : return info_ ? info_->contacts->getCertificateStatus(cert_id)
660 14094 : : dhtnet::tls::TrustStore::PermissionStatus::UNDEFINED;
661 : }
662 :
663 : bool
664 878 : AccountManager::isAllowed(const crypto::Certificate& crt, bool allowPublic)
665 : {
666 878 : return info_ and info_->contacts->isAllowed(crt, allowPublic);
667 : }
668 :
669 : std::vector<std::map<std::string, std::string>>
670 710 : AccountManager::getTrustRequests() const
671 : {
672 710 : if (not info_) {
673 0 : JAMI_ERR("getTrustRequests(): account not loaded");
674 0 : return {};
675 : }
676 710 : return info_->contacts->getTrustRequests();
677 : }
678 :
679 : bool
680 197 : AccountManager::acceptTrustRequest(const std::string& from, bool includeConversation)
681 : {
682 197 : dht::InfoHash f(from);
683 197 : if (info_) {
684 197 : auto req = info_->contacts->getTrustRequest(dht::InfoHash(from));
685 197 : if (info_->contacts->acceptTrustRequest(f)) {
686 43 : sendTrustRequestConfirm(f,
687 : includeConversation
688 86 : ? req[libjami::Account::TrustRequest::CONVERSATIONID]
689 : : "");
690 43 : syncDevices();
691 43 : return true;
692 : }
693 154 : return false;
694 197 : }
695 0 : return false;
696 : }
697 :
698 : bool
699 3 : AccountManager::discardTrustRequest(const std::string& from)
700 : {
701 3 : dht::InfoHash f(from);
702 3 : return info_ and info_->contacts->discardTrustRequest(f);
703 : }
704 :
705 : void
706 62 : AccountManager::sendTrustRequest(const std::string& to,
707 : const std::string& convId,
708 : const std::vector<uint8_t>& payload)
709 : {
710 62 : JAMI_WARN("AccountManager::sendTrustRequest");
711 62 : auto toH = dht::InfoHash(to);
712 62 : if (not toH) {
713 0 : JAMI_ERR("Unable to send trust request to invalid hash: %s", to.c_str());
714 0 : return;
715 : }
716 62 : if (not info_) {
717 0 : JAMI_ERR("sendTrustRequest(): account not loaded");
718 0 : return;
719 : }
720 62 : if (info_->contacts->addContact(toH, false, convId)) {
721 1 : syncDevices();
722 : }
723 62 : forEachDevice(toH,
724 140 : [this, toH, convId, payload](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
725 70 : auto to = toH.toString();
726 210 : JAMI_WARNING("sending trust request to: {:s} / {:s}",
727 : to,
728 : dev->getLongId().toString());
729 280 : dht_->putEncrypted(dht::InfoHash::get("inbox:" + dev->getId().toString()),
730 : dev,
731 140 : dht::TrustRequest(DHT_TYPE_NS, convId, payload),
732 70 : [to, size = payload.size()](bool ok) {
733 70 : if (!ok)
734 66 : JAMI_ERROR("Tried to send request {:s} (size: "
735 : "{:d}), but put failed",
736 : to,
737 : size);
738 70 : });
739 70 : });
740 : }
741 :
742 : void
743 50 : AccountManager::sendTrustRequestConfirm(const dht::InfoHash& toH, const std::string& convId)
744 : {
745 50 : JAMI_WARN("AccountManager::sendTrustRequestConfirm");
746 100 : dht::TrustRequest answer {DHT_TYPE_NS, ""};
747 50 : answer.confirm = true;
748 50 : answer.conversationId = convId;
749 :
750 50 : if (!convId.empty() && info_)
751 50 : info_->contacts->acceptConversation(convId);
752 :
753 50 : forEachDevice(toH, [this, toH, answer](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
754 52 : JAMI_WARN("sending trust request reply: %s / %s",
755 : toH.toString().c_str(),
756 : dev->getLongId().toString().c_str());
757 52 : dht_->putEncrypted(dht::InfoHash::get("inbox:" + dev->getId().toString()), dev, answer);
758 52 : });
759 50 : }
760 :
761 : void
762 16319 : AccountManager::forEachDevice(
763 : const dht::InfoHash& to,
764 : std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
765 : std::function<void(bool)>&& end)
766 : {
767 16319 : if (not dht_) {
768 0 : JAMI_ERR("forEachDevice: no dht");
769 0 : if (end)
770 0 : end(false);
771 0 : return;
772 : }
773 16319 : dht_->get<dht::crypto::RevocationList>(to, [to, this](dht::crypto::RevocationList&& crl) {
774 0 : certStore().pinRevocationList(to.toString(), std::move(crl));
775 0 : return true;
776 : });
777 :
778 : struct State
779 : {
780 : // Note: state is initialized to 1, because we need to wait that the get is finished
781 : unsigned remaining {1};
782 : std::set<dht::PkId> treatedDevices {};
783 : std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)> onDevice;
784 : std::function<void(bool)> onEnd;
785 :
786 32722 : void found(std::shared_ptr<dht::crypto::PublicKey> pk)
787 : {
788 32722 : remaining--;
789 32722 : if (pk && *pk) {
790 16402 : auto longId = pk->getLongId();
791 16403 : if (treatedDevices.emplace(longId).second) {
792 16399 : onDevice(pk);
793 : }
794 : }
795 32723 : ended();
796 32722 : }
797 :
798 32723 : void ended()
799 : {
800 32723 : if (remaining == 0 && onEnd) {
801 1131 : JAMI_DEBUG("Found {:d} devices", treatedDevices.size());
802 377 : onEnd(not treatedDevices.empty());
803 377 : onDevice = {};
804 377 : onEnd = {};
805 : }
806 32723 : }
807 : };
808 16319 : auto state = std::make_shared<State>();
809 16319 : state->onDevice = std::move(op);
810 16318 : state->onEnd = std::move(end);
811 :
812 16319 : dht_->get<DeviceAnnouncement>(
813 : to,
814 32804 : [this, to, state](DeviceAnnouncement&& dev) {
815 16402 : if (dev.from != to)
816 0 : return true;
817 16404 : state->remaining++;
818 16402 : findCertificate(dev.dev, [state](const std::shared_ptr<dht::crypto::Certificate>& cert) {
819 32808 : state->found(cert ? cert->getSharedPublicKey()
820 16404 : : std::shared_ptr<dht::crypto::PublicKey> {});
821 16403 : });
822 16402 : return true;
823 : },
824 16319 : [state](bool /*ok*/) { state->found({}); });
825 16319 : }
826 :
827 : void
828 4 : AccountManager::lookupUri(const std::string& name,
829 : const std::string& defaultServer,
830 : LookupCallback cb)
831 : {
832 4 : nameDir_.get().lookupUri(name, defaultServer, std::move(cb));
833 4 : }
834 :
835 : void
836 790 : AccountManager::lookupAddress(const std::string& addr, LookupCallback cb)
837 : {
838 790 : nameDir_.get().lookupAddress(addr, cb);
839 790 : }
840 :
841 : dhtnet::tls::CertificateStore&
842 19691 : AccountManager::certStore() const
843 : {
844 19691 : return Manager::instance().certStore(info_->contacts->accountId());
845 : }
846 :
847 : } // namespace jami
|