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