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: 379 914 41.5 %
Date: 2026-02-28 10:41:24 Functions: 91 247 36.8 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : #include "archive_account_manager.h"
      18             : #include "accountarchive.h"
      19             : #include "fileutils.h"
      20             : #include "libdevcrypto/Common.h"
      21             : #include "archiver.h"
      22             : #include "base64.h"
      23             : #include "jami/account_const.h"
      24             : #include "account_schema.h"
      25             : #include "jamidht/conversation_module.h"
      26             : #include "manager.h"
      27             : #include "jamidht/auth_channel_handler.h"
      28             : #include "client/jami_signal.h"
      29             : 
      30             : #include <dhtnet/multiplexed_socket.h>
      31             : #include <dhtnet/channel_utils.h>
      32             : #include <opendht/dhtrunner.h>
      33             : #include <opendht/thread_pool.h>
      34             : 
      35             : #include <memory>
      36             : #include <fstream>
      37             : 
      38             : #include "config/yamlparser.h"
      39             : 
      40             : namespace jami {
      41             : 
      42             : const constexpr auto EXPORT_KEY_RENEWAL_TIME = std::chrono::minutes(20);
      43             : constexpr auto AUTH_URI_SCHEME = "jami-auth://"sv;
      44             : constexpr auto CHANNEL_SCHEME = "auth:"sv;
      45             : constexpr auto OP_TIMEOUT = 5min;
      46             : 
      47             : void
      48         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           0 : toString(AuthDecodingState state)
     259             : {
     260           0 :     switch (state) {
     261           0 :     case AuthDecodingState::HANDSHAKE:
     262           0 :         return "HANDSHAKE"sv;
     263           0 :     case AuthDecodingState::EST:
     264           0 :         return "EST"sv;
     265           0 :     case AuthDecodingState::AUTH:
     266           0 :         return "AUTH"sv;
     267           0 :     case AuthDecodingState::DATA:
     268           0 :         return "DATA"sv;
     269           0 :     case AuthDecodingState::AUTH_ERROR:
     270           0 :         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           0 :     MSGPACK_DEFINE_MAP(schemeId, payload)
     297             : 
     298           0 :     void set(std::string_view key, std::string_view value) { payload.emplace(std::string(key), std::string(value)); }
     299             : 
     300           0 :     auto find(std::string_view key) const { return payload.find(std::string(key)); }
     301             : 
     302           0 :     auto at(std::string_view key) const { return payload.at(std::string(key)); }
     303             : 
     304           0 :     void logMsg() { JAMI_DEBUG("[LinkDevice]\nLinkDevice::logMsg:\n{}", formatMsg()); }
     305             : 
     306           0 :     std::string formatMsg()
     307             :     {
     308           0 :         std::string logStr = fmt::format("=========\nscheme: {}\n", schemeId);
     309           0 :         for (const auto& [msgKey, msgVal] : payload) {
     310           0 :             logStr += fmt::format(" - {}: {}\n", msgKey, msgVal);
     311             :         }
     312           0 :         logStr += "=========";
     313           0 :         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          10 :     DeviceAuthInfo() = default;
     340             :     DeviceAuthInfo(const Map& map)
     341             :         : Map(map)
     342             :     {}
     343           5 :     DeviceAuthInfo(Map&& map)
     344           5 :         : Map(std::move(map))
     345           5 :     {}
     346             : 
     347           5 :     void set(std::string_view key, std::string_view value) { emplace(std::string(key), std::string(value)); }
     348             : 
     349           5 :     static DeviceAuthInfo createError(Error err)
     350             :     {
     351           5 :         std::string errStr;
     352           5 :         switch (err) {
     353           5 :         case Error::NETWORK:
     354           5 :             errStr = "network";
     355           5 :             break;
     356           0 :         case Error::TIMEOUT:
     357           0 :             errStr = "timeout";
     358           0 :             break;
     359           0 :         case Error::AUTH_ERROR:
     360           0 :             errStr = "auth_error";
     361           0 :             break;
     362           0 :         case Error::CANCELED:
     363           0 :             errStr = "canceled";
     364           0 :             break;
     365           0 :         case Error::UNKNOWN:
     366           0 :             errStr = "unknown";
     367           0 :             break;
     368           0 :         case Error::NONE:
     369           0 :             errStr = "";
     370           0 :             break;
     371             :         }
     372          15 :         return DeviceAuthInfo {Map {{std::string(error), errStr}}};
     373           5 :     }
     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           5 :     DeviceContextBase(uint64_t operationId, AuthDecodingState initialState)
     386           5 :         : opId(operationId)
     387           5 :         , state(initialState)
     388           5 :     {}
     389             : 
     390           0 :     constexpr std::string_view formattedAuthState() const { return toString(state); }
     391             : 
     392           0 :     bool handleTimeoutMessage(const AuthMsg& msg)
     393             :     {
     394           0 :         auto stateMsgIt = msg.find(PayloadKey::stateMsg);
     395           0 :         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           0 :         return false;
     402             :     }
     403             : 
     404           0 :     bool handleCanceledMessage(const AuthMsg& msg)
     405             :     {
     406           0 :         auto stateMsgIt = msg.find(PayloadKey::stateMsg);
     407           0 :         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           0 :         return false;
     414             :     }
     415             : 
     416           0 :     DeviceAuthInfo::Error getErrorState() const
     417             :     {
     418           0 :         if (state == AuthDecodingState::AUTH_ERROR) {
     419           0 :             return DeviceAuthInfo::Error::AUTH_ERROR;
     420           0 :         } else if (state == AuthDecodingState::TIMEOUT) {
     421           0 :             return DeviceAuthInfo::Error::TIMEOUT;
     422           0 :         } else if (state == AuthDecodingState::CANCELED) {
     423           0 :             return DeviceAuthInfo::Error::CANCELED;
     424           0 :         } else if (state == AuthDecodingState::ERR) {
     425           0 :             return DeviceAuthInfo::Error::UNKNOWN;
     426           0 :         } else if (archiveTransferredWithoutFailure) {
     427           0 :             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           0 :     AddDeviceContext(std::shared_ptr<dhtnet::ChannelSocket> c)
     467           0 :         : DeviceContextBase(0, AuthDecodingState::EST)
     468           0 :         , channel(std::move(c))
     469           0 :     {}
     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           0 : ArchiveAccountManager::provideAccountAuthentication(const std::string& key, const std::string& scheme)
     481             : {
     482           0 :     if (scheme != fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD) {
     483           0 :         JAMI_ERROR("[LinkDevice] Unsupported account authentication scheme attempted.");
     484           0 :         return false;
     485             :     }
     486           0 :     auto ctx = authCtx_;
     487           0 :     if (!ctx) {
     488           0 :         JAMI_WARNING("[LinkDevice] No auth context found.");
     489           0 :         return false;
     490             :     }
     491             : 
     492           0 :     if (ctx->linkDevCtx->state != AuthDecodingState::AUTH) {
     493           0 :         JAMI_WARNING("[LinkDevice] Invalid state for providing account authentication.");
     494           0 :         return false;
     495             :     }
     496             : 
     497           0 :     ctx->linkDevCtx->authScheme = scheme;
     498           0 :     ctx->linkDevCtx->credentialsFromUser = key;
     499             :     // After authentication, the next step is to receive the account archive from the exporting device
     500           0 :     ctx->linkDevCtx->state = AuthDecodingState::DATA;
     501           0 :     emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(ctx->accountId,
     502             :                                                                      static_cast<uint8_t>(DeviceAuthState::IN_PROGRESS),
     503           0 :                                                                      DeviceAuthInfo {});
     504             : 
     505           0 :     dht::ThreadPool::io().run([key = std::move(key), scheme, ctx]() mutable {
     506           0 :         AuthMsg toSend;
     507           0 :         toSend.set(PayloadKey::password, std::move(key));
     508           0 :         msgpack::sbuffer buffer(UINT16_MAX);
     509           0 :         toSend.logMsg();
     510           0 :         msgpack::pack(buffer, toSend);
     511           0 :         std::error_code ec;
     512             :         try {
     513           0 :             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           0 :     });
     519             : 
     520           0 :     return true;
     521           0 : }
     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          10 :                                          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           0 :             if (auto ctx = wctx.lock()) {
     593           0 :                 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(ctx->accountId,
     594             :                                                                                  static_cast<uint8_t>(
     595             :                                                                                      DeviceAuthState::CONNECTING),
     596           0 :                                                                                  DeviceAuthInfo {});
     597           0 :                 return true;
     598           0 :             }
     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           0 :                 std::string_view url(name);
     605           0 :                 if (!starts_with(url, CHANNEL_SCHEME)) {
     606           0 :                     JAMI_WARNING("[LinkDevice] Temporary connection manager received invalid scheme: {}", name);
     607           0 :                     return false;
     608             :                 }
     609           0 :                 auto opStr = url.substr(CHANNEL_SCHEME.size());
     610           0 :                 auto parsedOpId = jami::to_int<uint64_t>(opStr);
     611             : 
     612           0 :                 if (ctx->linkDevCtx->opId == parsedOpId
     613           0 :                     && ctx->linkDevCtx->numOpenChannels < ctx->linkDevCtx->maxOpenChannels) {
     614           0 :                     ctx->linkDevCtx->numOpenChannels++;
     615           0 :                     JAMI_DEBUG("[LinkDevice] Opening channel ({}/{}): {}",
     616             :                                ctx->linkDevCtx->numOpenChannels,
     617             :                                ctx->linkDevCtx->maxOpenChannels,
     618             :                                name);
     619           0 :                     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             :                                                                const std::shared_ptr<dhtnet::ChannelSocket>& socket) {
     629           0 :             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           0 :             ctx->linkDevCtx->channel = socket;
     646             : 
     647           0 :             ctx->timeout = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext());
     648           0 :             ctx->timeout->expires_after(OP_TIMEOUT);
     649           0 :             ctx->timeout->async_wait([c = std::weak_ptr(ctx), socket](const std::error_code& ec) {
     650           0 :                 if (ec) {
     651           0 :                     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           0 :             socket->onShutdown([ctx, name, wthis](const std::error_code& /*error_code*/) {
     669           0 :                 JAMI_WARNING("[LinkDevice] Temporary connection manager closing socket: {}", name);
     670           0 :                 if (ctx->timeout)
     671           0 :                     ctx->timeout->cancel();
     672           0 :                 ctx->timeout.reset();
     673           0 :                 ctx->linkDevCtx->numOpenChannels--;
     674           0 :                 ctx->linkDevCtx->channel.reset();
     675           0 :                 if (auto sthis = wthis.lock())
     676           0 :                     sthis->authCtx_.reset();
     677             : 
     678           0 :                 DeviceAuthInfo::Error error = ctx->linkDevCtx->getErrorState();
     679           0 :                 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(ctx->accountId,
     680             :                                                                                  static_cast<uint8_t>(
     681             :                                                                                      DeviceAuthState::DONE),
     682           0 :                                                                                  DeviceAuthInfo::createError(error));
     683           0 :             });
     684             : 
     685           0 :             socket->setOnRecv(dhtnet::buildMsgpackReader<AuthMsg>([ctx, wthis](AuthMsg&& toRecv) {
     686           0 :                 if (!ctx || !wthis.lock())
     687           0 :                     return std::make_error_code(std::errc::operation_canceled);
     688           0 :                 JAMI_DEBUG("[LinkDevice] NEW: Successfully unpacked message from source\n{}", toRecv.formatMsg());
     689           0 :                 JAMI_DEBUG("[LinkDevice] NEW: State is {}:{}",
     690             :                            ctx->linkDevCtx->scheme,
     691             :                            ctx->linkDevCtx->formattedAuthState());
     692             : 
     693             :                 // check if scheme is supported
     694           0 :                 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           0 :                 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           0 :                 AuthMsg toSend;
     706           0 :                 bool shouldShutdown = false;
     707           0 :                 auto accDataIt = toRecv.find(PayloadKey::accData);
     708           0 :                 bool shouldLoadArchive = accDataIt != toRecv.payload.end();
     709             : 
     710           0 :                 if (ctx->linkDevCtx->state == AuthDecodingState::HANDSHAKE) {
     711           0 :                     auto peerCert = ctx->linkDevCtx->channel->peerCertificate();
     712           0 :                     auto authScheme = toRecv.at(PayloadKey::authScheme);
     713           0 :                     ctx->linkDevCtx->authEnabled = authScheme != fileutils::ARCHIVE_AUTH_SCHEME_NONE;
     714             : 
     715           0 :                     JAMI_DEBUG("[LinkDevice] NEW: Auth scheme from payload is '{}'", authScheme);
     716           0 :                     ctx->linkDevCtx->state = AuthDecodingState::AUTH;
     717           0 :                     DeviceAuthInfo info;
     718           0 :                     info.set(DeviceAuthInfo::auth_scheme, authScheme);
     719           0 :                     info.set(DeviceAuthInfo::peer_id, peerCert->issuer->getId().toString());
     720           0 :                     emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(ctx->accountId,
     721             :                                                                                      static_cast<uint8_t>(
     722             :                                                                                          DeviceAuthState::AUTHENTICATING),
     723             :                                                                                      info);
     724           0 :                 } else if (ctx->linkDevCtx->state == AuthDecodingState::DATA) {
     725           0 :                     auto passwordCorrectIt = toRecv.find(PayloadKey::passwordCorrect);
     726           0 :                     auto canRetry = toRecv.find(PayloadKey::canRetry);
     727             : 
     728             :                     // If we've reached the maximum number of retry attempts
     729           0 :                     if (canRetry != toRecv.payload.end() && canRetry->second == "false") {
     730           0 :                         JAMI_DEBUG("[LinkDevice] Authentication failed: maximum retry attempts reached");
     731           0 :                         ctx->linkDevCtx->state = AuthDecodingState::AUTH_ERROR;
     732           0 :                         return std::make_error_code(std::errc::operation_canceled);
     733             :                     }
     734             : 
     735             :                     // If the password was incorrect but we can still retry
     736           0 :                     if (passwordCorrectIt != toRecv.payload.end() && passwordCorrectIt->second == "false") {
     737           0 :                         ctx->linkDevCtx->state = AuthDecodingState::AUTH;
     738             : 
     739           0 :                         JAMI_DEBUG("[LinkDevice] NEW: Password incorrect.");
     740           0 :                         auto peerCert = ctx->linkDevCtx->channel->peerCertificate();
     741           0 :                         auto peer_id = peerCert->issuer->getId().toString();
     742             :                         // We received a password incorrect response, so we know we're using
     743             :                         // password authentication
     744           0 :                         auto authScheme = fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD;
     745             : 
     746           0 :                         DeviceAuthInfo info;
     747           0 :                         info.set(DeviceAuthInfo::auth_scheme, authScheme);
     748           0 :                         info.set(DeviceAuthInfo::peer_id, peer_id);
     749           0 :                         info.set(DeviceAuthInfo::auth_error, "invalid_credentials");
     750             : 
     751           0 :                         emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
     752           0 :                             ctx->accountId, static_cast<uint8_t>(DeviceAuthState::AUTHENTICATING), info);
     753           0 :                         return std::error_code();
     754           0 :                     }
     755             : 
     756           0 :                     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           0 :                 if (shouldLoadArchive) {
     767           0 :                     emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(ctx->accountId,
     768             :                                                                                      static_cast<uint8_t>(
     769             :                                                                                          DeviceAuthState::IN_PROGRESS),
     770           0 :                                                                                      DeviceAuthInfo {});
     771             :                     try {
     772           0 :                         auto archive = AccountArchive(std::string_view(accDataIt->second));
     773           0 :                         if (auto this_ = wthis.lock()) {
     774           0 :                             JAMI_DEBUG("[LinkDevice] NEW: Reading archive from peer.");
     775           0 :                             this_->onArchiveLoaded(*ctx, std::move(archive), true);
     776           0 :                             JAMI_DEBUG("[LinkDevice] NEW: Successfully loaded archive.");
     777           0 :                             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           0 :                         }
     783           0 :                     } catch (const std::exception& e) {
     784           0 :                         ctx->linkDevCtx->state = AuthDecodingState::ERR;
     785           0 :                         ctx->linkDevCtx->archiveTransferredWithoutFailure = false;
     786           0 :                         JAMI_WARNING("[LinkDevice] NEW: Error reading archive.");
     787           0 :                     }
     788           0 :                     shouldShutdown = true;
     789             :                 }
     790             : 
     791           0 :                 return shouldShutdown ? std::make_error_code(std::errc::operation_canceled) : std::error_code();
     792           0 :             })); // !onConnectionReady // TODO emit AuthStateChanged+"connection ready" signal
     793             : 
     794           0 :             ctx->linkDevCtx->state = AuthDecodingState::HANDSHAKE;
     795             :             // send first message to establish scheme
     796           0 :             AuthMsg toSend;
     797           0 :             toSend.schemeId = 0; // set latest scheme here
     798           0 :             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           0 :             msgpack::sbuffer buffer(UINT16_MAX);
     804           0 :             msgpack::pack(buffer, toSend);
     805           0 :             std::error_code ec;
     806           0 :             ctx->linkDevCtx->channel->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec);
     807             : 
     808           0 :             JAMI_LOG("[LinkDevice {}] Generated temporary account.", ctx->linkDevCtx->tmpId.second->getId());
     809           0 :         });
     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          20 :             JAMI_WARNING("[LinkDevice] Invalid socket event while AccountManager connecting.");
     859           5 :             if (this_)
     860           5 :                 this_->authCtx_.reset();
     861           5 :             emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(accountId,
     862           5 :                                                                             ctx->token,
     863             :                                                                             static_cast<uint8_t>(DeviceAuthState::DONE),
     864          10 :                                                                             DeviceAuthInfo::createError(
     865             :                                                                                 DeviceAuthInfo::Error::NETWORK));
     866             :         } else {
     867           0 :             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           5 :     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,
     887           0 :                                             const dht::PkId& /*infoHash*/) { onConnect(std::move(socket)); });
     888             :     }
     889             : 
     890           5 :     runOnMainThread([token, id = accountId_] {
     891           5 :         emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(id,
     892             :                                                                         token,
     893             :                                                                         static_cast<uint8_t>(
     894             :                                                                             DeviceAuthState::CONNECTING),
     895          10 :                                                                         DeviceAuthInfo {});
     896           5 :     });
     897           5 :     return token;
     898           5 : }
     899             : 
     900             : bool
     901           0 : ArchiveAccountManager::doAddDevice(std::string_view scheme,
     902             :                                    const std::shared_ptr<AuthContext>& ctx,
     903             :                                    std::shared_ptr<dhtnet::ChannelSocket> channel)
     904             : {
     905           0 :     if (ctx->canceled) {
     906           0 :         JAMI_WARNING("[LinkDevice] SOURCE: addDevice canceled.");
     907           0 :         channel->shutdown();
     908           0 :         return false;
     909             :     }
     910           0 :     JAMI_DEBUG("[LinkDevice] Setting up addDevice logic on SOURCE device.");
     911           0 :     JAMI_DEBUG("[LinkDevice] SOURCE: Creating addDeviceCtx.");
     912           0 :     ctx->addDeviceCtx = std::make_unique<AddDeviceContext>(std::move(channel));
     913           0 :     ctx->addDeviceCtx->authScheme = scheme;
     914           0 :     ctx->addDeviceCtx->state = AuthDecodingState::HANDSHAKE;
     915             : 
     916           0 :     ctx->timeout = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext());
     917           0 :     ctx->timeout->expires_after(OP_TIMEOUT);
     918           0 :     ctx->timeout->async_wait([wthis = weak(), wctx = std::weak_ptr(ctx)](const std::error_code& ec) {
     919           0 :         if (ec)
     920           0 :             return;
     921           0 :         dht::ThreadPool::io().run([wthis, wctx]() {
     922           0 :             if (auto ctx = wctx.lock()) {
     923           0 :                 if (!ctx->addDeviceCtx->isCompleted()) {
     924           0 :                     if (auto this_ = wthis.lock()) {
     925           0 :                         ctx->addDeviceCtx->state = AuthDecodingState::TIMEOUT;
     926           0 :                         JAMI_WARNING("[LinkDevice] Timeout for addDevice.");
     927             : 
     928             :                         // Create and send timeout message
     929           0 :                         msgpack::sbuffer buffer(UINT16_MAX);
     930           0 :                         msgpack::pack(buffer, AuthMsg::timeout());
     931           0 :                         std::error_code ec;
     932           0 :                         ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(buffer.data()),
     933             :                                                           buffer.size(),
     934             :                                                           ec);
     935           0 :                         ctx->addDeviceCtx->channel->shutdown();
     936           0 :                     }
     937             :                 }
     938           0 :             }
     939           0 :         });
     940             :     });
     941             : 
     942           0 :     JAMI_DEBUG("[LinkDevice] SOURCE: Creating callbacks.");
     943           0 :     ctx->addDeviceCtx->channel->onShutdown([ctx, w = weak()](const std::error_code& /*error_code*/) {
     944           0 :         JAMI_DEBUG("[LinkDevice] SOURCE: Shutdown with state {}... xfer {}uccessful",
     945             :                    ctx->addDeviceCtx->formattedAuthState(),
     946             :                    ctx->addDeviceCtx->archiveTransferredWithoutFailure ? "s" : "uns");
     947             :         // check if the archive was successfully loaded and emitSignal
     948           0 :         if (ctx->timeout)
     949           0 :             ctx->timeout->cancel();
     950           0 :         ctx->timeout.reset();
     951             : 
     952           0 :         if (auto this_ = w.lock()) {
     953           0 :             this_->authCtx_.reset();
     954           0 :         }
     955             : 
     956           0 :         DeviceAuthInfo::Error error = ctx->addDeviceCtx->getErrorState();
     957           0 :         emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(ctx->accountId,
     958           0 :                                                                         ctx->token,
     959             :                                                                         static_cast<uint8_t>(DeviceAuthState::DONE),
     960           0 :                                                                         DeviceAuthInfo::createError(error));
     961           0 :     });
     962             : 
     963             :     // for now we only have one valid protocol (version is AuthMsg::scheme = 0) but can later
     964             :     // add in more schemes inside this callback function
     965           0 :     JAMI_DEBUG("[LinkDevice] Setting up receiving logic callback.");
     966           0 :     ctx->addDeviceCtx->channel->setOnRecv(dhtnet::buildMsgpackReader<AuthMsg>([ctx, wthis = weak()](AuthMsg&& toRecv) {
     967           0 :         JAMI_DEBUG("[LinkDevice] Setting up receiver callback for communication logic on SOURCE device.");
     968             :         // when archive is sent to newDev we will get back a success or fail response before the
     969             :         // connection closes and we need to handle this and pass it to the shutdown callback
     970           0 :         auto this_ = wthis.lock();
     971           0 :         if (!this_) {
     972           0 :             JAMI_ERROR("[LinkDevice] Invalid state for ArchiveAccountManager.");
     973           0 :             return std::make_error_code(std::errc::operation_canceled);
     974             :         }
     975             : 
     976           0 :         if (ctx->canceled || ctx->addDeviceCtx->state == AuthDecodingState::ERR) {
     977           0 :             JAMI_ERROR("[LinkDevice] Error.");
     978           0 :             return std::make_error_code(std::errc::operation_canceled);
     979             :         }
     980             : 
     981             :         // handle unpacking the data from the peer
     982           0 :         JAMI_DEBUG("[LinkDevice] SOURCE: addDevice: setOnRecv: handling msg from NEW");
     983           0 :         JAMI_DEBUG("[LinkDevice] SOURCE: State is '{}'", ctx->addDeviceCtx->formattedAuthState());
     984             : 
     985             :         // It's possible to start handling different protocol scheme numbers here
     986             :         // one possibility is for multi-account xfer in the future
     987             :         // validate the scheme
     988           0 :         if (toRecv.schemeId != 0) {
     989           0 :             ctx->addDeviceCtx->state = AuthDecodingState::ERR;
     990           0 :             JAMI_WARNING("[LinkDevice] Unsupported scheme received from a connection.");
     991             :         }
     992             : 
     993           0 :         if (ctx->addDeviceCtx->state == AuthDecodingState::ERR
     994           0 :             || ctx->addDeviceCtx->state == AuthDecodingState::AUTH_ERROR) {
     995           0 :             JAMI_WARNING("[LinkDevice] Undefined behavior encountered during a link auth session.");
     996           0 :             return std::make_error_code(std::errc::operation_canceled);
     997             :         }
     998             :         // Check for timeout message
     999           0 :         if (ctx->addDeviceCtx->handleTimeoutMessage(toRecv)) {
    1000           0 :             return std::make_error_code(std::errc::operation_canceled);
    1001             :         }
    1002           0 :         AuthMsg toSend;
    1003           0 :         bool shouldSendMsg = false;
    1004           0 :         bool shouldShutdown = false;
    1005           0 :         bool shouldSendArchive = false;
    1006             : 
    1007             :         // we expect to be receiving credentials in this state and we know the archive is encrypted
    1008           0 :         if (ctx->addDeviceCtx->state == AuthDecodingState::AUTH) {
    1009             :             // receive the incoming password, check if the password is right, and send back the
    1010             :             // archive if it is correct
    1011           0 :             JAMI_DEBUG("[LinkDevice] SOURCE: addDevice: setOnRecv: verifying sent credentials from NEW");
    1012           0 :             shouldSendMsg = true;
    1013           0 :             const auto& passwordIt = toRecv.find(PayloadKey::password);
    1014           0 :             if (passwordIt != toRecv.payload.end()) {
    1015             :                 // try and decompress archive for xfer
    1016             :                 try {
    1017           0 :                     JAMI_DEBUG("[LinkDevice] Injecting account archive into outbound message.");
    1018           0 :                     ctx->addDeviceCtx->accData
    1019           0 :                         = this_->readArchive(fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, passwordIt->second).serialize();
    1020           0 :                     shouldSendArchive = true;
    1021           0 :                     JAMI_DEBUG("[LinkDevice] Sending account archive.");
    1022           0 :                 } catch (const std::exception& e) {
    1023           0 :                     JAMI_DEBUG("[LinkDevice] Finished reading archive: FAILURE: {}", e.what());
    1024           0 :                     shouldSendArchive = false;
    1025           0 :                 }
    1026             :             }
    1027           0 :             if (!shouldSendArchive) {
    1028             :                 // pass is not valid
    1029           0 :                 ctx->addDeviceCtx->numTries++;
    1030           0 :                 if (ctx->addDeviceCtx->numTries < ctx->addDeviceCtx->maxTries) {
    1031             :                     // can retry auth
    1032           0 :                     JAMI_DEBUG("[LinkDevice] Incorrect password received. Attempt {} out of {}.",
    1033             :                                ctx->addDeviceCtx->numTries,
    1034             :                                ctx->addDeviceCtx->maxTries);
    1035           0 :                     toSend.set(PayloadKey::passwordCorrect, "false");
    1036           0 :                     toSend.set(PayloadKey::canRetry, "true");
    1037             :                 } else {
    1038             :                     // cannot retry auth
    1039           0 :                     JAMI_WARNING("[LinkDevice] Incorrect password received, maximum attempts reached.");
    1040           0 :                     toSend.set(PayloadKey::canRetry, "false");
    1041           0 :                     ctx->addDeviceCtx->state = AuthDecodingState::AUTH_ERROR;
    1042           0 :                     shouldShutdown = true;
    1043             :                 }
    1044             :             }
    1045             :         }
    1046             : 
    1047           0 :         if (shouldSendArchive) {
    1048           0 :             JAMI_DEBUG("[LinkDevice] SOURCE: Archive in message has encryption scheme '{}'",
    1049             :                        ctx->addDeviceCtx->authScheme);
    1050           0 :             emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(ctx->accountId,
    1051           0 :                                                                             ctx->token,
    1052             :                                                                             static_cast<uint8_t>(
    1053             :                                                                                 DeviceAuthState::IN_PROGRESS),
    1054           0 :                                                                             DeviceAuthInfo {});
    1055           0 :             shouldShutdown = true;
    1056           0 :             shouldSendMsg = true;
    1057           0 :             ctx->addDeviceCtx->archiveTransferredWithoutFailure = true;
    1058           0 :             toSend.set(PayloadKey::accData, ctx->addDeviceCtx->accData);
    1059             :         }
    1060           0 :         if (shouldSendMsg) {
    1061           0 :             JAMI_DEBUG("[LinkDevice] SOURCE: Sending msg to NEW:\n{}", toSend.formatMsg());
    1062           0 :             msgpack::sbuffer buffer(UINT16_MAX);
    1063           0 :             msgpack::pack(buffer, toSend);
    1064           0 :             std::error_code ec;
    1065           0 :             ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec);
    1066           0 :         }
    1067             : 
    1068           0 :         return shouldShutdown ? std::make_error_code(std::errc::operation_canceled) : std::error_code();
    1069           0 :     })); // !channel onRecv closure
    1070             : 
    1071           0 :     if (ctx->addDeviceCtx->state == AuthDecodingState::HANDSHAKE) {
    1072           0 :         ctx->addDeviceCtx->state = AuthDecodingState::EST;
    1073           0 :         DeviceAuthInfo info;
    1074           0 :         info.set(DeviceAuthInfo::peer_address, ctx->addDeviceCtx->channel->getRemoteAddress().toString(true));
    1075           0 :         emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(ctx->accountId,
    1076           0 :                                                                         ctx->token,
    1077             :                                                                         static_cast<uint8_t>(
    1078             :                                                                             DeviceAuthState::AUTHENTICATING),
    1079             :                                                                         info);
    1080           0 :     }
    1081             : 
    1082           0 :     return true;
    1083             : }
    1084             : 
    1085             : bool
    1086           0 : ArchiveAccountManager::cancelAddDevice(uint32_t token)
    1087             : {
    1088           0 :     if (auto ctx = authCtx_) {
    1089           0 :         if (ctx->token == token) {
    1090           0 :             ctx->canceled = true;
    1091           0 :             if (ctx->addDeviceCtx) {
    1092           0 :                 ctx->addDeviceCtx->state = AuthDecodingState::CANCELED;
    1093           0 :                 if (ctx->addDeviceCtx->channel) {
    1094             :                     // Create and send canceled message
    1095           0 :                     auto canceledMsg = ctx->addDeviceCtx->createCanceledMsg();
    1096           0 :                     msgpack::sbuffer buffer(UINT16_MAX);
    1097           0 :                     msgpack::pack(buffer, canceledMsg);
    1098           0 :                     std::error_code ec;
    1099           0 :                     ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(buffer.data()),
    1100             :                                                       buffer.size(),
    1101             :                                                       ec);
    1102           0 :                     ctx->addDeviceCtx->channel->shutdown();
    1103           0 :                 }
    1104             :             }
    1105           0 :             if (ctx->onFailure)
    1106           0 :                 ctx->onFailure(AuthError::UNKNOWN, "");
    1107           0 :             authCtx_.reset();
    1108           0 :             return true;
    1109             :         }
    1110           0 :     }
    1111           0 :     return false;
    1112             : }
    1113             : 
    1114             : bool
    1115           0 : ArchiveAccountManager::confirmAddDevice(uint32_t token)
    1116             : {
    1117           0 :     if (auto ctx = authCtx_) {
    1118           0 :         if (ctx->token == token && ctx->addDeviceCtx && ctx->addDeviceCtx->state == AuthDecodingState::EST) {
    1119           0 :             dht::ThreadPool::io().run([ctx] {
    1120           0 :                 ctx->addDeviceCtx->state = AuthDecodingState::AUTH;
    1121           0 :                 AuthMsg toSend;
    1122           0 :                 JAMI_DEBUG("[LinkDevice] SOURCE: Packing first message for NEW and switching to "
    1123             :                            "state: {}",
    1124             :                            ctx->addDeviceCtx->formattedAuthState());
    1125           0 :                 toSend.set(PayloadKey::authScheme, ctx->addDeviceCtx->authScheme);
    1126           0 :                 msgpack::sbuffer buffer(UINT16_MAX);
    1127           0 :                 msgpack::pack(buffer, toSend);
    1128           0 :                 std::error_code ec;
    1129           0 :                 ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(buffer.data()),
    1130             :                                                   buffer.size(),
    1131             :                                                   ec);
    1132           0 :             });
    1133           0 :             return true;
    1134             :         }
    1135           0 :     }
    1136           0 :     return false;
    1137             : }
    1138             : 
    1139             : void
    1140           2 : ArchiveAccountManager::migrateAccount(AuthContext& ctx)
    1141             : {
    1142           2 :     JAMI_WARN("[Auth] Account migration needed");
    1143           2 :     AccountArchive archive;
    1144             :     try {
    1145           2 :         archive = readArchive(ctx.credentials->password_scheme, ctx.credentials->password);
    1146           0 :     } catch (...) {
    1147           0 :         JAMI_DBG("[Auth] Unable to load archive");
    1148           0 :         ctx.onFailure(AuthError::INVALID_ARGUMENTS, "");
    1149           0 :         return;
    1150           0 :     }
    1151             : 
    1152           2 :     updateArchive(archive);
    1153             : 
    1154           2 :     if (updateCertificates(archive, ctx.credentials->updateIdentity)) {
    1155             :         // because updateCertificates already regenerate a device, we do not need to
    1156             :         // regenerate one in onArchiveLoaded
    1157           2 :         onArchiveLoaded(ctx, std::move(archive), false);
    1158             :     } else {
    1159           0 :         ctx.onFailure(AuthError::UNKNOWN, "");
    1160             :     }
    1161           2 : }
    1162             : 
    1163             : void
    1164         771 : ArchiveAccountManager::onArchiveLoaded(AuthContext& ctx, AccountArchive&& a, bool isLinkDevProtocol)
    1165             : {
    1166        1542 :     auto ethAccount = dev::KeyPair(dev::Secret(a.eth_key)).address().hex();
    1167         771 :     dhtnet::fileutils::check_dir(path_, 0700);
    1168             : 
    1169         771 :     if (isLinkDevProtocol) {
    1170           0 :         a.config[libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD] = ctx.linkDevCtx->authScheme.empty()
    1171             :                                                                                ? FALSE_STR
    1172           0 :                                                                                : TRUE_STR;
    1173             : 
    1174           0 :         a.save(fileutils::getFullPath(path_, archivePath_),
    1175           0 :                ctx.linkDevCtx->authScheme,
    1176           0 :                ctx.linkDevCtx->credentialsFromUser);
    1177             :     } else {
    1178        2313 :         a.config[libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD] = ctx.credentials->password_scheme.empty()
    1179             :                                                                                ? FALSE_STR
    1180        1542 :                                                                                : TRUE_STR;
    1181             : 
    1182         771 :         a.save(fileutils::getFullPath(path_, archivePath_),
    1183        1542 :                ctx.credentials ? ctx.credentials->password_scheme : "",
    1184        1542 :                ctx.credentials ? ctx.credentials->password : "");
    1185             :     }
    1186             : 
    1187         771 :     if (not a.id.second->isCA()) {
    1188           0 :         JAMI_ERROR("[Account {}] [Auth] Attempting to sign a certificate with a non-CA.", accountId_);
    1189             :     }
    1190             : 
    1191         771 :     std::shared_ptr<dht::crypto::Certificate> deviceCertificate;
    1192         771 :     std::unique_ptr<ContactList> contacts;
    1193         771 :     auto usePreviousIdentity = false;
    1194             :     // If updateIdentity got a valid certificate, there is no need for a new cert
    1195         771 :     if (auto oldId = ctx.credentials->updateIdentity.second) {
    1196           4 :         contacts = std::make_unique<ContactList>(ctx.accountId, oldId, path_, onChange_);
    1197           4 :         if (contacts->isValidAccountDevice(*oldId) && ctx.credentials->updateIdentity.first) {
    1198           3 :             deviceCertificate = oldId;
    1199           3 :             usePreviousIdentity = true;
    1200          12 :             JAMI_WARNING("[Account {}] [Auth] Using previously generated device certificate {}",
    1201             :                          accountId_,
    1202             :                          deviceCertificate->getLongId());
    1203             :         } else {
    1204           1 :             contacts.reset();
    1205             :         }
    1206         771 :     }
    1207             : 
    1208             :     // Generate a new device if needed
    1209         771 :     if (!deviceCertificate) {
    1210        3072 :         JAMI_WARNING("[Account {}] [Auth] Creating new device certificate", accountId_);
    1211         768 :         auto request = ctx.request.get();
    1212         768 :         if (not request->verify()) {
    1213           0 :             JAMI_ERROR("[Account {}] [Auth] Invalid certificate request.", accountId_);
    1214           0 :             ctx.onFailure(AuthError::INVALID_ARGUMENTS, "");
    1215           0 :             return;
    1216             :         }
    1217        1536 :         deviceCertificate = std::make_shared<dht::crypto::Certificate>(
    1218        2304 :             dht::crypto::Certificate::generate(*request, a.id));
    1219        3072 :         JAMI_WARNING("[Account {}] [Auth] Created new device: {}", accountId_, deviceCertificate->getLongId());
    1220         768 :     }
    1221             : 
    1222         771 :     auto receipt = makeReceipt(a.id, *deviceCertificate, ethAccount);
    1223         771 :     auto receiptSignature = a.id.first->sign(receipt.first);
    1224             : 
    1225         771 :     auto info = std::make_unique<AccountInfo>();
    1226         771 :     auto pk = usePreviousIdentity ? ctx.credentials->updateIdentity.first : ctx.key.get();
    1227         771 :     auto sharedPk = pk->getSharedPublicKey();
    1228         771 :     info->identity.first = pk;
    1229         771 :     info->identity.second = deviceCertificate;
    1230         771 :     info->accountId = a.id.second->getId().toString();
    1231         771 :     info->devicePk = sharedPk;
    1232         771 :     info->deviceId = info->devicePk->getLongId().toString();
    1233         771 :     if (ctx.deviceName.empty())
    1234           0 :         ctx.deviceName = info->deviceId.substr(8);
    1235             : 
    1236         771 :     if (!contacts) {
    1237         768 :         contacts = std::make_unique<ContactList>(ctx.accountId, a.id.second, path_, onChange_);
    1238             :     }
    1239         771 :     info->contacts = std::move(contacts);
    1240         771 :     info->contacts->setContacts(a.contacts);
    1241         771 :     info->contacts->foundAccountDevice(deviceCertificate, ctx.deviceName, clock::now());
    1242         771 :     info->ethAccount = ethAccount;
    1243         771 :     info->announce = std::move(receipt.second);
    1244         771 :     ConversationModule::saveConvInfosToPath(path_, a.conversations);
    1245         771 :     ConversationModule::saveConvRequestsToPath(path_, a.conversationsRequests);
    1246         771 :     info_ = std::move(info);
    1247             : 
    1248         771 :     ctx.onSuccess(*info_, std::move(a.config), std::move(receipt.first), std::move(receiptSignature));
    1249         771 : }
    1250             : 
    1251             : std::pair<std::vector<uint8_t>, dht::InfoHash>
    1252           0 : ArchiveAccountManager::computeKeys(const std::string& password, const std::string& pin, bool previous)
    1253             : {
    1254             :     // Compute time seed
    1255           0 :     auto now = std::chrono::duration_cast<std::chrono::seconds>(clock::now().time_since_epoch());
    1256           0 :     auto tseed = now.count() / std::chrono::seconds(EXPORT_KEY_RENEWAL_TIME).count();
    1257           0 :     if (previous)
    1258           0 :         tseed--;
    1259           0 :     std::ostringstream ss;
    1260           0 :     ss << std::hex << tseed;
    1261           0 :     auto tseed_str = ss.str();
    1262             : 
    1263             :     // Generate key for archive encryption, using PIN as the salt
    1264           0 :     std::vector<uint8_t> salt_key;
    1265           0 :     salt_key.reserve(pin.size() + tseed_str.size());
    1266           0 :     salt_key.insert(salt_key.end(), pin.begin(), pin.end());
    1267           0 :     salt_key.insert(salt_key.end(), tseed_str.begin(), tseed_str.end());
    1268           0 :     auto key = dht::crypto::stretchKey(password, salt_key, 256 / 8);
    1269             : 
    1270             :     // Generate public storage location as SHA1(key).
    1271           0 :     auto loc = dht::InfoHash::get(key);
    1272             : 
    1273           0 :     return {key, loc};
    1274           0 : }
    1275             : 
    1276             : std::pair<std::string, std::shared_ptr<dht::Value>>
    1277         771 : ArchiveAccountManager::makeReceipt(const dht::crypto::Identity& id,
    1278             :                                    const dht::crypto::Certificate& device,
    1279             :                                    const std::string& ethAccount)
    1280             : {
    1281        3084 :     JAMI_LOG("[Account {}] [Auth] Signing receipt for device {}", accountId_, device.getLongId());
    1282         771 :     auto devId = device.getId();
    1283         771 :     DeviceAnnouncement announcement;
    1284         771 :     announcement.dev = devId;
    1285         771 :     announcement.pk = device.getSharedPublicKey();
    1286         771 :     dht::Value ann_val {announcement};
    1287         771 :     ann_val.sign(*id.first);
    1288             : 
    1289         771 :     auto packedAnnoucement = ann_val.getPacked();
    1290        3084 :     JAMI_LOG("[Account {}] [Auth] Device announcement size: {}", accountId_, packedAnnoucement.size());
    1291             : 
    1292         771 :     std::ostringstream is;
    1293         771 :     is << "{\"id\":\"" << id.second->getId() << "\",\"dev\":\"" << devId << "\",\"eth\":\"" << ethAccount
    1294         771 :        << "\",\"announce\":\"" << base64::encode(packedAnnoucement) << "\"}";
    1295             : 
    1296             :     // auto announce_ = ;
    1297        1542 :     return {is.str(), std::make_shared<dht::Value>(std::move(ann_val))};
    1298         771 : }
    1299             : 
    1300             : bool
    1301           4 : ArchiveAccountManager::needsMigration(const std::string& accountId, const dht::crypto::Identity& id)
    1302             : {
    1303           4 :     if (not id.second)
    1304           0 :         return true;
    1305           4 :     auto cert = id.second->issuer;
    1306           8 :     while (cert) {
    1307           6 :         if (not cert->isCA()) {
    1308           0 :             JAMI_WARNING("[Account {}] [Auth] certificate {} is not a CA, needs update.", accountId, cert->getId());
    1309           0 :             return true;
    1310             :         }
    1311           6 :         if (cert->getExpiration() < clock::now()) {
    1312           8 :             JAMI_WARNING("[Account {}] [Auth] certificate {} is expired, needs update.", accountId, cert->getId());
    1313           2 :             return true;
    1314             :         }
    1315           4 :         cert = cert->issuer;
    1316             :     }
    1317           2 :     return false;
    1318           4 : }
    1319             : 
    1320             : void
    1321         814 : ArchiveAccountManager::syncDevices()
    1322             : {
    1323        3256 :     JAMI_LOG("[Account {}] Building device sync from {}", accountId_, info_->deviceId);
    1324         814 :     onSyncData_(info_->contacts->getSyncData());
    1325         814 : }
    1326             : 
    1327             : AccountArchive
    1328          49 : ArchiveAccountManager::readArchive(std::string_view scheme, const std::string& pwd) const
    1329             : {
    1330         196 :     JAMI_LOG("[Account {}] [Auth] Reading account archive", accountId_);
    1331          98 :     return AccountArchive(fileutils::getFullPath(path_, archivePath_), scheme, pwd);
    1332             : }
    1333             : 
    1334             : void
    1335          42 : ArchiveAccountManager::updateArchive(AccountArchive& archive) const
    1336             : {
    1337             :     using namespace libjami::Account::ConfProperties;
    1338             : 
    1339             :     // Keys not exported to archive
    1340             :     static const auto filtered_keys = {Ringtone::PATH,
    1341             :                                        ARCHIVE_PATH,
    1342             :                                        DEVICE_ID,
    1343             :                                        DEVICE_NAME,
    1344             :                                        Conf::CONFIG_DHT_PORT,
    1345             :                                        DHT_PROXY_LIST_URL,
    1346             :                                        AUTOANSWER,
    1347             :                                        PROXY_ENABLED,
    1348             :                                        PROXY_SERVER,
    1349             :                                        PROXY_PUSH_TOKEN};
    1350             : 
    1351             :     // Keys with meaning of file path where the contents has to be exported in base64
    1352             :     static const auto encoded_keys = {TLS::CA_LIST_FILE, TLS::CERTIFICATE_FILE, TLS::PRIVATE_KEY_FILE};
    1353             : 
    1354         168 :     JAMI_LOG("[Account {}] [Auth] Building account archive", accountId_);
    1355        2436 :     for (const auto& it : onExportConfig_()) {
    1356             :         // filter-out?
    1357        2394 :         if (std::any_of(std::begin(filtered_keys), std::end(filtered_keys), [&](const auto& key) {
    1358       22680 :                 return key == it.first;
    1359             :             }))
    1360         294 :             continue;
    1361             : 
    1362             :         // file contents?
    1363        2100 :         if (std::any_of(std::begin(encoded_keys), std::end(encoded_keys), [&](const auto& key) {
    1364        6174 :                 return key == it.first;
    1365             :             })) {
    1366             :             try {
    1367         210 :                 archive.config.emplace(it.first, base64::encode(fileutils::loadFile(it.second)));
    1368          42 :             } catch (...) {
    1369          42 :             }
    1370             :         } else
    1371        1974 :             archive.config[it.first] = it.second;
    1372          42 :     }
    1373          42 :     if (info_) {
    1374             :         // If migrating from same archive, info_ will be null
    1375          40 :         archive.contacts = info_->contacts->getContacts();
    1376             :         // Note we do not know accountID_ here, use path
    1377          40 :         archive.conversations = ConversationModule::convInfosFromPath(path_);
    1378          40 :         archive.conversationsRequests = ConversationModule::convRequestsFromPath(path_);
    1379             :     }
    1380          42 : }
    1381             : 
    1382             : void
    1383           2 : ArchiveAccountManager::saveArchive(AccountArchive& archive, std::string_view scheme, const std::string& pwd)
    1384             : {
    1385             :     try {
    1386           2 :         updateArchive(archive);
    1387           2 :         if (archivePath_.empty())
    1388           0 :             archivePath_ = "export.gz";
    1389           2 :         archive.save(fileutils::getFullPath(path_, archivePath_), scheme, pwd);
    1390           0 :     } catch (const std::runtime_error& ex) {
    1391           0 :         JAMI_ERROR("[Account {}] [Auth] Unable to export archive: {}", accountId_, ex.what());
    1392           0 :         return;
    1393           0 :     }
    1394             : }
    1395             : 
    1396             : bool
    1397           4 : ArchiveAccountManager::changePassword(const std::string& password_old, const std::string& password_new)
    1398             : {
    1399             :     try {
    1400           4 :         auto path = fileutils::getFullPath(path_, archivePath_);
    1401           6 :         AccountArchive(path, fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password_old)
    1402           2 :             .save(path, fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password_new);
    1403           2 :         return true;
    1404           6 :     } catch (const std::exception&) {
    1405           2 :         return false;
    1406           2 :     }
    1407             : }
    1408             : 
    1409             : std::vector<uint8_t>
    1410           0 : ArchiveAccountManager::getPasswordKey(const std::string& password)
    1411             : {
    1412             :     try {
    1413           0 :         auto data = dhtnet::fileutils::loadFile(fileutils::getFullPath(path_, archivePath_));
    1414             :         // Try to decrypt to check if password is valid
    1415           0 :         auto key = dht::crypto::aesGetKey(data, password);
    1416           0 :         auto decrypted = dht::crypto::aesDecrypt(dht::crypto::aesGetEncrypted(data), key);
    1417           0 :         return key;
    1418           0 :     } catch (const std::exception& e) {
    1419           0 :         JAMI_ERROR("[Account {}] Error loading archive: {}", accountId_, e.what());
    1420           0 :     }
    1421           0 :     return {};
    1422             : }
    1423             : 
    1424             : bool
    1425           3 : ArchiveAccountManager::revokeDevice(const std::string& device,
    1426             :                                     std::string_view scheme,
    1427             :                                     const std::string& password,
    1428             :                                     RevokeDeviceCallback cb)
    1429             : {
    1430           3 :     auto fa = dht::ThreadPool::computation().getShared<AccountArchive>(
    1431           9 :         [this, scheme = std::string(scheme), password] { return readArchive(scheme, password); });
    1432           3 :     findCertificate(DeviceId(device),
    1433           3 :                     [fa = std::move(fa), scheme = std::string(scheme), password, device, cb, w = weak()](
    1434             :                         const std::shared_ptr<dht::crypto::Certificate>& crt) mutable {
    1435           3 :                         if (not crt) {
    1436           1 :                             cb(RevokeDeviceResult::ERROR_NETWORK);
    1437           1 :                             return;
    1438             :                         }
    1439           2 :                         auto this_ = w.lock();
    1440           2 :                         if (not this_)
    1441           0 :                             return;
    1442           2 :                         this_->info_->contacts->foundAccountDevice(crt);
    1443           2 :                         AccountArchive a;
    1444             :                         try {
    1445           2 :                             a = fa.get();
    1446           0 :                         } catch (...) {
    1447           0 :                             cb(RevokeDeviceResult::ERROR_CREDENTIALS);
    1448           0 :                             return;
    1449           0 :                         }
    1450             :                         // Add revoked device to the revocation list and resign it
    1451           2 :                         if (not a.revoked)
    1452           2 :                             a.revoked = std::make_shared<decltype(a.revoked)::element_type>();
    1453           2 :                         a.revoked->revoke(*crt);
    1454           2 :                         a.revoked->sign(a.id);
    1455             :                         // add to CRL cache
    1456           2 :                         this_->certStore().pinRevocationList(a.id.second->getId().toString(), a.revoked);
    1457           2 :                         this_->certStore().loadRevocations(*a.id.second);
    1458             : 
    1459             :                         // Announce CRL immediately
    1460           2 :                         auto h = a.id.second->getId();
    1461           2 :                         this_->dht_->put(h, a.revoked, dht::DoneCallback {}, {}, true);
    1462             : 
    1463           2 :                         this_->saveArchive(a, scheme, password);
    1464           2 :                         this_->info_->contacts->removeAccountDevice(crt->getLongId());
    1465           2 :                         cb(RevokeDeviceResult::SUCCESS);
    1466           2 :                         this_->syncDevices();
    1467           2 :                     });
    1468           3 :     return false;
    1469           3 : }
    1470             : 
    1471             : bool
    1472          38 : ArchiveAccountManager::exportArchive(const std::string& destinationPath,
    1473             :                                      std::string_view scheme,
    1474             :                                      const std::string& password)
    1475             : {
    1476             :     try {
    1477             :         // Save contacts if possible before exporting
    1478          38 :         AccountArchive archive = readArchive(scheme, password);
    1479          38 :         updateArchive(archive);
    1480          38 :         auto archivePath = fileutils::getFullPath(path_, archivePath_);
    1481          38 :         if (!archive.save(archivePath, scheme, password))
    1482           0 :             return false;
    1483             : 
    1484             :         // Export the file
    1485          38 :         std::error_code ec;
    1486          38 :         std::filesystem::copy_file(archivePath, destinationPath, std::filesystem::copy_options::overwrite_existing, ec);
    1487          38 :         return !ec;
    1488          38 :     } catch (const std::runtime_error& ex) {
    1489           0 :         JAMI_ERR("[Auth] Unable to export archive: %s", ex.what());
    1490           0 :         return false;
    1491           0 :     } catch (...) {
    1492           0 :         JAMI_ERR("[Auth] Unable to export archive: Unable to read archive");
    1493           0 :         return false;
    1494           0 :     }
    1495             : }
    1496             : 
    1497             : bool
    1498           3 : ArchiveAccountManager::isPasswordValid(const std::string& password)
    1499             : {
    1500             :     try {
    1501           3 :         readArchive(fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password);
    1502           3 :         return true;
    1503           0 :     } catch (...) {
    1504           0 :         return false;
    1505           0 :     }
    1506             : }
    1507             : 
    1508             : void
    1509           1 : ArchiveAccountManager::registerName(const std::string& name,
    1510             :                                     std::string_view scheme,
    1511             :                                     const std::string& password,
    1512             :                                     RegistrationCallback cb)
    1513             : {
    1514           1 :     std::string signedName;
    1515           1 :     auto nameLowercase {name};
    1516           1 :     std::transform(nameLowercase.begin(), nameLowercase.end(), nameLowercase.begin(), ::tolower);
    1517           1 :     std::string publickey;
    1518           1 :     std::string accountId;
    1519           1 :     std::string ethAccount;
    1520             : 
    1521             :     try {
    1522           1 :         auto archive = readArchive(scheme, password);
    1523           1 :         auto privateKey = archive.id.first;
    1524           1 :         const auto& pk = privateKey->getPublicKey();
    1525           1 :         publickey = pk.toString();
    1526           1 :         accountId = pk.getId().toString();
    1527           1 :         signedName = base64::encode(privateKey->sign(std::vector<uint8_t>(nameLowercase.begin(), nameLowercase.end())));
    1528           1 :         ethAccount = dev::KeyPair(dev::Secret(archive.eth_key)).address().hex();
    1529           1 :     } catch (const std::exception& e) {
    1530             :         // JAMI_ERR("[Auth] Unable to export account: %s", e.what());
    1531           0 :         cb(NameDirectory::RegistrationResponse::invalidCredentials, name);
    1532           0 :         return;
    1533           0 :     }
    1534             : 
    1535           1 :     nameDir_.get().registerName(accountId, nameLowercase, ethAccount, cb, signedName, publickey);
    1536           1 : }
    1537             : 
    1538             : } // namespace jami

Generated by: LCOV version 1.14