summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorKonstantinos Sideris <sideris.konstantin@gmail.com>2018-06-10 20:03:45 +0300
committerKonstantinos Sideris <sideris.konstantin@gmail.com>2018-06-10 20:03:45 +0300
commit626c68091126f84819091840a011c50e26dcbd8d (patch)
tree132afdb12f858a6a2a20e00e0c2a0417caf4ae13 /src
parentb89257a34b2a98b737f4ae544f7e436b9000b240 (diff)
Add support for displaying decrypted messages
Diffstat (limited to 'src')
-rw-r--r--src/Cache.cc250
-rw-r--r--src/ChatPage.cc199
-rw-r--r--src/CommunitiesList.cc3
-rw-r--r--src/Logging.cpp15
-rw-r--r--src/MainWindow.cc13
-rw-r--r--src/MatrixClient.cc8
-rw-r--r--src/Olm.cpp139
-rw-r--r--src/RoomList.cc2
-rw-r--r--src/main.cc8
-rw-r--r--src/timeline/TimelineView.cc112
10 files changed, 656 insertions, 93 deletions
diff --git a/src/Cache.cc b/src/Cache.cc
index 2a555425..150990b7 100644
--- a/src/Cache.cc
+++ b/src/Cache.cc
@@ -31,25 +31,42 @@
//! Should be changed when a breaking change occurs in the cache format.
//! This will reset client's data.
-static const std::string CURRENT_CACHE_FORMAT_VERSION("2018.05.11");
+static const std::string CURRENT_CACHE_FORMAT_VERSION("2018.06.10");
+static const std::string SECRET("secret");
static const lmdb::val NEXT_BATCH_KEY("next_batch");
+static const lmdb::val OLM_ACCOUNT_KEY("olm_account");
static const lmdb::val CACHE_FORMAT_VERSION_KEY("cache_format_version");
//! Cache databases and their format.
//!
//! Contains UI information for the joined rooms. (i.e name, topic, avatar url etc).
//! Format: room_id -> RoomInfo
-static constexpr const char *ROOMS_DB = "rooms";
-static constexpr const char *INVITES_DB = "invites";
+constexpr auto ROOMS_DB("rooms");
+constexpr auto INVITES_DB("invites");
//! Keeps already downloaded media for reuse.
//! Format: matrix_url -> binary data.
-static constexpr const char *MEDIA_DB = "media";
+constexpr auto MEDIA_DB("media");
//! Information that must be kept between sync requests.
-static constexpr const char *SYNC_STATE_DB = "sync_state";
+constexpr auto SYNC_STATE_DB("sync_state");
//! Read receipts per room/event.
-static constexpr const char *READ_RECEIPTS_DB = "read_receipts";
-static constexpr const char *NOTIFICATIONS_DB = "sent_notifications";
+constexpr auto READ_RECEIPTS_DB("read_receipts");
+constexpr auto NOTIFICATIONS_DB("sent_notifications");
+
+//! Encryption related databases.
+
+//! user_id -> list of devices
+constexpr auto DEVICES_DB("devices");
+//! device_id -> device keys
+constexpr auto DEVICE_KEYS_DB("device_keys");
+//! room_ids that have encryption enabled.
+// constexpr auto ENCRYPTED_ROOMS_DB("encrypted_rooms");
+
+//! MegolmSessionIndex -> pickled OlmInboundGroupSession
+constexpr auto INBOUND_MEGOLM_SESSIONS_DB("inbound_megolm_sessions");
+//! MegolmSessionIndex -> pickled OlmOutboundGroupSession
+constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions");
+constexpr auto OUTBOUND_OLM_SESSIONS_DB("outbound_olm_sessions");
using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
@@ -79,7 +96,7 @@ client()
{
return instance_.get();
}
-}
+} // namespace cache
Cache::Cache(const QString &userId, QObject *parent)
: QObject{parent}
@@ -90,6 +107,11 @@ Cache::Cache(const QString &userId, QObject *parent)
, mediaDb_{0}
, readReceiptsDb_{0}
, notificationsDb_{0}
+ , devicesDb_{0}
+ , deviceKeysDb_{0}
+ , inboundMegolmSessionDb_{0}
+ , outboundMegolmSessionDb_{0}
+ , outboundOlmSessionDb_{0}
, localUserId_{userId}
{}
@@ -149,9 +171,221 @@ Cache::setup()
mediaDb_ = lmdb::dbi::open(txn, MEDIA_DB, MDB_CREATE);
readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
+
+ // Device management
+ devicesDb_ = lmdb::dbi::open(txn, DEVICES_DB, MDB_CREATE);
+ deviceKeysDb_ = lmdb::dbi::open(txn, DEVICE_KEYS_DB, MDB_CREATE);
+
+ // Session management
+ inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
+ outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
+ outboundOlmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_OLM_SESSIONS_DB, MDB_CREATE);
+
+ txn.commit();
+}
+
+//
+// Device Management
+//
+
+//
+// Session Management
+//
+
+void
+Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
+ mtx::crypto::InboundGroupSessionPtr session)
+{
+ using namespace mtx::crypto;
+ const auto key = index.to_hash();
+ const auto pickled = pickle<InboundSessionObject>(session.get(), SECRET);
+
+ auto txn = lmdb::txn::begin(env_);
+ lmdb::dbi_put(txn, inboundMegolmSessionDb_, lmdb::val(key), lmdb::val(pickled));
+ txn.commit();
+
+ {
+ std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
+ session_storage.group_inbound_sessions[key] = std::move(session);
+ }
+}
+
+OlmInboundGroupSession *
+Cache::getInboundMegolmSession(const MegolmSessionIndex &index)
+{
+ std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
+ return session_storage.group_inbound_sessions[index.to_hash()].get();
+}
+
+bool
+Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept
+{
+ std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
+ return session_storage.group_inbound_sessions.find(index.to_hash()) !=
+ session_storage.group_inbound_sessions.end();
+}
+
+void
+Cache::saveOutboundMegolmSession(const MegolmSessionIndex &index,
+ const OutboundGroupSessionData &data,
+ mtx::crypto::OutboundGroupSessionPtr session)
+{
+ using namespace mtx::crypto;
+ const auto key = index.to_hash();
+ const auto pickled = pickle<OutboundSessionObject>(session.get(), SECRET);
+
+ json j;
+ j["data"] = data;
+ j["session"] = pickled;
+
+ auto txn = lmdb::txn::begin(env_);
+ lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(key), lmdb::val(j.dump()));
+ txn.commit();
+
+ {
+ std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
+ session_storage.group_outbound_session_data[key] = data;
+ session_storage.group_outbound_sessions[key] = std::move(session);
+ }
+}
+
+bool
+Cache::outboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept
+{
+ const auto key = index.to_hash();
+
+ std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
+ return (session_storage.group_outbound_sessions.find(key) !=
+ session_storage.group_outbound_sessions.end()) &&
+ (session_storage.group_outbound_session_data.find(key) !=
+ session_storage.group_outbound_session_data.end());
+}
+
+OutboundGroupSessionDataRef
+Cache::getOutboundMegolmSession(const MegolmSessionIndex &index)
+{
+ const auto key = index.to_hash();
+ std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
+ return OutboundGroupSessionDataRef{session_storage.group_outbound_sessions[key].get(),
+ session_storage.group_outbound_session_data[key]};
+}
+
+void
+Cache::saveOutboundOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session)
+{
+ using namespace mtx::crypto;
+ const auto pickled = pickle<SessionObject>(session.get(), SECRET);
+
+ auto txn = lmdb::txn::begin(env_);
+ lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(curve25519), lmdb::val(pickled));
txn.commit();
+
+ {
+ std::unique_lock<std::mutex> lock(session_storage.outbound_mtx);
+ session_storage.outbound_sessions[curve25519] = std::move(session);
+ }
+}
+
+bool
+Cache::outboundOlmSessionsExists(const std::string &curve25519) noexcept
+{
+ std::unique_lock<std::mutex> lock(session_storage.outbound_mtx);
+ return session_storage.outbound_sessions.find(curve25519) !=
+ session_storage.outbound_sessions.end();
}
+OlmSession *
+Cache::getOutboundOlmSession(const std::string &curve25519)
+{
+ std::unique_lock<std::mutex> lock(session_storage.outbound_mtx);
+ return session_storage.outbound_sessions.at(curve25519).get();
+}
+
+void
+Cache::saveOlmAccount(const std::string &data)
+{
+ auto txn = lmdb::txn::begin(env_);
+ lmdb::dbi_put(txn, syncStateDb_, OLM_ACCOUNT_KEY, lmdb::val(data));
+ txn.commit();
+}
+
+void
+Cache::restoreSessions()
+{
+ using namespace mtx::crypto;
+
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ std::string key, value;
+
+ //
+ // Inbound Megolm Sessions
+ //
+ {
+ auto cursor = lmdb::cursor::open(txn, inboundMegolmSessionDb_);
+ while (cursor.get(key, value, MDB_NEXT)) {
+ auto session = unpickle<InboundSessionObject>(value, SECRET);
+ session_storage.group_inbound_sessions[key] = std::move(session);
+ }
+ cursor.close();
+ }
+
+ //
+ // Outbound Megolm Sessions
+ //
+ {
+ auto cursor = lmdb::cursor::open(txn, outboundMegolmSessionDb_);
+ while (cursor.get(key, value, MDB_NEXT)) {
+ json obj;
+
+ try {
+ obj = json::parse(value);
+
+ session_storage.group_outbound_session_data[key] =
+ obj.at("data").get<OutboundGroupSessionData>();
+
+ auto session =
+ unpickle<OutboundSessionObject>(obj.at("session"), SECRET);
+ session_storage.group_outbound_sessions[key] = std::move(session);
+ } catch (const nlohmann::json::exception &e) {
+ log::db()->warn("failed to parse outbound megolm session data: {}",
+ e.what());
+ }
+ }
+ cursor.close();
+ }
+
+ //
+ // Outbound Olm Sessions
+ //
+ {
+ auto cursor = lmdb::cursor::open(txn, outboundOlmSessionDb_);
+ while (cursor.get(key, value, MDB_NEXT)) {
+ auto session = unpickle<SessionObject>(value, SECRET);
+ session_storage.outbound_sessions[key] = std::move(session);
+ }
+ cursor.close();
+ }
+
+ txn.commit();
+
+ log::db()->info("sessions restored");
+}
+
+std::string
+Cache::restoreOlmAccount()
+{
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ lmdb::val pickled;
+ lmdb::dbi_get(txn, syncStateDb_, OLM_ACCOUNT_KEY, pickled);
+ txn.commit();
+
+ return std::string(pickled.data(), pickled.size());
+}
+
+//
+// Media Management
+//
+
void
Cache::saveImage(const std::string &url, const std::string &img_data)
{
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index 64ce69d6..a5a6a8c0 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -25,6 +25,7 @@
#include "Logging.hpp"
#include "MainWindow.h"
#include "MatrixClient.h"
+#include "Olm.hpp"
#include "OverlayModal.h"
#include "QuickSwitcher.h"
#include "RoomList.h"
@@ -43,8 +44,12 @@
#include "dialogs/ReadReceipts.h"
#include "timeline/TimelineViewManager.h"
+// TODO: Needs to be updated with an actual secret.
+static const std::string STORAGE_SECRET_KEY("secret");
+
ChatPage *ChatPage::instance_ = nullptr;
constexpr int CHECK_CONNECTIVITY_INTERVAL = 15'000;
+constexpr size_t MAX_ONETIME_KEYS = 50;
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
: QWidget(parent)
@@ -612,6 +617,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
connect(this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync);
connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync);
+ connect(this, &ChatPage::tryDelayedSyncCb, this, [this]() {
+ QTimer::singleShot(5000, this, &ChatPage::trySync);
+ });
connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage);
@@ -728,6 +736,11 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
});
// TODO http::client()->getOwnCommunities();
+ // The Olm client needs the user_id & device_id that will be included
+ // in the generated payloads & keys.
+ olm::client()->set_user_id(http::v2::client()->user_id().to_string());
+ olm::client()->set_device_id(http::v2::client()->device_id());
+
cache::init(userid);
try {
@@ -741,6 +754,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
if (cache::client()->isInitialized()) {
loadStateFromCache();
+ // TODO: Bootstrap olm client with saved data.
return;
}
} catch (const lmdb::error &e) {
@@ -749,6 +763,22 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
log::net()->info("falling back to initial sync");
}
+ try {
+ // It's the first time syncing with this device
+ // There isn't a saved olm account to restore.
+ log::crypto()->info("creating new olm account");
+ olm::client()->create_new_account();
+ cache::client()->saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY));
+ } catch (const lmdb::error &e) {
+ log::crypto()->critical("failed to save olm account {}", e.what());
+ emit dropToLoginPageCb(QString::fromStdString(e.what()));
+ return;
+ } catch (const mtx::crypto::olm_exception &e) {
+ log::crypto()->critical("failed to create new olm account {}", e.what());
+ emit dropToLoginPageCb(QString::fromStdString(e.what()));
+ return;
+ }
+
tryInitialSync();
}
@@ -826,16 +856,29 @@ ChatPage::loadStateFromCache()
QtConcurrent::run([this]() {
try {
+ cache::client()->restoreSessions();
+ olm::client()->load(cache::client()->restoreOlmAccount(),
+ STORAGE_SECRET_KEY);
+
cache::client()->populateMembers();
emit initializeEmptyViews(cache::client()->joinedRooms());
emit initializeRoomList(cache::client()->roomInfo());
+ } catch (const mtx::crypto::olm_exception &e) {
+ log::crypto()->critical("failed to restore olm account: {}", e.what());
+ emit dropToLoginPageCb(
+ tr("Failed to restore OLM account. Please login again."));
+ return;
} catch (const lmdb::error &e) {
- std::cout << "load cache error:" << e.what() << '\n';
- // TODO Clear cache and restart.
+ log::db()->critical("failed to restore cache: {}", e.what());
+ emit dropToLoginPageCb(
+ tr("Failed to restore save data. Please login again."));
return;
}
+ log::crypto()->info("ed25519 : {}", olm::client()->identity_keys().ed25519);
+ log::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519);
+
// Start receiving events.
emit trySyncCb();
@@ -1008,49 +1051,40 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
void
ChatPage::tryInitialSync()
{
- mtx::http::SyncOpts opts;
- opts.timeout = 0;
+ log::crypto()->info("ed25519 : {}", olm::client()->identity_keys().ed25519);
+ log::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519);
- log::net()->info("trying initial sync");
+ // Upload one time keys for the device.
+ log::crypto()->info("generating one time keys");
+ olm::client()->generate_one_time_keys(MAX_ONETIME_KEYS);
- http::v2::client()->sync(
- opts, [this](const mtx::responses::Sync &res, mtx::http::RequestErr err) {
+ http::v2::client()->upload_keys(
+ olm::client()->create_upload_keys_request(),
+ [this](const mtx::responses::UploadKeys &res, mtx::http::RequestErr err) {
if (err) {
- const auto error = QString::fromStdString(err->matrix_error.error);
- const auto msg = tr("Please try to login again: %1").arg(error);
- const auto err_code = mtx::errors::to_string(err->matrix_error.errcode);
const int status_code = static_cast<int>(err->status_code);
-
- log::net()->error("sync error: {} {}", status_code, err_code);
-
- switch (status_code) {
- case 502:
- case 504:
- case 524: {
- emit tryInitialSyncCb();
- return;
- }
- default: {
- emit dropToLoginPageCb(msg);
- return;
- }
- }
- }
-
- log::net()->info("initial sync completed");
-
- try {
- cache::client()->saveState(res);
- emit initializeViews(std::move(res.rooms));
- emit initializeRoomList(cache::client()->roomInfo());
- } catch (const lmdb::error &e) {
- log::db()->error("{}", e.what());
+ log::crypto()->critical("failed to upload one time keys: {} {}",
+ err->matrix_error.error,
+ status_code);
+ // TODO We should have a timeout instead of keeping hammering the server.
emit tryInitialSyncCb();
return;
}
- emit trySyncCb();
- emit contentLoaded();
+ olm::client()->mark_keys_as_published();
+ for (const auto &entry : res.one_time_key_counts)
+ log::net()->info(
+ "uploaded {} {} one-time keys", entry.second, entry.first);
+
+ log::net()->info("trying initial sync");
+
+ mtx::http::SyncOpts opts;
+ opts.timeout = 0;
+ http::v2::client()->sync(opts,
+ std::bind(&ChatPage::initialSyncHandler,
+ this,
+ std::placeholders::_1,
+ std::placeholders::_2));
});
}
@@ -1079,24 +1113,31 @@ ChatPage::trySync()
log::net()->error("sync error: {} {}", status_code, err_code);
+ if (status_code <= 0 || status_code >= 600) {
+ if (!http::v2::is_logged_in())
+ return;
+
+ emit dropToLoginPageCb(msg);
+ return;
+ }
+
switch (status_code) {
case 502:
case 504:
case 524: {
- emit trySync();
+ emit trySyncCb();
return;
}
case 401:
case 403: {
- // We are logged out.
- if (http::v2::client()->access_token().empty())
+ if (!http::v2::is_logged_in())
return;
emit dropToLoginPageCb(msg);
return;
}
default: {
- emit trySync();
+ emit tryDelayedSyncCb();
return;
}
}
@@ -1104,9 +1145,14 @@ ChatPage::trySync()
log::net()->debug("sync completed: {}", res.next_batch);
+ // Ensure that we have enough one-time keys available.
+ ensureOneTimeKeyCount(res.device_one_time_keys_count);
+
// TODO: fine grained error handling
try {
cache::client()->saveState(res);
+ olm::handle_to_device_messages(res.to_device);
+
emit syncUI(res.rooms);
auto updates = cache::client()->roomUpdates(res);
@@ -1194,3 +1240,74 @@ ChatPage::sendTypingNotifications()
}
});
}
+
+void
+ChatPage::initialSyncHandler(const mtx::responses::Sync &res, mtx::http::RequestErr err)
+{
+ if (err) {
+ const auto error = QString::fromStdString(err->matrix_error.error);
+ const auto msg = tr("Please try to login again: %1").arg(error);
+ const auto err_code = mtx::errors::to_string(err->matrix_error.errcode);
+ const int status_code = static_cast<int>(err->status_code);
+
+ log::net()->error("sync error: {} {}", status_code, err_code);
+
+ switch (status_code) {
+ case 502:
+ case 504:
+ case 524: {
+ emit tryInitialSyncCb();
+ return;
+ }
+ default: {
+ emit dropToLoginPageCb(msg);
+ return;
+ }
+ }
+ }
+
+ log::net()->info("initial sync completed");
+
+ try {
+ cache::client()->saveState(res);
+
+ olm::handle_to_device_messages(res.to_device);
+
+ emit initializeViews(std::move(res.rooms));
+ emit initializeRoomList(cache::client()->roomInfo());
+ } catch (const lmdb::error &e) {
+ log::db()->error("{}", e.what());
+ emit tryInitialSyncCb();
+ return;
+ }
+
+ emit trySyncCb();
+ emit contentLoaded();
+}
+
+void
+ChatPage::ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts)
+{
+ for (const auto &entry : counts) {
+ if (entry.second < MAX_ONETIME_KEYS) {
+ const int nkeys = MAX_ONETIME_KEYS - entry.second;
+
+ log::crypto()->info("uploading {} {} keys", nkeys, entry.first);
+ olm::client()->generate_one_time_keys(nkeys);
+
+ http::v2::client()->upload_keys(
+ olm::client()->create_upload_keys_request(),
+ [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
+ if (err) {
+ log::crypto()->warn(
+ "failed to update one-time keys: {} {}",
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ olm::client()->mark_keys_as_published();
+ });
+ }
+ }
+}
diff --git a/src/CommunitiesList.cc b/src/CommunitiesList.cc
index 8ccd5e9d..49affcb7 100644
--- a/src/CommunitiesList.cc
+++ b/src/CommunitiesList.cc
@@ -128,6 +128,9 @@ CommunitiesList::fetchCommunityAvatar(const QString &id, const QString &avatarUr
return;
}
+ if (avatarUrl.isEmpty())
+ return;
+
mtx::http::ThumbOpts opts;
opts.mxc_url = avatarUrl.toStdString();
http::v2::client()->get_thumbnail(
diff --git a/src/Logging.cpp b/src/Logging.cpp
index c6c1c502..77e61e09 100644
--- a/src/Logging.cpp
+++ b/src/Logging.cpp
@@ -4,9 +4,10 @@
#include <spdlog/sinks/file_sinks.h>
namespace {
-std::shared_ptr<spdlog::logger> db_logger = nullptr;
-std::shared_ptr<spdlog::logger> net_logger = nullptr;
-std::shared_ptr<spdlog::logger> main_logger = nullptr;
+std::shared_ptr<spdlog::logger> db_logger = nullptr;
+std::shared_ptr<spdlog::logger> net_logger = nullptr;
+std::shared_ptr<spdlog::logger> crypto_logger = nullptr;
+std::shared_ptr<spdlog::logger> main_logger = nullptr;
constexpr auto MAX_FILE_SIZE = 1024 * 1024 * 6;
constexpr auto MAX_LOG_FILES = 3;
@@ -28,6 +29,8 @@ init(const std::string &file_path)
net_logger = std::make_shared<spdlog::logger>("net", std::begin(sinks), std::end(sinks));
main_logger = std::make_shared<spdlog::logger>("main", std::begin(sinks), std::end(sinks));
db_logger = std::make_shared<spdlog::logger>("db", std::begin(sinks), std::end(sinks));
+ crypto_logger =
+ std::make_shared<spdlog::logger>("crypto", std::begin(sinks), std::end(sinks));
}
std::shared_ptr<spdlog::logger>
@@ -47,4 +50,10 @@ db()
{
return db_logger;
}
+
+std::shared_ptr<spdlog::logger>
+crypto()
+{
+ return crypto_logger;
+}
}
diff --git a/src/MainWindow.cc b/src/MainWindow.cc
index 9ba8b28e..cca51f03 100644
--- a/src/MainWindow.cc
+++ b/src/MainWindow.cc
@@ -46,15 +46,6 @@
MainWindow *MainWindow::instance_ = nullptr;
-MainWindow::~MainWindow()
-{
- if (http::v2::client() != nullptr) {
- http::v2::client()->shutdown();
- // TODO: find out why waiting for the threads to join is slow.
- http::v2::client()->close();
- }
-}
-
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, progressModal_{nullptr}
@@ -154,9 +145,11 @@ MainWindow::MainWindow(QWidget *parent)
QString token = settings.value("auth/access_token").toString();
QString home_server = settings.value("auth/home_server").toString();
QString user_id = settings.value("auth/user_id").toString();
+ QString device_id = settings.value("auth/device_id").toString();
http::v2::client()->set_access_token(token.toStdString());
http::v2::client()->set_server(home_server.toStdString());
+ http::v2::client()->set_device_id(device_id.toStdString());
try {
using namespace mtx::identifiers;
@@ -228,6 +221,7 @@ void
MainWindow::showChatPage()
{
auto userid = QString::fromStdString(http::v2::client()->user_id().to_string());
+ auto device_id = QString::fromStdString(http::v2::client()->device_id());
auto homeserver = QString::fromStdString(http::v2::client()->server() + ":" +
std::to_string(http::v2::client()->port()));
auto token = QString::fromStdString(http::v2::client()->access_token());
@@ -236,6 +230,7 @@ MainWindow::showChatPage()
settings.setValue("auth/access_token", token);
settings.setValue("auth/home_server", homeserver);
settings.setValue("auth/user_id", userid);
+ settings.setValue("auth/device_id", device_id);
showOverlayProgressBar();
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index 0eb4658a..d4ab8e33 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cc
@@ -3,7 +3,7 @@
#include <memory>
namespace {
-auto v2_client_ = std::make_shared<mtx::http::Client>("matrix.org");
+auto v2_client_ = std::make_shared<mtx::http::Client>();
}
namespace http {
@@ -15,6 +15,12 @@ client()
return v2_client_.get();
}
+bool
+is_logged_in()
+{
+ return !v2_client_->access_token().empty();
+}
+
} // namespace v2
void
diff --git a/src/Olm.cpp b/src/Olm.cpp
new file mode 100644
index 00000000..769b0234
--- /dev/null
+++ b/src/Olm.cpp
@@ -0,0 +1,139 @@
+#include "Olm.hpp"
+
+#include "Cache.h"
+#include "Logging.hpp"
+
+using namespace mtx::crypto;
+
+namespace {
+auto client_ = std::make_unique<mtx::crypto::OlmClient>();
+}
+
+namespace olm {
+
+mtx::crypto::OlmClient *
+client()
+{
+ return client_.get();
+}
+
+void
+handle_to_device_messages(const std::vector<nlohmann::json> &msgs)
+{
+ if (msgs.empty())
+ return;
+
+ log::crypto()->info("received {} to_device messages", msgs.size());
+
+ for (const auto &msg : msgs) {
+ try {
+ OlmMessage olm_msg = msg;
+ handle_olm_message(std::move(olm_msg));
+ } catch (const nlohmann::json::exception &e) {
+ log::crypto()->warn(
+ "parsing error for olm message: {} {}", e.what(), msg.dump(2));
+ } catch (const std::invalid_argument &e) {
+ log::crypto()->warn(
+ "validation error for olm message: {} {}", e.what(), msg.dump(2));
+ }
+ }
+}
+
+void
+handle_olm_message(const OlmMessage &msg)
+{
+ log::crypto()->info("sender : {}", msg.sender);
+ log::crypto()->info("sender_key: {}", msg.sender_key);
+
+ const auto my_key = olm::client()->identity_keys().curve25519;
+
+ for (const auto &cipher : msg.ciphertext) {
+ // We skip messages not meant for the current device.
+ if (cipher.first != my_key)
+ continue;
+
+ const auto type = cipher.second.type;
+ log::crypto()->info("type: {}", type == 0 ? "OLM_PRE_KEY" : "OLM_MESSAGE");
+
+ if (type == OLM_MESSAGE_TYPE_PRE_KEY)
+ handle_pre_key_olm_message(msg.sender, msg.sender_key, cipher.second);
+ else
+ handle_olm_normal_message(msg.sender, msg.sender_key, cipher.second);
+ }
+}
+
+void
+handle_pre_key_olm_message(const std::string &sender,
+ const std::string &sender_key,
+ const OlmCipherContent &content)
+{
+ log::crypto()->info("opening olm session with {}", sender);
+
+ OlmSessionPtr inbound_session = nullptr;
+ try {
+ inbound_session = olm::client()->create_inbound_session(content.body);
+ } catch (const olm_exception &e) {
+ log::crypto()->critical(
+ "failed to create inbound session with {}: {}", sender, e.what());
+ return;
+ }
+
+ if (!matches_inbound_session_from(inbound_session.get(), sender_key, content.body)) {
+ log::crypto()->warn("inbound olm session doesn't match sender's key ({})", sender);
+ return;
+ }
+
+ mtx::crypto::BinaryBuf output;
+ try {
+ output = olm::client()->decrypt_message(
+ inbound_session.get(), OLM_MESSAGE_TYPE_PRE_KEY, content.body);
+ } catch (const olm_exception &e) {
+ log::crypto()->critical(
+ "failed to decrypt olm message {}: {}", content.body, e.what());
+ return;
+ }
+<