Line data Source code
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 :
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 678 : dht_->join();
57 792 : }
58 :
59 : void
60 357 : AccountManager::onSyncData(DeviceSync&& sync, bool checkDevice)
61 : {
62 357 : auto sync_date = clock::time_point(clock::duration(sync.date));
63 357 : 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 1428 : 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 756 : for (const auto& d : sync.devices) {
77 399 : findCertificate(d.first, [this, d](const std::shared_ptr<dht::crypto::Certificate>& crt) {
78 399 : if (not crt)
79 0 : return;
80 : // std::lock_guard lock(deviceListMutex_);
81 399 : foundAccountDevice(crt, d.second.name);
82 : });
83 : }
84 : // saveKnownDevices();
85 :
86 : // Sync contacts
87 357 : if (!sync.peers.empty()) {
88 128 : for (const auto& peer : sync.peers) {
89 64 : info_->contacts->updateContact(peer.first, peer.second);
90 : }
91 64 : info_->contacts->saveContacts();
92 : }
93 :
94 : // Sync trust requests
95 369 : for (const auto& tr : sync.trust_requests)
96 12 : info_->contacts
97 12 : ->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 673 : AccountManager::startSync(const OnNewDeviceCb& cb, const OnDeviceAnnouncedCb& dcb, bool publishPresence)
275 : {
276 : // Put device announcement
277 673 : if (info_->announce) {
278 673 : auto h = dht::InfoHash(info_->accountId);
279 673 : if (publishPresence) {
280 2692 : dht_->put(
281 : h,
282 673 : info_->announce,
283 1346 : [dcb = std::move(dcb), h, accountId = accountId_](bool ok) {
284 673 : if (ok)
285 2632 : 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 673 : for (const auto& crl : info_->identity.second->issuer->getRevocationLists())
295 673 : dht_->put(h, crl, dht::DoneCallback {}, {}, true);
296 673 : dht_->listen<DeviceAnnouncement>(h, [this, cb = std::move(cb)](DeviceAnnouncement&& dev) {
297 748 : if (dev.pk) {
298 748 : findCertificate(dev.pk->getLongId(), [this, cb](const std::shared_ptr<dht::crypto::Certificate>& crt) {
299 748 : foundAccountDevice(crt);
300 748 : if (cb)
301 748 : cb(crt);
302 748 : });
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 748 : return true;
311 : });
312 673 : 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 673 : syncDevices();
321 : } else {
322 0 : JAMI_ERROR("[Account {}] Unable to announce device: no announcement.", accountId_);
323 : }
324 :
325 673 : auto inboxKey = dht::InfoHash::get("inbox:" + info_->devicePk->getId().toString());
326 673 : dht_->listen<dht::TrustRequest>(inboxKey, [this](dht::TrustRequest&& v) {
327 109 : if (v.service != DHT_TYPE_NS)
328 0 : return true;
329 :
330 : // allowPublic always true for trust requests (only forbidden if banned)
331 218 : onPeerMessage(
332 109 : *v.owner,
333 : true,
334 454 : [this, v](const std::shared_ptr<dht::crypto::Certificate>&, dht::InfoHash peer_account) mutable {
335 432 : 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 108 : if (info_)
342 216 : if (info_->contacts->onTrustRequest(peer_account,
343 108 : v.owner,
344 : time(nullptr),
345 108 : v.confirm,
346 108 : v.conversationId,
347 108 : std::move(v.payload))) {
348 11 : if (v.confirm) // No need to send a confirmation as already accepted here
349 6 : return;
350 11 : auto conversationId = v.conversationId;
351 : // Check if there was an old active conversation.
352 11 : if (auto details = info_->contacts->getContactInfo(peer_account)) {
353 11 : if (!details->conversationId.empty()) {
354 11 : 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 6 : info_->contacts->acceptConversation(conversationId, v.owner->getLongId().toString());
360 6 : return;
361 : }
362 5 : conversationId = details->conversationId;
363 20 : JAMI_WARNING("Accept with old convId: {}", conversationId);
364 : }
365 11 : }
366 5 : sendTrustRequestConfirm(peer_account, conversationId);
367 11 : }
368 : });
369 109 : return true;
370 : });
371 673 : }
372 :
373 : const std::map<dht::PkId, KnownDevice>&
374 1209 : AccountManager::getKnownDevices() const
375 : {
376 1209 : return info_->contacts->getKnownDevices();
377 : }
378 :
379 : bool
380 1147 : AccountManager::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt,
381 : const std::string& name,
382 : const time_point& last_sync)
383 : {
384 1147 : 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 772 : AccountManager::getAccountDeviceName() const
396 : {
397 772 : if (info_)
398 1544 : return info_->contacts->getAccountDeviceName(DeviceId(info_->deviceId));
399 0 : return {};
400 : }
401 :
402 : bool
403 855 : AccountManager::foundPeerDevice(const std::string& accountId,
404 : const std::shared_ptr<dht::crypto::Certificate>& crt,
405 : dht::InfoHash& peer_id)
406 : {
407 855 : if (not crt)
408 0 : return false;
409 :
410 855 : auto top_issuer = crt;
411 2565 : while (top_issuer->issuer)
412 1710 : top_issuer = top_issuer->issuer;
413 :
414 : // Device certificate is unable to be self-signed
415 855 : 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 855 : dht::crypto::TrustList peer_trust;
423 855 : peer_trust.add(*top_issuer);
424 855 : 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 855 : 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 855 : peer_id = crt->issuer->getId();
436 3420 : 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 855 : return true;
442 855 : }
443 :
444 : void
445 109 : 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 109 : auto trustStatus = getCertificateStatus(peer_device.toString());
452 109 : if (trustStatus == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
453 0 : JAMI_WARNING("[Account {}] [Auth] Discarding message from banned device {}", accountId_, peer_device.toString());
454 0 : return;
455 : }
456 :
457 109 : findCertificate(peer_device.getId(),
458 109 : [this, cb = std::move(cb), allowPublic](const std::shared_ptr<dht::crypto::Certificate>& cert) {
459 109 : dht::InfoHash peer_account_id;
460 109 : if (onPeerCertificate(cert, allowPublic, peer_account_id)) {
461 108 : cb(cert, peer_account_id);
462 : }
463 109 : });
464 : }
465 :
466 : bool
467 855 : AccountManager::onPeerCertificate(const std::shared_ptr<dht::crypto::Certificate>& cert,
468 : bool allowPublic,
469 : dht::InfoHash& account_id)
470 : {
471 855 : dht::InfoHash peer_account_id;
472 855 : if (not foundPeerDevice(accountId_, cert, peer_account_id)) {
473 0 : JAMI_WARNING("[Account {}] [Auth] Discarding message from invalid peer certificate", accountId_);
474 0 : return false;
475 : }
476 :
477 855 : if (not isAllowed(*cert, allowPublic)) {
478 24 : JAMI_WARNING("[Account {}] [Auth] Discarding message from unauthorized peer {}.",
479 : accountId_,
480 : peer_account_id.toString());
481 6 : return false;
482 : }
483 :
484 849 : account_id = peer_account_id;
485 849 : return true;
486 : }
487 :
488 : bool
489 66 : AccountManager::addContact(const dht::InfoHash& uri, bool confirmed, const std::string& conversationId)
490 : {
491 66 : if (not info_) {
492 0 : JAMI_ERROR("addContact(): account not loaded");
493 0 : return false;
494 : }
495 264 : JAMI_WARNING("[Account {}] addContact {}", accountId_, confirmed);
496 66 : if (info_->contacts->addContact(uri, confirmed, conversationId)) {
497 66 : syncDevices();
498 66 : return true;
499 : }
500 0 : return false;
501 : }
502 :
503 : void
504 19 : AccountManager::removeContact(const std::string& uri, bool banned)
505 : {
506 19 : dht::InfoHash h(uri);
507 19 : if (not h) {
508 0 : JAMI_ERROR("[Account {}] removeContact: invalid contact URI", accountId_);
509 0 : return;
510 : }
511 19 : if (not info_) {
512 0 : JAMI_ERROR("[Account {}] removeContact: account not loaded", accountId_);
513 0 : return;
514 : }
515 19 : if (info_->contacts->removeContact(h, banned))
516 19 : syncDevices();
517 : }
518 :
519 : void
520 0 : AccountManager::removeContactConversation(const std::string& uri)
521 : {
522 0 : dht::InfoHash h(uri);
523 0 : if (not h) {
524 0 : JAMI_ERROR("[Account {}] removeContactConversation: invalid contact URI", accountId_);
525 0 : return;
526 : }
527 0 : if (not info_) {
528 0 : JAMI_ERROR("[Account {}] removeContactConversation: account not loaded", accountId_);
529 0 : return;
530 : }
531 0 : if (info_->contacts->removeContactConversation(h))
532 0 : syncDevices();
533 : }
534 :
535 : void
536 27 : AccountManager::updateContactConversation(const std::string& uri, const std::string& convId, bool added)
537 : {
538 27 : dht::InfoHash h(uri);
539 27 : if (not h) {
540 0 : JAMI_ERROR("[Account {}] updateContactConversation: invalid contact URI", accountId_);
541 0 : return;
542 : }
543 27 : if (not info_) {
544 0 : JAMI_ERROR("[Account {}] updateContactConversation: account not loaded", accountId_);
545 0 : return;
546 : }
547 27 : if (info_->contacts->updateConversation(h, convId, added)) {
548 : // Also decline trust request if there is one
549 11 : auto req = info_->contacts->getTrustRequest(h);
550 11 : auto convIt = req.find(libjami::Account::TrustRequest::CONVERSATIONID);
551 11 : if (convIt != req.end() && convIt->second == convId) {
552 0 : discardTrustRequest(uri);
553 : }
554 11 : syncDevices();
555 11 : }
556 : }
557 :
558 : std::map<dht::InfoHash, Contact>
559 681 : AccountManager::getContacts(bool includeRemoved) const
560 : {
561 681 : if (not info_) {
562 0 : JAMI_ERROR("[Account {}] getContacts(): account not loaded", accountId_);
563 0 : return {};
564 : }
565 681 : const auto& contacts = info_->contacts->getContacts();
566 681 : std::map<dht::InfoHash, Contact> ret;
567 690 : for (const auto& c : contacts) {
568 9 : if (!c.second.isActive() && !includeRemoved && !c.second.isBanned())
569 0 : continue;
570 9 : ret.emplace(c.first, c.second);
571 : }
572 681 : return ret;
573 681 : }
574 :
575 : /** Obtain details about one account contact in serializable form. */
576 : std::map<std::string, std::string>
577 8 : AccountManager::getContactDetails(const std::string& uri) const
578 : {
579 8 : if (!info_) {
580 0 : JAMI_ERROR("[Account {}] getContactDetails(): account not loaded", accountId_);
581 0 : return {};
582 : }
583 8 : dht::InfoHash h(uri);
584 8 : if (not h) {
585 0 : JAMI_ERROR("[Account {}] getContactDetails: invalid contact URI", accountId_);
586 0 : return {};
587 : }
588 8 : return info_->contacts->getContactDetails(h);
589 : }
590 :
591 : std::optional<Contact>
592 605 : AccountManager::getContactInfo(const std::string& uri) const
593 : {
594 605 : if (!info_) {
595 0 : JAMI_ERROR("[Account {}] getContactInfo(): account not loaded", accountId_);
596 0 : return {};
597 : }
598 605 : dht::InfoHash h(uri);
599 605 : if (not h) {
600 0 : JAMI_ERROR("[Account {}] getContactInfo: invalid contact URI", accountId_);
601 0 : return {};
602 : }
603 605 : return info_->contacts->getContactInfo(h);
604 : }
605 :
606 : bool
607 191 : AccountManager::findCertificate(const dht::InfoHash& h,
608 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
609 : {
610 382 : if (auto cert = certStore().getCertificate(h.toString())) {
611 60 : if (cb)
612 56 : cb(cert);
613 131 : } else if (dht_) {
614 131 : dht_->findCertificate(h, [cb = std::move(cb), this](const std::shared_ptr<dht::crypto::Certificate>& crt) {
615 131 : if (crt && info_) {
616 55 : certStore().pinCertificate(crt);
617 : }
618 131 : if (cb)
619 55 : cb(crt);
620 131 : });
621 0 : } else if (cb)
622 191 : cb(nullptr);
623 191 : return true;
624 : }
625 :
626 : bool
627 5927 : AccountManager::findCertificate(const dht::PkId& id,
628 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
629 : {
630 11854 : if (auto cert = certStore().getCertificate(id.toString())) {
631 5045 : if (cb)
632 5044 : cb(cert);
633 882 : } else if (dht_) {
634 882 : dht_->findCertificate(id, [cb = std::move(cb), this](const std::shared_ptr<dht::crypto::Certificate>& crt) {
635 882 : if (crt && info_) {
636 881 : certStore().pinCertificate(crt);
637 : }
638 882 : if (cb)
639 882 : cb(crt);
640 882 : });
641 0 : } else if (cb)
642 5926 : cb(nullptr);
643 5927 : return true;
644 : }
645 :
646 : bool
647 83 : AccountManager::setCertificateStatus(const std::string& cert_id, dhtnet::tls::TrustStore::PermissionStatus status)
648 : {
649 83 : return info_ and info_->contacts->setCertificateStatus(cert_id, status);
650 : }
651 :
652 : bool
653 0 : AccountManager::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
654 : dhtnet::tls::TrustStore::PermissionStatus status,
655 : bool local)
656 : {
657 0 : return info_ and info_->contacts->setCertificateStatus(cert, status, local);
658 : }
659 :
660 : std::vector<std::string>
661 0 : AccountManager::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
662 : {
663 0 : return info_ ? info_->contacts->getCertificatesByStatus(status) : std::vector<std::string> {};
664 : }
665 :
666 : dhtnet::tls::TrustStore::PermissionStatus
667 8471 : AccountManager::getCertificateStatus(const std::string& cert_id) const
668 : {
669 8466 : return info_ ? info_->contacts->getCertificateStatus(cert_id)
670 16944 : : dhtnet::tls::TrustStore::PermissionStatus::UNDEFINED;
671 : }
672 :
673 : bool
674 855 : AccountManager::isAllowed(const crypto::Certificate& crt, bool allowPublic)
675 : {
676 855 : return info_ and info_->contacts->isAllowed(crt, allowPublic);
677 : }
678 :
679 : std::vector<std::map<std::string, std::string>>
680 700 : AccountManager::getTrustRequests() const
681 : {
682 700 : if (not info_) {
683 0 : JAMI_ERROR("[Account {}] getTrustRequests(): account not loaded", accountId_);
684 0 : return {};
685 : }
686 700 : return info_->contacts->getTrustRequests();
687 : }
688 :
689 : bool
690 193 : AccountManager::acceptTrustRequest(const std::string& from, bool includeConversation)
691 : {
692 193 : dht::InfoHash f(from);
693 193 : if (info_) {
694 193 : auto req = info_->contacts->getTrustRequest(dht::InfoHash(from));
695 193 : if (info_->contacts->acceptTrustRequest(f)) {
696 42 : sendTrustRequestConfirm(f, includeConversation ? req[libjami::Account::TrustRequest::CONVERSATIONID] : "");
697 42 : syncDevices();
698 42 : return true;
699 : }
700 151 : return false;
701 193 : }
702 0 : return false;
703 : }
704 :
705 : bool
706 3 : AccountManager::discardTrustRequest(const std::string& from)
707 : {
708 3 : dht::InfoHash f(from);
709 3 : return info_ and info_->contacts->discardTrustRequest(f);
710 : }
711 :
712 : void
713 62 : AccountManager::sendTrustRequest(const std::string& to, const std::string& convId, const std::vector<uint8_t>& payload)
714 : {
715 248 : JAMI_WARNING("[Account {}] AccountManager::sendTrustRequest", accountId_);
716 62 : auto toH = dht::InfoHash(to);
717 62 : if (not toH) {
718 0 : JAMI_ERROR("[Account {}] Unable to send trust request to invalid hash: {}", accountId_, to);
719 0 : return;
720 : }
721 62 : if (not info_) {
722 0 : JAMI_ERROR("[Account {}] sendTrustRequest(): account not loaded", accountId_);
723 0 : return;
724 : }
725 62 : if (info_->contacts->addContact(toH, false, convId)) {
726 0 : syncDevices();
727 : }
728 62 : forEachDevice(toH, [this, toH, convId, payload](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
729 70 : auto to = toH.toString();
730 280 : JAMI_WARNING("[Account {}] [device {}] Sending trust request (size {:d}) to: {:s}",
731 : accountId_,
732 : dev->getLongId(),
733 : payload.size(),
734 : to);
735 280 : dht_->putEncrypted(dht::InfoHash::get("inbox:" + dev->getId().toString()),
736 : dev,
737 140 : dht::TrustRequest(DHT_TYPE_NS, convId, payload),
738 70 : [to, size = payload.size()](bool ok) {
739 70 : if (!ok)
740 84 : JAMI_ERROR("Tried to send request {:s} (size: "
741 : "{:d}), but put failed",
742 : to,
743 : size);
744 70 : });
745 70 : });
746 : }
747 :
748 : void
749 47 : AccountManager::sendTrustRequestConfirm(const dht::InfoHash& toH, const std::string& convId)
750 : {
751 188 : JAMI_WARNING("[Account {}] AccountManager::sendTrustRequestConfirm to {} (conversation {})",
752 : accountId_,
753 : toH,
754 : convId);
755 94 : dht::TrustRequest answer {DHT_TYPE_NS, convId};
756 47 : answer.confirm = true;
757 :
758 47 : if (!convId.empty() && info_)
759 47 : info_->contacts->acceptConversation(convId);
760 :
761 47 : forEachDevice(toH, [this, toH, answer](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
762 188 : JAMI_WARNING("[Account {}] sending trust request reply: {} / {}", accountId_, toH, dev->getLongId());
763 47 : dht_->putEncrypted(dht::InfoHash::get("inbox:" + dev->getId().toString()), dev, answer);
764 47 : });
765 47 : }
766 :
767 : void
768 3998 : AccountManager::forEachDevice(const dht::InfoHash& to,
769 : std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
770 : std::function<void(bool)>&& end)
771 : {
772 3998 : if (not dht_) {
773 0 : JAMI_ERROR("[Account {}] forEachDevice: no dht", accountId_);
774 0 : if (end)
775 0 : end(false);
776 0 : return;
777 : }
778 3998 : dht_->get<dht::crypto::RevocationList>(to, [to, this](dht::crypto::RevocationList&& crl) {
779 0 : certStore().pinRevocationList(to.toString(), std::move(crl));
780 0 : return true;
781 : });
782 :
783 : struct State
784 : {
785 : const dht::InfoHash to;
786 : const std::string accountId;
787 : // Note: state is initialized to 1, because we need to wait that the get is finished
788 : unsigned remaining {1};
789 : std::set<dht::PkId> treatedDevices {};
790 : std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)> onDevice;
791 : std::function<void(bool)> onEnd;
792 :
793 3998 : State(dht::InfoHash to, std::string accountId)
794 3998 : : to(std::move(to))
795 3998 : , accountId(std::move(accountId))
796 3998 : {}
797 :
798 8029 : void found(const std::shared_ptr<dht::crypto::PublicKey>& pk)
799 : {
800 8029 : remaining--;
801 8029 : if (pk && *pk) {
802 4031 : auto longId = pk->getLongId();
803 4031 : if (treatedDevices.emplace(longId).second) {
804 4026 : onDevice(pk);
805 : }
806 : }
807 8029 : ended();
808 8029 : }
809 :
810 8029 : void ended()
811 : {
812 8029 : if (remaining == 0 && onEnd) {
813 1516 : JAMI_LOG("[Account {}] Found {:d} device(s) for {}", accountId, treatedDevices.size(), to);
814 379 : onEnd(not treatedDevices.empty());
815 379 : onDevice = {};
816 379 : onEnd = {};
817 : }
818 8029 : }
819 : };
820 3998 : auto state = std::make_shared<State>(to, accountId_);
821 3998 : state->onDevice = std::move(op);
822 3998 : state->onEnd = std::move(end);
823 :
824 3997 : dht_->get<DeviceAnnouncement>(
825 : to,
826 8062 : [this, to, state](DeviceAnnouncement&& dev) {
827 4031 : if (dev.from != to)
828 0 : return true;
829 4031 : state->remaining++;
830 4031 : if (dev.pk) {
831 4031 : findCertificate(dev.pk->getLongId(), [state](const std::shared_ptr<dht::crypto::Certificate>& cert) {
832 4031 : state->found(cert ? cert->getSharedPublicKey() : std::shared_ptr<dht::crypto::PublicKey> {});
833 4031 : });
834 : } else {
835 0 : findCertificate(dev.dev, [state](const std::shared_ptr<dht::crypto::Certificate>& cert) {
836 0 : state->found(cert ? cert->getSharedPublicKey() : std::shared_ptr<dht::crypto::PublicKey> {});
837 0 : });
838 : }
839 4031 : return true;
840 : },
841 3998 : [state](bool /*ok*/) { state->found({}); });
842 3998 : }
843 :
844 : void
845 3 : AccountManager::lookupUri(const std::string& name, const std::string& defaultServer, LookupCallback cb)
846 : {
847 3 : nameDir_.get().lookupUri(name, defaultServer, std::move(cb));
848 3 : }
849 :
850 : void
851 777 : AccountManager::lookupAddress(const std::string& addr, LookupCallback cb)
852 : {
853 777 : nameDir_.get().lookupAddress(addr, cb);
854 777 : }
855 :
856 : dhtnet::tls::CertificateStore&
857 7061 : AccountManager::certStore() const
858 : {
859 7061 : return Manager::instance().certStore(info_->contacts->accountId());
860 : }
861 :
862 : } // namespace jami
|