diff options
author | Konstantinos Sideris <sideris.konstantin@gmail.com> | 2018-06-10 20:03:45 +0300 |
---|---|---|
committer | Konstantinos Sideris <sideris.konstantin@gmail.com> | 2018-06-10 20:03:45 +0300 |
commit | 626c68091126f84819091840a011c50e26dcbd8d (patch) | |
tree | 132afdb12f858a6a2a20e00e0c2a0417caf4ae13 /src | |
parent | b89257a34b2a98b737f4ae544f7e436b9000b240 (diff) |
Add support for displaying decrypted messages
Diffstat (limited to 'src')
-rw-r--r-- | src/Cache.cc | 250 | ||||
-rw-r--r-- | src/ChatPage.cc | 199 | ||||
-rw-r--r-- | src/CommunitiesList.cc | 3 | ||||
-rw-r--r-- | src/Logging.cpp | 15 | ||||
-rw-r--r-- | src/MainWindow.cc | 13 | ||||
-rw-r--r-- | src/MatrixClient.cc | 8 | ||||
-rw-r--r-- | src/Olm.cpp | 139 | ||||
-rw-r--r-- | src/RoomList.cc | 2 | ||||
-rw-r--r-- | src/main.cc | 8 | ||||
-rw-r--r-- | src/timeline/TimelineView.cc | 112 |
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; + } +< |