Add self verification after login

This commit is contained in:
Nicolas Werner 2021-10-30 00:22:47 +02:00
parent 7a9c69cbd0
commit 5688b2647e
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
9 changed files with 285 additions and 72 deletions

View file

@ -93,6 +93,7 @@ Item {
columns: 2
rowSpacing: 0
columnSpacing: 0
z: 1
Label {
Layout.margins: Nheko.paddingMedium
@ -220,15 +221,53 @@ Item {
MainWindowDialog {
id: verifyMasterKey
onAccepted: SelfVerificationStatus.verifyMasterKey()
standardButtons: Dialog.Cancel
GridLayout {
id: masterGrid
width: verifyMasterKey.useableWidth
columns: 2
rowSpacing: 0
columnSpacing: 0
columns: 1
z: 1
Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter
//Layout.columnSpan: 2
font.pointSize: fontMetrics.font.pointSize * 2
text: qsTr("Activate Encryption")
color: Nheko.colors.text
wrapMode: Text.Wrap
}
Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignLeft
//Layout.columnSpan: 2
Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2
text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.")
color: Nheko.colors.text
wrapMode: Text.Wrap
}
FlatButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("verify")
onClicked: {
console.log("AAAAA");
SelfVerificationStatus.verifyMasterKey();
verifyMasterKey.close();
}
}
FlatButton {
visible: SelfVerificationStatus.hasSSSS
Layout.alignment: Qt.AlignHCenter
text: qsTr("enter passphrase")
onClicked: {
SelfVerificationStatus.verifyMasterKeyWithPassphrase()
verifyMasterKey.close();
}
}
}
}
@ -237,8 +276,8 @@ Item {
console.log("STATUS CHANGED: " + SelfVerificationStatus.status);
if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey)
bootstrapCrosssigning.open();
// else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey)
// verifyMasterKey.open();
else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey)
verifyMasterKey.open();
}

View file

@ -1131,11 +1131,71 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
return;
}
auto deviceKeys = cache::client()->userKeys(http::client()->user_id().to_string());
mtx::requests::KeySignaturesUpload req;
for (const auto &[secretName, encryptedSecret] : secrets) {
auto decrypted = mtx::crypto::decrypt(encryptedSecret, *decryptionKey, secretName);
if (!decrypted.empty())
if (!decrypted.empty()) {
cache::storeSecret(secretName, decrypted);
if (deviceKeys &&
secretName == mtx::secret_storage::secrets::cross_signing_self_signing) {
auto myKey = deviceKeys->device_keys.at(http::client()->device_id());
if (myKey.user_id == http::client()->user_id().to_string() &&
myKey.device_id == http::client()->device_id() &&
myKey.keys["ed25519:" + http::client()->device_id()] ==
olm::client()->identity_keys().ed25519 &&
myKey.keys["curve25519:" + http::client()->device_id()] ==
olm::client()->identity_keys().curve25519) {
json j = myKey;
j.erase("signatures");
j.erase("unsigned");
auto ssk = mtx::crypto::PkSigning::from_seed(decrypted);
myKey.signatures[http::client()->user_id().to_string()]
["ed25519:" + ssk.public_key()] = ssk.sign(j.dump());
req.signatures[http::client()->user_id().to_string()]
[http::client()->device_id()] = myKey;
}
} else if (deviceKeys &&
secretName == mtx::secret_storage::secrets::cross_signing_master) {
auto mk = mtx::crypto::PkSigning::from_seed(decrypted);
if (deviceKeys->master_keys.user_id == http::client()->user_id().to_string() &&
deviceKeys->master_keys.keys["ed25519:" + mk.public_key()] == mk.public_key()) {
json j = deviceKeys->master_keys;
j.erase("signatures");
j.erase("unsigned");
mtx::crypto::CrossSigningKeys master_key = j;
master_key.signatures[http::client()->user_id().to_string()]
["ed25519:" + http::client()->device_id()] =
olm::client()->sign_message(j.dump());
req.signatures[http::client()->user_id().to_string()][mk.public_key()] =
master_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: {},{}",
mtx::errors::to_string(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,
mtx::errors::to_string(e.errcode),
e.error);
});
}
void

View file

@ -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();

View file

@ -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,

View file

@ -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);
});
});
});
});

View file

@ -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) {

View file

@ -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;
};

View file

@ -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());
}

View file

@ -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);