diff --git a/src/encryption/DeviceVerificationFlow.cpp b/src/encryption/DeviceVerificationFlow.cpp
index 2481d4f9..f05d5c9f 100644
--- a/src/encryption/DeviceVerificationFlow.cpp
+++ b/src/encryption/DeviceVerificationFlow.cpp
@@ -32,12 +32,15 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
DeviceVerificationFlow::Type flow_type,
TimelineModel *model,
QString userID,
- QString deviceId_)
+ std::vector<QString> deviceIds_)
: sender(false)
, type(flow_type)
- , deviceId(deviceId_)
+ , deviceIds(std::move(deviceIds_))
, model_(model)
{
+ if (deviceIds.size() == 1)
+ deviceId = deviceIds.front();
+
timeout = new QTimer(this);
timeout->setSingleShot(true);
this->sas = olm::client()->sas_init();
@@ -346,33 +349,62 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
}
});
- connect(ChatPage::instance(),
- &ChatPage::receivedDeviceVerificationReady,
- this,
- [this](const mtx::events::msg::KeyVerificationReady &msg) {
- nhlog::crypto()->info("verification: received ready");
- if (!sender) {
- if (msg.from_device != http::client()->device_id()) {
- error_ = User;
- emit errorChanged();
- setState(Failed);
- }
+ connect(
+ ChatPage::instance(),
+ &ChatPage::receivedDeviceVerificationReady,
+ this,
+ [this](const mtx::events::msg::KeyVerificationReady &msg) {
+ nhlog::crypto()->info("verification: received ready");
+ if (!sender) {
+ if (msg.from_device != http::client()->device_id()) {
+ error_ = User;
+ emit errorChanged();
+ setState(Failed);
+ }
- return;
- }
+ return;
+ }
- if (msg.transaction_id.has_value()) {
- if (msg.transaction_id.value() != this->transaction_id)
- return;
- } else if (msg.relations.references()) {
- if (msg.relations.references() != this->relation.event_id)
- return;
- else {
- this->deviceId = QString::fromStdString(msg.from_device);
- }
- }
- this->startVerificationRequest();
- });
+ if (msg.transaction_id.has_value()) {
+ if (msg.transaction_id.value() != this->transaction_id)
+ return;
+
+ if (this->deviceId.isEmpty() && this->deviceIds.size() > 1) {
+ auto from = QString::fromStdString(msg.from_device);
+ if (std::find(deviceIds.begin(), deviceIds.end(), from) != deviceIds.end()) {
+ mtx::events::msg::KeyVerificationCancel req{};
+ req.code = "m.user";
+ req.reason = "accepted by other device";
+ req.transaction_id = this->transaction_id;
+ mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationCancel> body;
+
+ for (const auto &d : this->deviceIds) {
+ if (d != from)
+ body[this->toClient][d.toStdString()] = req;
+ }
+
+ http::client()->send_to_device(
+ http::client()->generate_txn_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));
+ });
+
+ this->deviceId = from;
+ this->deviceIds = {from};
+ }
+ }
+ } else if (msg.relations.references()) {
+ if (msg.relations.references() != this->relation.event_id)
+ return;
+ else {
+ this->deviceId = QString::fromStdString(msg.from_device);
+ }
+ }
+ this->startVerificationRequest();
+ });
connect(ChatPage::instance(),
&ChatPage::receivedDeviceVerificationDone,
@@ -782,7 +814,7 @@ DeviceVerificationFlow::NewInRoomVerification(QObject *parent_,
Type::RoomMsg,
timelineModel_,
other_user_,
- QString::fromStdString(msg.from_device)));
+ {QString::fromStdString(msg.from_device)}));
flow->setEventId(event_id_.toStdString());
@@ -801,7 +833,7 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
QString txn_id_)
{
QSharedPointer<DeviceVerificationFlow> flow(new DeviceVerificationFlow(
- parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device)));
+ parent_, Type::ToDevice, nullptr, other_user_, {QString::fromStdString(msg.from_device)}));
flow->transaction_id = txn_id_.toStdString();
if (std::find(msg.methods.begin(),
@@ -819,7 +851,7 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
QString txn_id_)
{
QSharedPointer<DeviceVerificationFlow> flow(new DeviceVerificationFlow(
- parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device)));
+ parent_, Type::ToDevice, nullptr, other_user_, {QString::fromStdString(msg.from_device)}));
flow->transaction_id = txn_id_.toStdString();
flow->handleStartMessage(msg, "");
@@ -832,15 +864,19 @@ DeviceVerificationFlow::InitiateUserVerification(QObject *parent_,
QString userid)
{
QSharedPointer<DeviceVerificationFlow> flow(
- new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, ""));
+ new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, {}));
flow->sender = true;
return flow;
}
QSharedPointer<DeviceVerificationFlow>
-DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, QString userid, QString device)
+DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_,
+ QString userid,
+ std::vector<QString> devices)
{
+ assert(!devices.empty());
+
QSharedPointer<DeviceVerificationFlow> flow(
- new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device));
+ new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, devices));
flow->sender = true;
flow->transaction_id = http::client()->generate_txn_id();
diff --git a/src/encryption/DeviceVerificationFlow.h b/src/encryption/DeviceVerificationFlow.h
index f71fa337..55713def 100644
--- a/src/encryption/DeviceVerificationFlow.h
+++ b/src/encryption/DeviceVerificationFlow.h
@@ -120,9 +120,8 @@ public:
QString txn_id_);
static QSharedPointer<DeviceVerificationFlow>
InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid);
- static QSharedPointer<DeviceVerificationFlow> InitiateDeviceVerification(QObject *parent,
- QString userid,
- QString device);
+ static QSharedPointer<DeviceVerificationFlow>
+ InitiateDeviceVerification(QObject *parent, QString userid, std::vector<QString> devices);
// getters
QString state();
@@ -161,7 +160,7 @@ private:
DeviceVerificationFlow::Type flow_type,
TimelineModel *model,
QString userID,
- QString deviceId_);
+ std::vector<QString> deviceIds_);
void setState(State state)
{
if (state != state_) {
@@ -196,6 +195,7 @@ private:
Type type;
mtx::identifiers::User toClient;
QString deviceId;
+ std::vector<QString> deviceIds;
// public part of our master key, when trusted or empty
std::string our_trusted_master_key;
@@ -222,11 +222,12 @@ private:
{
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
mtx::requests::ToDeviceMessages<T> body;
- msg.transaction_id = this->transaction_id;
- body[this->toClient][deviceId.toStdString()] = msg;
+ msg.transaction_id = this->transaction_id;
+ for (const auto &d : deviceIds)
+ body[this->toClient][d.toStdString()] = msg;
http::client()->send_to_device<T>(
- this->transaction_id, body, [](mtx::http::RequestErr err) {
+ http::client()->generate_txn_id(), body, [](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn("failed to send verification to_device message: {} {}",
err->matrix_error.error,
diff --git a/src/encryption/Olm.cpp b/src/encryption/Olm.cpp
index 14c97984..01a16ba7 100644
--- a/src/encryption/Olm.cpp
+++ b/src/encryption/Olm.cpp
@@ -1540,6 +1540,7 @@ request_cross_signing_keys()
});
};
+ request(mtx::secret_storage::secrets::cross_signing_master);
request(mtx::secret_storage::secrets::cross_signing_self_signing);
request(mtx::secret_storage::secrets::cross_signing_user_signing);
request(mtx::secret_storage::secrets::megolm_backup_v1);
@@ -1574,36 +1575,52 @@ download_cross_signing_keys()
backup_key = secret;
http::client()->secret_storage_secret(
- secrets::cross_signing_self_signing,
- [backup_key](Secret secret, mtx::http::RequestErr err) {
- std::optional<Secret> self_signing_key;
+ secrets::cross_signing_master, [backup_key](Secret secret, mtx::http::RequestErr err) {
+ std::optional<Secret> master_key;
if (!err)
- self_signing_key = secret;
+ master_key = secret;
http::client()->secret_storage_secret(
- secrets::cross_signing_user_signing,
- [backup_key, self_signing_key](Secret secret, mtx::http::RequestErr err) {
- std::optional<Secret> user_signing_key;
+ secrets::cross_signing_self_signing,
+ [backup_key, master_key](Secret secret, mtx::http::RequestErr err) {
+ std::optional<Secret> self_signing_key;
if (!err)
- user_signing_key = secret;
+ self_signing_key = secret;
- std::map<std::string, std::map<std::string, AesHmacSha2EncryptedData>>
- secrets;
+ http::client()->secret_storage_secret(
+ secrets::cross_signing_user_signing,
+ [backup_key, self_signing_key, master_key](Secret secret,
+ mtx::http::RequestErr err) {
+ std::optional<Secret> user_signing_key;
+ if (!err)
+ user_signing_key = secret;
- if (backup_key && !backup_key->encrypted.empty())
- secrets[backup_key->encrypted.begin()->first][secrets::megolm_backup_v1] =
- backup_key->encrypted.begin()->second;
- if (self_signing_key && !self_signing_key->encrypted.empty())
- secrets[self_signing_key->encrypted.begin()->first]
- [secrets::cross_signing_self_signing] =
- self_signing_key->encrypted.begin()->second;
- if (user_signing_key && !user_signing_key->encrypted.empty())
- secrets[user_signing_key->encrypted.begin()->first]
- [secrets::cross_signing_user_signing] =
- user_signing_key->encrypted.begin()->second;
+ std::map<std::string, std::map<std::string, AesHmacSha2EncryptedData>>
+ secrets;
- for (const auto &[key, secrets] : secrets)
- unlock_secrets(key, secrets);
+ if (backup_key && !backup_key->encrypted.empty())
+ secrets[backup_key->encrypted.begin()->first]
+ [secrets::megolm_backup_v1] =
+ backup_key->encrypted.begin()->second;
+
+ if (master_key && !master_key->encrypted.empty())
+ secrets[master_key->encrypted.begin()->first]
+ [secrets::cross_signing_master] =
+ master_key->encrypted.begin()->second;
+
+ if (self_signing_key && !self_signing_key->encrypted.empty())
+ secrets[self_signing_key->encrypted.begin()->first]
+ [secrets::cross_signing_self_signing] =
+ self_signing_key->encrypted.begin()->second;
+
+ if (user_signing_key && !user_signing_key->encrypted.empty())
+ secrets[user_signing_key->encrypted.begin()->first]
+ [secrets::cross_signing_user_signing] =
+ user_signing_key->encrypted.begin()->second;
+
+ for (const auto &[key, secrets] : secrets)
+ unlock_secrets(key, secrets);
+ });
});
});
});
diff --git a/src/encryption/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp
index d75a2109..d4be4442 100644
--- a/src/encryption/SelfVerificationStatus.cpp
+++ b/src/encryption/SelfVerificationStatus.cpp
@@ -5,10 +5,12 @@
#include "SelfVerificationStatus.h"
#include "Cache_p.h"
+#include "ChatPage.h"
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "Olm.h"
+#include "timeline/TimelineViewManager.h"
#include "ui/UIA.h"
#include <mtx/responses/common.hpp>
@@ -196,6 +198,35 @@ void
SelfVerificationStatus::verifyMasterKey()
{
nhlog::db()->info("Clicked verify master key");
+
+ const auto this_user = http::client()->user_id().to_string();
+
+ auto keys = cache::client()->userKeys(this_user);
+ const auto &sigs = keys->master_keys.signatures[this_user];
+
+ std::vector<QString> devices;
+ for (const auto &[dev, sig] : sigs) {
+ (void)sig;
+
+ auto d = QString::fromStdString(dev);
+ if (d.startsWith("ed25519:")) {
+ d.remove("ed25519:");
+
+ if (keys->device_keys.count(d.toStdString()))
+ devices.push_back(std::move(d));
+ }
+ }
+
+ if (!devices.empty())
+ ChatPage::instance()->timelineManager()->verificationManager()->verifyOneOfDevices(
+ QString::fromStdString(this_user), std::move(devices));
+}
+
+void
+SelfVerificationStatus::verifyMasterKeyWithPassphrase()
+{
+ nhlog::db()->info("Clicked verify master key with passphrase");
+ olm::download_cross_signing_keys();
}
void
@@ -207,9 +238,15 @@ SelfVerificationStatus::verifyUnverifiedDevices()
void
SelfVerificationStatus::invalidate()
{
+ using namespace mtx::secret_storage;
+
nhlog::db()->info("Invalidating self verification status");
+ this->hasSSSS_ = false;
+ emit hasSSSSChanged();
+
auto keys = cache::client()->userKeys(http::client()->user_id().to_string());
- if (!keys) {
+ if (!keys || keys->device_keys.find(http::client()->device_id()) == keys->device_keys.end()) {
+ cache::client()->markUserKeysOutOfDate({http::client()->user_id().to_string()});
cache::client()->query_keys(http::client()->user_id().to_string(),
[](const UserKeyCache &, mtx::http::RequestErr) {});
return;
@@ -223,6 +260,14 @@ SelfVerificationStatus::invalidate()
return;
}
+ http::client()->secret_storage_secret(secrets::cross_signing_self_signing,
+ [this](Secret secret, mtx::http::RequestErr err) {
+ if (!err && !secret.encrypted.empty()) {
+ this->hasSSSS_ = true;
+ emit hasSSSSChanged();
+ }
+ });
+
auto verifStatus = cache::client()->verificationStatus(http::client()->user_id().to_string());
if (!verifStatus.user_verified) {
diff --git a/src/encryption/SelfVerificationStatus.h b/src/encryption/SelfVerificationStatus.h
index 8cb54df6..b1f315f4 100644
--- a/src/encryption/SelfVerificationStatus.h
+++ b/src/encryption/SelfVerificationStatus.h
@@ -11,6 +11,7 @@ class SelfVerificationStatus : public QObject
Q_OBJECT
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
+ Q_PROPERTY(bool hasSSSS READ hasSSSS NOTIFY hasSSSSChanged)
public:
SelfVerificationStatus(QObject *o = nullptr);
@@ -25,12 +26,15 @@ public:
Q_INVOKABLE void setupCrosssigning(bool useSSSS, QString password, bool useOnlineKeyBackup);
Q_INVOKABLE void verifyMasterKey();
+ Q_INVOKABLE void verifyMasterKeyWithPassphrase();
Q_INVOKABLE void verifyUnverifiedDevices();
Status status() const { return status_; }
+ bool hasSSSS() const { return hasSSSS_; }
signals:
void statusChanged();
+ void hasSSSSChanged();
void setupCompleted();
void showRecoveryKey(QString key);
void setupFailed(QString message);
@@ -40,4 +44,5 @@ public slots:
private:
Status status_ = AllVerified;
+ bool hasSSSS_ = true;
};
diff --git a/src/encryption/VerificationManager.cpp b/src/encryption/VerificationManager.cpp
index b9b51d35..f4c7ddf2 100644
--- a/src/encryption/VerificationManager.cpp
+++ b/src/encryption/VerificationManager.cpp
@@ -120,7 +120,16 @@ VerificationManager::removeVerificationFlow(DeviceVerificationFlow *flow)
void
VerificationManager::verifyDevice(QString userid, QString deviceid)
{
- auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid);
+ auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, {deviceid});
+ this->dvList[flow->transactionId()] = flow;
+ emit newDeviceVerificationRequest(flow.data());
+}
+
+void
+VerificationManager::verifyOneOfDevices(QString userid, std::vector<QString> deviceids)
+{
+ auto flow =
+ DeviceVerificationFlow::InitiateDeviceVerification(this, userid, std::move(deviceids));
this->dvList[flow->transactionId()] = flow;
emit newDeviceVerificationRequest(flow.data());
}
diff --git a/src/encryption/VerificationManager.h b/src/encryption/VerificationManager.h
index d6a39ccf..da646c2f 100644
--- a/src/encryption/VerificationManager.h
+++ b/src/encryption/VerificationManager.h
@@ -27,6 +27,7 @@ public:
Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow);
void verifyUser(QString userid);
void verifyDevice(QString userid, QString deviceid);
+ void verifyOneOfDevices(QString userid, std::vector<QString> deviceids);
signals:
void newDeviceVerificationRequest(DeviceVerificationFlow *flow);
|