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