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