forked from mirror/nheko
Merge pull request #270 from Chethan2k1/device-verification
Device verification and Cross-Signingmaster
commit
517a126a44
@ -0,0 +1,172 @@ |
|||||||
|
import QtQuick 2.9 |
||||||
|
import QtQuick.Controls 2.3 |
||||||
|
import QtQuick.Layouts 1.2 |
||||||
|
import QtQuick.Window 2.3 |
||||||
|
|
||||||
|
import im.nheko 1.0 |
||||||
|
|
||||||
|
import "./device-verification" |
||||||
|
|
||||||
|
ApplicationWindow{ |
||||||
|
property var profile |
||||||
|
|
||||||
|
id: userProfileDialog |
||||||
|
height: 650 |
||||||
|
width: 420 |
||||||
|
minimumHeight: 420 |
||||||
|
|
||||||
|
palette: colors |
||||||
|
|
||||||
|
Component { |
||||||
|
id: deviceVerificationDialog |
||||||
|
DeviceVerification {} |
||||||
|
} |
||||||
|
|
||||||
|
ColumnLayout{ |
||||||
|
id: contentL |
||||||
|
|
||||||
|
anchors.fill: parent |
||||||
|
anchors.margins: 10 |
||||||
|
|
||||||
|
spacing: 10 |
||||||
|
|
||||||
|
Avatar { |
||||||
|
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") |
||||||
|
height: 130 |
||||||
|
width: 130 |
||||||
|
displayName: profile.displayName |
||||||
|
userid: profile.userid |
||||||
|
Layout.alignment: Qt.AlignHCenter |
||||||
|
} |
||||||
|
|
||||||
|
Label { |
||||||
|
text: profile.displayName |
||||||
|
fontSizeMode: Text.HorizontalFit |
||||||
|
font.pixelSize: 20 |
||||||
|
color: TimelineManager.userColor(profile.userid, colors.window) |
||||||
|
font.bold: true |
||||||
|
Layout.alignment: Qt.AlignHCenter |
||||||
|
} |
||||||
|
|
||||||
|
MatrixText { |
||||||
|
text: profile.userid |
||||||
|
font.pixelSize: 15 |
||||||
|
Layout.alignment: Qt.AlignHCenter |
||||||
|
} |
||||||
|
|
||||||
|
Button { |
||||||
|
id: verifyUserButton |
||||||
|
text: qsTr("Verify") |
||||||
|
Layout.alignment: Qt.AlignHCenter |
||||||
|
enabled: !profile.isUserVerified |
||||||
|
visible: !profile.isUserVerified |
||||||
|
|
||||||
|
onClicked: profile.verify() |
||||||
|
} |
||||||
|
|
||||||
|
RowLayout { |
||||||
|
Layout.alignment: Qt.AlignHCenter |
||||||
|
spacing: 8 |
||||||
|
|
||||||
|
ImageButton { |
||||||
|
image:":/icons/icons/ui/do-not-disturb-rounded-sign.png" |
||||||
|
hoverEnabled: true |
||||||
|
ToolTip.visible: hovered |
||||||
|
ToolTip.text: qsTr("Ban the user") |
||||||
|
onClicked: profile.banUser() |
||||||
|
} |
||||||
|
// ImageButton{ |
||||||
|
// image:":/icons/icons/ui/volume-off-indicator.png" |
||||||
|
// Layout.margins: { |
||||||
|
// left: 5 |
||||||
|
// right: 5 |
||||||
|
// } |
||||||
|
// ToolTip.visible: hovered |
||||||
|
// ToolTip.text: qsTr("Ignore messages from this user") |
||||||
|
// onClicked : { |
||||||
|
// profile.ignoreUser() |
||||||
|
// } |
||||||
|
// } |
||||||
|
ImageButton{ |
||||||
|
image:":/icons/icons/ui/black-bubble-speech.png" |
||||||
|
hoverEnabled: true |
||||||
|
ToolTip.visible: hovered |
||||||
|
ToolTip.text: qsTr("Start a private chat") |
||||||
|
onClicked: profile.startChat() |
||||||
|
} |
||||||
|
ImageButton{ |
||||||
|
image:":/icons/icons/ui/round-remove-button.png" |
||||||
|
hoverEnabled: true |
||||||
|
ToolTip.visible: hovered |
||||||
|
ToolTip.text: qsTr("Kick the user") |
||||||
|
onClicked: profile.kickUser() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ListView{ |
||||||
|
id: devicelist |
||||||
|
|
||||||
|
Layout.fillHeight: true |
||||||
|
Layout.minimumHeight: 200 |
||||||
|
Layout.fillWidth: true |
||||||
|
|
||||||
|
clip: true |
||||||
|
spacing: 8 |
||||||
|
boundsBehavior: Flickable.StopAtBounds |
||||||
|
|
||||||
|
model: profile.deviceList |
||||||
|
|
||||||
|
delegate: RowLayout{ |
||||||
|
width: devicelist.width |
||||||
|
spacing: 4 |
||||||
|
|
||||||
|
ColumnLayout{ |
||||||
|
spacing: 0 |
||||||
|
Text{ |
||||||
|
Layout.fillWidth: true |
||||||
|
Layout.alignment: Qt.AlignLeft |
||||||
|
|
||||||
|
elide: Text.ElideRight |
||||||
|
font.bold: true |
||||||
|
color: colors.text |
||||||
|
text: model.deviceId |
||||||
|
} |
||||||
|
Text{ |
||||||
|
Layout.fillWidth: true |
||||||
|
Layout.alignment: Qt.AlignRight |
||||||
|
|
||||||
|
elide: Text.ElideRight |
||||||
|
color: colors.text |
||||||
|
text: model.deviceName |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Image{ |
||||||
|
Layout.preferredHeight: 16 |
||||||
|
Layout.preferredWidth: 16 |
||||||
|
|
||||||
|
source: ((model.verificationStatus == VerificationStatus.VERIFIED)?"image://colorimage/:/icons/icons/ui/lock.png?green": |
||||||
|
((model.verificationStatus == VerificationStatus.UNVERIFIED)?"image://colorimage/:/icons/icons/ui/unlock.png?yellow": |
||||||
|
"image://colorimage/:/icons/icons/ui/unlock.png?red")) |
||||||
|
} |
||||||
|
Button{ |
||||||
|
id: verifyButton |
||||||
|
text: (model.verificationStatus != VerificationStatus.VERIFIED)?"Verify":"Unverify" |
||||||
|
onClicked: { |
||||||
|
if(model.verificationStatus == VerificationStatus.VERIFIED){ |
||||||
|
profile.unverify(model.deviceId) |
||||||
|
}else{ |
||||||
|
profile.verify(model.deviceId); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
footer: DialogButtonBox { |
||||||
|
standardButtons: DialogButtonBox.Ok |
||||||
|
|
||||||
|
onAccepted: userProfileDialog.close() |
||||||
|
} |
||||||
|
} |
@ -1,10 +1,12 @@ |
|||||||
import ".." |
import ".." |
||||||
|
|
||||||
|
import im.nheko 1.0 |
||||||
|
|
||||||
MatrixText { |
MatrixText { |
||||||
property string formatted: model.data.formattedBody |
property string formatted: model.data.formattedBody |
||||||
text: "<style type=\"text/css\">a { color:"+colors.link+";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap'>") |
text: "<style type=\"text/css\">a { color:"+colors.link+";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap'>") |
||||||
width: parent ? parent.width : undefined |
width: parent ? parent.width : undefined |
||||||
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined |
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined |
||||||
clip: true |
clip: true |
||||||
font.pointSize: (settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? settings.fontSize * 3 : settings.fontSize |
font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize |
||||||
} |
} |
||||||
|
@ -0,0 +1,39 @@ |
|||||||
|
import QtQuick 2.3 |
||||||
|
import QtQuick.Controls 2.10 |
||||||
|
import QtQuick.Layouts 1.10 |
||||||
|
|
||||||
|
import im.nheko 1.0 |
||||||
|
|
||||||
|
Pane { |
||||||
|
property string title: qsTr("Awaiting Confirmation") |
||||||
|
ColumnLayout { |
||||||
|
spacing: 16 |
||||||
|
Label { |
||||||
|
Layout.maximumWidth: 400 |
||||||
|
Layout.fillHeight: true |
||||||
|
Layout.fillWidth: true |
||||||
|
wrapMode: Text.Wrap |
||||||
|
id: content |
||||||
|
text: qsTr("Waiting for other side to complete verification.") |
||||||
|
color:colors.text |
||||||
|
verticalAlignment: Text.AlignVCenter |
||||||
|
} |
||||||
|
BusyIndicator { |
||||||
|
Layout.alignment: Qt.AlignHCenter |
||||||
|
} |
||||||
|
RowLayout { |
||||||
|
Button { |
||||||
|
Layout.alignment: Qt.AlignLeft |
||||||
|
text: qsTr("Cancel") |
||||||
|
|
||||||
|
onClicked: { |
||||||
|
flow.cancel(); |
||||||
|
dialog.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
Item { |
||||||
|
Layout.fillWidth: true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,97 @@ |
|||||||
|
import QtQuick 2.10 |
||||||
|
import QtQuick.Controls 2.10 |
||||||
|
import QtQuick.Window 2.10 |
||||||
|
|
||||||
|
import im.nheko 1.0 |
||||||
|
|
||||||
|
ApplicationWindow { |
||||||
|
property var flow |
||||||
|
|
||||||
|
onClosing: TimelineManager.removeVerificationFlow(flow) |
||||||
|
|
||||||
|
title: stack.currentItem.title |
||||||
|
id: dialog |
||||||
|
|
||||||
|
flags: Qt.Dialog |
||||||
|
|
||||||
|
palette: colors |
||||||
|
|
||||||
|
height: stack.implicitHeight |
||||||
|
width: stack.implicitWidth |
||||||
|
|
||||||
|
StackView { |
||||||
|
id: stack |
||||||
|
initialItem: newVerificationRequest |
||||||
|
implicitWidth: currentItem.implicitWidth |
||||||
|
implicitHeight: currentItem.implicitHeight |
||||||
|
} |
||||||
|
|
||||||
|
Component{ |
||||||
|
id: newVerificationRequest |
||||||
|
NewVerificationRequest {} |
||||||
|
} |
||||||
|
|
||||||
|
Component { |
||||||
|
id: waiting |
||||||
|
Waiting {} |
||||||
|
} |
||||||
|
|
||||||
|
Component { |
||||||
|
id: success |
||||||
|
Success {} |
||||||
|
} |
||||||
|
|
||||||
|
Component { |
||||||
|
id: failed |
||||||
|
Failed {} |
||||||
|
} |
||||||
|
|
||||||
|
Component { |
||||||
|
id: digitVerification |
||||||
|
DigitVerification {} |
||||||
|
} |
||||||
|
|
||||||
|
Component { |
||||||
|
id: emojiVerification |
||||||
|
EmojiVerification {} |
||||||
|
} |
||||||
|
|
||||||
|
Item { |
||||||
|
state: flow.state |
||||||
|
|
||||||
|
states: [ |
||||||
|
State { |
||||||
|
name: "PromptStartVerification" |
||||||
|
StateChangeScript { script: stack.replace(newVerificationRequest) } |
||||||
|
}, |
||||||
|
State { |
||||||
|
name: "CompareEmoji" |
||||||
|
StateChangeScript { script: stack.replace(emojiVerification) } |
||||||
|
}, |
||||||
|
State { |
||||||
|
name: "CompareNumber" |
||||||
|
StateChangeScript { script: stack.replace(digitVerification) } |
||||||
|
}, |
||||||
|
State { |
||||||
|
name: "WaitingForKeys" |
||||||
|
StateChangeScript { script: stack.replace(waiting) } |
||||||
|
}, |
||||||
|
State { |
||||||
|
name: "WaitingForOtherToAccept" |
||||||
|
StateChangeScript { script: stack.replace(waiting) } |
||||||
|
}, |
||||||
|
State { |
||||||
|
name: "WaitingForMac" |
||||||
|
StateChangeScript { script: stack.replace(waiting) } |
||||||
|
}, |
||||||
|
State { |
||||||
|
name: "Success" |
||||||
|
StateChangeScript { script: stack.replace(success) } |
||||||
|
}, |
||||||
|
State { |
||||||
|
name: "Failed" |
||||||
|
StateChangeScript { script: stack.replace(failed); } |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
import QtQuick 2.3 |
||||||
|
import QtQuick.Controls 2.10 |
||||||
|
import QtQuick.Layouts 1.10 |
||||||
|
|
||||||
|
import im.nheko 1.0 |
||||||
|
|
||||||
|
Pane { |
||||||
|
property string title: qsTr("Verification Code") |
||||||
|
|
||||||
|
ColumnLayout { |
||||||
|
spacing: 16 |
||||||
|
Label { |
||||||
|
Layout.maximumWidth: 400 |
||||||
|
Layout.fillHeight: true |
||||||
|
Layout.fillWidth: true |
||||||
|
wrapMode: Text.Wrap |
||||||
|
text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!") |
||||||
|
color:colors.text |
||||||
|
verticalAlignment: Text.AlignVCenter |
||||||
|
} |
||||||
|
RowLayout { |
||||||
|
Layout.alignment: Qt.AlignHCenter |
||||||
|
Label { |
||||||
|
font.pixelSize: Qt.application.font.pixelSize * 2 |
||||||
|
text: flow.sasList[0] |
||||||
|
color:colors.text |
||||||
|
} |
||||||
|
Label { |
||||||
|
font.pixelSize: Qt.application.font.pixelSize * 2 |
||||||
|
text: flow.sasList[1] |
||||||
|
color:colors.text |
||||||
|
} |
||||||
|
Label { |
||||||
|
font.pixelSize: Qt.application.font.pixelSize * 2 |
||||||
|
text: flow.sasList[2] |
||||||
|
color:colors.text |
||||||
|
} |
||||||
|
} |
||||||
|
RowLayout { |
||||||
|
Button { |
||||||
|
Layout.alignment: Qt.AlignLeft |
||||||
|
text: qsTr("They do not match!") |
||||||
|
|
||||||
|
onClicked: { |
||||||
|
flow.cancel(); |
||||||
|
dialog.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
Item { |
||||||
|
Layout.fillWidth: true |
||||||
|
} |
||||||
|
Button { |
||||||
|
Layout.alignment: Qt.AlignRight |
||||||
|
text: qsTr("They match!") |
||||||
|
|
||||||
|
onClicked: flow.next(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
import QtQuick 2.3 |
||||||
|
import QtQuick.Controls 2.10 |
||||||
|
import QtQuick.Layouts 1.10 |
||||||
|
|
||||||
|
Rectangle { |
||||||
|
color: "red" |
||||||
|
implicitHeight: Qt.application.font.pixelSize * 4 |
||||||
|
implicitWidth: col.width |
||||||
|
height: Qt.application.font.pixelSize * 4 |
||||||
|
width: col.width |
||||||
|
ColumnLayout { |
||||||
|
id: col |
||||||
|
anchors.bottom: parent.bottom |
||||||
|
property var emoji: emojis.mapping[Math.floor(Math.random()*64)] |
||||||
|
Label { |
||||||
|
height: font.pixelSize * 2 |
||||||
|
Layout.alignment: Qt.AlignHCenter |
||||||
|
text: col.emoji.emoji |
||||||
|
font.pixelSize: Qt.application.font.pixelSize * 2 |
||||||
|
} |
||||||
|
Label { |
||||||
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom |
||||||
|
text: col.emoji.description |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
import QtQuick 2.3 |
||||||
|
import QtQuick.Controls 2.10 |
||||||
|
import QtQuick.Layouts 1.10 |
||||||
|
|
||||||
|
import im.nheko 1.0 |
||||||
|
|
||||||
|
Pane { |
||||||
|
property string title: qsTr("Verification failed") |
||||||
|
ColumnLayout { |
||||||
|
spacing: 16 |
||||||
|
Text { |
||||||
|
id: content |
||||||
|
|
||||||
|
Layout.maximumWidth: 400 |
||||||
|
Layout.fillHeight: true |
||||||
|
Layout.fillWidth: true |
||||||
|
|
||||||
|
wrapMode: Text.Wrap |
||||||
|
text: switch (flow.error) { |
||||||
|
case DeviceVerificationFlow.UnknownMethod: return qsTr("Other client does not support our verification protocol.") |
||||||
|
case DeviceVerificationFlow.MismatchedCommitment: |
||||||
|
case DeviceVerificationFlow.MismatchedSAS: |
||||||
|
case DeviceVerificationFlow.KeyMismatch: return qsTr("Key mismatch detected!") |
||||||
|
case DeviceVerificationFlow.Timeout: return qsTr("Device verification timed out.") |
||||||
|
case DeviceVerificationFlow.User: return qsTr("Other party canceled the verification.") |
||||||
|
case DeviceVerificationFlow.OutOfOrder: return qsTr("Device verification timed out.") |
||||||
|
default: return "Unknown verification error."; |
||||||
|
} |
||||||
|
color:colors.text |
||||||
|
verticalAlignment: Text.AlignVCenter |
||||||
|
} |
||||||
|
RowLayout { |
||||||
|
Item { |
||||||
|
Layout.fillWidth: true |
||||||
|
} |
||||||
|
Button { |
||||||
|
Layout.alignment: Qt.AlignRight |
||||||
|
text: qsTr("Close") |
||||||
|
|
||||||
|
onClicked: dialog.close() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
import QtQuick 2.3 |
||||||
|
import QtQuick.Controls 2.10 |
||||||
|
import QtQuick.Layouts 1.10 |
||||||
|
|
||||||
|
import im.nheko 1.0 |
||||||
|
|
||||||
|
Pane { |
||||||
|
property string title: flow.sender ? qsTr("Send Device Verification Request") : qsTr("Recieved Device Verification Request") |
||||||
|
|
||||||
|
ColumnLayout { |
||||||
|
spacing: 16 |
||||||
|
Label { |
||||||
|
Layout.maximumWidth: 400 |
||||||
|
Layout.fillHeight: true |
||||||
|
Layout.fillWidth: true |
||||||
|
wrapMode: Text.Wrap |
||||||
|
text: flow.sender ? |
||||||
|
qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device.") |
||||||
|
: qsTr("The device was requested to be verified") |
||||||
|
color:colors.text |
||||||
|
verticalAlignment: Text.AlignVCenter |
||||||
|
} |
||||||
|
RowLayout { |
||||||
|
Button { |
||||||
|
Layout.alignment: Qt.AlignLeft |
||||||
|
text: flow.sender ? qsTr("Cancel") : qsTr("Deny") |
||||||
|
|
||||||
|
onClicked: { |
||||||
|
flow.cancel(); |
||||||
|
dialog.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
Item { |
||||||
|
Layout.fillWidth: true |
||||||
|
} |
||||||
|
Button { |
||||||
|
Layout.alignment: Qt.AlignRight |
||||||
|
text: flow.sender ? qsTr("Start verification") : qsTr("Accept") |
||||||
|
|
||||||
|
onClicked: flow.next(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
import QtQuick 2.3 |
||||||
|
import QtQuick.Controls 2.10 |
||||||
|
import QtQuick.Layouts 1.10 |
||||||
|
|
||||||
|
Pane { |
||||||
|
property string title: qsTr("Successful Verification") |
||||||
|
ColumnLayout { |
||||||
|
spacing: 16 |
||||||
|
Label { |
||||||
|
Layout.maximumWidth: 400 |
||||||
|
Layout.fillHeight: true |
||||||
|
Layout.fillWidth: true |
||||||
|
wrapMode: Text.Wrap |
||||||
|
id: content |
||||||
|
text: qsTr("Verification successful! Both sides verified their devices!") |
||||||
|
color:colors.text |
||||||
|
verticalAlignment: Text.AlignVCenter |
||||||
|
} |
||||||
|
RowLayout { |
||||||
|
Item { |
||||||
|
Layout.fillWidth: true |
||||||
|
} |
||||||
|
Button { |
||||||
|
Layout.alignment: Qt.AlignRight |
||||||
|
text: qsTr("Close") |
||||||
|
|
||||||
|
onClicked: dialog.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
import QtQuick 2.3 |
||||||
|
import QtQuick.Controls 2.10 |
||||||
|
import QtQuick.Layouts 1.10 |
||||||
|
|
||||||
|
import im.nheko 1.0 |
||||||
|
|
||||||
|
Pane { |
||||||
|
property string title: qsTr("Waiting for other party") |
||||||
|
ColumnLayout { |
||||||
|
spacing: 16 |
||||||
|
Label { |
||||||
|
Layout.maximumWidth: 400 |
||||||
|
Layout.fillHeight: true |
||||||
|
Layout.fillWidth: true |
||||||
|
wrapMode: Text.Wrap |
||||||
|
id: content |
||||||
|
text: switch (flow.state) { |
||||||
|
case "WaitingForOtherToAccept": return qsTr("Waiting for other side to accept the verification request.") |
||||||
|
case "WaitingForKeys": return qsTr("Waiting for other side to continue the verification request.") |
||||||
|
case "WaitingForMac": return qsTr("Waiting for other side to complete the verification request.") |
||||||
|
} |
||||||
|
|
||||||
|
color: colors.text |
||||||
|
verticalAlignment: Text.AlignVCenter |
||||||
|
} |
||||||
|
BusyIndicator { |
||||||
|
Layout.alignment: Qt.AlignHCenter |
||||||
|
palette: colors |
||||||
|
} |
||||||
|
RowLayout { |
||||||
|
Button { |
||||||
|
Layout.alignment: Qt.AlignLeft |
||||||
|
text: qsTr("Cancel") |
||||||
|
|
||||||
|
onClicked: { |
||||||
|
flow.cancel(); |
||||||
|
dialog.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
Item { |
||||||
|
Layout.fillWidth: true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,794 @@ |
|||||||
|
#include "DeviceVerificationFlow.h" |
||||||
|
|
||||||
|
#include "Cache.h" |
||||||
|
#include "ChatPage.h" |
||||||
|
#include "Logging.h" |
||||||
|
#include "timeline/TimelineModel.h" |
||||||
|
|
||||||
|
#include <QDateTime> |
||||||
|
#include <QTimer> |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes
|
||||||
|
|
||||||
|
namespace msgs = mtx::events::msg; |
||||||
|
|
||||||
|
static mtx::events::msg::KeyVerificationMac |
||||||
|
key_verification_mac(mtx::crypto::SAS *sas, |
||||||
|
mtx::identifiers::User sender, |
||||||
|
const std::string &senderDevice, |
||||||
|
mtx::identifiers::User receiver, |
||||||
|
const std::string &receiverDevice, |
||||||
|
const std::string &transactionId, |
||||||
|
std::map<std::string, std::string> keys); |
||||||
|
|
||||||
|
DeviceVerificationFlow::DeviceVerificationFlow(QObject *, |
||||||
|
DeviceVerificationFlow::Type flow_type, |
||||||
|
TimelineModel *model, |
||||||
|
QString userID, |
||||||
|
QString deviceId_) |
||||||
|
: sender(false) |
||||||
|
, type(flow_type) |
||||||
|
, deviceId(deviceId_) |
||||||
|
, model_(model) |
||||||
|
{ |
||||||
|
timeout = new QTimer(this); |
||||||
|
timeout->setSingleShot(true); |
||||||
|
this->sas = olm::client()->sas_init(); |
||||||
|
this->isMacVerified = false; |
||||||
|
|
||||||
|
auto user_id = userID.toStdString(); |
||||||
|
this->toClient = mtx::identifiers::parse<mtx::identifiers::User>(user_id); |
||||||
|
ChatPage::instance()->query_keys( |
||||||
|
user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) { |
||||||
|
if (err) { |
||||||
|
nhlog::net()->warn("failed to query device keys: {},{}", |
||||||
|
err->matrix_error.errcode, |
||||||
|
static_cast<int>(err->status_code)); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (!this->deviceId.isEmpty() && |
||||||
|
(res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) { |
||||||
|
nhlog::net()->warn("no devices retrieved {}", user_id); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
this->their_keys = res; |
||||||
|
}); |
||||||
|
|
||||||
|
ChatPage::instance()->query_keys( |
||||||
|
http::client()->user_id().to_string(), |
||||||
|
[this](const UserKeyCache &res, mtx::http::RequestErr err) { |
||||||
|
if (err) { |
||||||
|
nhlog::net()->warn("failed to query device keys: {},{}", |
||||||
|
err->matrix_error.errcode, |
||||||
|
static_cast<int>(err->status_code)); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (res.master_keys.keys.empty()) |
||||||
|
return; |
||||||
|
|
||||||
|
if (auto status = |
||||||
|
cache::verificationStatus(http::client()->user_id().to_string()); |
||||||
|
status && status->user_verified) |
||||||
|
this->our_trusted_master_key = res.master_keys.keys.begin()->second; |
||||||
|
}); |
||||||
|
|
||||||
|
if (model) { |
||||||
|
connect(this->model_, |
||||||
|
&TimelineModel::updateFlowEventId, |
||||||
|
this, |
||||||
|
[this](std::string event_id_) { |
||||||
|
this->relation.rel_type = mtx::common::RelationType::Reference; |
||||||
|
this->relation.event_id = event_id_; |
||||||
|
this->transaction_id = event_id_; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
connect(timeout, &QTimer::timeout, this, [this]() { |
||||||
|
if (state_ != Success && state_ != Failed) |
||||||
|
this->cancelVerification(DeviceVerificationFlow::Error::Timeout); |
||||||
|
}); |
||||||
|
|
||||||
|
connect(ChatPage::instance(), |
||||||
|
&ChatPage::receivedDeviceVerificationStart, |
||||||
|
this, |
||||||
|
&DeviceVerificationFlow::handleStartMessage); |
||||||
|
connect(ChatPage::instance(), |
||||||
|
&ChatPage::receivedDeviceVerificationAccept, |
||||||
|
this, |
||||||
|
[this](const mtx::events::msg::KeyVerificationAccept &msg) { |
||||||
|
if (msg.transaction_id.has_value()) { |
||||||
|
if (msg.transaction_id.value() != this->transaction_id) |
||||||
|
return; |
||||||
|
} else if (msg.relates_to.has_value()) { |
||||||
|
if (msg.relates_to.value().event_id != this->relation.event_id) |
||||||
|
return; |
||||||
|
} |
||||||
|
if ((msg.key_agreement_protocol == "curve25519-hkdf-sha256") && |
||||||
|
(msg.hash == "sha256") && |
||||||
|
(msg.message_authentication_code == "hkdf-hmac-sha256")) { |
||||||
|
this->commitment = msg.commitment; |
||||||
|
if (std::find(msg.short_authentication_string.begin(), |
||||||
|
msg.short_authentication_string.end(), |
||||||
|
mtx::events::msg::SASMethods::Emoji) != |
||||||
|
msg.short_authentication_string.end()) { |
||||||
|
this->method = mtx::events::msg::SASMethods::Emoji; |
||||||
|
} else { |
||||||
|
this->method = mtx::events::msg::SASMethods::Decimal; |
||||||
|
} |
||||||
|
this->mac_method = msg.message_authentication_code; |
||||||
|
this->sendVerificationKey(); |
||||||
|
} else { |
||||||
|
this->cancelVerification( |
||||||
|
DeviceVerificationFlow::Error::UnknownMethod); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
connect(ChatPage::instance(), |
||||||
|
&ChatPage::receivedDeviceVerificationCancel, |
||||||
|
this, |
||||||
|
[this](const mtx::events::msg::KeyVerificationCancel &msg) { |
||||||
|
if (msg.transaction_id.has_value()) { |
||||||
|
if (msg.transaction_id.value() != this->transaction_id) |
||||||
|
return; |
||||||
|
} else if (msg.relates_to.has_value()) { |
||||||
|
if (msg.relates_to.value().event_id != this->relation.event_id) |
||||||
|
return; |
||||||
|
} |
||||||
|
error_ = User; |
||||||
|
emit errorChanged(); |
||||||
|
setState(Failed); |
||||||
|
}); |
||||||
|
|
||||||
|
connect(ChatPage::instance(), |
||||||
|
&ChatPage::receivedDeviceVerificationKey, |
||||||
|
this, |
||||||
|
[this](const mtx::events::msg::KeyVerificationKey &msg) { |
||||||
|
if (msg.transaction_id.has_value()) { |
||||||
|
if (msg.transaction_id.value() != this->transaction_id) |
||||||
|
return; |
||||||
|
} else if (msg.relates_to.has_value()) { |
||||||
|
if (msg.relates_to.value().event_id != this->relation.event_id) |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (sender) { |
||||||
|
if (state_ != WaitingForOtherToAccept) { |
||||||
|
this->cancelVerification(OutOfOrder); |
||||||
|
return; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (state_ != WaitingForKeys) { |
||||||
|
this->cancelVerification(OutOfOrder); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this->sas->set_their_key(msg.key); |
||||||
|
std::string info; |
||||||
|
if (this->sender == true) { |
||||||
|
info = "MATRIX_KEY_VERIFICATION_SAS|" + |
||||||
|
http::client()->user_id().to_string() + "|" + |
||||||
|
http::client()->device_id() + "|" + this->sas->public_key() + |
||||||
|
"|" + this->toClient.to_string() + "|" + |
||||||
|
this->deviceId.toStdString() + "|" + msg.key + "|" + |
||||||
|
this->transaction_id; |
||||||
|
} else { |
||||||
|
info = "MATRIX_KEY_VERIFICATION_SAS|" + this->toClient.to_string() + |
||||||
|
"|" + this->deviceId.toStdString() + "|" + msg.key + "|" + |
||||||
|
http::client()->user_id().to_string() + "|" + |
||||||
|
http::client()->device_id() + "|" + this->sas->public_key() + |
||||||
|
"|" + this->transaction_id; |
||||||
|
} |
||||||
|
|
||||||
|
nhlog::ui()->info("Info is: '{}'", info); |
||||||
|
|
||||||
|
if (this->sender == false) { |
||||||
|
this->sendVerificationKey(); |
||||||
|
} else { |
||||||
|
if (this->commitment != |
||||||
|
mtx::crypto::bin2base64_unpadded( |
||||||
|
mtx::crypto::sha256(msg.key + this->canonical_json.dump()))) { |
||||||
|
this->cancelVerification( |
||||||
|
DeviceVerificationFlow::Error::MismatchedCommitment); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (this->method == mtx::events::msg::SASMethods::Emoji) { |
||||||
|
this->sasList = this->sas->generate_bytes_emoji(info); |
||||||
|
setState(CompareEmoji); |
||||||
|
} else if (this->method == mtx::events::msg::SASMethods::Decimal) { |
||||||
|
this->sasList = this->sas->generate_bytes_decimal(info); |
||||||
|
setState(CompareNumber); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
connect( |
||||||
|
ChatPage::instance(), |
||||||
|
&ChatPage::receivedDeviceVerificationMac, |
||||||
|
this, |
||||||
|
[this](const mtx::events::msg::KeyVerificationMac &msg) { |
||||||
|
if (msg.transaction_id.has_value()) { |
||||||
|
if (msg.transaction_id.value() != this->transaction_id) |
||||||
|
return; |
||||||
|
} else if (msg.relates_to.has_value()) { |
||||||
|
if (msg.relates_to.value().event_id != this->relation.event_id) |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
std::map<std::string, std::string> key_list; |
||||||
|
std::string key_string; |
||||||
|
for (const auto &mac : msg.mac) { |
||||||
|
for (const auto &[deviceid, key] : their_keys.device_keys) { |
||||||
|
(void)deviceid; |
||||||
|
if (key.keys.count(mac.first)) |
||||||
|
key_list[mac.first] = key.keys.at(mac.first); |
||||||
|
} |
||||||
|
|
||||||
|
if (their_keys.master_keys.keys.count(mac.first)) |
||||||
|
key_list[mac.first] = their_keys.master_keys.keys[mac.first]; |
||||||
|
if (their_keys.user_signing_keys.keys.count(mac.first)) |
||||||
|
key_list[mac.first] = |
||||||
|
their_keys.user_signing_keys.keys[mac.first]; |
||||||
|
if (their_keys.self_signing_keys.keys.count(mac.first)) |
||||||
|
key_list[mac.first] = |
||||||
|
their_keys.self_signing_keys.keys[mac.first]; |
||||||
|
} |
||||||
|
auto macs = key_verification_mac(sas.get(), |
||||||
|
toClient, |
||||||
|
this->deviceId.toStdString(), |
||||||
|
http::client()->user_id(), |
||||||
|
http::client()->device_id(), |
||||||
|
this->transaction_id, |
||||||
|
key_list); |
||||||
|
|
||||||
|
for (const auto &[key, mac] : macs.mac) { |
||||||
|
if (mac != msg.mac.at(key)) { |
||||||
|
this->cancelVerification( |
||||||
|
DeviceVerificationFlow::Error::KeyMismatch); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (msg.keys == macs.keys) { |
||||||
|
mtx::requests::KeySignaturesUpload req; |
||||||
|
if (utils::localUser().toStdString() == this->toClient.to_string()) { |
||||||
|
// self verification, sign master key with device key, if we
|
||||||
|
// verified it
|
||||||
|
for (const auto &mac : msg.mac) { |
||||||
|
if (their_keys.master_keys.keys.count(mac.first)) { |
||||||
|
json j = their_keys.master_keys; |
||||||
|
j.erase("signatures"); |
||||||
|
j.erase("unsigned"); |
||||||
|
mtx::crypto::CrossSigningKeys master_key = j; |
||||||
|
master_key |
||||||
|
.signatures[utils::localUser().toStdString()] |
||||||
|
["ed25519:" + |
||||||
|
http::client()->device_id()] = |
||||||
|
olm::client()->sign_message(j.dump()); |
||||||
|
req.signatures[utils::localUser().toStdString()] |
||||||
|
[master_key.keys.at(mac.first)] = |
||||||
|
master_key; |
||||||
|
} |
||||||
|
} |
||||||
|
// TODO(Nico): Sign their device key with self signing key
|
||||||
|
} else { |
||||||
|
// TODO(Nico): Sign their master key with user signing key
|
||||||
|
} |
||||||
|
|
||||||
|
if (!req.signatures.empty()) { |
||||||
|
http::client()->keys_signatures_upload( |
||||||
|
req, |
||||||
|
[](const mtx::responses::KeySignaturesUpload &res, |
||||||
|
mtx::http::RequestErr err) { |
||||||
|
if (err) { |
||||||
|
nhlog::net()->error( |
||||||
|
"failed to upload signatures: {},{}", |
||||||
|
err->matrix_error.errcode, |
||||||
|
static_cast<int>(err->status_code)); |
||||||
|
} |
||||||
|
|
||||||
|
for (const auto &[user_id, tmp] : res.errors) |
||||||
|
for (const auto &[key_id, e] : tmp) |
||||||
|
nhlog::net()->error( |
||||||
|
"signature error for user {} and key " |
||||||
|
"id {}: {}, {}", |
||||||
|
user_id, |
||||||
|
key_id, |
||||||
|
e.errcode, |
||||||
|
e.error); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
this->isMacVerified = true; |
||||||
|
this->acceptDevice(); |
||||||
|
} else { |
||||||
|
this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
connect(ChatPage::instance(), |
||||||
|
&ChatPage::receivedDeviceVerificationReady, |
||||||
|
this, |
||||||
|
[this](const mtx::events::msg::KeyVerificationReady &msg) { |
||||||
|
if (!sender) { |
||||||
|
if (msg.from_device != http::client()->device_id()) { |
||||||
|
error_ = User; |
||||||
|
emit errorChanged(); |
||||||
|
setState(Failed); |
||||||
|
} |
||||||
|
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (msg.transaction_id.has_value()) { |
||||||
|
if (msg.transaction_id.value() != this->transaction_id) |
||||||
|
return; |
||||||
|
} else if ((msg.relates_to.has_value() && sender)) { |
||||||
|
if (msg.relates_to.value().event_id != this->relation.event_id) |
||||||
|
return; |
||||||
|
else { |
||||||
|
this->deviceId = QString::fromStdString(msg.from_device); |
||||||
|
} |
||||||
|
} |
||||||
|
this->startVerificationRequest(); |
||||||
|
}); |
||||||
|
|
||||||
|
connect(ChatPage::instance(), |
||||||
|
&ChatPage::receivedDeviceVerificationDone, |
||||||
|
this, |
||||||
|
[this](const mtx::events::msg::KeyVerificationDone &msg) { |
||||||
|
if (msg.transaction_id.has_value()) { |
||||||
|
if (msg.transaction_id.value() != this->transaction_id) |
||||||
|
return; |
||||||
|
} else if (msg.relates_to.has_value()) { |
||||||
|
if (msg.relates_to.value().event_id != this->relation.event_id) |
||||||
|
return; |
||||||
|
} |
||||||
|
nhlog::ui()->info("Flow done on other side"); |
||||||
|
}); |
||||||
|
|
||||||
|
timeout->start(TIMEOUT); |
||||||
|
} |
||||||
|
|
||||||
|
QString |
||||||
|
DeviceVerificationFlow::state() |
||||||
|
{ |
||||||
|
switch (state_) { |
||||||
|
case PromptStartVerification: |
||||||
|
return "PromptStartVerification"; |
||||||
|
case CompareEmoji: |
||||||
|
return "CompareEmoji"; |
||||||
|
case CompareNumber: |
||||||
|
return "CompareNumber"; |
||||||
|
case WaitingForKeys: |
||||||
|
return "WaitingForKeys"; |
||||||
|
case WaitingForOtherToAccept: |
||||||
|
return "WaitingForOtherToAccept"; |
||||||
|
case WaitingForMac: |
||||||
|
return "WaitingForMac"; |
||||||
|
case Success: |
||||||
|
return "Success"; |
||||||
|
case Failed: |
||||||
|
return "Failed"; |
||||||
|
default: |
||||||
|
return ""; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::next() |
||||||
|
{ |
||||||
|
if (sender) { |
||||||
|
switch (state_) { |
||||||
|
case PromptStartVerification: |
||||||
|
sendVerificationRequest(); |
||||||
|
break; |
||||||
|
case CompareEmoji: |
||||||
|
case CompareNumber: |
||||||
|
sendVerificationMac(); |
||||||
|
break; |
||||||
|
case WaitingForKeys: |
||||||
|
case WaitingForOtherToAccept: |
||||||
|
case WaitingForMac: |
||||||
|
case Success: |
||||||
|
case Failed: |
||||||
|
nhlog::db()->error("verification: Invalid state transition!"); |
||||||
|
break; |
||||||
|
} |
||||||
|
} else { |
||||||
|
switch (state_) { |
||||||
|
case PromptStartVerification: |
||||||
|
if (canonical_json.is_null()) |
||||||
|
sendVerificationReady(); |
||||||
|
else // legacy path without request and ready
|
||||||
|
acceptVerificationRequest(); |
||||||
|
break; |
||||||
|
case CompareEmoji: |
||||||
|
[[fallthrough]]; |
||||||
|
case CompareNumber: |
||||||
|
sendVerificationMac(); |
||||||
|
break; |
||||||
|
case WaitingForKeys: |
||||||
|
case WaitingForOtherToAccept: |
||||||
|
case WaitingForMac: |
||||||
|
case Success: |
||||||
|
case Failed: |
||||||
|
nhlog::db()->error("verification: Invalid state transition!"); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
QString |
||||||
|
DeviceVerificationFlow::getUserId() |
||||||
|
{ |
||||||
|
return QString::fromStdString(this->toClient.to_string()); |
||||||
|
} |
||||||
|
|
||||||
|
QString |
||||||
|
DeviceVerificationFlow::getDeviceId() |
||||||
|
{ |
||||||
|
return this->deviceId; |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
DeviceVerificationFlow::getSender() |
||||||
|
{ |
||||||
|
return this->sender; |
||||||
|
} |
||||||
|
|
||||||
|
std::vector<int> |
||||||
|
DeviceVerificationFlow::getSasList() |
||||||
|
{ |
||||||
|
return this->sasList; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::setEventId(std::string event_id_) |
||||||
|
{ |
||||||
|
this->relation.rel_type = mtx::common::RelationType::Reference; |
||||||
|
this->relation.event_id = event_id_; |
||||||
|
this->transaction_id = event_id_; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, |
||||||
|
std::string) |
||||||
|
{ |
||||||
|
if (msg.transaction_id.has_value()) { |
||||||
|
if (msg.transaction_id.value() != this->transaction_id) |
||||||
|
return; |
||||||
|
} else if (msg.relates_to.has_value()) { |
||||||
|
if (msg.relates_to.value().event_id != this->relation.event_id) |
||||||
|
return; |
||||||
|
} |
||||||
|
if ((std::find(msg.key_agreement_protocols.begin(), |
||||||
|
msg.key_agreement_protocols.end(), |
||||||
|
"curve25519-hkdf-sha256") != msg.key_agreement_protocols.end()) && |
||||||
|
(std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") != msg.hashes.end()) && |
||||||
|
(std::find(msg.message_authentication_codes.begin(), |
||||||
|
msg.message_authentication_codes.end(), |
||||||
|
"hkdf-hmac-sha256") != msg.message_authentication_codes.end())) { |
||||||
|
if (std::find(msg.short_authentication_string.begin(), |
||||||
|
msg.short_authentication_string.end(), |
||||||
|
mtx::events::msg::SASMethods::Emoji) != |
||||||
|
msg.short_authentication_string.end()) { |
||||||
|
this->method = mtx::events::msg::SASMethods::Emoji; |
||||||
|
} else if (std::find(msg.short_authentication_string.begin(), |
||||||
|
msg.short_authentication_string.end(), |
||||||
|
mtx::events::msg::SASMethods::Decimal) != |
||||||
|
msg.short_authentication_string.end()) { |
||||||
|
this->method = mtx::events::msg::SASMethods::Decimal; |
||||||
|
} else { |
||||||
|
this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (!sender) |
||||||
|
this->canonical_json = nlohmann::json(msg); |
||||||
|
else { |
||||||
|
if (utils::localUser().toStdString() < this->toClient.to_string()) { |
||||||
|
this->canonical_json = nlohmann::json(msg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (state_ != PromptStartVerification) |
||||||
|
this->acceptVerificationRequest(); |
||||||
|
} else { |
||||||
|
this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//! accepts a verification
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::acceptVerificationRequest() |
||||||
|
{ |
||||||
|
mtx::events::msg::KeyVerificationAccept req; |
||||||
|
|
||||||
|
req.method = mtx::events::msg::VerificationMethods::SASv1; |
||||||
|
req.key_agreement_protocol = "curve25519-hkdf-sha256"; |
||||||
|
req.hash = "sha256"; |
||||||
|
req.message_authentication_code = "hkdf-hmac-sha256"; |
||||||
|
if (this->method == mtx::events::msg::SASMethods::Emoji) |
||||||
|
req.short_authentication_string = {mtx::events::msg::SASMethods::Emoji}; |
||||||
|
else if (this->method == mtx::events::msg::SASMethods::Decimal) |
||||||
|
req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal}; |
||||||
|
req.commitment = mtx::crypto::bin2base64_unpadded( |
||||||
|
mtx::crypto::sha256(this->sas->public_key() + this->canonical_json.dump())); |
||||||
|
|
||||||
|
send(req); |
||||||
|
setState(WaitingForKeys); |
||||||
|
} |
||||||
|
//! responds verification request
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::sendVerificationReady() |
||||||
|
{ |
||||||
|
mtx::events::msg::KeyVerificationReady req; |
||||||
|
|
||||||
|
req.from_device = http::client()->device_id(); |
||||||
|
req.methods = {mtx::events::msg::VerificationMethods::SASv1}; |
||||||
|
|
||||||
|
send(req); |
||||||
|
setState(WaitingForKeys); |
||||||
|
} |
||||||
|
//! accepts a verification
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::sendVerificationDone() |
||||||
|
{ |
||||||
|
mtx::events::msg::KeyVerificationDone req; |
||||||
|
|
||||||
|
send(req); |
||||||
|
} |
||||||
|
//! starts the verification flow
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::startVerificationRequest() |
||||||
|
{ |
||||||
|
mtx::events::msg::KeyVerificationStart req; |
||||||
|
|
||||||
|
req.from_device = http::client()->device_id(); |
||||||
|
req.method = mtx::events::msg::VerificationMethods::SASv1; |
||||||
|
req.key_agreement_protocols = {"curve25519-hkdf-sha256"}; |
||||||
|
req.hashes = {"sha256"}; |
||||||
|
req.message_authentication_codes = {"hkdf-hmac-sha256"}; |
||||||
|
req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal, |
||||||
|
mtx::events::msg::SASMethods::Emoji}; |
||||||
|
|
||||||
|
if (this->type == DeviceVerificationFlow::Type::ToDevice) { |
||||||
|
mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationStart> body; |
||||||
|
req.transaction_id = this->transaction_id; |
||||||
|
this->canonical_json = nlohmann::json(req); |
||||||
|
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { |
||||||
|
req.relates_to = this->relation; |
||||||
|
this->canonical_json = nlohmann::json(req); |
||||||
|
} |
||||||
|
send(req); |
||||||
|
setState(WaitingForOtherToAccept); |
||||||
|
} |
||||||
|
//! sends a verification request
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::sendVerificationRequest() |
||||||
|
{ |
||||||
|
mtx::events::msg::KeyVerificationRequest req; |
||||||
|
|
||||||
|
req.from_device = http::client()->device_id(); |
||||||
|
req.methods = {mtx::events::msg::VerificationMethods::SASv1}; |
||||||
|
|
||||||
|
if (this->type == DeviceVerificationFlow::Type::ToDevice) { |
||||||
|
QDateTime currentTime = QDateTime::currentDateTimeUtc(); |
||||||
|
|
||||||
|
req.timestamp = (uint64_t)currentTime.toMSecsSinceEpoch(); |
||||||
|
|
||||||
|
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { |
||||||
|
req.to = this->toClient.to_string(); |
||||||
|
req.msgtype = "m.key.verification.request"; |
||||||
|
req.body = "User is requesting to verify keys with you. However, your client does " |
||||||
|
"not support this method, so you will need to use the legacy method of " |
||||||
|
"key verification."; |
||||||
|
} |
||||||
|
|
||||||
|
send(req); |
||||||
|
setState(WaitingForOtherToAccept); |
||||||
|
} |
||||||
|
//! cancels a verification flow
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_code) |
||||||
|
{ |
||||||
|
mtx::events::msg::KeyVerificationCancel req; |
||||||
|
|
||||||
|
if (error_code == DeviceVerificationFlow::Error::UnknownMethod) { |
||||||
|
req.code = "m.unknown_method"; |
||||||
|
req.reason = "unknown method received"; |
||||||
|
} else if (error_code == DeviceVerificationFlow::Error::MismatchedCommitment) { |
||||||
|
req.code = "m.mismatched_commitment"; |
||||||
|
req.reason = "commitment didn't match"; |
||||||
|
} else if (error_code == DeviceVerificationFlow::Error::MismatchedSAS) { |
||||||
|
req.code = "m.mismatched_sas"; |
||||||
|
req.reason = "sas didn't match"; |
||||||
|
} else if (error_code == DeviceVerificationFlow::Error::KeyMismatch) { |
||||||
|
req.code = "m.key_match"; |
||||||
|
req.reason = "keys did not match"; |
||||||
|
} else if (error_code == DeviceVerificationFlow::Error::Timeout) { |
||||||
|
req.code = "m.timeout"; |
||||||
|
req.reason = "timed out"; |
||||||
|
} else if (error_code == DeviceVerificationFlow::Error::User) { |
||||||
|
req.code = "m.user"; |
||||||
|
req.reason = "user cancelled the verification"; |
||||||
|
} else if (error_code == DeviceVerificationFlow::Error::OutOfOrder) { |
||||||
|
req.code = "m.unexpected_message"; |
||||||
|
req.reason = "received messages out of order"; |
||||||
|
} |
||||||
|
|
||||||
|
this->error_ = error_code; |
||||||
|
emit errorChanged(); |
||||||
|
this->setState(Failed); |
||||||
|
|
||||||
|
send(req); |
||||||
|
} |
||||||
|
//! sends the verification key
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::sendVerificationKey() |
||||||
|
{ |
||||||
|
mtx::events::msg::KeyVerificationKey req; |
||||||
|
|
||||||
|
req.key = this->sas->public_key(); |
||||||
|
|
||||||
|
send(req); |
||||||
|
} |
||||||
|
|
||||||
|
mtx::events::msg::KeyVerificationMac |
||||||
|
key_verification_mac(mtx::crypto::SAS *sas, |
||||||
|
mtx::identifiers::User sender, |
||||||
|
const std::string &senderDevice, |
||||||
|
mtx::identifiers::User receiver, |
||||||
|
const std::string &receiverDevice, |
||||||
|
const std::string &transactionId, |
||||||
|
std::map<std::string, std::string> keys) |
||||||
|
{ |
||||||
|
mtx::events::msg::KeyVerificationMac req; |
||||||
|
|
||||||
|
std::string info = "MATRIX_KEY_VERIFICATION_MAC" + sender.to_string() + senderDevice + |
||||||
|
receiver.to_string() + receiverDevice + transactionId; |
||||||
|
|
||||||
|
std::string key_list; |
||||||
|
bool first = true; |
||||||
|
for (const auto &[key_id, key] : keys) { |
||||||
|
req.mac[key_id] = sas->calculate_mac(key, info + key_id); |
||||||
|
|
||||||
|
if (!first) |
||||||
|
key_list += ","; |
||||||
|
key_list += key_id; |
||||||
|
first = false; |
||||||
|
} |
||||||
|
|
||||||
|
req.keys = sas->calculate_mac(key_list, info + "KEY_IDS"); |
||||||
|
|
||||||
|
return req; |
||||||
|
} |
||||||
|
|
||||||
|
//! sends the mac of the keys
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::sendVerificationMac() |
||||||
|
{ |
||||||
|
std::map<std::string, std::string> key_list; |
||||||
|
key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519; |
||||||
|
|
||||||
|
// send our master key, if we trust it
|
||||||
|
if (!this->our_trusted_master_key.empty()) |
||||||
|
key_list["ed25519:" + our_trusted_master_key] = our_trusted_master_key; |
||||||
|
|
||||||
|
mtx::events::msg::KeyVerificationMac req = |
||||||
|
key_verification_mac(sas.get(), |
||||||
|
http::client()->user_id(), |
||||||
|
http::client()->device_id(), |
||||||
|
this->toClient, |
||||||
|
this->deviceId.toStdString(), |
||||||
|
this->transaction_id, |
||||||
|
key_list); |
||||||
|
|
||||||
|
send(req); |
||||||
|
|
||||||
|
setState(WaitingForMac); |
||||||
|
acceptDevice(); |
||||||
|
} |
||||||
|
//! Completes the verification flow
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::acceptDevice() |
||||||
|
{ |
||||||
|
if (!isMacVerified) { |
||||||
|
setState(WaitingForMac); |
||||||
|
} else if (state_ == WaitingForMac) { |
||||||
|
cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString()); |
||||||
|
this->sendVerificationDone(); |
||||||
|
setState(Success); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
DeviceVerificationFlow::unverify() |
||||||
|
{ |
||||||
|
cache::markDeviceUnverified(this->toClient.to_string(), this->deviceId.toStdString()); |
||||||
|
|
||||||
|
emit refreshProfile(); |
||||||
|
} |
||||||
|
|
||||||
|
QSharedPointer<DeviceVerificationFlow> |
||||||
|
DeviceVerificationFlow::NewInRoomVerification(QObject *parent_, |
||||||
|
TimelineModel *timelineModel_, |
||||||
|
const mtx::events::msg::KeyVerificationRequest &msg, |
||||||
|
QString other_user_, |
||||||
|
QString event_id_) |
||||||
|
{ |
||||||
|
QSharedPointer<DeviceVerificationFlow> flow( |
||||||
|
new DeviceVerificationFlow(parent_, |
||||||
|
Type::RoomMsg, |
||||||
|
timelineModel_, |
||||||
|
other_user_, |
||||||
|
QString::fromStdString(msg.from_device))); |
||||||
|
|
||||||
|
flow->setEventId(event_id_.toStdString()); |
||||||
|
|
||||||
|
if (std::find(msg.methods.begin(), |
||||||
|
msg.methods.end(), |
||||||
|
mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) { |
||||||
|
flow->cancelVerification(UnknownMethod); |
||||||
|
} |
||||||
|
|
||||||
|
return flow; |
||||||
|
} |
||||||
|
QSharedPointer<DeviceVerificationFlow> |
||||||
|
DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, |
||||||
|
const mtx::events::msg::KeyVerificationRequest &msg, |
||||||
|
QString other_user_, |
||||||
|
QString txn_id_) |
||||||
|
{ |
||||||
|
QSharedPointer<DeviceVerificationFlow> flow(new DeviceVerificationFlow( |
||||||
|
parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); |
||||||
|
flow->transaction_id = txn_id_.toStdString(); |
||||||
|
|
||||||
|
if (std::find(msg.methods.begin(), |
||||||
|
msg.methods.end(), |
||||||
|
mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) { |
||||||
|
flow->cancelVerification(UnknownMethod); |
||||||
|
} |
||||||
|
|
||||||
|
return flow; |
||||||
|
} |
||||||
|
QSharedPointer<DeviceVerificationFlow> |
||||||
|
DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, |
||||||
|
const mtx::events::msg::KeyVerificationStart &msg, |
||||||
|
QString other_user_, |
||||||
|
QString txn_id_) |
||||||
|
{ |
||||||
|
QSharedPointer<DeviceVerificationFlow> flow(new DeviceVerificationFlow( |
||||||
|
parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); |
||||||
|
flow->transaction_id = txn_id_.toStdString(); |
||||||
|
|
||||||
|
flow->handleStartMessage(msg, ""); |
||||||
|
|
||||||
|
return flow; |
||||||
|
} |
||||||
|
QSharedPointer<DeviceVerificationFlow> |
||||||
|
DeviceVerificationFlow::InitiateUserVerification(QObject *parent_, |
||||||
|
TimelineModel *timelineModel_, |
||||||
|
QString userid) |
||||||
|
{ |
||||||
|
QSharedPointer<DeviceVerificationFlow> flow( |
||||||
|
new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, "")); |
||||||
|
flow->sender = true; |
||||||
|
return flow; |
||||||
|
} |
||||||
|
QSharedPointer<DeviceVerificationFlow> |
||||||
|
DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, QString userid, QString device) |
||||||
|
{ |
||||||
|
QSharedPointer<DeviceVerificationFlow> flow( |
||||||
|
new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device)); |
||||||
|
|
||||||
|
flow->sender = true; |
||||||
|
flow->transaction_id = http::client()->generate_txn_id(); |
||||||
|
|
||||||
|
return flow; |
||||||
|
} |
@ -0,0 +1,235 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <QObject> |
||||||
|
|
||||||
|
#include <mtx/responses/crypto.hpp> |
||||||
|
|
||||||
|
#include "CacheCryptoStructs.h" |
||||||
|
#include "Logging.h" |
||||||
|
#include "MatrixClient.h" |
||||||
|
#include "Olm.h" |
||||||
|
#include "timeline/TimelineModel.h" |
||||||
|
|
||||||
|
class QTimer; |
||||||
|
|
||||||
|
using sas_ptr = std::unique_ptr<mtx::crypto::SAS>; |
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
/*
|
||||||
|
* Stolen from fluffy chat :D |
||||||
|
* |
||||||
|
* State | +-------------+ +-----------+ | |
||||||
|
* | | AliceDevice | | BobDevice | | |
||||||
|
* | | (sender) | | | | |
||||||
|
* | +-------------+ +-----------+ | |
||||||
|
* promptStartVerify | | | | |
||||||
|
* | o | (m.key.verification.request) | | |
||||||
|
* | p |-------------------------------->| (ASK FOR VERIFICATION REQUEST) | |
||||||
|
* waitForOtherAccept | t | | | promptStartVerify |
||||||
|
* && | i | (m.key.verification.ready) | | |
||||||
|
* no commitment | o |<--------------------------------| | |
||||||
|
* && | n | | | |
||||||
|
* no canonical_json | a | (m.key.verification.start) | | waitingForKeys |
||||||
|
* | l |<--------------------------------| Not sending to prevent the glare resolve| && no commitment |
||||||
|
* | | | | && no canonical_json |
||||||
|
* | | m.key.verification.start | | |
||||||
|
* waitForOtherAccept | |-------------------------------->| (IF NOT ALREADY ASKED, | |
||||||
|
* && | | | ASK FOR VERIFICATION REQUEST) | promptStartVerify, if not accepted |
||||||
|
* canonical_json | | m.key.verification.accept | | |
||||||
|
* | |<--------------------------------| | |
||||||
|
* waitForOtherAccept | | | | waitingForKeys |
||||||
|
* && | | m.key.verification.key | | && canonical_json |
||||||
|
* commitment | |-------------------------------->| | && commitment |
||||||
|
* | | | | |
||||||
|
* | | m.key.verification.key | | |
||||||
|
* | |<--------------------------------| | |
||||||
|
* compareEmoji/Number| | | | compareEmoji/Number |
||||||
|
* | | COMPARE EMOJI / NUMBERS | | |
||||||
|
* | | | | |
||||||
|
* waitingForMac | | m.key.verification.mac | | waitingForMac |
||||||
|
* | success |<------------------------------->| success | |
||||||
|
* | | | | |
||||||
|
* success/fail | | m.key.verification.done | | success/fail |
||||||
|
* | |<------------------------------->| | |
||||||
|
*/ |
||||||
|
// clang-format on
|
||||||
|
class DeviceVerificationFlow : public QObject |
||||||
|
{ |
||||||
|
Q_OBJECT |
||||||
|
Q_PROPERTY(QString state READ state NOTIFY stateChanged) |
||||||
|
Q_PROPERTY(Error error READ error NOTIFY errorChanged) |
||||||
|
Q_PROPERTY(QString userId READ getUserId CONSTANT) |
||||||
|
Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT) |
||||||
|
Q_PROPERTY(bool sender READ getSender CONSTANT) |
||||||
|
Q_PROPERTY(std::vector<int> sasList READ getSasList CONSTANT) |
||||||
|
|
||||||
|
public: |
||||||
|
enum State |
||||||
|
{ |
||||||
|
PromptStartVerification, |
||||||
|
WaitingForOtherToAccept, |
||||||
|
WaitingForKeys, |
||||||
|
CompareEmoji, |
||||||
|
CompareNumber, |
||||||
|
WaitingForMac, |
||||||
|
Success, |
||||||
|
Failed, |
||||||
|
}; |
||||||
|
Q_ENUM(State) |
||||||
|
|
||||||
|
enum Type |
||||||
|
{ |
||||||
|
ToDevice, |
||||||
|
RoomMsg |
||||||
|
}; |
||||||
|
|
||||||
|
enum Error |
||||||
|
{ |
||||||
|
UnknownMethod, |
||||||
|
MismatchedCommitment, |
||||||
|
MismatchedSAS, |
||||||
|
KeyMismatch, |
||||||
|
Timeout, |
||||||
|
User, |
||||||
|
OutOfOrder, |
||||||
|
}; |
||||||
|
Q_ENUM(Error) |
||||||
|
|
||||||
|
static QSharedPointer<DeviceVerificationFlow> NewInRoomVerification( |
||||||
|
QObject *parent_, |
||||||
|
TimelineModel *timelineModel_, |
||||||
|
const mtx::events::msg::KeyVerificationRequest &msg, |
||||||
|
QString other_user_, |
||||||
|
QString event_id_); |
||||||
|
static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification( |
||||||
|
QObject *parent_, |
||||||
|
const mtx::events::msg::KeyVerificationRequest &msg, |
||||||
|
QString other_user_, |
||||||
|
QString txn_id_); |
||||||
|
static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification( |
||||||
|
QObject *parent_, |
||||||
|
const mtx::events::msg::KeyVerificationStart &msg, |
||||||
|
QString other_user_, |
||||||
|
QString txn_id_); |
||||||
|
static QSharedPointer<DeviceVerificationFlow> |
||||||
|
InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid); |
||||||
|
static QSharedPointer<DeviceVerificationFlow> InitiateDeviceVerification(QObject *parent, |
||||||
|
QString userid, |
||||||
|
QString device); |
||||||
|
|
||||||
|
// getters
|
||||||
|
QString state(); |
||||||
|
Error error() { return error_; } |
||||||
|
QString getUserId(); |
||||||
|
QString getDeviceId(); |
||||||
|
bool getSender(); |
||||||
|
std::vector<int> getSasList(); |
||||||
|
QString transactionId() { return QString::fromStdString(this->transaction_id); } |
||||||
|
// setters
|
||||||
|
void setDeviceId(QString deviceID); |
||||||
|
void setEventId(std::string event_id); |
||||||
|
|
||||||
|
void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id); |
||||||
|
|
||||||
|
public slots: |
||||||
|
//! unverifies a device
|
||||||
|
void unverify(); |
||||||
|
//! Continues the flow
|
||||||
|
void next(); |
||||||
|
//! Cancel the flow
|
||||||
|
void cancel() { cancelVerification(User); } |
||||||
|
|
||||||
|
signals: |
||||||
|
void refreshProfile(); |
||||||
|
void stateChanged(); |
||||||
|
void errorChanged(); |
||||||
|
|
||||||
|
private: |
||||||
|
DeviceVerificationFlow(QObject *, |
||||||
|
DeviceVerificationFlow::Type flow_type, |
||||||
|
TimelineModel *model, |
||||||
|
QString userID, |
||||||
|
QString deviceId_); |
||||||
|
void setState(State state) |
||||||
|
{ |
||||||
|
if (state != state_) { |
||||||
|
state_ = state; |
||||||
|
emit stateChanged(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string); |
||||||
|
//! sends a verification request
|
||||||
|
void sendVerificationRequest(); |
||||||
|
//! accepts a verification request
|
||||||
|
void sendVerificationReady(); |
||||||
|
//! completes the verification flow();
|
||||||
|
void sendVerificationDone(); |
||||||
|
//! accepts a verification
|
||||||
|
void acceptVerificationRequest(); |
||||||
|
//! starts the verification flow
|
||||||
|
void startVerificationRequest(); |
||||||
|
//! cancels a verification flow
|
||||||
|
void cancelVerification(DeviceVerificationFlow::Error error_code); |
||||||
|
//! sends the verification key
|
||||||
|
void sendVerificationKey(); |
||||||
|
//! sends the mac of the keys
|
||||||
|
void sendVerificationMac(); |
||||||
|
//! Completes the verification flow
|
||||||
|
void acceptDevice(); |
||||||
|
|
||||||
|
std::string transaction_id; |
||||||
|
|
||||||
|
bool sender; |
||||||
|
Type type; |
||||||
|
mtx::identifiers::User toClient; |
||||||
|
QString deviceId; |
||||||
|
|
||||||
|
// public part of our master key, when trusted or empty
|
||||||
|
std::string our_trusted_master_key; |
||||||
|
|
||||||
|
mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji; |
||||||
|
QTimer *timeout = nullptr; |
||||||
|
sas_ptr sas; |
||||||
|
std::string mac_method; |
||||||
|
std::string commitment; |
||||||
|
nlohmann::json canonical_json; |
||||||
|
|
||||||
|
std::vector<int> sasList; |
||||||
|
UserKeyCache their_keys; |
||||||
|
TimelineModel *model_; |
||||||
|
mtx::common::RelatesTo relation; |
||||||
|
|
||||||
|
State state_ = PromptStartVerification; |
||||||
|
Error error_ = UnknownMethod; |
||||||
|
|
||||||
|
bool isMacVerified = false; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
void send(T msg) |
||||||
|
{ |
||||||
|
if (this->type == DeviceVerificationFlow::Type::ToDevice) { |
||||||
|
mtx::requests::ToDeviceMessages<T> body; |
||||||
|
msg.transaction_id = this->transaction_id; |
||||||
|
body[this->toClient][deviceId.toStdString()] = msg; |
||||||
|
|
||||||
|
http::client()->send_to_device<T>( |
||||||
|
this->transaction_id, body, [](mtx::http::RequestErr err) { |
||||||
|
if (err) |
||||||
|
nhlog::net()->warn( |
||||||
|
"failed to send verification to_device message: {} {}", |
||||||
|
err->matrix_error.error, |
||||||
|
static_cast<int>(err->status_code)); |
||||||
|
}); |
||||||
|
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { |
||||||
|
if constexpr (!std::is_same_v<T, mtx::events::msg::KeyVerificationRequest>) |
||||||
|
msg.relates_to = this->relation; |
||||||
|
(model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type<T>); |
||||||
|
} |
||||||
|
|
||||||
|
nhlog::net()->debug( |
||||||
|
"Sent verification step: {} in state: {}", |
||||||
|
mtx::events::to_string(mtx::events::to_device_content_to_type<T>), |
||||||
|
state().toStdString()); |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,227 @@ |
|||||||
|
#include "UserProfile.h" |
||||||
|
#include "Cache_p.h" |
||||||
|
#include "ChatPage.h" |
||||||
|
#include "DeviceVerificationFlow.h" |
||||||
|
#include "Logging.h" |
||||||
|
#include "Utils.h" |
||||||
|
#include "mtx/responses/crypto.hpp" |
||||||
|
#include "timeline/TimelineModel.h" |
||||||
|
#include "timeline/TimelineViewManager.h" |
||||||
|
|
||||||
|
UserProfile::UserProfile(QString roomid, |
||||||
|
QString userid, |
||||||
|
TimelineViewManager *manager_, |
||||||
|
TimelineModel *parent) |
||||||
|
: QObject(parent) |
||||||
|
, roomid_(roomid) |
||||||
|
, userid_(userid) |
||||||
|
, manager(manager_) |
||||||
|
, model(parent) |
||||||
|
{ |
||||||
|
fetchDeviceList(this->userid_); |
||||||
|
|
||||||
|
connect(cache::client(), |
||||||
|
&Cache::verificationStatusChanged, |
||||||
|
this, |
||||||
|
[this](const std::string &user_id) { |
||||||
|
if (user_id != this->userid_.toStdString()) |
||||||
|
return; |
||||||
|
|
||||||
|
auto status = cache::verificationStatus(user_id); |
||||||
|
if (!status) |
||||||
|
return; |
||||||
|
this->isUserVerified = status->user_verified; |
||||||
|
emit userStatusChanged(); |
||||||
|
|
||||||
|
for (auto &deviceInfo : deviceList_.deviceList_) { |
||||||
|
deviceInfo.verification_status = |
||||||
|
std::find(status->verified_devices.begin(), |
||||||
|
status->verified_devices.end(), |
||||||
|
deviceInfo.device_id.toStdString()) == |
||||||
|
status->verified_devices.end() |
||||||
|
? verification::UNVERIFIED |
||||||
|
: verification::VERIFIED; |
||||||
|
} |
||||||
|
deviceList_.reset(deviceList_.deviceList_); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
QHash<int, QByteArray> |
||||||
|
DeviceInfoModel::roleNames() const |
||||||
|
{ |
||||||
|
return { |
||||||
|
{DeviceId, "deviceId"}, |
||||||
|
{DeviceName, "deviceName"}, |
||||||
|
{VerificationStatus, "verificationStatus"}, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
QVariant |
||||||
|
DeviceInfoModel::data(const QModelIndex &index, int role) const |
||||||
|
{ |
||||||
|
if (!index.isValid() || index.row() >= (int)deviceList_.size() || index.row() < 0) |
||||||
|
return {}; |
||||||
|
|
||||||
|
switch (role) { |
||||||
|
case DeviceId: |
||||||
|
return deviceList_[index.row()].device_id; |
||||||
|
case DeviceName: |
||||||
|
return deviceList_[index.row()].display_name; |
||||||
|
case VerificationStatus: |
||||||
|
return QVariant::fromValue(deviceList_[index.row()].verification_status); |
||||||
|
default: |
||||||
|
return {}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
DeviceInfoModel::reset(const std::vector<DeviceInfo> &deviceList) |
||||||
|
{ |
||||||
|
beginResetModel(); |
||||||
|
this->deviceList_ = std::move(deviceList); |
||||||
|
endResetModel(); |
||||||
|
} |
||||||
|
|
||||||
|
DeviceInfoModel * |
||||||
|
UserProfile::deviceList() |
||||||
|
{ |
||||||
|
return &this->deviceList_; |
||||||
|
} |
||||||
|
|
||||||
|
QString |
||||||
|
UserProfile::userid() |
||||||
|
{ |
||||||
|
return this->userid_; |
||||||
|
} |
||||||
|
|
||||||
|
QString |
||||||
|
UserProfile::displayName() |
||||||
|
{ |
||||||
|
return cache::displayName(roomid_, userid_); |
||||||
|
} |
||||||
|
|
||||||
|
QString |
||||||
|
UserProfile::avatarUrl() |
||||||
|
{ |
||||||
|
return cache::avatarUrl(roomid_, userid_); |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
UserProfile::getUserStatus() |
||||||
|
{ |
||||||
|
return isUserVerified; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
UserProfile::fetchDeviceList(const QString &userID) |
||||||
|
{ |
||||||
|
auto localUser = utils::localUser(); |
||||||
|
|
||||||
|
ChatPage::instance()->query_keys( |
||||||
|
userID.toStdString(), |
||||||
|
[other_user_id = userID.toStdString(), this](const UserKeyCache &other_user_keys, |
||||||
|
mtx::http::RequestErr err) { |
||||||
|
if (err) { |
||||||
|
nhlog::net()->warn("failed to query device keys: {},{}", |
||||||
|
err->matrix_error.errcode, |
||||||
|
static_cast<int>(err->status_code)); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Finding if the User is Verified or not based on the Signatures
|
||||||
|
ChatPage::instance()->query_keys( |
||||||
|
utils::localUser().toStdString(), |
||||||
|
[other_user_id, other_user_keys, this](const UserKeyCache &res, |
||||||
|
mtx::http::RequestErr err) { |
||||||
|
using namespace mtx; |
||||||
|
std::string local_user_id = utils::localUser().toStdString(); |
||||||
|
|
||||||
|
if (err) { |
||||||
|
nhlog::net()->warn("failed to query device keys: {},{}", |
||||||
|
err->matrix_error.errcode, |
||||||
|
static_cast<int>(err->status_code)); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (res.device_keys.empty()) { |
||||||
|
nhlog::net()->warn("no devices retrieved {}", local_user_id); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
std::vector<DeviceInfo> deviceInfo; |
||||||
|
auto devices = other_user_keys.device_keys; |
||||||
|
auto verificationStatus = |
||||||
|
cache::client()->verificationStatus(other_user_id); |
||||||
|
|
||||||
|
isUserVerified = verificationStatus.user_verified; |
||||||
|
emit userStatusChanged(); |
||||||
|
|
||||||
|
for (const auto &d : devices) { |
||||||
|
auto device = d.second; |
||||||
|
verification::Status verified = |
||||||
|
verification::Status::UNVERIFIED; |
||||||
|
|
||||||
|
if (std::find(verificationStatus.verified_devices.begin(), |
||||||
|
verificationStatus.verified_devices.end(), |
||||||
|
device.device_id) != |
||||||
|
verificationStatus.verified_devices.end() && |
||||||
|
mtx::crypto::verify_identity_signature( |
||||||
|
device, |
||||||
|
DeviceId(device.device_id), |
||||||
|
UserId(other_user_id))) |
||||||
|
verified = verification::Status::VERIFIED; |
||||||
|
|
||||||
|
deviceInfo.push_back( |
||||||
|
{QString::fromStdString(d.first), |
||||||
|
QString::fromStdString( |
||||||
|
device.unsigned_info.device_display_name), |
||||||
|
verified}); |
||||||
|
} |
||||||
|
|
||||||
|
this->deviceList_.queueReset(std::move(deviceInfo)); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
UserProfile::banUser() |
||||||
|
{ |
||||||
|
ChatPage::instance()->banUser(this->userid_, ""); |
||||||
|
} |
||||||
|
|
||||||
|
// void ignoreUser(){
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
void |
||||||
|
UserProfile::kickUser() |
||||||
|
{ |
||||||
|
ChatPage::instance()->kickUser(this->userid_, ""); |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
UserProfile::startChat() |
||||||
|
{ |
||||||
|
mtx::requests::CreateRoom req; |
||||||
|
req.preset = mtx::requests::Preset::PrivateChat; |
||||||
|
req.visibility = mtx::requests::Visibility::Private; |
||||||
|
if (utils::localUser() != this->userid_) |
||||||
|
req.invite = {this->userid_.toStdString()}; |
||||||
|
emit ChatPage::instance()->createRoom(req); |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
UserProfile::verify(QString device) |
||||||
|
{ |
||||||
|
if (!device.isEmpty()) |
||||||
|
manager->verifyDevice(userid_, device); |
||||||
|
else { |
||||||
|
manager->verifyUser(userid_); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
UserProfile::unverify(QString device) |
||||||
|
{ |
||||||
|
cache::markDeviceUnverified(userid_.toStdString(), device.toStdString()); |
||||||
|
} |
@ -0,0 +1,123 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <QAbstractListModel> |
||||||
|
#include <QObject> |
||||||
|
#include <QString> |
||||||
|
#include <QVector> |
||||||
|
|
||||||
|
#include "MatrixClient.h" |
||||||
|
|
||||||
|
namespace verification { |
||||||
|
Q_NAMESPACE |
||||||
|
|
||||||
|
enum Status |
||||||
|
{ |
||||||
|
VERIFIED, |
||||||
|
UNVERIFIED, |
||||||
|
BLOCKED |
||||||
|
}; |
||||||
|
Q_ENUM_NS(Status) |
||||||
|
} |
||||||
|
|
||||||
|
class DeviceVerificationFlow; |
||||||
|
class TimelineModel; |
||||||
|
class TimelineViewManager; |
||||||
|
|
||||||
|
class DeviceInfo |
||||||
|
{ |
||||||
|
public: |
||||||
|
DeviceInfo(const QString deviceID, |
||||||
|
const QString displayName, |
||||||
|
verification::Status verification_status_) |
||||||
|
: device_id(deviceID) |
||||||
|
, display_name(displayName) |
||||||
|
, verification_status(verification_status_) |
||||||
|
{} |
||||||
|
DeviceInfo() |
||||||
|
: verification_status(verification::UNVERIFIED) |
||||||
|
{} |
||||||
|
|
||||||
|
QString device_id; |
||||||
|
QString display_name; |
||||||
|
|
||||||
|
verification::Status verification_status; |
||||||
|
}; |
||||||
|
|
||||||
|
class DeviceInfoModel : public QAbstractListModel |
||||||
|
{ |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
enum Roles |
||||||
|
{ |
||||||
|
DeviceId, |
||||||
|
DeviceName, |
||||||
|
VerificationStatus, |
||||||
|
}; |
||||||
|
|
||||||
|
explicit DeviceInfoModel(QObject *parent = nullptr) |
||||||
|
{ |
||||||
|
(void)parent; |
||||||
|
connect(this, &DeviceInfoModel::queueReset, this, &DeviceInfoModel::reset); |
||||||
|
}; |
||||||
|
QHash<int, QByteArray> roleNames() const override; |
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override |
||||||
|
{ |
||||||
|
(void)parent; |
||||||
|
return (int)deviceList_.size(); |
||||||
|
} |
||||||
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; |
||||||
|
|
||||||
|
signals: |
||||||
|
void queueReset(const std::vector<DeviceInfo> &deviceList); |
||||||
|
public slots: |
||||||
|
void reset(const std::vector<DeviceInfo> &deviceList); |
||||||
|
|
||||||
|
private: |
||||||
|
std::vector<DeviceInfo> deviceList_; |
||||||
|
|
||||||
|
friend class UserProfile; |
||||||
|
}; |
||||||
|
|
||||||
|
class UserProfile : public QObject |
||||||
|
{ |
||||||
|
Q_OBJECT |
||||||
|
Q_PROPERTY(QString displayName READ displayName CONSTANT) |
||||||
|
Q_PROPERTY(QString userid READ userid CONSTANT) |
||||||
|
Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT) |
||||||
|
Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT) |
||||||
|
Q_PROPERTY(bool isUserVerified READ getUserStatus NOTIFY userStatusChanged) |
||||||
|
public: |
||||||
|
UserProfile(QString roomid, |
||||||
|
QString userid, |
||||||
|
TimelineViewManager *manager_, |
||||||
|
TimelineModel *parent = nullptr); |
||||||
|
|
||||||
|
DeviceInfoModel *deviceList(); |
||||||
|
|
||||||
|
QString userid(); |
||||||
|
QString displayName(); |
||||||
|
QString avatarUrl(); |
||||||
|
bool getUserStatus(); |
||||||
|
|
||||||
|
Q_INVOKABLE void verify(QString device = ""); |
||||||
|
Q_INVOKABLE void unverify(QString device = ""); |
||||||
|
Q_INVOKABLE void fetchDeviceList(const QString &userID); |
||||||
|
Q_INVOKABLE void banUser(); |
||||||
|
// Q_INVOKABLE void ignoreUser();
|
||||||
|
Q_INVOKABLE void kickUser(); |
||||||
|
Q_INVOKABLE void startChat(); |
||||||
|
|
||||||
|
signals: |
||||||
|
void userStatusChanged(); |
||||||
|
|
||||||
|
private: |
||||||
|
QString roomid_, userid_; |
||||||
|
DeviceInfoModel deviceList_; |
||||||
|
bool isUserVerified = false; |
||||||
|
TimelineViewManager *manager; |
||||||
|
TimelineModel *model; |
||||||
|
|
||||||
|
void callback_fn(const mtx::responses::QueryKeys &res, |
||||||
|
mtx::http::RequestErr err, |
||||||
|
std::string user_id); |
||||||
|
}; |
Loading…
Reference in new issue