LCOV - code coverage report
Current view: top level - src/jamidht - account_manager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 367 472 77.8 %
Date: 2024-11-13 09:04:27 Functions: 72 89 80.9 %

          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

Generated by: LCOV version 1.14