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