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