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