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