LCOV - code coverage report
Current view: top level - src/jamidht - archive_account_manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 73.0 % 952 695
Test Date: 2026-11-09 10:41:14 Functions: 88.9 % 72 64

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

Generated by: LCOV version 2.0-1