diff options
author | DeepBlueV7.X <nicolas.werner@hotmail.de> | 2021-10-09 23:35:09 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-09 23:35:09 +0000 |
commit | 281d764aa3ce0ea55536a6356e1ed3511aaff6f4 (patch) | |
tree | aa8c88bdf2788c84053e7009adafea4c1b4a4c75 /src | |
parent | Merge pull request #743 from LorenDB/qmlLogout (diff) | |
parent | Support bootstrapping crosssigning (diff) | |
download | nheko-281d764aa3ce0ea55536a6356e1ed3511aaff6f4.tar.xz |
Merge pull request #755 from Nheko-Reborn/bootstrapping
Support bootstrapping crosssigning
Diffstat (limited to 'src')
-rw-r--r-- | src/Cache.cpp | 12 | ||||
-rw-r--r-- | src/Cache_p.h | 1 | ||||
-rw-r--r-- | src/SelfVerificationStatus.cpp | 249 | ||||
-rw-r--r-- | src/SelfVerificationStatus.h | 43 | ||||
-rw-r--r-- | src/timeline/TimelineViewManager.cpp | 44 | ||||
-rw-r--r-- | src/ui/UIA.cpp | 136 | ||||
-rw-r--r-- | src/ui/UIA.h | 40 |
7 files changed, 495 insertions, 30 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp index ee0ca0c2..ea3dd525 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -201,6 +201,18 @@ Cache::Cache(const QString &userId, QObject *parent) { setup(); connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection); + connect( + this, + &Cache::verificationStatusChanged, + this, + [this](const std::string &u) { + if (u == localUserId_.toStdString()) { + auto status = verificationStatus(u); + if (status.unverified_device_count || !status.user_verified) + emit selfUnverified(); + } + }, + Qt::QueuedConnection); } void diff --git a/src/Cache_p.h b/src/Cache_p.h index 52375d38..f7db77d4 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -310,6 +310,7 @@ signals: void removeNotification(const QString &room_id, const QString &event_id); void userKeysUpdate(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); void verificationStatusChanged(const std::string &userid); + void selfUnverified(); void secretChanged(const std::string name); private: diff --git a/src/SelfVerificationStatus.cpp b/src/SelfVerificationStatus.cpp new file mode 100644 index 00000000..d75a2109 --- /dev/null +++ b/src/SelfVerificationStatus.cpp @@ -0,0 +1,249 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "SelfVerificationStatus.h" + +#include "Cache_p.h" +#include "Logging.h" +#include "MainWindow.h" +#include "MatrixClient.h" +#include "Olm.h" +#include "ui/UIA.h" + +#include <mtx/responses/common.hpp> + +SelfVerificationStatus::SelfVerificationStatus(QObject *o) + : QObject(o) +{ + connect(MainWindow::instance(), &MainWindow::reload, this, [this] { + connect(cache::client(), + &Cache::selfUnverified, + this, + &SelfVerificationStatus::invalidate, + Qt::UniqueConnection); + invalidate(); + }); +} + +void +SelfVerificationStatus::setupCrosssigning(bool useSSSS, QString password, bool useOnlineKeyBackup) +{ + nhlog::db()->info("Clicked setup crossigning"); + + auto xsign_keys = olm::client()->create_crosssigning_keys(); + + if (!xsign_keys) { + nhlog::crypto()->critical("Failed to setup cross-signing keys!"); + emit setupFailed(tr("Failed to create keys for cross-signing!")); + return; + } + + cache::client()->storeSecret(mtx::secret_storage::secrets::cross_signing_master, + xsign_keys->private_master_key); + cache::client()->storeSecret(mtx::secret_storage::secrets::cross_signing_self_signing, + xsign_keys->private_self_signing_key); + cache::client()->storeSecret(mtx::secret_storage::secrets::cross_signing_user_signing, + xsign_keys->private_user_signing_key); + + std::optional<mtx::crypto::OlmClient::OnlineKeyBackupSetup> okb; + if (useOnlineKeyBackup) { + okb = olm::client()->create_online_key_backup(xsign_keys->private_master_key); + if (!okb) { + nhlog::crypto()->critical("Failed to setup online key backup!"); + emit setupFailed(tr("Failed to create keys for online key backup!")); + return; + } + + cache::client()->storeSecret( + mtx::secret_storage::secrets::megolm_backup_v1, + mtx::crypto::bin2base64(mtx::crypto::to_string(okb->privateKey))); + + http::client()->post_backup_version( + okb->backupVersion.algorithm, + okb->backupVersion.auth_data, + [](const mtx::responses::Version &v, mtx::http::RequestErr e) { + if (e) { + nhlog::net()->error("error setting up online key backup: {} {} {} {}", + e->parse_error, + e->status_code, + e->error_code, + e->matrix_error.error); + } else { + nhlog::crypto()->info("Set up online key backup: '{}'", v.version); + } + }); + } + + std::optional<mtx::crypto::OlmClient::SSSSSetup> ssss; + if (useSSSS) { + ssss = olm::client()->create_ssss_key(password.toStdString()); + if (!ssss) { + nhlog::crypto()->critical("Failed to setup secure server side secret storage!"); + emit setupFailed(tr("Failed to create keys secure server side secret storage!")); + return; + } + + auto master = mtx::crypto::PkSigning::from_seed(xsign_keys->private_master_key); + nlohmann::json j = ssss->keyDescription; + j.erase("signatures"); + ssss->keyDescription + .signatures[http::client()->user_id().to_string()]["ed25519:" + master.public_key()] = + master.sign(j.dump()); + + http::client()->upload_secret_storage_key( + ssss->keyDescription.name, ssss->keyDescription, [](mtx::http::RequestErr) {}); + http::client()->set_secret_storage_default_key(ssss->keyDescription.name, + [](mtx::http::RequestErr) {}); + + auto uploadSecret = [ssss](const std::string &key_name, const std::string &secret) { + mtx::secret_storage::Secret s; + s.encrypted[ssss->keyDescription.name] = + mtx::crypto::encrypt(secret, ssss->privateKey, key_name); + http::client()->upload_secret_storage_secret( + key_name, s, [key_name](mtx::http::RequestErr) { + nhlog::crypto()->info("Uploaded secret: {}", key_name); + }); + }; + + uploadSecret(mtx::secret_storage::secrets::cross_signing_master, + xsign_keys->private_master_key); + uploadSecret(mtx::secret_storage::secrets::cross_signing_self_signing, + xsign_keys->private_self_signing_key); + uploadSecret(mtx::secret_storage::secrets::cross_signing_user_signing, + xsign_keys->private_user_signing_key); + + if (okb) + uploadSecret(mtx::secret_storage::secrets::megolm_backup_v1, + mtx::crypto::bin2base64(mtx::crypto::to_string(okb->privateKey))); + } + + mtx::requests::DeviceSigningUpload device_sign{}; + device_sign.master_key = xsign_keys->master_key; + device_sign.self_signing_key = xsign_keys->self_signing_key; + device_sign.user_signing_key = xsign_keys->user_signing_key; + http::client()->device_signing_upload( + device_sign, + UIA::instance()->genericHandler(tr("Encryption Setup")), + [this, ssss, xsign_keys](mtx::http::RequestErr e) { + if (e) { + nhlog::crypto()->critical("Failed to upload cross signing keys: {}", + e->matrix_error.error); + + emit setupFailed(tr("Encryption setup failed: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + return; + } + nhlog::crypto()->info("Crosssigning keys uploaded!"); + + auto deviceKeys = cache::client()->userKeys(http::client()->user_id().to_string()); + if (deviceKeys) { + 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(xsign_keys->private_self_signing_key); + myKey.signatures[http::client()->user_id().to_string()] + ["ed25519:" + ssk.public_key()] = ssk.sign(j.dump()); + mtx::requests::KeySignaturesUpload req; + req.signatures[http::client()->user_id().to_string()] + [http::client()->device_id()] = myKey; + + 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); + }); + } + } + + if (ssss) { + auto k = QString::fromStdString(mtx::crypto::key_to_recoverykey(ssss->privateKey)); + + QString r; + for (int i = 0; i < k.size(); i += 4) + r += k.mid(i, 4) + " "; + + emit showRecoveryKey(r.trimmed()); + } else { + emit setupCompleted(); + } + }); +} + +void +SelfVerificationStatus::verifyMasterKey() +{ + nhlog::db()->info("Clicked verify master key"); +} + +void +SelfVerificationStatus::verifyUnverifiedDevices() +{ + nhlog::db()->info("Clicked verify unverified devices"); +} + +void +SelfVerificationStatus::invalidate() +{ + nhlog::db()->info("Invalidating self verification status"); + auto keys = cache::client()->userKeys(http::client()->user_id().to_string()); + if (!keys) { + cache::client()->query_keys(http::client()->user_id().to_string(), + [](const UserKeyCache &, mtx::http::RequestErr) {}); + return; + } + + if (keys->master_keys.keys.empty()) { + if (status_ != SelfVerificationStatus::NoMasterKey) { + this->status_ = SelfVerificationStatus::NoMasterKey; + emit statusChanged(); + } + return; + } + + auto verifStatus = cache::client()->verificationStatus(http::client()->user_id().to_string()); + + if (!verifStatus.user_verified) { + if (status_ != SelfVerificationStatus::UnverifiedMasterKey) { + this->status_ = SelfVerificationStatus::UnverifiedMasterKey; + emit statusChanged(); + } + return; + } + + if (verifStatus.unverified_device_count > 0) { + if (status_ != SelfVerificationStatus::UnverifiedDevices) { + this->status_ = SelfVerificationStatus::UnverifiedDevices; + emit statusChanged(); + } + return; + } + + if (status_ != SelfVerificationStatus::AllVerified) { + this->status_ = SelfVerificationStatus::AllVerified; + emit statusChanged(); + return; + } +} diff --git a/src/SelfVerificationStatus.h b/src/SelfVerificationStatus.h new file mode 100644 index 00000000..8cb54df6 --- /dev/null +++ b/src/SelfVerificationStatus.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <QObject> + +class SelfVerificationStatus : public QObject +{ + Q_OBJECT + + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + +public: + SelfVerificationStatus(QObject *o = nullptr); + enum Status + { + AllVerified, + NoMasterKey, + UnverifiedMasterKey, + UnverifiedDevices, + }; + Q_ENUM(Status) + + Q_INVOKABLE void setupCrosssigning(bool useSSSS, QString password, bool useOnlineKeyBackup); + Q_INVOKABLE void verifyMasterKey(); + Q_INVOKABLE void verifyUnverifiedDevices(); + + Status status() const { return status_; } + +signals: + void statusChanged(); + void setupCompleted(); + void showRecoveryKey(QString key); + void setupFailed(QString message); + +public slots: + void invalidate(); + +private: + Status status_ = AllVerified; +}; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 8a33dc2b..df8210d3 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -29,6 +29,7 @@ #include "ReadReceiptsModel.h" #include "RoomDirectoryModel.h" #include "RoomsModel.h" +#include "SelfVerificationStatus.h" #include "SingleImagePackModel.h" #include "UserSettingsPage.h" #include "UsersModel.h" @@ -40,6 +41,7 @@ #include "ui/NhekoCursorShape.h" #include "ui/NhekoDropArea.h" #include "ui/NhekoGlobalObject.h" +#include "ui/UIA.h" Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) Q_DECLARE_METATYPE(std::vector<DeviceInfo>) @@ -212,18 +214,9 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "ReadReceiptsProxy needs to be instantiated on the C++ side"); static auto self = this; - qmlRegisterSingletonType<MainWindow>( - "im.nheko", 1, 0, "MainWindow", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = MainWindow::instance(); - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); - qmlRegisterSingletonType<TimelineViewManager>( - "im.nheko", 1, 0, "TimelineManager", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = self; - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); + qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", MainWindow::instance()); + qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", self); + qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance()); qmlRegisterSingletonType<RoomlistModel>( "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * { auto ptr = new FilteredRoomlistModel(self->rooms_); @@ -238,24 +231,11 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par &FilteredRoomlistModel::updateHiddenTagsAndSpaces); return ptr; }); - qmlRegisterSingletonType<RoomlistModel>( - "im.nheko", 1, 0, "Communities", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = self->communities_; - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); - qmlRegisterSingletonType<UserSettings>( - "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = ChatPage::instance()->userSettings().data(); - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); - qmlRegisterSingletonType<CallManager>( - "im.nheko", 1, 0, "CallManager", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = ChatPage::instance()->callManager(); - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); + qmlRegisterSingletonInstance("im.nheko", 1, 0, "Communities", self->communities_); + qmlRegisterSingletonInstance( + "im.nheko", 1, 0, "Settings", ChatPage::instance()->userSettings().data()); + qmlRegisterSingletonInstance( + "im.nheko", 1, 0, "CallManager", ChatPage::instance()->callManager()); qmlRegisterSingletonType<Clipboard>( "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * { return new Clipboard(); @@ -264,6 +244,10 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * { return new Nheko(); }); + qmlRegisterSingletonType<SelfVerificationStatus>( + "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * { + return new SelfVerificationStatus(); + }); qRegisterMetaType<mtx::events::collections::TimelineEvents>(); qRegisterMetaType<std::vector<DeviceInfo>>(); diff --git a/src/ui/UIA.cpp b/src/ui/UIA.cpp new file mode 100644 index 00000000..29161382 --- /dev/null +++ b/src/ui/UIA.cpp @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "UIA.h" + +#include <algorithm> + +#include <QInputDialog> +#include <QTimer> + +#include "Logging.h" +#include "MainWindow.h" +#include "dialogs/FallbackAuth.h" +#include "dialogs/ReCaptcha.h" + +UIA * +UIA::instance() +{ + static UIA uia; + return &uia; +} + +mtx::http::UIAHandler +UIA::genericHandler(QString context) +{ + return mtx::http::UIAHandler([this, context](const mtx::http::UIAHandler &h, + const mtx::user_interactive::Unauthorized &u) { + QTimer::singleShot(0, this, [this, h, u, context]() { + this->currentHandler = h; + this->currentStatus = u; + this->title_ = context; + emit titleChanged(); + + std::vector<mtx::user_interactive::Flow> flows = u.flows; + + nhlog::ui()->info("Completed stages: {}", u.completed.size()); + + if (!u.completed.empty()) { + // Get rid of all flows which don't start with the sequence of + // stages that have already been completed. + flows.erase(std::remove_if(flows.begin(), + flows.end(), + [completed_stages = u.completed](auto flow) { + if (completed_stages.size() > flow.stages.size()) + return true; + for (size_t f = 0; f < completed_stages.size(); f++) + if (completed_stages[f] != flow.stages[f]) + return true; + return false; + }), + flows.end()); + } + + if (flows.empty()) { + nhlog::ui()->error("No available registration flows!"); + return; + } + + auto current_stage = flows.front().stages.at(u.completed.size()); + + if (current_stage == mtx::user_interactive::auth_types::password) { + emit password(); + } else if (current_stage == mtx::user_interactive::auth_types::recaptcha) { + auto captchaDialog = + new dialogs::ReCaptcha(QString::fromStdString(u.session), MainWindow::instance()); + captchaDialog->setWindowTitle(context); + + connect( + captchaDialog, &dialogs::ReCaptcha::confirmation, this, [captchaDialog, h, u]() { + captchaDialog->close(); + captchaDialog->deleteLater(); + h.next(mtx::user_interactive::Auth{u.session, + mtx::user_interactive::auth::Fallback{}}); + }); + + // connect( + // captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred); + + QTimer::singleShot(0, this, [captchaDialog]() { captchaDialog->show(); }); + + } else if (current_stage == mtx::user_interactive::auth_types::dummy) { + h.next( + mtx::user_interactive::Auth{u.session, mtx::user_interactive::auth::Dummy{}}); + + } else if (current_stage == mtx::user_interactive::auth_types::registration_token) { + bool ok; + QString token = + QInputDialog::getText(MainWindow::instance(), + context, + tr("Please enter a valid registration token."), + QLineEdit::Normal, + QString(), + &ok); + + if (ok) { + h.next(mtx::user_interactive::Auth{ + u.session, + mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); + } else { + // emit errorOccurred(); + } + } else { + // use fallback + auto dialog = new dialogs::FallbackAuth(QString::fromStdString(current_stage), + QString::fromStdString(u.session), + MainWindow::instance()); + dialog->setWindowTitle(context); + + connect(dialog, &dialogs::FallbackAuth::confirmation, this, [h, u, dialog]() { + dialog->close(); + dialog->deleteLater(); + h.next(mtx::user_interactive::Auth{u.session, + mtx::user_interactive::auth::Fallback{}}); + }); + + // connect(dialog, &dialogs::FallbackAuth::cancel, this, + // &RegisterPage::errorOccurred); + + dialog->show(); + } + }); + }); +} + +void +UIA::continuePassword(QString password) +{ + mtx::user_interactive::auth::Password p{}; + p.identifier_type = mtx::user_interactive::auth::Password::UserId; + p.password = password.toStdString(); + p.identifier_user = http::client()->user_id().to_string(); + + if (currentHandler) + currentHandler->next(mtx::user_interactive::Auth{currentStatus.session, p}); +} diff --git a/src/ui/UIA.h b/src/ui/UIA.h new file mode 100644 index 00000000..fb047451 --- /dev/null +++ b/src/ui/UIA.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <QObject> + +#include <MatrixClient.h> + +class UIA : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString title READ title NOTIFY titleChanged) + +public: + static UIA *instance(); + + UIA(QObject *parent = nullptr) + : QObject(parent) + {} + + mtx::http::UIAHandler genericHandler(QString context); + + QString title() const { return title_; } + +public slots: + void continuePassword(QString password); + +signals: + void password(); + + void titleChanged(); + +private: + std::optional<mtx::http::UIAHandler> currentHandler; + mtx::user_interactive::Unauthorized currentStatus; + QString title_; +}; |