diff --git a/src/Olm.cpp b/src/Olm.cpp
index 994a3a67..466fe940 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -3,6 +3,7 @@
#include "Olm.h"
#include "Cache.h"
+#include "Cache_p.h"
#include "Logging.h"
#include "MatrixClient.h"
#include "Utils.h"
@@ -551,4 +552,50 @@ send_megolm_key_to_device(const std::string &user_id,
});
}
+DecryptionResult
+decryptEvent(const MegolmSessionIndex &index,
+ const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &event)
+{
+ try {
+ if (!cache::client()->inboundMegolmSessionExists(index)) {
+ return {DecryptionErrorCode::MissingSession, std::nullopt, std::nullopt};
+ }
+ } catch (const lmdb::error &e) {
+ return {DecryptionErrorCode::DbError, e.what(), std::nullopt};
+ }
+
+ // TODO: Lookup index,event_id,origin_server_ts tuple for replay attack errors
+ // TODO: Verify sender_key
+
+ std::string msg_str;
+ try {
+ auto session = cache::client()->getInboundMegolmSession(index);
+ auto res = olm::client()->decrypt_group_message(session, event.content.ciphertext);
+ msg_str = std::string((char *)res.data.data(), res.data.size());
+ } catch (const lmdb::error &e) {
+ return {DecryptionErrorCode::DbError, e.what(), std::nullopt};
+ } catch (const mtx::crypto::olm_exception &e) {
+ return {DecryptionErrorCode::DecryptionFailed, e.what(), std::nullopt};
+ }
+
+ // Add missing fields for the event.
+ json body = json::parse(msg_str);
+ body["event_id"] = event.event_id;
+ body["sender"] = event.sender;
+ body["origin_server_ts"] = event.origin_server_ts;
+ body["unsigned"] = event.unsigned_data;
+
+ // relations are unencrypted in content...
+ if (json old_ev = event; old_ev["content"].count("m.relates_to") != 0)
+ body["content"]["m.relates_to"] = old_ev["content"]["m.relates_to"];
+
+ mtx::events::collections::TimelineEvent te;
+ try {
+ mtx::events::collections::from_json(body, te);
+ } catch (std::exception &e) {
+ return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt};
+ }
+
+ return {std::nullopt, std::nullopt, std::move(te.data)};
+}
} // namespace olm
diff --git a/src/Olm.h b/src/Olm.h
index 09038ad1..87f4e3ec 100644
--- a/src/Olm.h
+++ b/src/Olm.h
@@ -7,10 +7,30 @@
#include <mtx/events/encrypted.hpp>
#include <mtxclient/crypto/client.hpp>
+#include <CacheCryptoStructs.h>
+
constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2";
namespace olm {
+enum class DecryptionErrorCode
+{
+ MissingSession, // Session was not found, retrieve from backup or request from other devices
+ // and try again
+ DbError, // DB read failed
+ DecryptionFailed, // libolm error
+ ParsingFailed, // Failed to parse the actual event
+ ReplayAttack, // Megolm index reused
+ UnknownFingerprint, // Unknown device Fingerprint
+};
+
+struct DecryptionResult
+{
+ std::optional<DecryptionErrorCode> error;
+ std::optional<std::string> error_message;
+ std::optional<mtx::events::collections::TimelineEvents> event;
+};
+
struct OlmMessage
{
std::string sender_key;
@@ -65,6 +85,10 @@ encrypt_group_message(const std::string &room_id,
const std::string &device_id,
nlohmann::json body);
+DecryptionResult
+decryptEvent(const MegolmSessionIndex &index,
+ const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &event);
+
void
mark_keys_as_published();
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 639cae0f..0e4c8b05 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -379,103 +379,96 @@ EventStore::decryptEvent(const IdIndex &idx,
index.session_id = e.content.session_id;
index.sender_key = e.content.sender_key;
- mtx::events::RoomEvent<mtx::events::msg::Notice> dummy;
- dummy.origin_server_ts = e.origin_server_ts;
- dummy.event_id = e.event_id;
- dummy.sender = e.sender;
- dummy.content.body =
- tr("-- Encrypted Event (No keys found for decryption) --",
- "Placeholder, when the message was not decrypted yet or can't be decrypted.")
- .toStdString();
-
auto asCacheEntry = [&idx](mtx::events::collections::TimelineEvents &&event) {
auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(event));
decryptedEvents_.insert(idx, event_ptr);
return event_ptr;
};
- try {
- if (!cache::client()->inboundMegolmSessionExists(index)) {
+ auto decryptionResult = olm::decryptEvent(index, e);
+
+ if (decryptionResult.error) {
+ mtx::events::RoomEvent<mtx::events::msg::Notice> dummy;
+ dummy.origin_server_ts = e.origin_server_ts;
+ dummy.event_id = e.event_id;
+ dummy.sender = e.sender;
+ switch (*decryptionResult.error) {
+ case olm::DecryptionErrorCode::MissingSession:
+ dummy.content.body =
+ tr("-- Encrypted Event (No keys found for decryption) --",
+ "Placeholder, when the message was not decrypted yet or can't be "
+ "decrypted.")
+ .toStdString();
nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
index.room_id,
index.session_id,
e.sender);
- // TODO: request megolm session_id & session_key from the sender.
- return asCacheEntry(std::move(dummy));
+ // TODO: Check if this actually works and look in key backup
+ olm::send_key_request_for(room_id_, e);
+ break;
+ case olm::DecryptionErrorCode::DbError:
+ nhlog::db()->critical(
+ "failed to retrieve megolm session with index ({}, {}, {})",
+ index.room_id,
+ index.session_id,
+ index.sender_key,
+ decryptionResult.error_message.value_or(""));
+ dummy.content.body =
+ tr("-- Decryption Error (failed to retrieve megolm keys from db) --",
+ "Placeholder, when the message can't be decrypted, because the DB "
+ "access "
+ "failed.")
+ .toStdString();
+ break;
+ case olm::DecryptionErrorCode::DecryptionFailed:
+ nhlog::crypto()->critical(
+ "failed to decrypt message with index ({}, {}, {}): {}",
+ index.room_id,
+ index.session_id,
+ index.sender_key,
+ decryptionResult.error_message.value_or(""));
+ dummy.content.body =
+ tr("-- Decryption Error (%1) --",
+ "Placeholder, when the message can't be decrypted. In this case, the "
+ "Olm "
+ "decrytion returned an error, which is passed as %1.")
+ .arg(
+ QString::fromStdString(decryptionResult.error_message.value_or("")))
+ .toStdString();
+ break;
+ case olm::DecryptionErrorCode::ParsingFailed:
+ dummy.content.body =
+ tr("-- Encrypted Event (Unknown event type) --",
+ "Placeholder, when the message was decrypted, but we couldn't parse "
+ "it, because "
+ "Nheko/mtxclient don't support that event type yet.")
+ .toStdString();
+ break;
+ case olm::DecryptionErrorCode::ReplayAttack:
+ nhlog::crypto()->critical(
+ "Reply attack while decryptiong event {} in room {} from {}!",
+ e.event_id,
+ room_id_,
+ index.sender_key);
+ dummy.content.body =
+ tr("-- Reply attack! This message index was reused! --").toStdString();
+ break;
+ case olm::DecryptionErrorCode::UnknownFingerprint:
+ // TODO: don't fail, just show in UI.
+ nhlog::crypto()->critical("Message by unverified fingerprint {}",
+ index.sender_key);
+ dummy.content.body =
+ tr("-- Message by unverified device! --").toStdString();
+ break;
}
- } catch (const lmdb::error &e) {
- nhlog::db()->critical("failed to check megolm session's existence: {}", e.what());
- dummy.content.body = tr("-- Decryption Error (failed to communicate with DB) --",
- "Placeholder, when the message can't be decrypted, because "
- "the DB access failed when trying to lookup the session.")
- .toStdString();
- return asCacheEntry(std::move(dummy));
- }
-
- std::string msg_str;
- try {
- auto session = cache::client()->getInboundMegolmSession(index);
- auto res = olm::client()->decrypt_group_message(session, e.content.ciphertext);
- msg_str = std::string((char *)res.data.data(), res.data.size());
- } catch (const lmdb::error &e) {
- nhlog::db()->critical("failed to retrieve megolm session with index ({}, {}, {})",
- index.room_id,
- index.session_id,
- index.sender_key,
- e.what());
- dummy.content.body =
- tr("-- Decryption Error (failed to retrieve megolm keys from db) --",
- "Placeholder, when the message can't be decrypted, because the DB access "
- "failed.")
- .toStdString();
- return asCacheEntry(std::move(dummy));
- } catch (const mtx::crypto::olm_exception &e) {
- nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}",
- index.room_id,
- index.session_id,
- index.sender_key,
- e.what());
- dummy.content.body =
- tr("-- Decryption Error (%1) --",
- "Placeholder, when the message can't be decrypted. In this case, the Olm "
- "decrytion returned an error, which is passed as %1.")
- .arg(e.what())
- .toStdString();
return asCacheEntry(std::move(dummy));
}
- // Add missing fields for the event.
- json body = json::parse(msg_str);
- body["event_id"] = e.event_id;
- body["sender"] = e.sender;
- body["origin_server_ts"] = e.origin_server_ts;
- body["unsigned"] = e.unsigned_data;
-
- // relations are unencrypted in content...
- if (json old_ev = e; old_ev["content"].count("m.relates_to") != 0)
- body["content"]["m.relates_to"] = old_ev["content"]["m.relates_to"];
-
- json event_array = json::array();
- event_array.push_back(body);
-
- std::vector<mtx::events::collections::TimelineEvents> temp_events;
- mtx::responses::utils::parse_timeline_events(event_array, temp_events);
-
- if (temp_events.size() == 1) {
- auto encInfo = mtx::accessors::file(temp_events[0]);
-
- if (encInfo)
- emit newEncryptedImage(encInfo.value());
-
- return asCacheEntry(std::move(temp_events[0]));
- }
+ auto encInfo = mtx::accessors::file(decryptionResult.event.value());
+ if (encInfo)
+ emit newEncryptedImage(encInfo.value());
- dummy.content.body =
- tr("-- Encrypted Event (Unknown event type) --",
- "Placeholder, when the message was decrypted, but we couldn't parse it, because "
- "Nheko/mtxclient don't support that event type yet.")
- .toStdString();
- return asCacheEntry(std::move(dummy));
+ return asCacheEntry(std::move(decryptionResult.event.value()));
}
mtx::events::collections::TimelineEvents *
|