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