Show encryption errors in qml and add request keys button
This commit is contained in:
parent
9f742fe23d
commit
72bbad7485
@ -541,32 +541,33 @@ qt5_wrap_cpp(MOC_HEADERS
|
|||||||
|
|
||||||
src/AvatarProvider.h
|
src/AvatarProvider.h
|
||||||
src/BlurhashProvider.h
|
src/BlurhashProvider.h
|
||||||
src/Cache_p.h
|
|
||||||
src/CacheCryptoStructs.h
|
src/CacheCryptoStructs.h
|
||||||
|
src/Cache_p.h
|
||||||
src/CallDevices.h
|
src/CallDevices.h
|
||||||
src/CallManager.h
|
src/CallManager.h
|
||||||
src/ChatPage.h
|
src/ChatPage.h
|
||||||
src/Clipboard.h
|
src/Clipboard.h
|
||||||
|
src/CombinedImagePackModel.h
|
||||||
src/CompletionProxyModel.h
|
src/CompletionProxyModel.h
|
||||||
src/DeviceVerificationFlow.h
|
src/DeviceVerificationFlow.h
|
||||||
|
src/ImagePackListModel.h
|
||||||
src/InviteesModel.h
|
src/InviteesModel.h
|
||||||
src/LoginPage.h
|
src/LoginPage.h
|
||||||
src/MainWindow.h
|
src/MainWindow.h
|
||||||
src/MemberList.h
|
src/MemberList.h
|
||||||
src/MxcImageProvider.h
|
src/MxcImageProvider.h
|
||||||
src/ReadReceiptsModel.h
|
src/Olm.h
|
||||||
src/RegisterPage.h
|
src/RegisterPage.h
|
||||||
|
src/RoomsModel.h
|
||||||
src/SSOHandler.h
|
src/SSOHandler.h
|
||||||
src/CombinedImagePackModel.h
|
|
||||||
src/SingleImagePackModel.h
|
src/SingleImagePackModel.h
|
||||||
src/ImagePackListModel.h
|
|
||||||
src/TrayIcon.h
|
src/TrayIcon.h
|
||||||
src/UserSettingsPage.h
|
src/UserSettingsPage.h
|
||||||
src/UsersModel.h
|
src/UsersModel.h
|
||||||
src/RoomsModel.h
|
|
||||||
src/WebRTCSession.h
|
src/WebRTCSession.h
|
||||||
src/WelcomePage.h
|
src/WelcomePage.h
|
||||||
)
|
src/ReadReceiptsModel.h
|
||||||
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Bundle translations.
|
# Bundle translations.
|
||||||
|
@ -349,6 +349,7 @@ ScrollView {
|
|||||||
required property string callType
|
required property string callType
|
||||||
required property var reactions
|
required property var reactions
|
||||||
required property int trustlevel
|
required property int trustlevel
|
||||||
|
required property int encryptionError
|
||||||
required property var timestamp
|
required property var timestamp
|
||||||
required property int status
|
required property int status
|
||||||
required property int index
|
required property int index
|
||||||
@ -456,6 +457,7 @@ ScrollView {
|
|||||||
callType: wrapper.callType
|
callType: wrapper.callType
|
||||||
reactions: wrapper.reactions
|
reactions: wrapper.reactions
|
||||||
trustlevel: wrapper.trustlevel
|
trustlevel: wrapper.trustlevel
|
||||||
|
encryptionError: wrapper.encryptionError
|
||||||
timestamp: wrapper.timestamp
|
timestamp: wrapper.timestamp
|
||||||
status: wrapper.status
|
status: wrapper.status
|
||||||
relatedEventCacheBuster: wrapper.relatedEventCacheBuster
|
relatedEventCacheBuster: wrapper.relatedEventCacheBuster
|
||||||
|
@ -38,6 +38,7 @@ Item {
|
|||||||
required property string callType
|
required property string callType
|
||||||
required property var reactions
|
required property var reactions
|
||||||
required property int trustlevel
|
required property int trustlevel
|
||||||
|
required property int encryptionError
|
||||||
required property var timestamp
|
required property var timestamp
|
||||||
required property int status
|
required property int status
|
||||||
required property int relatedEventCacheBuster
|
required property int relatedEventCacheBuster
|
||||||
@ -110,6 +111,7 @@ Item {
|
|||||||
roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
|
roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
|
||||||
roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
|
roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
|
||||||
callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? ""
|
callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? ""
|
||||||
|
encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? ""
|
||||||
relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
|
relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +138,7 @@ Item {
|
|||||||
roomTopic: r.roomTopic
|
roomTopic: r.roomTopic
|
||||||
roomName: r.roomName
|
roomName: r.roomName
|
||||||
callType: r.callType
|
callType: r.callType
|
||||||
|
encryptionError: r.encryptionError
|
||||||
relatedEventCacheBuster: r.relatedEventCacheBuster
|
relatedEventCacheBuster: r.relatedEventCacheBuster
|
||||||
isReply: false
|
isReply: false
|
||||||
}
|
}
|
||||||
|
48
resources/qml/delegates/Encrypted.qml
Normal file
48
resources/qml/delegates/Encrypted.qml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import ".."
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Layouts 1.2
|
||||||
|
import im.nheko 1.0
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: r
|
||||||
|
|
||||||
|
required property int encryptionError
|
||||||
|
required property string eventId
|
||||||
|
|
||||||
|
width: parent ? parent.width : undefined
|
||||||
|
|
||||||
|
MatrixText {
|
||||||
|
text: {
|
||||||
|
switch (encryptionError) {
|
||||||
|
case Olm.MissingSession:
|
||||||
|
return qsTr("There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient.");
|
||||||
|
case Olm.MissingSessionIndex:
|
||||||
|
return qsTr("This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message.");
|
||||||
|
case Olm.DbError:
|
||||||
|
return qsTr("There was an internal error reading the decryption key from the database.");
|
||||||
|
case Olm.DecryptionFailed:
|
||||||
|
return qsTr("There was an error decrypting this message.");
|
||||||
|
case Olm.ParsingFailed:
|
||||||
|
return qsTr("The message couldn't be parsed.");
|
||||||
|
case Olm.ReplayAttack:
|
||||||
|
return qsTr("The encryption key was reused! Someone is possibly trying to insert false messages into this chat!");
|
||||||
|
default:
|
||||||
|
return qsTr("Unknown decryption error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
color: Nheko.colors.buttonText
|
||||||
|
width: r ? r.width : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
palette: Nheko.colors
|
||||||
|
visible: encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex
|
||||||
|
text: qsTr("Request key")
|
||||||
|
onClicked: room.requestKeyForEvent(eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -29,6 +29,7 @@ Item {
|
|||||||
required property string roomTopic
|
required property string roomTopic
|
||||||
required property string roomName
|
required property string roomName
|
||||||
required property string callType
|
required property string callType
|
||||||
|
required property int encryptionError
|
||||||
required property int relatedEventCacheBuster
|
required property int relatedEventCacheBuster
|
||||||
|
|
||||||
height: chooser.childrenRect.height
|
height: chooser.childrenRect.height
|
||||||
@ -189,6 +190,16 @@ Item {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: MtxEvent.Encrypted
|
||||||
|
|
||||||
|
Encrypted {
|
||||||
|
encryptionError: d.encryptionError
|
||||||
|
eventId: d.eventId
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: MtxEvent.Name
|
roleValue: MtxEvent.Name
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ Item {
|
|||||||
property string roomTopic
|
property string roomTopic
|
||||||
property string roomName
|
property string roomName
|
||||||
property string callType
|
property string callType
|
||||||
|
property int encryptionError
|
||||||
property int relatedEventCacheBuster
|
property int relatedEventCacheBuster
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@ -97,6 +98,7 @@ Item {
|
|||||||
roomName: r.roomName
|
roomName: r.roomName
|
||||||
callType: r.callType
|
callType: r.callType
|
||||||
relatedEventCacheBuster: r.relatedEventCacheBuster
|
relatedEventCacheBuster: r.relatedEventCacheBuster
|
||||||
|
encryptionError: r.encryptionError
|
||||||
enabled: false
|
enabled: false
|
||||||
width: parent.width
|
width: parent.width
|
||||||
isReply: true
|
isReply: true
|
||||||
|
@ -143,14 +143,15 @@
|
|||||||
<file>qml/emoji/StickerPicker.qml</file>
|
<file>qml/emoji/StickerPicker.qml</file>
|
||||||
<file>qml/UserProfile.qml</file>
|
<file>qml/UserProfile.qml</file>
|
||||||
<file>qml/delegates/MessageDelegate.qml</file>
|
<file>qml/delegates/MessageDelegate.qml</file>
|
||||||
<file>qml/delegates/TextMessage.qml</file>
|
<file>qml/delegates/Encrypted.qml</file>
|
||||||
<file>qml/delegates/NoticeMessage.qml</file>
|
|
||||||
<file>qml/delegates/ImageMessage.qml</file>
|
|
||||||
<file>qml/delegates/PlayableMediaMessage.qml</file>
|
|
||||||
<file>qml/delegates/FileMessage.qml</file>
|
<file>qml/delegates/FileMessage.qml</file>
|
||||||
|
<file>qml/delegates/ImageMessage.qml</file>
|
||||||
|
<file>qml/delegates/NoticeMessage.qml</file>
|
||||||
<file>qml/delegates/Pill.qml</file>
|
<file>qml/delegates/Pill.qml</file>
|
||||||
<file>qml/delegates/Placeholder.qml</file>
|
<file>qml/delegates/Placeholder.qml</file>
|
||||||
|
<file>qml/delegates/PlayableMediaMessage.qml</file>
|
||||||
<file>qml/delegates/Reply.qml</file>
|
<file>qml/delegates/Reply.qml</file>
|
||||||
|
<file>qml/delegates/TextMessage.qml</file>
|
||||||
<file>qml/device-verification/Waiting.qml</file>
|
<file>qml/device-verification/Waiting.qml</file>
|
||||||
<file>qml/device-verification/DeviceVerification.qml</file>
|
<file>qml/device-verification/DeviceVerification.qml</file>
|
||||||
<file>qml/device-verification/DigitVerification.qml</file>
|
<file>qml/device-verification/DigitVerification.qml</file>
|
||||||
|
@ -1069,7 +1069,7 @@ decryptEvent(const MegolmSessionIndex &index,
|
|||||||
mtx::events::collections::TimelineEvent te;
|
mtx::events::collections::TimelineEvent te;
|
||||||
mtx::events::collections::from_json(body, te);
|
mtx::events::collections::from_json(body, te);
|
||||||
|
|
||||||
return {std::nullopt, std::nullopt, std::move(te.data)};
|
return {DecryptionErrorCode::NoError, std::nullopt, std::move(te.data)};
|
||||||
} catch (std::exception &e) {
|
} catch (std::exception &e) {
|
||||||
return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt};
|
return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt};
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,11 @@
|
|||||||
constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2";
|
constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2";
|
||||||
|
|
||||||
namespace olm {
|
namespace olm {
|
||||||
|
Q_NAMESPACE
|
||||||
|
|
||||||
enum class DecryptionErrorCode
|
enum DecryptionErrorCode
|
||||||
{
|
{
|
||||||
|
NoError,
|
||||||
MissingSession, // Session was not found, retrieve from backup or request from other devices
|
MissingSession, // Session was not found, retrieve from backup or request from other devices
|
||||||
// and try again
|
// and try again
|
||||||
MissingSessionIndex, // Session was found, but it does not reach back enough to this index,
|
MissingSessionIndex, // Session was found, but it does not reach back enough to this index,
|
||||||
@ -25,14 +27,13 @@ enum class DecryptionErrorCode
|
|||||||
DecryptionFailed, // libolm error
|
DecryptionFailed, // libolm error
|
||||||
ParsingFailed, // Failed to parse the actual event
|
ParsingFailed, // Failed to parse the actual event
|
||||||
ReplayAttack, // Megolm index reused
|
ReplayAttack, // Megolm index reused
|
||||||
UnknownFingerprint, // Unknown device Fingerprint
|
|
||||||
};
|
};
|
||||||
|
Q_ENUM_NS(DecryptionErrorCode)
|
||||||
|
|
||||||
struct DecryptionResult
|
struct DecryptionResult
|
||||||
{
|
{
|
||||||
std::optional<DecryptionErrorCode> error;
|
DecryptionErrorCode error;
|
||||||
std::optional<std::string> error_message;
|
std::optional<std::string> error_message;
|
||||||
|
|
||||||
std::optional<mtx::events::collections::TimelineEvents> event;
|
std::optional<mtx::events::collections::TimelineEvents> event;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -20,8 +20,7 @@
|
|||||||
|
|
||||||
Q_DECLARE_METATYPE(Reaction)
|
Q_DECLARE_METATYPE(Reaction)
|
||||||
|
|
||||||
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::decryptedEvents_{
|
QCache<EventStore::IdIndex, olm::DecryptionResult> EventStore::decryptedEvents_{1000};
|
||||||
1000};
|
|
||||||
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::events_by_id_{
|
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::events_by_id_{
|
||||||
1000};
|
1000};
|
||||||
QCache<EventStore::Index, mtx::events::collections::TimelineEvents> EventStore::events_{1000};
|
QCache<EventStore::Index, mtx::events::collections::TimelineEvents> EventStore::events_{1000};
|
||||||
@ -144,12 +143,16 @@ EventStore::EventStore(std::string room_id, QObject *)
|
|||||||
mtx::events::msg::Encrypted>) {
|
mtx::events::msg::Encrypted>) {
|
||||||
auto event =
|
auto event =
|
||||||
decryptEvent({room_id_, e.event_id}, e);
|
decryptEvent({room_id_, e.event_id}, e);
|
||||||
if (auto dec =
|
if (event->event) {
|
||||||
std::get_if<mtx::events::RoomEvent<
|
if (auto dec = std::get_if<
|
||||||
mtx::events::msg::
|
mtx::events::RoomEvent<
|
||||||
KeyVerificationRequest>>(event)) {
|
mtx::events::msg::
|
||||||
emit updateFlowEventId(
|
KeyVerificationRequest>>(
|
||||||
event_id.event_id.to_string());
|
&event->event.value())) {
|
||||||
|
emit updateFlowEventId(
|
||||||
|
event_id.event_id
|
||||||
|
.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -393,12 +396,12 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
|
|||||||
if (auto encrypted =
|
if (auto encrypted =
|
||||||
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||||
&event)) {
|
&event)) {
|
||||||
mtx::events::collections::TimelineEvents *d_event =
|
auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
|
||||||
decryptEvent({room_id_, encrypted->event_id}, *encrypted);
|
if (d_event->event &&
|
||||||
if (std::visit(
|
std::visit(
|
||||||
[](auto e) { return (e.sender != utils::localUser().toStdString()); },
|
[](auto e) { return (e.sender != utils::localUser().toStdString()); },
|
||||||
*d_event)) {
|
*d_event->event)) {
|
||||||
handle_room_verification(*d_event);
|
handle_room_verification(*d_event->event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -599,11 +602,15 @@ EventStore::get(int idx, bool decrypt)
|
|||||||
events_.insert(index, event_ptr);
|
events_.insert(index, event_ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decrypt)
|
if (decrypt) {
|
||||||
if (auto encrypted =
|
if (auto encrypted =
|
||||||
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||||
event_ptr))
|
event_ptr)) {
|
||||||
return decryptEvent({room_id_, encrypted->event_id}, *encrypted);
|
auto decrypted = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
|
||||||
|
if (decrypted->event)
|
||||||
|
return &*decrypted->event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return event_ptr;
|
return event_ptr;
|
||||||
}
|
}
|
||||||
@ -629,7 +636,7 @@ EventStore::indexToId(int idx) const
|
|||||||
return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx));
|
return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
mtx::events::collections::TimelineEvents *
|
olm::DecryptionResult *
|
||||||
EventStore::decryptEvent(const IdIndex &idx,
|
EventStore::decryptEvent(const IdIndex &idx,
|
||||||
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e)
|
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e)
|
||||||
{
|
{
|
||||||
@ -641,57 +648,24 @@ EventStore::decryptEvent(const IdIndex &idx,
|
|||||||
index.session_id = e.content.session_id;
|
index.session_id = e.content.session_id;
|
||||||
index.sender_key = e.content.sender_key;
|
index.sender_key = e.content.sender_key;
|
||||||
|
|
||||||
auto asCacheEntry = [&idx](mtx::events::collections::TimelineEvents &&event) {
|
auto asCacheEntry = [&idx](olm::DecryptionResult &&event) {
|
||||||
auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(event));
|
auto event_ptr = new olm::DecryptionResult(std::move(event));
|
||||||
decryptedEvents_.insert(idx, event_ptr);
|
decryptedEvents_.insert(idx, event_ptr);
|
||||||
return event_ptr;
|
return event_ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto decryptionResult = olm::decryptEvent(index, e);
|
auto decryptionResult = olm::decryptEvent(index, e);
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (decryptionResult.error) {
|
if (decryptionResult.error) {
|
||||||
switch (*decryptionResult.error) {
|
switch (decryptionResult.error) {
|
||||||
case olm::DecryptionErrorCode::MissingSession:
|
case olm::DecryptionErrorCode::MissingSession:
|
||||||
case olm::DecryptionErrorCode::MissingSessionIndex: {
|
case olm::DecryptionErrorCode::MissingSessionIndex: {
|
||||||
if (decryptionResult.error == 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();
|
|
||||||
else
|
|
||||||
dummy.content.body =
|
|
||||||
tr("-- Encrypted Event (Key not valid for this index) --",
|
|
||||||
"Placeholder, when the message can't be decrypted with this "
|
|
||||||
"key since it is not valid for this index ")
|
|
||||||
.toStdString();
|
|
||||||
nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
|
nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
|
||||||
index.room_id,
|
index.room_id,
|
||||||
index.session_id,
|
index.session_id,
|
||||||
e.sender);
|
e.sender);
|
||||||
// we may not want to request keys during initial sync and such
|
|
||||||
if (suppressKeyRequests)
|
requestSession(e, false);
|
||||||
break;
|
|
||||||
// TODO: Check if this actually works and look in key backup
|
|
||||||
auto copy = e;
|
|
||||||
copy.room_id = room_id_;
|
|
||||||
if (pending_key_requests.count(e.content.session_id)) {
|
|
||||||
pending_key_requests.at(e.content.session_id)
|
|
||||||
.events.push_back(copy);
|
|
||||||
} else {
|
|
||||||
PendingKeyRequests request;
|
|
||||||
request.request_id =
|
|
||||||
"key_request." + http::client()->generate_txn_id();
|
|
||||||
request.events.push_back(copy);
|
|
||||||
olm::send_key_request_for(copy, request.request_id);
|
|
||||||
pending_key_requests[e.content.session_id] = request;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case olm::DecryptionErrorCode::DbError:
|
case olm::DecryptionErrorCode::DbError:
|
||||||
@ -701,12 +675,6 @@ EventStore::decryptEvent(const IdIndex &idx,
|
|||||||
index.session_id,
|
index.session_id,
|
||||||
index.sender_key,
|
index.sender_key,
|
||||||
decryptionResult.error_message.value_or(""));
|
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;
|
break;
|
||||||
case olm::DecryptionErrorCode::DecryptionFailed:
|
case olm::DecryptionErrorCode::DecryptionFailed:
|
||||||
nhlog::crypto()->critical(
|
nhlog::crypto()->critical(
|
||||||
@ -715,22 +683,8 @@ EventStore::decryptEvent(const IdIndex &idx,
|
|||||||
index.session_id,
|
index.session_id,
|
||||||
index.sender_key,
|
index.sender_key,
|
||||||
decryptionResult.error_message.value_or(""));
|
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;
|
break;
|
||||||
case olm::DecryptionErrorCode::ParsingFailed:
|
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;
|
break;
|
||||||
case olm::DecryptionErrorCode::ReplayAttack:
|
case olm::DecryptionErrorCode::ReplayAttack:
|
||||||
nhlog::crypto()->critical(
|
nhlog::crypto()->critical(
|
||||||
@ -738,85 +692,50 @@ EventStore::decryptEvent(const IdIndex &idx,
|
|||||||
e.event_id,
|
e.event_id,
|
||||||
room_id_,
|
room_id_,
|
||||||
index.sender_key);
|
index.sender_key);
|
||||||
dummy.content.body =
|
|
||||||
tr("-- Replay attack! This message index was reused! --").toStdString();
|
|
||||||
break;
|
break;
|
||||||
case olm::DecryptionErrorCode::UnknownFingerprint:
|
case olm::DecryptionErrorCode::NoError:
|
||||||
// TODO: don't fail, just show in UI.
|
// unreachable
|
||||||
nhlog::crypto()->critical("Message by unverified fingerprint {}",
|
|
||||||
index.sender_key);
|
|
||||||
dummy.content.body =
|
|
||||||
tr("-- Message by unverified device! --").toStdString();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return asCacheEntry(std::move(dummy));
|
return asCacheEntry(std::move(decryptionResult));
|
||||||
}
|
|
||||||
|
|
||||||
std::string msg_str;
|
|
||||||
try {
|
|
||||||
auto session = cache::client()->getInboundMegolmSession(index);
|
|
||||||
auto res =
|
|
||||||
olm::client()->decrypt_group_message(session.get(), 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...
|
|
||||||
mtx::common::add_relations(body["content"], e.content.relations);
|
|
||||||
|
|
||||||
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());
|
auto encInfo = mtx::accessors::file(decryptionResult.event.value());
|
||||||
if (encInfo)
|
if (encInfo)
|
||||||
emit newEncryptedImage(encInfo.value());
|
emit newEncryptedImage(encInfo.value());
|
||||||
|
|
||||||
return asCacheEntry(std::move(decryptionResult.event.value()));
|
return asCacheEntry(std::move(decryptionResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
EventStore::requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev,
|
||||||
|
bool manual)
|
||||||
|
{
|
||||||
|
// we may not want to request keys during initial sync and such
|
||||||
|
if (suppressKeyRequests)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO: Look in key backup
|
||||||
|
auto copy = ev;
|
||||||
|
copy.room_id = room_id_;
|
||||||
|
if (pending_key_requests.count(ev.content.session_id)) {
|
||||||
|
auto &r = pending_key_requests.at(ev.content.session_id);
|
||||||
|
r.events.push_back(copy);
|
||||||
|
|
||||||
|
// automatically request once every 10 min, manually every 1 min
|
||||||
|
qint64 delay = manual ? 60 : (60 * 10);
|
||||||
|
if (r.requested_at + delay < QDateTime::currentSecsSinceEpoch()) {
|
||||||
|
r.requested_at = QDateTime::currentSecsSinceEpoch();
|
||||||
|
olm::send_key_request_for(copy, r.request_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PendingKeyRequests request;
|
||||||
|
request.request_id = "key_request." + http::client()->generate_txn_id();
|
||||||
|
request.requested_at = QDateTime::currentSecsSinceEpoch();
|
||||||
|
request.events.push_back(copy);
|
||||||
|
olm::send_key_request_for(copy, request.request_id);
|
||||||
|
pending_key_requests[ev.content.session_id] = request;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -877,15 +796,56 @@ EventStore::get(std::string id, std::string_view related_to, bool decrypt, bool
|
|||||||
events_by_id_.insert(index, event_ptr);
|
events_by_id_.insert(index, event_ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decrypt)
|
if (decrypt) {
|
||||||
if (auto encrypted =
|
if (auto encrypted =
|
||||||
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||||
event_ptr))
|
event_ptr)) {
|
||||||
return decryptEvent(index, *encrypted);
|
auto decrypted = decryptEvent(index, *encrypted);
|
||||||
|
if (decrypted->event)
|
||||||
|
return &*decrypted->event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return event_ptr;
|
return event_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
olm::DecryptionErrorCode
|
||||||
|
EventStore::decryptionError(std::string id)
|
||||||
|
{
|
||||||
|
if (this->thread() != QThread::currentThread())
|
||||||
|
nhlog::db()->warn("{} called from a different thread!", __func__);
|
||||||
|
|
||||||
|
if (id.empty())
|
||||||
|
return olm::DecryptionErrorCode::NoError;
|
||||||
|
|
||||||
|
IdIndex index{room_id_, std::move(id)};
|
||||||
|
auto edits_ = edits(index.id);
|
||||||
|
if (!edits_.empty()) {
|
||||||
|
index.id = mtx::accessors::event_id(edits_.back());
|
||||||
|
auto event_ptr =
|
||||||
|
new mtx::events::collections::TimelineEvents(std::move(edits_.back()));
|
||||||
|
events_by_id_.insert(index, event_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto event_ptr = events_by_id_.object(index);
|
||||||
|
if (!event_ptr) {
|
||||||
|
auto event = cache::client()->getEvent(room_id_, index.id);
|
||||||
|
if (!event) {
|
||||||
|
return olm::DecryptionErrorCode::NoError;
|
||||||
|
}
|
||||||
|
event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data));
|
||||||
|
events_by_id_.insert(index, event_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto encrypted =
|
||||||
|
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(event_ptr)) {
|
||||||
|
auto decrypted = decryptEvent(index, *encrypted);
|
||||||
|
return decrypted->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return olm::DecryptionErrorCode::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
EventStore::fetchMore()
|
EventStore::fetchMore()
|
||||||
{
|
{
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
#include <mtx/responses/messages.hpp>
|
#include <mtx/responses/messages.hpp>
|
||||||
#include <mtx/responses/sync.hpp>
|
#include <mtx/responses/sync.hpp>
|
||||||
|
|
||||||
|
#include "Olm.h"
|
||||||
#include "Reaction.h"
|
#include "Reaction.h"
|
||||||
|
|
||||||
class EventStore : public QObject
|
class EventStore : public QObject
|
||||||
@ -78,6 +79,9 @@ public:
|
|||||||
mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true);
|
mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true);
|
||||||
|
|
||||||
QVariantList reactions(const std::string &event_id);
|
QVariantList reactions(const std::string &event_id);
|
||||||
|
olm::DecryptionErrorCode decryptionError(std::string id);
|
||||||
|
void requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev,
|
||||||
|
bool manual);
|
||||||
|
|
||||||
int size() const
|
int size() const
|
||||||
{
|
{
|
||||||
@ -119,7 +123,7 @@ public slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id);
|
std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id);
|
||||||
mtx::events::collections::TimelineEvents *decryptEvent(
|
olm::DecryptionResult *decryptEvent(
|
||||||
const IdIndex &idx,
|
const IdIndex &idx,
|
||||||
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
|
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
|
||||||
void handle_room_verification(mtx::events::collections::TimelineEvents event);
|
void handle_room_verification(mtx::events::collections::TimelineEvents event);
|
||||||
@ -129,7 +133,7 @@ private:
|
|||||||
uint64_t first = std::numeric_limits<uint64_t>::max(),
|
uint64_t first = std::numeric_limits<uint64_t>::max(),
|
||||||
last = std::numeric_limits<uint64_t>::max();
|
last = std::numeric_limits<uint64_t>::max();
|
||||||
|
|
||||||
static QCache<IdIndex, mtx::events::collections::TimelineEvents> decryptedEvents_;
|
static QCache<IdIndex, olm::DecryptionResult> decryptedEvents_;
|
||||||
static QCache<Index, mtx::events::collections::TimelineEvents> events_;
|
static QCache<Index, mtx::events::collections::TimelineEvents> events_;
|
||||||
static QCache<IdIndex, mtx::events::collections::TimelineEvents> events_by_id_;
|
static QCache<IdIndex, mtx::events::collections::TimelineEvents> events_by_id_;
|
||||||
|
|
||||||
@ -137,6 +141,7 @@ private:
|
|||||||
{
|
{
|
||||||
std::string request_id;
|
std::string request_id;
|
||||||
std::vector<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>> events;
|
std::vector<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>> events;
|
||||||
|
qint64 requested_at;
|
||||||
};
|
};
|
||||||
std::map<std::string, PendingKeyRequests> pending_key_requests;
|
std::map<std::string, PendingKeyRequests> pending_key_requests;
|
||||||
|
|
||||||
|
@ -452,6 +452,7 @@ TimelineModel::roleNames() const
|
|||||||
{IsEditable, "isEditable"},
|
{IsEditable, "isEditable"},
|
||||||
{IsEncrypted, "isEncrypted"},
|
{IsEncrypted, "isEncrypted"},
|
||||||
{Trustlevel, "trustlevel"},
|
{Trustlevel, "trustlevel"},
|
||||||
|
{EncryptionError, "encryptionError"},
|
||||||
{ReplyTo, "replyTo"},
|
{ReplyTo, "replyTo"},
|
||||||
{Reactions, "reactions"},
|
{Reactions, "reactions"},
|
||||||
{RoomId, "roomId"},
|
{RoomId, "roomId"},
|
||||||
@ -639,6 +640,9 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
|
|||||||
return crypto::Trust::Unverified;
|
return crypto::Trust::Unverified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case EncryptionError:
|
||||||
|
return events.decryptionError(event_id(event));
|
||||||
|
|
||||||
case ReplyTo:
|
case ReplyTo:
|
||||||
return QVariant(QString::fromStdString(relations(event).reply_to().value_or("")));
|
return QVariant(QString::fromStdString(relations(event).reply_to().value_or("")));
|
||||||
case Reactions: {
|
case Reactions: {
|
||||||
@ -690,6 +694,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
|
|||||||
m.insert(names[RoomName], data(event, static_cast<int>(RoomName)));
|
m.insert(names[RoomName], data(event, static_cast<int>(RoomName)));
|
||||||
m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic)));
|
m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic)));
|
||||||
m.insert(names[CallType], data(event, static_cast<int>(CallType)));
|
m.insert(names[CallType], data(event, static_cast<int>(CallType)));
|
||||||
|
m.insert(names[EncryptionError], data(event, static_cast<int>(EncryptionError)));
|
||||||
|
|
||||||
return QVariant(m);
|
return QVariant(m);
|
||||||
}
|
}
|
||||||
@ -1551,6 +1556,17 @@ TimelineModel::scrollTimerEvent()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineModel::requestKeyForEvent(QString id)
|
||||||
|
{
|
||||||
|
auto encrypted_event = events.get(id.toStdString(), "", false);
|
||||||
|
if (encrypted_event) {
|
||||||
|
if (auto ev = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||||
|
encrypted_event))
|
||||||
|
events.requestSession(*ev, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TimelineModel::copyLinkToEvent(QString eventId) const
|
TimelineModel::copyLinkToEvent(QString eventId) const
|
||||||
{
|
{
|
||||||
|
@ -212,6 +212,7 @@ public:
|
|||||||
IsEditable,
|
IsEditable,
|
||||||
IsEncrypted,
|
IsEncrypted,
|
||||||
Trustlevel,
|
Trustlevel,
|
||||||
|
EncryptionError,
|
||||||
ReplyTo,
|
ReplyTo,
|
||||||
Reactions,
|
Reactions,
|
||||||
RoomId,
|
RoomId,
|
||||||
@ -264,6 +265,8 @@ public:
|
|||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_INVOKABLE void requestKeyForEvent(QString id);
|
||||||
|
|
||||||
std::vector<::Reaction> reactions(const std::string &event_id)
|
std::vector<::Reaction> reactions(const std::string &event_id)
|
||||||
{
|
{
|
||||||
auto list = events.reactions(event_id);
|
auto list = events.reactions(event_id);
|
||||||
|
@ -157,6 +157,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
|||||||
0,
|
0,
|
||||||
"MtxEvent",
|
"MtxEvent",
|
||||||
"Can't instantiate enum!");
|
"Can't instantiate enum!");
|
||||||
|
qmlRegisterUncreatableMetaObject(
|
||||||
|
olm::staticMetaObject, "im.nheko", 1, 0, "Olm", "Can't instantiate enum!");
|
||||||
qmlRegisterUncreatableMetaObject(
|
qmlRegisterUncreatableMetaObject(
|
||||||
crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!");
|
crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!");
|
||||||
qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
|
qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
|
||||||
|
Loading…
Reference in New Issue
Block a user