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