diff --git a/MSVC/ring-daemon.vcxproj b/MSVC/ring-daemon.vcxproj index a5d3abc5f55fb5090e5f57b6e85e761241780705..10d1263f382af2a32177cabe739b2593c6c58901 100644 --- a/MSVC/ring-daemon.vcxproj +++ b/MSVC/ring-daemon.vcxproj @@ -718,6 +718,8 @@ <ClCompile Include="..\src\im\instant_messaging.cpp" /> <ClCompile Include="..\src\im\message_engine.cpp" /> <ClCompile Include="..\src\ip_utils.cpp" /> + <ClCompile Include="..\src\jamidht\account_manager.cpp" /> + <ClCompile Include="..\src\jamidht\contact_list.cpp" /> <ClCompile Include="..\src\logger.cpp" /> <ClCompile Include="..\src\manager.cpp" /> <ClCompile Include="..\src\media\audio\audiobuffer.cpp" /> @@ -876,6 +878,9 @@ <ClInclude Include="..\src\im\instant_messaging.h" /> <ClInclude Include="..\src\im\message_engine.h" /> <ClInclude Include="..\src\ip_utils.h" /> + <ClInclude Include="..\src\jamidht\account_manager.h" /> + <ClInclude Include="..\src\jamidht\contact_list.h" /> + <ClInclude Include="..\src\jamidht\jami_contact.h" /> <ClInclude Include="..\src\logger.h" /> <ClInclude Include="..\src\manager.h" /> <ClInclude Include="..\src\map_utils.h" /> diff --git a/MSVC/ring-daemon.vcxproj.filters b/MSVC/ring-daemon.vcxproj.filters index 2324c271da4f391f8fcf68446ad242869d334315..6efcfd31118a1afc44a7933196544c25f9f2edcd 100644 --- a/MSVC/ring-daemon.vcxproj.filters +++ b/MSVC/ring-daemon.vcxproj.filters @@ -421,6 +421,12 @@ <ClCompile Include="dlfcn.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\src\jamidht\account_manager.cpp"> + <Filter>Source Files\jamidht</Filter> + </ClCompile> + <ClCompile Include="..\src\jamidht\contact_list.cpp"> + <Filter>Source Files\jamidht</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="..\src\account.h"> @@ -867,6 +873,15 @@ <ClInclude Include="dlfcn.h"> <Filter>Source Files</Filter> </ClInclude> + <ClInclude Include="..\src\jamidht\account_manager.h"> + <Filter>Source Files\jamidht</Filter> + </ClInclude> + <ClInclude Include="..\src\jamidht\contact_list.h"> + <Filter>Source Files\jamidht</Filter> + </ClInclude> + <ClInclude Include="..\src\jamidht\jami_contact.h"> + <Filter>Source Files\jamidht</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <None Include="..\src\jamidht\eth\libdevcore\Makefile.am"> diff --git a/bin/Makefile.am b/bin/Makefile.am index 7ba24001846c46e6001aae50d0f939c68f08e847..63f86319d5f16c4edbcbc3b4edf0105cc9a89742 100644 --- a/bin/Makefile.am +++ b/bin/Makefile.am @@ -34,6 +34,6 @@ dring_CXXFLAGS= -I$(top_srcdir)/src ${DBUSCPP_CFLAGS} \ dring_LDADD = dbus/libclient_dbus.la ${DBUSCPP_LIBS} $(top_builddir)/src/libring.la -ldl endif -if RING_NODEJS +if ENABLE_NODEJS SUBDIRS+=nodejs endif diff --git a/configure.ac b/configure.ac index 6cb5ae83bf4a6beeb9d2fb78022ad68b84e03499..208a8b854f014ad4003741fefef5b68bd4c41bfc 100644 --- a/configure.ac +++ b/configure.ac @@ -421,16 +421,16 @@ AC_DEFINE_UNQUOTED([HAVE_RINGNS], `if test "x$enable_ringns" != "xno"; then echo dnl nodejs module AC_ARG_WITH([nodejs], AS_HELP_STRING([--with-nodejs], [Enable NodeJS module])) -AM_CONDITIONAL([RING_NODEJS], test "x$enable_nodejs" != "xno", [Define if you use the NodeJS module]) +AM_CONDITIONAL([ENABLE_NODEJS], test "x$enable_nodejs" != "xno", [Define if you use the NodeJS module]) AC_DEFINE_UNQUOTED([HAVE_NODEJS], `if test "x$enable_ringns" != "xno"; then echo 1; else echo 0; fi`, [Define if you use the NodeJS module]) AS_IF([test "x$with_nodejs" = "xyes"], [ AC_PATH_PROG(SWIG, swig, "") AS_AC_EXPAND(SBINDIR, $sbindir) AC_SUBST(SBINDIR) AC_CONFIG_FILES([bin/nodejs/Makefile]) - AM_CONDITIONAL(RING_NODEJS, true) + AM_CONDITIONAL(ENABLE_NODEJS, true) ], - AM_CONDITIONAL(RING_NODEJS, false) + AM_CONDITIONAL(ENABLE_NODEJS, false) ); AS_IF([test "x$enable_ringns" != "xno"], [ diff --git a/src/im/message_engine.cpp b/src/im/message_engine.cpp index 033fdec71cee957bf5c4f93bc30556164be26e41..9567109196671a667ad4b957784f971e93d7b5f6 100644 --- a/src/im/message_engine.cpp +++ b/src/im/message_engine.cpp @@ -211,7 +211,7 @@ MessageEngine::load() } JAMI_DBG("[Account %s] loaded %lu messages from %s", account_.getAccountID().c_str(), loaded, savePath_.c_str()); } catch (const std::exception& e) { - JAMI_ERR("[Account %s] couldn't load messages from %s: %s", account_.getAccountID().c_str(), savePath_.c_str(), e.what()); + JAMI_DBG("[Account %s] couldn't load messages from %s: %s", account_.getAccountID().c_str(), savePath_.c_str(), e.what()); } } diff --git a/src/jamidht/Makefile.am b/src/jamidht/Makefile.am index 36014ec785fe3eb59e6e93d1773ffe12437684ca..21f0493f7fa95a6ae42fb13471b3b1b71b4f6c2a 100644 --- a/src/jamidht/Makefile.am +++ b/src/jamidht/Makefile.am @@ -19,10 +19,16 @@ libringacc_la_SOURCES = \ accountarchive.cpp \ accountarchive.h \ p2p.cpp \ - p2p.h + p2p.h \ + contact_list.h \ + contact_list.cpp \ + account_manager.h \ + account_manager.cpp if RINGNS libringacc_la_SOURCES += \ namedirectory.cpp \ namedirectory.h endif + +nobase_include_HEADERS= jami_contact.h diff --git a/src/jamidht/account_manager.cpp b/src/jamidht/account_manager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ff69f0dddb58750c4083be3d1cd267507d60a1f0 --- /dev/null +++ b/src/jamidht/account_manager.cpp @@ -0,0 +1,1095 @@ +/* + * Copyright (C) 2014-2019 Savoir-faire Linux Inc. + * Author : Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +#include "account_manager.h" +#include "accountarchive.h" +#include "jamiaccount.h" +#include "base64.h" +#include "dring/account_const.h" +#include "account_schema.h" +#include "archiver.h" + +#include "libdevcrypto/Common.h" + +#include <opendht/thread_pool.h> + +#include <exception> +#include <future> +#include <fstream> + +namespace jami { + +const constexpr auto EXPORT_KEY_RENEWAL_TIME = std::chrono::minutes(20); +constexpr static const char* const DHT_TYPE_NS = "cx.ring"; + +template <typename To, typename From> +std::unique_ptr<To> dynamic_unique_cast(std::unique_ptr<From>&& p) { + if (auto cast = dynamic_cast<To*>(p.get())) { + std::unique_ptr<To> result(cast); + p.release(); + return result; + } + return {}; +} + +void +ArchiveAccountManager::initAuthentication( + CertRequest request, + std::unique_ptr<AccountCredentials> credentials, + AuthSuccessCallback onSuccess, + AuthFailureCallback onFailure, + OnChangeCallback onChange) +{ + auto ctx = std::make_shared<AuthContext>(); + ctx->request = std::move(request); + ctx->credentials = dynamic_unique_cast<ArchiveAccountCredentials>(std::move(credentials)); + ctx->onSuccess = std::move(onSuccess); + ctx->onFailure = std::move(onFailure); + + if (not ctx->credentials) { + onFailure(AuthError::INVALID_ARGUMENTS, "invalid credentials"); + return; + } + + onChange_ = std::move(onChange); + + if (ctx->credentials->scheme == "dht") { + loadFromDHT(ctx); + return; + } + + dht::ThreadPool::computation().run([ + ctx = std::move(ctx), + onAsync = onAsync_ + ]() mutable { + onAsync([ctx = std::move(ctx)](AccountManager& accountManager) mutable { + auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager); + try { + if (ctx->credentials->scheme == "file") { + this_.loadFromFile(ctx); + return; + } else { + if (ctx->credentials->updateIdentity.first and ctx->credentials->updateIdentity.second) { + auto future_keypair = dht::ThreadPool::computation().get<dev::KeyPair>(&dev::KeyPair::create); + AccountArchive a; + JAMI_WARN("[Auth] converting certificate from old account %s", ctx->credentials->updateIdentity.first->getPublicKey().getId().toString().c_str()); + a.id = std::move(ctx->credentials->updateIdentity); + try { + a.ca_key = std::make_shared<dht::crypto::PrivateKey>(fileutils::loadFile("ca.key", this_.path_)); + } catch (...) {} + this_.updateCertificates(a, ctx->credentials->updateIdentity); + auto keypair = future_keypair.get(); + a.eth_key = keypair.secret().makeInsecure().asBytes(); + this_.onArchiveLoaded(*ctx, std::move(a)); + } else { + this_.createAccount(ctx); + } + } + } catch (const std::exception& e) { + ctx->onFailure(AuthError::UNKNOWN, e.what()); + } + }); + }); +} + +bool +ArchiveAccountManager::updateCertificates(AccountArchive& archive, dht::crypto::Identity& device) +{ + JAMI_WARN("Updating certificates"); + using Certificate = dht::crypto::Certificate; + + // We need the CA key to resign certificates + if (not archive.id.first or + not *archive.id.first or + not archive.id.second or + not archive.ca_key or + not *archive.ca_key) + return false; + + // Currently set the CA flag and update expiration dates + bool updated = false; + + auto& cert = archive.id.second; + auto ca = cert->issuer; + // Update CA if possible and relevant + if (not ca or (not ca->issuer and (not ca->isCA() or ca->getExpiration() < clock::now()))) { + ca = std::make_shared<Certificate>(Certificate::generate(*archive.ca_key, "Jami CA", {}, true)); + updated = true; + JAMI_DBG("CA CRT re-generated"); + } + + // Update certificate + if (updated or not cert->isCA() or cert->getExpiration() < clock::now()) { + cert = std::make_shared<Certificate>(Certificate::generate(*archive.id.first, "Jami", dht::crypto::Identity{archive.ca_key, ca}, true)); + updated = true; + JAMI_DBG("Jami CRT re-generated"); + } + + if (updated and device.first and *device.first) { + // update device certificate + device.second = std::make_shared<Certificate>(Certificate::generate(*device.first, "Jami device", archive.id)); + JAMI_DBG("device CRT re-generated"); + } + + return updated; +} + +void +ArchiveAccountManager::createAccount(const std::shared_ptr<AuthContext>& ctx) +{ + AccountArchive a; + auto future_keypair = dht::ThreadPool::computation().get<dev::KeyPair>(&dev::KeyPair::create); + auto ca = dht::crypto::generateIdentity("Jami CA"); + if (!ca.first || !ca.second) { + throw std::runtime_error("Can't generate CA for this account."); + } + a.id = dht::crypto::generateIdentity("Jami", ca, 4096, true); + if (!a.id.first || !a.id.second) { + throw std::runtime_error("Can't generate identity for this account."); + } + JAMI_WARN("[Auth] new account: CA: %s, RingID: %s", + ca.second->getId().toString().c_str(), + a.id.second->getId().toString().c_str()); + a.ca_key = ca.first; + auto keypair = future_keypair.get(); + a.eth_key = keypair.secret().makeInsecure().asBytes(); + onArchiveLoaded(*ctx, std::move(a)); +} + +void +ArchiveAccountManager::loadFromFile(const std::shared_ptr<AuthContext>& ctx) +{ + AccountArchive archive; + try { + archive = AccountArchive(ctx->credentials->uri, ctx->credentials->password); + } catch (const std::exception& ex) { + JAMI_WARN("[Auth] can't read file: %s", ex.what()); + ctx->onFailure(AuthError::UNKNOWN, ex.what()); + return; + } + onArchiveLoaded(*ctx, std::move(archive)); +} + +struct ArchiveAccountManager::DhtLoadContext { + dht::DhtRunner dht; + std::pair<bool, bool> stateOld {false, true}; + std::pair<bool, bool> stateNew {false, true}; + bool found {false}; +}; + +void +ArchiveAccountManager::loadFromDHT(const std::shared_ptr<AuthContext>& ctx) +{ + ctx->dhtContext = std::make_unique<DhtLoadContext>(); + ctx->dhtContext->dht.run(ctx->credentials->dhtPort, {}, true); + for (const auto& bootstrap : ctx->credentials->dhtBootstrap) + ctx->dhtContext->dht.bootstrap(bootstrap); + auto searchEnded = [ctx]() { + if (not ctx->dhtContext or ctx->dhtContext->found) { + return; + } + auto& s = *ctx->dhtContext; + if (s.stateOld.first && s.stateNew.first) { + dht::ThreadPool::computation().run([ctx, network_error = !s.stateOld.second && !s.stateNew.second]{ + ctx->dhtContext.reset(); + JAMI_WARN("[Auth] failure looking for archive on DHT: %s", /**/network_error ? "network error" : "not found"); + ctx->onFailure(network_error ? AuthError::NETWORK : AuthError::UNKNOWN, ""); + }); + } + }; + + auto search = [ctx, searchEnded, onAsync = onAsync_](bool previous) { + std::vector<uint8_t> key; + dht::InfoHash loc; + auto& s = previous ? ctx->dhtContext->stateOld : ctx->dhtContext->stateNew; + + // compute archive location and decryption keys + try { + std::tie(key, loc) = computeKeys(ctx->credentials->password, ctx->credentials->uri, previous); + JAMI_DBG("[Auth] trying to load account from DHT with %s at %s", /**/ctx->credentials->uri.c_str(), loc.toString().c_str()); + ctx->dhtContext->dht.get(loc, [ctx, key=std::move(key), onAsync](const std::shared_ptr<dht::Value>& val) { + std::vector<uint8_t> decrypted; + try { + decrypted = archiver::decompress(dht::crypto::aesDecrypt(val->data, key)); + } catch (const std::exception& ex) { + return true; + } + JAMI_DBG("[Auth] found archive on the DHT"); + ctx->dhtContext->found = true; + dht::ThreadPool::computation().run([ + ctx, + decrypted = std::move(decrypted), + onAsync + ]{ + try { + auto archive = AccountArchive(decrypted); + onAsync([&](AccountManager& accountManager) { + auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager); + if (ctx->dhtContext) { + ctx->dhtContext->dht.join(); + ctx->dhtContext.reset(); + } + this_.onArchiveLoaded(*ctx, std::move(archive)/*, std::move(contacts)*/); + }); + } catch (const std::exception& e) { + ctx->onFailure(AuthError::UNKNOWN, ""); + } + }); + return not ctx->dhtContext->found; + }, [=, &s](bool ok) { + JAMI_DBG("[Auth] DHT archive search ended at %s", /**/loc.toString().c_str()); + s.first = true; + s.second = ok; + searchEnded(); + }); + } catch (const std::exception& e) { + // JAMI_ERR("Error computing kedht::ThreadPool::computation().run(ys: %s", e.what()); + s.first = true; + s.second = true; + searchEnded(); + return; + } + }; + dht::ThreadPool::computation().run(std::bind(search, true)); + dht::ThreadPool::computation().run(std::bind(search, false)); +} + +void +ArchiveAccountManager::onArchiveLoaded( + AuthContext& ctx, + AccountArchive&& a) +{ + auto ethAccount = dev::KeyPair(dev::Secret(a.eth_key)).address().hex(); + fileutils::check_dir(path_.c_str(), 0700); + + auto path = fileutils::getFullPath(path_, archivePath_); + a.save(path, ctx.credentials ? ctx.credentials->password : ""); + + if (not a.id.second->isCA()) { + JAMI_ERR("[Auth] trying to sign a certificate with a non-CA."); + } + JAMI_WARN("generating device certificate"); + + auto request = ctx.request.get(); + if (not request->verify()) { + JAMI_ERR("[Auth] Invalid certificate request."); + ctx.onFailure(AuthError::INVALID_ARGUMENTS, ""); + return; + } + + auto deviceCertificate = std::make_shared<dht::crypto::Certificate>(dht::crypto::Certificate::generate(*request, a.id)); + auto receipt = makeReceipt(a.id, *deviceCertificate, ethAccount); + auto receiptSignature = a.id.first->sign({receipt.first.begin(), receipt.first.end()}); + + auto info = std::make_unique<AccountInfo>(); + info->identity.second = deviceCertificate; + info->contacts = std::make_unique<ContactList>(a.id.second, path_, onChange_); + info->contacts->setContacts(a.contacts); + info->accountId = a.id.second->getId().toString(); + info->deviceId = deviceCertificate->getPublicKey().getId().toString(); + info->ethAccount = ethAccount; + info->announce = std::move(receipt.second); + info_ = std::move(info); + + JAMI_WARN("[Auth] created new device: %s", info_->deviceId.c_str()); + ctx.onSuccess(*info_, {}, std::move(receipt.first), std::move(receiptSignature)); +} + +std::pair<std::vector<uint8_t>, dht::InfoHash> +ArchiveAccountManager::computeKeys(const std::string& password, const std::string& pin, bool previous) +{ + // Compute time seed + auto now = std::chrono::duration_cast<std::chrono::seconds>(clock::now().time_since_epoch()); + auto tseed = now.count() / std::chrono::seconds(EXPORT_KEY_RENEWAL_TIME).count(); + if (previous) + tseed--; + std::stringstream ss; + ss << std::hex << tseed; + auto tseed_str = ss.str(); + + // Generate key for archive encryption, using PIN as the salt + std::vector<uint8_t> salt_key; + salt_key.reserve(pin.size() + tseed_str.size()); + salt_key.insert(salt_key.end(), pin.begin(), pin.end()); + salt_key.insert(salt_key.end(), tseed_str.begin(), tseed_str.end()); + auto key = dht::crypto::stretchKey(password, salt_key, 256/8); + + // Generate public storage location as SHA1(key). + auto loc = dht::InfoHash::get(key); + + return {key, loc}; +} + +std::pair<std::string, std::shared_ptr<dht::Value>> +ArchiveAccountManager::makeReceipt(const dht::crypto::Identity& id, const dht::crypto::Certificate& device, const std::string& ethAccount) +{ + JAMI_DBG("[Auth] signing device receipt"); + auto devId = device.getId(); + DeviceAnnouncement announcement; + announcement.dev = devId; + dht::Value ann_val {announcement}; + ann_val.sign(*id.first); + + std::ostringstream is; + is << "{\"id\":\"" << id.second->getId() + << "\",\"dev\":\"" << devId + << "\",\"eth\":\"" << ethAccount + << "\",\"announce\":\"" << base64::encode(ann_val.getPacked()) << "\"}"; + + //auto announce_ = ; + return {is.str(), std::make_shared<dht::Value>(std::move(ann_val))}; +} + +bool +ArchiveAccountManager::needsMigration(const dht::crypto::Identity& id) +{ + if (not id.second) + return false; + auto cert = id.second->issuer; + while (cert) { + if (not cert->isCA()){ + JAMI_WARN("certificate %s is not a CA, needs update.", cert->getId().toString().c_str()); + return true; + } + if (cert->getExpiration() < clock::now()) { + JAMI_WARN("certificate %s is expired, needs update.", cert->getId().toString().c_str()); + return true; + } + cert = cert->issuer; + } + return false; +} + +dht::crypto::Identity +AccountManager::loadIdentity(const std::string& crt_path, const std::string& key_path, const std::string& key_pwd) const +{ + JAMI_DBG("Loading certificate from '%s' and key from '%s' at %s", crt_path.c_str(), key_path.c_str(), path_.c_str()); + try { + dht::crypto::Certificate dht_cert(fileutils::loadFile(crt_path, path_)); + dht::crypto::PrivateKey dht_key(fileutils::loadFile(key_path, path_), key_pwd); + auto crt_id = dht_cert.getLongId(); + if (!crt_id or crt_id != dht_key.getPublicKey().getLongId()) { + JAMI_ERR("Device certificate not matching public key!"); + return {}; + } + if (not dht_cert.issuer) { + JAMI_ERR("Device certificate %s has no issuer", dht_cert.getId().to_c_str()); + return {}; + } + // load revocation lists for device authority (account certificate). + tls::CertificateStore::instance().loadRevocations(*dht_cert.issuer); + + return { + std::make_shared<dht::crypto::PrivateKey>(std::move(dht_key)), + std::make_shared<dht::crypto::Certificate>(std::move(dht_cert)) + }; + } + catch (const std::exception& e) { + JAMI_ERR("Error loading identity: %s", e.what()); + } + return {}; +} + +const AccountInfo* +AccountManager::useIdentity( + const dht::crypto::Identity& identity, + const std::string& receipt, + const std::vector<uint8_t>& receiptSignature, + OnChangeCallback&& onChange) +{ + if (receipt.empty() or receiptSignature.empty()) + return nullptr; + + if (not identity.first or not identity.second) { + JAMI_ERR("[Auth] no identity provided"); + return nullptr; + } + + auto accountCertificate = identity.second->issuer; + if (not accountCertificate) { + JAMI_ERR("[Auth] device certificate must be issued by the account certificate"); + return nullptr; + } + + // match certificate chain + auto contactList = std::make_unique<ContactList>(accountCertificate, path_, onChange); + if (not contactList->isValidAccountDevice(*identity.second)) { + JAMI_ERR("[Auth] can't use identity: device certificate chain can't be verified"); + return nullptr; + } + + auto pk = accountCertificate->getPublicKey(); + JAMI_DBG("[Auth] checking device receipt for %s", pk.getId().toString().c_str()); + if (!pk.checkSignature({receipt.begin(), receipt.end()}, receiptSignature)) { + JAMI_ERR("[Auth] device receipt signature check failed"); + return nullptr; + } + + Json::Value root; + Json::CharReaderBuilder rbuilder; + auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader()); + if (!reader->parse(&receipt[0], &receipt[receipt.size()], &root, nullptr)) { + JAMI_ERR() << this << " device receipt parsing error"; + return nullptr; + } + + auto dev_id = root["dev"].asString(); + if (dev_id != identity.second->getId().toString()) { + JAMI_ERR("[Auth] device ID mismatch between receipt and certificate"); + return nullptr; + } + auto id = root["id"].asString(); + if (id != pk.getId().toString()) { + JAMI_ERR("[Auth] account ID mismatch between receipt and certificate"); + return nullptr; + } + + dht::Value announce_val; + try { + auto announce = base64::decode(root["announce"].asString()); + msgpack::object_handle announce_msg = msgpack::unpack((const char*)announce.data(), announce.size()); + announce_val.msgpack_unpack(announce_msg.get()); + if (not announce_val.checkSignature()) { + JAMI_ERR("[Auth] announce signature check failed"); + return nullptr; + } + DeviceAnnouncement da; + da.unpackValue(announce_val); + if (da.from.toString() != id or da.dev.toString() != dev_id) { + JAMI_ERR("[Auth] device ID mismatch in announce"); + return nullptr; + } + } catch (const std::exception& e) { + JAMI_ERR("[Auth] can't read announce: %s", e.what()); + return nullptr; + } + + onChange_ = std::move(onChange); + + auto info = std::make_unique<AccountInfo>(); + info->identity = identity; + info->contacts = std::move(contactList); + info->contacts->load(); + info->accountId = id; + info->deviceId = identity.first->getPublicKey().getId().toString(); + info->announce = std::make_shared<dht::Value>(std::move(announce_val)); + info->ethAccount = root["eth"].asString(); + info_ = std::move(info); + + JAMI_DBG("[Auth] Device %s receipt checked successfully for account %s", info_->deviceId.c_str(), id.c_str()); + return info_.get(); +} + +AccountArchive +ArchiveAccountManager::readArchive(const std::string& pwd) const +{ + JAMI_DBG("[Auth] reading account archive"); + return AccountArchive(fileutils::getFullPath(path_, archivePath_), pwd); +} + +void +ArchiveAccountManager::updateArchive(AccountArchive& archive) const +{ + using namespace DRing::Account::ConfProperties; + + // Keys not exported to archive + static const auto filtered_keys = { Ringtone::PATH, + ARCHIVE_PATH, + RING_DEVICE_ID, + RING_DEVICE_NAME, + Conf::CONFIG_DHT_PORT }; + + // Keys with meaning of file path where the contents has to be exported in base64 + static const auto encoded_keys = { TLS::CA_LIST_FILE, + TLS::CERTIFICATE_FILE, + TLS::PRIVATE_KEY_FILE }; + + JAMI_DBG("[Auth] building account archive"); + for (const auto& it : onExportConfig_()) { + // filter-out? + if (std::any_of(std::begin(filtered_keys), std::end(filtered_keys), + [&](const auto& key){ return key == it.first; })) + continue; + + // file contents? + if (std::any_of(std::begin(encoded_keys), std::end(encoded_keys), + [&](const auto& key){ return key == it.first; })) { + try { + archive.config.emplace(it.first, base64::encode(fileutils::loadFile(it.second))); + } catch (...) {} + } else + archive.config.insert(it); + } + archive.contacts = info_->contacts->getContacts(); +} + +void +ArchiveAccountManager::saveArchive(AccountArchive& archive, const std::string& pwd) +{ + try { + updateArchive(archive); + if (archivePath_.empty()) + archivePath_ = "export.gz"; + archive.save(fileutils::getFullPath(path_, archivePath_), pwd); + //archiveHasPassword_ = not pwd.empty(); + } catch (const std::runtime_error& ex) { + JAMI_ERR("[Auth] Can't export archive: %s", ex.what()); + return; + } +} + +bool +ArchiveAccountManager::changePassword(const std::string& password_old, const std::string& password_new) +{ + try { + auto path = fileutils::getFullPath(path_, archivePath_); + AccountArchive(path, password_old).save(path, password_new); + return true; + } catch (const std::exception&) { + return false; + } +} + +std::string +generatePIN(size_t length = 8) +{ + static constexpr const char alphabet[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + dht::crypto::random_device rd; + std::uniform_int_distribution<size_t> dis(0, sizeof(alphabet)-2); + std::string ret; + ret.reserve(length); + for (size_t i=0; i<length; i++) + ret.push_back(alphabet[dis(rd)]); + return ret; +} + +void +ArchiveAccountManager::addDevice(const std::string& password, AddDeviceCallback cb) +{ + dht::ThreadPool::computation().run([onAsync = onAsync_, password, cb = std::move(cb)]() mutable { + onAsync([password = std::move(password), cb = std::move(cb)](AccountManager& accountManager) mutable { + auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager); + + std::vector<uint8_t> key; + dht::InfoHash loc; + std::string pin_str; + AccountArchive a; + try { + JAMI_DBG("[Auth] exporting account"); + + a = this_.readArchive(password); + + // Generate random PIN + pin_str = generatePIN(); + + std::tie(key, loc) = computeKeys(password, pin_str); + } catch (const std::exception& e) { + JAMI_ERR("[Auth] can't export account: %s", e.what()); + cb(AddDeviceResult::ERROR_CREDENTIALS, {}); + return; + } + // now that key and loc are computed, display to user in lowercase + std::transform(pin_str.begin(), pin_str.end(), pin_str.begin(), ::tolower); + try { + this_.updateArchive(a); + auto encrypted = dht::crypto::aesEncrypt(archiver::compress(a.serialize()), key); + if (not this_.dht_->isRunning()) + throw std::runtime_error("DHT is not running.."); + JAMI_WARN("[Auth] exporting account with PIN: %s at %s (size %zu)", pin_str.c_str(), loc.toString().c_str(), encrypted.size()); + this_.dht_->put(loc, encrypted, [cb, pin = std::move(pin_str)](bool ok) { + JAMI_DBG("[Auth] account archive published: %s", ok ? "success" : "failure"); + if (ok) + cb(AddDeviceResult::SUCCESS_SHOW_PIN, pin); + else + cb(AddDeviceResult::ERROR_NETWORK, {}); + }); + } catch (const std::exception& e) { + JAMI_ERR("[Auth] can't export account: %s", e.what()); + cb(AddDeviceResult::ERROR_NETWORK, {}); + return; + } + }); + }); +} + +bool +ArchiveAccountManager::revokeDevice(const std::string& password, const std::string& device, RevokeDeviceCallback cb) +{ + auto fa = dht::ThreadPool::computation().getShared<AccountArchive>([this, password] { return readArchive(password); }); + findCertificate(dht::InfoHash(device), + [fa=std::move(fa),password,device,cb,onAsync=onAsync_](const std::shared_ptr<dht::crypto::Certificate>& crt) mutable + { + if (not crt) { + cb(RevokeDeviceResult::ERROR_NETWORK); + return; + } + onAsync([cb, crt=std::move(crt), fa=std::move(fa), password=std::move(password)](AccountManager& accountManager) mutable { + auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager); + this_.info_->contacts->foundAccountDevice(crt); + AccountArchive a; + try { + a = fa.get(); + } catch (...) { + cb(RevokeDeviceResult::ERROR_CREDENTIALS); + return; + } + // Add revoked device to the revocation list and resign it + if (not a.revoked) + a.revoked = std::make_shared<decltype(a.revoked)::element_type>(); + a.revoked->revoke(*crt); + a.revoked->sign(a.id); + // add to CRL cache + tls::CertificateStore::instance().pinRevocationList(a.id.second->getId().toString(), a.revoked); + tls::CertificateStore::instance().loadRevocations(*a.id.second); + + this_.saveArchive(a, password); + this_.info_->contacts->removeAccountDevice(crt->getId()); + cb(RevokeDeviceResult::SUCCESS); + this_.syncDevices(); + }); + }); + return false; +} + +bool +ArchiveAccountManager::exportArchive(const std::string& destinationPath, const std::string& password) +{ + try { + // Save contacts if possible before exporting + AccountArchive archive = readArchive(password); + updateArchive(archive); + archive.save(fileutils::getFullPath(path_, archivePath_), password); + + // Export the file + auto sourcePath = fileutils::getFullPath(path_, archivePath_); + std::ifstream src(sourcePath, std::ios::in | std::ios::binary); + if (!src) return false; + std::ofstream dst(destinationPath, std::ios::out | std::ios::binary); + dst << src.rdbuf(); + return true; + } catch (const std::runtime_error& ex) { + JAMI_ERR("[Auth] Can't export archive: %s", ex.what()); + return false; + } catch (...) { + JAMI_ERR("[Auth] Can't export archive: can't read archive"); + return false; + } +} + +const std::map<dht::InfoHash, KnownDevice>& +AccountManager::getKnownDevices() const { + return info_->contacts->getKnownDevices(); +} + +bool +AccountManager::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, const std::string& name, const time_point& last_sync) +{ + return info_->contacts->foundAccountDevice(crt, name, last_sync); +} + +void +AccountManager::setAccountDeviceName(const std::string& name) +{ + if (info_) + info_->contacts->setAccountDeviceName(dht::InfoHash(info_->deviceId), name); +} + + +bool +AccountManager::foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, dht::InfoHash& account_id) +{ + if (not crt) + return false; + + auto top_issuer = crt; + while (top_issuer->issuer) + top_issuer = top_issuer->issuer; + + // Device certificate can't be self-signed + if (top_issuer == crt) { + JAMI_WARN("Found invalid peer device: %s", crt->getId().toString().c_str()); + return false; + } + + // Check peer certificate chain + // Trust store with top issuer as the only CA + dht::crypto::TrustList peer_trust; + peer_trust.add(*top_issuer); + if (not peer_trust.verify(*crt)) { + JAMI_WARN("Found invalid peer device: %s", crt->getId().toString().c_str()); + return false; + } + + account_id = crt->issuer->getId(); + JAMI_WARN("Found peer device: %s account:%s CA:%s", crt->getId().toString().c_str(), account_id.toString().c_str(), top_issuer->getId().toString().c_str()); + return true; +} + + +void +AccountManager::addContact(const std::string& uri, bool confirmed) +{ + JAMI_WARN("AccountManager::addContact %d", confirmed); + dht::InfoHash h (uri); + if (not h) { + JAMI_ERR("addContact: invalid contact URI"); + return; + } + if (info_->contacts->addContact(h, confirmed)) { + syncDevices(); + } +} + +void +AccountManager::removeContact(const std::string& uri, bool banned) +{ + dht::InfoHash h (uri); + if (not h) { + JAMI_ERR("removeContact: invalid contact URI"); + return; + } + if (info_->contacts->removeContact(h, banned)) { + syncDevices(); + } +} + +std::vector<std::map<std::string, std::string>> +AccountManager::getContacts() const +{ + if (not info_) { + JAMI_ERR("getContacts(): account not loaded"); + return {}; + } + const auto& contacts = info_->contacts->getContacts(); + std::vector<std::map<std::string, std::string>> ret; + ret.reserve(contacts.size()); + + for (const auto& c : contacts) { + auto details = c.second.toMap(); + if (not details.empty()) { + details["id"] = c.first.toString(); + ret.emplace_back(std::move(details)); + } + } + return ret; +} + +/** Obtain details about one account contact in serializable form. */ +std::map<std::string, std::string> +AccountManager::getContactDetails(const std::string& uri) const +{ + dht::InfoHash h (uri); + if (not h) { + JAMI_ERR("getContactDetails: invalid contact URI"); + return {}; + } + return info_->contacts->getContactDetails(h); +} + +bool +ArchiveAccountManager::findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb) +{ + if (auto cert = tls::CertificateStore::instance().getCertificate(h.toString())) { + if (cb) + cb(cert); + } else { + dht_->findCertificate(h, [cb](const std::shared_ptr<dht::crypto::Certificate>& crt) { + if (crt) + tls::CertificateStore::instance().pinCertificate(crt); + if (cb) + cb(crt); + }); + } + return true; +} + +bool +AccountManager::setCertificateStatus(const std::string& cert_id, tls::TrustStore::PermissionStatus status) +{ + return info_->contacts->setCertificateStatus(cert_id, status); +} + +std::vector<std::string> +AccountManager::getCertificatesByStatus(tls::TrustStore::PermissionStatus status) +{ + return info_->contacts->getCertificatesByStatus(status); +} + +tls::TrustStore::PermissionStatus +AccountManager::getCertificateStatus(const std::string& cert_id) const +{ + return info_->contacts->getCertificateStatus(cert_id); +} + +bool +AccountManager::isAllowed(const crypto::Certificate& crt, bool allowPublic) +{ + return info_->contacts->isAllowed(crt, allowPublic); +} + +std::vector<std::map<std::string, std::string>> +AccountManager::getTrustRequests() const +{ + if (not info_) { + JAMI_ERR("getTrustRequests(): account not loaded"); + return {}; + } + return info_->contacts->getTrustRequests(); +} + +bool +AccountManager::acceptTrustRequest(const std::string& from) +{ + dht::InfoHash f(from); + if (info_->contacts->acceptTrustRequest(f)) { + sendTrustRequestConfirm(f); + syncDevices(); + return true; + } + return false; +} + +bool +AccountManager::discardTrustRequest(const std::string& from) +{ + dht::InfoHash f(from); + return info_->contacts->discardTrustRequest(f); +} + +void +AccountManager::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload) +{ + JAMI_WARN("AccountManager::sendTrustRequest"); + auto toH = dht::InfoHash(to); + if (not toH) { + JAMI_ERR("can't send trust request to invalid hash: %s", to.c_str()); + return; + } + if (info_->contacts->addContact(toH)) { + syncDevices(); + } + forEachDevice(toH, [this,toH,payload](const dht::InfoHash& dev) + { + JAMI_WARN("sending trust request to: %s / %s", toH.toString().c_str(), dev.toString().c_str()); + dht_->putEncrypted(dht::InfoHash::get("inbox:"+dev.toString()), + dev, + dht::TrustRequest(DHT_TYPE_NS, payload)); + }); +} + +void +AccountManager::sendTrustRequestConfirm(const dht::InfoHash& toH) +{ + JAMI_WARN("AccountManager::sendTrustRequestConfirm"); + dht::TrustRequest answer {DHT_TYPE_NS}; + answer.confirm = true; + forEachDevice(toH, [this,toH,answer](const dht::InfoHash& dev) { + JAMI_WARN("sending trust request reply: %s / %s", toH.toString().c_str(), dev.toString().c_str()); + dht_->putEncrypted(dht::InfoHash::get("inbox:"+dev.toString()), dev, answer); + }); +} + + +void +AccountManager::forEachDevice(const dht::InfoHash& to, + std::function<void(const dht::InfoHash&)>&& op, + std::function<void(bool)>&& end) +{ + auto treatedDevices = std::make_shared<std::set<dht::InfoHash>>(); + dht_->get<dht::crypto::RevocationList>(to, [to](dht::crypto::RevocationList&& crl){ + tls::CertificateStore::instance().pinRevocationList(to.toString(), std::move(crl)); + return true; + }); + dht_->get<DeviceAnnouncement>(to, [this,to,treatedDevices,op=std::move(op)](DeviceAnnouncement&& dev) { + if (dev.from != to) + return true; + if (treatedDevices->emplace(dev.dev).second) { + op(dev.dev); + } + return true; + }, [=, end=std::move(end)](bool /*ok*/){ + JAMI_DBG("Found %lu devices for %s", treatedDevices->size(), to.to_c_str()); + if (end) + end(not treatedDevices->empty()); + }); +} + + +void +ArchiveAccountManager::syncDevices() +{ + JAMI_DBG("Building device sync from %s", info_->deviceId.c_str()); + auto sync_data = info_->contacts->getSyncData(); + + for (const auto& dev : getKnownDevices()) { + // don't send sync data to ourself + if (dev.first.toString() == info_->deviceId) + continue; + JAMI_DBG("sending device sync to %s %s", dev.second.name.c_str(), dev.first.toString().c_str()); + auto syncDeviceKey = dht::InfoHash::get("inbox:"+dev.first.toString()); + dht_->putEncrypted(syncDeviceKey, dev.first, sync_data); + } +} + + +void +ArchiveAccountManager::startSync() +{ + // Put device annoucement + if (info_->announce) { + auto h = dht::InfoHash(info_->accountId); + JAMI_DBG("announcing device at %s", h.toString().c_str()); + dht_->put(h, info_->announce, dht::DoneCallback{}, {}, true); + for (const auto& crl : info_->identity.second->issuer->getRevocationLists()) + dht_->put(h, crl, dht::DoneCallback{}, {}, true); + dht_->listen<DeviceAnnouncement>(h, [this](DeviceAnnouncement&& dev) { + findCertificate(dev.dev, [this](const std::shared_ptr<dht::crypto::Certificate>& crt) { + foundAccountDevice(crt); + }); + return true; + }); + dht_->listen<dht::crypto::RevocationList>(h, [this](dht::crypto::RevocationList&& crl) { + if (crl.isSignedBy(*info_->identity.second->issuer)) { + JAMI_DBG("found CRL for account."); + tls::CertificateStore::instance().pinRevocationList( + info_->accountId, + std::make_shared<dht::crypto::RevocationList>(std::move(crl))); + } + return true; + }); + syncDevices(); + } else { + JAMI_WARN("can't announce device: no annoucement..."); + } + + auto inboxKey = dht::InfoHash::get("inbox:"+info_->deviceId); + dht_->listen<dht::TrustRequest>( + inboxKey, + [this](dht::TrustRequest&& v) { + if (v.service != DHT_TYPE_NS) + return true; + + findCertificate(v.from, [this, v](const std::shared_ptr<dht::crypto::Certificate>& cert) mutable { + // check peer certificate + dht::InfoHash peer_account; + if (not foundPeerDevice(cert, peer_account)) { + return; + } + + JAMI_WARN("Got trust request from: %s / %s", peer_account.toString().c_str(), v.from.toString().c_str()); + if (info_) + if (info_->contacts->onTrustRequest(peer_account, v.from, time(nullptr), v.confirm, std::move(v.payload))) { + sendTrustRequestConfirm(peer_account); + } + }); + return true; + } + ); + + dht_->listen<DeviceSync>( + inboxKey, + [this](DeviceSync&& sync) { + // Received device sync data. + // check device certificate + findCertificate(sync.from, [this,sync](const std::shared_ptr<dht::crypto::Certificate>& cert) mutable { + if (!cert or cert->getId() != sync.from) { + JAMI_WARN("Can't find certificate for device %s", sync.from.toString().c_str()); + return; + } + if (not foundAccountDevice(cert)) + return; + onSyncData(std::move(sync)); + }); + + return true; + } + ); +} + +void +ArchiveAccountManager::onSyncData(DeviceSync&& sync) +{ + auto sync_date = clock::time_point(clock::duration(sync.date)); + if (not info_->contacts->syncDevice(sync.from, sync_date)) { + return; + } + + // Sync known devices + JAMI_DBG("[Contacts] received device sync data (%lu devices, %lu contacts)", sync.devices_known.size(), sync.peers.size()); + for (const auto& d : sync.devices_known) { + findCertificate(d.first, [this,d](const std::shared_ptr<dht::crypto::Certificate>& crt) { + if (not crt) + return; + //std::lock_guard<std::mutex> lock(deviceListMutex_); + foundAccountDevice(crt, d.second); + }); + } + //saveKnownDevices(); + + // Sync contacts + for (const auto& peer : sync.peers) + info_->contacts->updateContact(peer.first, peer.second); + //saveContacts(); + + // Sync trust requests + for (const auto& tr : sync.trust_requests) + info_->contacts->onTrustRequest(tr.first, tr.second.device, tr.second.received, false, {}); +} + + +#if HAVE_RINGNS +void +ArchiveAccountManager::lookupName(const std::string& name, LookupCallback cb) +{ + nameDir_.get().lookupName(name, cb); +} + +void +ArchiveAccountManager::lookupAddress(const std::string& addr, LookupCallback cb) +{ + nameDir_.get().lookupAddress(addr, cb); +} + +void +ArchiveAccountManager::registerName(const std::string& password, const std::string& name, RegistrationCallback cb) +{ + std::string signedName; + auto nameLowercase {name}; + std::transform(nameLowercase.begin(), nameLowercase.end(), nameLowercase.begin(), ::tolower); + std::string publickey; + std::string accountId; + std::string ethAccount; + + try { + auto archive = readArchive(password); + auto privateKey = archive.id.first; + auto pk = privateKey->getPublicKey(); + publickey = pk.toString(); + accountId = pk.getId().toString(); + signedName = base64::encode(privateKey->sign(std::vector<uint8_t>(nameLowercase.begin(), nameLowercase.end()))); + ethAccount = dev::KeyPair(dev::Secret(archive.eth_key)).address().hex(); + } catch (const std::exception& e) { + //JAMI_ERR("[Auth] can't export account: %s", e.what()); + cb(NameDirectory::RegistrationResponse::invalidCredentials); + return; + } + + nameDir_.get().registerName(accountId, nameLowercase, ethAccount, cb, signedName, publickey); +} +#endif + +} diff --git a/src/jamidht/account_manager.h b/src/jamidht/account_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..c15d538b6bdc813c1064becd766c472c47a3ce86 --- /dev/null +++ b/src/jamidht/account_manager.h @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2014-2019 Savoir-faire Linux Inc. + * Author : Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +#pragma once + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "contact_list.h" +#include "logger.h" +#if HAVE_RINGNS +#include "namedirectory.h" +#endif + +#include <opendht/crypto.h> + +#include <functional> +#include <string> +#include <map> + +namespace dht { + class DhtRunner; +} + +namespace jami { + +class AccountArchive; + +struct AccountInfo { + dht::crypto::Identity identity; + std::unique_ptr<ContactList> contacts; + std::string accountId; + std::string deviceId; + std::shared_ptr<dht::Value> announce; + std::string ethAccount; +}; + +class AccountManager { +public: + using AsyncUser = std::function<void(AccountManager&)>; + using OnAsync = std::function<void(AsyncUser&&)>; + using OnChangeCallback = ContactList::OnChangeCallback; + using OnExportConfig = std::function<std::map<std::string, std::string>()>; + using clock = std::chrono::system_clock; + using time_point = clock::time_point; + + AccountManager( + const std::string& path, + OnAsync&& onAsync, + OnExportConfig&& onExportConfig, + std::shared_ptr<dht::DhtRunner> dht) + : path_(path), onAsync_(std::move(onAsync)), onExportConfig_(std::move(onExportConfig)), dht_(std::move(dht)) {}; + + virtual ~AccountManager() = default; + + // Auth + + enum class AuthError { + UNKNOWN, + INVALID_ARGUMENTS, + NETWORK + }; + + using AuthSuccessCallback = std::function<void( + const AccountInfo& info, + const std::map<std::string, std::string>& config, + std::string&& receipt, + std::vector<uint8_t>&& receipt_signature)>; + + using AuthFailureCallback = std::function<void(AuthError error, const std::string& message)>; + using DeviceSyncCallback = std::function<void(DeviceSync&& syncData)>; + using CertRequest = std::future<std::unique_ptr<dht::crypto::CertificateRequest>>; + + struct AccountCredentials { + std::string scheme; + std::string uri; + std::string password; + virtual ~AccountCredentials() {}; + }; + + virtual void initAuthentication( + CertRequest request, + std::unique_ptr<AccountCredentials> credentials, + AuthSuccessCallback onSuccess, + AuthFailureCallback onFailure, + OnChangeCallback onChange) = 0; + + virtual bool changePassword(const std::string& password_old, const std::string& password_new) = 0; + + virtual void syncDevices() = 0; + + dht::crypto::Identity loadIdentity(const std::string& crt_path, const std::string& key_path, const std::string& key_pwd) const; + + const AccountInfo* useIdentity( + const dht::crypto::Identity& id, + const std::string& receipt, + const std::vector<uint8_t>& receiptSignature, + OnChangeCallback&& onChange); + + virtual void startSync() {}; + + const AccountInfo* getInfo() const { + return info_.get() ; + } + + // Device management + + enum class AddDeviceResult { + SUCCESS_SHOW_PIN = 0, + ERROR_CREDENTIALS, + ERROR_NETWORK, + }; + using AddDeviceCallback = std::function<void(AddDeviceResult, std::string pin)>; + + enum class RevokeDeviceResult { + SUCCESS = 0, + ERROR_CREDENTIALS, + ERROR_NETWORK, + }; + using RevokeDeviceCallback = std::function<void(RevokeDeviceResult)>; + + virtual void addDevice(const std::string& /*password*/, AddDeviceCallback) {}; + virtual bool revokeDevice(const std::string& /*password*/, const std::string& /*device*/, RevokeDeviceCallback) { return false; }; + + const std::map<dht::InfoHash, KnownDevice>& getKnownDevices() const; + bool foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, const std::string& name = {}, const time_point& last_sync = time_point::min()); + //bool removeAccountDevice(const dht::InfoHash& device); + void setAccountDeviceName(/*const dht::InfoHash& device, */const std::string& name); + + + void forEachDevice(const dht::InfoHash& to, + std::function<void(const dht::InfoHash&)>&& op, + std::function<void(bool)>&& end = {}); + + /** + * Inform that a potential peer device have been found. + * Returns true only if the device certificate is a valid device certificate. + * In that case (true is returned) the account_id parameter is set to the peer account ID. + */ + static bool foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, dht::InfoHash& account_id); + + + // Contact requests + + std::vector<std::map<std::string, std::string>> getTrustRequests() const; + bool acceptTrustRequest(const std::string& from); + bool discardTrustRequest(const std::string& from); + + void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload); + void sendTrustRequestConfirm(const dht::InfoHash& to); + + // Contact + + /** + * Add contact to the account contact list. + * Set confirmed if we know the contact also added us. + */ + void addContact(const std::string& uri, bool confirmed = false); + void removeContact(const std::string& uri, bool banned = true); + std::vector<std::map<std::string, std::string>> getContacts() const; + + /** Obtain details about one account contact in serializable form. */ + std::map<std::string, std::string> getContactDetails(const std::string& uri) const; + + virtual bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {}) = 0; + bool setCertificateStatus(const std::string& cert_id, tls::TrustStore::PermissionStatus status); + std::vector<std::string> getCertificatesByStatus(tls::TrustStore::PermissionStatus status); + tls::TrustStore::PermissionStatus getCertificateStatus(const std::string& cert_id) const; + bool isAllowed(const crypto::Certificate& crt, bool allowPublic); + + // Name resolver +#if HAVE_RINGNS + using LookupCallback = NameDirectory::LookupCallback; + using RegistrationCallback = NameDirectory::RegistrationCallback; + + virtual void lookupName(const std::string& name, LookupCallback cb) = 0; + virtual void lookupAddress(const std::string& address, LookupCallback cb) = 0; + virtual void registerName(const std::string& password, const std::string& name, RegistrationCallback cb) = 0; +#endif + +protected: + std::string path_; + OnAsync onAsync_; + OnExportConfig onExportConfig_; + OnChangeCallback onChange_; + std::unique_ptr<AccountInfo> info_; + std::shared_ptr<dht::DhtRunner> dht_; +}; + + +class ArchiveAccountManager : public AccountManager { +public: + ArchiveAccountManager( + const std::string& path, + std::shared_ptr<dht::DhtRunner> dht, + OnAsync&& onAsync, + OnExportConfig&& onExportConfig, + std::string archivePath, + const std::string& nameServer) + : AccountManager(path, std::move(onAsync), std::move(onExportConfig), std::move(dht)), archivePath_(std::move(archivePath)), nameDir_(NameDirectory::instance(nameServer)) {}; + + struct ArchiveAccountCredentials : AccountCredentials { + std::string archivePath; + in_port_t dhtPort; + std::vector<std::string> dhtBootstrap; + dht::crypto::Identity updateIdentity; + }; + + void initAuthentication( + CertRequest request, + std::unique_ptr<AccountCredentials> credentials, + AuthSuccessCallback onSuccess, + AuthFailureCallback onFailure, + OnChangeCallback onChange) override; + + void startSync() override; + + bool changePassword(const std::string& password_old, const std::string& password_new) override; + + void syncDevices() override; + void onSyncData(DeviceSync&& device); + + bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {}) override; + + void addDevice(const std::string& password, AddDeviceCallback) override; + bool revokeDevice(const std::string& password, const std::string& device, RevokeDeviceCallback) override; + bool exportArchive(const std::string& destinationPath, const std::string& password); + +#if HAVE_RINGNS + void lookupName(const std::string& name, LookupCallback cb) override; + void lookupAddress(const std::string& address, LookupCallback cb) override; + void registerName(const std::string& password, const std::string& name, RegistrationCallback cb) override; +#endif + +private: + struct DhtLoadContext; + struct AuthContext { + CertRequest request; + //std::unique_ptr<dht::crypto::CertificateRequest> request; + std::unique_ptr<ArchiveAccountCredentials> credentials; + std::unique_ptr<DhtLoadContext> dhtContext; + AuthSuccessCallback onSuccess; + AuthFailureCallback onFailure; + }; + + void createAccount(const std::shared_ptr<AuthContext>& ctx); + std::pair<std::string, std::shared_ptr<dht::Value>> makeReceipt(const dht::crypto::Identity& id, const dht::crypto::Certificate& device, const std::string& ethAccount); + void updateArchive(AccountArchive& content/*, const ContactList& syncData*/) const; + void saveArchive(AccountArchive& content, const std::string& pwd); + AccountArchive readArchive(const std::string& pwd) const; + static std::pair<std::vector<uint8_t>, dht::InfoHash> computeKeys(const std::string& password, const std::string& pin, bool previous=false); + bool updateCertificates(AccountArchive& archive, dht::crypto::Identity& device); + static bool needsMigration(const dht::crypto::Identity& id); + + void loadFromFile(const std::shared_ptr<AuthContext>& ctx); + void loadFromDHT(const std::shared_ptr<AuthContext>& ctx); + void onArchiveLoaded(AuthContext& ctx, AccountArchive&& a); + + std::string archivePath_; + std::reference_wrapper<NameDirectory> nameDir_; +}; + +class ServerAccountManager : public AccountManager { + + +}; + +} diff --git a/src/jamidht/accountarchive.cpp b/src/jamidht/accountarchive.cpp index 0db1a067b11f7ce6a7bdef06c6820f49204b5010..05a6e684c9ab6c277820b4f28f15cffa23a2ba16 100644 --- a/src/jamidht/accountarchive.cpp +++ b/src/jamidht/accountarchive.cpp @@ -89,6 +89,7 @@ AccountArchive::serialize() const if (ca_key and *ca_key) root[Conf::RING_CA_KEY] = base64::encode(ca_key->serialize()); + root[Conf::RING_ACCOUNT_KEY] = base64::encode(id.first->serialize()); root[Conf::RING_ACCOUNT_CERT] = base64::encode(id.second->getPacked()); root[Conf::ETH_KEY] = base64::encode(eth_key); diff --git a/src/jamidht/accountarchive.h b/src/jamidht/accountarchive.h index b0d88992adacf739b2e55f79eea462c42bc19c49..a0c89fdf863eed56ee59320c420ff3c496a8d526 100644 --- a/src/jamidht/accountarchive.h +++ b/src/jamidht/accountarchive.h @@ -17,7 +17,7 @@ */ #pragma once -#include "ringcontact.h" +#include "jami_contact.h" #include "fileutils.h" #include <opendht/crypto.h> diff --git a/src/jamidht/contact_list.cpp b/src/jamidht/contact_list.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1de78f2809ee1b58cc3460f6bc8e37571becf001 --- /dev/null +++ b/src/jamidht/contact_list.cpp @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2014-2019 Savoir-faire Linux Inc. + * Author : Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +#include "contact_list.h" +#include "logger.h" +#include "jamiaccount.h" +#include "fileutils.h" + +#include "account_const.h" + +#include <fstream> + +namespace jami { + +ContactList::ContactList(const std::shared_ptr<crypto::Certificate>& cert, const std::string& path, OnChangeCallback cb) + : path_(path), callbacks_(std::move(cb)) +{ + if (cert) + accountTrust_.add(*cert); +} + +ContactList::~ContactList() {} + +void +ContactList::load() +{ + loadContacts(); + loadTrustRequests(); + loadKnownDevices(); +} + +void +ContactList::save() +{ + saveContacts(); + saveTrustRequests(); + saveKnownDevices(); +} + +bool +ContactList::setCertificateStatus(const std::string& cert_id, const tls::TrustStore::PermissionStatus status) +{ + if (contacts_.find(dht::InfoHash(cert_id)) != contacts_.end()) { + JAMI_DBG("Can't set certificate status for existing contacts %s", cert_id.c_str()); + return false; + } + return trust_.setCertificateStatus(cert_id, status); +} + +bool +ContactList::addContact(const dht::InfoHash& h, bool confirmed) +{ + JAMI_WARN("[Contacts] addContact: %s", h.to_c_str()); + auto c = contacts_.find(h); + if (c == contacts_.end()) + c = contacts_.emplace(h, Contact{}).first; + else if (c->second.isActive() and c->second.confirmed == confirmed) + return false; + c->second.added = std::time(nullptr); + c->second.confirmed |= confirmed; + auto hStr = h.toString(); + trust_.setCertificateStatus(hStr, tls::TrustStore::PermissionStatus::ALLOWED); + saveContacts(); + callbacks_.contactAdded(hStr, c->second.confirmed); + //emitSignal<DRing::ConfigurationSignal::ContactAdded>(account_.get().getAccountID(), hStr, c->second.confirmed); + //syncDevices(); + return true; +} + +bool +ContactList::removeContact(const dht::InfoHash& h, bool ban) +{ + JAMI_WARN("[Contacts] removeContact: %s", h.to_c_str()); + auto c = contacts_.find(h); + if (c == contacts_.end()) + c = contacts_.emplace(h, Contact{}).first; + else if (not c->second.isActive() and c->second.banned == ban) + return false; + c->second.removed = std::time(nullptr); + c->second.banned = ban; + auto uri = h.toString(); + trust_.setCertificateStatus(uri, ban ? tls::TrustStore::PermissionStatus::BANNED + : tls::TrustStore::PermissionStatus::UNDEFINED); + if (ban and trustRequests_.erase(h) > 0) + saveTrustRequests(); + saveContacts(); + callbacks_.contactRemoved(uri, ban); + //emitSignal<DRing::ConfigurationSignal::ContactRemoved>(account_.get().getAccountID(), uri, ban); + //syncDevices(); + return true; +} + +std::map<std::string, std::string> +ContactList::getContactDetails(const dht::InfoHash& h) const +{ + const auto c = contacts_.find(h); + if (c == std::end(contacts_)) { + JAMI_WARN("[Contacts] contact '%s' not found", h.to_c_str()); + return {}; + } + + auto details = c->second.toMap(); + if (not details.empty()) + details["id"] = c->first.toString(); + + return details; +} + +const std::map<dht::InfoHash, Contact>& +ContactList::getContacts() const +{ + return contacts_; +} + +void +ContactList::setContacts(const std::map<dht::InfoHash, Contact>& contacts) +{ + contacts_ = contacts; + saveContacts(); +} + +void +ContactList::updateContact(const dht::InfoHash& id, const Contact& contact) +{ + if (not id) { + JAMI_ERR("[Contacts] updateContact: invalid contact ID"); + return; + } + bool stateChanged {false}; + auto c = contacts_.find(id); + if (c == contacts_.end()) { + JAMI_DBG("[Contacts] new contact: %s", id.toString().c_str()); + c = contacts_.emplace(id, contact).first; + stateChanged = c->second.isActive() or c->second.isBanned(); + } else { + JAMI_DBG("[Contacts] updated contact: %s", id.toString().c_str()); + stateChanged = c->second.update(contact); + } + if (stateChanged) { + if (c->second.isActive()) { + trust_.setCertificateStatus(id.toString(), tls::TrustStore::PermissionStatus::ALLOWED); + callbacks_.contactAdded(id.toString(), c->second.confirmed); + } else { + if (c->second.banned) + trust_.setCertificateStatus(id.toString(), tls::TrustStore::PermissionStatus::BANNED); + callbacks_.contactRemoved(id.toString(), c->second.banned); + } + } +} + +void +ContactList::loadContacts() +{ + decltype(contacts_) contacts; + try { + // read file + auto file = fileutils::loadFile("contacts", path_); + // load values + msgpack::object_handle oh = msgpack::unpack((const char*)file.data(), file.size()); + oh.get().convert(contacts); + } catch (const std::exception& e) { + JAMI_WARN("[Contacts] error loading contacts: %s", e.what()); + return; + } + + for (auto& peer : contacts) + updateContact(peer.first, peer.second); +} + +void +ContactList::saveContacts() const +{ + std::ofstream file(path_+DIR_SEPARATOR_STR "contacts", std::ios::trunc | std::ios::binary); + msgpack::pack(file, contacts_); +} + +void +ContactList::saveTrustRequests() const +{ + std::ofstream file(path_+DIR_SEPARATOR_STR "incomingTrustRequests", std::ios::trunc | std::ios::binary); + msgpack::pack(file, trustRequests_); +} + +void +ContactList::loadTrustRequests() +{ + std::map<dht::InfoHash, TrustRequest> requests; + try { + // read file + auto file = fileutils::loadFile("incomingTrustRequests", path_); + // load values + msgpack::object_handle oh = msgpack::unpack((const char*)file.data(), file.size()); + oh.get().convert(requests); + } catch (const std::exception& e) { + JAMI_WARN("[Contacts] error loading trust requests: %s", e.what()); + return; + } + + for (auto& tr : requests) + onTrustRequest(tr.first, tr.second.device, tr.second.received, false, std::move(tr.second.payload)); +} + +bool +ContactList::onTrustRequest(const dht::InfoHash& peer_account, const dht::InfoHash& peer_device, time_t received, bool confirm, std::vector<uint8_t>&& payload) +{ + bool accept = false; + // Check existing contact + auto contact = contacts_.find(peer_account); + if (contact != contacts_.end()) { + // Banned contact: discard request + if (contact->second.isBanned()) + return false; + // Send confirmation + if (not confirm) + accept = true; + // Contact exists, update confirmation status + if (not contact->second.confirmed) { + contact->second.confirmed = true; + callbacks_.contactAdded(peer_account.toString(), true); + saveContacts(); + //syncDevices(); + } + } else { + auto req = trustRequests_.find(peer_account); + if (req == trustRequests_.end()) { + // Add trust request + req = trustRequests_.emplace(peer_account, TrustRequest{ + peer_device, received, std::move(payload) + }).first; + } else { + // Update trust request + if (received < req->second.received) { + req->second.device = peer_device; + req->second.received = received; + req->second.payload = std::move(payload); + } else { + JAMI_DBG("[Contacts] Ignoring outdated trust request from %s", peer_account.toString().c_str()); + } + } + saveTrustRequests(); + callbacks_.trustRequest(req->first.toString(), req->second.payload, received); + } + return accept; +} + + +/* trust requests */ + +std::vector<std::map<std::string, std::string>> +ContactList::getTrustRequests() const +{ + using Map = std::map<std::string, std::string>; + std::vector<Map> ret; + ret.reserve(trustRequests_.size()); + for (const auto& r : trustRequests_) { + ret.emplace_back(Map { + {DRing::Account::TrustRequest::FROM, r.first.toString()}, + {DRing::Account::TrustRequest::RECEIVED, std::to_string(r.second.received)}, + {DRing::Account::TrustRequest::PAYLOAD, std::string(r.second.payload.begin(), r.second.payload.end())} + }); + } + return ret; +} + +bool +ContactList::acceptTrustRequest(const dht::InfoHash& from) +{ + // The contact sent us a TR so we are in its contact list + addContact(from, true); + + auto i = trustRequests_.find(from); + if (i == trustRequests_.end()) + return false; + + // Clear trust request + auto treq = std::move(i->second); + trustRequests_.erase(i); + saveTrustRequests(); + + // Send confirmation + //account_.get().sendTrustRequestConfirm(from); + return true; +} + +bool +ContactList::discardTrustRequest(const dht::InfoHash& from) +{ + if (trustRequests_.erase(from) > 0) { + saveTrustRequests(); + return true; + } + return false; +} + +void +ContactList::loadKnownDevices() +{ + std::map<dht::InfoHash, std::pair<std::string, uint64_t>> knownDevices; + try { + // read file + auto file = fileutils::loadFile("knownDevicesNames", path_); + // load values + msgpack::object_handle oh = msgpack::unpack((const char*)file.data(), file.size()); + oh.get().convert(knownDevices); + } catch (const std::exception& e) { + JAMI_WARN("[Contacts] error loading devices: %s", e.what()); + return; + } + + for (const auto& d : knownDevices) { + JAMI_DBG("[Contacts] loading known account device %s %s", + d.second.first.c_str(), + d.first.toString().c_str()); + if (auto crt = tls::CertificateStore::instance().getCertificate(d.first.toString())) { + if (not foundAccountDevice(crt, d.second.first, clock::from_time_t(d.second.second))) + JAMI_WARN("[Contacts] can't add device %s", d.first.toString().c_str()); + } + else { + JAMI_WARN("[Contacts] can't find certificate for device %s", d.first.toString().c_str()); + } + } +} + +void +ContactList::saveKnownDevices() const +{ + std::ofstream file(path_+DIR_SEPARATOR_STR "knownDevicesNames", std::ios::trunc | std::ios::binary); + + std::map<dht::InfoHash, std::pair<std::string, uint64_t>> devices; + for (const auto& id : knownDevices_) + devices.emplace(id.first, std::make_pair(id.second.name, clock::to_time_t(id.second.last_sync))); + + msgpack::pack(file, devices); +} + +bool +ContactList::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, const std::string& name, const time_point& updated) +{ + if (not crt) + return false; + + // match certificate chain + if (not accountTrust_.verify(*crt)) { + JAMI_WARN("[Contacts] Found invalid account device: %s", crt->getId().toString().c_str()); + return false; + } + + // insert device + auto it = knownDevices_.emplace(crt->getId(), KnownDevice{crt, name, updated}); + if (it.second) { + JAMI_DBG("[Contacts] Found account device: %s %s", + name.c_str(), + crt->getId().toString().c_str()); + tls::CertificateStore::instance().pinCertificate(crt); + saveKnownDevices(); + callbacks_.devicesChanged(); + } else { + // update device name + if (not name.empty() and it.first->second.name != name) { + JAMI_DBG("[Contacts] updating device name: %s %s", + name.c_str(), + crt->getId().toString().c_str()); + it.first->second.name = name; + saveKnownDevices(); + callbacks_.devicesChanged(); + } + } + return true; +} + +bool +ContactList::removeAccountDevice(const dht::InfoHash& device) +{ + if (knownDevices_.erase(device) > 0) { + saveKnownDevices(); + return true; + } + return false; +} + +void +ContactList::setAccountDeviceName(const dht::InfoHash& device, const std::string& name) +{ + auto dev = knownDevices_.find(device); + if (dev != knownDevices_.end()) { + if (dev->second.name != name) { + dev->second.name = name; + saveKnownDevices(); + } + } +} + +DeviceSync +ContactList::getSyncData() const +{ + DeviceSync sync_data; + sync_data.date = clock::now().time_since_epoch().count(); + //sync_data.device_name = ringDeviceName_; + sync_data.peers = getContacts(); + + static constexpr size_t MAX_TRUST_REQUESTS = 20; + if (trustRequests_.size() <= MAX_TRUST_REQUESTS) + for (const auto& req : trustRequests_) + sync_data.trust_requests.emplace(req.first, TrustRequest{req.second.device, req.second.received, {}}); + else { + size_t inserted = 0; + auto req = trustRequests_.lower_bound(dht::InfoHash::getRandom()); + while (inserted++ < MAX_TRUST_REQUESTS) { + if (req == trustRequests_.end()) + req = trustRequests_.begin(); + sync_data.trust_requests.emplace(req->first, TrustRequest{req->second.device, req->second.received, {}}); + ++req; + } + } + + for (const auto& dev : knownDevices_) { + sync_data.devices_known.emplace(dev.first, dev.second.name); + } + return sync_data; +} + +bool +ContactList::syncDevice(const dht::InfoHash& device, const time_point& syncDate) +{ + auto it = knownDevices_.find(device); + if (it == knownDevices_.end()) { + JAMI_WARN("[Contacts] dropping sync data from unknown device"); + return false; + } + if (it->second.last_sync >= syncDate) { + JAMI_DBG("[Contacts] dropping outdated sync data"); + return false; + } + it->second.last_sync = syncDate; + return true; +} + +/* +void +ContactList::onSyncData(DeviceSync&& sync) +{ + auto it = knownDevices_.find(sync.from); + if (it == knownDevices_.end()) { + JAMI_WARN("[Contacts] dropping sync data from unknown device"); + return; + } + auto sync_date = clock::time_point(clock::duration(sync.date)); + if (it->second.last_sync >= sync_date) { + JAMI_DBG("[Contacts] dropping outdated sync data"); + return; + } + + // Sync known devices + JAMI_DBG("[Contacts] received device sync data (%lu devices, %lu contacts)", sync.devices_known.size(), sync.peers.size()); + for (const auto& d : sync.devices_known) { + account_.get().findCertificate(d.first, [this,d](const std::shared_ptr<dht::crypto::Certificate>& crt) { + if (not crt) + return; + //std::lock_guard<std::mutex> lock(deviceListMutex_); + foundAccountDevice(crt, d.second); + }); + } + saveKnownDevices(); + + // Sync contacts + for (const auto& peer : sync.peers) + updateContact(peer.first, peer.second); + saveContacts(); + + // Sync trust requests + for (const auto& tr : sync.trust_requests) + onTrustRequest(tr.first, tr.second.device, tr.second.received, false, {}); + + it->second.last_sync = sync_date; +} +*/ +} diff --git a/src/jamidht/contact_list.h b/src/jamidht/contact_list.h new file mode 100644 index 0000000000000000000000000000000000000000..e293758951d40afbc8d5a44b8ba4586315a7b2eb --- /dev/null +++ b/src/jamidht/contact_list.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2019 Savoir-faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#pragma once + +#include "jami_contact.h" +#include "security/certstore.h" + +#include <opendht/infohash.h> +#include <opendht/crypto.h> + +#include <map> +#include <mutex> +#include <chrono> + +namespace jami { + +class ContactList { +public: + using clock = std::chrono::system_clock; + using time_point = clock::time_point; + using VerifyResult = dht::crypto::TrustList::VerifyResult; + + using OnContactAdded = std::function<void(const std::string&, bool)>; + using OnContactRemoved = std::function<void(const std::string&, bool)>; + using OnIncomingTrustRequest = std::function<void(const std::string&, const std::vector<uint8_t>&, time_t)>; + using OnDevicesChanged = std::function<void()>; + + struct OnChangeCallback { + OnContactAdded contactAdded; + OnContactRemoved contactRemoved; + OnIncomingTrustRequest trustRequest; + OnDevicesChanged devicesChanged; + }; + + ContactList(const std::shared_ptr<crypto::Certificate>& cert, const std::string& path, OnChangeCallback cb); + ~ContactList(); + + void load(); + void save(); + + /* Contacts */ + std::map<std::string, std::string> getContactDetails(const dht::InfoHash&) const; + bool removeContact(const dht::InfoHash&, bool ban); + bool addContact(const dht::InfoHash&, bool confirmed = false); + + bool setCertificateStatus(const std::string& cert_id, const tls::TrustStore::PermissionStatus status); + bool setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert, tls::TrustStore::PermissionStatus status, bool local = true); + + tls::TrustStore::PermissionStatus getCertificateStatus(const std::string& cert_id) const { + return trust_.getCertificateStatus(cert_id); + } + + std::vector<std::string> getCertificatesByStatus(tls::TrustStore::PermissionStatus status) const { + return trust_.getCertificatesByStatus(status); + } + + bool isAllowed(const crypto::Certificate& crt, bool allowPublic) { + return trust_.isAllowed(crt, allowPublic); + } + + VerifyResult isValidAccountDevice(const crypto::Certificate& crt) const { + return accountTrust_.verify(crt); + } + + const std::map<dht::InfoHash, Contact>& getContacts() const; + void setContacts(const std::map<dht::InfoHash, Contact>&); + void updateContact(const dht::InfoHash&, const Contact&); + + /* Contact requests */ + + /** Inform of a new contact request. Returns true if the request should be immediatly accepted (already a contact) */ + bool onTrustRequest(const dht::InfoHash& peer_account, const dht::InfoHash& peer_device, time_t received, bool confirm, std::vector<uint8_t>&& payload); + std::vector<std::map<std::string, std::string>> getTrustRequests() const; + bool acceptTrustRequest(const dht::InfoHash& from); + bool discardTrustRequest(const dht::InfoHash& from); + + /* Devices */ + const std::map<dht::InfoHash, KnownDevice>& getKnownDevices() const { return knownDevices_; } + bool foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, const std::string& name = {}, const time_point& last_sync = time_point::min()); + bool removeAccountDevice(const dht::InfoHash& device); + void setAccountDeviceName(const dht::InfoHash& device, const std::string& name); + + DeviceSync getSyncData() const; + bool syncDevice(const dht::InfoHash& device, const time_point& syncDate); + //void onSyncData(DeviceSync&& device); + +private: + mutable std::mutex lock; + std::map<dht::InfoHash, Contact> contacts_; + std::map<dht::InfoHash, TrustRequest> trustRequests_; + std::map<dht::InfoHash, KnownDevice> knownDevices_; + + // Trust store with account main certificate as the only CA + dht::crypto::TrustList accountTrust_; + // Trust store for to match peer certificates + tls::TrustStore trust_; + std::string path_; + + OnChangeCallback callbacks_; + + void loadContacts(); + void saveContacts() const; + + void loadTrustRequests(); + void saveTrustRequests() const; + + void loadKnownDevices(); + void saveKnownDevices() const; +}; + +} diff --git a/src/jamidht/ringcontact.h b/src/jamidht/jami_contact.h similarity index 67% rename from src/jamidht/ringcontact.h rename to src/jamidht/jami_contact.h index 2ed9a3a08a03b5b7ee1af17c97f406f03c9af237..9138a6d1a964f5ddabdeff9012ee4c3287739b70 100644 --- a/src/jamidht/ringcontact.h +++ b/src/jamidht/jami_contact.h @@ -19,6 +19,10 @@ #include "string_utils.h" +#include <opendht/infohash.h> +#include <opendht/value.h> +#include <opendht/default_types.h> + #include <msgpack.hpp> #include <json/json.h> @@ -108,4 +112,51 @@ struct Contact MSGPACK_DEFINE_MAP(added, removed, confirmed, banned) }; +struct TrustRequest { + dht::InfoHash device; + time_t received; + std::vector<uint8_t> payload; + MSGPACK_DEFINE_MAP(device, received, payload) +}; + +struct DeviceAnnouncement : public dht::SignedValue<DeviceAnnouncement> +{ +private: + using BaseClass = dht::SignedValue<DeviceAnnouncement>; +public: + static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA; + dht::InfoHash dev; + MSGPACK_DEFINE_MAP(dev); +}; + +struct DeviceSync : public dht::EncryptedValue<DeviceSync> +{ + static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA; + uint64_t date; + std::string device_name; + std::map<dht::InfoHash, std::string> devices_known; + std::map<dht::InfoHash, Contact> peers; + std::map<dht::InfoHash, TrustRequest> trust_requests; + MSGPACK_DEFINE_MAP(date, device_name, devices_known, peers, trust_requests) +}; + +struct KnownDevice { + using clock = std::chrono::system_clock; + using time_point = clock::time_point; + + /** Device certificate */ + std::shared_ptr<dht::crypto::Certificate> certificate; + + /** Device name */ + std::string name {}; + + /** Time of last received device sync */ + time_point last_sync {time_point::min()}; + + KnownDevice(const std::shared_ptr<dht::crypto::Certificate>& cert, + const std::string& n = {}, + time_point sync = time_point::min()) + : certificate(cert), name(n), last_sync(sync) {} +}; + } diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp index de6e75dea20174c6a98967660dc5d2b8ffbce263..c30795ac4e1e5551c50e7f487acc2c5328ad0fb8 100644 --- a/src/jamidht/jamiaccount.cpp +++ b/src/jamidht/jamiaccount.cpp @@ -31,8 +31,10 @@ #include "logger.h" #include "accountarchive.h" -#include "ringcontact.h" +#include "jami_contact.h" #include "configkeys.h" +#include "contact_list.h" +#include "account_manager.h" #include "sip/sdp.h" #include "sip/sipvoiplink.h" @@ -153,58 +155,6 @@ struct JamiAccount::PendingMessage std::chrono::steady_clock::time_point received; }; -struct -JamiAccount::TrustRequest { - dht::InfoHash device; - time_t received; - std::vector<uint8_t> payload; - MSGPACK_DEFINE_MAP(device, received, payload) -}; - -/** - * Represents a known device attached to this account - */ -struct JamiAccount::KnownDevice -{ - /** Device certificate */ - std::shared_ptr<dht::crypto::Certificate> certificate; - - /** Device name */ - std::string name {}; - - /** Time of last received device sync */ - time_point last_sync {time_point::min()}; - - KnownDevice(const std::shared_ptr<dht::crypto::Certificate>& cert, - const std::string& n = {}, - time_point sync = time_point::min()) - : certificate(cert), name(n), last_sync(sync) {} -}; - -/** - * Device announcement stored on DHT. - */ -struct JamiAccount::DeviceAnnouncement : public dht::SignedValue<DeviceAnnouncement> -{ -private: - using BaseClass = dht::SignedValue<DeviceAnnouncement>; -public: - static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA; - dht::InfoHash dev; - MSGPACK_DEFINE_MAP(dev); -}; - -struct JamiAccount::DeviceSync : public dht::EncryptedValue<DeviceSync> -{ - static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA; - uint64_t date; - std::string device_name; - std::map<dht::InfoHash, std::string> devices_known; - std::map<dht::InfoHash, Contact> peers; - std::map<dht::InfoHash, TrustRequest> trust_requests; - MSGPACK_DEFINE_MAP(date, device_name, devices_known, peers, trust_requests) -}; - struct AccountPeerInfo { dht::InfoHash accountId; @@ -258,11 +208,11 @@ parseRingUri(const std::string& toUrl) { auto sufix = stripPrefix(toUrl); if (sufix.length() < 40) - throw std::invalid_argument("id must be a ring infohash"); + throw std::invalid_argument("id must be a Jami infohash"); const std::string toUri = sufix.substr(0, 40); if (std::find_if_not(toUri.cbegin(), toUri.cend(), ::isxdigit) != toUri.cend()) - throw std::invalid_argument("id must be a ring infohash"); + throw std::invalid_argument("id must be a Jami infohash"); return toUri; } @@ -291,15 +241,10 @@ JamiAccount::createIceTransport(const Args&... args) JamiAccount::JamiAccount(const std::string& accountID, bool /* presenceEnabled */) : SIPAccountBase(accountID) -#if HAVE_RINGNS - , nameDir_(NameDirectory::instance()) -#endif + , dht_(new dht::DhtRunner) , idPath_(fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID()) , cachePath_(fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID()) , dataPath_(cachePath_ + DIR_SEPARATOR_STR "values") - , proxyEnabled_(false) - , proxyServer_("") - , deviceKey_("") , dhtPeerConnector_ {new DhtPeerConnector {*this}} { // Force the SFL turn server if none provided yet @@ -326,7 +271,8 @@ JamiAccount::~JamiAccount() peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE); peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE); } - dht_.join(); + if (auto dht = dht_) + dht->join(); } void @@ -449,7 +395,7 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std:: std::weak_ptr<SIPCall> wCall = call; #if HAVE_RINGNS - nameDir_.get().lookupAddress(toUri, [wCall](const std::string& result, const NameDirectory::Response& response){ + accountManager_->lookupAddress(toUri, [wCall](const std::string& result, const NameDirectory::Response& response){ if (response == NameDirectory::Response::found) if (auto call = wCall.lock()) { call->setPeerRegistredName(result); @@ -460,7 +406,7 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std:: // Find listening devices for this account dht::InfoHash peer_account(toUri); - forEachDevice(peer_account, [this, wCall, toUri, peer_account](const dht::InfoHash& dev) + accountManager_->forEachDevice(peer_account, [this, wCall, toUri, peer_account](const dht::InfoHash& dev) { auto call = wCall.lock(); if (not call) return; @@ -476,7 +422,7 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std:: auto ice = createIceTransport(("sip:" + dev_call->getCallId()).c_str(), ICE_COMPONENTS, true, getIceOptions()); if (not ice) { - JAMI_WARN("Can't create ICE"); + JAMI_WARN("[call %s] Can't create ICE", call->getCallId().c_str()); dev_call->removeCall(); return; } @@ -489,7 +435,10 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std:: } call->addSubCall(*dev_call); - manager.addTask([sthis=shared(), weak_dev_call, ice, ice_tcp, dev, toUri, peer_account] { + manager.addTask([w=weak(), weak_dev_call, ice, ice_tcp, dev, toUri, peer_account] { + auto sthis = w.lock(); + if (not sthis) + return false; auto call = weak_dev_call.lock(); // call aborted? @@ -524,10 +473,10 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std:: } dht::Value val { dht::IceCandidates(callvid, blob) }; - sthis->dht_.putEncrypted( + sthis->dht_->putEncrypted( callkey, dev, std::move(val), - [=](bool ok) { // Put complete callback + [weak_dev_call](bool ok) { // Put complete callback if (!ok) { JAMI_WARN("Can't put ICE descriptor on DHT"); if (auto call = weak_dev_call.lock()) @@ -537,7 +486,7 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std:: } ); - auto listenKey = sthis->dht_.listen<dht::IceCandidates>( + auto listenKey = sthis->dht_->listen<dht::IceCandidates>( callkey, [weak_dev_call, ice, ice_tcp, callvid, dev] (dht::IceCandidates&& msg) { if (msg.id != callvid or msg.from != dev) @@ -803,6 +752,7 @@ void JamiAccount::unserialize(const YAML::Node &node) JAMI_WARN("can't read receipt: %s", e.what()); } + parseValueOptional(node, Conf::DHT_PORT_KEY, dhtPort_); if (not dhtPort_) dhtPort_ = getRandomEvenPort(DHT_PORT_RANGE); dhtPortUsed_ = dhtPort_; @@ -813,7 +763,6 @@ void JamiAccount::unserialize(const YAML::Node &node) #if HAVE_RINGNS parseValueOptional(node, DRing::Account::ConfProperties::RingNS::URI, nameServer_); - nameDir_ = NameDirectory::instance(nameServer_); if (registeredName_.empty()) { parseValueOptional(node, DRing::Account::VolatileProperties::REGISTERED_NAME, registeredName_); } @@ -823,202 +772,7 @@ void JamiAccount::unserialize(const YAML::Node &node) loadAccount(); } - -void -JamiAccount::createRingDevice(const dht::crypto::Identity& id) -{ - if (not id.second->isCA()) { - JAMI_ERR("[Account %s] trying to sign a certificate with a non-CA.", getAccountID().c_str()); - } - auto dev_id = dht::crypto::generateIdentity("Ring device", id); - if (!dev_id.first || !dev_id.second) { - throw VoipLinkException("Can't generate identity for this account."); - } - idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID(); - fileutils::check_dir(idPath_.c_str(), 0700); - - // save the chain including CA - std::tie(tlsPrivateKeyFile_, tlsCertificateFile_) = saveIdentity(dev_id, idPath_, "ring_device"); - tlsPassword_ = {}; - identity_ = dev_id; - accountTrust_ = dht::crypto::TrustList{}; - accountTrust_.add(*id.second); - auto deviceId = dev_id.first->getPublicKey().getId(); - ringDeviceId_ = deviceId.toString(); - ringDeviceName_ = ip_utils::getDeviceName(); - if (ringDeviceName_.empty()) - ringDeviceName_ = ringDeviceId_.substr(8); - - { - std::lock_guard<std::mutex> devicelock(deviceListMutex_); - knownDevices_.emplace(deviceId, KnownDevice{dev_id.second, ringDeviceName_, clock::now()}); - } - - receipt_ = makeReceipt(id); - receiptSignature_ = id.first->sign({receipt_.begin(), receipt_.end()}); - JAMI_WARN("[Account %s] created new device: %s (%s)", - getAccountID().c_str(), ringDeviceId_.c_str(), ringDeviceName_.c_str()); -} - -void -JamiAccount::initRingDevice(const AccountArchive& a) -{ - JAMI_WARN("[Account %s] creating new device from archive", getAccountID().c_str()); - SIPAccountBase::setAccountDetails(a.config); - parseInt(a.config, Conf::CONFIG_DHT_PORT, dhtPort_); - parseBool(a.config, Conf::CONFIG_DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_); - parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_HISTORY, allowPeersFromHistory_); - parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_CONTACT, allowPeersFromContact_); - parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_TRUSTED, allowPeersFromTrusted_); - ringAccountId_ = a.id.second->getId().toString(); - username_ = RING_URI_PREFIX+ringAccountId_; - ethAccount_ = dev::KeyPair(dev::Secret(a.eth_key)).address().hex(); - contacts_ = a.contacts; - createRingDevice(a.id); - saveContacts(); -} - -std::string -JamiAccount::makeReceipt(const dht::crypto::Identity& id) -{ - JAMI_DBG("[Account %s] signing device receipt", getAccountID().c_str()); - DeviceAnnouncement announcement; - announcement.dev = identity_.second->getId(); - dht::Value ann_val {announcement}; - ann_val.sign(*id.first); - - std::ostringstream is; - is << "{\"id\":\"" << id.second->getId() - << "\",\"dev\":\"" << identity_.second->getId() - << "\",\"eth\":\"" << ethAccount_ - << "\",\"announce\":\"" << base64::encode(ann_val.getPacked()) << "\"}"; - - announce_ = std::make_shared<dht::Value>(std::move(ann_val)); - return is.str(); -} - -bool -JamiAccount::useIdentity(const dht::crypto::Identity& identity) -{ - if (receipt_.empty() or receiptSignature_.empty()) - return false; - - if (not identity.first or not identity.second) { - JAMI_ERR("[Account %s] no identity provided", getAccountID().c_str()); - return false; - } - - auto accountCertificate = identity.second->issuer; - if (not accountCertificate) { - JAMI_ERR("[Account %s] device certificate must be issued by the account certificate", getAccountID().c_str()); - return false; - } - - // match certificate chain - dht::crypto::TrustList account_trust; - account_trust.add(*accountCertificate); - if (not account_trust.verify(*identity.second)) { - JAMI_ERR("[Account %s] can't use identity: device certificate chain can't be verified", getAccountID().c_str()); - return false; - } - - auto pk = accountCertificate->getPublicKey(); - JAMI_DBG("[Account %s] checking device receipt for %s", getAccountID().c_str(), pk.getId().toString().c_str()); - if (!pk.checkSignature({receipt_.begin(), receipt_.end()}, receiptSignature_)) { - JAMI_ERR("[Account %s] device receipt signature check failed", getAccountID().c_str()); - return false; - } - - Json::Value root; - Json::CharReaderBuilder rbuilder; - auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader()); - if (!reader->parse(&receipt_[0], &receipt_[receipt_.size()], &root, nullptr)) { - JAMI_ERR() << this << " device receipt parsing error"; - return false; - } - - auto dev_id = root["dev"].asString(); - if (dev_id != identity.second->getId().toString()) { - JAMI_ERR("[Account %s] device ID mismatch between receipt and certificate", getAccountID().c_str()); - return false; - } - auto id = root["id"].asString(); - if (id != pk.getId().toString()) { - JAMI_ERR("[Account %s] account ID mismatch between receipt and certificate", getAccountID().c_str()); - return false; - } - - dht::Value announce_val; - try { - auto announce = base64::decode(root["announce"].asString()); - msgpack::object_handle announce_msg = msgpack::unpack((const char*)announce.data(), announce.size()); - //dht::Value announce_val (announce_msg.get()); - announce_val.msgpack_unpack(announce_msg.get()); - if (not announce_val.checkSignature()) { - JAMI_ERR("[Account %s] announce signature check failed", getAccountID().c_str()); - return false; - } - DeviceAnnouncement da; - da.unpackValue(announce_val); - if (da.from.toString() != id or da.dev.toString() != dev_id) { - JAMI_ERR("[Account %s] device ID mismatch in announce", getAccountID().c_str()); - return false; - } - } catch (const std::exception& e) { - JAMI_ERR("[Account %s] can't read announce: %s", getAccountID().c_str(), e.what()); - return false; - } - - // success, make use of this identity (certificate chain and private key) - identity_ = identity; - accountTrust_ = std::move(account_trust); - ringAccountId_ = id; - ringDeviceId_ = identity.first->getPublicKey().getId().toString(); - username_ = RING_URI_PREFIX + id; - announce_ = std::make_shared<dht::Value>(std::move(announce_val)); - ethAccount_ = root["eth"].asString(); - - JAMI_DBG("[Account %s] ring:%s device %s receipt checked successfully", getAccountID().c_str(), id.c_str(), ringDeviceId_.c_str()); - return true; -} - -dht::crypto::Identity -JamiAccount::loadIdentity(const std::string& crt_path, const std::string& key_path, const std::string& key_pwd) const -{ - JAMI_DBG("[Account %s] loading identity: %s %s", getAccountID().c_str(), crt_path.c_str(), key_path.c_str()); - dht::crypto::Identity id; - try { - dht::crypto::Certificate dht_cert(fileutils::loadFile(crt_path, idPath_)); - dht::crypto::PrivateKey dht_key(fileutils::loadFile(key_path, idPath_), key_pwd); - auto crt_id = dht_cert.getId(); - if (crt_id != dht_key.getPublicKey().getId()) - return {}; - - if (not dht_cert.issuer) { - JAMI_ERR("[Account %s] device certificate %s has no issuer", getAccountID().c_str(), dht_cert.getId().toString().c_str()); - return {}; - } - // load revocation lists for device authority (account certificate). - tls::CertificateStore::instance().loadRevocations(*dht_cert.issuer); - - id = { - std::make_shared<dht::crypto::PrivateKey>(std::move(dht_key)), - std::make_shared<dht::crypto::Certificate>(std::move(dht_cert)) - }; - } - catch (const std::exception& e) { - JAMI_ERR("Error loading identity: %s", e.what()); - } - - return id; -} - -AccountArchive -JamiAccount::readArchive(const std::string& pwd) const -{ - JAMI_DBG("[Account %s] reading account archive", getAccountID().c_str()); - return AccountArchive(fileutils::getFullPath(idPath_, archivePath_), pwd); -} +/* void JamiAccount::updateArchive(AccountArchive& archive) const @@ -1053,31 +807,15 @@ JamiAccount::updateArchive(AccountArchive& archive) const } else archive.config.insert(it); } - archive.contacts = contacts_; -} - -void -JamiAccount::saveArchive(AccountArchive& archive, const std::string& pwd) -{ - try { - updateArchive(archive); - if (archivePath_.empty()) - archivePath_ = "export.gz"; - archive.save(fileutils::getFullPath(idPath_, archivePath_), pwd); - archiveHasPassword_ = not pwd.empty(); - } catch (const std::runtime_error& ex) { - JAMI_ERR("[Account %s] Can't export archive: %s", getAccountID().c_str(), ex.what()); - return; - } + archive.contacts = contactList_->getContacts(); } +*/ bool JamiAccount::changeArchivePassword(const std::string& password_old, const std::string& password_new) { - auto path = fileutils::getFullPath(idPath_, archivePath_); try { - AccountArchive(path, password_old).save(path, password_new); - archiveHasPassword_ = not password_new.empty(); + accountManager_->changePassword(password_old, password_new); } catch (const std::exception& ex) { JAMI_ERR("[Account %s] Can't change archive password: %s", getAccountID().c_str(), ex.what()); if (password_old.empty()) { @@ -1091,85 +829,20 @@ JamiAccount::changeArchivePassword(const std::string& password_old, const std::s return true; } -std::pair<std::vector<uint8_t>, dht::InfoHash> -JamiAccount::computeKeys(const std::string& password, const std::string& pin, bool previous) -{ - // Compute time seed - auto now = std::chrono::duration_cast<std::chrono::seconds>(clock::now().time_since_epoch()); - auto tseed = now.count() / std::chrono::seconds(EXPORT_KEY_RENEWAL_TIME).count(); - if (previous) - tseed--; - std::stringstream ss; - ss << std::hex << tseed; - auto tseed_str = ss.str(); - - // Generate key for archive encryption, using PIN as the salt - std::vector<uint8_t> salt_key; - salt_key.reserve(pin.size() + tseed_str.size()); - salt_key.insert(salt_key.end(), pin.begin(), pin.end()); - salt_key.insert(salt_key.end(), tseed_str.begin(), tseed_str.end()); - auto key = dht::crypto::stretchKey(password, salt_key, 256/8); - - // Generate public storage location as SHA1(key). - auto loc = dht::InfoHash::get(key); - - return {key, loc}; -} - -std::string -generatePIN(size_t length = 8) -{ - static constexpr const char alphabet[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - dht::crypto::random_device rd; - std::uniform_int_distribution<size_t> dis(0, sizeof(alphabet)-2); - std::string ret; - ret.reserve(length); - for (size_t i=0; i<length; i++) - ret.push_back(alphabet[dis(rd)]); - return ret; -} - void JamiAccount::addDevice(const std::string& password) { - dht::ThreadPool::computation().run([this_=shared(), password]() { - std::vector<uint8_t> key; - dht::InfoHash loc; - std::string pin_str; - AccountArchive a; - try { - JAMI_DBG("[Account %s] exporting account", this_->getAccountID().c_str()); - - a = this_->readArchive(password); - - // Generate random PIN - pin_str = generatePIN(); - - std::tie(key, loc) = computeKeys(password, pin_str); - } catch (const std::exception& e) { - JAMI_ERR("[Account %s] can't export account: %s", this_->getAccountID().c_str(), e.what()); - emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 1, ""); - return; - } - // now that key and loc are computed, display to user in lowercase - std::transform(pin_str.begin(), pin_str.end(), pin_str.begin(), ::tolower); - try { - this_->updateArchive(a); - auto encrypted = dht::crypto::aesEncrypt(archiver::compress(a.serialize()), key); - if (not this_->dht_.isRunning()) - throw std::runtime_error("DHT is not running.."); - this_->dht_.put(loc, encrypted, [this_,pin_str](bool ok) { - JAMI_DBG("[Account %s] account archive published: %s", this_->getAccountID().c_str(), ok ? "success" : "failure"); - if (ok) - emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 0, pin_str); - else - emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 2, ""); - }); - JAMI_WARN("[Account %s] exporting account with PIN: %s at %s (size %zu)", this_->getAccountID().c_str(), pin_str.c_str(), loc.toString().c_str(), encrypted.size()); - } catch (const std::exception& e) { - JAMI_ERR("[Account %s] can't export account: %s", this_->getAccountID().c_str(), e.what()); - emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 2, ""); - return; + accountManager_->addDevice(password, [this](AccountManager::AddDeviceResult result, std::string pin){ + switch(result) { + case AccountManager::AddDeviceResult::SUCCESS_SHOW_PIN: + emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(getAccountID(), 0, pin); + break; + case AccountManager::AddDeviceResult::ERROR_CREDENTIALS: + emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(getAccountID(), 1, ""); + break; + case AccountManager::AddDeviceResult::ERROR_NETWORK: + emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(getAccountID(), 2, ""); + break; } }); } @@ -1177,71 +850,32 @@ JamiAccount::addDevice(const std::string& password) bool JamiAccount::exportArchive(const std::string& destinationPath, const std::string& password) { - try { - // Save contacts if possible before exporting - AccountArchive archive; - if (!archiveHasPassword_ || !password.empty()) { - archive = readArchive(password); - updateArchive(archive); - archive.save(fileutils::getFullPath(idPath_, archivePath_), password); - } - // Export the file - auto sourcePath = fileutils::getFullPath(idPath_, archivePath_); - std::ifstream src = fileutils::ifstream(sourcePath, std::ios::in | std::ios::binary); - if (!src) return false; - std::ofstream dst = fileutils::ofstream(destinationPath, std::ios::out | std::ios::binary); - dst << src.rdbuf(); - } catch (const std::runtime_error& ex) { - JAMI_ERR("[Account %s] Can't export archive: %s", getAccountID().c_str(), ex.what()); - return false; - } catch (...) { - JAMI_ERR("[Account %s] Can't export archive: can't read archive", getAccountID().c_str()); - return false; + if (auto manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) { + return manager->exportArchive(destinationPath, password); } - return true; + return false; } bool JamiAccount::revokeDevice(const std::string& password, const std::string& device) { - // shared_ptr of future - auto fa = dht::ThreadPool::computation().getShared<AccountArchive>( - [this, password] { return readArchive(password); }); - findCertificate(dht::InfoHash(device), - [this,fa=std::move(fa),password,device](const std::shared_ptr<dht::crypto::Certificate>& crt) mutable - { - if (not crt) { - emitSignal<DRing::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(), device, 2); - return; - } - std::unique_lock<std::mutex> lock(deviceListMutex_); - foundAccountDevice(crt); - AccountArchive a; - try { - a = fa.get(); - } catch (...) { + return accountManager_->revokeDevice(password, device, [this, device](AccountManager::RevokeDeviceResult result){ + switch(result) { + case AccountManager::RevokeDeviceResult::SUCCESS: + emitSignal<DRing::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(), device, 0); + break; + case AccountManager::RevokeDeviceResult::ERROR_CREDENTIALS: emitSignal<DRing::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(), device, 1); - return; + break; + case AccountManager::RevokeDeviceResult::ERROR_NETWORK: + emitSignal<DRing::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(), device, 2); + break; } - // Add revoked device to the revocation list and resign it - if (not a.revoked) - a.revoked = std::make_shared<decltype(a.revoked)::element_type>(); - a.revoked->revoke(*crt); - a.revoked->sign(a.id); - // add to CRL cache - tls::CertificateStore::instance().pinRevocationList(a.id.second->getId().toString(), a.revoked); - tls::CertificateStore::instance().loadRevocations(*identity_.second->issuer); - saveArchive(a, password); - knownDevices_.erase(crt->getId()); - saveKnownDevices(); - emitSignal<DRing::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(), device, 0); - emitSignal<DRing::ConfigurationSignal::KnownDevicesChanged>(getAccountID(), getKnownDevices()); - lock.unlock(); - syncDevices(); }); return true; } + std::pair<std::string, std::string> JamiAccount::saveIdentity(const dht::crypto::Identity id, const std::string& path, const std::string& name) { @@ -1253,275 +887,7 @@ JamiAccount::saveIdentity(const dht::crypto::Identity id, const std::string& pat return names; } -void -JamiAccount::loadAccountFromArchive(AccountArchive&& archive, const std::string& archive_password) -{ - initRingDevice(archive); - saveArchive(archive, archive_password); - registrationState_ = RegistrationState::UNREGISTERED; - saveConfig(); - doRegister(); -} - -void -JamiAccount::loadAccountFromFile(const std::string& archive_path, const std::string& archive_password) -{ - setRegistrationState(RegistrationState::INITIALIZING); - dht::ThreadPool::computation().run([w=weak(), archive_password, archive_path, accountId = getAccountID()]{ - AccountArchive archive; - try { - archive = AccountArchive(archive_path, archive_password); - } catch (const std::exception& ex) { - JAMI_WARN("[Account %s] can't read file: %s", accountId.c_str(), ex.what()); - if (auto this_ = w.lock()) { - this_->setRegistrationState(RegistrationState::ERROR_GENERIC); - runOnMainThread([accountId]() { - Manager::instance().removeAccount(accountId); - }); - } - return; - } - runOnMainThread([w, archive, archive_password]() mutable { - if (auto this_ = w.lock()) - this_->loadAccountFromArchive(std::move(archive), archive_password); - }); - }); -} - -void -JamiAccount::loadAccountFromDHT(const std::string& archive_password, const std::string& archive_pin) -{ - setRegistrationState(RegistrationState::INITIALIZING); - - // launch dedicated dht instance - if (dht_.isRunning()) { - JAMI_ERR("DHT already running (stopping it first)."); - dht_.join(); - } - - dht::DhtRunner::Config config {}; - config.dht_config.node_config.persist_path = cachePath_+DIR_SEPARATOR_STR "dhtstate"; - config.proxy_server = getDhtProxyServer(); - - dht::DhtRunner::Context context {}; - context.statusChangedCallback = [](dht::NodeStatus s4, dht::NodeStatus s6) { - JAMI_WARN("Dht status : IPv4 %s; IPv6 %s", dhtStatusStr(s4), dhtStatusStr(s6)); - }; - - dht_.run((in_port_t)dhtPortUsed_, config, std::move(context)); - for (const auto& bootstrap : loadBootstrap()) - dht_.bootstrap(bootstrap); - - auto w = weak(); - auto state_old = std::make_shared<std::pair<bool, bool>>(false, true); - auto state_new = std::make_shared<std::pair<bool, bool>>(false, true); - auto found = std::make_shared<bool>(false); - - auto searchEnded = [w,found,state_old,state_new](){ - if (*found) - return; - if (state_old->first && state_new->first) { - bool network_error = !state_old->second && !state_new->second; - if (auto this_ = w.lock()) { - JAMI_WARN("[Account %s] failure looking for archive on DHT: %s", this_->getAccountID().c_str(), network_error ? "network error" : "not found"); - this_->setRegistrationState(network_error ? RegistrationState::ERROR_NETWORK : RegistrationState::ERROR_GENERIC); - runOnMainThread([=]() { - Manager::instance().removeAccount(this_->getAccountID()); - }); - } - } - }; - - auto search = [w,found,archive_password,archive_pin,searchEnded](bool previous, std::shared_ptr<std::pair<bool, bool>>& state) { - std::vector<uint8_t> key; - dht::InfoHash loc; - - // compute archive location and decryption keys - try { - std::tie(key, loc) = computeKeys(archive_password, archive_pin, previous); - if (auto this_ = w.lock()) { - JAMI_DBG("[Account %s] trying to load account from DHT with %s at %s", this_->getAccountID().c_str(), archive_pin.c_str(), loc.toString().c_str()); - this_->dht_.get(loc, [w,key,found,archive_password](const std::shared_ptr<dht::Value>& val) { - std::vector<uint8_t> decrypted; - try { - decrypted = archiver::decompress(dht::crypto::aesDecrypt(val->data, key)); - } catch (const std::exception& ex) { - return true; - } - JAMI_DBG("Found archive on the DHT"); - dht::ThreadPool::computation().run([=]() { - if (auto this_ = w.lock()) { - try { - *found = true; - this_->loadAccountFromArchive(AccountArchive(decrypted), archive_password); - } catch (const std::exception& e) { - JAMI_WARN("[Account %s] error reading archive: %s", this_->getAccountID().c_str(), e.what()); - this_->setRegistrationState(RegistrationState::ERROR_GENERIC); - runOnMainThread([=]() { - Manager::instance().removeAccount(this_->getAccountID()); - }); - } - } - }); - return not *found; - }, [=](bool ok) { - JAMI_DBG("[Account %s] DHT archive search ended at %s", this_->getAccountID().c_str(), loc.toString().c_str()); - state->first = true; - state->second = ok; - searchEnded(); - }); - } - } catch (const std::exception& e) { - JAMI_ERR("Error computing keys: %s", e.what()); - state->first = true; - state->second = true; - searchEnded(); - return; - } - }; - - dht::ThreadPool::computation().run(std::bind(search, true, state_old)); - dht::ThreadPool::computation().run(std::bind(search, false, state_new)); -} - -void -JamiAccount::createAccount(const std::string& archive_password, dht::crypto::Identity&& migrate) -{ - JAMI_WARN("[Account %s] creating new account", getAccountID().c_str()); - setRegistrationState(RegistrationState::INITIALIZING); - dht::ThreadPool::computation().run([sthis=shared(), archive_password, migrate]() mutable { - AccountArchive a; - auto& this_ = *sthis; - - auto future_keypair = dht::ThreadPool::computation().get<dev::KeyPair>(std::bind(&dev::KeyPair::create)); - try { - if (migrate.first and migrate.second) { - JAMI_WARN("[Account %s] converting certificate from old ring account %s", - this_.getAccountID().c_str(), migrate.first->getPublicKey().getId().toString().c_str()); - a.id = std::move(migrate); - try { - a.ca_key = std::make_shared<dht::crypto::PrivateKey>(fileutils::loadFile("ca.key", this_.idPath_)); - } catch (...) {} - updateCertificates(a, migrate); - } else { - auto ca = dht::crypto::generateIdentity("Ring CA"); - if (!ca.first || !ca.second) { - throw VoipLinkException("Can't generate CA for this account."); - } - a.id = dht::crypto::generateIdentity("Ring", ca, 4096, true); - if (!a.id.first || !a.id.second) { - throw VoipLinkException("Can't generate identity for this account."); - } - JAMI_WARN("[Account %s] new account: CA: %s, RingID: %s", - this_.getAccountID().c_str(), ca.second->getId().toString().c_str(), - a.id.second->getId().toString().c_str()); - a.ca_key = ca.first; - } - this_.ringAccountId_ = a.id.second->getId().toString(); - this_.username_ = RING_URI_PREFIX+this_.ringAccountId_; - auto keypair = future_keypair.get(); - this_.ethAccount_ = keypair.address().hex(); - a.eth_key = keypair.secret().makeInsecure().asBytes(); - - this_.createRingDevice(a.id); - this_.saveArchive(a, archive_password); - } catch (...) { - this_.setRegistrationState(RegistrationState::ERROR_GENERIC); - runOnMainThread([sthis]() { - Manager::instance().removeAccount(sthis->getAccountID()); - }); - } - JAMI_DBG("[Account %s] Account creation ended, saving configuration", this_.getAccountID().c_str()); - this_.setRegistrationState(RegistrationState::UNREGISTERED); - this_.saveConfig(); - this_.doRegister(); - }); -} - -bool -JamiAccount::needsMigration(const dht::crypto::Identity& id) -{ - if (not id.second) - return false; - auto cert = id.second->issuer; - while (cert) { - if (not cert->isCA()){ - JAMI_WARN("certificate %s is not a CA, needs update.", cert->getId().toString().c_str()); - return true; - } - if (cert->getExpiration() < clock::now()) { - JAMI_WARN("certificate %s is expired, needs update.", cert->getId().toString().c_str()); - return true; - } - cert = cert->issuer; - } - return false; -} - -bool -JamiAccount::updateCertificates(AccountArchive& archive, dht::crypto::Identity& device) -{ - JAMI_WARN("Updating certificates"); - using Certificate = dht::crypto::Certificate; - - // We need the CA key to resign certificates - if (not archive.id.first or - not *archive.id.first or - not archive.id.second or - not archive.ca_key or - not *archive.ca_key) - return false; - - // Currently set the CA flag and update expiration dates - bool updated = false; - - auto& cert = archive.id.second; - auto ca = cert->issuer; - // Update CA if possible and relevant - if (not ca or (not ca->issuer and (not ca->isCA() or ca->getExpiration() < clock::now()))) { - ca = std::make_shared<Certificate>(Certificate::generate(*archive.ca_key, "Ring CA", {}, true)); - updated = true; - JAMI_DBG("CA CRT re-generated"); - } - - // Update certificate - if (updated or not cert->isCA() or cert->getExpiration() < clock::now()) { - cert = std::make_shared<Certificate>(Certificate::generate(*archive.id.first, "Ring", dht::crypto::Identity{archive.ca_key, ca}, true)); - updated = true; - JAMI_DBG("ring CRT re-generated"); - } - - if (updated and device.first and *device.first) { - // update device certificate - device.second = std::make_shared<Certificate>(Certificate::generate(*device.first, "Ring device", archive.id)); - JAMI_DBG("device CRT re-generated"); - } - - return updated; -} - -void -JamiAccount::migrateAccount(const std::string& pwd, dht::crypto::Identity& device) -{ - AccountArchive archive; - try { - archive = readArchive(pwd); - } catch (...) { - JAMI_DBG("[Account %s] Can't load archive", getAccountID().c_str()); - Migration::setState(accountID_, Migration::State::INVALID); - setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION); - return; - } - - if (updateCertificates(archive, device)) { - std::tie(tlsPrivateKeyFile_, tlsCertificateFile_) = saveIdentity(device, idPath_, "ring_device"); - saveArchive(archive, pwd); - setRegistrationState(RegistrationState::UNREGISTERED); - Migration::setState(accountID_, Migration::State::SUCCESS); - } else - Migration::setState(accountID_, Migration::State::INVALID); -} - +// must be called while configurationMutex_ is locked void JamiAccount::loadAccount(const std::string& archive_password, const std::string& archive_pin, const std::string& archive_path) { @@ -1529,66 +895,118 @@ JamiAccount::loadAccount(const std::string& archive_password, const std::string& return; JAMI_DBG("[Account %s] loading account", getAccountID().c_str()); + + AccountManager::OnChangeCallback callbacks { + [this](const std::string& uri, bool confirmed) { + emitSignal<DRing::ConfigurationSignal::ContactAdded>(getAccountID(), uri, confirmed); + }, + [this](const std::string& uri, bool banned){ + emitSignal<DRing::ConfigurationSignal::ContactRemoved>(getAccountID(), uri, banned); + }, + [this](const std::string& uri, const std::vector<uint8_t>& payload, time_t received){ + emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(getAccountID(), uri, payload, received); + }, + [this](){ + emitSignal<DRing::ConfigurationSignal::KnownDevicesChanged>(getAccountID(), getKnownDevices()); + }, + }; + try { - auto id = loadIdentity(tlsCertificateFile_, tlsPrivateKeyFile_, tlsPassword_); + accountManager_.reset(new ArchiveAccountManager(getPath(), + dht_, + [w = weak()](AccountManager::AsyncUser&& cb) { + if (auto this_ = w.lock()) + cb(*this_->accountManager_); + }, + [this]() { return getAccountDetails(); }, + archivePath_.empty() ? "archive.gz" : archivePath_, + nameServer_)); + + auto id = accountManager_->loadIdentity(tlsCertificateFile_, tlsPrivateKeyFile_, tlsPassword_); bool hasArchive = not archivePath_.empty() and fileutils::isFile(fileutils::getFullPath(idPath_, archivePath_)); - if (useIdentity(id)) { + + if (auto info = accountManager_->useIdentity(id, receipt_, receiptSignature_, std::move(callbacks))) { // normal loading path - loadKnownDevices(); - loadContacts(); - loadTrustRequests(); - if (not hasArchive) - JAMI_WARN("[Account %s] account archive not found, won't be able to add new devices", getAccountID().c_str()); + id_ = std::move(id); + username_ = RING_URI_PREFIX+info->accountId; + JAMI_WARN("[Account %s] loaded account identity", getAccountID().c_str()); if (not isEnabled()) { setRegistrationState(RegistrationState::UNREGISTERED); } } else if (isEnabled()) { - if (hasArchive) { - if (needsMigration(id)) { - JAMI_WARN("[Account %s] account certificate needs update", getAccountID().c_str()); - migrateAccount(archive_password, id); - } - else { - JAMI_WARN("[Account %s] archive present but no valid receipt: creating new device", getAccountID().c_str()); - try { - initRingDevice(readArchive(archive_password)); - } - catch (...) { - Migration::setState(accountID_, Migration::State::INVALID); - setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION); - return; - } - Migration::setState(accountID_, Migration::State::SUCCESS); - setRegistrationState(RegistrationState::UNREGISTERED); - } - dht::ThreadPool::io().run([w = weak()]{ - if (auto this_ = w.lock()) - this_->saveConfig(); + setRegistrationState(RegistrationState::INITIALIZING); + auto fDeviceKey = dht::ThreadPool::computation().getShared<std::shared_ptr<dht::crypto::PrivateKey>>([](){ + return std::make_shared<dht::crypto::PrivateKey>(dht::crypto::PrivateKey::generate()); }); - loadAccount(archive_password); - } - else { - // no receipt or archive, creating new account + + auto fReq = dht::ThreadPool::computation().get<std::unique_ptr<dht::crypto::CertificateRequest>>([fDeviceKey]{ + auto request = std::make_unique<dht::crypto::CertificateRequest>(); + request->setName("Jami device"); + auto deviceKey = fDeviceKey.get(); + request->setUID(deviceKey->getPublicKey().getId().toString()); + request->sign(*deviceKey); + return request; + }); + + auto creds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>(); + creds->archivePath = archivePath_.empty() ? "archive.gz" : archivePath_; if (not archive_path.empty()) { - // import account from file - loadAccountFromFile(archive_path, archive_password); + creds->scheme = "file"; + creds->uri = archive_path; } else if (not archive_pin.empty()) { - // import account from DHT - loadAccountFromDHT(archive_password, archive_pin); - } - else { - // create new account - createAccount(archive_password, std::move(id)); + creds->scheme = "dht"; + creds->uri = archive_pin; + creds->dhtBootstrap = loadBootstrap(); + creds->dhtPort = (in_port_t)dhtPortUsed_; } - } + creds->password = archive_password; + accountManager_->initAuthentication( + std::move(fReq), + std::move(creds), + [this, fDeviceKey](const AccountInfo& info, + const std::map<std::string, std::string>& config, + std::string&& receipt, + std::vector<uint8_t>&& receipt_signature) { + JAMI_WARN("Auth success !"); + + fileutils::check_dir(idPath_.c_str(), 0700); + + // save the chain including CA + auto id = info.identity; + id.first = std::move(fDeviceKey.get()); + std::tie(tlsPrivateKeyFile_, tlsCertificateFile_) = saveIdentity(id, idPath_, "ring_device"); + id_ = std::move(id); + tlsPassword_ = {}; + + username_ = RING_URI_PREFIX+info.accountId; + + ringDeviceName_ = ip_utils::getDeviceName(); + if (ringDeviceName_.empty()) + ringDeviceName_ = info.deviceId.substr(8); + + receipt_ = std::move(receipt); + receiptSignature_ = std::move(receipt_signature); + accountManager_->foundAccountDevice(info.identity.second, ringDeviceName_, clock::now()); + setRegistrationState(RegistrationState::UNREGISTERED); + + saveConfig(); + doRegister(); + }, [this](AccountManager::AuthError error, const std::string& message) { + JAMI_WARN("Auth error: %d %s", (int)error, message.c_str()); + { + std::lock_guard<std::mutex> lock(configurationMutex_); + setRegistrationState(RegistrationState::ERROR_GENERIC); + } + Manager::instance().removeAccount(getAccountID()); + }, std::move(callbacks)); } } catch (const std::exception& e) { JAMI_WARN("[Account %s] error loading account: %s", getAccountID().c_str(), e.what()); - identity_ = dht::crypto::Identity{}; + accountManager_.reset(); setRegistrationState(RegistrationState::ERROR_GENERIC); } } @@ -1647,20 +1065,13 @@ JamiAccount::setAccountDetails(const std::map<std::string, std::string>& details #if HAVE_RINGNS parseString(details, DRing::Account::ConfProperties::RingNS::URI, nameServer_); - nameDir_ = NameDirectory::instance(nameServer_); #endif loadAccount(archive_password, archive_pin, archive_path); // update device name if necessary - std::lock_guard<std::mutex> devicelock(deviceListMutex_); - auto dev = knownDevices_.find(dht::InfoHash(ringDeviceId_)); - if (dev != knownDevices_.end()) { - if (dev->second.name != ringDeviceName_) { - dev->second.name = ringDeviceName_; - saveKnownDevices(); - } - } + if (accountManager_) + accountManager_->setAccountDeviceName(ringDeviceName_); } std::map<std::string, std::string> @@ -1673,7 +1084,12 @@ JamiAccount::getAccountDetails() const a.emplace(DRing::Account::ConfProperties::DHT_PEER_DISCOVERY, dhtPeerDiscovery_ ? TRUE_STR : FALSE_STR); a.emplace(DRing::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY, accountPeerDiscovery_ ? TRUE_STR : FALSE_STR); a.emplace(DRing::Account::ConfProperties::ACCOUNT_PUBLISH, accountPublish_ ? TRUE_STR : FALSE_STR); - a.emplace(DRing::Account::ConfProperties::RING_DEVICE_ID, ringDeviceId_); + if (accountManager_) { + if (auto info = accountManager_->getInfo()) { + a.emplace(DRing::Account::ConfProperties::RING_DEVICE_ID, info->deviceId); + a.emplace(DRing::Account::ConfProperties::RingNS::ACCOUNT, info->ethAccount); + } + } a.emplace(DRing::Account::ConfProperties::RING_DEVICE_NAME, ringDeviceName_); a.emplace(DRing::Account::ConfProperties::Presence::SUPPORT_SUBSCRIBE, TRUE_STR); if (not archivePath_.empty()) @@ -1703,11 +1119,8 @@ JamiAccount::getAccountDetails() const a.emplace(DRing::Account::ConfProperties::PROXY_ENABLED, proxyEnabled_ ? TRUE_STR : FALSE_STR); a.emplace(DRing::Account::ConfProperties::PROXY_SERVER, proxyServer_); a.emplace(DRing::Account::ConfProperties::PROXY_PUSH_TOKEN, deviceKey_); - - //a.emplace(DRing::Account::ConfProperties::ETH::KEY_FILE, ethPath_); - a.emplace(DRing::Account::ConfProperties::RingNS::ACCOUNT, ethAccount_); #if HAVE_RINGNS - a.emplace(DRing::Account::ConfProperties::RingNS::URI, nameDir_.get().getServer()); + a.emplace(DRing::Account::ConfProperties::RingNS::URI, nameServer_); #endif return a; @@ -1730,47 +1143,35 @@ void JamiAccount::lookupName(const std::string& name) { auto acc = getAccountID(); - NameDirectory::lookupUri(name, nameServer_, [acc,name](const std::string& result, NameDirectory::Response response) { + accountManager_->lookupName(name, [acc,name](const std::string& result, NameDirectory::Response response) { emitSignal<DRing::ConfigurationSignal::RegisteredNameFound>(acc, (int)response, result, name); }); + //NameDirectory::lookupUri(); } void JamiAccount::lookupAddress(const std::string& addr) { auto acc = getAccountID(); - nameDir_.get().lookupAddress(addr, [acc,addr](const std::string& result, NameDirectory::Response response) { + accountManager_->lookupAddress(addr, [acc,addr](const std::string& result, NameDirectory::Response response) { emitSignal<DRing::ConfigurationSignal::RegisteredNameFound>(acc, (int)response, addr, result); }); } -using Blob = std::vector<uint8_t>; + void JamiAccount::registerName(const std::string& password, const std::string& name) { - std::string signedName; - auto nameLowercase {name}; - std::transform(nameLowercase.begin(), nameLowercase.end(), nameLowercase.begin(), ::tolower); - std::string publickey; - try { - auto privateKey = readArchive(password).id.first; - signedName = base64::encode(privateKey->sign(Blob(nameLowercase.begin(), nameLowercase.end()))); - publickey = privateKey->getPublicKey().toString(); - } catch (const std::exception& e) { - JAMI_ERR("[Account %s] can't export account: %s", getAccountID().c_str(), e.what()); - emitSignal<DRing::ConfigurationSignal::NameRegistrationEnded>(getAccountID(), 1, name); - return; - } - - nameDir_.get().registerName(ringAccountId_, nameLowercase, ethAccount_, [acc=getAccountID(), name, w=weak()](NameDirectory::RegistrationResponse response){ + accountManager_->registerName(password, name, [acc=getAccountID(), name, w=weak()](NameDirectory::RegistrationResponse response){ int res = (response == NameDirectory::RegistrationResponse::success) ? 0 : ( + (response == NameDirectory::RegistrationResponse::invalidCredentials) ? 1 : ( (response == NameDirectory::RegistrationResponse::invalidName) ? 2 : ( - (response == NameDirectory::RegistrationResponse::alreadyTaken) ? 3 : 4)); + (response == NameDirectory::RegistrationResponse::alreadyTaken) ? 3 : 4))); if (response == NameDirectory::RegistrationResponse::success) { if (auto this_ = w.lock()) this_->registeredName_ = name; } emitSignal<DRing::ConfigurationSignal::NameRegistrationEnded>(acc, res, name); - }, signedName, publickey); + }); } #endif @@ -1801,7 +1202,7 @@ JamiAccount::handlePendingCallList() if (handled) { // Cancel pending listen (outgoing call) if (not incoming) - dht_.cancelListen(pc_iter->call_key, std::move(pc_iter->listen_key)); + dht_->cancelListen(pc_iter->call_key, std::move(pc_iter->listen_key)); pc_iter = pending_calls.erase(pc_iter); } else ++pc_iter; @@ -1841,7 +1242,7 @@ JamiAccount::checkPeerTlsCertificate(dht::InfoHash from, // Check expected peer identity dht::InfoHash tls_account_id; - if (not foundPeerDevice(crt, tls_account_id)) { + if (not AccountManager::foundPeerDevice(crt, tls_account_id)) { JAMI_ERR("[peer:%s] Discarding message from invalid peer certificate.", from.toString().c_str()); return PJ_SSL_CERT_EUNKNOWN; } @@ -1916,7 +1317,8 @@ JamiAccount::handlePendingCall(PendingCall& pc, bool incoming) // Securize a SIP transport with TLS (on top of ICE tranport) and assign the call with it auto remote_device = pc.from; auto remote_account = pc.from_account; - if (not identity_.first or not identity_.second) + auto id = accountManager_->getInfo()->identity; + if (not id.first or not id.second) throw std::runtime_error("No identity configured for this account."); std::weak_ptr<JamiAccount> waccount = weak(); @@ -1924,8 +1326,8 @@ JamiAccount::handlePendingCall(PendingCall& pc, bool incoming) tls::TlsParams tlsParams { /*.ca_list = */"", /*.ca = */pc.from_cert, - /*.cert = */identity_.second, - /*.cert_key = */identity_.first, + /*.cert = */id.second, + /*.cert_key = */id.first, /*.dh_params = */dhParams_, /*.timeout = */std::chrono::duration_cast<decltype(tls::TlsParams::timeout)>(TLS_TIMEOUT), /*.cert_check = */[waccount,wcall,remote_device,remote_account](unsigned status, @@ -2117,8 +1519,9 @@ JamiAccount::trackBuddyPresence(const std::string& buddy_id, bool track) } else { auto buddy = trackedBuddies_.find(h); if (buddy != trackedBuddies_.end()) { - if (dht_.isRunning()) - dht_.cancelListen(h, std::move(buddy->second.listenToken)); + if (auto dht = dht_) + if (dht->isRunning()) + dht->cancelListen(h, std::move(buddy->second.listenToken)); trackedBuddies_.erase(buddy); } } @@ -2127,10 +1530,11 @@ JamiAccount::trackBuddyPresence(const std::string& buddy_id, bool track) void JamiAccount::trackPresence(const dht::InfoHash& h, BuddyInfo& buddy) { - if (not dht_.isRunning()) { + auto dht = dht_; + if (not dht or not dht->isRunning()) { return; } - buddy.listenToken = dht_.listen<DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&&, bool expired){ + buddy.listenToken = dht->listen<DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&&, bool expired){ bool wasConnected, isConnected; { std::lock_guard<std::mutex> lock(buddyInfoMtx); @@ -2151,7 +1555,7 @@ JamiAccount::trackPresence(const dht::InfoHash& h, BuddyInfo& buddy) } return true; }); - // JAMI_DBG("[Account %s] tracking buddy %s", getAccountID().c_str(), h.to_c_str()); + JAMI_DBG("[Account %s] tracking buddy %s", getAccountID().c_str(), h.to_c_str()); } std::map<std::string, bool> @@ -2186,19 +1590,19 @@ JamiAccount::doRegister_() std::lock_guard<std::mutex> lock(configurationMutex_); try { - if (not identity_.first or not identity_.second) + if (not accountManager_->getInfo()) throw std::runtime_error("No identity configured for this account."); loadTreatedCalls(); loadTreatedMessages(); - if (dht_.isRunning()) { + if (dht_->isRunning()) { JAMI_ERR("[Account %s] DHT already running (stopping it first).", getAccountID().c_str()); - dht_.join(); + dht_->join(); } #if HAVE_RINGNS // Look for registered name on the blockchain - nameDir_.get().lookupAddress(ringAccountId_, [w=weak()](const std::string& result, const NameDirectory::Response& response) { + accountManager_->lookupAddress(accountManager_->getInfo()->accountId, [w=weak()](const std::string& result, const NameDirectory::Response& response) { if (auto this_ = w.lock()) { if (response == NameDirectory::Response::found) { if (this_->registeredName_ != result) { @@ -2219,7 +1623,7 @@ JamiAccount::doRegister_() config.dht_config.node_config.network = 0; config.dht_config.node_config.maintain_storage = false; config.dht_config.node_config.persist_path = cachePath_+DIR_SEPARATOR_STR "dhtstate"; - config.dht_config.id = identity_; + config.dht_config.id = accountManager_->getInfo()->identity; config.proxy_server = getDhtProxyServer(); config.push_node_id = getAccountID(); config.threaded = true; @@ -2230,7 +1634,7 @@ JamiAccount::doRegister_() if (not deviceKey_.empty()) { JAMI_INFO("[Account %s] using push notifications", getAccountID().c_str()); - dht_.setPushNotificationToken(deviceKey_); + dht_->setPushNotificationToken(deviceKey_); } //check if dht peer service is enabled @@ -2313,48 +1717,22 @@ JamiAccount::doRegister_() }; setRegistrationState(RegistrationState::TRYING); - dht_.run((in_port_t)dhtPortUsed_, config, std::move(context)); + dht_->run((in_port_t)dhtPortUsed_, config, std::move(context)); for (const auto& bootstrap : loadBootstrap()) - dht_.bootstrap(bootstrap); - - // Put device annoucement - if (announce_) { - auto h = dht::InfoHash(ringAccountId_); - JAMI_DBG("[Account %s] announcing device at %s", getAccountID().c_str(), h.toString().c_str()); - dht_.put(h, announce_, dht::DoneCallback{}, {}, true); - for (const auto& crl : identity_.second->issuer->getRevocationLists()) - dht_.put(h, crl, dht::DoneCallback{}, {}, true); - dht_.listen<DeviceAnnouncement>(h, [this](DeviceAnnouncement&& dev) { - findCertificate(dev.dev, [this](const std::shared_ptr<dht::crypto::Certificate>& crt) { - std::lock_guard<std::mutex> lock(deviceListMutex_); - foundAccountDevice(crt); - }); - return true; - }); - dht_.listen<dht::crypto::RevocationList>(h, [this](dht::crypto::RevocationList&& crl) { - if (crl.isSignedBy(*identity_.second->issuer)) { - JAMI_DBG("[Account %s] found CRL for account.", getAccountID().c_str()); - tls::CertificateStore::instance().pinRevocationList( - ringAccountId_, - std::make_shared<dht::crypto::RevocationList>(std::move(crl))); - } - return true; - }); - syncDevices(); - } else { - JAMI_WARN("[Account %s] can't announce device: no annoucement...", getAccountID().c_str()); - } + dht_->bootstrap(bootstrap); + + accountManager_->startSync(); // Listen for incoming calls - callKey_ = dht::InfoHash::get("callto:"+ringDeviceId_); - JAMI_DBG("[Account %s] Listening on callto:%s : %s", getAccountID().c_str(), ringDeviceId_.c_str(), callKey_.toString().c_str()); - dht_.listen<dht::IceCandidates>( + callKey_ = dht::InfoHash::get("callto:"+accountManager_->getInfo()->deviceId); + JAMI_DBG("[Account %s] Listening on callto:%s : %s", getAccountID().c_str(), accountManager_->getInfo()->deviceId.c_str(), callKey_.toString().c_str()); + dht_->listen<dht::IceCandidates>( callKey_, [this] (dht::IceCandidates&& msg) { // callback for incoming call auto from = msg.from; - if (from == dht_.getId()) + if (from == dht_->getId()) return true; auto res = treatedCalls_.insert(msg.id); @@ -2373,51 +1751,8 @@ JamiAccount::doRegister_() } ); - auto inboxKey = dht::InfoHash::get("inbox:"+ringDeviceId_); - dht_.listen<dht::TrustRequest>( - inboxKey, - [this](dht::TrustRequest&& v) { - if (v.service != DHT_TYPE_NS) - return true; - - findCertificate(v.from, [this, v](const std::shared_ptr<dht::crypto::Certificate>& cert) mutable { - // check peer certificate - dht::InfoHash peer_account; - if (not foundPeerDevice(cert, peer_account)) { - return; - } - - JAMI_WARN("Got trust request from: %s / %s", peer_account.toString().c_str(), v.from.toString().c_str()); - onTrustRequest(peer_account, v.from, time(nullptr), v.confirm, std::move(v.payload)); - }); - return true; - } - ); - - auto syncDeviceKey = dht::InfoHash::get("inbox:"+ringDeviceId_); - dht_.listen<DeviceSync>( - syncDeviceKey, - [this](DeviceSync&& sync) { - // Received device sync data. - // check device certificate - findCertificate(sync.from, [this,sync](const std::shared_ptr<dht::crypto::Certificate>& cert) mutable { - if (!cert or cert->getId() != sync.from) { - JAMI_WARN("Can't find certificate for device %s", sync.from.toString().c_str()); - return; - } - std::unique_lock<std::mutex> lock(deviceListMutex_); - if (not foundAccountDevice(cert)) - return; - lock.unlock(); - onReceiveDeviceSync(std::move(sync)); - }); - - return true; - } - ); - - auto inboxDeviceKey = dht::InfoHash::get("inbox:"+ringDeviceId_); - dht_.listen<dht::ImMessage>( + auto inboxDeviceKey = dht::InfoHash::get("inbox:"+accountManager_->getInfo()->deviceId); + dht_->listen<dht::ImMessage>( inboxDeviceKey, [this,inboxDeviceKey](dht::ImMessage&& v) { { @@ -2439,7 +1774,7 @@ JamiAccount::doRegister_() utf8_make_valid(v.msg)}}; onTextMessage(peer_account.toString(), payloads); JAMI_DBG() << "Sending message confirmation " << v.id; - dht_.putEncrypted(inboxDeviceKey, + dht_->putEncrypted(inboxDeviceKey, v.from, dht::ImMessage(v.id, std::string(), now)); }); @@ -2447,7 +1782,7 @@ JamiAccount::doRegister_() } ); - dhtPeerConnector_->onDhtConnected(ringDeviceId_); + dhtPeerConnector_->onDhtConnected(accountManager_->getInfo()->deviceId); std::lock_guard<std::mutex> lock(buddyInfoMtx); for (auto& buddy : trackedBuddies_) { @@ -2461,71 +1796,25 @@ JamiAccount::doRegister_() } } -void -JamiAccount::onTrustRequest(const dht::InfoHash& peer_account, const dht::InfoHash& peer_device, time_t received, bool confirm, std::vector<uint8_t>&& payload) -{ - // Check existing contact - auto contact = contacts_.find(peer_account); - if (contact != contacts_.end()) { - // Banned contact: discard request - if (contact->second.isBanned()) - return; - // Send confirmation - if (not confirm) - sendTrustRequestConfirm(peer_account); - // Contact exists, update confirmation status - if (not contact->second.confirmed) { - contact->second.confirmed = true; - emitSignal<DRing::ConfigurationSignal::ContactAdded>(getAccountID(), peer_account.toString(), true); - saveContacts(); - syncDevices(); - } - } else { - auto req = trustRequests_.find(peer_account); - if (req == trustRequests_.end()) { - // Add trust request - req = trustRequests_.emplace(peer_account, TrustRequest{ - peer_device, received, std::move(payload) - }).first; - } else { - // Update trust request - if (received < req->second.received) { - req->second.device = peer_device; - req->second.received = received; - req->second.payload = std::move(payload); - } else { - JAMI_DBG("[Account %s] Ignoring outdated trust request from %s", getAccountID().c_str(), peer_account.toString().c_str()); - } - } - saveTrustRequests(); - emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>( - getAccountID(), - req->first.toString(), - req->second.payload, - received - ); - } -} - void JamiAccount::onPeerMessage(const dht::InfoHash& peer_device, std::function<void(const std::shared_ptr<dht::crypto::Certificate>& crt, const dht::InfoHash& peer_account)>&& cb) { // quick check in case we already explicilty banned this device - auto trustStatus = trust_.getCertificateStatus(peer_device.toString()); + auto trustStatus = accountManager_->getCertificateStatus(peer_device.toString()); if (trustStatus == tls::TrustStore::PermissionStatus::BANNED) { JAMI_WARN("[Account %s] Discarding message from banned device %s", getAccountID().c_str(), peer_device.toString().c_str()); return; } - findCertificate(peer_device, + accountManager_->findCertificate(peer_device, [this, peer_device, cb=std::move(cb)](const std::shared_ptr<dht::crypto::Certificate>& cert) { dht::InfoHash peer_account_id; - if (not foundPeerDevice(cert, peer_account_id)) { + if (not AccountManager::foundPeerDevice(cert, peer_account_id)) { JAMI_WARN("[Account %s] Discarding message from invalid peer certificate %s.", getAccountID().c_str(), peer_device.toString().c_str()); return; } - if (not trust_.isAllowed(*cert, dhtPublicInCalls_)) { + if (not accountManager_->isAllowed(*cert, dhtPublicInCalls_)) { JAMI_WARN("[Account %s] Discarding message from unauthorized peer %s.", getAccountID().c_str(), peer_device.toString().c_str()); return; } @@ -2568,71 +1857,6 @@ JamiAccount::incomingCall(dht::IceCandidates&& msg, const std::shared_ptr<dht::c }); } -bool -JamiAccount::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, const std::string& name, const time_point& updated) -{ - if (not crt) - return false; - - // match certificate chain - if (not accountTrust_.verify(*crt)) { - JAMI_WARN("[Account %s] Found invalid account device: %s", getAccountID().c_str(), crt->getId().toString().c_str()); - return false; - } - - // insert device - auto it = knownDevices_.emplace(crt->getId(), KnownDevice{crt, name, updated}); - if (it.second) { - JAMI_DBG("[Account %s] Found account device: %s %s", getAccountID().c_str(), - name.c_str(), - crt->getId().toString().c_str()); - tls::CertificateStore::instance().pinCertificate(crt); - saveKnownDevices(); - emitSignal<DRing::ConfigurationSignal::KnownDevicesChanged>(getAccountID(), getKnownDevices()); - } else { - // update device name - if (not name.empty() and it.first->second.name != name) { - JAMI_DBG("[Account %s] updating device name: %s %s", getAccountID().c_str(), - name.c_str(), - crt->getId().toString().c_str()); - it.first->second.name = name; - saveKnownDevices(); - emitSignal<DRing::ConfigurationSignal::KnownDevicesChanged>(getAccountID(), getKnownDevices()); - } - } - return true; -} - -bool -JamiAccount::foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, dht::InfoHash& account_id) -{ - if (not crt) - return false; - - auto top_issuer = crt; - while (top_issuer->issuer) - top_issuer = top_issuer->issuer; - - // Device certificate can't be self-signed - if (top_issuer == crt) { - JAMI_WARN("Found invalid peer device: %s", crt->getId().toString().c_str()); - return false; - } - - // Check peer certificate chain - // Trust store with top issuer as the only CA - dht::crypto::TrustList peer_trust; - peer_trust.add(*top_issuer); - if (not peer_trust.verify(*crt)) { - JAMI_WARN("Found invalid peer device: %s", crt->getId().toString().c_str()); - return false; - } - - account_id = crt->issuer->getId(); - JAMI_WARN("Found peer device: %s account:%s CA:%s", crt->getId().toString().c_str(), account_id.toString().c_str(), top_issuer->getId().toString().c_str()); - return true; -} - void JamiAccount::replyToIncomingIceMsg(const std::shared_ptr<SIPCall>& call, const std::shared_ptr<IceTransport>& ice, @@ -2645,7 +1869,7 @@ JamiAccount::replyToIncomingIceMsg(const std::shared_ptr<SIPCall>& call, call->setPeerUri(RING_URI_PREFIX + from); std::weak_ptr<SIPCall> wcall = call; #if HAVE_RINGNS - nameDir_.get().lookupAddress(from, [wcall](const std::string& result, const NameDirectory::Response& response){ + accountManager_->lookupAddress(from, [wcall](const std::string& result, const NameDirectory::Response& response){ if (response == NameDirectory::Response::found) if (auto call = wcall.lock()) { call->setPeerRegistredName(result); @@ -2663,7 +1887,7 @@ JamiAccount::replyToIncomingIceMsg(const std::shared_ptr<SIPCall>& call, } // Asynchronous DHT put of our local ICE data - dht_.putEncrypted( + dht_->putEncrypted( callKey_, peer_ice_msg.from, dht::Value {dht::IceCandidates(peer_ice_msg.id, blob)}, @@ -2719,7 +1943,7 @@ JamiAccount::doUnregister(std::function<void(bool)> released_cb) } JAMI_WARN("[Account %s] unregistering account %p", getAccountID().c_str(), this); - dht_.shutdown([this](){ + dht_->shutdown([this](){ JAMI_WARN("[Account %s] dht shutdown complete", getAccountID().c_str()); }); @@ -2735,7 +1959,7 @@ JamiAccount::doUnregister(std::function<void(bool)> released_cb) upnp_->removeMappings(); } - dht_.join(); + dht_->join(); lock.unlock(); setRegistrationState(RegistrationState::UNREGISTERED); @@ -2753,52 +1977,37 @@ JamiAccount::connectivityChanged() return; } - dht_.connectivityChanged(); + dht_->connectivityChanged(); setPublishedAddress({}); // reset cache } bool JamiAccount::findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb) { - if (auto cert = tls::CertificateStore::instance().getCertificate(h.toString())) { - if (cb) - cb(cert); - } else { - dht_.findCertificate(h, [cb](const std::shared_ptr<dht::crypto::Certificate>& crt) { - if (crt) - tls::CertificateStore::instance().pinCertificate(crt); - if (cb) - cb(crt); - }); - } - return true; + return accountManager_->findCertificate(h, std::move(cb)); } bool JamiAccount::findCertificate(const std::string& crt_id) { - findCertificate(dht::InfoHash(crt_id)); - return true; + return accountManager_->findCertificate(dht::InfoHash(crt_id)); } bool JamiAccount::setCertificateStatus(const std::string& cert_id, tls::TrustStore::PermissionStatus status) { - if (contacts_.find(dht::InfoHash(cert_id)) != contacts_.end()) { - JAMI_DBG("Can't set certificate status for existing contacts %s", cert_id.c_str()); - return false; - } - findCertificate(cert_id); - bool done = trust_.setCertificateStatus(cert_id, status); - if (done) + bool done = accountManager_->setCertificateStatus(cert_id, status); + if (done) { + findCertificate(cert_id); emitSignal<DRing::ConfigurationSignal::CertificateStateChanged>(getAccountID(), cert_id, tls::TrustStore::statusToStr(status)); + } return done; } std::vector<std::string> JamiAccount::getCertificatesByStatus(tls::TrustStore::PermissionStatus status) { - return trust_.getCertificatesByStatus(status); + return accountManager_->getCertificatesByStatus(status); } template<typename ID=dht::Value::Id> @@ -2879,53 +2088,13 @@ JamiAccount::isMessageTreated(unsigned int id) return true; } -void -JamiAccount::loadKnownDevices() -{ - std::map<dht::InfoHash, std::pair<std::string, uint64_t>> knownDevices; - try { - // read file - auto file = fileutils::loadFile("knownDevicesNames", idPath_); - // load values - msgpack::object_handle oh = msgpack::unpack((const char*)file.data(), file.size()); - oh.get().convert(knownDevices); - } catch (const std::exception& e) { - JAMI_WARN("[Account %s] error loading devices: %s", getAccountID().c_str(), e.what()); - return; - } - - std::lock_guard<std::mutex> lock(deviceListMutex_); - for (const auto& d : knownDevices) { - JAMI_DBG("[Account %s] loading known account device %s %s", getAccountID().c_str(), - d.second.first.c_str(), - d.first.toString().c_str()); - if (auto crt = tls::CertificateStore::instance().getCertificate(d.first.toString())) { - if (not foundAccountDevice(crt, d.second.first, clock::from_time_t(d.second.second))) - JAMI_WARN("[Account %s] can't add device %s", getAccountID().c_str(), d.first.toString().c_str()); - } - else { - JAMI_WARN("[Account %s] can't find certificate for device %s", getAccountID().c_str(), d.first.toString().c_str()); - } - } -} - -void -JamiAccount::saveKnownDevices() const -{ - std::ofstream file = fileutils::ofstream(idPath_+DIR_SEPARATOR_STR "knownDevicesNames", std::ios::trunc | std::ios::binary); - - std::map<dht::InfoHash, std::pair<std::string, uint64_t>> devices; - for (const auto& id : knownDevices_) - devices.emplace(id.first, std::make_pair(id.second.name, clock::to_time_t(id.second.last_sync))); - - msgpack::pack(file, devices); -} - std::map<std::string, std::string> JamiAccount::getKnownDevices() const { + if (not accountManager_->getInfo()) + return {}; std::map<std::string, std::string> ids; - for (auto& d : knownDevices_) { + for (auto& d : accountManager_->getKnownDevices()) { auto id = d.first.toString(); auto label = d.second.name.empty() ? id.substr(0, 8) : d.second.name; ids.emplace(std::move(id), std::move(label)); @@ -3015,7 +2184,10 @@ JamiAccount::generateDhParams() MatchRank JamiAccount::matches(const std::string &userName, const std::string &server) const { - if (userName == ringAccountId_ || server == ringAccountId_ || userName == ringDeviceId_) { + if (not accountManager_->getInfo()) + return MatchRank::NONE; + + if (userName == accountManager_->getInfo()->accountId || server == accountManager_->getInfo()->accountId || userName == accountManager_->getInfo()->deviceId) { JAMI_DBG("Matching account id in request with username %s", userName.c_str()); return MatchRank::FULL; } else { @@ -3026,7 +2198,7 @@ JamiAccount::matches(const std::string &userName, const std::string &server) con std::string JamiAccount::getFromUri() const { - const std::string uri = "<sip:" + ringAccountId_ + "@ring.dht>"; + const std::string uri = "<sip:" + accountManager_->getInfo()->accountId + "@ring.dht>"; if (not displayName_.empty()) return "\"" + displayName_ + "\" " + uri; return uri; @@ -3049,7 +2221,7 @@ JamiAccount::getContactHeader(pjsip_transport* t) contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE, "%s<sips:%s%s%s;transport=dtls>", quotedDisplayName.c_str(), - identity_.second->getId().toString().c_str(), + accountManager_->getInfo()->identity.second->getId().toString().c_str(), (address.empty() ? "" : "@"), address.c_str()); } else { @@ -3057,7 +2229,7 @@ JamiAccount::getContactHeader(pjsip_transport* t) contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE, "%s<sips:%s@ring.dht>", quotedDisplayName.c_str(), - identity_.second->getId().toString().c_str()); + accountManager_->getInfo()->identity.second->getId().toString().c_str()); } return contact_; } @@ -3067,342 +2239,72 @@ JamiAccount::getContactHeader(pjsip_transport* t) void JamiAccount::addContact(const std::string& uri, bool confirmed) { - dht::InfoHash h (uri); - if (not h) { - JAMI_ERR("[Account %s] addContact: invalid contact URI", getAccountID().c_str()); - return; - } - addContact(h, confirmed); -} - -void -JamiAccount::addContact(const dht::InfoHash& h, bool confirmed) -{ - JAMI_WARN("[Account %s] addContact: %s", getAccountID().c_str(), h.to_c_str()); - auto c = contacts_.find(h); - if (c == contacts_.end()) - c = contacts_.emplace(h, Contact{}).first; - else if (c->second.isActive() and c->second.confirmed == confirmed) - return; - c->second.added = std::time(nullptr); - c->second.confirmed = confirmed or c->second.confirmed; - auto hStr = h.toString(); - trust_.setCertificateStatus(hStr, tls::TrustStore::PermissionStatus::ALLOWED); - saveContacts(); - emitSignal<DRing::ConfigurationSignal::ContactAdded>(getAccountID(), hStr, c->second.confirmed); - syncDevices(); + JAMI_WARN("JamiAccount::addContact %d", confirmed); + accountManager_->addContact(uri, confirmed); } void JamiAccount::removeContact(const std::string& uri, bool ban) { - JAMI_WARN("[Account %s] removeContact: %s", getAccountID().c_str(), uri.c_str()); - dht::InfoHash h (uri); - auto c = contacts_.find(h); - if (c == contacts_.end()) - c = contacts_.emplace(h, Contact{}).first; - else if (not c->second.isActive() and c->second.banned == ban) - return; - c->second.removed = std::time(nullptr); - c->second.banned = ban; - trust_.setCertificateStatus(uri, ban ? tls::TrustStore::PermissionStatus::BANNED - : tls::TrustStore::PermissionStatus::UNDEFINED); - if (ban and trustRequests_.erase(h) > 0) - saveTrustRequests(); - saveContacts(); - emitSignal<DRing::ConfigurationSignal::ContactRemoved>(getAccountID(), uri, ban); - syncDevices(); + accountManager_->removeContact(uri, ban); } std::map<std::string, std::string> JamiAccount::getContactDetails(const std::string& uri) const { - dht::InfoHash h (uri); - - const auto c = contacts_.find(h); - if (c == std::end(contacts_)) { - JAMI_WARN("[dht] contact '%s' not found", uri.c_str()); - return {}; - } - - auto details = c->second.toMap(); - if (not details.empty()) - details["id"] = c->first.toString(); - - return details; + return accountManager_->getInfo() ? accountManager_->getContactDetails(uri) : std::map<std::string, std::string>{}; } std::vector<std::map<std::string, std::string>> JamiAccount::getContacts() const { - std::vector<std::map<std::string, std::string>> ret; - ret.reserve(contacts_.size()); - - for (const auto& c : contacts_) { - auto details = c.second.toMap(); - if (not details.empty()) { - details["id"] = c.first.toString(); - ret.emplace_back(std::move(details)); - } - } - return ret; -} - -void -JamiAccount::updateContact(const dht::InfoHash& id, const Contact& contact) -{ - if (not id) { - JAMI_ERR("[Account %s] updateContact: invalid contact ID", getAccountID().c_str()); - return; - } - bool stateChanged {false}; - auto c = contacts_.find(id); - if (c == contacts_.end()) { - //JAMI_DBG("[Account %s] new contact: %s", getAccountID().c_str(), id.toString().c_str()); - c = contacts_.emplace(id, contact).first; - stateChanged = c->second.isActive() or c->second.isBanned(); - } else { - //JAMI_DBG("[Account %s] updated contact: %s", getAccountID().c_str(), id.toString().c_str()); - stateChanged = c->second.update(contact); - } - if (stateChanged) { - if (c->second.isActive()) { - trust_.setCertificateStatus(id.toString(), tls::TrustStore::PermissionStatus::ALLOWED); - emitSignal<DRing::ConfigurationSignal::ContactAdded>(getAccountID(), id.toString(), c->second.confirmed); - } else { - if (c->second.banned) - trust_.setCertificateStatus(id.toString(), tls::TrustStore::PermissionStatus::BANNED); - emitSignal<DRing::ConfigurationSignal::ContactRemoved>(getAccountID(), id.toString(), c->second.banned); - } - } -} - -void -JamiAccount::loadContacts() -{ - decltype(contacts_) contacts; - try { - // read file - auto file = fileutils::loadFile("contacts", idPath_); - // load values - msgpack::object_handle oh = msgpack::unpack((const char*)file.data(), file.size()); - oh.get().convert(contacts); - } catch (const std::exception& e) { - JAMI_WARN("[Account %s] error loading contacts: %s", getAccountID().c_str(), e.what()); - return; - } - - for (auto& peer : contacts) - updateContact(peer.first, peer.second); -} - -void -JamiAccount::saveContacts() const -{ - std::ofstream file = fileutils::ofstream(idPath_+DIR_SEPARATOR_STR "contacts", std::ios::trunc | std::ios::binary); - msgpack::pack(file, contacts_); + if (not accountManager_) + return {}; + return accountManager_->getContacts(); } /* trust requests */ + std::vector<std::map<std::string, std::string>> JamiAccount::getTrustRequests() const { - using Map = std::map<std::string, std::string>; - std::vector<Map> ret; - ret.reserve(trustRequests_.size()); - for (const auto& r : trustRequests_) { - ret.emplace_back(Map { - {DRing::Account::TrustRequest::FROM, r.first.toString()}, - {DRing::Account::TrustRequest::RECEIVED, std::to_string(r.second.received)}, - {DRing::Account::TrustRequest::PAYLOAD, std::string(r.second.payload.begin(), r.second.payload.end())} - }); - } - return ret; + return accountManager_ ? accountManager_->getTrustRequests() : std::vector<std::map<std::string, std::string>>{}; } bool JamiAccount::acceptTrustRequest(const std::string& from) { - dht::InfoHash f(from); - if (not f) - return false; - - // The contact sent us a TR so we are in its contact list - addContact(f, true); - - auto i = trustRequests_.find(f); - if (i == trustRequests_.end()) - return false; - - // Clear trust request - auto treq = std::move(i->second); - trustRequests_.erase(i); - saveTrustRequests(); - - // Send confirmation - sendTrustRequestConfirm(f); - return true; + JAMI_WARN("JamiAccount::acceptTrustRequest"); + return accountManager_->acceptTrustRequest(from); } bool JamiAccount::discardTrustRequest(const std::string& from) { - dht::InfoHash f(from); - if (trustRequests_.erase(f) > 0) { - saveTrustRequests(); - return true; - } - return false; + return accountManager_->discardTrustRequest(from); } void JamiAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload) { - auto toH = dht::InfoHash(to); - if (not toH) { - JAMI_ERR("[Account %s] can't send trust request to invalid hash: %s", getAccountID().c_str(), to.c_str()); - return; - } - addContact(toH); - forEachDevice(toH, [this,toH,payload](const dht::InfoHash& dev) - { - JAMI_WARN("[Account %s] sending trust request to: %s / %s", getAccountID().c_str(), toH.toString().c_str(), dev.toString().c_str()); - dht_.putEncrypted(dht::InfoHash::get("inbox:"+dev.toString()), - dev, - dht::TrustRequest(DHT_TYPE_NS, payload)); - }); + JAMI_WARN("JamiAccount::sendTrustRequest"); + return accountManager_->sendTrustRequest(to, payload); } void -JamiAccount::sendTrustRequestConfirm(const dht::InfoHash& to) +JamiAccount::sendTrustRequestConfirm(const std::string& to) { - dht::TrustRequest answer {DHT_TYPE_NS}; - answer.confirm = true; - forEachDevice(to, [this,to,answer](const dht::InfoHash& dev) - { - JAMI_WARN("[Account %s] sending trust request reply: %s / %s", getAccountID().c_str(), to.toString().c_str(), dev.toString().c_str()); - dht_.putEncrypted(dht::InfoHash::get("inbox:"+dev.toString()), dev, answer); - }); -} - -void -JamiAccount::saveTrustRequests() const -{ - std::ofstream file = fileutils::ofstream(idPath_+DIR_SEPARATOR_STR "incomingTrustRequests", std::ios::trunc | std::ios::binary); - msgpack::pack(file, trustRequests_); -} - -void -JamiAccount::loadTrustRequests() -{ - std::map<dht::InfoHash, TrustRequest> requests; - try { - // read file - auto file = fileutils::loadFile("incomingTrustRequests", idPath_); - // load values - msgpack::object_handle oh = msgpack::unpack((const char*)file.data(), file.size()); - oh.get().convert(requests); - } catch (const std::exception& e) { - JAMI_WARN("[Account %s] error loading trust requests: %s", getAccountID().c_str(), e.what()); - return; - } - - for (auto& tr : requests) - onTrustRequest(tr.first, tr.second.device, tr.second.received, false, std::move(tr.second.payload)); + JAMI_WARN("JamiAccount::sendTrustRequestConfirm"); + return accountManager_->sendTrustRequestConfirm(dht::InfoHash(to)); } /* sync */ -void -JamiAccount::syncDevices() -{ - JAMI_DBG("[Account %s] building device sync from %s %s", getAccountID().c_str(), ringDeviceName_.c_str(), ringDeviceId_.c_str()); - DeviceSync sync_data; - sync_data.date = clock::now().time_since_epoch().count(); - sync_data.device_name = ringDeviceName_; - sync_data.peers = contacts_; - - static const size_t MAX_TRUST_REQUESTS = 20; - if (trustRequests_.size() <= MAX_TRUST_REQUESTS) - for (const auto& req : trustRequests_) - sync_data.trust_requests.emplace(req.first, TrustRequest{req.second.device, req.second.received, {}}); - else { - size_t inserted = 0; - auto req = trustRequests_.lower_bound(dht::InfoHash::getRandom()); - while (inserted++ < MAX_TRUST_REQUESTS) { - if (req == trustRequests_.end()) - req = trustRequests_.begin(); - sync_data.trust_requests.emplace(req->first, TrustRequest{req->second.device, req->second.received, {}}); - ++req; - } - } - - std::lock_guard<std::mutex> lock(deviceListMutex_); - for (const auto& dev : knownDevices_) { - if (dev.first.toString() == ringDeviceId_) - sync_data.devices_known.emplace(dev.first, ringDeviceName_); - else - sync_data.devices_known.emplace(dev.first, dev.second.name); - } - for (const auto& dev : knownDevices_) { - // don't send sync data to ourself - if (dev.first.toString() == ringDeviceId_) - continue; - JAMI_DBG("[Account %s] sending device sync to %s %s", getAccountID().c_str(), dev.second.name.c_str(), dev.first.toString().c_str()); - auto syncDeviceKey = dht::InfoHash::get("inbox:"+dev.first.toString()); - dht_.putEncrypted(syncDeviceKey, dev.first, sync_data); - } -} - -void -JamiAccount::onReceiveDeviceSync(DeviceSync&& sync) -{ - auto sync_date = clock::time_point(clock::duration(sync.date)); - { - std::lock_guard<std::mutex> lock(deviceListMutex_); - auto it = knownDevices_.find(sync.from); - if (it == knownDevices_.end()) { - JAMI_WARN("[Account %s] dropping sync data from unknown device", getAccountID().c_str()); - return; - } - if (it->second.last_sync >= sync_date) { - JAMI_DBG("[Account %s] dropping outdated sync data", getAccountID().c_str()); - return; - } - it->second.last_sync = sync_date; - } - - // Sync known devices - JAMI_DBG("[Account %s] received device sync data (%lu devices, %lu contacts)", getAccountID().c_str(), sync.devices_known.size(), sync.peers.size()); - for (const auto& d : sync.devices_known) { - findCertificate(d.first, [this,d](const std::shared_ptr<dht::crypto::Certificate>& crt) { - if (not crt) - return; - std::lock_guard<std::mutex> lock(deviceListMutex_); - foundAccountDevice(crt, d.second); - }); - } - { - std::lock_guard<std::mutex> lock(deviceListMutex_); - saveKnownDevices(); - } - - // Sync contacts - for (const auto& peer : sync.peers) - updateContact(peer.first, peer.second); - saveContacts(); - - // Sync trust requests - for (const auto& tr : sync.trust_requests) - onTrustRequest(tr.first, tr.second.device, tr.second.received, false, {}); - -} - void JamiAccount::igdChanged() { - if (not dht_.isRunning()) + if (not dht_->isRunning()) return; if (upnp_) { dht::ThreadPool::io().run([w = weak(), oldPort = static_cast<in_port_t>(dhtPortUsed_)] { @@ -3414,12 +2316,13 @@ JamiAccount::igdChanged() if (oldPort != newPort) { JAMI_WARN("DHT port changed: restarting network"); this_.doRegister_(); - } else - this_.dht_.connectivityChanged(); + } else { + this_.dht_->connectivityChanged(); + } } }); } else - dht_.connectivityChanged(); + dht_->connectivityChanged(); } void @@ -3427,24 +2330,7 @@ JamiAccount::forEachDevice(const dht::InfoHash& to, std::function<void(const dht::InfoHash&)>&& op, std::function<void(bool)>&& end) { - auto treatedDevices = std::make_shared<std::set<dht::InfoHash>>(); - dht_.get<dht::crypto::RevocationList>(to, [to](dht::crypto::RevocationList&& crl){ - tls::CertificateStore::instance().pinRevocationList(to.toString(), std::move(crl)); - return true; - }); - dht_.get<DeviceAnnouncement>(to, [this,to,treatedDevices,op=std::move(op)](DeviceAnnouncement&& dev) { - if (dev.from != to) - return true; - if (treatedDevices->emplace(dev.dev).second) { - op(dev.dev); - } - return true; - }, [=, end=std::move(end)](bool /*ok*/){ - JAMI_DBG("[Account %s] found %lu devices for %s", - getAccountID().c_str(), treatedDevices->size(), to.to_c_str()); - if (end) - end(not treatedDevices->empty()); - }); + accountManager_->forEachDevice(to, std::move(op), std::move(end)); } uint64_t @@ -3494,7 +2380,7 @@ JamiAccount::sendTextMessage(const std::string& to, const std::map<std::string, auto confirm = std::make_shared<PendingConfirmation>(); // Find listening devices for this account - forEachDevice(toH, [this,confirm,to,token,payloads,now](const dht::InfoHash& dev) + accountManager_->forEachDevice(toH, [this,confirm,to,token,payloads,now](const dht::InfoHash& dev) { { std::lock_guard<std::mutex> lock(messageMutex_); @@ -3504,7 +2390,7 @@ JamiAccount::sendTextMessage(const std::string& to, const std::map<std::string, auto h = dht::InfoHash::get("inbox:"+dev.toString()); std::lock_guard<std::mutex> l(confirm->lock); - auto list_token = dht_.listen<dht::ImMessage>(h, [this, to, token, confirm](dht::ImMessage&& msg) { + auto list_token = dht_->listen<dht::ImMessage>(h, [this, to, token, confirm](dht::ImMessage&& msg) { // check expected message confirmation if (msg.id != token) return true; @@ -3530,7 +2416,7 @@ JamiAccount::sendTextMessage(const std::string& to, const std::map<std::string, { std::lock_guard<std::mutex> l(confirm->lock); for (auto& t : confirm->listenTokens) - dht_.cancelListen(t.first, std::move(t.second)); + dht_->cancelListen(t.first, std::move(t.second)); confirm->listenTokens.clear(); confirm->replied = true; } @@ -3538,7 +2424,7 @@ JamiAccount::sendTextMessage(const std::string& to, const std::map<std::string, return false; }); confirm->listenTokens.emplace(h, std::move(list_token)); - dht_.putEncrypted(h, dev, + dht_->putEncrypted(h, dev, dht::ImMessage(token, std::string(payloads.begin()->first), std::string(payloads.begin()->second), now), [this,to,token,confirm,h](bool ok) { JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token << "] Put encrypted " << (ok ? "ok" : "failed"); @@ -3546,7 +2432,7 @@ JamiAccount::sendTextMessage(const std::string& to, const std::map<std::string, std::unique_lock<std::mutex> l(confirm->lock); auto lt = confirm->listenTokens.find(h); if (lt != confirm->listenTokens.end()) { - dht_.cancelListen(h, std::move(lt->second)); + dht_->cancelListen(h, std::move(lt->second)); confirm->listenTokens.erase(lt); } if (confirm->listenTokens.empty() and not confirm->replied) { @@ -3570,7 +2456,7 @@ JamiAccount::sendTextMessage(const std::string& to, const std::map<std::string, if (auto this_ = w.lock()) { JAMI_DBG() << "[Account " << this_->getAccountID() << "] [message " << token << "] Timeout"; for (auto& t : confirm->listenTokens) - this_->dht_.cancelListen(t.first, std::move(t.second)); + this_->dht_->cancelListen(t.first, std::move(t.second)); confirm->listenTokens.clear(); confirm->replied = true; l.unlock(); @@ -3596,12 +2482,12 @@ JamiAccount::registerDhtAddress(IceTransport& ice) // Trying to use one discovered by DHT service // IPv6 (sdp support only one IP, put IPv6 before IPv4 as this last has the priority over IPv6 less NAT'able) - const auto& addr6 = dht_.getPublicAddress(AF_INET6); + const auto& addr6 = dht_->getPublicAddress(AF_INET6); if (addr6.size()) setPublishedAddress(reg_addr(ice, *addr6[0].get())); // IPv4 - const auto& addr4 = dht_.getPublicAddress(AF_INET); + const auto& addr4 = dht_->getPublicAddress(AF_INET); if (addr4.size()) setPublishedAddress(reg_addr(ice, *addr4[0].get())); } else { @@ -3613,10 +2499,10 @@ std::vector<std::string> JamiAccount::publicAddresses() { std::vector<std::string> addresses; - for (auto& addr : dht_.getPublicAddress(AF_INET)) { + for (auto& addr : dht_->getPublicAddress(AF_INET)) { addresses.emplace_back(addr.toString()); } - for (auto& addr : dht_.getPublicAddress(AF_INET6)) { + for (auto& addr : dht_->getPublicAddress(AF_INET6)) { addresses.emplace_back(addr.toString()); } return addresses; @@ -3639,14 +2525,14 @@ void JamiAccount::enableProxyClient(bool enable) { JAMI_WARN("[Account %s] DHT proxy client: %s", getAccountID().c_str(), enable ? "enable" : "disable"); - dht_.enableProxy(enable); + dht_->enableProxy(enable); } void JamiAccount::setPushNotificationToken(const std::string& token) { JAMI_WARN("[Account %s] setPushNotificationToken: %s", getAccountID().c_str(), token.c_str()); deviceKey_ = token; - dht_.setPushNotificationToken(deviceKey_); + dht_->setPushNotificationToken(deviceKey_); } /** @@ -3655,7 +2541,7 @@ void JamiAccount::setPushNotificationToken(const std::string& token) void JamiAccount::pushNotificationReceived(const std::string& from, const std::map<std::string, std::string>& data) { JAMI_WARN("[Account %s] pushNotificationReceived: %s", getAccountID().c_str(), from.c_str()); - dht_.pushNotificationReceived(data); + dht_->pushNotificationReceived(data); } std::string @@ -3694,7 +2580,7 @@ void JamiAccount::startAccountPublish() { AccountPeerInfo info_pub; - info_pub.accountId = dht::InfoHash(ringAccountId_); + info_pub.accountId = dht::InfoHash(accountManager_->getInfo()->accountId); info_pub.displayName = displayName_; peerDiscovery_->startPublish<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE, info_pub); } @@ -3702,11 +2588,12 @@ JamiAccount::startAccountPublish() void JamiAccount::startAccountDiscovery() { - peerDiscovery_->startDiscovery<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE,[this](AccountPeerInfo&& v, dht::SockAddr&& add){ + auto id = dht::InfoHash(accountManager_->getInfo()->accountId); + peerDiscovery_->startDiscovery<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE,[this,id](AccountPeerInfo&& v, dht::SockAddr&& add){ std::lock_guard<std::mutex> lc(discoveryMapMtx_); //Make sure that account itself will not be recorded - if(v.accountId != dht::InfoHash(ringAccountId_)){ + if(v.accountId != id){ //Create or Find the old one auto& dp = discoveredPeers_[v.accountId]; dp.displayName = v.displayName; diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h index cc30163557bb02910746b0b1d13a32750bd77344..6c1f7868a779160c387600e0e63e4d24c4878655 100644 --- a/src/jamidht/jamiaccount.h +++ b/src/jamidht/jamiaccount.h @@ -68,646 +68,595 @@ namespace jami { class IceTransport; struct Contact; +struct DeviceSync; struct AccountArchive; class DhtPeerConnector; class PeerConnection; +class ContactList; +class AccountManager; +struct AccountInfo; /** * @brief Ring Account is build on top of SIPAccountBase and uses DHT to handle call connectivity. */ class JamiAccount : public SIPAccountBase { - private: - struct PeerConnectionMsg; - - public: - constexpr static const char* const ACCOUNT_TYPE = "RING"; - constexpr static const char* const DHT_DEFAULT_BOOTSTRAP = "bootstrap.jami.net"; - constexpr static const char* const DHT_DEFAULT_PROXY = "dhtproxy.jami.net:[80-100]"; - constexpr static const char* const DHT_TYPE_NS = "cx.ring"; - - /* constexpr */ static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE; - - const char* getAccountType() const override { - return ACCOUNT_TYPE; - } - - std::shared_ptr<JamiAccount> shared() { - return std::static_pointer_cast<JamiAccount>(shared_from_this()); - } - std::shared_ptr<JamiAccount const> shared() const { - return std::static_pointer_cast<JamiAccount const>(shared_from_this()); - } - std::weak_ptr<JamiAccount> weak() { - return std::static_pointer_cast<JamiAccount>(shared_from_this()); - } - std::weak_ptr<JamiAccount const> weak() const { - return std::static_pointer_cast<JamiAccount const>(shared_from_this()); - } - - const std::string& getPath() const { - return idPath_; - } - - /** - * Constructor - * @param accountID The account identifier - */ - JamiAccount(const std::string& accountID, bool presenceEnabled); - - ~JamiAccount(); - - /** - * Serialize internal state of this account for configuration - * @param YamlEmitter the configuration engine which generate the configuration file - */ - virtual void serialize(YAML::Emitter &out) const override; - - /** - * Populate the internal state for this account based on info stored in the configuration file - * @param The configuration node for this account - */ - virtual void unserialize(const YAML::Node &node) override; - - /** - * Return an map containing the internal state of this account. Client application can use this method to manage - * account info. - * @return A map containing the account information. - */ - virtual std::map<std::string, std::string> getAccountDetails() const override; - - /** - * Retrieve volatile details such as recent registration errors - * @return std::map< std::string, std::string > The account volatile details - */ - virtual std::map<std::string, std::string> getVolatileAccountDetails() const override; - - /** - * Actually useless, since config loading is done in init() - */ - void loadConfig() override {} - - /** - * Adds an account id to the list of accounts to track on the DHT for - * buddy presence. - * - * @param buddy_id The buddy id. - */ - void trackBuddyPresence(const std::string& buddy_id, bool track); - - /** - * Tells for each tracked account id if it has been seen online so far - * in the last DeviceAnnouncement::TYPE.expiration minutes. - * - * @return map of buddy_uri to bool (online or not) - */ - std::map<std::string, bool> getTrackedBuddyPresence() const; - - void setActiveCodecs(const std::vector<unsigned>& list) override; - - /** - * Connect to the DHT. - */ - void doRegister() override; - - /** - * Disconnect from the DHT. - */ - void doUnregister(std::function<void(bool)> cb = {}) override; - - /** - * @return pj_str_t "From" uri based on account information. - * From RFC3261: "The To header field first and foremost specifies the desired - * logical" recipient of the request, or the address-of-record of the - * user or resource that is the target of this request. [...] As such, it is - * very important that the From URI not contain IP addresses or the FQDN - * of the host on which the UA is running, since these are not logical - * names." - */ - std::string getFromUri() const; - - /** - * This method adds the correct scheme, hostname and append - * the ;transport= parameter at the end of the uri, in accordance with RFC3261. - * It is expected that "port" is present in the internal hostname_. - * - * @return pj_str_t "To" uri based on @param username - * @param username A string formatted as : "username" - */ - std::string getToUri(const std::string& username) const override; - - /** - * In the current version, "srv" uri is obtained in the preformated - * way: hostname:port. This method adds the correct scheme and append - * the ;transport= parameter at the end of the uri, in accordance with RFC3261. - * - * @return pj_str_t "server" uri based on @param hostPort - * @param hostPort A string formatted as : "hostname:port" - */ - std::string getServerUri() const { return ""; }; - - /** - * Get the contact header for - * @return pj_str_t The contact header based on account information - */ - pj_str_t getContactHeader(pjsip_transport* = nullptr) override; - - void setReceivedParameter(const std::string &received) { - receivedParameter_ = received; - via_addr_.host.ptr = (char *) receivedParameter_.c_str(); - via_addr_.host.slen = receivedParameter_.size(); - } - - std::string getReceivedParameter() const { - return receivedParameter_; - } - - pjsip_host_port * - getViaAddr() { - return &via_addr_; - } - - /* Returns true if the username and/or hostname match this account */ - MatchRank matches(const std::string &username, const std::string &hostname) const override; - - /** - * Implementation of Account::newOutgoingCall() - * Note: keep declaration before newOutgoingCall template. - */ - std::shared_ptr<Call> newOutgoingCall(const std::string& toUrl, - const std::map<std::string, std::string>& volatileCallDetails = {}) override; - - /** - * Create outgoing SIPCall. - * @param[in] toUrl The address to call - * @return std::shared_ptr<T> A shared pointer on the created call. - * The type of this instance is given in template argument. - * This type can be any base class of SIPCall class (included). - */ +public: + constexpr static const char* const ACCOUNT_TYPE = "RING"; + constexpr static const in_port_t DHT_DEFAULT_PORT = 4222; + constexpr static const char* const DHT_DEFAULT_BOOTSTRAP = "bootstrap.jami.net"; + constexpr static const char* const DHT_DEFAULT_PROXY = "dhtproxy.jami.net:[80-100]"; + constexpr static const char* const DHT_TYPE_NS = "cx.ring"; + + /* constexpr */ static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE; + + const char* getAccountType() const override { + return ACCOUNT_TYPE; + } + + std::shared_ptr<JamiAccount> shared() { + return std::static_pointer_cast<JamiAccount>(shared_from_this()); + } + std::shared_ptr<JamiAccount const> shared() const { + return std::static_pointer_cast<JamiAccount const>(shared_from_this()); + } + std::weak_ptr<JamiAccount> weak() { + return std::static_pointer_cast<JamiAccount>(shared_from_this()); + } + std::weak_ptr<JamiAccount const> weak() const { + return std::static_pointer_cast<JamiAccount const>(shared_from_this()); + } + + const std::string& getPath() const { + return idPath_; + } + + /** + * Constructor + * @param accountID The account identifier + */ + JamiAccount(const std::string& accountID, bool presenceEnabled); + + ~JamiAccount(); + + /** + * Serialize internal state of this account for configuration + * @param YamlEmitter the configuration engine which generate the configuration file + */ + virtual void serialize(YAML::Emitter &out) const override; + + /** + * Populate the internal state for this account based on info stored in the configuration file + * @param The configuration node for this account + */ + virtual void unserialize(const YAML::Node &node) override; + + /** + * Return an map containing the internal state of this account. Client application can use this method to manage + * account info. + * @return A map containing the account information. + */ + virtual std::map<std::string, std::string> getAccountDetails() const override; + + /** + * Retrieve volatile details such as recent registration errors + * @return std::map< std::string, std::string > The account volatile details + */ + virtual std::map<std::string, std::string> getVolatileAccountDetails() const override; + + /** + * Actually useless, since config loading is done in init() + */ + void loadConfig() override {} + + /** + * Adds an account id to the list of accounts to track on the DHT for + * buddy presence. + * + * @param buddy_id The buddy id. + */ + void trackBuddyPresence(const std::string& buddy_id, bool track); + + /** + * Tells for each tracked account id if it has been seen online so far + * in the last DeviceAnnouncement::TYPE.expiration minutes. + * + * @return map of buddy_uri to bool (online or not) + */ + std::map<std::string, bool> getTrackedBuddyPresence() const; + + void setActiveCodecs(const std::vector<unsigned>& list) override; + + /** + * Connect to the DHT. + */ + void doRegister() override; + + /** + * Disconnect from the DHT. + */ + void doUnregister(std::function<void(bool)> cb = {}) override; + + /** + * @return pj_str_t "From" uri based on account information. + * From RFC3261: "The To header field first and foremost specifies the desired + * logical" recipient of the request, or the address-of-record of the + * user or resource that is the target of this request. [...] As such, it is + * very important that the From URI not contain IP addresses or the FQDN + * of the host on which the UA is running, since these are not logical + * names." + */ + std::string getFromUri() const; + + /** + * This method adds the correct scheme, hostname and append + * the ;transport= parameter at the end of the uri, in accordance with RFC3261. + * It is expected that "port" is present in the internal hostname_. + * + * @return pj_str_t "To" uri based on @param username + * @param username A string formatted as : "username" + */ + std::string getToUri(const std::string& username) const override; + + /** + * In the current version, "srv" uri is obtained in the preformated + * way: hostname:port. This method adds the correct scheme and append + * the ;transport= parameter at the end of the uri, in accordance with RFC3261. + * + * @return pj_str_t "server" uri based on @param hostPort + * @param hostPort A string formatted as : "hostname:port" + */ + std::string getServerUri() const { return ""; }; + + /** + * Get the contact header for + * @return pj_str_t The contact header based on account information + */ + pj_str_t getContactHeader(pjsip_transport* = nullptr) override; + + void setReceivedParameter(const std::string &received) { + receivedParameter_ = received; + via_addr_.host.ptr = (char *) receivedParameter_.c_str(); + via_addr_.host.slen = receivedParameter_.size(); + } + + std::string getReceivedParameter() const { + return receivedParameter_; + } + + pjsip_host_port * + getViaAddr() { + return &via_addr_; + } + + /* Returns true if the username and/or hostname match this account */ + MatchRank matches(const std::string &username, const std::string &hostname) const override; + + /** + * Implementation of Account::newOutgoingCall() + * Note: keep declaration before newOutgoingCall template. + */ + std::shared_ptr<Call> newOutgoingCall(const std::string& toUrl, + const std::map<std::string, std::string>& volatileCallDetails = {}) override; + + /** + * Create outgoing SIPCall. + * @param[in] toUrl The address to call + * @return std::shared_ptr<T> A shared pointer on the created call. + * The type of this instance is given in template argument. + * This type can be any base class of SIPCall class (included). + */ #ifndef _MSC_VER - template <class T=SIPCall> - std::shared_ptr<enable_if_base_of<T, SIPCall> > - newOutgoingCall(const std::string& toUrl, const std::map<std::string, std::string>& volatileCallDetails = {}); + template <class T=SIPCall> + std::shared_ptr<enable_if_base_of<T, SIPCall> > + newOutgoingCall(const std::string& toUrl, const std::map<std::string, std::string>& volatileCallDetails = {}); #else - template <class T> - std::shared_ptr<T> - newOutgoingCall(const std::string& toUrl, const std::map<std::string, std::string>& volatileCallDetails = {}); + template <class T> + std::shared_ptr<T> + newOutgoingCall(const std::string& toUrl, const std::map<std::string, std::string>& volatileCallDetails = {}); #endif - /** - * Create incoming SIPCall. - * @param[in] from The origin of the call - * @param details use to set some specific details - * @return std::shared_ptr<T> A shared pointer on the created call. - * The type of this instance is given in template argument. - * This type can be any base class of SIPCall class (included). - */ - virtual std::shared_ptr<SIPCall> - newIncomingCall(const std::string& from, const std::map<std::string, std::string>& details = {}) override; - - virtual bool isTlsEnabled() const override { - return true; - } - - virtual bool isSrtpEnabled() const { - return true; - } - - virtual sip_utils::KeyExchangeProtocol getSrtpKeyExchange() const override { - return sip_utils::KeyExchangeProtocol::SDES; - } - - virtual bool getSrtpFallback() const override { - return false; - } - - bool setCertificateStatus(const std::string& cert_id, tls::TrustStore::PermissionStatus status); - - std::vector<std::string> getCertificatesByStatus(tls::TrustStore::PermissionStatus status); - - bool findCertificate(const std::string& id); - bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {}); - - /* contact requests */ - std::vector<std::map<std::string, std::string>> getTrustRequests() const; - bool acceptTrustRequest(const std::string& from); - bool discardTrustRequest(const std::string& from); - - /** - * Add contact to the account contact list. - * Set confirmed if we know the contact also added us. - */ - void addContact(const std::string& uri, bool confirmed = false); - void removeContact(const std::string& uri, bool banned = true); - std::vector<std::map<std::string, std::string>> getContacts() const; - - /// - /// Obtain details about one account contact in serializable form. - /// - std::map<std::string, std::string> getContactDetails(const std::string& uri) const; - - void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload); - void sendTrustRequestConfirm(const dht::InfoHash& to); - virtual void sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads, uint64_t id) override; - virtual uint64_t sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads) override; - - /* Devices */ - void addDevice(const std::string& password); - /** - * Export the archive to a file - * @param destinationPath - * @param (optional) password, if not provided, will update the contacts only if the archive doesn't have a password - * @return if the archive was exported - */ - bool exportArchive(const std::string& destinationPath, const std::string& password = {}); - bool revokeDevice(const std::string& password, const std::string& device); - std::map<std::string, std::string> getKnownDevices() const; - - bool changeArchivePassword(const std::string& password_old, const std::string& password_new); - - void connectivityChanged() override; - - // overloaded methods - void flush() override; + /** + * Create incoming SIPCall. + * @param[in] from The origin of the call + * @param details use to set some specific details + * @return std::shared_ptr<T> A shared pointer on the created call. + * The type of this instance is given in template argument. + * This type can be any base class of SIPCall class (included). + */ + virtual std::shared_ptr<SIPCall> + newIncomingCall(const std::string& from, const std::map<std::string, std::string>& details = {}) override; + + virtual bool isTlsEnabled() const override { + return true; + } + + virtual bool isSrtpEnabled() const { + return true; + } + + virtual sip_utils::KeyExchangeProtocol getSrtpKeyExchange() const override { + return sip_utils::KeyExchangeProtocol::SDES; + } + + virtual bool getSrtpFallback() const override { + return false; + } + + bool setCertificateStatus(const std::string& cert_id, tls::TrustStore::PermissionStatus status); + std::vector<std::string> getCertificatesByStatus(tls::TrustStore::PermissionStatus status); + + bool findCertificate(const std::string& id); + bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {}); + + /* contact requests */ + std::vector<std::map<std::string, std::string>> getTrustRequests() const; + bool acceptTrustRequest(const std::string& from); + bool discardTrustRequest(const std::string& from); + + /** + * Add contact to the account contact list. + * Set confirmed if we know the contact also added us. + */ + void addContact(const std::string& uri, bool confirmed = false); + void removeContact(const std::string& uri, bool banned = true); + std::vector<std::map<std::string, std::string>> getContacts() const; + + /// + /// Obtain details about one account contact in serializable form. + /// + std::map<std::string, std::string> getContactDetails(const std::string& uri) const; + + void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload); + void sendTrustRequestConfirm(const std::string& to); + virtual void sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads, uint64_t id) override; + virtual uint64_t sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads) override; + + /* Devices */ + void addDevice(const std::string& password); + /** + * Export the archive to a file + * @param destinationPath + * @param (optional) password, if not provided, will update the contacts only if the archive doesn't have a password + * @return if the archive was exported + */ + bool exportArchive(const std::string& destinationPath, const std::string& password = {}); + bool revokeDevice(const std::string& password, const std::string& device); + std::map<std::string, std::string> getKnownDevices() const; + + bool changeArchivePassword(const std::string& password_old, const std::string& password_new); + + void connectivityChanged() override; + + // overloaded methods + void flush() override; #if HAVE_RINGNS - void lookupName(const std::string& name); - void lookupAddress(const std::string& address); - void registerName(const std::string& password, const std::string& name); + void lookupName(const std::string& name); + void lookupAddress(const std::string& address); + void registerName(const std::string& password, const std::string& name); #endif - /// - /// Send a E2E connection request to a given peer for the given transfer id - /// - /// /// \param[in] peer RingID on request's recipient - /// /// \param[in] tid linked outgoing data transfer - /// - void requestPeerConnection(const std::string& peer, const DRing::DataTransferId& tid, - const std::function<void(PeerConnection*)>& connect_cb); - - /// - /// Close a E2E connection between a given peer and a given transfer id. - /// - /// /// \param[in] peer RingID on request's recipient - /// /// \param[in] tid linked outgoing data transfer - /// - void closePeerConnection(const std::string& peer, const DRing::DataTransferId& tid); - - std::vector<std::string> publicAddresses(); - - - /// \return true if the given DHT message identifier has been treated - /// \note if message has not been treated yet this method store this id and returns true at further calls - bool isMessageTreated(unsigned int id) ; - - dht::DhtRunner& dht() { return dht_; } - - const dht::crypto::Identity& identity() const { return identity_; } - - const std::shared_future<tls::DhParams> dhParams() const { return dhParams_; } - - void forEachDevice(const dht::InfoHash& to, - std::function<void(const dht::InfoHash&)>&& op, - std::function<void(bool)>&& end = {}); - - /** - * Inform that a potential peer device have been found. - * Returns true only if the device certificate is a valid device certificate. - * In that case (true is returned) the account_id parameter is set to the peer account ID. - */ - static bool foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, dht::InfoHash& account_id); - - /** - * Start or stop to use the proxy client - * @param address of the proxy - * @param deviceKey the device key for push notifications (empty to not use it) - */ - void enableProxyClient(bool enable); - - void setPushNotificationToken(const std::string& pushDeviceToken = ""); - - /** - * To be called by clients with relevant data when a push notification is received. - */ - void pushNotificationReceived(const std::string& from, const std::map<std::string, std::string>& data); - - std::string getUserUri() const override; - - /** - * Get last messages (should be used to retrieve messages when launching the client) - * @param base_timestamp - */ - std::vector<DRing::Message> getLastMessages(const uint64_t& base_timestamp) override; - - void saveConfig() const; - - /** - * Get current discovered peers account id and display name - */ - std::map<std::string, std::string> getNearbyPeers() const override; - - /** - * Add public addresses to ice transport - */ - void registerDhtAddress(IceTransport&); - - private: - NON_COPYABLE(JamiAccount); - - using clock = std::chrono::system_clock; - using time_point = clock::time_point; - - /** - * Private structures - */ - struct PendingCall; - struct PendingMessage; - struct TrustRequest; - struct KnownDevice; - struct DeviceAnnouncement; - struct DeviceSync; - struct BuddyInfo; - struct DiscoveredPeer; - - void syncDevices(); - void onReceiveDeviceSync(DeviceSync&& sync); + /// + /// Send a E2E connection request to a given peer for the given transfer id + /// + /// /// \param[in] peer RingID on request's recipient + /// /// \param[in] tid linked outgoing data transfer + /// + void requestPeerConnection(const std::string& peer, const DRing::DataTransferId& tid, + const std::function<void(PeerConnection*)>& connect_cb); + + /// + /// Close a E2E connection between a given peer and a given transfer id. + /// + /// /// \param[in] peer RingID on request's recipient + /// /// \param[in] tid linked outgoing data transfer + /// + void closePeerConnection(const std::string& peer, const DRing::DataTransferId& tid); + + std::vector<std::string> publicAddresses(); + + + /// \return true if the given DHT message identifier has been treated + /// \note if message has not been treated yet this method store this id and returns true at further calls + bool isMessageTreated(unsigned int id) ; + + std::shared_ptr<dht::DhtRunner> dht() { return dht_; } + + const dht::crypto::Identity& identity() const { return id_; } + + const std::shared_future<tls::DhParams> dhParams() const { return dhParams_; } + + void forEachDevice(const dht::InfoHash& to, + std::function<void(const dht::InfoHash&)>&& op, + std::function<void(bool)>&& end = {}); + + /** + * Inform that a potential peer device have been found. + * Returns true only if the device certificate is a valid device certificate. + * In that case (true is returned) the account_id parameter is set to the peer account ID. + */ + //static bool foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, dht::InfoHash& account_id); + + /** + * Start or stop to use the proxy client + * @param address of the proxy + * @param deviceKey the device key for push notifications (empty to not use it) + */ + void enableProxyClient(bool enable); + + void setPushNotificationToken(const std::string& pushDeviceToken = ""); + + /** + * To be called by clients with relevant data when a push notification is received. + */ + void pushNotificationReceived(const std::string& from, const std::map<std::string, std::string>& data); + + std::string getUserUri() const override; + + /** + * Get last messages (should be used to retrieve messages when launching the client) + * @param base_timestamp + */ + std::vector<DRing::Message> getLastMessages(const uint64_t& base_timestamp) override; + + /** + * Start Publish the Jami Account onto the Network + */ + void startAccountPublish(); + + /** + * Start Discovery the Jami Account from the Network + */ + void startAccountDiscovery(); + + void saveConfig() const; + + /** + * Get current discovered peers account id and display name + */ + std::map<std::string, std::string> getNearbyPeers() const override; + + /** + * Add public addresses to ice transport + */ + void registerDhtAddress(IceTransport&); + +private: + NON_COPYABLE(JamiAccount); + + using clock = std::chrono::system_clock; + using time_point = clock::time_point; + + /** + * Private structures + */ + struct PendingCall; + struct PendingMessage; + struct BuddyInfo; + struct DiscoveredPeer; + + /** + * Compute archive encryption key and DHT storage location from password and PIN. + */ + static std::pair<std::vector<uint8_t>, dht::InfoHash> computeKeys(const std::string& password, const std::string& pin, bool previous=false); + + void trackPresence(const dht::InfoHash& h, BuddyInfo& buddy); + + void doRegister_(); + void incomingCall(dht::IceCandidates&& msg, const std::shared_ptr<dht::crypto::Certificate>& from_cert, const dht::InfoHash& from); + + const dht::ValueType USER_PROFILE_TYPE = {9, "User profile", std::chrono::hours(24 * 7)}; + + void startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri); + + void onConnectedOutgoingCall(SIPCall& call, const std::string& to_id, IpAddr target); + + /** + * Set the internal state for this account, mainly used to manage account details from the client application. + * @param The map containing the account information. + */ + virtual void setAccountDetails(const std::map<std::string, std::string> &details) override; + + /** + * Start a SIP Call + * @param call The current call + * @return true if all is correct + */ + bool SIPStartCall(SIPCall& call, IpAddr target); + + /** + * For a call with (from_device, from_account), check the peer certificate chain (cert_list, cert_num) + * with session check status. + * Put deserialized certificate to cert_out; + */ + static pj_status_t checkPeerTlsCertificate(dht::InfoHash from_device, dht::InfoHash from_account, + unsigned status, + const gnutls_datum_t* cert_list, unsigned cert_num, + std::shared_ptr<dht::crypto::Certificate>& cert_out); + + /** + * Check that a peer is authorised to talk to us. + * If everything is in order, calls the callback with the + * peer certificate chain (down to the peer device certificate), + * and the peer account id. + */ + void onPeerMessage(const dht::InfoHash& peer_device, std::function<void(const std::shared_ptr<dht::crypto::Certificate>& crt, const dht::InfoHash& account_id)>&&); + + /** + * Update tracking info when buddy appears offline. + */ + void onTrackedBuddyOffline(const dht::InfoHash&); + + /** + * Update tracking info when buddy appears offline. + */ + void onTrackedBuddyOnline(const dht::InfoHash&); + + /** + * Maps require port via UPnP + */ + bool mapPortUPnP(); + + void igdChanged(); + + bool handlePendingCallList(); + bool handlePendingCall(PendingCall& pc, bool incoming); + + void loadAccount(const std::string& archive_password = {}, const std::string& archive_pin = {}, const std::string& archive_path = {}); + void loadAccountFromFile(const std::string& archive_path, const std::string& archive_password); + void loadAccountFromDHT(const std::string& archive_password, const std::string& archive_pin); + void loadAccountFromArchive(AccountArchive&& archive, const std::string& archive_password); + + bool hasCertificate() const; + bool hasPrivateKey() const; + + std::string makeReceipt(const dht::crypto::Identity& id); + void createRingDevice(const dht::crypto::Identity& id); + void initRingDevice(const AccountArchive& a); + void migrateAccount(const std::string& pwd, dht::crypto::Identity& device); + static bool updateCertificates(AccountArchive& archive, dht::crypto::Identity& device); + + void createAccount(const std::string& archive_password, dht::crypto::Identity&& migrate); + void updateArchive(AccountArchive& content) const; + void saveArchive(AccountArchive& content, const std::string& pwd); + AccountArchive readArchive(const std::string& pwd) const; + std::vector<std::string> loadBootstrap() const; + + static std::pair<std::string, std::string> saveIdentity(const dht::crypto::Identity id, const std::string& path, const std::string& name); + + void loadTreatedCalls(); + void saveTreatedCalls() const; + + void loadTreatedMessages(); + void saveTreatedMessages() const; + + void replyToIncomingIceMsg(const std::shared_ptr<SIPCall>&, + const std::shared_ptr<IceTransport>&, + const std::shared_ptr<IceTransport>&, + const dht::IceCandidates&, + const std::shared_ptr<dht::crypto::Certificate>& from_cert, + const dht::InfoHash& from); + + static tls::DhParams loadDhParams(std::string path); + + std::string getDhtProxyServer(); + + /** + * The TLS settings, used only if tls is chosen as a sip transport. + */ + void generateDhParams(); + + template <class... Args> + std::shared_ptr<IceTransport> createIceTransport(const Args&... args); + + void checkPendingCallsTask(); #if HAVE_RINGNS - std::reference_wrapper<NameDirectory> nameDir_; - std::string nameServer_; - std::string registeredName_; + std::string nameServer_; + std::string registeredName_; #endif std::shared_ptr<dht::Logger> logger_; - /** - * Compute archive encryption key and DHT storage location from password and PIN. - */ - static std::pair<std::vector<uint8_t>, dht::InfoHash> computeKeys(const std::string& password, const std::string& pin, bool previous=false); - - void trackPresence(const dht::InfoHash& h, BuddyInfo& buddy); - - /** - * Update tracking info when buddy appears offline. - */ - void onTrackedBuddyOffline(const dht::InfoHash&); - - /** - * Update tracking info when buddy appears offline. - */ - void onTrackedBuddyOnline(const dht::InfoHash&); - - void doRegister_(); - void incomingCall(dht::IceCandidates&& msg, const std::shared_ptr<dht::crypto::Certificate>& from_cert, const dht::InfoHash& from); - - const dht::ValueType USER_PROFILE_TYPE = {9, "User profile", std::chrono::hours(24 * 7)}; - - void startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri); - - void onConnectedOutgoingCall(SIPCall& call, const std::string& to_id, IpAddr target); - - /** - * Set the internal state for this account, mainly used to manage account details from the client application. - * @param The map containing the account information. - */ - virtual void setAccountDetails(const std::map<std::string, std::string> &details) override; - - /** - * Start a SIP Call - * @param call The current call - * @return true if all is correct - */ - bool SIPStartCall(SIPCall& call, IpAddr target); - - /** - * Inform that a potential account device have been found. - * Returns true if the device have been validated to be part of this account - */ - bool foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, const std::string& name = {}, const time_point& last_sync = time_point::min()); - - /** - * For a call with (from_device, from_account), check the peer certificate chain (cert_list, cert_num) - * with session check status. - * Put deserialized certificate to cert_out; - */ - static pj_status_t checkPeerTlsCertificate(dht::InfoHash from_device, dht::InfoHash from_account, - unsigned status, - const gnutls_datum_t* cert_list, unsigned cert_num, - std::shared_ptr<dht::crypto::Certificate>& cert_out); - - /** - * Check that a peer is authorised to talk to us. - * If everything is in order, calls the callback with the - * peer certificate chain (down to the peer device certificate), - * and the peer account id. - */ - void onPeerMessage(const dht::InfoHash& peer_device, std::function<void(const std::shared_ptr<dht::crypto::Certificate>& crt, const dht::InfoHash& account_id)>&&); - - void onTrustRequest(const dht::InfoHash& peer_account, const dht::InfoHash& peer_device, time_t received , bool confirm, std::vector<uint8_t>&& payload); - - /** - * Start Publish the Jami Account onto the Network - */ - void startAccountPublish(); - - /** - * Start Discovery the Jami Account from the Network - */ - void startAccountDiscovery(); - - /** - * Maps require port via UPnP - */ - bool mapPortUPnP(); - - void igdChanged(); - - dht::DhtRunner dht_ {}; - dht::crypto::Identity identity_ {}; - - dht::InfoHash callKey_; - - bool handlePendingCallList(); - bool handlePendingCall(PendingCall& pc, bool incoming); - - mutable std::mutex callsMutex_ {}; - - /** - * DHT calls waiting for ICE negotiation - */ - std::list<PendingCall> pendingCalls_; - - /** - * Incoming DHT calls that are not yet actual SIP calls. - */ - std::list<PendingCall> pendingSipCalls_; - std::set<dht::Value::Id> treatedCalls_ {}; - - mutable std::mutex messageMutex_ {}; - std::map<dht::Value::Id, PendingMessage> sentMessages_; - std::set<dht::Value::Id> treatedMessages_ {}; - - std::string ringAccountId_ {}; - std::string ringDeviceId_ {}; - std::string ringDeviceName_ {}; - std::string idPath_ {}; - std::string cachePath_ {}; - std::string dataPath_ {}; - std::string ethPath_ {}; - std::string ethAccount_ {}; - - std::string archivePath_ {}; - bool archiveHasPassword_ {true}; - - std::string receipt_ {}; - std::vector<uint8_t> receiptSignature_ {}; - dht::Value announceVal_; - - std::map<dht::InfoHash, TrustRequest> trustRequests_; - void loadTrustRequests(); - void saveTrustRequests() const; - - std::map<dht::InfoHash, Contact> contacts_; - void loadContacts(); - void saveContacts() const; - void updateContact(const dht::InfoHash&, const Contact&); - void addContact(const dht::InfoHash&, bool confirmed = false); - - // Trust store with account main certificate as the only CA - dht::crypto::TrustList accountTrust_; - // Trust store for to match peer certificates - tls::TrustStore trust_; - - std::shared_ptr<dht::Value> announce_; - - /* this ring account associated devices */ - mutable std::mutex deviceListMutex_ {}; - std::map<dht::InfoHash, KnownDevice> knownDevices_; - - /* tracked buddies presence */ - mutable std::mutex buddyInfoMtx; - std::map<dht::InfoHash, BuddyInfo> trackedBuddies_; - - void loadAccount(const std::string& archive_password = {}, const std::string& archive_pin = {}, const std::string& archive_path = {}); - void loadAccountFromFile(const std::string& archive_path, const std::string& archive_password); - void loadAccountFromDHT(const std::string& archive_password, const std::string& archive_pin); - void loadAccountFromArchive(AccountArchive&& archive, const std::string& archive_password); - - bool hasCertificate() const; - bool hasPrivateKey() const; - bool useIdentity(const dht::crypto::Identity& id); - static bool needsMigration(const dht::crypto::Identity& id); - - std::string makeReceipt(const dht::crypto::Identity& id); - void createRingDevice(const dht::crypto::Identity& id); - void initRingDevice(const AccountArchive& a); - void migrateAccount(const std::string& pwd, dht::crypto::Identity& device); - static bool updateCertificates(AccountArchive& archive, dht::crypto::Identity& device); - - void createAccount(const std::string& archive_password, dht::crypto::Identity&& migrate); - void updateArchive(AccountArchive& content) const; - void saveArchive(AccountArchive& content, const std::string& pwd); - AccountArchive readArchive(const std::string& pwd) const; - std::vector<std::string> loadBootstrap() const; - - static std::pair<std::string, std::string> saveIdentity(const dht::crypto::Identity id, const std::string& path, const std::string& name); - - void loadTreatedCalls(); - void saveTreatedCalls() const; - - void loadTreatedMessages(); - void saveTreatedMessages() const; - - void loadKnownDevices(); - void saveKnownDevices() const; - - void replyToIncomingIceMsg(const std::shared_ptr<SIPCall>&, - const std::shared_ptr<IceTransport>&, - const std::shared_ptr<IceTransport>&, - const dht::IceCandidates&, - const std::shared_ptr<dht::crypto::Certificate>& from_cert, - const dht::InfoHash& from); - - static tls::DhParams loadDhParams(std::string path); - - /** - * If privkeyPath_ is a valid private key file (PEM or DER), - * and certPath_ a valid certificate file, load and returns them. - * Otherwise, generate a new identity and returns it. - */ - dht::crypto::Identity loadIdentity(const std::string& crt_path, const std::string& key_path, const std::string& key_pwd) const; - - bool dhtPublicInCalls_ {true}; - - /** - * DHT port preference - */ - in_port_t dhtPort_ {}; - - bool dhtPeerDiscovery_ {false}; - - /** - * DHT port actually used, - * this holds the actual port used for DHT, which may not be the port - * selected in the configuration in the case that UPnP is used and the - * configured port is already used by another client - */ - UsedPort dhtPortUsed_ {}; - - /** - * Proxy - */ - bool proxyEnabled_; - std::string proxyServer_; - std::string proxyServerCached_; - std::string deviceKey_; - std::string getDhtProxyServer(); - - /** - * The TLS settings, used only if tls is chosen as a sip transport. - */ - void generateDhParams(); - - std::shared_future<tls::DhParams> dhParams_; - std::mutex dhParamsMtx_; - std::condition_variable dhParamsCv_; - - bool allowPeersFromHistory_ {true}; - bool allowPeersFromContact_ {true}; - bool allowPeersFromTrusted_ {true}; - - /** - * Optional: "received" parameter from VIA header - */ - std::string receivedParameter_ {}; - - /** - * Optional: "rport" parameter from VIA header - */ - int rPort_ {-1}; - - /** - * Optional: via_addr construct from received parameters - */ - pjsip_host_port via_addr_ {}; - - char contactBuffer_[PJSIP_MAX_URL_SIZE] {}; - pj_str_t contact_ {contactBuffer_, 0}; - pjsip_transport* via_tp_ {nullptr}; - - template <class... Args> - std::shared_ptr<IceTransport> createIceTransport(const Args&... args); - - - std::unique_ptr<DhtPeerConnector> dhtPeerConnector_; - - std::mutex discoveryMapMtx_; - std::shared_ptr<dht::PeerDiscovery> peerDiscovery_; - std::map<dht::InfoHash, DiscoveredPeer> discoveredPeers_; - std::map<std::string, std::string> discoveredPeerMap_; - bool accountPeerDiscovery_ {false}; - bool accountPublish_ {false}; - - std::shared_ptr<RepeatedTask> eventHandler {}; - void checkPendingCallsTask(); + std::shared_ptr<dht::DhtRunner> dht_ {}; + std::unique_ptr<AccountManager> accountManager_; + dht::crypto::Identity id_ {}; + + dht::InfoHash callKey_; + + /** + * DHT calls waiting for ICE negotiation + */ + std::list<PendingCall> pendingCalls_; + + /** + * Incoming DHT calls that are not yet actual SIP calls. + */ + std::list<PendingCall> pendingSipCalls_; + std::set<dht::Value::Id> treatedCalls_ {}; + mutable std::mutex callsMutex_ {}; + + mutable std::mutex messageMutex_ {}; + std::map<dht::Value::Id, PendingMessage> sentMessages_; + std::set<dht::Value::Id> treatedMessages_ {}; + + std::string ringDeviceName_ {}; + std::string idPath_ {}; + std::string cachePath_ {}; + std::string dataPath_ {}; + + std::string archivePath_ {}; + bool archiveHasPassword_ {true}; + + std::string receipt_ {}; + std::vector<uint8_t> receiptSignature_ {}; + + /* tracked buddies presence */ + mutable std::mutex buddyInfoMtx; + std::map<dht::InfoHash, BuddyInfo> trackedBuddies_; + + mutable std::mutex dhtValuesMtx_; + bool dhtPublicInCalls_ {true}; + + /** + * DHT port preference + */ + in_port_t dhtPort_ {}; + + bool dhtPeerDiscovery_ {false}; + + /** + * DHT port actually used, + * this holds the actual port used for DHT, which may not be the port + * selected in the configuration in the case that UPnP is used and the + * configured port is already used by another client + */ + UsedPort dhtPortUsed_ {}; + + /** + * Proxy + */ + bool proxyEnabled_ {false}; + std::string proxyServer_ {}; + std::string proxyServerCached_ {}; + std::string deviceKey_ {}; + + std::mutex dhParamsMtx_ {}; + std::shared_future<tls::DhParams> dhParams_; + std::condition_variable dhParamsCv_; + + bool allowPeersFromHistory_ {true}; + bool allowPeersFromContact_ {true}; + bool allowPeersFromTrusted_ {true}; + + /** + * Optional: "received" parameter from VIA header + */ + std::string receivedParameter_ {}; + + /** + * Optional: "rport" parameter from VIA header + */ + int rPort_ {-1}; + + /** + * Optional: via_addr construct from received parameters + */ + pjsip_host_port via_addr_ {}; + + char contactBuffer_[PJSIP_MAX_URL_SIZE] {}; + pj_str_t contact_ {contactBuffer_, 0}; + pjsip_transport* via_tp_ {nullptr}; + + std::unique_ptr<DhtPeerConnector> dhtPeerConnector_; + + std::mutex discoveryMapMtx_; + std::shared_ptr<dht::PeerDiscovery> peerDiscovery_; + std::map<dht::InfoHash, DiscoveredPeer> discoveredPeers_; + std::map<std::string, std::string> discoveredPeerMap_; + bool accountPeerDiscovery_ {false}; + bool accountPublish_ {false}; + + std::shared_ptr<RepeatedTask> eventHandler {}; }; static inline std::ostream& operator<< (std::ostream& os, const JamiAccount& acc) diff --git a/src/jamidht/namedirectory.h b/src/jamidht/namedirectory.h index 4f62ef93d633b6edf6e545ae7b63d916265c7b27..8e30b2171d384c53d6adfa3bb86b6857094a865c 100644 --- a/src/jamidht/namedirectory.h +++ b/src/jamidht/namedirectory.h @@ -51,6 +51,7 @@ public: enum class Response : int { found = 0, invalidResponse, notFound, error }; enum class RegistrationResponse : int { success = 0, invalidName, + invalidCredentials, alreadyTaken, error, incompleteRequest, diff --git a/src/jamidht/p2p.cpp b/src/jamidht/p2p.cpp index ef90528acc899f8320eeaca1354df3887f82f51d..4bfb52357078afb1d31ba9f5b402b065e1cfaf39 100644 --- a/src/jamidht/p2p.cpp +++ b/src/jamidht/p2p.cpp @@ -30,6 +30,7 @@ #include "peer_connection.h" #include "security/tls_session.h" #include "turn_transport.h" +#include "account_manager.h" #include <opendht/default_types.h> #include <opendht/rng.h> @@ -400,7 +401,7 @@ private: // Send connection request through DHT JAMI_DBG() << parent_.account << "[CNX] request connection to " << peer_; - parent_.account.dht().putEncrypted( + parent_.account.dht()->putEncrypted( dht::InfoHash::get(PeerConnectionMsg::key_prefix + peer_.toString()), peer_, request); // Wait for call to onResponse() operated by DHT @@ -674,7 +675,7 @@ DhtPeerConnector::Impl::onRequestMsg(PeerConnectionMsg&& request) request.from, [this, request=std::move(request)] (const std::shared_ptr<dht::crypto::Certificate>& cert) mutable { dht::InfoHash peer_h; - if (account.foundPeerDevice(cert, peer_h)) + if (AccountManager::foundPeerDevice(cert, peer_h)) onTrustedRequestMsg(std::move(request), cert, peer_h); else JAMI_WARN() << account << "[CNX] rejected untrusted connection request from " @@ -798,7 +799,7 @@ DhtPeerConnector::Impl::answerToRequest(PeerConnectionMsg&& request, } JAMI_DBG() << account << "[CNX] connection accepted, DHT reply to " << request.from; - account.dht().putEncrypted( + account.dht()->putEncrypted( dht::InfoHash::get(PeerConnectionMsg::key_prefix + request.from.toString()), request.from, request.respond(addresses)); @@ -982,10 +983,10 @@ DhtPeerConnector::~DhtPeerConnector() = default; void DhtPeerConnector::onDhtConnected(const std::string& device_id) { - pimpl_->account.dht().listen<PeerConnectionMsg>( + pimpl_->account.dht()->listen<PeerConnectionMsg>( dht::InfoHash::get(PeerConnectionMsg::key_prefix + device_id), [this](PeerConnectionMsg&& msg) { - if (msg.from == pimpl_->account.dht().getId()) + if (msg.from == pimpl_->account.dht()->getId()) return true; if (!pimpl_->account.isMessageTreated(msg.id)) { if (msg.isRequest()) { @@ -1024,7 +1025,7 @@ DhtPeerConnector::requestConnection(const std::string& peer_id, pimpl_->account.forEachDevice( peer_h, [this, addresses, connect_cb, tid](const dht::InfoHash& dev_h) { - if (dev_h == pimpl_->account.dht().getId()) { + if (dev_h == pimpl_->account.dht()->getId()) { JAMI_ERR() << pimpl_->account.getAccountID() << "[CNX] no connection to yourself, bad person!"; return; } @@ -1060,7 +1061,7 @@ DhtPeerConnector::closeConnection(const std::string& peer_id, const DRing::DataT pimpl_->account.forEachDevice( peer_h, [this, tid](const dht::InfoHash& dev_h) { - if (dev_h == pimpl_->account.dht().getId()) { + if (dev_h == pimpl_->account.dht()->getId()) { JAMI_ERR() << pimpl_->account.getAccountID() << "[CNX] no connection to yourself, bad person!"; return; } diff --git a/src/security/certstore.cpp b/src/security/certstore.cpp index 05d5efbf6143231c513157bee322e6b806ffdb81..78ecbfa8949d9342cc75479a4f32f1570062b127 100644 --- a/src/security/certstore.cpp +++ b/src/security/certstore.cpp @@ -487,7 +487,7 @@ TrustStore::getCertificateStatus(const std::string& cert_id) const } std::vector<std::string> -TrustStore::getCertificatesByStatus(TrustStore::PermissionStatus status) +TrustStore::getCertificatesByStatus(TrustStore::PermissionStatus status) const { std::vector<std::string> ret; for (const auto& i : certStatus_) diff --git a/src/security/certstore.h b/src/security/certstore.h index 0981efab946e9abf4c1d6569bf381fd2b84eba21..bf26d91b6487630d8c546f4d3dae96c1e549f3e3 100644 --- a/src/security/certstore.h +++ b/src/security/certstore.h @@ -126,7 +126,7 @@ public: PermissionStatus getCertificateStatus(const std::string& cert_id) const; - std::vector<std::string> getCertificatesByStatus(PermissionStatus status); + std::vector<std::string> getCertificatesByStatus(PermissionStatus status) const; /** * Check that the certificate is allowed (valid and permited) for contact.