LCOV - code coverage report
Current view: top level - foo/src/jamidht - archive_account_manager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 674 927 72.7 %
Date: 2025-12-18 10:07:43 Functions: 179 252 71.0 %

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

Generated by: LCOV version 1.14