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