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 797 : AccountManager::buildRequest(PrivateKey fDeviceKey)
41 : {
42 797 : return dht::ThreadPool::computation().get<std::unique_ptr<dht::crypto::CertificateRequest>>(
43 1594 : [fDeviceKey = std::move(fDeviceKey)] {
44 797 : auto request = std::make_unique<dht::crypto::CertificateRequest>();
45 1594 : request->setName("Jami device");
46 797 : const auto& deviceKey = fDeviceKey.get();
47 797 : request->setUID(deviceKey->getPublicKey().getId().toString());
48 797 : request->sign(*deviceKey);
49 797 : return request;
50 1594 : });
51 : }
52 :
53 2439 : AccountManager::~AccountManager()
54 : {
55 813 : if (dht_)
56 704 : dht_->join();
57 813 : }
58 :
59 : void
60 352 : AccountManager::onSyncData(DeviceSync&& sync, bool checkDevice)
61 : {
62 352 : auto sync_date = clock::time_point(clock::duration(sync.date));
63 351 : 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 1405 : 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 743 : 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 353 : if (!sync.peers.empty()) {
88 148 : for (const auto& peer : sync.peers) {
89 74 : info_->contacts->updateContact(peer.first, peer.second);
90 : }
91 74 : info_->contacts->saveContacts();
92 : }
93 :
94 : // Sync trust requests
95 358 : for (const auto& tr : sync.trust_requests)
96 5 : info_->contacts
97 5 : ->onTrustRequest(tr.first, tr.second.device, tr.second.received, false, tr.second.conversationId, {});
98 : }
99 :
100 : dht::crypto::Identity
101 813 : 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 813 : if (crt_path.empty() or key_path.empty())
106 793 : 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 20 : dht::crypto::Certificate dht_cert(fileutils::loadFile(crt_path, path_));
115 20 : 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.dev.toString() != deviceSha1) {
163 0 : JAMI_ERROR("[Auth] Device ID mismatch in announce (device: {}, in announce: {})", deviceSha1, da.dev);
164 0 : return {};
165 : }
166 16 : if (!da.pk) {
167 0 : JAMI_ERROR("[Auth] No public key found in announce (account: {}, device: {})", accountId, da.dev);
168 0 : return {};
169 : }
170 16 : if (da.pk->getLongId().to_view() != deviceSha256) {
171 0 : JAMI_ERROR("[Auth] Public key ID mismatch in announce (device: {}, in announce: {})",
172 : deviceSha256,
173 : da.pk->getLongId().to_view());
174 0 : return {};
175 : }
176 16 : } catch (const std::exception& e) {
177 0 : JAMI_ERROR("[Auth] unable to read announce: {}", e.what());
178 0 : return {};
179 0 : }
180 : // Device announcements are presence-only — use normal priority to avoid
181 : // waking peers with high-priority push notifications.
182 16 : announce_val->priority = 1;
183 16 : return announce_val;
184 16 : }
185 :
186 : const AccountInfo*
187 813 : AccountManager::useIdentity(const dht::crypto::Identity& identity,
188 : const std::string& receipt,
189 : const std::vector<uint8_t>& receiptSignature,
190 : const std::string& username,
191 : const OnChangeCallback& onChange)
192 : {
193 813 : if (receipt.empty() or receiptSignature.empty())
194 797 : return nullptr;
195 :
196 16 : if (not identity.first or not identity.second) {
197 0 : JAMI_ERROR("[Account {}] [Auth] no identity provided", accountId_);
198 0 : return nullptr;
199 : }
200 :
201 16 : auto accountCertificate = identity.second->issuer;
202 16 : if (not accountCertificate) {
203 0 : JAMI_ERROR("[Account {}] [Auth] device certificate must be issued by the account certificate", accountId_);
204 0 : return nullptr;
205 : }
206 :
207 : // match certificate chain
208 16 : auto contactList = std::make_unique<ContactList>(accountId_, accountCertificate, path_, onChange);
209 16 : auto result = contactList->isValidAccountDevice(*identity.second);
210 16 : if (not result) {
211 0 : JAMI_ERROR("[Account {}] [Auth] unable to use identity: device certificate chain is unable to be verified: {}",
212 : accountId_,
213 : result.toString());
214 0 : return nullptr;
215 : }
216 :
217 16 : auto pk = accountCertificate->getSharedPublicKey();
218 64 : JAMI_LOG("[Account {}] [Auth] checking device receipt for account:{} device:{}",
219 : accountId_,
220 : pk->getId().toString(),
221 : identity.second->getLongId().toString());
222 48 : if (!pk->checkSignature({receipt.begin(), receipt.end()}, receiptSignature)) {
223 0 : JAMI_ERROR("[Account {}] [Auth] device receipt signature check failed", accountId_);
224 0 : return nullptr;
225 : }
226 :
227 16 : Json::Value root;
228 16 : if (!json::parse(receipt, root) || !root.isMember("announce")) {
229 0 : JAMI_ERROR("[Account {}] [Auth] device receipt parsing error", accountId_);
230 0 : return nullptr;
231 : }
232 :
233 16 : auto dev_id = root["dev"].asString();
234 16 : if (dev_id != identity.second->getId().toString()) {
235 0 : JAMI_ERROR("[Account {}] [Auth] device ID mismatch between receipt and certificate", accountId_);
236 0 : return nullptr;
237 : }
238 16 : auto id = root["id"].asString();
239 16 : if (id != pk->getId().toString()) {
240 0 : JAMI_ERROR("[Account {}] [Auth] account ID mismatch between receipt and certificate", accountId_);
241 0 : return nullptr;
242 : }
243 :
244 16 : auto devicePk = identity.first->getSharedPublicKey();
245 16 : if (!devicePk) {
246 0 : JAMI_ERROR("[Account {}] [Auth] No device pk found", accountId_);
247 0 : return nullptr;
248 : }
249 :
250 32 : auto announce = parseAnnounce(root["announce"].asString(),
251 : id,
252 32 : devicePk->getId().toString(),
253 48 : devicePk->getLongId().toString());
254 16 : if (not announce) {
255 0 : return nullptr;
256 : }
257 :
258 16 : onChange_ = std::move(onChange);
259 :
260 16 : auto info = std::make_unique<AccountInfo>();
261 16 : info->identity = identity;
262 16 : info->contacts = std::move(contactList);
263 16 : info->contacts->load();
264 16 : info->accountId = id;
265 16 : info->devicePk = std::move(devicePk);
266 16 : info->deviceId = info->devicePk->getLongId().toString();
267 16 : info->announce = std::move(announce);
268 16 : info->ethAccount = root["eth"].asString();
269 16 : info->username = username;
270 16 : info_ = std::move(info);
271 :
272 64 : JAMI_LOG("[Account {}] [Auth] Device {} receipt checked successfully for user {}", accountId_, info_->deviceId, id);
273 16 : return info_.get();
274 16 : }
275 :
276 : void
277 0 : AccountManager::reloadContacts()
278 : {
279 0 : if (info_) {
280 0 : info_->contacts->load();
281 : }
282 0 : }
283 :
284 : void
285 697 : AccountManager::startSync(const OnNewDeviceCb& cb, const OnDeviceAnnouncedCb& dcb, bool publishPresence)
286 : {
287 : // Put device announcement
288 697 : if (info_->announce) {
289 697 : auto h = dht::InfoHash(info_->accountId);
290 697 : if (publishPresence) {
291 1392 : dht_->put(
292 : h,
293 696 : info_->announce,
294 1392 : [dcb = std::move(dcb), h, accountId = accountId_](bool ok) {
295 696 : if (ok)
296 2744 : JAMI_DEBUG("[Account {}] device announced at {}", accountId, h.toString());
297 : // We do not care about the status, it's a permanent put, if this fail,
298 : // this means the DHT is disconnected but the put will be retried when connected.
299 696 : if (dcb)
300 696 : dcb();
301 696 : },
302 : {},
303 : true);
304 : }
305 697 : for (const auto& crl : info_->identity.second->issuer->getRevocationLists()) {
306 0 : auto crlVal = std::make_shared<dht::Value>(*crl);
307 0 : crlVal->priority = 1; // CRLs are not urgent — use normal priority
308 0 : dht_->put(h, crlVal, dht::DoneCallback {}, {}, true);
309 0 : }
310 697 : dht_->listen<DeviceAnnouncement>(h, [this, cb = std::move(cb)](DeviceAnnouncement&& dev) {
311 768 : if (dev.pk) {
312 768 : findCertificate(dev.pk->getLongId(), [this, cb](const std::shared_ptr<dht::crypto::Certificate>& crt) {
313 768 : foundAccountDevice(crt);
314 768 : if (cb)
315 768 : cb(crt);
316 768 : });
317 : } else {
318 0 : findCertificate(dev.dev, [this, cb](const std::shared_ptr<dht::crypto::Certificate>& crt) {
319 0 : foundAccountDevice(crt);
320 0 : if (cb)
321 0 : cb(crt);
322 0 : });
323 : }
324 768 : return true;
325 : });
326 697 : dht_->listen<dht::crypto::RevocationList>(h, [this](dht::crypto::RevocationList&& crl) {
327 3 : if (crl.isSignedBy(*info_->identity.second->issuer)) {
328 12 : JAMI_DEBUG("[Account {}] Found CRL for account.", accountId_);
329 6 : certStore().pinRevocationList(info_->accountId,
330 6 : std::make_shared<dht::crypto::RevocationList>(std::move(crl)));
331 : }
332 3 : return true;
333 : });
334 697 : syncDevices();
335 : } else {
336 0 : JAMI_ERROR("[Account {}] Unable to announce device: no announcement.", accountId_);
337 : }
338 :
339 697 : auto inboxKey = dht::InfoHash::get("inbox:" + info_->devicePk->getId().toString());
340 697 : dht_->listen<dht::TrustRequest>(inboxKey, [this](dht::TrustRequest&& v) {
341 189 : if (v.service != DHT_TYPE_NS)
342 0 : return true;
343 :
344 : // allowPublic always true for trust requests (only forbidden if banned)
345 189 : onPeerMessage(*v.owner,
346 : true,
347 378 : [this, v](const std::shared_ptr<dht::crypto::Certificate>&, dht::InfoHash peer_account) mutable {
348 748 : JAMI_WARNING(
349 : "[Account {}] [device {}] Got trust request (confirm: {}) from: {}. ConversationId: {}",
350 : accountId_,
351 : v.owner->getLongId().toString(),
352 : v.confirm,
353 : peer_account.toString(),
354 : v.conversationId);
355 187 : if (info_)
356 374 : if (info_->contacts->onTrustRequest(peer_account,
357 187 : v.owner,
358 : nowMs(),
359 187 : v.confirm,
360 187 : v.conversationId,
361 187 : std::move(v.payload))) {
362 35 : if (v.confirm) // No need to send a confirmation as already accepted here
363 0 : return;
364 35 : sendTrustRequestConfirm(peer_account, v.conversationId);
365 : }
366 : });
367 189 : return true;
368 : });
369 697 : }
370 :
371 : const std::map<dht::PkId, KnownDevice>&
372 353 : AccountManager::getKnownDevices() const
373 : {
374 353 : return info_->contacts->getKnownDevices();
375 : }
376 :
377 : bool
378 1158 : AccountManager::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt,
379 : const std::string& name,
380 : const time_point& last_sync)
381 : {
382 1158 : return info_->contacts->foundAccountDevice(crt, name, last_sync);
383 : }
384 :
385 : void
386 20 : AccountManager::setAccountDeviceName(const std::string& name)
387 : {
388 20 : if (info_)
389 16 : info_->contacts->setAccountDeviceName(DeviceId(info_->deviceId), name);
390 20 : }
391 :
392 : std::string
393 795 : AccountManager::getAccountDeviceName() const
394 : {
395 795 : if (info_)
396 795 : return info_->contacts->getAccountDeviceName(DeviceId(info_->deviceId));
397 0 : return {};
398 : }
399 :
400 : bool
401 975 : AccountManager::foundPeerDevice(const std::string& accountId,
402 : const std::shared_ptr<dht::crypto::Certificate>& crt,
403 : dht::InfoHash& peer_id)
404 : {
405 975 : if (not crt)
406 0 : return false;
407 :
408 975 : auto top_issuer = crt;
409 2925 : while (top_issuer->issuer)
410 1950 : top_issuer = top_issuer->issuer;
411 :
412 : // Device certificate is unable to be self-signed
413 975 : 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 975 : dht::crypto::TrustList peer_trust;
421 975 : peer_trust.add(*top_issuer);
422 975 : 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 975 : 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 975 : peer_id = crt->issuer->getId();
434 3900 : JAMI_LOG("[Account {}] [device {}] Found device for peer: {} CA:{}",
435 : accountId,
436 : crt->getLongId().toString(),
437 : peer_id.toString(),
438 : top_issuer->getId().toString());
439 975 : return true;
440 975 : }
441 :
442 : void
443 189 : AccountManager::onPeerMessage(
444 : const dht::crypto::PublicKey& peer_device,
445 : bool allowPublic,
446 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>& crt, const dht::InfoHash& peer_account)>&& cb)
447 : {
448 : // quick check in case we already explicilty banned this device
449 189 : auto trustStatus = getCertificateStatus(peer_device.getLongId().toString());
450 189 : if (trustStatus == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
451 4 : JAMI_WARNING("[Account {}] [Auth] Discarding message from banned device {}",
452 : accountId_,
453 : peer_device.getLongId().to_view());
454 1 : return;
455 : }
456 :
457 188 : findCertificate(peer_device.getLongId(),
458 376 : [this, cb = std::move(cb), allowPublic](const std::shared_ptr<dht::crypto::Certificate>& cert) {
459 188 : dht::InfoHash peer_account_id;
460 188 : if (onPeerCertificate(cert, allowPublic, peer_account_id)) {
461 187 : cb(cert, peer_account_id);
462 : }
463 188 : });
464 : }
465 :
466 : bool
467 975 : AccountManager::onPeerCertificate(const std::shared_ptr<dht::crypto::Certificate>& cert,
468 : bool allowPublic,
469 : dht::InfoHash& account_id)
470 : {
471 975 : dht::InfoHash peer_account_id;
472 975 : 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 975 : 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 969 : account_id = peer_account_id;
485 969 : 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_, uri);
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_, uri);
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_, uri);
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 703 : AccountManager::getContacts(bool includeRemoved) const
560 : {
561 703 : if (not info_) {
562 0 : JAMI_ERROR("[Account {}] getContacts(): account not loaded", accountId_);
563 0 : return {};
564 : }
565 703 : const auto& contacts = info_->contacts->getContacts();
566 703 : std::map<dht::InfoHash, Contact> ret;
567 712 : 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 703 : return ret;
573 703 : }
574 :
575 : /** Obtain details about one account contact in serializable form. */
576 : std::map<std::string, std::string>
577 6 : AccountManager::getContactDetails(const std::string& uri) const
578 : {
579 6 : if (!info_) {
580 0 : JAMI_ERROR("[Account {}] getContactDetails(): account not loaded", accountId_);
581 0 : return {};
582 : }
583 6 : dht::InfoHash h(uri);
584 6 : if (not h) {
585 0 : JAMI_ERROR("[Account {}] getContactDetails: invalid contact URI: {}", accountId_, uri);
586 0 : return {};
587 : }
588 6 : return info_->contacts->getContactDetails(h);
589 : }
590 :
591 : std::optional<Contact>
592 1236 : AccountManager::getContactInfo(const std::string& uri) const
593 : {
594 1236 : if (!info_) {
595 0 : JAMI_ERROR("[Account {}] getContactInfo(): account not loaded", accountId_);
596 0 : return {};
597 : }
598 1236 : dht::InfoHash h(uri);
599 1235 : if (not h) {
600 0 : JAMI_ERROR("[Account {}] getContactInfo: invalid contact URI: {}", accountId_, uri);
601 0 : return {};
602 : }
603 1235 : return info_->contacts->getContactInfo(h);
604 : }
605 :
606 : bool
607 898 : AccountManager::findCertificate(const dht::InfoHash& h,
608 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
609 : {
610 898 : if (auto cert = certStore().getCertificate(h.toString())) {
611 5 : if (cb)
612 1 : cb(cert);
613 893 : } else if (cb)
614 898 : cb(nullptr);
615 898 : return true;
616 : }
617 :
618 : bool
619 5748 : AccountManager::findCertificate(const dht::PkId& id,
620 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
621 : {
622 5748 : if (auto cert = certStore().getCertificate(id.toString())) {
623 4728 : if (cb)
624 4728 : cb(cert);
625 1020 : } else if (dht_) {
626 1020 : dht_->findCertificate(id, [cb = std::move(cb), this](const std::shared_ptr<dht::crypto::Certificate>& crt) {
627 1020 : if (crt && info_) {
628 1019 : certStore().pinCertificate(crt);
629 : }
630 1020 : if (cb)
631 1020 : cb(crt);
632 1020 : });
633 0 : } else if (cb)
634 5748 : cb(nullptr);
635 5748 : return true;
636 : }
637 :
638 : bool
639 89 : AccountManager::setCertificateStatus(const std::string& cert_id, dhtnet::tls::TrustStore::PermissionStatus status)
640 : {
641 89 : return info_ and info_->contacts->setCertificateStatus(cert_id, status);
642 : }
643 :
644 : bool
645 811 : AccountManager::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
646 : dhtnet::tls::TrustStore::PermissionStatus status,
647 : bool local)
648 : {
649 811 : return info_ and info_->contacts->setCertificateStatus(cert, status, local);
650 : }
651 :
652 : std::vector<std::string>
653 0 : AccountManager::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
654 : {
655 0 : return info_ ? info_->contacts->getCertificatesByStatus(status) : std::vector<std::string> {};
656 : }
657 :
658 : dhtnet::tls::TrustStore::PermissionStatus
659 20790 : AccountManager::getCertificateStatus(const std::string& cert_id) const
660 : {
661 20790 : return info_ ? info_->contacts->getCertificateStatus(cert_id)
662 20802 : : dhtnet::tls::TrustStore::PermissionStatus::UNDEFINED;
663 : }
664 :
665 : bool
666 975 : AccountManager::isAllowed(const crypto::Certificate& crt, bool allowPublic)
667 : {
668 975 : return info_ and info_->contacts->isAllowed(crt, allowPublic);
669 : }
670 :
671 : std::vector<std::map<std::string, std::string>>
672 722 : AccountManager::getTrustRequests() const
673 : {
674 722 : if (not info_) {
675 0 : JAMI_ERROR("[Account {}] getTrustRequests(): account not loaded", accountId_);
676 0 : return {};
677 : }
678 722 : return info_->contacts->getTrustRequests();
679 : }
680 :
681 : bool
682 193 : AccountManager::acceptTrustRequest(const std::string& from, bool includeConversation)
683 : {
684 193 : dht::InfoHash f(from);
685 193 : if (info_) {
686 193 : auto req = info_->contacts->getTrustRequest(dht::InfoHash(from));
687 193 : if (info_->contacts->acceptTrustRequest(f)) {
688 84 : sendTrustRequestConfirm(f, includeConversation ? req[libjami::Account::TrustRequest::CONVERSATIONID] : "");
689 42 : syncDevices();
690 42 : return true;
691 : }
692 151 : return false;
693 193 : }
694 0 : return false;
695 : }
696 :
697 : bool
698 3 : AccountManager::discardTrustRequest(const std::string& from)
699 : {
700 3 : dht::InfoHash f(from);
701 6 : return info_ and info_->contacts->discardTrustRequest(f);
702 : }
703 :
704 : void
705 102 : AccountManager::sendTrustRequest(const std::string& to, const std::string& convId, const std::vector<uint8_t>& payload)
706 : {
707 408 : JAMI_WARNING("[Account {}] AccountManager::sendTrustRequest", accountId_);
708 102 : auto toH = dht::InfoHash(to);
709 102 : if (not toH) {
710 0 : JAMI_ERROR("[Account {}] Unable to send trust request to invalid hash: {}", accountId_, to);
711 0 : return;
712 : }
713 102 : if (not info_) {
714 0 : JAMI_ERROR("[Account {}] sendTrustRequest(): account not loaded", accountId_);
715 0 : return;
716 : }
717 102 : if (info_->contacts->addContact(toH, false, convId)) {
718 0 : syncDevices();
719 : }
720 102 : forEachDevice(toH, [this, toH, convId, payload](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
721 117 : auto to = toH.toString();
722 468 : JAMI_WARNING("[Account {}] [device {}] Sending trust request (size {:d}) to: {:s}",
723 : accountId_,
724 : dev->getLongId(),
725 : payload.size(),
726 : to);
727 234 : dht_->putEncrypted(dht::InfoHash::get(concat("inbox:"sv, dev->getId().to_view())),
728 : dev,
729 468 : dht::TrustRequest(DHT_TYPE_NS, convId, payload),
730 234 : [to, size = payload.size()](bool ok) {
731 117 : if (!ok)
732 108 : JAMI_ERROR("Tried to send request {:s} (size: "
733 : "{:d}), but put failed",
734 : to,
735 : size);
736 117 : });
737 117 : });
738 : }
739 :
740 : void
741 77 : AccountManager::sendTrustRequestConfirm(const dht::InfoHash& toH, const std::string& convId)
742 : {
743 308 : JAMI_WARNING("[Account {}] AccountManager::sendTrustRequestConfirm to {} (conversation {})",
744 : accountId_,
745 : toH,
746 : convId);
747 77 : dht::TrustRequest answer {DHT_TYPE_NS, convId};
748 77 : answer.confirm = true;
749 :
750 77 : if (!convId.empty() && info_)
751 231 : info_->contacts->acceptConversation(convId);
752 :
753 77 : forEachDevice(toH, [this, toH, answer](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
754 316 : JAMI_WARNING("[Account {}] sending trust request reply: {} / {}", accountId_, toH, dev->getLongId());
755 79 : dht_->putEncrypted(dht::InfoHash::get("inbox:" + dev->getId().toString()), dev, answer);
756 79 : });
757 77 : }
758 :
759 : void
760 3591 : AccountManager::forEachDevice(const dht::InfoHash& to,
761 : std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
762 : std::function<void(bool)>&& end)
763 : {
764 3591 : if (not dht_) {
765 0 : JAMI_ERROR("[Account {}] forEachDevice: no dht", accountId_);
766 0 : if (end)
767 0 : end(false);
768 0 : return;
769 : }
770 3591 : dht_->get<dht::crypto::RevocationList>(to, [to, this](dht::crypto::RevocationList&& crl) {
771 0 : certStore().pinRevocationList(to.toString(), std::move(crl));
772 0 : return true;
773 : });
774 :
775 : struct State
776 : {
777 : const dht::InfoHash to;
778 : const std::string accountId;
779 : // Note: state is initialized to 1, because we need to wait that the get is finished
780 : unsigned remaining {1};
781 : std::set<dht::PkId> treatedDevices {};
782 : std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)> onDevice;
783 : std::function<void(bool)> onEnd;
784 :
785 3590 : State(dht::InfoHash to, std::string accountId)
786 3590 : : to(std::move(to))
787 3590 : , accountId(std::move(accountId))
788 3591 : {}
789 :
790 7203 : void found(const std::shared_ptr<dht::crypto::PublicKey>& pk)
791 : {
792 7203 : remaining--;
793 7203 : if (pk && *pk) {
794 3612 : auto longId = pk->getLongId();
795 3612 : if (treatedDevices.emplace(longId).second) {
796 3603 : onDevice(pk);
797 : }
798 : }
799 7203 : ended();
800 7203 : }
801 :
802 7203 : void ended()
803 : {
804 7203 : if (remaining == 0 && onEnd) {
805 356 : JAMI_LOG("[Account {}] Found {:d} device(s) for {}", accountId, treatedDevices.size(), to);
806 89 : onEnd(not treatedDevices.empty());
807 89 : onDevice = {};
808 89 : onEnd = {};
809 : }
810 7203 : }
811 : };
812 3590 : auto state = std::make_shared<State>(to, accountId_);
813 3590 : state->onDevice = std::move(op);
814 3591 : state->onEnd = std::move(end);
815 :
816 10771 : dht_->get<DeviceAnnouncement>(
817 : to,
818 7181 : [this, to, state](DeviceAnnouncement&& dev) {
819 3612 : if (dev.from != to)
820 0 : return true;
821 3612 : state->remaining++;
822 3612 : if (dev.pk) {
823 3612 : findCertificate(dev.pk->getLongId(), [state](const std::shared_ptr<dht::crypto::Certificate>& cert) {
824 3612 : state->found(cert ? cert->getSharedPublicKey() : std::shared_ptr<dht::crypto::PublicKey> {});
825 3612 : });
826 : } else {
827 0 : findCertificate(dev.dev, [state](const std::shared_ptr<dht::crypto::Certificate>& cert) {
828 0 : state->found(cert ? cert->getSharedPublicKey() : std::shared_ptr<dht::crypto::PublicKey> {});
829 0 : });
830 : }
831 3612 : return true;
832 : },
833 10772 : [state](bool /*ok*/) { state->found({}); });
834 3591 : }
835 :
836 : void
837 3 : AccountManager::lookupUri(const std::string& name, const std::string& defaultServer, LookupCallback cb)
838 : {
839 3 : nameDir_.get().lookupUri(name, defaultServer, std::move(cb));
840 3 : }
841 :
842 : void
843 808 : AccountManager::lookupAddress(const std::string& addr, LookupCallback cb)
844 : {
845 808 : nameDir_.get().lookupAddress(addr, cb);
846 808 : }
847 :
848 : dhtnet::tls::CertificateStore&
849 7672 : AccountManager::certStore() const
850 : {
851 7672 : return Manager::instance().certStore(info_->contacts->accountId());
852 : }
853 :
854 : } // namespace jami
|