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// TODO remove?
274{
275 dht::DhtRunner dht;
276 std::pair<bool, bool> stateOld {false, true};
277 std::pair<bool, bool> stateNew {false, true};
278 bool found {false};
279};
280
281// this enum is for the states of add device TLS protocol
282// used for LinkDeviceProtocolStateChanged = AddDeviceStateChanged
283enum class AuthDecodingState : uint8_t {
284 HANDSHAKE = 0,
285 EST,
286 AUTH,
287 DATA,
288 ERR,
290 DONE,
291 TIMEOUT,
293};
294
295static constexpr std::string_view
297{
298 switch (state) {
299 case AuthDecodingState::HANDSHAKE:
300 return "HANDSHAKE"sv;
301 case AuthDecodingState::EST:
302 return "EST"sv;
303 case AuthDecodingState::AUTH:
304 return "AUTH"sv;
305 case AuthDecodingState::DATA:
306 return "DATA"sv;
307 case AuthDecodingState::AUTH_ERROR:
308 return "AUTH_ERROR"sv;
309 case AuthDecodingState::DONE:
310 return "DONE"sv;
311 case AuthDecodingState::TIMEOUT:
312 return "TIMEOUT"sv;
313 case AuthDecodingState::CANCELED:
314 return "CANCELED"sv;
315 case AuthDecodingState::ERR:
316 default:
317 return "ERR"sv;
318 }
319}
320
321namespace PayloadKey {
322static constexpr auto passwordCorrect = "passwordCorrect"sv;
323static constexpr auto canRetry = "canRetry"sv;
324static constexpr auto accData = "accData"sv;
325static constexpr auto authScheme = "authScheme"sv;
326static constexpr auto password = "password"sv;
327static constexpr auto stateMsg = "stateMsg"sv;
328}
329
331{
332 uint8_t schemeId {0};
333 std::map<std::string, std::string> payload;
334 MSGPACK_DEFINE_MAP(schemeId, payload)
335
336 void set(std::string_view key, std::string_view value) {
337 payload.emplace(std::string(key), std::string(value));
338 }
339
340 auto find(std::string_view key) const { return payload.find(std::string(key)); }
341
342 auto at(std::string_view key) const { return payload.at(std::string(key)); }
343
344 void logMsg() { JAMI_DEBUG("[LinkDevice]\nLinkDevice::logMsg:\n{}", formatMsg()); }
345
346 std::string formatMsg() {
347 std::string logStr = "=========\n";
348 logStr += fmt::format("scheme: {}\n", schemeId);
349 for (const auto& [msgKey, msgVal] : payload) {
350 logStr += fmt::format(" - {}: {}\n", msgKey, msgVal);
351 }
352 logStr += "=========";
353 return logStr;
354 }
355
356 static AuthMsg timeout() {
357 AuthMsg timeoutMsg;
358 timeoutMsg.set(PayloadKey::stateMsg, toString(AuthDecodingState::TIMEOUT));
359 return timeoutMsg;
360 }
361};
362
363struct ArchiveAccountManager::DeviceAuthInfo : public std::map<std::string, std::string>
364{
365 // Static key definitions
366 static constexpr auto token = "token"sv;
367 static constexpr auto error = "error"sv;
368 static constexpr auto auth_scheme = "auth_scheme"sv;
369 static constexpr auto peer_id = "peer_id"sv;
370 static constexpr auto auth_error = "auth_error"sv;
371 static constexpr auto peer_address = "peer_address"sv;
372
373 // Add error enum
374 enum class Error { NETWORK, TIMEOUT, AUTH_ERROR, CANCELED, UNKNOWN, NONE };
375
376 using Map = std::map<std::string, std::string>;
377
378 DeviceAuthInfo() = default;
379 DeviceAuthInfo(const Map& map)
380 : Map(map)
381 {}
383 : Map(std::move(map))
384 {}
385
386 void set(std::string_view key, std::string_view value) {
387 emplace(std::string(key), std::string(value));
388 }
389
391 {
392 std::string errStr;
393 switch (err) {
394 case Error::NETWORK:
395 errStr = "network";
396 break;
397 case Error::TIMEOUT:
398 errStr = "timeout";
399 break;
400 case Error::AUTH_ERROR:
401 errStr = "auth_error";
402 break;
403 case Error::CANCELED:
404 errStr = "canceled";
405 break;
406 case Error::UNKNOWN:
407 errStr = "unknown";
408 break;
409 case Error::NONE:
410 errStr = "";
411 break;
412 }
413 return DeviceAuthInfo {Map {{std::string(error), errStr}}};
414 }
415};
416
418{
419 uint64_t opId;
420 AuthDecodingState state {AuthDecodingState::EST};
421 std::string scheme;
422 bool authEnabled {false};
423 bool archiveTransferredWithoutFailure {false};
424 std::string accData;
425
426 DeviceContextBase(uint64_t operationId, AuthDecodingState initialState)
427 : opId(operationId)
428 , state(initialState)
429 {}
430
431 constexpr std::string_view formattedAuthState() const { return toString(state); }
432
434 {
435 auto stateMsgIt = msg.find(PayloadKey::stateMsg);
436 if (stateMsgIt != msg.payload.end()) {
437 if (stateMsgIt->second == toString(AuthDecodingState::TIMEOUT)) {
438 this->state = AuthDecodingState::TIMEOUT;
439 return true;
440 }
441 }
442 return false;
443 }
444
446 {
447 auto stateMsgIt = msg.find(PayloadKey::stateMsg);
448 if (stateMsgIt != msg.payload.end()) {
449 if (stateMsgIt->second == toString(AuthDecodingState::CANCELED)) {
450 this->state = AuthDecodingState::CANCELED;
451 return true;
452 }
453 }
454 return false;
455 }
456
458 {
459 if (state == AuthDecodingState::AUTH_ERROR) {
460 return DeviceAuthInfo::Error::AUTH_ERROR;
461 } else if (state == AuthDecodingState::TIMEOUT) {
462 return DeviceAuthInfo::Error::TIMEOUT;
463 } else if (state == AuthDecodingState::CANCELED) {
464 return DeviceAuthInfo::Error::CANCELED;
465 } else if (state == AuthDecodingState::ERR) {
466 return DeviceAuthInfo::Error::UNKNOWN;
467 } else if (archiveTransferredWithoutFailure) {
468 return DeviceAuthInfo::Error::NONE;
469 }
470 return DeviceAuthInfo::Error::NETWORK;
471 }
472
473 bool isCompleted() const
474 {
475 return state == AuthDecodingState::DONE || state == AuthDecodingState::ERR
476 || state == AuthDecodingState::AUTH_ERROR || state == AuthDecodingState::TIMEOUT
477 || state == AuthDecodingState::CANCELED;
478 }
479};
480
482{
483 dht::crypto::Identity tmpId;
484 dhtnet::ConnectionManager tempConnMgr;
485 unsigned numOpenChannels {0};
486 unsigned maxOpenChannels {1};
487 std::shared_ptr<dhtnet::ChannelSocket> channel;
488 msgpack::unpacker pac {[](msgpack::type::object_type, std::size_t, void*) { return true; },
489 nullptr,
490 512};
491 std::string authScheme {fileutils::ARCHIVE_AUTH_SCHEME_NONE};
492 std::string credentialsFromUser {""};
493
494 LinkDeviceContext(dht::crypto::Identity id)
496 , tmpId(std::move(id))
497 , tempConnMgr(tmpId)
498 {}
499};
500
502{
503 unsigned numTries {0};
504 unsigned maxTries {3};
505 std::shared_ptr<dhtnet::ChannelSocket> channel;
506 std::string_view authScheme;
507 std::string credentials;
508
509 AddDeviceContext(std::shared_ptr<dhtnet::ChannelSocket> c)
511 , channel(std::move(c))
512 {}
513
515 {
516 AuthMsg timeoutMsg;
517 timeoutMsg.set(PayloadKey::stateMsg, toString(AuthDecodingState::CANCELED));
518 return timeoutMsg;
519 }
520};
521
522bool
523ArchiveAccountManager::provideAccountAuthentication(const std::string& key,
524 const std::string& scheme)
525{
526 if (scheme != fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD) {
527 JAMI_ERROR("[LinkDevice] Unsupported account authentication scheme attempted.");
528 return false;
529 }
530 auto ctx = authCtx_;
531 if (!ctx) {
532 JAMI_WARNING("[LinkDevice] No auth context found.");
533 return false;
534 }
535
536 if (ctx->linkDevCtx->state != AuthDecodingState::AUTH) {
537 JAMI_WARNING("[LinkDevice] Invalid state for providing account authentication.");
538 return false;
539 }
540
541 ctx->linkDevCtx->authScheme = scheme;
542 ctx->linkDevCtx->credentialsFromUser = key;
543 // After authentication, the next step is to receive the account archive from the exporting device
544 ctx->linkDevCtx->state = AuthDecodingState::DATA;
545 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
546 ctx->accountId, static_cast<uint8_t>(DeviceAuthState::IN_PROGRESS), DeviceAuthInfo {});
547
548 dht::ThreadPool::io().run([key = std::move(key), scheme, ctx]() mutable {
549 AuthMsg toSend;
550 toSend.set(PayloadKey::password, std::move(key));
551 msgpack::sbuffer buffer(UINT16_MAX);
552 toSend.logMsg();
553 msgpack::pack(buffer, toSend);
554 std::error_code ec;
555 try {
556 ctx->linkDevCtx->channel->write(reinterpret_cast<const unsigned char*>(buffer.data()),
557 buffer.size(),
558 ec);
559 } catch (const std::exception& e) {
560 JAMI_WARNING("[LinkDevice] Failed to send password over auth ChannelSocket. Channel "
561 "may be invalid.");
562 }
563 });
564
565 return true;
566}
567
569{
570 msgpack::unpacker pac {[](msgpack::type::object_type, std::size_t, void*) { return true; },
571 nullptr,
572 512};
573};
574
575// link device: newDev: creates a new temporary account on the DHT for establishing a TLS connection
576void
577ArchiveAccountManager::startLoadArchiveFromDevice(const std::shared_ptr<AuthContext>& ctx)
578{
579 if (authCtx_) {
580 JAMI_WARNING("[LinkDevice] Already loading archive from device.");
581 ctx->onFailure(AuthError::INVALID_ARGUMENTS, "Already loading archive from device.");
582 return;
583 }
584 JAMI_DEBUG("[LinkDevice] Starting load archive from device {} {}.",
585 fmt::ptr(this),
586 fmt::ptr(ctx));
587 authCtx_ = ctx;
588 // move the account creation to another thread
589 dht::ThreadPool::computation().run([ctx, wthis = weak()] {
590 auto ca = dht::crypto::generateEcIdentity("Jami Temporary CA");
591 if (!ca.first || !ca.second) {
592 throw std::runtime_error("[LinkDevice] Can't generate CA for this account.");
593 }
594 // temporary user for bootstrapping p2p connection is created here
595 auto user = dht::crypto::generateIdentity("Jami Temporary User", ca, 4096, true);
596 if (!user.first || !user.second) {
597 throw std::runtime_error("[LinkDevice] Can't generate identity for this account.");
598 }
599
600 auto this_ = wthis.lock();
601 if (!this_) {
602 JAMI_WARNING("[LinkDevice] Failed to get the ArchiveAccountManager.");
603 return;
604 }
605
606 // establish linkDevCtx
607 ctx->linkDevCtx = std::make_shared<LinkDeviceContext>(
608 dht::crypto::generateIdentity("Jami Temporary device", user));
609 JAMI_LOG("[LinkDevice] Established linkDevCtx. {} {} {}.",
610 fmt::ptr(this_),
611 fmt::ptr(ctx),
612 fmt::ptr(ctx->linkDevCtx));
613
614 // set up auth channel code and also use it as opId
615 auto gen = Manager::instance().getSeededRandomEngine();
616 ctx->linkDevCtx->opId = std::uniform_int_distribution<uint64_t>(100000, 999999)(gen);
617#if TARGET_OS_IOS
618 ctx->linkDevCtx->tempConnMgr.oniOSConnected(
619 [&](const std::string& connType, dht::InfoHash peer_h) { return false; });
620#endif
621 ctx->linkDevCtx->tempConnMgr.onDhtConnected(ctx->linkDevCtx->tmpId.second->getPublicKey());
622
623 auto accountScheme = fmt::format("{}{}/{}",
624 AUTH_URI_SCHEME,
625 ctx->linkDevCtx->tmpId.second->getId(),
626 ctx->linkDevCtx->opId);
627 JAMI_LOG("[LinkDevice] auth scheme will be: {}", accountScheme);
628
629 DeviceAuthInfo info;
630 info.set(DeviceAuthInfo::token, accountScheme);
631
632 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
633 ctx->accountId, static_cast<uint8_t>(DeviceAuthState::TOKEN_AVAILABLE), info);
634
635 ctx->linkDevCtx->tempConnMgr.onICERequest(
636 [wctx = std::weak_ptr(ctx)](const DeviceId& deviceId) {
637 if (auto ctx = wctx.lock()) {
638 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
639 ctx->accountId,
640 static_cast<uint8_t>(DeviceAuthState::CONNECTING),
641 DeviceAuthInfo {});
642 return true;
643 }
644 return false;
645 });
646
647 ctx->linkDevCtx->tempConnMgr.onChannelRequest(
648 [wthis, ctx](const std::shared_ptr<dht::crypto::Certificate>& cert,
649 const std::string& name) {
650 std::string_view url(name);
651 if (!starts_with(url, CHANNEL_SCHEME)) {
653 "[LinkDevice] Temporary connection manager received invalid scheme: {}",
654 name);
655 return false;
656 }
657 auto opStr = url.substr(CHANNEL_SCHEME.size());
658 auto parsedOpId = jami::to_int<uint64_t>(opStr);
659
660 if (ctx->linkDevCtx->opId == parsedOpId
661 && ctx->linkDevCtx->numOpenChannels < ctx->linkDevCtx->maxOpenChannels) {
662 ctx->linkDevCtx->numOpenChannels++;
663 JAMI_DEBUG("[LinkDevice] Opening channel ({}/{}): {}",
664 ctx->linkDevCtx->numOpenChannels,
665 ctx->linkDevCtx->maxOpenChannels,
666 name);
667 return true;
668 }
669 return false;
670 });
671
672 ctx->linkDevCtx->tempConnMgr.onConnectionReady([ctx,
673 accountScheme,
674 wthis](const DeviceId& deviceId,
675 const std::string& name,
676 std::shared_ptr<dhtnet::ChannelSocket> socket) {
677 if (!socket) {
678 JAMI_WARNING("[LinkDevice] Temporary connection manager received invalid socket.");
679 if (ctx->timeout)
680 ctx->timeout->cancel();
681 ctx->timeout.reset();
682 ctx->linkDevCtx->numOpenChannels--;
683 if (auto sthis = wthis.lock())
684 sthis->authCtx_.reset();
685 ctx->linkDevCtx->state = AuthDecodingState::ERR;
686 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
687 ctx->accountId,
688 static_cast<uint8_t>(DeviceAuthState::DONE),
689 DeviceAuthInfo::createError(DeviceAuthInfo::Error::NETWORK));
690 return;
691 }
692 ctx->linkDevCtx->channel = socket;
693
694 ctx->timeout = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext());
695 ctx->timeout->expires_after(OP_TIMEOUT);
696 ctx->timeout->async_wait([c = std::weak_ptr(ctx), socket](const std::error_code& ec) {
697 if (ec) {
698 return;
699 }
700 if (auto ctx = c.lock()) {
701 if (!ctx->linkDevCtx->isCompleted()) {
702 ctx->linkDevCtx->state = AuthDecodingState::TIMEOUT;
703 JAMI_WARNING("[LinkDevice] timeout: {}", socket->name());
704
705 // Create and send timeout message
706 msgpack::sbuffer buffer(UINT16_MAX);
707 msgpack::pack(buffer, AuthMsg::timeout());
708 std::error_code ec;
709 socket->write(reinterpret_cast<const unsigned char*>(buffer.data()),
710 buffer.size(),
711 ec);
712 socket->shutdown();
713 }
714 }
715 });
716
717 socket->onShutdown([ctx, name, wthis]() {
718 JAMI_WARNING("[LinkDevice] Temporary connection manager closing socket: {}", name);
719 if (ctx->timeout)
720 ctx->timeout->cancel();
721 ctx->timeout.reset();
722 ctx->linkDevCtx->numOpenChannels--;
723 ctx->linkDevCtx->channel.reset();
724 if (auto sthis = wthis.lock())
725 sthis->authCtx_.reset();
726
727 DeviceAuthInfo::Error error = ctx->linkDevCtx->getErrorState();
728 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
729 ctx->accountId,
730 static_cast<uint8_t>(DeviceAuthState::DONE),
731 DeviceAuthInfo::createError(error));
732 });
733
734 socket->setOnRecv([ctx,
735 decodingCtx = std::make_shared<DecodingContext>(),
736 wthis](const uint8_t* buf, size_t len) {
737 if (!buf) {
738 return len;
739 }
740
741 decodingCtx->pac.reserve_buffer(len);
742 std::copy_n(buf, len, decodingCtx->pac.buffer());
743 decodingCtx->pac.buffer_consumed(len);
744 AuthMsg toRecv;
745 try {
746 msgpack::object_handle oh;
747 if (decodingCtx->pac.next(oh)) {
748 JAMI_DEBUG("[LinkDevice] NEW: Unpacking message.");
749 oh.get().convert(toRecv);
750 } else {
751 return len;
752 }
753 } catch (const std::exception& e) {
754 ctx->linkDevCtx->state = AuthDecodingState::ERR;
755 JAMI_ERROR("[LinkDevice] Error unpacking message from source device: {}", e.what());
756 return len;
757 }
758
759 JAMI_DEBUG("[LinkDevice] NEW: Successfully unpacked message from source\n{}",
760 toRecv.formatMsg());
761 JAMI_DEBUG("[LinkDevice] NEW: State is {}:{}",
762 ctx->linkDevCtx->scheme,
763 ctx->linkDevCtx->formattedAuthState());
764
765 // check if scheme is supported
766 if (toRecv.schemeId != 0) {
767 JAMI_WARNING("[LinkDevice] NEW: Unsupported scheme received from source");
768 ctx->linkDevCtx->state = AuthDecodingState::ERR;
769 return len;
770 }
771
772 // handle the protocol logic
773 if (ctx->linkDevCtx->handleCanceledMessage(toRecv)) {
774 // import canceled. Will be handeled onShutdown
775 return len;
776 }
777 AuthMsg toSend;
778 bool shouldShutdown = false;
779 auto accDataIt = toRecv.find(PayloadKey::accData);
780 bool shouldLoadArchive = accDataIt != toRecv.payload.end();
781
782 if (ctx->linkDevCtx->state == AuthDecodingState::HANDSHAKE) {
783 auto peerCert = ctx->linkDevCtx->channel->peerCertificate();
784 auto authScheme = toRecv.at(PayloadKey::authScheme);
785 ctx->linkDevCtx->authEnabled = authScheme
786 != fileutils::ARCHIVE_AUTH_SCHEME_NONE;
787
788 JAMI_DEBUG("[LinkDevice] NEW: Auth scheme from payload is '{}'", authScheme);
789 ctx->linkDevCtx->state = AuthDecodingState::AUTH;
790 DeviceAuthInfo info;
791 info.set(DeviceAuthInfo::auth_scheme, authScheme);
792 info.set(DeviceAuthInfo::peer_id, peerCert->issuer->getId().toString());
793 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
794 ctx->accountId, static_cast<uint8_t>(DeviceAuthState::AUTHENTICATING), info);
795 } else if (ctx->linkDevCtx->state == AuthDecodingState::DATA) {
796 auto passwordCorrectIt = toRecv.find(PayloadKey::passwordCorrect);
797 auto canRetry = toRecv.find(PayloadKey::canRetry);
798
799 // If we've reached the maximum number of retry attempts
800 if (canRetry != toRecv.payload.end() && canRetry->second == "false") {
801 JAMI_DEBUG("[LinkDevice] Authentication failed: maximum retry attempts "
802 "reached");
803 ctx->linkDevCtx->state = AuthDecodingState::AUTH_ERROR;
804 return len;
805 }
806
807 // If the password was incorrect but we can still retry
808 if (passwordCorrectIt != toRecv.payload.end()
809 && passwordCorrectIt->second == "false") {
810 ctx->linkDevCtx->state = AuthDecodingState::AUTH;
811
812 JAMI_DEBUG("[LinkDevice] NEW: Password incorrect.");
813 auto peerCert = ctx->linkDevCtx->channel->peerCertificate();
814 auto peer_id = peerCert->issuer->getId().toString();
815 // We received a password incorrect response, so we know we're using
816 // password authentication
817 auto authScheme = fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD;
818
819 DeviceAuthInfo info;
820 info.set(DeviceAuthInfo::auth_scheme, authScheme);
821 info.set(DeviceAuthInfo::peer_id, peer_id);
822 info.set(DeviceAuthInfo::auth_error, "invalid_credentials");
823
824 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
825 ctx->accountId,
826 static_cast<uint8_t>(DeviceAuthState::AUTHENTICATING),
827 info);
828 return len;
829 }
830
831 if (!shouldLoadArchive) {
832 JAMI_DEBUG("[LinkDevice] NEW: no archive received.");
833 // at this point we suppose to have archive. If not, export failed.
834 // Update state and signal will be handeled onShutdown
835 ctx->linkDevCtx->state = AuthDecodingState::ERR;
836 shouldShutdown = true;
837 }
838 }
839
840 // check if an account archive is ready to be loaded
841 if (shouldLoadArchive) {
842 emitSignal<libjami::ConfigurationSignal::DeviceAuthStateChanged>(
843 ctx->accountId,
844 static_cast<uint8_t>(DeviceAuthState::IN_PROGRESS),
845 DeviceAuthInfo {});
846 try {
847 auto archive = AccountArchive(std::string_view(accDataIt->second));
848 if (auto this_ = wthis.lock()) {
849 JAMI_DEBUG("[LinkDevice] NEW: Reading archive from peer.");
850 this_->onArchiveLoaded(*ctx, std::move(archive), true);
851 JAMI_DEBUG("[LinkDevice] NEW: Successfully loaded archive.");
852 ctx->linkDevCtx->archiveTransferredWithoutFailure = true;
853 } else {
854 ctx->linkDevCtx->archiveTransferredWithoutFailure = false;
855 JAMI_ERROR("[LinkDevice] NEW: Failed to load account because of "
856 "null ArchiveAccountManager!");
857 }
858 } catch (const std::exception& e) {
859 ctx->linkDevCtx->state = AuthDecodingState::ERR;
860 ctx->linkDevCtx->archiveTransferredWithoutFailure = false;
861 JAMI_WARNING("[LinkDevice] NEW: Error reading archive.");
862 }
863 shouldShutdown = true;
864 }
865
866 if (shouldShutdown) {
867 ctx->linkDevCtx->channel->shutdown();
868 }
869
870 return len;
871 }); // !onConnectionReady // TODO emit AuthStateChanged+"connection ready" signal
872
873 ctx->linkDevCtx->state = AuthDecodingState::HANDSHAKE;
874 // send first message to establish scheme
875 AuthMsg toSend;
876 toSend.schemeId = 0; // set latest scheme here
877 JAMI_DEBUG("[LinkDevice] NEW: Packing first message for SOURCE.\nCurrent state is: "
878 "\n\tauth "
879 "state = {}:{}",
880 toSend.schemeId,
881 ctx->linkDevCtx->formattedAuthState());
882 msgpack::sbuffer buffer(UINT16_MAX);
883 msgpack::pack(buffer, toSend);
884 std::error_code ec;
885 ctx->linkDevCtx->channel->write(reinterpret_cast<const unsigned char*>(buffer.data()),
886 buffer.size(),
887 ec);
888
889 JAMI_LOG("[LinkDevice {}] Generated temporary account.",
890 ctx->linkDevCtx->tmpId.second->getId());
891 });
892 });
893 JAMI_DEBUG("[LinkDevice] Starting load archive from device END {} {}.",
894 fmt::ptr(this),
895 fmt::ptr(ctx));
896}
897
898int32_t
899ArchiveAccountManager::addDevice(const std::string& uriProvided,
900 std::string_view auth_scheme,
901 AuthChannelHandler* channelHandler)
902{
903 if (authCtx_) {
904 JAMI_WARNING("[LinkDevice] addDevice: auth context already exists.");
905 return static_cast<int32_t>(AccountManager::AddDeviceError::ALREADY_LINKING);
906 }
907 JAMI_LOG("[LinkDevice] ArchiveAccountManager::addDevice({}, {})", accountId_, uriProvided);
908 try {
909 std::string_view url(uriProvided);
910 if (!starts_with(url, AUTH_URI_SCHEME)) {
911 JAMI_ERROR("[LinkDevice] Invalid uri provided: {}", uriProvided);
912 return static_cast<int32_t>(AccountManager::AddDeviceError::INVALID_URI);
913 }
914 auto peerTempAcc = url.substr(AUTH_URI_SCHEME.length(), 40);
915 auto peerCodeS = url.substr(AUTH_URI_SCHEME.length() + peerTempAcc.length() + 1, 6);
916 JAMI_LOG("[LinkDevice] ======\n * tempAcc = {}\n * code = {}", peerTempAcc, peerCodeS);
917
918 auto gen = Manager::instance().getSeededRandomEngine();
919 std::uniform_int_distribution<int32_t> dist(1, INT32_MAX);
920 auto token = dist(gen);
921 JAMI_WARNING("[LinkDevice] SOURCE: Creating auth context, token: {}.", token);
922 auto ctx = std::make_shared<AuthContext>();
923 ctx->accountId = accountId_;
924 ctx->token = token;
925 ctx->credentials = std::make_unique<ArchiveAccountCredentials>();
926 authCtx_ = ctx;
927
928 channelHandler->connect(
929 dht::InfoHash(peerTempAcc),
930 fmt::format("{}{}", CHANNEL_SCHEME, peerCodeS),
931 [wthis = weak(), auth_scheme, ctx, accountId=accountId_](std::shared_ptr<dhtnet::ChannelSocket> socket,
932 const dht::InfoHash& infoHash) {
933 auto this_ = wthis.lock();
934 if (!socket || !this_) {
936 "[LinkDevice] Invalid socket event while AccountManager connecting.");
937 if (this_)
938 this_->authCtx_.reset();
939 emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(
940 accountId,
941 ctx->token,
942 static_cast<uint8_t>(DeviceAuthState::DONE),
943 DeviceAuthInfo::createError(DeviceAuthInfo::Error::NETWORK));
944 } else {
945 if (!this_->doAddDevice(auth_scheme, ctx, socket))
946 emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(
947 accountId,
948 ctx->token,
949 static_cast<uint8_t>(DeviceAuthState::DONE),
950 DeviceAuthInfo::createError(DeviceAuthInfo::Error::UNKNOWN));
951 }
952 });
953 runOnMainThread([token, id = accountId_] {
954 emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(
955 id, token, static_cast<uint8_t>(DeviceAuthState::CONNECTING), DeviceAuthInfo {});
956 });
957 return token;
958 } catch (const std::exception& e) {
959 JAMI_ERROR("[LinkDevice] Parsing uri failed: {}", uriProvided);
960 return static_cast<int32_t>(AccountManager::AddDeviceError::GENERIC);
961 }
962}
963
964bool
965ArchiveAccountManager::doAddDevice(std::string_view scheme,
966 const std::shared_ptr<AuthContext>& ctx,
967 const std::shared_ptr<dhtnet::ChannelSocket>& channel)
968{
969 if (ctx->canceled) {
970 JAMI_WARNING("[LinkDevice] SOURCE: addDevice canceled.");
971 channel->shutdown();
972 return false;
973 }
974 JAMI_DEBUG("[LinkDevice] Setting up addDevice logic on SOURCE device.");
975 JAMI_DEBUG("[LinkDevice] SOURCE: Creating addDeviceCtx.");
976 ctx->addDeviceCtx = std::make_unique<AddDeviceContext>(channel);
977 ctx->addDeviceCtx->authScheme = scheme;
978 ctx->addDeviceCtx->state = AuthDecodingState::HANDSHAKE;
979
980 ctx->timeout = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext());
981 ctx->timeout->expires_after(OP_TIMEOUT);
982 ctx->timeout->async_wait(
983 [wthis = weak(), wctx = std::weak_ptr(ctx)](const std::error_code& ec) {
984 if (ec) {
985 return;
986 }
987 if (auto ctx = wctx.lock()) {
988 if (!ctx->addDeviceCtx->isCompleted()) {
989 if (auto this_ = wthis.lock()) {
990 ctx->addDeviceCtx->state = AuthDecodingState::TIMEOUT;
991 JAMI_WARNING("[LinkDevice] Timeout for addDevice.");
992
993 // Create and send timeout message
994 msgpack::sbuffer buffer(UINT16_MAX);
995 msgpack::pack(buffer, AuthMsg::timeout());
996 std::error_code ec;
997 ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(
998 buffer.data()),
999 buffer.size(),
1000 ec);
1001 ctx->addDeviceCtx->channel->shutdown();
1002 }
1003 }
1004 }
1005 });
1006
1007 JAMI_DEBUG("[LinkDevice] SOURCE: Creating callbacks.");
1008 channel->onShutdown([ctx, w = weak()]() {
1009 JAMI_DEBUG("[LinkDevice] SOURCE: Shutdown with state {}... xfer {}uccessful",
1010 ctx->addDeviceCtx->formattedAuthState(),
1011 ctx->addDeviceCtx->archiveTransferredWithoutFailure ? "s" : "uns");
1012 // check if the archive was successfully loaded and emitSignal
1013 if (ctx->timeout)
1014 ctx->timeout->cancel();
1015 ctx->timeout.reset();
1016
1017 if (auto this_ = w.lock()) {
1018 this_->authCtx_.reset();
1019 }
1020
1021 DeviceAuthInfo::Error error = ctx->addDeviceCtx->getErrorState();
1022 emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(ctx->accountId,
1023 ctx->token,
1024 static_cast<uint8_t>(
1025 DeviceAuthState::DONE),
1026 DeviceAuthInfo::createError(
1027 error));
1028 });
1029
1030 // for now we only have one valid protocol (version is AuthMsg::scheme = 0) but can later
1031 // add in more schemes inside this callback function
1032 JAMI_DEBUG("[LinkDevice] Setting up receiving logic callback.");
1033 channel->setOnRecv([ctx,
1034 wthis = weak(),
1035 decodeCtx = std::make_shared<ArchiveAccountManager::DecodingContext>()](
1036 const uint8_t* buf, size_t len) {
1037 JAMI_DEBUG("[LinkDevice] Setting up receiver callback for communication logic on SOURCE "
1038 "device.");
1039 // when archive is sent to newDev we will get back a success or fail response before the
1040 // connection closes and we need to handle this and pass it to the shutdown callback
1041 auto this_ = wthis.lock();
1042 if (!this_) {
1043 JAMI_ERROR("[LinkDevice] Invalid state for ArchiveAccountManager.");
1044 return (size_t) 0;
1045 }
1046
1047 if (!buf) {
1048 JAMI_ERROR("[LinkDevice] Invalid buffer.");
1049 return (size_t) 0;
1050 }
1051
1052 if (ctx->canceled || ctx->addDeviceCtx->state == AuthDecodingState::ERR) {
1053 JAMI_ERROR("[LinkDevice] Error.");
1054 return (size_t) 0;
1055 }
1056
1057 decodeCtx->pac.reserve_buffer(len);
1058 std::copy_n(buf, len, decodeCtx->pac.buffer());
1059 decodeCtx->pac.buffer_consumed(len);
1060
1061 // handle unpacking the data from the peer
1062 JAMI_DEBUG("[LinkDevice] SOURCE: addDevice: setOnRecv: handling msg from NEW");
1063 msgpack::object_handle oh;
1064 AuthMsg toRecv;
1065 try {
1066 if (decodeCtx->pac.next(oh)) {
1067 oh.get().convert(toRecv);
1068 JAMI_DEBUG("[LinkDevice] SOURCE: Successfully unpacked message from NEW "
1069 "(NEW->SOURCE)\n{}",
1070 toRecv.formatMsg());
1071 } else {
1072 return len;
1073 }
1074 } catch (const std::exception& e) {
1075 // set the generic error state in the context
1076 ctx->addDeviceCtx->state = AuthDecodingState::ERR;
1077 JAMI_ERROR("[LinkDevice] error unpacking message from new device: {}", e.what()); // also warn in logs
1078 }
1079
1080 JAMI_DEBUG("[LinkDevice] SOURCE: State is '{}'", ctx->addDeviceCtx->formattedAuthState());
1081
1082 // It's possible to start handling different protocol scheme numbers here
1083 // one possibility is for multi-account xfer in the future
1084 // validate the scheme
1085 if (toRecv.schemeId != 0) {
1086 ctx->addDeviceCtx->state = AuthDecodingState::ERR;
1087 JAMI_WARNING("[LinkDevice] Unsupported scheme received from a connection.");
1088 }
1089
1090 if (ctx->addDeviceCtx->state == AuthDecodingState::ERR
1091 || ctx->addDeviceCtx->state == AuthDecodingState::AUTH_ERROR) {
1092 JAMI_WARNING("[LinkDevice] Undefined behavior encountered during a link auth session.");
1093 ctx->addDeviceCtx->channel->shutdown();
1094 }
1095 // Check for timeout message
1096 if (ctx->addDeviceCtx->handleTimeoutMessage(toRecv)) {
1097 return len;
1098 }
1099 AuthMsg toSend;
1100 bool shouldSendMsg = false;
1101 bool shouldShutdown = false;
1102 bool shouldSendArchive = false;
1103
1104 // we expect to be receiving credentials in this state and we know the archive is encrypted
1105 if (ctx->addDeviceCtx->state == AuthDecodingState::AUTH) {
1106 // receive the incoming password, check if the password is right, and send back the
1107 // archive if it is correct
1108 JAMI_DEBUG("[LinkDevice] SOURCE: addDevice: setOnRecv: verifying sent "
1109 "credentials from NEW");
1110 shouldSendMsg = true;
1111 const auto& passwordIt = toRecv.find(PayloadKey::password);
1112 if (passwordIt != toRecv.payload.end()) {
1113 // try and decompress archive for xfer
1114 try {
1115 JAMI_DEBUG("[LinkDevice] Injecting account archive into outbound message.");
1116 ctx->addDeviceCtx->accData
1117 = this_
1118 ->readArchive(fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD,
1119 passwordIt->second)
1120 .serialize();
1121 shouldSendArchive = true;
1122 JAMI_DEBUG("[LinkDevice] Sending account archive.");
1123 } catch (const std::exception& e) {
1124 ctx->addDeviceCtx->state = AuthDecodingState::ERR;
1125 JAMI_DEBUG("[LinkDevice] Finished reading archive: FAILURE: {}", e.what());
1126 shouldSendArchive = false;
1127 }
1128 }
1129 if (!shouldSendArchive) {
1130 // pass is not valid
1131 if (ctx->addDeviceCtx->numTries < ctx->addDeviceCtx->maxTries) {
1132 // can retry auth
1133 ctx->addDeviceCtx->numTries++;
1134 JAMI_DEBUG("[LinkDevice] Incorrect password received. "
1135 "Attempt {} out of {}.",
1136 ctx->addDeviceCtx->numTries,
1137 ctx->addDeviceCtx->maxTries);
1138 toSend.set(PayloadKey::passwordCorrect, "false");
1139 toSend.set(PayloadKey::canRetry, "true");
1140 } else {
1141 // cannot retry auth
1142 JAMI_WARNING("[LinkDevice] Incorrect password received, maximum attempts reached.");
1143 toSend.set(PayloadKey::canRetry, "false");
1144 ctx->addDeviceCtx->state = AuthDecodingState::AUTH_ERROR;
1145 shouldShutdown = true;
1146 }
1147 }
1148 }
1149
1150 if (shouldSendArchive) {
1151 JAMI_DEBUG("[LinkDevice] SOURCE: Archive in message has encryption scheme '{}'",
1152 ctx->addDeviceCtx->authScheme);
1153 emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(
1154 ctx->accountId,
1155 ctx->token,
1156 static_cast<uint8_t>(DeviceAuthState::IN_PROGRESS),
1157 DeviceAuthInfo {});
1158 shouldShutdown = true;
1159 shouldSendMsg = true;
1160 ctx->addDeviceCtx->archiveTransferredWithoutFailure = true;
1161 toSend.set(PayloadKey::accData, ctx->addDeviceCtx->accData);
1162 }
1163 if (shouldSendMsg) {
1164 JAMI_DEBUG("[LinkDevice] SOURCE: Sending msg to NEW:\n{}", toSend.formatMsg());
1165 msgpack::sbuffer buffer(UINT16_MAX);
1166 msgpack::pack(buffer, toSend);
1167 std::error_code ec;
1168 ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(buffer.data()),
1169 buffer.size(),
1170 ec);
1171 }
1172
1173 if (shouldShutdown) {
1174 ctx->addDeviceCtx->channel->shutdown();
1175 }
1176
1177 return len;
1178 }); // !channel onRecv closure
1179
1180 if (ctx->addDeviceCtx->state == AuthDecodingState::HANDSHAKE) {
1181 ctx->addDeviceCtx->state = AuthDecodingState::EST;
1182 DeviceAuthInfo info;
1183 info.set(DeviceAuthInfo::peer_address, channel->getRemoteAddress().toString(true));
1184 emitSignal<libjami::ConfigurationSignal::AddDeviceStateChanged>(
1185 ctx->accountId, ctx->token, static_cast<uint8_t>(DeviceAuthState::AUTHENTICATING), info);
1186 }
1187
1188 return true;
1189}
1190
1191bool
1192ArchiveAccountManager::cancelAddDevice(uint32_t token)
1193{
1194 if (auto ctx = authCtx_) {
1195 if (ctx->token == token) {
1196 ctx->canceled = true;
1197 if (ctx->addDeviceCtx) {
1198 ctx->addDeviceCtx->state = AuthDecodingState::CANCELED;
1199 if (ctx->addDeviceCtx->channel) {
1200 // Create and send canceled message
1201 auto canceledMsg = ctx->addDeviceCtx->createCanceledMsg();
1202 msgpack::sbuffer buffer(UINT16_MAX);
1203 msgpack::pack(buffer, canceledMsg);
1204 std::error_code ec;
1205 ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(
1206 buffer.data()),
1207 buffer.size(),
1208 ec);
1209 ctx->addDeviceCtx->channel->shutdown();
1210 }
1211 }
1212 if (ctx->onFailure)
1213 ctx->onFailure(AuthError::UNKNOWN, "");
1214 authCtx_.reset();
1215 return true;
1216 }
1217 }
1218 return false;
1219}
1220
1221bool
1222ArchiveAccountManager::confirmAddDevice(uint32_t token)
1223{
1224 if (auto ctx = authCtx_) {
1225 if (ctx->token == token && ctx->addDeviceCtx
1226 && ctx->addDeviceCtx->state == AuthDecodingState::EST) {
1227 dht::ThreadPool::io().run([ctx] {
1228 ctx->addDeviceCtx->state = AuthDecodingState::AUTH;
1229 AuthMsg toSend;
1230 JAMI_DEBUG("[LinkDevice] SOURCE: Packing first message for NEW and switching to "
1231 "state: {}",
1232 ctx->addDeviceCtx->formattedAuthState());
1233 toSend.set(PayloadKey::authScheme, ctx->addDeviceCtx->authScheme);
1234 msgpack::sbuffer buffer(UINT16_MAX);
1235 msgpack::pack(buffer, toSend);
1236 std::error_code ec;
1237 ctx->addDeviceCtx->channel->write(reinterpret_cast<const unsigned char*>(
1238 buffer.data()),
1239 buffer.size(),
1240 ec);
1241 });
1242 return true;
1243 }
1244 }
1245 return false;
1246}
1247
1248void
1249ArchiveAccountManager::loadFromDHT(const std::shared_ptr<AuthContext>& ctx)
1250{
1251 ctx->dhtContext = std::make_unique<DhtLoadContext>();
1252 ctx->dhtContext->dht.run(ctx->credentials->dhtPort, {}, true);
1253 for (const auto& bootstrap : ctx->credentials->dhtBootstrap) {
1254 ctx->dhtContext->dht.bootstrap(bootstrap);
1255 auto searchEnded = [ctx, accountId = accountId_]() {
1256 if (not ctx->dhtContext or ctx->dhtContext->found) {
1257 return;
1258 }
1259 auto& s = *ctx->dhtContext;
1260 if (s.stateOld.first && s.stateNew.first) {
1261 dht::ThreadPool::computation().run(
1262 [ctx,
1263 network_error = !s.stateOld.second && !s.stateNew.second,
1264 accountId = std::move(accountId)] {
1265 ctx->dhtContext.reset();
1266 JAMI_WARNING("[Account {}] [Auth] Failure looking for archive on DHT: {}",
1267 accountId,
1268 network_error ? "network error" : "not found");
1269 ctx->onFailure(network_error ? AuthError::NETWORK : AuthError::UNKNOWN, "");
1270 });
1271 }
1272 };
1273
1274 auto search = [ctx, searchEnded, w = weak()](bool previous) {
1275 std::vector<uint8_t> key;
1276 dht::InfoHash loc;
1277 auto& s = previous ? ctx->dhtContext->stateOld : ctx->dhtContext->stateNew;
1278
1279 // compute archive location and decryption keys
1280 try {
1281 std::tie(key, loc) = computeKeys(ctx->credentials->password,
1282 ctx->credentials->uri,
1283 previous);
1284 JAMI_LOG("[Auth] Attempting to load account from DHT with {:s} at {:s}",
1285 ctx->credentials->uri,
1286 loc.toString());
1287 if (not ctx->dhtContext or ctx->dhtContext->found) {
1288 return;
1289 }
1290 ctx->dhtContext->dht.get(
1291 loc,
1292 [ctx, key = std::move(key), w](const std::shared_ptr<dht::Value>& val) {
1293 std::vector<uint8_t> decrypted;
1294 try {
1295 decrypted = archiver::decompress(
1296 dht::crypto::aesDecrypt(val->data, key));
1297 } catch (const std::exception& ex) {
1298 return true;
1299 }
1300 JAMI_DBG("[Auth] Found archive on the DHT");
1301 ctx->dhtContext->found = true;
1302 dht::ThreadPool::computation().run(
1303 [ctx, decrypted = std::move(decrypted), w] {
1304 try {
1305 auto archive = AccountArchive(decrypted);
1306 if (auto sthis = w.lock()) {
1307 if (ctx->dhtContext) {
1308 ctx->dhtContext->dht.join();
1309 ctx->dhtContext.reset();
1310 }
1311 sthis->onArchiveLoaded(*ctx, std::move(archive), false);
1312 }
1313 } catch (const std::exception& e) {
1314 ctx->onFailure(AuthError::UNKNOWN, "");
1315 }
1316 });
1317 return not ctx->dhtContext->found;
1318 },
1319 [=, &s](bool ok) {
1320 JAMI_LOG("[Auth] DHT archive search ended at {}", loc.toString());
1321 s.first = true;
1322 s.second = ok;
1323 searchEnded();
1324 });
1325 } catch (const std::exception& e) {
1326 // JAMI_ERROR("Error computing keys: {}", e.what());
1327 s.first = true;
1328 s.second = true;
1329 searchEnded();
1330 return;
1331 }
1332 };
1333 dht::ThreadPool::computation().run(std::bind(search, true));
1334 dht::ThreadPool::computation().run(std::bind(search, false));
1335 }
1336}
1337
1338void
1339ArchiveAccountManager::migrateAccount(AuthContext& ctx)
1340{
1341 JAMI_WARN("[Auth] Account migration needed");
1342 AccountArchive archive;
1343 try {
1344 archive = readArchive(ctx.credentials->password_scheme, ctx.credentials->password);
1345 } catch (...) {
1346 JAMI_DBG("[Auth] Unable to load archive");
1347 ctx.onFailure(AuthError::INVALID_ARGUMENTS, "");
1348 return;
1349 }
1350
1351 updateArchive(archive);
1352
1353 if (updateCertificates(archive, ctx.credentials->updateIdentity)) {
1354 // because updateCertificates already regenerate a device, we do not need to
1355 // regenerate one in onArchiveLoaded
1356 onArchiveLoaded(ctx, std::move(archive), false);
1357 } else {
1358 ctx.onFailure(AuthError::UNKNOWN, "");
1359 }
1360}
1361
1362void
1363ArchiveAccountManager::onArchiveLoaded(AuthContext& ctx, AccountArchive&& a, bool isLinkDevProtocol)
1364{
1365 auto ethAccount = dev::KeyPair(dev::Secret(a.eth_key)).address().hex();
1366 dhtnet::fileutils::check_dir(path_, 0700);
1367
1368 if (isLinkDevProtocol) {
1370 ctx.linkDevCtx->authScheme.empty() ? FALSE_STR : TRUE_STR;
1371
1372 a.save(fileutils::getFullPath(path_, archivePath_),
1373 ctx.linkDevCtx->authScheme,
1374 ctx.linkDevCtx->credentialsFromUser);
1375 } else {
1377 ctx.credentials->password_scheme.empty() ? FALSE_STR : TRUE_STR;
1378
1379 a.save(fileutils::getFullPath(path_, archivePath_),
1380 ctx.credentials ? ctx.credentials->password_scheme : "",
1381 ctx.credentials ? ctx.credentials->password : "");
1382 }
1383
1384 if (not a.id.second->isCA()) {
1385 JAMI_ERROR("[Account {}] [Auth] Attempting to sign a certificate with a non-CA.",
1386 accountId_);
1387 }
1388
1389 std::shared_ptr<dht::crypto::Certificate> deviceCertificate;
1390 std::unique_ptr<ContactList> contacts;
1391 auto usePreviousIdentity = false;
1392 // If updateIdentity got a valid certificate, there is no need for a new cert
1393 if (auto oldId = ctx.credentials->updateIdentity.second) {
1394 contacts = std::make_unique<ContactList>(ctx.accountId, oldId, path_, onChange_);
1395 if (contacts->isValidAccountDevice(*oldId) && ctx.credentials->updateIdentity.first) {
1396 deviceCertificate = oldId;
1397 usePreviousIdentity = true;
1398 JAMI_WARNING("[Account {}] [Auth] Using previously generated device certificate {}",
1399 accountId_,
1400 deviceCertificate->getLongId());
1401 } else {
1402 contacts.reset();
1403 }
1404 }
1405
1406 // Generate a new device if needed
1407 if (!deviceCertificate) {
1408 JAMI_WARNING("[Account {}] [Auth] Creating new device certificate", accountId_);
1409 auto request = ctx.request.get();
1410 if (not request->verify()) {
1411 JAMI_ERROR("[Account {}] [Auth] Invalid certificate request.", accountId_);
1412 ctx.onFailure(AuthError::INVALID_ARGUMENTS, "");
1413 return;
1414 }
1415 deviceCertificate = std::make_shared<dht::crypto::Certificate>(
1416 dht::crypto::Certificate::generate(*request, a.id));
1417 JAMI_WARNING("[Account {}] [Auth] Created new device: {}",
1418 accountId_,
1419 deviceCertificate->getLongId());
1420 }
1421
1422 auto receipt = makeReceipt(a.id, *deviceCertificate, ethAccount);
1423 auto receiptSignature = a.id.first->sign({receipt.first.begin(), receipt.first.end()});
1424
1425 auto info = std::make_unique<AccountInfo>();
1426 auto pk = usePreviousIdentity ? ctx.credentials->updateIdentity.first : ctx.key.get();
1427 auto sharedPk = pk->getSharedPublicKey();
1428 info->identity.first = pk;
1429 info->identity.second = deviceCertificate;
1430 info->accountId = a.id.second->getId().toString();
1431 info->devicePk = sharedPk;
1432 info->deviceId = info->devicePk->getLongId().toString();
1433 if (ctx.deviceName.empty())
1434 ctx.deviceName = info->deviceId.substr(8);
1435
1436 if (!contacts) {
1437 contacts = std::make_unique<ContactList>(ctx.accountId, a.id.second, path_, onChange_);
1438 }
1439 info->contacts = std::move(contacts);
1440 info->contacts->setContacts(a.contacts);
1441 info->contacts->foundAccountDevice(deviceCertificate, ctx.deviceName, clock::now());
1442 info->ethAccount = ethAccount;
1443 info->announce = std::move(receipt.second);
1444 ConversationModule::saveConvInfosToPath(path_, a.conversations);
1445 ConversationModule::saveConvRequestsToPath(path_, a.conversationsRequests);
1446 info_ = std::move(info);
1447
1448 ctx.onSuccess(*info_,
1449 std::move(a.config),
1450 std::move(receipt.first),
1451 std::move(receiptSignature));
1452}
1453
1454std::pair<std::vector<uint8_t>, dht::InfoHash>
1455ArchiveAccountManager::computeKeys(const std::string& password,
1456 const std::string& pin,
1457 bool previous)
1458{
1459 // Compute time seed
1460 auto now = std::chrono::duration_cast<std::chrono::seconds>(clock::now().time_since_epoch());
1461 auto tseed = now.count() / std::chrono::seconds(EXPORT_KEY_RENEWAL_TIME).count();
1462 if (previous)
1463 tseed--;
1464 std::ostringstream ss;
1465 ss << std::hex << tseed;
1466 auto tseed_str = ss.str();
1467
1468 // Generate key for archive encryption, using PIN as the salt
1469 std::vector<uint8_t> salt_key;
1470 salt_key.reserve(pin.size() + tseed_str.size());
1471 salt_key.insert(salt_key.end(), pin.begin(), pin.end());
1472 salt_key.insert(salt_key.end(), tseed_str.begin(), tseed_str.end());
1473 auto key = dht::crypto::stretchKey(password, salt_key, 256 / 8);
1474
1475 // Generate public storage location as SHA1(key).
1476 auto loc = dht::InfoHash::get(key);
1477
1478 return {key, loc};
1479}
1480
1481std::pair<std::string, std::shared_ptr<dht::Value>>
1482ArchiveAccountManager::makeReceipt(const dht::crypto::Identity& id,
1483 const dht::crypto::Certificate& device,
1484 const std::string& ethAccount)
1485{
1486 JAMI_LOG("[Account {}] [Auth] Signing receipt for device {}", accountId_, device.getLongId());
1487 auto devId = device.getId();
1488 DeviceAnnouncement announcement;
1489 announcement.dev = devId;
1490 announcement.pk = device.getSharedPublicKey();
1491 dht::Value ann_val {announcement};
1492 ann_val.sign(*id.first);
1493
1494 auto packedAnnoucement = ann_val.getPacked();
1495 JAMI_LOG("[Account {}] [Auth] Device announcement size: {}",
1496 accountId_,
1497 packedAnnoucement.size());
1498
1499 std::ostringstream is;
1500 is << "{\"id\":\"" << id.second->getId() << "\",\"dev\":\"" << devId << "\",\"eth\":\""
1501 << ethAccount << "\",\"announce\":\"" << base64::encode(packedAnnoucement) << "\"}";
1502
1503 // auto announce_ = ;
1504 return {is.str(), std::make_shared<dht::Value>(std::move(ann_val))};
1505}
1506
1507bool
1508ArchiveAccountManager::needsMigration(const std::string& accountId, const dht::crypto::Identity& id)
1509{
1510 if (not id.second)
1511 return true;
1512 auto cert = id.second->issuer;
1513 while (cert) {
1514 if (not cert->isCA()) {
1515 JAMI_WARNING("[Account {}] [Auth] certificate {} is not a CA, needs update.",
1516 accountId,
1517 cert->getId());
1518 return true;
1519 }
1520 if (cert->getExpiration() < clock::now()) {
1521 JAMI_WARNING("[Account {}] [Auth] certificate {} is expired, needs update.",
1522 accountId,
1523 cert->getId());
1524 return true;
1525 }
1526 cert = cert->issuer;
1527 }
1528 return false;
1529}
1530
1531void
1532ArchiveAccountManager::syncDevices()
1533{
1534 if (not dht_ or not dht_->isRunning()) {
1535 JAMI_WARNING("[Account {}] Not syncing devices: DHT is not running", accountId_);
1536 return;
1537 }
1538 JAMI_LOG("[Account {}] Building device sync from {}", accountId_, info_->deviceId);
1539 auto sync_data = info_->contacts->getSyncData();
1540
1541 for (const auto& dev : getKnownDevices()) {
1542 // don't send sync data to ourself
1543 if (dev.first.toString() == info_->deviceId) {
1544 continue;
1545 }
1546 if (!dev.second.certificate) {
1547 JAMI_WARNING("[Account {}] Unable to find certificate for {}", accountId_, dev.first);
1548 continue;
1549 }
1550 auto pk = dev.second.certificate->getSharedPublicKey();
1551 JAMI_LOG("[Account {}] Sending device sync to {} {}",
1552 accountId_,
1553 dev.second.name,
1554 dev.first.toString());
1555 auto syncDeviceKey = dht::InfoHash::get("inbox:" + pk->getId().toString());
1556 dht_->putEncrypted(syncDeviceKey, pk, sync_data);
1557 }
1558}
1559
1560void
1561ArchiveAccountManager::startSync(const OnNewDeviceCb& cb,
1562 const OnDeviceAnnouncedCb& dcb,
1563 bool publishPresence)
1564{
1565 AccountManager::startSync(std::move(cb), std::move(dcb), publishPresence);
1566
1567 dht_->listen<DeviceSync>(
1568 dht::InfoHash::get("inbox:" + info_->devicePk->getId().toString()),
1569 [this](DeviceSync&& sync) {
1570 // Received device sync data.
1571 // check device certificate
1572 findCertificate(
1573 sync.from,
1574 [this, sync](const std::shared_ptr<dht::crypto::Certificate>& cert) mutable {
1575 if (!cert or cert->getId() != sync.from) {
1576 JAMI_WARNING("[Account {}] Unable to find certificate for device {}",
1577 accountId_,
1578 sync.from.toString());
1579 return;
1580 }
1581 if (not foundAccountDevice(cert))
1582 return;
1583 onSyncData(std::move(sync));
1584 });
1585
1586 return true;
1587 });
1588}
1589
1590AccountArchive
1591ArchiveAccountManager::readArchive(std::string_view scheme, const std::string& pwd) const
1592{
1593 JAMI_LOG("[Account {}] [Auth] Reading account archive", accountId_);
1594 return AccountArchive(fileutils::getFullPath(path_, archivePath_), scheme, pwd);
1595}
1596
1597void
1598ArchiveAccountManager::updateArchive(AccountArchive& archive) const
1599{
1600 using namespace libjami::Account::ConfProperties;
1601
1602 // Keys not exported to archive
1603 static const auto filtered_keys = {Ringtone::PATH,
1604 ARCHIVE_PATH,
1605 DEVICE_ID,
1606 DEVICE_NAME,
1607 Conf::CONFIG_DHT_PORT,
1608 DHT_PROXY_LIST_URL,
1609 AUTOANSWER,
1610 PROXY_ENABLED,
1611 PROXY_SERVER,
1612 PROXY_PUSH_TOKEN};
1613
1614 // Keys with meaning of file path where the contents has to be exported in base64
1615 static const auto encoded_keys = {TLS::CA_LIST_FILE,
1616 TLS::CERTIFICATE_FILE,
1617 TLS::PRIVATE_KEY_FILE};
1618
1619 JAMI_LOG("[Account {}] [Auth] Building account archive", accountId_);
1620 for (const auto& it : onExportConfig_()) {
1621 // filter-out?
1622 if (std::any_of(std::begin(filtered_keys), std::end(filtered_keys), [&](const auto& key) {
1623 return key == it.first;
1624 }))
1625 continue;
1626
1627 // file contents?
1628 if (std::any_of(std::begin(encoded_keys), std::end(encoded_keys), [&](const auto& key) {
1629 return key == it.first;
1630 })) {
1631 try {
1632 archive.config.emplace(it.first, base64::encode(fileutils::loadFile(it.second)));
1633 } catch (...) {
1634 }
1635 } else
1636 archive.config[it.first] = it.second;
1637 }
1638 if (info_) {
1639 // If migrating from same archive, info_ will be null
1640 archive.contacts = info_->contacts->getContacts();
1641 // Note we do not know accountID_ here, use path
1642 archive.conversations = ConversationModule::convInfosFromPath(path_);
1643 archive.conversationsRequests = ConversationModule::convRequestsFromPath(path_);
1644 }
1645}
1646
1647void
1648ArchiveAccountManager::saveArchive(AccountArchive& archive,
1649 std::string_view scheme,
1650 const std::string& pwd)
1651{
1652 try {
1653 updateArchive(archive);
1654 if (archivePath_.empty())
1655 archivePath_ = "export.gz";
1656 archive.save(fileutils::getFullPath(path_, archivePath_), scheme, pwd);
1657 } catch (const std::runtime_error& ex) {
1658 JAMI_ERROR("[Account {}] [Auth] Unable to export archive: {}", accountId_, ex.what());
1659 return;
1660 }
1661}
1662
1663bool
1664ArchiveAccountManager::changePassword(const std::string& password_old,
1665 const std::string& password_new)
1666{
1667 try {
1668 auto path = fileutils::getFullPath(path_, archivePath_);
1669 AccountArchive(path, fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password_old)
1670 .save(path, fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password_new);
1671 return true;
1672 } catch (const std::exception&) {
1673 return false;
1674 }
1675}
1676
1677std::vector<uint8_t>
1678ArchiveAccountManager::getPasswordKey(const std::string& password)
1679{
1680 try {
1681 auto data = dhtnet::fileutils::loadFile(fileutils::getFullPath(path_, archivePath_));
1682 // Try to decrypt to check if password is valid
1683 auto key = dht::crypto::aesGetKey(data, password);
1684 auto decrypted = dht::crypto::aesDecrypt(dht::crypto::aesGetEncrypted(data), key);
1685 return key;
1686 } catch (const std::exception& e) {
1687 JAMI_ERROR("[Account {}] Error loading archive: {}", accountId_, e.what());
1688 }
1689 return {};
1690}
1691
1692bool
1693ArchiveAccountManager::revokeDevice(const std::string& device,
1694 std::string_view scheme,
1695 const std::string& password,
1697{
1698 auto fa = dht::ThreadPool::computation().getShared<AccountArchive>(
1699 [this, scheme = std::string(scheme), password] { return readArchive(scheme, password); });
1700 findCertificate(DeviceId(device),
1701 [fa = std::move(fa),
1702 scheme = std::string(scheme),
1703 password,
1704 device,
1705 cb,
1706 w = weak()](
1707 const std::shared_ptr<dht::crypto::Certificate>& crt) mutable {
1708 if (not crt) {
1709 cb(RevokeDeviceResult::ERROR_NETWORK);
1710 return;
1711 }
1712 auto this_ = w.lock();
1713 if (not this_)
1714 return;
1715 this_->info_->contacts->foundAccountDevice(crt);
1717 try {
1718 a = fa.get();
1719 } catch (...) {
1720 cb(RevokeDeviceResult::ERROR_CREDENTIALS);
1721 return;
1722 }
1723 // Add revoked device to the revocation list and resign it
1724 if (not a.revoked)
1725 a.revoked = std::make_shared<decltype(a.revoked)::element_type>();
1726 a.revoked->revoke(*crt);
1727 a.revoked->sign(a.id);
1728 // add to CRL cache
1729 this_->certStore().pinRevocationList(a.id.second->getId().toString(),
1730 a.revoked);
1731 this_->certStore().loadRevocations(*a.id.second);
1732
1733 // Announce CRL immediately
1734 auto h = a.id.second->getId();
1735 this_->dht_->put(h, a.revoked, dht::DoneCallback {}, {}, true);
1736
1737 this_->saveArchive(a, scheme, password);
1738 this_->info_->contacts->removeAccountDevice(crt->getLongId());
1739 cb(RevokeDeviceResult::SUCCESS);
1740 this_->syncDevices();
1741 });
1742 return false;
1743}
1744
1745bool
1746ArchiveAccountManager::exportArchive(const std::string& destinationPath,
1747 std::string_view scheme,
1748 const std::string& password)
1749{
1750 try {
1751 // Save contacts if possible before exporting
1752 AccountArchive archive = readArchive(scheme, password);
1753 updateArchive(archive);
1754 auto archivePath = fileutils::getFullPath(path_, archivePath_);
1755 if (!archive.save(archivePath, scheme, password))
1756 return false;
1757
1758 // Export the file
1759 std::error_code ec;
1760 std::filesystem::copy_file(archivePath,
1761 destinationPath,
1762 std::filesystem::copy_options::overwrite_existing,
1763 ec);
1764 return !ec;
1765 } catch (const std::runtime_error& ex) {
1766 JAMI_ERR("[Auth] Unable to export archive: %s", ex.what());
1767 return false;
1768 } catch (...) {
1769 JAMI_ERR("[Auth] Unable to export archive: Unable to read archive");
1770 return false;
1771 }
1772}
1773
1774bool
1775ArchiveAccountManager::isPasswordValid(const std::string& password)
1776{
1777 try {
1778 readArchive(fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD, password);
1779 return true;
1780 } catch (...) {
1781 return false;
1782 }
1783}
1784
1785#if HAVE_RINGNS
1786void
1787ArchiveAccountManager::registerName(const std::string& name,
1788 std::string_view scheme,
1789 const std::string& password,
1790 RegistrationCallback cb)
1791{
1792 std::string signedName;
1793 auto nameLowercase {name};
1794 std::transform(nameLowercase.begin(), nameLowercase.end(), nameLowercase.begin(), ::tolower);
1795 std::string publickey;
1796 std::string accountId;
1797 std::string ethAccount;
1798
1799 try {
1800 auto archive = readArchive(scheme, password);
1801 auto privateKey = archive.id.first;
1802 const auto& pk = privateKey->getPublicKey();
1803 publickey = pk.toString();
1804 accountId = pk.getId().toString();
1805 signedName = base64::encode(
1806 privateKey->sign(std::vector<uint8_t>(nameLowercase.begin(), nameLowercase.end())));
1807 ethAccount = dev::KeyPair(dev::Secret(archive.eth_key)).address().hex();
1808 } catch (const std::exception& e) {
1809 // JAMI_ERR("[Auth] Unable to export account: %s", e.what());
1810 cb(NameDirectory::RegistrationResponse::invalidCredentials, name);
1811 return;
1812 }
1813
1814 nameDir_.get().registerName(accountId, nameLowercase, ethAccount, cb, signedName, publickey);
1815}
1816#endif
1817
1818} // 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
Definition Address.h:25
static constexpr auto stateMsg
static constexpr auto accData
static constexpr auto password
static constexpr auto passwordCorrect
static constexpr auto canRetry
static constexpr auto authScheme
ArchiveStorageData readArchive(const std::filesystem::path &path, std::string_view scheme, const std::string &pwd)
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