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