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