LCOV - code coverage report
Current view: top level - foo/src/jamidht - archive_account_manager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 715 981 72.9 %
Date: 2025-08-24 09:11:10 Functions: 185 258 71.7 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2025 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             : #include "jamidht/auth_channel_handler.h"
      28             : #include "client/ring_signal.h"
      29             : 
      30             : #include <dhtnet/multiplexed_socket.h>
      31             : #include <opendht/dhtrunner.h>
      32             : #include <opendht/thread_pool.h>
      33             : 
      34             : #include <memory>
      35             : #include <fstream>
      36             : 
      37             : #include "config/yamlparser.h"
      38             : 
      39             : namespace jami {
      40             : 
      41             : const constexpr auto EXPORT_KEY_RENEWAL_TIME = std::chrono::minutes(20);
      42             : constexpr auto AUTH_URI_SCHEME = "jami-auth://"sv;
      43             : constexpr auto CHANNEL_SCHEME = "auth:"sv;
      44             : constexpr auto OP_TIMEOUT = 5min;
      45             : 
      46             : void
      47         634 : ArchiveAccountManager::initAuthentication(PrivateKey key,
      48             :                                           std::string deviceName,
      49             :                                           std::unique_ptr<AccountCredentials> credentials,
      50             :                                           AuthSuccessCallback onSuccess,
      51             :                                           AuthFailureCallback onFailure,
      52             :                                           const OnChangeCallback& onChange)
      53             : {
      54        1902 :     JAMI_WARNING("[Account {}] [Auth] starting authentication with scheme '{}'",
      55             :                  accountId_,
      56             :                  credentials->scheme);
      57         634 :     auto ctx = std::make_shared<AuthContext>();
      58         634 :     ctx->accountId = accountId_;
      59         634 :     ctx->key = key;
      60         634 :     ctx->request = buildRequest(key);
      61         634 :     ctx->deviceName = std::move(deviceName);
      62         634 :     ctx->credentials = dynamic_unique_cast<ArchiveAccountCredentials>(std::move(credentials));
      63         634 :     ctx->onSuccess = std::move(onSuccess);
      64         634 :     ctx->onFailure = std::move(onFailure);
      65             : 
      66         634 :     if (not ctx->credentials) {
      67           0 :         ctx->onFailure(AuthError::INVALID_ARGUMENTS, "invalid credentials");
      68           0 :         return;
      69             :     }
      70         634 :     onChange_ = std::move(onChange);
      71             : 
      72         634 :     if (ctx->credentials->scheme == "p2p") {
      73          15 :         JAMI_DEBUG("[LinkDevice] Importing account via p2p scheme.");
      74           5 :         startLoadArchiveFromDevice(ctx);
      75           5 :         return;
      76             :     }
      77             : 
      78         629 :     dht::ThreadPool::computation().run([ctx = std::move(ctx), wthis = weak()] {
      79         629 :         auto this_ = wthis.lock();
      80         629 :         if (not this_)
      81           0 :             return;
      82             :         try {
      83         629 :             if (ctx->credentials->scheme == "file") {
      84             :                 // Import from external archive
      85          31 :                 this_->loadFromFile(*ctx);
      86             :             } else {
      87             :                 // Create/migrate local account
      88         598 :                 bool hasArchive = not ctx->credentials->uri.empty()
      89        1196 :                                   and std::filesystem::is_regular_file(ctx->credentials->uri);
      90         598 :                 if (hasArchive) {
      91             :                     // Create/migrate from local archive
      92           4 :                     if (ctx->credentials->updateIdentity.first
      93           8 :                         and ctx->credentials->updateIdentity.second
      94           8 :                         and needsMigration(this_->accountId_, ctx->credentials->updateIdentity)) {
      95           2 :                         this_->migrateAccount(*ctx);
      96             :                     } else {
      97           2 :                         this_->loadFromFile(*ctx);
      98             :                     }
      99         594 :                 } else if (ctx->credentials->updateIdentity.first
     100         594 :                            and ctx->credentials->updateIdentity.second) {
     101           0 :                     auto future_keypair = dht::ThreadPool::computation().get<dev::KeyPair>(
     102           0 :                         &dev::KeyPair::create);
     103           0 :                     AccountArchive a;
     104           0 :                     JAMI_WARNING("[Account {}] [Auth] Converting certificate from old account {}",
     105             :                                  this_->accountId_,
     106             :                                  ctx->credentials->updateIdentity.first->getPublicKey()
     107             :                                      .getId()
     108             :                                      .to_view());
     109           0 :                     a.id = std::move(ctx->credentials->updateIdentity);
     110             :                     try {
     111           0 :                         a.ca_key = std::make_shared<dht::crypto::PrivateKey>(
     112           0 :                             fileutils::loadFile("ca.key", this_->path_));
     113           0 :                     } catch (...) {
     114           0 :                     }
     115           0 :                     this_->updateCertificates(a, ctx->credentials->updateIdentity);
     116           0 :                     a.eth_key = future_keypair.get().secret().makeInsecure().asBytes();
     117           0 :                     this_->onArchiveLoaded(*ctx, std::move(a), false);
     118           0 :                 } else {
     119         594 :                     this_->createAccount(*ctx);
     120             :                 }
     121             :             }
     122           0 :         } catch (const std::exception& e) {
     123           0 :             ctx->onFailure(AuthError::UNKNOWN, e.what());
     124           0 :         }
     125         629 :     });
     126         634 : }
     127             : 
     128             : bool
     129           2 : ArchiveAccountManager::updateCertificates(AccountArchive& archive, dht::crypto::Identity& device)
     130             : {
     131           6 :     JAMI_WARNING("[Account {}] [Auth] Updating certificates", accountId_);
     132             :     using Certificate = dht::crypto::Certificate;
     133             : 
     134             :     // We need the CA key to resign certificates
     135           4 :     if (not archive.id.first or not *archive.id.first or not archive.id.second or not archive.ca_key
     136           4 :         or not *archive.ca_key)
     137           0 :         return false;
     138             : 
     139             :     // Currently set the CA flag and update expiration dates
     140           2 :     bool updated = false;
     141             : 
     142           2 :     auto& cert = archive.id.second;
     143           2 :     auto ca = cert->issuer;
     144             :     // Update CA if possible and relevant
     145           2 :     if (not ca or (not ca->issuer and (not ca->isCA() or ca->getExpiration() < clock::now()))) {
     146           4 :         ca = std::make_shared<Certificate>(
     147           6 :             Certificate::generate(*archive.ca_key, "Jami CA", {}, true));
     148           2 :         updated = true;
     149           6 :         JAMI_LOG("[Account {}] [Auth] CA certificate re-generated", accountId_);
     150             :     }
     151             : 
     152             :     // Update certificate
     153           2 :     if (updated or not cert->isCA() or cert->getExpiration() < clock::now()) {
     154           4 :         cert = std::make_shared<Certificate>(
     155           4 :             Certificate::generate(*archive.id.first,
     156             :                                   "Jami",
     157           4 :                                   dht::crypto::Identity {archive.ca_key, ca},
     158           2 :                                   true));
     159           2 :         updated = true;
     160           6 :         JAMI_LOG("[Account {}] [Auth] Account certificate for {} re-generated",
     161             :                  accountId_,
     162             :                  cert->getId());
     163             :     }
     164             : 
     165           2 :     if (updated and device.first and *device.first) {
     166             :         // update device certificate
     167           4 :         device.second = std::make_shared<Certificate>(
     168           6 :             Certificate::generate(*device.first, "Jami device", archive.id));
     169           6 :         JAMI_LOG("[Account {}] [Auth] Device certificate re-generated", accountId_);
     170             :     }
     171             : 
     172           2 :     return updated;
     173           2 : }
     174             : 
     175             : bool
     176           2 : ArchiveAccountManager::setValidity(std::string_view scheme,
     177             :                                    const std::string& password,
     178             :                                    dht::crypto::Identity& device,
     179             :                                    const dht::InfoHash& id,
     180             :                                    int64_t validity)
     181             : {
     182           2 :     auto archive = readArchive(scheme, password);
     183             :     // We need the CA key to resign certificates
     184           4 :     if (not archive.id.first or not *archive.id.first or not archive.id.second or not archive.ca_key
     185           4 :         or not *archive.ca_key)
     186           0 :         return false;
     187             : 
     188           2 :     auto updated = false;
     189             : 
     190           2 :     if (id)
     191           0 :         JAMI_WARNING("[Account {}] [Auth] Updating validity for certificate with id: {}",
     192             :                      accountId_,
     193             :                      id);
     194             :     else
     195           6 :         JAMI_WARNING("[Account {}] [Auth] Updating validity for certificates", accountId_);
     196             : 
     197           2 :     auto& cert = archive.id.second;
     198           2 :     auto ca = cert->issuer;
     199           2 :     if (not ca)
     200           0 :         return false;
     201             : 
     202             :     // using Certificate = dht::crypto::Certificate;
     203             :     //  Update CA if possible and relevant
     204           2 :     if (not id or ca->getId() == id) {
     205           2 :         ca->setValidity(*archive.ca_key, validity);
     206           2 :         updated = true;
     207           6 :         JAMI_LOG("[Account {}] [Auth] CA certificate re-generated", accountId_);
     208             :     }
     209             : 
     210             :     // Update certificate
     211           2 :     if (updated or not id or cert->getId() == id) {
     212           2 :         cert->setValidity(dht::crypto::Identity {archive.ca_key, ca}, validity);
     213           2 :         device.second->issuer = cert;
     214           2 :         updated = true;
     215           6 :         JAMI_LOG("[Account {}] [Auth] Jami certificate re-generated", accountId_);
     216             :     }
     217             : 
     218           2 :     if (updated) {
     219           2 :         archive.save(fileutils::getFullPath(path_, archivePath_), scheme, password);
     220             :     }
     221             : 
     222           2 :     if (updated or not id or device.second->getId() == id) {
     223             :         // update device certificate
     224           2 :         device.second->setValidity(archive.id, validity);
     225           2 :         updated = true;
     226             :     }
     227             : 
     228           2 :     return updated;
     229           2 : }
     230             : 
     231             : void
     232         594 : ArchiveAccountManager::createAccount(AuthContext& ctx)
     233             : {
     234         594 :     AccountArchive a;
     235        1188 :     auto ca = dht::crypto::generateIdentity("Jami CA");
     236         594 :     if (!ca.first || !ca.second) {
     237           0 :         throw std::runtime_error("Unable to generate CA for this account.");
     238             :     }
     239         594 :     a.id = dht::crypto::generateIdentity("Jami", ca, 4096, true);
     240         594 :     if (!a.id.first || !a.id.second) {
     241           0 :         throw std::runtime_error("Unable to generate identity for this account.");
     242             :     }
     243        1782 :     JAMI_WARNING("[Account {}] [Auth] New account: CA: {}, ID: {}",
     244             :                  accountId_,
     245             :                  ca.second->getId(),
     246             :                  a.id.second->getId());
     247         594 :     a.ca_key = ca.first;
     248         594 :     auto keypair = dev::KeyPair::create();
     249         594 :     a.eth_key = keypair.secret().makeInsecure().asBytes();
     250         594 :     onArchiveLoaded(ctx, std::move(a), false);
     251         594 : }
     252             : 
     253             : void
     254          33 : ArchiveAccountManager::loadFromFile(AuthContext& ctx)
     255             : {
     256          99 :     JAMI_WARNING("[Account {}] [Auth] Loading archive from: {}",
     257             :                  accountId_,
     258             :                  ctx.credentials->uri.c_str());
     259          33 :     AccountArchive archive;
     260             :     try {
     261          66 :         archive = AccountArchive(ctx.credentials->uri,
     262          33 :                                  ctx.credentials->password_scheme,
     263          66 :                                  ctx.credentials->password);
     264           0 :     } catch (const std::exception& ex) {
     265           0 :         JAMI_WARNING("[Account {}] [Auth] Unable to read archive file: {}", accountId_, ex.what());
     266           0 :         ctx.onFailure(AuthError::INVALID_ARGUMENTS, ex.what());
     267           0 :         return;
     268           0 :     }
     269          33 :     onArchiveLoaded(ctx, std::move(archive), false);
     270          33 : }
     271             : 
     272             : // this enum is for the states of add device TLS protocol
     273             : // used for LinkDeviceProtocolStateChanged = AddDeviceStateChanged
     274             : enum class AuthDecodingState : uint8_t {
     275             :     HANDSHAKE = 0,
     276             :     EST,
     277             :     AUTH,
     278             :     DATA,
     279             :     ERR,
     280             :     AUTH_ERROR,
     281             :     DONE,
     282             :     TIMEOUT,
     283             :     CANCELED
     284             : };
     285             : 
     286             : static constexpr std::string_view
     287          36 : toString(AuthDecodingState state)
     288             : {
     289          36 :     switch (state) {
     290           8 :     case AuthDecodingState::HANDSHAKE:
     291           8 :         return "HANDSHAKE"sv;
     292           0 :     case AuthDecodingState::EST:
     293           0 :         return "EST"sv;
     294          22 :     case AuthDecodingState::AUTH:
     295          22 :         return "AUTH"sv;
     296           5 :     case AuthDecodingState::DATA:
     297           5 :         return "DATA"sv;
     298           1 :     case AuthDecodingState::AUTH_ERROR:
     299           1 :         return "AUTH_ERROR"sv;
     300           0 :     case AuthDecodingState::DONE:
     301           0 :         return "DONE"sv;
     302           0 :     case AuthDecodingState::TIMEOUT:
     303           0 :         return "TIMEOUT"sv;
     304           0 :     case AuthDecodingState::CANCELED:
     305           0 :         return "CANCELED"sv;
     306           0 :     case AuthDecodingState::ERR:
     307             :     default:
     308           0 :         return "ERR"sv;
     309             :     }
     310             : }
     311             : 
     312             : namespace PayloadKey {
     313             : static constexpr auto passwordCorrect = "passwordCorrect"sv;
     314             : static constexpr auto canRetry = "canRetry"sv;
     315             : static constexpr auto accData = "accData"sv;
     316             : static constexpr auto authScheme = "authScheme"sv;
     317             : static constexpr auto password = "password"sv;
     318             : static constexpr auto stateMsg = "stateMsg"sv;
     319             : }
     320             : 
     321             : struct ArchiveAccountManager::AuthMsg
     322             : {
     323             :     uint8_t schemeId {0};
     324             :     std::map<std::string, std::string> payload;
     325          48 :     MSGPACK_DEFINE_MAP(schemeId, payload)
     326             : 
     327          26 :     void set(std::string_view key, std::string_view value) {
     328          26 :         payload.emplace(std::string(key), std::string(value));
     329          26 :     }
     330             : 
     331          60 :     auto find(std::string_view key) const { return payload.find(std::string(key)); }
     332             : 
     333           8 :     auto at(std::string_view key) const { return payload.at(std::string(key)); }
     334             : 
     335          18 :     void logMsg() { JAMI_DEBUG("[LinkDevice]\nLinkDevice::logMsg:\n{}", formatMsg()); }
     336             : 
     337          40 :     std::string formatMsg() {
     338          40 :         std::string logStr = fmt::format("=========\nscheme: {}\n", schemeId);
     339          88 :         for (const auto& [msgKey, msgVal] : payload) {
     340          48 :             logStr += fmt::format(" - {}: {}\n", msgKey, msgVal);
     341             :         }
     342          40 :         logStr += "=========";
     343          40 :         return logStr;
     344           0 :     }
     345             : 
     346           0 :     static AuthMsg timeout() {
     347           0 :         AuthMsg timeoutMsg;
     348           0 :         timeoutMsg.set(PayloadKey::stateMsg, toString(AuthDecodingState::TIMEOUT));
     349           0 :         return timeoutMsg;
     350           0 :     }
     351             : };
     352             : 
     353             : struct ArchiveAccountManager::DeviceAuthInfo : public std::map<std::string, std::string>
     354             : {
     355             :     // Static key definitions
     356             :     static constexpr auto token = "token"sv;
     357             :     static constexpr auto error = "error"sv;
     358             :     static constexpr auto auth_scheme = "auth_scheme"sv;
     359             :     static constexpr auto peer_id = "peer_id"sv;
     360             :     static constexpr auto auth_error = "auth_error"sv;
     361             :     static constexpr auto peer_address = "peer_address"sv;
     362             : 
     363             :     // Add error enum
     364             :     enum class Error { NETWORK, TIMEOUT, AUTH_ERROR, CANCELED, UNKNOWN, NONE };
     365             : 
     366             :     using Map = std::map<std::string, std::string>;
     367             : 
     368          38 :     DeviceAuthInfo() = default;
     369             :     DeviceAuthInfo(const Map& map)
     370             :         : Map(map)
     371             :     {}
     372           9 :     DeviceAuthInfo(Map&& map)
     373           9 :         : Map(std::move(map))
     374           9 :     {}
     375             : 
     376          29 :     void set(std::string_view key, std::string_view value) {
     377          29 :         emplace(std::string(key), std::string(value));
     378          29 :     }
     379             : 
     380           9 :     static DeviceAuthInfo createError(Error err)
     381             :     {
     382           9 :         std::string errStr;
     383           9 :         switch (err) {
     384           1 :         case Error::NETWORK:
     385           1 :             errStr = "network";
     386           1 :             break;
     387           0 :         case Error::TIMEOUT:
     388           0 :             errStr = "timeout";
     389           0 :             break;
     390           2 :         case Error::AUTH_ERROR:
     391           2 :             errStr = "auth_error";
     392           2 :             break;
     393           0 :         case Error::CANCELED:
     394           0 :             errStr = "canceled";
     395           0 :             break;
     396           2 :         case Error::UNKNOWN:
     397           2 :             errStr = "unknown";
     398           2 :             break;
     399           4 :         case Error::NONE:
     400           4 :             errStr = "";
     401           4 :             break;
     402             :         }
     403          27 :         return DeviceAuthInfo {Map {{std::string(error), errStr}}};
     404           9 :     }
     405             : };
     406             : 
     407             : struct ArchiveAccountManager::DeviceContextBase
     408             : {
     409             :     uint64_t opId;
     410             :     AuthDecodingState state {AuthDecodingState::EST};
     411             :     std::string scheme;
     412             :     bool authEnabled {false};
     413             :     bool archiveTransferredWithoutFailure {false};
     414             :     std::string accData;
     415             : 
     416           9 :     DeviceContextBase(uint64_t operationId, AuthDecodingState initialState)
     417           9 :         : opId(operationId)
     418           9 :         , state(initialState)
     419           9 :     {}
     420             : 
     421          36 :     constexpr std::string_view formattedAuthState() const { return toString(state); }
     422             : 
     423          10 :     bool handleTimeoutMessage(const AuthMsg& msg)
     424             :     {
     425          10 :         auto stateMsgIt = msg.find(PayloadKey::stateMsg);
     426          10 :         if (stateMsgIt != msg.payload.end()) {
     427           0 :             if (stateMsgIt->second == toString(AuthDecodingState::TIMEOUT)) {
     428           0 :                 this->state = AuthDecodingState::TIMEOUT;
     429           0 :                 return true;
     430             :             }
     431             :         }
     432          10 :         return false;
     433             :     }
     434             : 
     435          14 :     bool handleCanceledMessage(const AuthMsg& msg)
     436             :     {
     437          14 :         auto stateMsgIt = msg.find(PayloadKey::stateMsg);
     438          14 :         if (stateMsgIt != msg.payload.end()) {
     439           0 :             if (stateMsgIt->second == toString(AuthDecodingState::CANCELED)) {
     440           0 :                 this->state = AuthDecodingState::CANCELED;
     441           0 :                 return true;
     442             :             }
     443             :         }
     444          14 :         return false;
     445             :     }
     446             : 
     447           8 :     DeviceAuthInfo::Error getErrorState() const
     448             :     {
     449           8 :         if (state == AuthDecodingState::AUTH_ERROR) {
     450           2 :             return DeviceAuthInfo::Error::AUTH_ERROR;
     451           6 :         } else if (state == AuthDecodingState::TIMEOUT) {
     452           0 :             return DeviceAuthInfo::Error::TIMEOUT;
     453           6 :         } else if (state == AuthDecodingState::CANCELED) {
     454           0 :             return DeviceAuthInfo::Error::CANCELED;
     455           6 :         } else if (state == AuthDecodingState::ERR) {
     456           2 :             return DeviceAuthInfo::Error::UNKNOWN;
     457           4 :         } else if (archiveTransferredWithoutFailure) {
     458           4 :             return DeviceAuthInfo::Error::NONE;
     459             :         }
     460           0 :         return DeviceAuthInfo::Error::NETWORK;
     461             :     }
     462             : 
     463           0 :     bool isCompleted() const
     464             :     {
     465           0 :         return state == AuthDecodingState::DONE || state == AuthDecodingState::ERR
     466           0 :                || state == AuthDecodingState::AUTH_ERROR || state == AuthDecodingState::TIMEOUT
     467           0 :                || state == AuthDecodingState::CANCELED;
     468             :     }
     469             : };
     470             : 
     471             : struct ArchiveAccountManager::LinkDeviceContext : public DeviceContextBase
     472             : {
     473             :     dht::crypto::Identity tmpId;
     474             :     dhtnet::ConnectionManager tempConnMgr;
     475             :     unsigned numOpenChannels {0};
     476             :     unsigned maxOpenChannels {1};
     477             :     std::shared_ptr<dhtnet::ChannelSocket> channel;
     478           0 :     msgpack::unpacker pac {[](msgpack::type::object_type, std::size_t, void*) { return true; },
     479             :                            nullptr,
     480             :                            512};
     481             :     std::string authScheme {fileutils::ARCHIVE_AUTH_SCHEME_NONE};
     482             :     std::string credentialsFromUser {""};
     483             : 
     484           5 :     LinkDeviceContext(dht::crypto::Identity id)
     485           5 :         : DeviceContextBase(0, AuthDecodingState::HANDSHAKE)
     486           5 :         , tmpId(std::move(id))
     487          10 :         , tempConnMgr(tmpId)
     488           5 :     {}
     489             : };
     490             : 
     491             : struct ArchiveAccountManager::AddDeviceContext : public DeviceContextBase
     492             : {
     493             :     unsigned numTries {0};
     494             :     unsigned maxTries {3};
     495             :     std::shared_ptr<dhtnet::ChannelSocket> channel;
     496             :     std::string_view authScheme;
     497             :     std::string credentials;
     498             : 
     499           4 :     AddDeviceContext(std::shared_ptr<dhtnet::ChannelSocket> c)
     500           4 :         : DeviceContextBase(0, AuthDecodingState::EST)
     501           4 :         , channel(std::move(c))
     502           4 :     {}
     503             : 
     504           0 :     AuthMsg createCanceledMsg() const
     505             :     {
     506           0 :         AuthMsg timeoutMsg;
     507           0 :         timeoutMsg.set(PayloadKey::stateMsg, toString(AuthDecodingState::CANCELED));
     508           0 :         return timeoutMsg;
     509           0 :     }
     510             : };
     511             : 
     512             : bool
     513           7 : ArchiveAccountManager::provideAccountAuthentication(const std::string& key,
     514             :                                                     const std::string& scheme)
     515             : {
     516           7 :     if (scheme != fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD) {
     517           0 :         JAMI_ERROR("[LinkDevice] Unsupported account authentication scheme attempted.");
     518           0 :         return false;
     519             :     }
     520           7 :     auto ctx = authCtx_;
     521           7 :     if (!ctx) {
     522           0 :         JAMI_WARNING("[LinkDevice] No auth context found.");
     523           0 :         return false;
     524             :     }
     525             : 
     526           7 :     if (ctx->linkDevCtx->state != AuthDecodingState::AUTH) {
     527           3 :         JAMI_WARNING("[LinkDevice] Invalid state for providing account authentication.");
     528           1 :         return false;
     529             :     }
     530             : 
     531           6 :     ctx->linkDevCtx->authScheme = scheme;
     532           6 :     ctx->linkDevCtx->credentialsFromUser = key;
     533             :     // After authentication, the next step is to receive the account archive from the exporting device
     534           6 :     ctx->linkDevCtx->state = AuthDecodingState::DATA;
     535           6 :     emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
     536          12 :         ctx->accountId, static_cast<uint8_t>(DeviceAuthState::IN_PROGRESS), DeviceAuthInfo {});
     537             : 
     538           6 :     dht::ThreadPool::io().run([key = std::move(key), scheme, ctx]() mutable {
     539           6 :         AuthMsg toSend;
     540           6 :         toSend.set(PayloadKey::password, std::move(key));
     541           6 :         msgpack::sbuffer buffer(UINT16_MAX);
     542           6 :         toSend.logMsg();
     543           6 :         msgpack::pack(buffer, toSend);
     544           6 :         std::error_code ec;
     545             :         try {
     546           6 :             ctx->linkDevCtx->channel->write(reinterpret_cast<const unsigned char*>(buffer.data()),
     547             :                                             buffer.size(),
     548             :                                             ec);
     549           0 :         } catch (const std::exception& e) {
     550           0 :             JAMI_WARNING("[LinkDevice] Failed to send password over auth ChannelSocket. Channel "
     551             :                          "may be invalid.");
     552           0 :         }
     553           6 :     });
     554             : 
     555           6 :     return true;
     556           7 : }
     557             : 
     558             : struct ArchiveAccountManager::DecodingContext
     559             : {
     560         100 :     msgpack::unpacker pac {[](msgpack::type::object_type, std::size_t, void*) { return true; },
     561             :                            nullptr,
     562             :                            512};
     563             : };
     564             : 
     565             : // link device: newDev: creates a new temporary account on the DHT for establishing a TLS connection
     566             : void
     567           5 : ArchiveAccountManager::startLoadArchiveFromDevice(const std::shared_ptr<AuthContext>& ctx)
     568             : {
     569           5 :     if (authCtx_) {
     570           0 :         JAMI_WARNING("[LinkDevice] Already loading archive from device.");
     571           0 :         ctx->onFailure(AuthError::INVALID_ARGUMENTS, "Already loading archive from device.");
     572           0 :         return;
     573             :     }
     574          15 :     JAMI_DEBUG("[LinkDevice] Starting load archive from device {} {}.",
     575             :                fmt::ptr(this),
     576             :                fmt::ptr(ctx));
     577           5 :     authCtx_ = ctx;
     578             :     // move the account creation to another thread
     579           5 :     dht::ThreadPool::computation().run([ctx, wthis = weak()] {
     580          10 :         auto ca = dht::crypto::generateEcIdentity("Jami Temporary CA");
     581           5 :         if (!ca.first || !ca.second) {
     582           0 :             throw std::runtime_error("[LinkDevice] Can't generate CA for this account.");
     583             :         }
     584             :         // temporary user for bootstrapping p2p connection is created here
     585          10 :         auto user = dht::crypto::generateIdentity("Jami Temporary User", ca, 4096, true);
     586           5 :         if (!user.first || !user.second) {
     587           0 :             throw std::runtime_error("[LinkDevice] Can't generate identity for this account.");
     588             :         }
     589             : 
     590           5 :         auto this_ = wthis.lock();
     591           5 :         if (!this_) {
     592           0 :             JAMI_WARNING("[LinkDevice] Failed to get the ArchiveAccountManager.");
     593           0 :             return;
     594             :         }
     595             : 
     596             :         // establish linkDevCtx
     597          10 :         ctx->linkDevCtx = std::make_shared<LinkDeviceContext>(
     598          15 :             dht::crypto::generateIdentity("Jami Temporary device", user));
     599          15 :         JAMI_LOG("[LinkDevice] Established linkDevCtx. {} {} {}.",
     600             :                  fmt::ptr(this_),
     601             :                  fmt::ptr(ctx),
     602             :                  fmt::ptr(ctx->linkDevCtx));
     603             : 
     604             :         // set up auth channel code and also use it as opId
     605           5 :         auto gen = Manager::instance().getSeededRandomEngine();
     606           5 :         ctx->linkDevCtx->opId = std::uniform_int_distribution<uint64_t>(100000, 999999)(gen);
     607             : #if TARGET_OS_IOS
     608             :         ctx->linkDevCtx->tempConnMgr.oniOSConnected(
     609             :             [&](const std::string& connType, dht::InfoHash peer_h) { return false; });
     610             : #endif
     611           5 :         ctx->linkDevCtx->tempConnMgr.onDhtConnected(ctx->linkDevCtx->tmpId.second->getPublicKey());
     612             : 
     613             :         auto accountScheme = fmt::format("{}{}/{}",
     614             :                                          AUTH_URI_SCHEME,
     615           5 :                                          ctx->linkDevCtx->tmpId.second->getId(),
     616           5 :                                          ctx->linkDevCtx->opId);
     617          15 :         JAMI_LOG("[LinkDevice] auth scheme will be: {}", accountScheme);
     618             : 
     619           5 :         DeviceAuthInfo info;
     620           5 :         info.set(DeviceAuthInfo::token, accountScheme);
     621             : 
     622          10 :         emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
     623           5 :             ctx->accountId, static_cast<uint8_t>(DeviceAuthState::TOKEN_AVAILABLE), info);
     624             : 
     625          10 :         ctx->linkDevCtx->tempConnMgr.onICERequest(
     626           5 :             [wctx = std::weak_ptr(ctx)](const DeviceId& deviceId) {
     627           4 :                 if (auto ctx = wctx.lock()) {
     628           4 :                     emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
     629           4 :                         ctx->accountId,
     630             :                         static_cast<uint8_t>(DeviceAuthState::CONNECTING),
     631           8 :                         DeviceAuthInfo {});
     632           4 :                     return true;
     633           4 :                 }
     634           0 :                 return false;
     635             :             });
     636             : 
     637          10 :         ctx->linkDevCtx->tempConnMgr.onChannelRequest(
     638           5 :             [wthis, ctx](const std::shared_ptr<dht::crypto::Certificate>& cert,
     639             :                          const std::string& name) {
     640           4 :                 std::string_view url(name);
     641           4 :                 if (!starts_with(url, CHANNEL_SCHEME)) {
     642           0 :                     JAMI_WARNING(
     643             :                         "[LinkDevice] Temporary connection manager received invalid scheme: {}",
     644             :                         name);
     645           0 :                     return false;
     646             :                 }
     647           4 :                 auto opStr = url.substr(CHANNEL_SCHEME.size());
     648           4 :                 auto parsedOpId = jami::to_int<uint64_t>(opStr);
     649             : 
     650           4 :                 if (ctx->linkDevCtx->opId == parsedOpId
     651           4 :                     && ctx->linkDevCtx->numOpenChannels < ctx->linkDevCtx->maxOpenChannels) {
     652           4 :                     ctx->linkDevCtx->numOpenChannels++;
     653          12 :                     JAMI_DEBUG("[LinkDevice] Opening channel ({}/{}): {}",
     654             :                                ctx->linkDevCtx->numOpenChannels,
     655             :                                ctx->linkDevCtx->maxOpenChannels,
     656             :                                name);
     657           4 :                     return true;
     658             :                 }
     659           0 :                 return false;
     660             :             });
     661             : 
     662          10 :         ctx->linkDevCtx->tempConnMgr.onConnectionReady([ctx,
     663             :                                                         accountScheme,
     664           5 :                                                         wthis](const DeviceId& deviceId,
     665             :                                                                const std::string& name,
     666             :                                                                std::shared_ptr<dhtnet::ChannelSocket> socket) {
     667           4 :             if (!socket) {
     668           0 :                 JAMI_WARNING("[LinkDevice] Temporary connection manager received invalid socket.");
     669           0 :                 if (ctx->timeout)
     670           0 :                     ctx->timeout->cancel();
     671           0 :                 ctx->timeout.reset();
     672           0 :                 ctx->linkDevCtx->numOpenChannels--;
     673           0 :                 if (auto sthis = wthis.lock())
     674           0 :                     sthis->authCtx_.reset();
     675           0 :                 ctx->linkDevCtx->state = AuthDecodingState::ERR;
     676           0 :                 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
     677           0 :                     ctx->accountId,
     678             :                     static_cast<uint8_t>(DeviceAuthState::DONE),
     679           0 :                     DeviceAuthInfo::createError(DeviceAuthInfo::Error::NETWORK));
     680           0 :                 return;
     681             :             }
     682           4 :             ctx->linkDevCtx->channel = socket;
     683             : 
     684           4 :             ctx->timeout = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext());
     685           4 :             ctx->timeout->expires_after(OP_TIMEOUT);
     686           4 :             ctx->timeout->async_wait([c = std::weak_ptr(ctx), socket](const std::error_code& ec) {
     687           4 :                 if (ec) {
     688           4 :                     return;
     689             :                 }
     690           0 :                 if (auto ctx = c.lock()) {
     691           0 :                     if (!ctx->linkDevCtx->isCompleted()) {
     692           0 :                         ctx->linkDevCtx->state = AuthDecodingState::TIMEOUT;
     693           0 :                         JAMI_WARNING("[LinkDevice] timeout: {}", socket->name());
     694             : 
     695             :                         // Create and send timeout message
     696           0 :                         msgpack::sbuffer buffer(UINT16_MAX);
     697           0 :                         msgpack::pack(buffer, AuthMsg::timeout());
     698           0 :                         std::error_code ec;
     699           0 :                         socket->write(reinterpret_cast<const unsigned char*>(buffer.data()),
     700             :                                       buffer.size(),
     701             :                                       ec);
     702           0 :                         socket->shutdown();
     703           0 :                     }
     704           0 :                 }
     705             :             });
     706             : 
     707           4 :             socket->onShutdown([ctx, name, wthis]() {
     708          12 :                 JAMI_WARNING("[LinkDevice] Temporary connection manager closing socket: {}", name);
     709           4 :                 if (ctx->timeout)
     710           4 :                     ctx->timeout->cancel();
     711           4 :                 ctx->timeout.reset();
     712           4 :                 ctx->linkDevCtx->numOpenChannels--;
     713           4 :                 ctx->linkDevCtx->channel.reset();
     714           4 :                 if (auto sthis = wthis.lock())
     715           4 :                     sthis->authCtx_.reset();
     716             : 
     717           4 :                 DeviceAuthInfo::Error error = ctx->linkDevCtx->getErrorState();
     718           4 :                 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
     719           4 :                     ctx->accountId,
     720             :                     static_cast<uint8_t>(DeviceAuthState::DONE),
     721           8 :                     DeviceAuthInfo::createError(error));
     722           4 :             });
     723             : 
     724           8 :             socket->setOnRecv([ctx,
     725             :                                decodingCtx = std::make_shared<DecodingContext>(),
     726           4 :                                wthis](const uint8_t* buf, size_t len) {
     727          14 :                 if (!buf) {
     728           0 :                     return len;
     729             :                 }
     730             : 
     731          14 :                 decodingCtx->pac.reserve_buffer(len);
     732          14 :                 std::copy_n(buf, len, decodingCtx->pac.buffer());
     733          14 :                 decodingCtx->pac.buffer_consumed(len);
     734          14 :                 AuthMsg toRecv;
     735             :                 try {
     736          14 :                     msgpack::object_handle oh;
     737          14 :                     if (decodingCtx->pac.next(oh)) {
     738          42 :                         JAMI_DEBUG("[LinkDevice] NEW: Unpacking message.");
     739          14 :                         oh.get().convert(toRecv);
     740             :                     } else {
     741           0 :                         return len;
     742             :                     }
     743          14 :                 } catch (const std::exception& e) {
     744           0 :                     ctx->linkDevCtx->state = AuthDecodingState::ERR;
     745           0 :                     JAMI_ERROR("[LinkDevice] Error unpacking message from source device: {}", e.what());
     746           0 :                     return len;
     747           0 :                 }
     748             : 
     749          42 :                 JAMI_DEBUG("[LinkDevice] NEW: Successfully unpacked message from source\n{}",
     750             :                            toRecv.formatMsg());
     751          42 :                 JAMI_DEBUG("[LinkDevice] NEW: State is {}:{}",
     752             :                            ctx->linkDevCtx->scheme,
     753             :                            ctx->linkDevCtx->formattedAuthState());
     754             : 
     755             :                 // check if scheme is supported
     756          14 :                 if (toRecv.schemeId != 0) {
     757           0 :                     JAMI_WARNING("[LinkDevice] NEW: Unsupported scheme received from source");
     758           0 :                     ctx->linkDevCtx->state = AuthDecodingState::ERR;
     759           0 :                     return len;
     760             :                 }
     761             : 
     762             :                 // handle the protocol logic
     763          14 :                 if (ctx->linkDevCtx->handleCanceledMessage(toRecv)) {
     764             :                     // import canceled. Will be handeled onShutdown
     765           0 :                     return len;
     766             :                 }
     767          14 :                 AuthMsg toSend;
     768          14 :                 bool shouldShutdown = false;
     769          14 :                 auto accDataIt = toRecv.find(PayloadKey::accData);
     770          14 :                 bool shouldLoadArchive = accDataIt != toRecv.payload.end();
     771             : 
     772          14 :                 if (ctx->linkDevCtx->state == AuthDecodingState::HANDSHAKE) {
     773           4 :                     auto peerCert = ctx->linkDevCtx->channel->peerCertificate();
     774           4 :                     auto authScheme = toRecv.at(PayloadKey::authScheme);
     775           4 :                     ctx->linkDevCtx->authEnabled = authScheme
     776           4 :                                                    != fileutils::ARCHIVE_AUTH_SCHEME_NONE;
     777             : 
     778          12 :                     JAMI_DEBUG("[LinkDevice] NEW: Auth scheme from payload is '{}'", authScheme);
     779           4 :                     ctx->linkDevCtx->state = AuthDecodingState::AUTH;
     780           4 :                     DeviceAuthInfo info;
     781           4 :                     info.set(DeviceAuthInfo::auth_scheme, authScheme);
     782           4 :                     info.set(DeviceAuthInfo::peer_id, peerCert->issuer->getId().toString());
     783           8 :                     emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
     784           4 :                         ctx->accountId, static_cast<uint8_t>(DeviceAuthState::AUTHENTICATING), info);
     785          14 :                 } else if (ctx->linkDevCtx->state == AuthDecodingState::DATA) {
     786           6 :                     auto passwordCorrectIt = toRecv.find(PayloadKey::passwordCorrect);
     787           6 :                     auto canRetry = toRecv.find(PayloadKey::canRetry);
     788             : 
     789             :                     // If we've reached the maximum number of retry attempts
     790           6 :                     if (canRetry != toRecv.payload.end() && canRetry->second == "false") {
     791           3 :                         JAMI_DEBUG("[LinkDevice] Authentication failed: maximum retry attempts "
     792             :                                    "reached");
     793           1 :                         ctx->linkDevCtx->state = AuthDecodingState::AUTH_ERROR;
     794           5 :                         return len;
     795             :                     }
     796             : 
     797             :                     // If the password was incorrect but we can still retry
     798           5 :                     if (passwordCorrectIt != toRecv.payload.end()
     799           5 :                         && passwordCorrectIt->second == "false") {
     800           4 :                         ctx->linkDevCtx->state = AuthDecodingState::AUTH;
     801             : 
     802          12 :                         JAMI_DEBUG("[LinkDevice] NEW: Password incorrect.");
     803           4 :                         auto peerCert = ctx->linkDevCtx->channel->peerCertificate();
     804           4 :                         auto peer_id = peerCert->issuer->getId().toString();
     805             :                         // We received a password incorrect response, so we know we're using
     806             :                         // password authentication
     807           4 :                         auto authScheme = fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD;
     808             : 
     809           4 :                         DeviceAuthInfo info;
     810           4 :                         info.set(DeviceAuthInfo::auth_scheme, authScheme);
     811           4 :                         info.set(DeviceAuthInfo::peer_id, peer_id);
     812           4 :                         info.set(DeviceAuthInfo::auth_error, "invalid_credentials");
     813             : 
     814           8 :                         emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
     815           4 :                             ctx->accountId,
     816             :                             static_cast<uint8_t>(DeviceAuthState::AUTHENTICATING),
     817             :                             info);
     818           4 :                         return len;
     819           4 :                     }
     820             : 
     821           1 :                     if (!shouldLoadArchive) {
     822           0 :                         JAMI_DEBUG("[LinkDevice] NEW: no archive received.");
     823             :                         // at this point we suppose to have archive. If not, export failed.
     824             :                         // Update state and signal will be handeled onShutdown
     825           0 :                         ctx->linkDevCtx->state = AuthDecodingState::ERR;
     826           0 :                         shouldShutdown = true;
     827             :                     }
     828             :                 }
     829             : 
     830             :                 // check if an account archive is ready to be loaded
     831           9 :                 if (shouldLoadArchive) {
     832           3 :                     emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
     833           3 :                         ctx->accountId,
     834             :                         static_cast<uint8_t>(DeviceAuthState::IN_PROGRESS),
     835           6 :                         DeviceAuthInfo {});
     836             :                     try {
     837           3 :                         auto archive = AccountArchive(std::string_view(accDataIt->second));
     838           3 :                         if (auto this_ = wthis.lock()) {
     839           9 :                             JAMI_DEBUG("[LinkDevice] NEW: Reading archive from peer.");
     840           3 :                             this_->onArchiveLoaded(*ctx, std::move(archive), true);
     841           3 :                             JAMI_DEBUG("[LinkDevice] NEW: Successfully loaded archive.");
     842           1 :                             ctx->linkDevCtx->archiveTransferredWithoutFailure = true;
     843             :                         } else {
     844           0 :                             ctx->linkDevCtx->archiveTransferredWithoutFailure = false;
     845           0 :                             JAMI_ERROR("[LinkDevice] NEW: Failed to load account because of "
     846             :                                        "null ArchiveAccountManager!");
     847           3 :                         }
     848           5 :                     } catch (const std::exception& e) {
     849           2 :                         ctx->linkDevCtx->state = AuthDecodingState::ERR;
     850           2 :                         ctx->linkDevCtx->archiveTransferredWithoutFailure = false;
     851           6 :                         JAMI_WARNING("[LinkDevice] NEW: Error reading archive.");
     852           2 :                     }
     853           3 :                     shouldShutdown = true;
     854             :                 }
     855             : 
     856           9 :                 if (shouldShutdown) {
     857           3 :                     ctx->linkDevCtx->channel->shutdown();
     858             :                 }
     859             : 
     860           9 :                 return len;
     861          14 :             }); // !onConnectionReady // TODO emit AuthStateChanged+"connection ready" signal
     862             : 
     863           4 :             ctx->linkDevCtx->state = AuthDecodingState::HANDSHAKE;
     864             :             // send first message to establish scheme
     865           4 :             AuthMsg toSend;
     866           4 :             toSend.schemeId = 0; // set latest scheme here
     867          12 :             JAMI_DEBUG("[LinkDevice] NEW: Packing first message for SOURCE.\nCurrent state is: "
     868             :                        "\n\tauth "
     869             :                        "state = {}:{}",
     870             :                        toSend.schemeId,
     871             :                        ctx->linkDevCtx->formattedAuthState());
     872           4 :             msgpack::sbuffer buffer(UINT16_MAX);
     873           4 :             msgpack::pack(buffer, toSend);
     874           4 :             std::error_code ec;
     875           4 :             ctx->linkDevCtx->channel->write(reinterpret_cast<const unsigned char*>(buffer.data()),
     876             :                                             buffer.size(),
     877             :                                             ec);
     878             : 
     879          12 :             JAMI_LOG("[LinkDevice {}] Generated temporary account.",
     880             :                      ctx->linkDevCtx->tmpId.second->getId());
     881           4 :         });
     882           5 :     });
     883          15 :     JAMI_DEBUG("[LinkDevice] Starting load archive from device END {} {}.",
     884             :                fmt::ptr(this),
     885             :                fmt::ptr(ctx));
     886             : }
     887             : 
     888             : int32_t
     889           5 : ArchiveAccountManager::addDevice(const std::string& uriProvided,
     890             :                                  std::string_view auth_scheme,
     891             :                                  AuthChannelHandler* channelHandler)
     892             : {
     893           5 :     if (authCtx_) {
     894           0 :         JAMI_WARNING("[LinkDevice] addDevice: auth context already exists.");
     895           0 :         return static_cast<int32_t>(AccountManager::AddDeviceError::ALREADY_LINKING);
     896             :     }
     897          15 :     JAMI_LOG("[LinkDevice] ArchiveAccountManager::addDevice({}, {})", accountId_, uriProvided);
     898             :     try {
     899           5 :         std::string_view url(uriProvided);
     900           5 :         if (!starts_with(url, AUTH_URI_SCHEME)) {
     901           0 :             JAMI_ERROR("[LinkDevice] Invalid uri provided: {}", uriProvided);
     902           0 :             return static_cast<int32_t>(AccountManager::AddDeviceError::INVALID_URI);
     903             :         }
     904           5 :         auto peerTempAcc = url.substr(AUTH_URI_SCHEME.length(), 40);
     905           5 :         auto peerCodeS = url.substr(AUTH_URI_SCHEME.length() + peerTempAcc.length() + 1, 6);
     906          15 :         JAMI_LOG("[LinkDevice] ======\n * tempAcc =  {}\n * code = {}", peerTempAcc, peerCodeS);
     907             : 
     908           5 :         auto gen = Manager::instance().getSeededRandomEngine();
     909           5 :         std::uniform_int_distribution<int32_t> dist(1, INT32_MAX);
     910           5 :         auto token = dist(gen);
     911          15 :         JAMI_WARNING("[LinkDevice] SOURCE: Creating auth context, token: {}.", token);
     912           5 :         auto ctx = std::make_shared<AuthContext>();
     913           5 :         ctx->accountId = accountId_;
     914           5 :         ctx->token = token;
     915           5 :         ctx->credentials = std::make_unique<ArchiveAccountCredentials>();
     916           5 :         authCtx_ = ctx;
     917             : 
     918          20 :         channelHandler->connect(
     919           0 :             dht::InfoHash(peerTempAcc),
     920          10 :             fmt::format("{}{}", CHANNEL_SCHEME, peerCodeS),
     921           5 :             [wthis = weak(), auth_scheme, ctx, accountId=accountId_](std::shared_ptr<dhtnet::ChannelSocket> socket,
     922             :                                      const dht::InfoHash& infoHash) {
     923           5 :                 auto this_ = wthis.lock();
     924           5 :                 if (!socket || !this_) {
     925           3 :                     JAMI_WARNING("[LinkDevice] Invalid socket event while AccountManager connecting.");
     926           1 :                     if (this_)
     927           1 :                         this_->authCtx_.reset();
     928           1 :                     emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(
     929           1 :                         accountId,
     930           1 :                         ctx->token,
     931             :                         static_cast<uint8_t>(DeviceAuthState::DONE),
     932           2 :                         DeviceAuthInfo::createError(DeviceAuthInfo::Error::NETWORK));
     933             :                 } else {
     934           4 :                     if (!this_->doAddDevice(auth_scheme, ctx, socket))
     935           0 :                         emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(
     936           0 :                             accountId,
     937           0 :                             ctx->token,
     938             :                             static_cast<uint8_t>(DeviceAuthState::DONE),
     939           0 :                             DeviceAuthInfo::createError(DeviceAuthInfo::Error::UNKNOWN));
     940             :                 }
     941           5 :             });
     942           5 :         runOnMainThread([token, id = accountId_] {
     943           5 :             emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(
     944          10 :                 id, token, static_cast<uint8_t>(DeviceAuthState::CONNECTING), DeviceAuthInfo {});
     945           5 :         });
     946           5 :         return token;
     947           5 :     } catch (const std::exception& e) {
     948           0 :         JAMI_ERROR("[LinkDevice] Parsing uri failed: {}", uriProvided);
     949           0 :         return static_cast<int32_t>(AccountManager::AddDeviceError::GENERIC);
     950           0 :     }
     951             : }
     952             : 
     953             : bool
     954           4 : ArchiveAccountManager::doAddDevice(std::string_view scheme,
     955             :                                    const std::shared_ptr<AuthContext>& ctx,
     956             :                                    const std::shared_ptr<dhtnet::ChannelSocket>& channel)
     957             : {
     958           4 :     if (ctx->canceled) {
     959           0 :         JAMI_WARNING("[LinkDevice] SOURCE: addDevice canceled.");
     960           0 :         channel->shutdown();
     961           0 :         return false;
     962             :     }
     963          12 :     JAMI_DEBUG("[LinkDevice] Setting up addDevice logic on SOURCE device.");
     964          12 :     JAMI_DEBUG("[LinkDevice] SOURCE: Creating addDeviceCtx.");
     965           4 :     ctx->addDeviceCtx = std::make_unique<AddDeviceContext>(channel);
     966           4 :     ctx->addDeviceCtx->authScheme = scheme;
     967           4 :     ctx->addDeviceCtx->state = AuthDecodingState::HANDSHAKE;
     968             : 
     969           4 :     ctx->timeout = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext());
     970           4 :     ctx->timeout->expires_after(OP_TIMEOUT);
     971           4 :     ctx->timeout->async_wait(
     972           4 :         [wthis = weak(), wctx = std::weak_ptr(ctx)](const std::error_code& ec) {
     973           4 :             if (ec) {
     974           4 :                 return;
     975             :             }
     976           0 :             if (auto ctx = wctx.lock()) {
     977           0 :                 if (!ctx->addDeviceCtx->isCompleted()) {
     978           0 :                     if (auto this_ = wthis.lock()) {
     979           0 :                         ctx->addDeviceCtx->state = AuthDecodingState::TIMEOUT;
     980           0 :                         JAMI_WARNING("[LinkDevice] Timeout for addDevice.");
     981             : 
     982             :                         // Create and send timeout message
     983           0 :                         msgpack::sbuffer buffer(UINT16_MAX);
     984           0 :                         msgpack::pack(buffer, AuthMsg::timeout());
     985           0 :                         std::error_code ec;
     986           0 :                         ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(
     987           0 :                                                               buffer.data()),
     988             :                                                           buffer.size(),
     989             :                                                           ec);
     990           0 :                         ctx->addDeviceCtx->channel->shutdown();
     991           0 :                     }
     992             :                 }
     993           0 :             }
     994             :         });
     995             : 
     996          12 :     JAMI_DEBUG("[LinkDevice] SOURCE: Creating callbacks.");
     997           4 :     channel->onShutdown([ctx, w = weak()]() {
     998          12 :         JAMI_DEBUG("[LinkDevice] SOURCE: Shutdown with state {}... xfer {}uccessful",
     999             :                    ctx->addDeviceCtx->formattedAuthState(),
    1000             :                    ctx->addDeviceCtx->archiveTransferredWithoutFailure ? "s" : "uns");
    1001             :         // check if the archive was successfully loaded and emitSignal
    1002           4 :         if (ctx->timeout)
    1003           4 :             ctx->timeout->cancel();
    1004           4 :         ctx->timeout.reset();
    1005             : 
    1006           4 :         if (auto this_ = w.lock()) {
    1007           4 :             this_->authCtx_.reset();
    1008           4 :         }
    1009             : 
    1010           4 :         DeviceAuthInfo::Error error = ctx->addDeviceCtx->getErrorState();
    1011           4 :         emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(ctx->accountId,
    1012           4 :                                                                         ctx->token,
    1013             :                                                                         static_cast<uint8_t>(
    1014             :                                                                             DeviceAuthState::DONE),
    1015           8 :                                                                         DeviceAuthInfo::createError(
    1016             :                                                                             error));
    1017           4 :     });
    1018             : 
    1019             :     // for now we only have one valid protocol (version is AuthMsg::scheme = 0) but can later
    1020             :     // add in more schemes inside this callback function
    1021          12 :     JAMI_DEBUG("[LinkDevice] Setting up receiving logic callback.");
    1022           4 :     channel->setOnRecv([ctx,
    1023             :                         wthis = weak(),
    1024             :                         decodeCtx = std::make_shared<ArchiveAccountManager::DecodingContext>()](
    1025             :                            const uint8_t* buf, size_t len) {
    1026          30 :         JAMI_DEBUG("[LinkDevice] Setting up receiver callback for communication logic on SOURCE "
    1027             :                    "device.");
    1028             :         // when archive is sent to newDev we will get back a success or fail response before the
    1029             :         // connection closes and we need to handle this and pass it to the shutdown callback
    1030          10 :         auto this_ = wthis.lock();
    1031          10 :         if (!this_) {
    1032           0 :             JAMI_ERROR("[LinkDevice] Invalid state for ArchiveAccountManager.");
    1033           0 :             return (size_t) 0;
    1034             :         }
    1035             : 
    1036          10 :         if (!buf) {
    1037           0 :             JAMI_ERROR("[LinkDevice] Invalid buffer.");
    1038           0 :             return (size_t) 0;
    1039             :         }
    1040             : 
    1041          10 :         if (ctx->canceled || ctx->addDeviceCtx->state == AuthDecodingState::ERR) {
    1042           0 :             JAMI_ERROR("[LinkDevice] Error.");
    1043           0 :             return (size_t) 0;
    1044             :         }
    1045             : 
    1046          10 :         decodeCtx->pac.reserve_buffer(len);
    1047          10 :         std::copy_n(buf, len, decodeCtx->pac.buffer());
    1048          10 :         decodeCtx->pac.buffer_consumed(len);
    1049             : 
    1050             :         // handle unpacking the data from the peer
    1051          30 :         JAMI_DEBUG("[LinkDevice] SOURCE: addDevice: setOnRecv: handling msg from NEW");
    1052          10 :         msgpack::object_handle oh;
    1053          10 :         AuthMsg toRecv;
    1054             :         try {
    1055          10 :             if (decodeCtx->pac.next(oh)) {
    1056          10 :                 oh.get().convert(toRecv);
    1057          30 :                 JAMI_DEBUG("[LinkDevice] SOURCE: Successfully unpacked message from NEW "
    1058             :                            "(NEW->SOURCE)\n{}",
    1059             :                            toRecv.formatMsg());
    1060             :             } else {
    1061           0 :                 return len;
    1062             :             }
    1063           0 :         } catch (const std::exception& e) {
    1064             :             // set the generic error state in the context
    1065           0 :             ctx->addDeviceCtx->state = AuthDecodingState::ERR;
    1066           0 :             JAMI_ERROR("[LinkDevice] error unpacking message from new device: {}", e.what()); // also warn in logs
    1067           0 :         }
    1068             : 
    1069          30 :         JAMI_DEBUG("[LinkDevice] SOURCE: State is '{}'", ctx->addDeviceCtx->formattedAuthState());
    1070             : 
    1071             :         // It's possible to start handling different protocol scheme numbers here
    1072             :         // one possibility is for multi-account xfer in the future
    1073             :         // validate the scheme
    1074          10 :         if (toRecv.schemeId != 0) {
    1075           0 :             ctx->addDeviceCtx->state = AuthDecodingState::ERR;
    1076           0 :             JAMI_WARNING("[LinkDevice] Unsupported scheme received from a connection.");
    1077             :         }
    1078             : 
    1079          10 :         if (ctx->addDeviceCtx->state == AuthDecodingState::ERR
    1080          10 :             || ctx->addDeviceCtx->state == AuthDecodingState::AUTH_ERROR) {
    1081           0 :             JAMI_WARNING("[LinkDevice] Undefined behavior encountered during a link auth session.");
    1082           0 :             ctx->addDeviceCtx->channel->shutdown();
    1083             :         }
    1084             :         // Check for timeout message
    1085          10 :         if (ctx->addDeviceCtx->handleTimeoutMessage(toRecv)) {
    1086           0 :             return len;
    1087             :         }
    1088          10 :         AuthMsg toSend;
    1089          10 :         bool shouldSendMsg = false;
    1090          10 :         bool shouldShutdown = false;
    1091          10 :         bool shouldSendArchive = false;
    1092             : 
    1093             :         // we expect to be receiving credentials in this state and we know the archive is encrypted
    1094          10 :         if (ctx->addDeviceCtx->state == AuthDecodingState::AUTH) {
    1095             :             // receive the incoming password, check if the password is right, and send back the
    1096             :             // archive if it is correct
    1097          30 :             JAMI_DEBUG("[LinkDevice] SOURCE: addDevice: setOnRecv: verifying sent "
    1098             :                        "credentials from NEW");
    1099          10 :             shouldSendMsg = true;
    1100          10 :             const auto& passwordIt = toRecv.find(PayloadKey::password);
    1101          10 :             if (passwordIt != toRecv.payload.end()) {
    1102             :                 // try and decompress archive for xfer
    1103             :                 try {
    1104          18 :                     JAMI_DEBUG("[LinkDevice] Injecting account archive into outbound message.");
    1105           3 :                     ctx->addDeviceCtx->accData
    1106             :                         = this_
    1107           6 :                               ->readArchive(fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD,
    1108           6 :                                             passwordIt->second)
    1109           6 :                               .serialize();
    1110           3 :                     shouldSendArchive = true;
    1111           9 :                     JAMI_DEBUG("[LinkDevice] Sending account archive.");
    1112           3 :                 } catch (const std::exception& e) {
    1113           9 :                     JAMI_DEBUG("[LinkDevice] Finished reading archive: FAILURE: {}", e.what());
    1114           3 :                     shouldSendArchive = false;
    1115           3 :                 }
    1116             :             }
    1117          10 :             if (!shouldSendArchive) {
    1118             :                 // pass is not valid
    1119           7 :                 ctx->addDeviceCtx->numTries++;
    1120           7 :                 if (ctx->addDeviceCtx->numTries < ctx->addDeviceCtx->maxTries) {
    1121             :                     // can retry auth
    1122          18 :                     JAMI_DEBUG("[LinkDevice] Incorrect password received. "
    1123             :                                "Attempt {} out of {}.",
    1124             :                                ctx->addDeviceCtx->numTries,
    1125             :                                ctx->addDeviceCtx->maxTries);
    1126           6 :                     toSend.set(PayloadKey::passwordCorrect, "false");
    1127           6 :                     toSend.set(PayloadKey::canRetry, "true");
    1128             :                 } else {
    1129             :                     // cannot retry auth
    1130           3 :                     JAMI_WARNING("[LinkDevice] Incorrect password received, maximum attempts reached.");
    1131           1 :                     toSend.set(PayloadKey::canRetry, "false");
    1132           1 :                     ctx->addDeviceCtx->state = AuthDecodingState::AUTH_ERROR;
    1133           1 :                     shouldShutdown = true;
    1134             :                 }
    1135             :             }
    1136             :         }
    1137             : 
    1138          10 :         if (shouldSendArchive) {
    1139           9 :             JAMI_DEBUG("[LinkDevice] SOURCE: Archive in message has encryption scheme '{}'",
    1140             :                        ctx->addDeviceCtx->authScheme);
    1141           3 :             emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(
    1142           3 :                 ctx->accountId,
    1143           3 :                 ctx->token,
    1144             :                 static_cast<uint8_t>(DeviceAuthState::IN_PROGRESS),
    1145           6 :                 DeviceAuthInfo {});
    1146           3 :             shouldShutdown = true;
    1147           3 :             shouldSendMsg = true;
    1148           3 :             ctx->addDeviceCtx->archiveTransferredWithoutFailure = true;
    1149           3 :             toSend.set(PayloadKey::accData, ctx->addDeviceCtx->accData);
    1150             :         }
    1151          10 :         if (shouldSendMsg) {
    1152          30 :             JAMI_DEBUG("[LinkDevice] SOURCE: Sending msg to NEW:\n{}", toSend.formatMsg());
    1153          10 :             msgpack::sbuffer buffer(UINT16_MAX);
    1154          10 :             msgpack::pack(buffer, toSend);
    1155          10 :             std::error_code ec;
    1156          10 :             ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(buffer.data()),
    1157             :                                               buffer.size(),
    1158             :                                               ec);
    1159          10 :         }
    1160             : 
    1161          10 :         if (shouldShutdown) {
    1162           4 :             ctx->addDeviceCtx->channel->shutdown();
    1163             :         }
    1164             : 
    1165          10 :         return len;
    1166          10 :     }); // !channel onRecv closure
    1167             : 
    1168           4 :     if (ctx->addDeviceCtx->state == AuthDecodingState::HANDSHAKE) {
    1169           4 :         ctx->addDeviceCtx->state = AuthDecodingState::EST;
    1170           4 :         DeviceAuthInfo info;
    1171           4 :         info.set(DeviceAuthInfo::peer_address, channel->getRemoteAddress().toString(true));
    1172           8 :         emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(
    1173           4 :             ctx->accountId, ctx->token, static_cast<uint8_t>(DeviceAuthState::AUTHENTICATING), info);
    1174           4 :     }
    1175             : 
    1176           4 :     return true;
    1177             : }
    1178             : 
    1179             : bool
    1180           0 : ArchiveAccountManager::cancelAddDevice(uint32_t token)
    1181             : {
    1182           0 :     if (auto ctx = authCtx_) {
    1183           0 :         if (ctx->token == token) {
    1184           0 :             ctx->canceled = true;
    1185           0 :             if (ctx->addDeviceCtx) {
    1186           0 :                 ctx->addDeviceCtx->state = AuthDecodingState::CANCELED;
    1187           0 :                 if (ctx->addDeviceCtx->channel) {
    1188             :                     // Create and send canceled message
    1189           0 :                     auto canceledMsg = ctx->addDeviceCtx->createCanceledMsg();
    1190           0 :                     msgpack::sbuffer buffer(UINT16_MAX);
    1191           0 :                     msgpack::pack(buffer, canceledMsg);
    1192           0 :                     std::error_code ec;
    1193           0 :                     ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(
    1194           0 :                                                           buffer.data()),
    1195             :                                                       buffer.size(),
    1196             :                                                       ec);
    1197           0 :                     ctx->addDeviceCtx->channel->shutdown();
    1198           0 :                 }
    1199             :             }
    1200           0 :             if (ctx->onFailure)
    1201           0 :                 ctx->onFailure(AuthError::UNKNOWN, "");
    1202           0 :             authCtx_.reset();
    1203           0 :             return true;
    1204             :         }
    1205           0 :     }
    1206           0 :     return false;
    1207             : }
    1208             : 
    1209             : bool
    1210           4 : ArchiveAccountManager::confirmAddDevice(uint32_t token)
    1211             : {
    1212           4 :     if (auto ctx = authCtx_) {
    1213           8 :         if (ctx->token == token && ctx->addDeviceCtx
    1214           8 :             && ctx->addDeviceCtx->state == AuthDecodingState::EST) {
    1215           4 :             dht::ThreadPool::io().run([ctx] {
    1216           4 :                 ctx->addDeviceCtx->state = AuthDecodingState::AUTH;
    1217           4 :                 AuthMsg toSend;
    1218          12 :                 JAMI_DEBUG("[LinkDevice] SOURCE: Packing first message for NEW and switching to "
    1219             :                            "state: {}",
    1220             :                            ctx->addDeviceCtx->formattedAuthState());
    1221           4 :                 toSend.set(PayloadKey::authScheme, ctx->addDeviceCtx->authScheme);
    1222           4 :                 msgpack::sbuffer buffer(UINT16_MAX);
    1223           4 :                 msgpack::pack(buffer, toSend);
    1224           4 :                 std::error_code ec;
    1225           8 :                 ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(
    1226           4 :                                                       buffer.data()),
    1227             :                                                   buffer.size(),
    1228             :                                                   ec);
    1229           4 :             });
    1230           4 :             return true;
    1231             :         }
    1232           4 :     }
    1233           0 :     return false;
    1234             : }
    1235             : 
    1236             : void
    1237           2 : ArchiveAccountManager::migrateAccount(AuthContext& ctx)
    1238             : {
    1239           2 :     JAMI_WARN("[Auth] Account migration needed");
    1240           2 :     AccountArchive archive;
    1241             :     try {
    1242           2 :         archive = readArchive(ctx.credentials->password_scheme, ctx.credentials->password);
    1243           0 :     } catch (...) {
    1244           0 :         JAMI_DBG("[Auth] Unable to load archive");
    1245           0 :         ctx.onFailure(AuthError::INVALID_ARGUMENTS, "");
    1246           0 :         return;
    1247           0 :     }
    1248             : 
    1249           2 :     updateArchive(archive);
    1250             : 
    1251           2 :     if (updateCertificates(archive, ctx.credentials->updateIdentity)) {
    1252             :         // because updateCertificates already regenerate a device, we do not need to
    1253             :         // regenerate one in onArchiveLoaded
    1254           2 :         onArchiveLoaded(ctx, std::move(archive), false);
    1255             :     } else {
    1256           0 :         ctx.onFailure(AuthError::UNKNOWN, "");
    1257             :     }
    1258           2 : }
    1259             : 
    1260             : void
    1261         632 : ArchiveAccountManager::onArchiveLoaded(AuthContext& ctx, AccountArchive&& a, bool isLinkDevProtocol)
    1262             : {
    1263        1264 :     auto ethAccount = dev::KeyPair(dev::Secret(a.eth_key)).address().hex();
    1264         632 :     dhtnet::fileutils::check_dir(path_, 0700);
    1265             : 
    1266         632 :     if (isLinkDevProtocol) {
    1267           6 :         a.config[libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD] =
    1268           6 :             ctx.linkDevCtx->authScheme.empty() ? FALSE_STR : TRUE_STR;
    1269             : 
    1270           3 :         a.save(fileutils::getFullPath(path_, archivePath_),
    1271           3 :                ctx.linkDevCtx->authScheme,
    1272           3 :                ctx.linkDevCtx->credentialsFromUser);
    1273             :     } else {
    1274        1258 :         a.config[libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD] =
    1275        1258 :             ctx.credentials->password_scheme.empty() ? FALSE_STR : TRUE_STR;
    1276             : 
    1277         629 :         a.save(fileutils::getFullPath(path_, archivePath_),
    1278        1258 :                ctx.credentials ? ctx.credentials->password_scheme : "",
    1279        1258 :                ctx.credentials ? ctx.credentials->password : "");
    1280             :     }
    1281             : 
    1282         632 :     if (not a.id.second->isCA()) {
    1283           0 :         JAMI_ERROR("[Account {}] [Auth] Attempting to sign a certificate with a non-CA.",
    1284             :                    accountId_);
    1285             :     }
    1286             : 
    1287         632 :     std::shared_ptr<dht::crypto::Certificate> deviceCertificate;
    1288         632 :     std::unique_ptr<ContactList> contacts;
    1289         632 :     auto usePreviousIdentity = false;
    1290             :     // If updateIdentity got a valid certificate, there is no need for a new cert
    1291         632 :     if (auto oldId = ctx.credentials->updateIdentity.second) {
    1292           4 :         contacts = std::make_unique<ContactList>(ctx.accountId, oldId, path_, onChange_);
    1293           4 :         if (contacts->isValidAccountDevice(*oldId) && ctx.credentials->updateIdentity.first) {
    1294           3 :             deviceCertificate = oldId;
    1295           3 :             usePreviousIdentity = true;
    1296           9 :             JAMI_WARNING("[Account {}] [Auth] Using previously generated device certificate {}",
    1297             :                          accountId_,
    1298             :                          deviceCertificate->getLongId());
    1299             :         } else {
    1300           1 :             contacts.reset();
    1301             :         }
    1302         632 :     }
    1303             : 
    1304             :     // Generate a new device if needed
    1305         632 :     if (!deviceCertificate) {
    1306        1887 :         JAMI_WARNING("[Account {}] [Auth] Creating new device certificate", accountId_);
    1307         629 :         auto request = ctx.request.get();
    1308         629 :         if (not request->verify()) {
    1309           0 :             JAMI_ERROR("[Account {}] [Auth] Invalid certificate request.", accountId_);
    1310           0 :             ctx.onFailure(AuthError::INVALID_ARGUMENTS, "");
    1311           0 :             return;
    1312             :         }
    1313        1258 :         deviceCertificate = std::make_shared<dht::crypto::Certificate>(
    1314        1887 :             dht::crypto::Certificate::generate(*request, a.id));
    1315        1887 :         JAMI_WARNING("[Account {}] [Auth] Created new device: {}",
    1316             :                      accountId_,
    1317             :                      deviceCertificate->getLongId());
    1318         629 :     }
    1319             : 
    1320         632 :     auto receipt = makeReceipt(a.id, *deviceCertificate, ethAccount);
    1321        1264 :     auto receiptSignature = a.id.first->sign({receipt.first.begin(), receipt.first.end()});
    1322             : 
    1323         632 :     auto info = std::make_unique<AccountInfo>();
    1324         632 :     auto pk = usePreviousIdentity ? ctx.credentials->updateIdentity.first : ctx.key.get();
    1325         632 :     auto sharedPk = pk->getSharedPublicKey();
    1326         632 :     info->identity.first = pk;
    1327         632 :     info->identity.second = deviceCertificate;
    1328         632 :     info->accountId = a.id.second->getId().toString();
    1329         632 :     info->devicePk = sharedPk;
    1330         632 :     info->deviceId = info->devicePk->getLongId().toString();
    1331         632 :     if (ctx.deviceName.empty())
    1332           0 :         ctx.deviceName = info->deviceId.substr(8);
    1333             : 
    1334         632 :     if (!contacts) {
    1335         629 :         contacts = std::make_unique<ContactList>(ctx.accountId, a.id.second, path_, onChange_);
    1336             :     }
    1337         630 :     info->contacts = std::move(contacts);
    1338         630 :     info->contacts->setContacts(a.contacts);
    1339         630 :     info->contacts->foundAccountDevice(deviceCertificate, ctx.deviceName, clock::now());
    1340         630 :     info->ethAccount = ethAccount;
    1341         630 :     info->announce = std::move(receipt.second);
    1342         630 :     ConversationModule::saveConvInfosToPath(path_, a.conversations);
    1343         630 :     ConversationModule::saveConvRequestsToPath(path_, a.conversationsRequests);
    1344         630 :     info_ = std::move(info);
    1345             : 
    1346         630 :     ctx.onSuccess(*info_,
    1347         630 :                   std::move(a.config),
    1348         630 :                   std::move(receipt.first),
    1349         630 :                   std::move(receiptSignature));
    1350         646 : }
    1351             : 
    1352             : std::pair<std::vector<uint8_t>, dht::InfoHash>
    1353           0 : ArchiveAccountManager::computeKeys(const std::string& password,
    1354             :                                    const std::string& pin,
    1355             :                                    bool previous)
    1356             : {
    1357             :     // Compute time seed
    1358           0 :     auto now = std::chrono::duration_cast<std::chrono::seconds>(clock::now().time_since_epoch());
    1359           0 :     auto tseed = now.count() / std::chrono::seconds(EXPORT_KEY_RENEWAL_TIME).count();
    1360           0 :     if (previous)
    1361           0 :         tseed--;
    1362           0 :     std::ostringstream ss;
    1363           0 :     ss << std::hex << tseed;
    1364           0 :     auto tseed_str = ss.str();
    1365             : 
    1366             :     // Generate key for archive encryption, using PIN as the salt
    1367           0 :     std::vector<uint8_t> salt_key;
    1368           0 :     salt_key.reserve(pin.size() + tseed_str.size());
    1369           0 :     salt_key.insert(salt_key.end(), pin.begin(), pin.end());
    1370           0 :     salt_key.insert(salt_key.end(), tseed_str.begin(), tseed_str.end());
    1371           0 :     auto key = dht::crypto::stretchKey(password, salt_key, 256 / 8);
    1372             : 
    1373             :     // Generate public storage location as SHA1(key).
    1374           0 :     auto loc = dht::InfoHash::get(key);
    1375             : 
    1376           0 :     return {key, loc};
    1377           0 : }
    1378             : 
    1379             : std::pair<std::string, std::shared_ptr<dht::Value>>
    1380         632 : ArchiveAccountManager::makeReceipt(const dht::crypto::Identity& id,
    1381             :                                    const dht::crypto::Certificate& device,
    1382             :                                    const std::string& ethAccount)
    1383             : {
    1384        1896 :     JAMI_LOG("[Account {}] [Auth] Signing receipt for device {}", accountId_, device.getLongId());
    1385         632 :     auto devId = device.getId();
    1386         632 :     DeviceAnnouncement announcement;
    1387         632 :     announcement.dev = devId;
    1388         632 :     announcement.pk = device.getSharedPublicKey();
    1389         632 :     dht::Value ann_val {announcement};
    1390         632 :     ann_val.sign(*id.first);
    1391             : 
    1392         632 :     auto packedAnnoucement = ann_val.getPacked();
    1393        1896 :     JAMI_LOG("[Account {}] [Auth] Device announcement size: {}",
    1394             :              accountId_,
    1395             :              packedAnnoucement.size());
    1396             : 
    1397         632 :     std::ostringstream is;
    1398         632 :     is << "{\"id\":\"" << id.second->getId() << "\",\"dev\":\"" << devId << "\",\"eth\":\""
    1399         632 :        << ethAccount << "\",\"announce\":\"" << base64::encode(packedAnnoucement) << "\"}";
    1400             : 
    1401             :     // auto announce_ = ;
    1402        1264 :     return {is.str(), std::make_shared<dht::Value>(std::move(ann_val))};
    1403         632 : }
    1404             : 
    1405             : bool
    1406           4 : ArchiveAccountManager::needsMigration(const std::string& accountId, const dht::crypto::Identity& id)
    1407             : {
    1408           4 :     if (not id.second)
    1409           0 :         return true;
    1410           4 :     auto cert = id.second->issuer;
    1411           8 :     while (cert) {
    1412           6 :         if (not cert->isCA()) {
    1413           0 :             JAMI_WARNING("[Account {}] [Auth] certificate {} is not a CA, needs update.",
    1414             :                          accountId,
    1415             :                          cert->getId());
    1416           0 :             return true;
    1417             :         }
    1418           6 :         if (cert->getExpiration() < clock::now()) {
    1419           6 :             JAMI_WARNING("[Account {}] [Auth] certificate {} is expired, needs update.",
    1420             :                          accountId,
    1421             :                          cert->getId());
    1422           2 :             return true;
    1423             :         }
    1424           4 :         cert = cert->issuer;
    1425             :     }
    1426           2 :     return false;
    1427           4 : }
    1428             : 
    1429             : void
    1430        1263 : ArchiveAccountManager::syncDevices()
    1431             : {
    1432        3789 :     JAMI_LOG("[Account {}] Building device sync from {}", accountId_, info_->deviceId);
    1433        1263 :     onSyncData_(info_->contacts->getSyncData());
    1434        1263 : }
    1435             : 
    1436             : void
    1437        1135 : ArchiveAccountManager::startSync(const OnNewDeviceCb& cb,
    1438             :                                  const OnDeviceAnnouncedCb& dcb,
    1439             :                                  bool publishPresence)
    1440             : {
    1441        1135 :     AccountManager::startSync(std::move(cb), std::move(dcb), publishPresence);
    1442             : 
    1443        3405 :     dht_->listen<DeviceSync>(
    1444        2270 :         dht::InfoHash::get("inbox:" + info_->devicePk->getId().toString()),
    1445           0 :         [this](DeviceSync&& sync) {
    1446             :             // Received device sync data.
    1447             :             // check device certificate
    1448           0 :             findCertificate(
    1449           0 :                 sync.from,
    1450           0 :                 [this, sync](const std::shared_ptr<dht::crypto::Certificate>& cert) mutable {
    1451           0 :                     if (!cert or cert->getId() != sync.from) {
    1452           0 :                         JAMI_WARNING("[Account {}] Unable to find certificate for device {}",
    1453             :                                      accountId_,
    1454             :                                      sync.from.toString());
    1455           0 :                         return;
    1456             :                     }
    1457           0 :                     if (not foundAccountDevice(cert))
    1458           0 :                         return;
    1459           0 :                     onSyncData(std::move(sync));
    1460             :                 });
    1461             : 
    1462           0 :             return true;
    1463             :         });
    1464        1135 : }
    1465             : 
    1466             : AccountArchive
    1467          48 : ArchiveAccountManager::readArchive(std::string_view scheme, const std::string& pwd) const
    1468             : {
    1469         144 :     JAMI_LOG("[Account {}] [Auth] Reading account archive", accountId_);
    1470          96 :     return AccountArchive(fileutils::getFullPath(path_, archivePath_), scheme, pwd);
    1471             : }
    1472             : 
    1473             : void
    1474          35 : ArchiveAccountManager::updateArchive(AccountArchive& archive) const
    1475             : {
    1476             :     using namespace libjami::Account::ConfProperties;
    1477             : 
    1478             :     // Keys not exported to archive
    1479             :     static const auto filtered_keys = {Ringtone::PATH,
    1480             :                                        ARCHIVE_PATH,
    1481             :                                        DEVICE_ID,
    1482             :                                        DEVICE_NAME,
    1483             :                                        Conf::CONFIG_DHT_PORT,
    1484             :                                        DHT_PROXY_LIST_URL,
    1485             :                                        AUTOANSWER,
    1486             :                                        PROXY_ENABLED,
    1487             :                                        PROXY_SERVER,
    1488             :                                        PROXY_PUSH_TOKEN};
    1489             : 
    1490             :     // Keys with meaning of file path where the contents has to be exported in base64
    1491             :     static const auto encoded_keys = {TLS::CA_LIST_FILE,
    1492             :                                       TLS::CERTIFICATE_FILE,
    1493             :                                       TLS::PRIVATE_KEY_FILE};
    1494             : 
    1495         105 :     JAMI_LOG("[Account {}] [Auth] Building account archive", accountId_);
    1496        2030 :     for (const auto& it : onExportConfig_()) {
    1497             :         // filter-out?
    1498        1995 :         if (std::any_of(std::begin(filtered_keys), std::end(filtered_keys), [&](const auto& key) {
    1499       18900 :                 return key == it.first;
    1500             :             }))
    1501         245 :             continue;
    1502             : 
    1503             :         // file contents?
    1504        1750 :         if (std::any_of(std::begin(encoded_keys), std::end(encoded_keys), [&](const auto& key) {
    1505        5145 :                 return key == it.first;
    1506             :             })) {
    1507             :             try {
    1508         175 :                 archive.config.emplace(it.first, base64::encode(fileutils::loadFile(it.second)));
    1509          35 :             } catch (...) {
    1510          35 :             }
    1511             :         } else
    1512        1645 :             archive.config[it.first] = it.second;
    1513          35 :     }
    1514          35 :     if (info_) {
    1515             :         // If migrating from same archive, info_ will be null
    1516          33 :         archive.contacts = info_->contacts->getContacts();
    1517             :         // Note we do not know accountID_ here, use path
    1518          33 :         archive.conversations = ConversationModule::convInfosFromPath(path_);
    1519          33 :         archive.conversationsRequests = ConversationModule::convRequestsFromPath(path_);
    1520             :     }
    1521          35 : }
    1522             : 
    1523             : void
    1524           2 : ArchiveAccountManager::saveArchive(AccountArchive& archive,
    1525             :                                    std::string_view scheme,
    1526             :                                    const std::string& pwd)
    1527             : {
    1528             :     try {
    1529           2 :         updateArchive(archive);
    1530           2 :         if (archivePath_.empty())
    1531           0 :             archivePath_ = "export.gz";
    1532           2 :         archive.save(fileutils::getFullPath(path_, archivePath_), scheme, pwd);
    1533           0 :     } catch (const std::runtime_error& ex) {
    1534           0 :         JAMI_ERROR("[Account {}] [Auth] Unable to export archive: {}", accountId_, ex.what());
    1535           0 :         return;
    1536           0 :     }
    1537             : }
    1538             : 
    1539             : bool
    1540           4 : ArchiveAccountManager::changePassword(const std::string& password_old,
    1541             :                                       const std::string& password_new)
    1542             : {
    1543             :     try {
    1544           4 :         auto path = fileutils::getFullPath(path_, archivePath_);
    1545           6 :         AccountArchive(path, fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password_old)
    1546           2 :             .save(path, fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password_new);
    1547           2 :         return true;
    1548           6 :     } catch (const std::exception&) {
    1549           2 :         return false;
    1550           2 :     }
    1551             : }
    1552             : 
    1553             : std::vector<uint8_t>
    1554           0 : ArchiveAccountManager::getPasswordKey(const std::string& password)
    1555             : {
    1556             :     try {
    1557           0 :         auto data = dhtnet::fileutils::loadFile(fileutils::getFullPath(path_, archivePath_));
    1558             :         // Try to decrypt to check if password is valid
    1559           0 :         auto key = dht::crypto::aesGetKey(data, password);
    1560           0 :         auto decrypted = dht::crypto::aesDecrypt(dht::crypto::aesGetEncrypted(data), key);
    1561           0 :         return key;
    1562           0 :     } catch (const std::exception& e) {
    1563           0 :         JAMI_ERROR("[Account {}] Error loading archive: {}", accountId_, e.what());
    1564           0 :     }
    1565           0 :     return {};
    1566             : }
    1567             : 
    1568             : bool
    1569           3 : ArchiveAccountManager::revokeDevice(const std::string& device,
    1570             :                                     std::string_view scheme,
    1571             :                                     const std::string& password,
    1572             :                                     RevokeDeviceCallback cb)
    1573             : {
    1574           3 :     auto fa = dht::ThreadPool::computation().getShared<AccountArchive>(
    1575           9 :         [this, scheme = std::string(scheme), password] { return readArchive(scheme, password); });
    1576           3 :     findCertificate(DeviceId(device),
    1577           3 :                     [fa = std::move(fa),
    1578             :                      scheme = std::string(scheme),
    1579             :                      password,
    1580             :                      device,
    1581             :                      cb,
    1582             :                      w = weak()](
    1583             :                         const std::shared_ptr<dht::crypto::Certificate>& crt) mutable {
    1584           3 :                         if (not crt) {
    1585           1 :                             cb(RevokeDeviceResult::ERROR_NETWORK);
    1586           1 :                             return;
    1587             :                         }
    1588           2 :                         auto this_ = w.lock();
    1589           2 :                         if (not this_)
    1590           0 :                             return;
    1591           2 :                         this_->info_->contacts->foundAccountDevice(crt);
    1592           2 :                         AccountArchive a;
    1593             :                         try {
    1594           2 :                             a = fa.get();
    1595           0 :                         } catch (...) {
    1596           0 :                             cb(RevokeDeviceResult::ERROR_CREDENTIALS);
    1597           0 :                             return;
    1598           0 :                         }
    1599             :                         // Add revoked device to the revocation list and resign it
    1600           2 :                         if (not a.revoked)
    1601           2 :                             a.revoked = std::make_shared<decltype(a.revoked)::element_type>();
    1602           2 :                         a.revoked->revoke(*crt);
    1603           2 :                         a.revoked->sign(a.id);
    1604             :                         // add to CRL cache
    1605           2 :                         this_->certStore().pinRevocationList(a.id.second->getId().toString(),
    1606             :                                                              a.revoked);
    1607           2 :                         this_->certStore().loadRevocations(*a.id.second);
    1608             : 
    1609             :                         // Announce CRL immediately
    1610           2 :                         auto h = a.id.second->getId();
    1611           2 :                         this_->dht_->put(h, a.revoked, dht::DoneCallback {}, {}, true);
    1612             : 
    1613           2 :                         this_->saveArchive(a, scheme, password);
    1614           2 :                         this_->info_->contacts->removeAccountDevice(crt->getLongId());
    1615           2 :                         cb(RevokeDeviceResult::SUCCESS);
    1616           2 :                         this_->syncDevices();
    1617           2 :                     });
    1618           3 :     return false;
    1619           3 : }
    1620             : 
    1621             : bool
    1622          31 : ArchiveAccountManager::exportArchive(const std::string& destinationPath,
    1623             :                                      std::string_view scheme,
    1624             :                                      const std::string& password)
    1625             : {
    1626             :     try {
    1627             :         // Save contacts if possible before exporting
    1628          31 :         AccountArchive archive = readArchive(scheme, password);
    1629          31 :         updateArchive(archive);
    1630          31 :         auto archivePath = fileutils::getFullPath(path_, archivePath_);
    1631          31 :         if (!archive.save(archivePath, scheme, password))
    1632           0 :             return false;
    1633             : 
    1634             :         // Export the file
    1635          31 :         std::error_code ec;
    1636          31 :         std::filesystem::copy_file(archivePath,
    1637             :                                    destinationPath,
    1638             :                                    std::filesystem::copy_options::overwrite_existing,
    1639             :                                    ec);
    1640          31 :         return !ec;
    1641          31 :     } catch (const std::runtime_error& ex) {
    1642           0 :         JAMI_ERR("[Auth] Unable to export archive: %s", ex.what());
    1643           0 :         return false;
    1644           0 :     } catch (...) {
    1645           0 :         JAMI_ERR("[Auth] Unable to export archive: Unable to read archive");
    1646           0 :         return false;
    1647           0 :     }
    1648             : }
    1649             : 
    1650             : bool
    1651           3 : ArchiveAccountManager::isPasswordValid(const std::string& password)
    1652             : {
    1653             :     try {
    1654           3 :         readArchive(fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password);
    1655           3 :         return true;
    1656           0 :     } catch (...) {
    1657           0 :         return false;
    1658           0 :     }
    1659             : }
    1660             : 
    1661             : #ifdef ENABLE_NAMESERVER
    1662             : void
    1663           1 : ArchiveAccountManager::registerName(const std::string& name,
    1664             :                                     std::string_view scheme,
    1665             :                                     const std::string& password,
    1666             :                                     RegistrationCallback cb)
    1667             : {
    1668           1 :     std::string signedName;
    1669           1 :     auto nameLowercase {name};
    1670           1 :     std::transform(nameLowercase.begin(), nameLowercase.end(), nameLowercase.begin(), ::tolower);
    1671           1 :     std::string publickey;
    1672           1 :     std::string accountId;
    1673           1 :     std::string ethAccount;
    1674             : 
    1675             :     try {
    1676           1 :         auto archive = readArchive(scheme, password);
    1677           1 :         auto privateKey = archive.id.first;
    1678           1 :         const auto& pk = privateKey->getPublicKey();
    1679           1 :         publickey = pk.toString();
    1680           1 :         accountId = pk.getId().toString();
    1681           1 :         signedName = base64::encode(privateKey->sign(std::vector<uint8_t>(nameLowercase.begin(), nameLowercase.end())));
    1682           1 :         ethAccount = dev::KeyPair(dev::Secret(archive.eth_key)).address().hex();
    1683           1 :     } catch (const std::exception& e) {
    1684             :         // JAMI_ERR("[Auth] Unable to export account: %s", e.what());
    1685           0 :         cb(NameDirectory::RegistrationResponse::invalidCredentials, name);
    1686           0 :         return;
    1687           0 :     }
    1688             : 
    1689           1 :     nameDir_.get().registerName(accountId, nameLowercase, ethAccount, cb, signedName, publickey);
    1690           1 : }
    1691             : #endif
    1692             : 
    1693             : } // namespace jami

Generated by: LCOV version 1.14