LCOV - code coverage report
Current view: top level - src/jamidht - archive_account_manager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 408 515 79.2 %
Date: 2024-12-21 08:56:24 Functions: 40 43 93.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 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 "archive_account_manager.h"
      18             : #include "accountarchive.h"
      19             : #include "fileutils.h"
      20             : #include "libdevcrypto/Common.h"
      21             : #include "archiver.h"
      22             : #include "base64.h"
      23             : #include "jami/account_const.h"
      24             : #include "account_schema.h"
      25             : #include "jamidht/conversation_module.h"
      26             : #include "manager.h"
      27             : 
      28             : #include <opendht/dhtrunner.h>
      29             : #include <opendht/thread_pool.h>
      30             : 
      31             : #include <memory>
      32             : #include <fstream>
      33             : 
      34             : namespace jami {
      35             : 
      36             : const constexpr auto EXPORT_KEY_RENEWAL_TIME = std::chrono::minutes(20);
      37             : 
      38             : void
      39         783 : ArchiveAccountManager::initAuthentication(const std::string& accountId,
      40             :                                           PrivateKey key,
      41             :                                           std::string deviceName,
      42             :                                           std::unique_ptr<AccountCredentials> credentials,
      43             :                                           AuthSuccessCallback onSuccess,
      44             :                                           AuthFailureCallback onFailure,
      45             :                                           const OnChangeCallback& onChange)
      46             : {
      47         783 :     auto ctx = std::make_shared<AuthContext>();
      48         783 :     ctx->accountId = accountId;
      49         783 :     ctx->key = key;
      50         783 :     ctx->request = buildRequest(key);
      51         783 :     ctx->deviceName = std::move(deviceName);
      52         783 :     ctx->credentials = dynamic_unique_cast<ArchiveAccountCredentials>(std::move(credentials));
      53         783 :     ctx->onSuccess = std::move(onSuccess);
      54         783 :     ctx->onFailure = std::move(onFailure);
      55             : 
      56         783 :     if (not ctx->credentials) {
      57           0 :         ctx->onFailure(AuthError::INVALID_ARGUMENTS, "invalid credentials");
      58           0 :         return;
      59             :     }
      60             : 
      61         783 :     onChange_ = std::move(onChange);
      62             : 
      63         783 :     if (ctx->credentials->scheme == "dht") {
      64           1 :         loadFromDHT(ctx);
      65           1 :         return;
      66             :     }
      67             : 
      68         782 :     dht::ThreadPool::computation().run([ctx = std::move(ctx), w = weak_from_this()] {
      69         782 :         auto this_ = std::static_pointer_cast<ArchiveAccountManager>(w.lock());
      70         782 :         if (not this_) return;
      71             :         try {
      72         782 :             if (ctx->credentials->scheme == "file") {
      73             :                 // Import from external archive
      74          39 :                 this_->loadFromFile(*ctx);
      75             :             } else {
      76             :                 // Create/migrate local account
      77         743 :                 bool hasArchive = not ctx->credentials->uri.empty()
      78        1486 :                                     and std::filesystem::is_regular_file(ctx->credentials->uri);
      79         743 :                 if (hasArchive) {
      80             :                     // Create/migrate from local archive
      81           4 :                     if (ctx->credentials->updateIdentity.first
      82           8 :                         and ctx->credentials->updateIdentity.second
      83           8 :                         and needsMigration(ctx->credentials->updateIdentity)) {
      84           2 :                         this_->migrateAccount(*ctx);
      85             :                     } else {
      86           2 :                         this_->loadFromFile(*ctx);
      87             :                     }
      88         739 :                 } else if (ctx->credentials->updateIdentity.first
      89         739 :                             and ctx->credentials->updateIdentity.second) {
      90           0 :                     auto future_keypair = dht::ThreadPool::computation().get<dev::KeyPair>(
      91           0 :                         &dev::KeyPair::create);
      92           0 :                     AccountArchive a;
      93           0 :                     JAMI_WARN("[Auth] Converting certificate from old account %s",
      94             :                                 ctx->credentials->updateIdentity.first->getPublicKey()
      95             :                                     .getId()
      96             :                                     .toString()
      97             :                                     .c_str());
      98           0 :                     a.id = std::move(ctx->credentials->updateIdentity);
      99             :                     try {
     100           0 :                         a.ca_key = std::make_shared<dht::crypto::PrivateKey>(
     101           0 :                             fileutils::loadFile("ca.key", this_->path_));
     102           0 :                     } catch (...) {
     103           0 :                     }
     104           0 :                     this_->updateCertificates(a, ctx->credentials->updateIdentity);
     105           0 :                     a.eth_key = future_keypair.get().secret().makeInsecure().asBytes();
     106           0 :                     this_->onArchiveLoaded(*ctx, std::move(a));
     107           0 :                 } else {
     108         739 :                     this_->createAccount(*ctx);
     109             :                 }
     110             :             }
     111           0 :         } catch (const std::exception& e) {
     112           0 :             ctx->onFailure(AuthError::UNKNOWN, e.what());
     113           0 :         }
     114         782 :     });
     115         783 : }
     116             : 
     117             : bool
     118           2 : ArchiveAccountManager::updateCertificates(AccountArchive& archive, dht::crypto::Identity& device)
     119             : {
     120           2 :     JAMI_WARN("Updating certificates");
     121             :     using Certificate = dht::crypto::Certificate;
     122             : 
     123             :     // We need the CA key to resign certificates
     124           4 :     if (not archive.id.first or not *archive.id.first or not archive.id.second or not archive.ca_key
     125           4 :         or not *archive.ca_key)
     126           0 :         return false;
     127             : 
     128             :     // Currently set the CA flag and update expiration dates
     129           2 :     bool updated = false;
     130             : 
     131           2 :     auto& cert = archive.id.second;
     132           2 :     auto ca = cert->issuer;
     133             :     // Update CA if possible and relevant
     134           2 :     if (not ca or (not ca->issuer and (not ca->isCA() or ca->getExpiration() < clock::now()))) {
     135           4 :         ca = std::make_shared<Certificate>(
     136           6 :             Certificate::generate(*archive.ca_key, "Jami CA", {}, true));
     137           2 :         updated = true;
     138           2 :         JAMI_DBG("CA CRT re-generated");
     139             :     }
     140             : 
     141             :     // Update certificate
     142           2 :     if (updated or not cert->isCA() or cert->getExpiration() < clock::now()) {
     143           4 :         cert = std::make_shared<Certificate>(
     144           4 :             Certificate::generate(*archive.id.first,
     145             :                                   "Jami",
     146           4 :                                   dht::crypto::Identity {archive.ca_key, ca},
     147           2 :                                   true));
     148           2 :         updated = true;
     149           2 :         JAMI_DBG("Jami CRT re-generated");
     150             :     }
     151             : 
     152           2 :     if (updated and device.first and *device.first) {
     153             :         // update device certificate
     154           4 :         device.second = std::make_shared<Certificate>(
     155           6 :             Certificate::generate(*device.first, "Jami device", archive.id));
     156           2 :         JAMI_DBG("device CRT re-generated");
     157             :     }
     158             : 
     159           2 :     return updated;
     160           2 : }
     161             : 
     162             : bool
     163           2 : ArchiveAccountManager::setValidity(std::string_view scheme, const std::string& password,
     164             :                                    dht::crypto::Identity& device,
     165             :                                    const dht::InfoHash& id,
     166             :                                    int64_t validity)
     167             : {
     168           2 :     auto archive = readArchive(scheme, password);
     169             :     // We need the CA key to resign certificates
     170           4 :     if (not archive.id.first or not *archive.id.first or not archive.id.second or not archive.ca_key
     171           4 :         or not *archive.ca_key)
     172           0 :         return false;
     173             : 
     174           2 :     auto updated = false;
     175             : 
     176           2 :     if (id)
     177           0 :         JAMI_WARN("Updating validity for certificate with id: %s", id.to_c_str());
     178             :     else
     179           2 :         JAMI_WARN("Updating validity for certificates");
     180             : 
     181           2 :     auto& cert = archive.id.second;
     182           2 :     auto ca = cert->issuer;
     183           2 :     if (not ca)
     184           0 :         return false;
     185             : 
     186             :     // using Certificate = dht::crypto::Certificate;
     187             :     //  Update CA if possible and relevant
     188           2 :     if (not id or ca->getId() == id) {
     189           2 :         ca->setValidity(*archive.ca_key, validity);
     190           2 :         updated = true;
     191           2 :         JAMI_DBG("CA CRT re-generated");
     192             :     }
     193             : 
     194             :     // Update certificate
     195           2 :     if (updated or not id or cert->getId() == id) {
     196           2 :         cert->setValidity(dht::crypto::Identity {archive.ca_key, ca}, validity);
     197           2 :         device.second->issuer = cert;
     198           2 :         updated = true;
     199           2 :         JAMI_DBG("Jami CRT re-generated");
     200             :     }
     201             : 
     202           2 :     if (updated) {
     203           2 :         archive.save(fileutils::getFullPath(path_, archivePath_), scheme, password);
     204             :     }
     205             : 
     206           2 :     if (updated or not id or device.second->getId() == id) {
     207             :         // update device certificate
     208           2 :         device.second->setValidity(archive.id, validity);
     209           2 :         updated = true;
     210             :     }
     211             : 
     212           2 :     return updated;
     213           2 : }
     214             : 
     215             : void
     216         739 : ArchiveAccountManager::createAccount(AuthContext& ctx)
     217             : {
     218         739 :     AccountArchive a;
     219        1478 :     auto ca = dht::crypto::generateIdentity("Jami CA");
     220         739 :     if (!ca.first || !ca.second) {
     221           0 :         throw std::runtime_error("Unable to generate CA for this account.");
     222             :     }
     223         739 :     a.id = dht::crypto::generateIdentity("Jami", ca, 4096, true);
     224         739 :     if (!a.id.first || !a.id.second) {
     225           0 :         throw std::runtime_error("Unable to generate identity for this account.");
     226             :     }
     227         739 :     JAMI_WARN("[Auth] New account: CA: %s, ID: %s",
     228             :               ca.second->getId().toString().c_str(),
     229             :               a.id.second->getId().toString().c_str());
     230         739 :     a.ca_key = ca.first;
     231         739 :     auto keypair = dev::KeyPair::create();
     232         739 :     a.eth_key = keypair.secret().makeInsecure().asBytes();
     233         739 :     onArchiveLoaded(ctx, std::move(a));
     234         739 : }
     235             : 
     236             : void
     237          41 : ArchiveAccountManager::loadFromFile(AuthContext& ctx)
     238             : {
     239          41 :     JAMI_WARN("[Auth] Loading archive from: %s", ctx.credentials->uri.c_str());
     240          41 :     AccountArchive archive;
     241             :     try {
     242          41 :         archive = AccountArchive(ctx.credentials->uri, ctx.credentials->password_scheme, ctx.credentials->password);
     243           0 :     } catch (const std::exception& ex) {
     244           0 :         JAMI_WARN("[Auth] Unable to read file: %s", ex.what());
     245           0 :         ctx.onFailure(AuthError::INVALID_ARGUMENTS, ex.what());
     246           0 :         return;
     247           0 :     }
     248          41 :     onArchiveLoaded(ctx, std::move(archive));
     249          41 : }
     250             : 
     251             : struct ArchiveAccountManager::DhtLoadContext
     252             : {
     253             :     dht::DhtRunner dht;
     254             :     std::pair<bool, bool> stateOld {false, true};
     255             :     std::pair<bool, bool> stateNew {false, true};
     256             :     bool found {false};
     257             : };
     258             : 
     259             : void
     260           1 : ArchiveAccountManager::loadFromDHT(const std::shared_ptr<AuthContext>& ctx)
     261             : {
     262           1 :     ctx->dhtContext = std::make_unique<DhtLoadContext>();
     263           1 :     ctx->dhtContext->dht.run(ctx->credentials->dhtPort, {}, true);
     264           2 :     for (const auto& bootstrap : ctx->credentials->dhtBootstrap)
     265           1 :         ctx->dhtContext->dht.bootstrap(bootstrap);
     266           1 :     auto searchEnded = [ctx]() {
     267           1 :         if (not ctx->dhtContext or ctx->dhtContext->found) {
     268           1 :             return;
     269             :         }
     270           0 :         auto& s = *ctx->dhtContext;
     271           0 :         if (s.stateOld.first && s.stateNew.first) {
     272           0 :             dht::ThreadPool::computation().run(
     273           0 :                 [ctx, network_error = !s.stateOld.second && !s.stateNew.second] {
     274           0 :                     ctx->dhtContext.reset();
     275           0 :                     JAMI_WARN("[Auth] Failure looking for archive on DHT: %s",
     276             :                               /**/ network_error ? "network error" : "not found");
     277           0 :                     ctx->onFailure(network_error ? AuthError::NETWORK : AuthError::UNKNOWN, "");
     278           0 :                 });
     279             :         }
     280           1 :     };
     281             : 
     282           2 :     auto search = [ctx, searchEnded, w=weak_from_this()](bool previous) {
     283           2 :         std::vector<uint8_t> key;
     284           2 :         dht::InfoHash loc;
     285           2 :         auto& s = previous ? ctx->dhtContext->stateOld : ctx->dhtContext->stateNew;
     286             : 
     287             :         // compute archive location and decryption keys
     288             :         try {
     289           4 :             std::tie(key, loc) = computeKeys(ctx->credentials->password,
     290           2 :                                              ctx->credentials->uri,
     291           2 :                                              previous);
     292           2 :             JAMI_DBG("[Auth] Attempting to load account from DHT with %s at %s",
     293             :                      /**/ ctx->credentials->uri.c_str(),
     294             :                      loc.toString().c_str());
     295           2 :             if (not ctx->dhtContext or ctx->dhtContext->found) {
     296           0 :                 return;
     297             :             }
     298           6 :             ctx->dhtContext->dht.get(
     299             :                 loc,
     300           4 :                 [ctx, key = std::move(key), w](const std::shared_ptr<dht::Value>& val) {
     301           1 :                     std::vector<uint8_t> decrypted;
     302             :                     try {
     303           1 :                         decrypted = archiver::decompress(dht::crypto::aesDecrypt(val->data, key));
     304           0 :                     } catch (const std::exception& ex) {
     305           0 :                         return true;
     306           0 :                     }
     307           1 :                     JAMI_DBG("[Auth] Found archive on the DHT");
     308           1 :                     ctx->dhtContext->found = true;
     309           3 :                     dht::ThreadPool::computation().run([ctx,
     310           2 :                                                         decrypted = std::move(decrypted), w] {
     311             :                         try {
     312           1 :                             auto archive = AccountArchive(decrypted);
     313           2 :                             if (auto sthis = std::static_pointer_cast<ArchiveAccountManager>(w.lock())) {
     314           1 :                                 if (ctx->dhtContext) {
     315           1 :                                     ctx->dhtContext->dht.join();
     316           1 :                                     ctx->dhtContext.reset();
     317             :                                 }
     318           2 :                                 sthis->onArchiveLoaded(*ctx,
     319           1 :                                                       std::move(archive) /*, std::move(contacts)*/);
     320           1 :                             }
     321           1 :                         } catch (const std::exception& e) {
     322           0 :                             ctx->onFailure(AuthError::UNKNOWN, "");
     323           0 :                         }
     324           1 :                     });
     325           1 :                     return not ctx->dhtContext->found;
     326           1 :                 },
     327           2 :                 [=, &s](bool ok) {
     328           1 :                     JAMI_DBG("[Auth] DHT archive search ended at %s", /**/ loc.toString().c_str());
     329           1 :                     s.first = true;
     330           1 :                     s.second = ok;
     331           1 :                     searchEnded();
     332           1 :                 });
     333           0 :         } catch (const std::exception& e) {
     334             :             // JAMI_ERR("Error computing kedht::ThreadPool::computation().run(ys: %s", e.what());
     335           0 :             s.first = true;
     336           0 :             s.second = true;
     337           0 :             searchEnded();
     338           0 :             return;
     339           0 :         }
     340           3 :     };
     341           1 :     dht::ThreadPool::computation().run(std::bind(search, true));
     342           1 :     dht::ThreadPool::computation().run(std::bind(search, false));
     343           1 : }
     344             : 
     345             : void
     346           2 : ArchiveAccountManager::migrateAccount(AuthContext& ctx)
     347             : {
     348           2 :     JAMI_WARN("[Auth] Account migration needed");
     349           2 :     AccountArchive archive;
     350             :     try {
     351           2 :         archive = readArchive(ctx.credentials->password_scheme, ctx.credentials->password);
     352           0 :     } catch (...) {
     353           0 :         JAMI_DBG("[Auth] Unable to load archive");
     354           0 :         ctx.onFailure(AuthError::INVALID_ARGUMENTS, "");
     355           0 :         return;
     356           0 :     }
     357             : 
     358           2 :     updateArchive(archive);
     359             : 
     360           2 :     if (updateCertificates(archive, ctx.credentials->updateIdentity)) {
     361             :         // because updateCertificates already regenerate a device, we do not need to
     362             :         // regenerate one in onArchiveLoaded
     363           2 :         onArchiveLoaded(ctx, std::move(archive));
     364             :     } else
     365           0 :         ctx.onFailure(AuthError::UNKNOWN, "");
     366           2 : }
     367             : 
     368             : void
     369         783 : ArchiveAccountManager::onArchiveLoaded(AuthContext& ctx,
     370             :                                        AccountArchive&& a)
     371             : {
     372        1566 :     auto ethAccount = dev::KeyPair(dev::Secret(a.eth_key)).address().hex();
     373         783 :     dhtnet::fileutils::check_dir(path_, 0700);
     374             : 
     375         783 :     a.save(fileutils::getFullPath(path_, archivePath_), ctx.credentials ? ctx.credentials->password_scheme : "", ctx.credentials ? ctx.credentials->password : "");
     376             : 
     377         783 :     if (not a.id.second->isCA()) {
     378           0 :         JAMI_ERR("[Auth] Attempting to sign a certificate with a non-CA.");
     379             :     }
     380             : 
     381         783 :     std::shared_ptr<dht::crypto::Certificate> deviceCertificate;
     382         783 :     std::unique_ptr<ContactList> contacts;
     383         783 :     auto usePreviousIdentity = false;
     384             :     // If updateIdentity got a valid certificate, there is no need for a new cert
     385         783 :     if (auto oldId = ctx.credentials->updateIdentity.second) {
     386           4 :         contacts = std::make_unique<ContactList>(ctx.accountId, oldId, path_, onChange_);
     387           4 :         if (contacts->isValidAccountDevice(*oldId) && ctx.credentials->updateIdentity.first) {
     388           3 :             deviceCertificate = oldId;
     389           3 :             usePreviousIdentity = true;
     390           3 :             JAMI_WARN("[Auth] Using previously generated certificate %s",
     391             :                                           deviceCertificate->getLongId().toString().c_str());
     392             :         } else {
     393           1 :             contacts.reset();
     394             :         }
     395         783 :     }
     396             : 
     397             :     // Generate a new device if needed
     398         783 :     if (!deviceCertificate) {
     399         780 :         JAMI_WARN("[Auth] Creating new device certificate");
     400         780 :         auto request = ctx.request.get();
     401         780 :         if (not request->verify()) {
     402           0 :             JAMI_ERR("[Auth] Invalid certificate request.");
     403           0 :             ctx.onFailure(AuthError::INVALID_ARGUMENTS, "");
     404           0 :             return;
     405             :         }
     406        1560 :         deviceCertificate = std::make_shared<dht::crypto::Certificate>(
     407        2340 :             dht::crypto::Certificate::generate(*request, a.id));
     408        2340 :         JAMI_WARNING("[Auth] Created new device: {}",
     409             :                   deviceCertificate->getLongId());
     410         780 :     }
     411             : 
     412         783 :     auto receipt = makeReceipt(a.id, *deviceCertificate, ethAccount);
     413        1566 :     auto receiptSignature = a.id.first->sign({receipt.first.begin(), receipt.first.end()});
     414             : 
     415         783 :     auto info = std::make_unique<AccountInfo>();
     416         783 :     auto pk = usePreviousIdentity ? ctx.credentials->updateIdentity.first : ctx.key.get();
     417         783 :     auto sharedPk = pk->getSharedPublicKey();
     418         783 :     info->identity.first = pk;
     419         783 :     info->identity.second = deviceCertificate;
     420         783 :     info->accountId = a.id.second->getId().toString();
     421         783 :     info->devicePk = sharedPk;
     422         783 :     info->deviceId = info->devicePk->getLongId().toString();
     423         783 :     if (ctx.deviceName.empty())
     424           0 :         ctx.deviceName = info->deviceId.substr(8);
     425             : 
     426         783 :     if (!contacts)
     427         780 :         contacts = std::make_unique<ContactList>(ctx.accountId, a.id.second, path_, onChange_);
     428         783 :     info->contacts = std::move(contacts);
     429         783 :     info->contacts->setContacts(a.contacts);
     430         783 :     info->contacts->foundAccountDevice(deviceCertificate, ctx.deviceName, clock::now());
     431         783 :     info->ethAccount = ethAccount;
     432         783 :     info->announce = std::move(receipt.second);
     433         783 :     ConversationModule::saveConvInfosToPath(path_, a.conversations);
     434         783 :     ConversationModule::saveConvRequestsToPath(path_, a.conversationsRequests);
     435         783 :     info_ = std::move(info);
     436             : 
     437         783 :     ctx.onSuccess(*info_,
     438         783 :                   std::move(a.config),
     439         783 :                   std::move(receipt.first),
     440         783 :                   std::move(receiptSignature));
     441         783 : }
     442             : 
     443             : std::pair<std::vector<uint8_t>, dht::InfoHash>
     444           3 : ArchiveAccountManager::computeKeys(const std::string& password,
     445             :                                    const std::string& pin,
     446             :                                    bool previous)
     447             : {
     448             :     // Compute time seed
     449           3 :     auto now = std::chrono::duration_cast<std::chrono::seconds>(clock::now().time_since_epoch());
     450           3 :     auto tseed = now.count() / std::chrono::seconds(EXPORT_KEY_RENEWAL_TIME).count();
     451           3 :     if (previous)
     452           1 :         tseed--;
     453           3 :     std::ostringstream ss;
     454           3 :     ss << std::hex << tseed;
     455           3 :     auto tseed_str = ss.str();
     456             : 
     457             :     // Generate key for archive encryption, using PIN as the salt
     458           3 :     std::vector<uint8_t> salt_key;
     459           3 :     salt_key.reserve(pin.size() + tseed_str.size());
     460           3 :     salt_key.insert(salt_key.end(), pin.begin(), pin.end());
     461           3 :     salt_key.insert(salt_key.end(), tseed_str.begin(), tseed_str.end());
     462           3 :     auto key = dht::crypto::stretchKey(password, salt_key, 256 / 8);
     463             : 
     464             :     // Generate public storage location as SHA1(key).
     465           3 :     auto loc = dht::InfoHash::get(key);
     466             : 
     467           6 :     return {key, loc};
     468           3 : }
     469             : 
     470             : std::pair<std::string, std::shared_ptr<dht::Value>>
     471         783 : ArchiveAccountManager::makeReceipt(const dht::crypto::Identity& id,
     472             :                                    const dht::crypto::Certificate& device,
     473             :                                    const std::string& ethAccount)
     474             : {
     475         783 :     JAMI_DBG("[Auth] Signing device receipt");
     476         783 :     auto devId = device.getId();
     477         783 :     DeviceAnnouncement announcement;
     478         783 :     announcement.dev = devId;
     479         783 :     announcement.pk = device.getSharedPublicKey();
     480         783 :     dht::Value ann_val {announcement};
     481         783 :     ann_val.sign(*id.first);
     482             : 
     483         783 :     auto packedAnnoucement = ann_val.getPacked();
     484         783 :     JAMI_DBG("[Auth] Device announcement size: %zu", packedAnnoucement.size());
     485             : 
     486         783 :     std::ostringstream is;
     487         783 :     is << "{\"id\":\"" << id.second->getId() << "\",\"dev\":\"" << devId << "\",\"eth\":\""
     488         783 :        << ethAccount << "\",\"announce\":\"" << base64::encode(packedAnnoucement) << "\"}";
     489             : 
     490             :     // auto announce_ = ;
     491        1566 :     return {is.str(), std::make_shared<dht::Value>(std::move(ann_val))};
     492         783 : }
     493             : 
     494             : bool
     495           4 : ArchiveAccountManager::needsMigration(const dht::crypto::Identity& id)
     496             : {
     497           4 :     if (not id.second)
     498           0 :         return false;
     499           4 :     auto cert = id.second->issuer;
     500           8 :     while (cert) {
     501           6 :         if (not cert->isCA()) {
     502           0 :             JAMI_WARN("certificate %s is not a CA, needs update.", cert->getId().toString().c_str());
     503           0 :             return true;
     504             :         }
     505           6 :         if (cert->getExpiration() < clock::now()) {
     506           2 :             JAMI_WARN("certificate %s is expired, needs update.", cert->getId().toString().c_str());
     507           2 :             return true;
     508             :         }
     509           4 :         cert = cert->issuer;
     510             :     }
     511           2 :     return false;
     512           4 : }
     513             : 
     514             : void
     515         827 : ArchiveAccountManager::syncDevices()
     516             : {
     517         827 :     if (not dht_ or not dht_->isRunning()) {
     518           0 :         JAMI_WARN("Not syncing devices: DHT is not running");
     519           0 :         return;
     520             :     }
     521         827 :     JAMI_DBG("Building device sync from %s", info_->deviceId.c_str());
     522         827 :     auto sync_data = info_->contacts->getSyncData();
     523             : 
     524        2672 :     for (const auto& dev : getKnownDevices()) {
     525             :         // don't send sync data to ourself
     526        1845 :         if (dev.first.toString() == info_->deviceId)
     527        1827 :             continue;
     528        1018 :         if (!dev.second.certificate) {
     529        3000 :             JAMI_WARNING("Unable to find certificate for {}", dev.first);
     530        1000 :             continue;
     531        1000 :         }
     532          18 :         auto pk = dev.second.certificate->getSharedPublicKey();
     533          18 :         JAMI_DBG("sending device sync to %s %s",
     534             :                  dev.second.name.c_str(),
     535             :                  dev.first.toString().c_str());
     536          18 :         auto syncDeviceKey = dht::InfoHash::get("inbox:" + pk->getId().toString());
     537          18 :         dht_->putEncrypted(syncDeviceKey, pk, sync_data);
     538          18 :     }
     539         827 : }
     540             : 
     541             : void
     542         681 : ArchiveAccountManager::startSync(const OnNewDeviceCb& cb, const OnDeviceAnnouncedCb& dcb, bool publishPresence)
     543             : {
     544         681 :     AccountManager::startSync(std::move(cb), std::move(dcb), publishPresence);
     545             : 
     546        2043 :     dht_->listen<DeviceSync>(
     547        1362 :         dht::InfoHash::get("inbox:" + info_->devicePk->getId().toString()),
     548          15 :         [this](DeviceSync&& sync) {
     549             :             // Received device sync data.
     550             :             // check device certificate
     551          15 :             findCertificate(sync.from,
     552          15 :                             [this,
     553          30 :                              sync](const std::shared_ptr<dht::crypto::Certificate>& cert) mutable {
     554          15 :                                 if (!cert or cert->getId() != sync.from) {
     555           0 :                                     JAMI_WARN("Unable to find certificate for device %s",
     556             :                                               sync.from.toString().c_str());
     557           0 :                                     return;
     558             :                                 }
     559          15 :                                 if (not foundAccountDevice(cert))
     560           0 :                                     return;
     561          15 :                                 onSyncData(std::move(sync));
     562             :                             });
     563             : 
     564          15 :             return true;
     565             :         });
     566         681 : }
     567             : 
     568             : AccountArchive
     569          48 : ArchiveAccountManager::readArchive(std::string_view scheme, const std::string& pwd) const
     570             : {
     571          48 :     JAMI_DBG("[Auth] Reading account archive");
     572          96 :     return AccountArchive(fileutils::getFullPath(path_, archivePath_), scheme, pwd);
     573             : }
     574             : 
     575             : void
     576          43 : ArchiveAccountManager::updateArchive(AccountArchive& archive) const
     577             : {
     578             :     using namespace libjami::Account::ConfProperties;
     579             : 
     580             :     // Keys not exported to archive
     581             :     static const auto filtered_keys = {Ringtone::PATH,
     582             :                                        ARCHIVE_PATH,
     583             :                                        DEVICE_ID,
     584             :                                        DEVICE_NAME,
     585             :                                        Conf::CONFIG_DHT_PORT,
     586             :                                        DHT_PROXY_LIST_URL,
     587             :                                        AUTOANSWER,
     588             :                                        PROXY_ENABLED,
     589             :                                        PROXY_SERVER,
     590             :                                        PROXY_PUSH_TOKEN};
     591             : 
     592             :     // Keys with meaning of file path where the contents has to be exported in base64
     593             :     static const auto encoded_keys = {TLS::CA_LIST_FILE,
     594             :                                       TLS::CERTIFICATE_FILE,
     595             :                                       TLS::PRIVATE_KEY_FILE};
     596             : 
     597          43 :     JAMI_DBG("[Auth] Building account archive");
     598        2451 :     for (const auto& it : onExportConfig_()) {
     599             :         // filter-out?
     600        2408 :         if (std::any_of(std::begin(filtered_keys), std::end(filtered_keys), [&](const auto& key) {
     601       22790 :                 return key == it.first;
     602             :             }))
     603         301 :             continue;
     604             : 
     605             :         // file contents?
     606        2107 :         if (std::any_of(std::begin(encoded_keys), std::end(encoded_keys), [&](const auto& key) {
     607        6192 :                 return key == it.first;
     608             :             })) {
     609             :             try {
     610         215 :                 archive.config.emplace(it.first, base64::encode(fileutils::loadFile(it.second)));
     611          43 :             } catch (...) {
     612          43 :             }
     613             :         } else
     614        1978 :             archive.config[it.first] = it.second;
     615          43 :     }
     616          43 :     if (info_) {
     617             :         // If migrating from same archive, info_ will be null
     618          41 :         archive.contacts = info_->contacts->getContacts();
     619             :         // Note we do not know accountID_ here, use path
     620          41 :         archive.conversations = ConversationModule::convInfosFromPath(path_);
     621          41 :         archive.conversationsRequests = ConversationModule::convRequestsFromPath(path_);
     622             :     }
     623          43 : }
     624             : 
     625             : void
     626           2 : ArchiveAccountManager::saveArchive(AccountArchive& archive, std::string_view scheme, const std::string& pwd)
     627             : {
     628             :     try {
     629           2 :         updateArchive(archive);
     630           2 :         if (archivePath_.empty())
     631           0 :             archivePath_ = "export.gz";
     632           2 :         archive.save(fileutils::getFullPath(path_, archivePath_), scheme, pwd);
     633           0 :     } catch (const std::runtime_error& ex) {
     634           0 :         JAMI_ERR("[Auth] Unable to export archive: %s", ex.what());
     635           0 :         return;
     636           0 :     }
     637             : }
     638             : 
     639             : bool
     640           4 : ArchiveAccountManager::changePassword(const std::string& password_old,
     641             :                                       const std::string& password_new)
     642             : {
     643             :     try {
     644           4 :         auto path = fileutils::getFullPath(path_, archivePath_);
     645           6 :         AccountArchive(path, fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password_old)
     646           2 :             .save(path, fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password_new);
     647           2 :         return true;
     648           6 :     } catch (const std::exception&) {
     649           2 :         return false;
     650           2 :     }
     651             : }
     652             : 
     653             : std::vector<uint8_t>
     654           0 : ArchiveAccountManager::getPasswordKey(const std::string& password)
     655             : {
     656             :     try {
     657           0 :         auto data = dhtnet::fileutils::loadFile(fileutils::getFullPath(path_, archivePath_));
     658             :         // Try to decrypt to check if password is valid
     659           0 :         auto key = dht::crypto::aesGetKey(data, password);
     660           0 :         auto decrypted = dht::crypto::aesDecrypt(dht::crypto::aesGetEncrypted(data), key);
     661           0 :         return key;
     662           0 :     } catch (const std::exception& e) {
     663           0 :         JAMI_ERR("Error loading archive: %s", e.what());
     664           0 :     }
     665           0 :     return {};
     666             : }
     667             : 
     668             : std::string
     669           1 : generatePIN(size_t length = 16, size_t split = 8)
     670             : {
     671             :     static constexpr const char alphabet[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
     672           1 :     std::random_device rd;
     673           1 :     std::uniform_int_distribution<size_t> dis(0, sizeof(alphabet) - 2);
     674           1 :     std::string ret;
     675           1 :     ret.reserve(length);
     676          17 :     for (size_t i = 0; i < length; i++) {
     677          16 :         ret.push_back(alphabet[dis(rd)]);
     678          16 :         if (i % split == split - 1 and i != length - 1)
     679           1 :             ret.push_back('-');
     680             :     }
     681           2 :     return ret;
     682           1 : }
     683             : 
     684             : void
     685           2 : ArchiveAccountManager::addDevice(const std::string& password, AddDeviceCallback cb)
     686             : {
     687           2 :     dht::ThreadPool::computation().run([password, cb = std::move(cb), w=weak_from_this()] {
     688           2 :         auto this_ = std::static_pointer_cast<ArchiveAccountManager>(w.lock());
     689           2 :         if (not this_) return;
     690             : 
     691           2 :         std::vector<uint8_t> key;
     692           2 :         dht::InfoHash loc;
     693           2 :         std::string pin_str;
     694           2 :         AccountArchive a;
     695             :         try {
     696           2 :             JAMI_DBG("[Auth] Exporting account");
     697             : 
     698           2 :             a = this_->readArchive("password", password);
     699             : 
     700             :             // Generate random PIN
     701           1 :             pin_str = generatePIN();
     702             : 
     703           1 :             std::tie(key, loc) = computeKeys(password, pin_str);
     704           1 :         } catch (const std::exception& e) {
     705           1 :             JAMI_ERR("[Auth] Unable to export account: %s", e.what());
     706           1 :             cb(AddDeviceResult::ERROR_CREDENTIALS, {});
     707           1 :             return;
     708           1 :         }
     709             :         // now that key and loc are computed, display to user in lowercase
     710           1 :         std::transform(pin_str.begin(), pin_str.end(), pin_str.begin(), ::tolower);
     711             :         try {
     712           1 :             this_->updateArchive(a);
     713           2 :             auto encrypted = dht::crypto::aesEncrypt(archiver::compress(a.serialize()), key);
     714           1 :             if (not this_->dht_ or not this_->dht_->isRunning())
     715           0 :                 throw std::runtime_error("DHT is not running..");
     716           1 :             JAMI_WARN("[Auth] Exporting account with PIN: %s at %s (size %zu)",
     717             :                         pin_str.c_str(),
     718             :                         loc.toString().c_str(),
     719             :                         encrypted.size());
     720           1 :             this_->dht_->put(loc, encrypted, [cb, pin = std::move(pin_str)](bool ok) {
     721           1 :                 JAMI_DBG("[Auth] Account archive published: %s", ok ? "success" : "failure");
     722           1 :                 if (ok)
     723           1 :                     cb(AddDeviceResult::SUCCESS_SHOW_PIN, pin);
     724             :                 else
     725           0 :                     cb(AddDeviceResult::ERROR_NETWORK, {});
     726           1 :             });
     727           1 :         } catch (const std::exception& e) {
     728           0 :             JAMI_ERR("[Auth] Unable to export account: %s", e.what());
     729           0 :             cb(AddDeviceResult::ERROR_NETWORK, {});
     730           0 :             return;
     731           0 :         }
     732           5 :     });
     733           2 : }
     734             : 
     735             : bool
     736           3 : ArchiveAccountManager::revokeDevice(const std::string& device,
     737             :                                     std::string_view scheme,
     738             :                                     const std::string& password,
     739             :                                     RevokeDeviceCallback cb)
     740             : {
     741           3 :     auto fa = dht::ThreadPool::computation().getShared<AccountArchive>(
     742           9 :         [this, scheme=std::string(scheme), password] { return readArchive(scheme, password); });
     743           3 :     findCertificate(DeviceId(device),
     744           3 :         [fa = std::move(fa), scheme=std::string(scheme), password, device, cb, w=weak_from_this()](
     745             :             const std::shared_ptr<dht::crypto::Certificate>& crt) mutable {
     746           3 :                 if (not crt) {
     747           1 :                     cb(RevokeDeviceResult::ERROR_NETWORK);
     748           1 :                     return;
     749             :                 }
     750           2 :                 auto this_ = std::static_pointer_cast<ArchiveAccountManager>(w.lock());
     751           2 :                 if (not this_) return;
     752           2 :                 this_->info_->contacts->foundAccountDevice(crt);
     753           2 :                 AccountArchive a;
     754             :                 try {
     755           2 :                     a = fa.get();
     756           0 :                 } catch (...) {
     757           0 :                     cb(RevokeDeviceResult::ERROR_CREDENTIALS);
     758           0 :                     return;
     759           0 :                 }
     760             :                 // Add revoked device to the revocation list and resign it
     761           2 :                 if (not a.revoked)
     762           2 :                     a.revoked = std::make_shared<decltype(a.revoked)::element_type>();
     763           2 :                 a.revoked->revoke(*crt);
     764           2 :                 a.revoked->sign(a.id);
     765             :                 // add to CRL cache
     766           2 :                 this_->certStore().pinRevocationList(a.id.second->getId().toString(), a.revoked);
     767           2 :                 this_->certStore().loadRevocations(*a.id.second);
     768             : 
     769             :                 // Announce CRL immediately
     770           2 :                 auto h = a.id.second->getId();
     771           2 :                 this_->dht_->put(h, a.revoked, dht::DoneCallback {}, {}, true);
     772             : 
     773           2 :                 this_->saveArchive(a, scheme, password);
     774           2 :                 this_->info_->contacts->removeAccountDevice(crt->getLongId());
     775           2 :                 cb(RevokeDeviceResult::SUCCESS);
     776           2 :                 this_->syncDevices();
     777           2 :             });
     778           3 :     return false;
     779           3 : }
     780             : 
     781             : bool
     782          38 : ArchiveAccountManager::exportArchive(const std::string& destinationPath, std::string_view scheme, const std::string& password)
     783             : {
     784             :     try {
     785             :         // Save contacts if possible before exporting
     786          38 :         AccountArchive archive = readArchive(scheme, password);
     787          38 :         updateArchive(archive);
     788          38 :         auto archivePath = fileutils::getFullPath(path_, archivePath_);
     789          38 :         archive.save(archivePath, scheme, password);
     790             : 
     791             :         // Export the file
     792          38 :         std::error_code ec;
     793          38 :         std::filesystem::copy_file(archivePath, destinationPath, std::filesystem::copy_options::overwrite_existing, ec);
     794          38 :         return !ec;
     795          38 :     } catch (const std::runtime_error& ex) {
     796           0 :         JAMI_ERR("[Auth] Unable to export archive: %s", ex.what());
     797           0 :         return false;
     798           0 :     } catch (...) {
     799           0 :         JAMI_ERR("[Auth] Unable to export archive: Unable to read archive");
     800           0 :         return false;
     801           0 :     }
     802             : }
     803             : 
     804             : bool
     805           0 : ArchiveAccountManager::isPasswordValid(const std::string& password)
     806             : {
     807             :     try {
     808           0 :         readArchive(fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password);
     809           0 :         return true;
     810           0 :     } catch (...) {
     811           0 :         return false;
     812           0 :     }
     813             : }
     814             : 
     815             : #if HAVE_RINGNS
     816             : void
     817           1 : ArchiveAccountManager::registerName(const std::string& name,
     818             :                                     std::string_view scheme,
     819             :                                     const std::string& password,
     820             :                                     RegistrationCallback cb)
     821             : {
     822           1 :     std::string signedName;
     823           1 :     auto nameLowercase {name};
     824           1 :     std::transform(nameLowercase.begin(), nameLowercase.end(), nameLowercase.begin(), ::tolower);
     825           1 :     std::string publickey;
     826           1 :     std::string accountId;
     827           1 :     std::string ethAccount;
     828             : 
     829             :     try {
     830           1 :         auto archive = readArchive(scheme, password);
     831           1 :         auto privateKey = archive.id.first;
     832           1 :         const auto& pk = privateKey->getPublicKey();
     833           1 :         publickey = pk.toString();
     834           1 :         accountId = pk.getId().toString();
     835           2 :         signedName = base64::encode(
     836           3 :             privateKey->sign(std::vector<uint8_t>(nameLowercase.begin(), nameLowercase.end())));
     837           1 :         ethAccount = dev::KeyPair(dev::Secret(archive.eth_key)).address().hex();
     838           1 :     } catch (const std::exception& e) {
     839             :         // JAMI_ERR("[Auth] Unable to export account: %s", e.what());
     840           0 :         cb(NameDirectory::RegistrationResponse::invalidCredentials, name);
     841           0 :         return;
     842           0 :     }
     843             : 
     844           1 :     nameDir_.get().registerName(accountId, nameLowercase, ethAccount, cb, signedName, publickey);
     845           1 : }
     846             : #endif
     847             : 
     848             : } // namespace jami

Generated by: LCOV version 1.14