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