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