LCOV - code coverage report
Current view: top level - src/jamidht - archive_account_manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 72.8 % 922 671
Test Date: 2026-06-13 09:18:46 Functions: 70.9 % 261 185

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

Generated by: LCOV version 2.0-1