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