mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 20:48:52 +03:00
Merge pull request #355 from Nheko-Reborn/ssss
Cross-signing with self and user signing keys
This commit is contained in:
commit
45701b0896
17 changed files with 735 additions and 78 deletions
|
@ -12,3 +12,4 @@ brew "gstreamer"
|
||||||
brew "gst-plugins-base"
|
brew "gst-plugins-base"
|
||||||
brew "gst-plugins-good"
|
brew "gst-plugins-good"
|
||||||
brew "gst-plugins-bad"
|
brew "gst-plugins-bad"
|
||||||
|
brew "qtkeychain"
|
||||||
|
|
|
@ -41,6 +41,8 @@ option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++."
|
||||||
${HUNTER_ENABLED})
|
${HUNTER_ENABLED})
|
||||||
option(USE_BUNDLED_TWEENY "Use the bundled version of tweeny."
|
option(USE_BUNDLED_TWEENY "Use the bundled version of tweeny."
|
||||||
${HUNTER_ENABLED})
|
${HUNTER_ENABLED})
|
||||||
|
option(USE_BUNDLED_QTKEYCHAIN "Use the bundled version of Qt5Keychain."
|
||||||
|
${HUNTER_ENABLED})
|
||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
|
@ -137,6 +139,24 @@ find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia
|
||||||
find_package(Qt5QuickCompiler)
|
find_package(Qt5QuickCompiler)
|
||||||
find_package(Qt5DBus)
|
find_package(Qt5DBus)
|
||||||
|
|
||||||
|
if (USE_BUNDLED_QTKEYCHAIN)
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
qt5keychain
|
||||||
|
GIT_REPOSITORY https://github.com/frankosterfeld/qtkeychain.git
|
||||||
|
GIT_TAG v0.12.0
|
||||||
|
)
|
||||||
|
if (BUILD_SHARED_LIBS)
|
||||||
|
set(QTKEYCHAIN_STATIC OFF CACHE INTERNAL "")
|
||||||
|
else()
|
||||||
|
set(QTKEYCHAIN_STATIC ON CACHE INTERNAL "")
|
||||||
|
endif()
|
||||||
|
set(BUILD_TEST_APPLICATION OFF CACHE INTERNAL "")
|
||||||
|
FetchContent_MakeAvailable(qt5keychain)
|
||||||
|
else()
|
||||||
|
find_package(Qt5Keychain REQUIRED)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
find_package(Qt5MacExtras REQUIRED)
|
find_package(Qt5MacExtras REQUIRED)
|
||||||
endif(APPLE)
|
endif(APPLE)
|
||||||
|
@ -333,25 +353,25 @@ endif()
|
||||||
find_package(OpenSSL 1.1.0 REQUIRED)
|
find_package(OpenSSL 1.1.0 REQUIRED)
|
||||||
if(USE_BUNDLED_MTXCLIENT)
|
if(USE_BUNDLED_MTXCLIENT)
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
|
||||||
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
MatrixClient
|
MatrixClient
|
||||||
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
||||||
GIT_TAG ed6315563409ce9d47978ff2a2d771b863e375c5
|
GIT_TAG ce8bc9c3dd6bba432e716f55136133111b0186e7
|
||||||
)
|
)
|
||||||
|
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
||||||
|
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
||||||
FetchContent_MakeAvailable(MatrixClient)
|
FetchContent_MakeAvailable(MatrixClient)
|
||||||
else()
|
else()
|
||||||
find_package(MatrixClient 0.3.1 REQUIRED)
|
find_package(MatrixClient 0.3.1 REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
if(USE_BUNDLED_OLM)
|
if(USE_BUNDLED_OLM)
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
set(OLM_TESTS OFF CACHE INTERNAL "")
|
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
Olm
|
Olm
|
||||||
GIT_REPOSITORY https://gitlab.matrix.org/matrix-org/olm.git
|
GIT_REPOSITORY https://gitlab.matrix.org/matrix-org/olm.git
|
||||||
GIT_TAG 3.1.4
|
GIT_TAG 3.1.4
|
||||||
)
|
)
|
||||||
|
set(OLM_TESTS OFF CACHE INTERNAL "")
|
||||||
FetchContent_MakeAvailable(Olm)
|
FetchContent_MakeAvailable(Olm)
|
||||||
else()
|
else()
|
||||||
find_package(Olm 3)
|
find_package(Olm 3)
|
||||||
|
@ -573,6 +593,11 @@ else()
|
||||||
endif()
|
endif()
|
||||||
target_include_directories(nheko PRIVATE src includes third_party/blurhash third_party/cpp-httplib-0.5.12)
|
target_include_directories(nheko PRIVATE src includes third_party/blurhash third_party/cpp-httplib-0.5.12)
|
||||||
|
|
||||||
|
# Fixup bundled keychain include dirs
|
||||||
|
if (USE_BUNDLED_QTKEYCHAIN)
|
||||||
|
target_include_directories(nheko PRIVATE ${qt5keychain_SOURCE_DIR} ${qt5keychain_BINARY_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(nheko PRIVATE
|
target_link_libraries(nheko PRIVATE
|
||||||
MatrixClient::MatrixClient
|
MatrixClient::MatrixClient
|
||||||
Boost::iostreams
|
Boost::iostreams
|
||||||
|
@ -587,6 +612,7 @@ target_link_libraries(nheko PRIVATE
|
||||||
Qt5::Qml
|
Qt5::Qml
|
||||||
Qt5::QuickControls2
|
Qt5::QuickControls2
|
||||||
Qt5::QuickWidgets
|
Qt5::QuickWidgets
|
||||||
|
qt5keychain
|
||||||
nlohmann_json::nlohmann_json
|
nlohmann_json::nlohmann_json
|
||||||
lmdbxx::lmdbxx
|
lmdbxx::lmdbxx
|
||||||
liblmdb::lmdb
|
liblmdb::lmdb
|
||||||
|
|
|
@ -94,6 +94,22 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"config-opts": [
|
||||||
|
"-DCMAKE_BUILD_TYPE=Release",
|
||||||
|
"-DBUILD_TEST_APPLICATION=OFF"
|
||||||
|
],
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"name": "QtKeychain",
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"commit": "815fe610353ff8ad7e2f1121c368a74df8db5eb7",
|
||||||
|
"tag": "v0.12.0",
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/frankosterfeld/qtkeychain.git"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"config-opts":[
|
"config-opts":[
|
||||||
"-DJSON_BuildTests=OFF"
|
"-DJSON_BuildTests=OFF"
|
||||||
|
@ -145,7 +161,7 @@
|
||||||
"name": "mtxclient",
|
"name": "mtxclient",
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"commit": "ed6315563409ce9d47978ff2a2d771b863e375c5",
|
"commit": "ce8bc9c3dd6bba432e716f55136133111b0186e7",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Nheko-Reborn/mtxclient.git"
|
"url": "https://github.com/Nheko-Reborn/mtxclient.git"
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,14 @@
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QSettings>
|
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
#if __has_include(<keychain.h>)
|
||||||
|
#include <keychain.h>
|
||||||
|
#else
|
||||||
|
#include <qt5keychain/keychain.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <mtx/responses/common.hpp>
|
#include <mtx/responses/common.hpp>
|
||||||
|
|
||||||
#include "Cache.h"
|
#include "Cache.h"
|
||||||
|
@ -569,6 +574,64 @@ Cache::restoreOlmAccount()
|
||||||
return std::string(pickled.data(), pickled.size());
|
return std::string(pickled.data(), pickled.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Cache::storeSecret(const std::string &name, const std::string &secret)
|
||||||
|
{
|
||||||
|
QKeychain::WritePasswordJob job(QCoreApplication::applicationName());
|
||||||
|
job.setAutoDelete(false);
|
||||||
|
job.setInsecureFallback(true);
|
||||||
|
job.setKey(QString::fromStdString(name));
|
||||||
|
job.setTextData(QString::fromStdString(secret));
|
||||||
|
QEventLoop loop;
|
||||||
|
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||||
|
job.start();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
if (job.error()) {
|
||||||
|
nhlog::db()->warn(
|
||||||
|
"Storing secret '{}' failed: {}", name, job.errorString().toStdString());
|
||||||
|
} else {
|
||||||
|
emit secretChanged(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Cache::deleteSecret(const std::string &name)
|
||||||
|
{
|
||||||
|
QKeychain::DeletePasswordJob job(QCoreApplication::applicationName());
|
||||||
|
job.setAutoDelete(false);
|
||||||
|
job.setInsecureFallback(true);
|
||||||
|
job.setKey(QString::fromStdString(name));
|
||||||
|
QEventLoop loop;
|
||||||
|
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||||
|
job.start();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
emit secretChanged(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string>
|
||||||
|
Cache::secret(const std::string &name)
|
||||||
|
{
|
||||||
|
QKeychain::ReadPasswordJob job(QCoreApplication::applicationName());
|
||||||
|
job.setAutoDelete(false);
|
||||||
|
job.setInsecureFallback(true);
|
||||||
|
job.setKey(QString::fromStdString(name));
|
||||||
|
QEventLoop loop;
|
||||||
|
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||||
|
job.start();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
const QString secret = job.textData();
|
||||||
|
if (job.error()) {
|
||||||
|
nhlog::db()->debug(
|
||||||
|
"Restoring secret '{}' failed: {}", name, job.errorString().toStdString());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return secret.toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Media Management
|
// Media Management
|
||||||
//
|
//
|
||||||
|
@ -726,10 +789,32 @@ void
|
||||||
Cache::deleteData()
|
Cache::deleteData()
|
||||||
{
|
{
|
||||||
// TODO: We need to remove the env_ while not accepting new requests.
|
// TODO: We need to remove the env_ while not accepting new requests.
|
||||||
|
lmdb::dbi_close(env_, syncStateDb_);
|
||||||
|
lmdb::dbi_close(env_, roomsDb_);
|
||||||
|
lmdb::dbi_close(env_, invitesDb_);
|
||||||
|
lmdb::dbi_close(env_, mediaDb_);
|
||||||
|
lmdb::dbi_close(env_, readReceiptsDb_);
|
||||||
|
lmdb::dbi_close(env_, notificationsDb_);
|
||||||
|
|
||||||
|
lmdb::dbi_close(env_, devicesDb_);
|
||||||
|
lmdb::dbi_close(env_, deviceKeysDb_);
|
||||||
|
|
||||||
|
lmdb::dbi_close(env_, inboundMegolmSessionDb_);
|
||||||
|
lmdb::dbi_close(env_, outboundMegolmSessionDb_);
|
||||||
|
|
||||||
|
env_.close();
|
||||||
|
|
||||||
|
verification_storage.status.clear();
|
||||||
|
|
||||||
if (!cacheDirectory_.isEmpty()) {
|
if (!cacheDirectory_.isEmpty()) {
|
||||||
QDir(cacheDirectory_).removeRecursively();
|
QDir(cacheDirectory_).removeRecursively();
|
||||||
nhlog::db()->info("deleted cache files from disk");
|
nhlog::db()->info("deleted cache files from disk");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1);
|
||||||
|
deleteSecret(mtx::secret_storage::secrets::cross_signing_master);
|
||||||
|
deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing);
|
||||||
|
deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing);
|
||||||
}
|
}
|
||||||
|
|
||||||
//! migrates db to the current format
|
//! migrates db to the current format
|
||||||
|
@ -4262,4 +4347,15 @@ restoreOlmAccount()
|
||||||
{
|
{
|
||||||
return instance_->restoreOlmAccount();
|
return instance_->restoreOlmAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
storeSecret(const std::string &name, const std::string &secret)
|
||||||
|
{
|
||||||
|
instance_->storeSecret(name, secret);
|
||||||
|
}
|
||||||
|
std::optional<std::string>
|
||||||
|
secret(const std::string &name)
|
||||||
|
{
|
||||||
|
return instance_->secret(name);
|
||||||
|
}
|
||||||
} // namespace cache
|
} // namespace cache
|
||||||
|
|
|
@ -282,4 +282,9 @@ saveOlmAccount(const std::string &pickled);
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
restoreOlmAccount();
|
restoreOlmAccount();
|
||||||
|
|
||||||
|
void
|
||||||
|
storeSecret(const std::string &name, const std::string &secret);
|
||||||
|
std::optional<std::string>
|
||||||
|
secret(const std::string &name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,6 +269,10 @@ public:
|
||||||
void saveOlmAccount(const std::string &pickled);
|
void saveOlmAccount(const std::string &pickled);
|
||||||
std::string restoreOlmAccount();
|
std::string restoreOlmAccount();
|
||||||
|
|
||||||
|
void storeSecret(const std::string &name, const std::string &secret);
|
||||||
|
void deleteSecret(const std::string &name);
|
||||||
|
std::optional<std::string> secret(const std::string &name);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
||||||
void roomReadStatus(const std::map<QString, bool> &status);
|
void roomReadStatus(const std::map<QString, bool> &status);
|
||||||
|
@ -276,6 +280,7 @@ signals:
|
||||||
void userKeysUpdate(const std::string &sync_token,
|
void userKeysUpdate(const std::string &sync_token,
|
||||||
const mtx::responses::QueryKeys &keyQuery);
|
const mtx::responses::QueryKeys &keyQuery);
|
||||||
void verificationStatusChanged(const std::string &userid);
|
void verificationStatusChanged(const std::string &userid);
|
||||||
|
void secretChanged(const std::string name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//! Save an invited room.
|
//! Save an invited room.
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
|
#include <QInputDialog>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
|
@ -64,6 +65,8 @@ constexpr size_t MAX_ONETIME_KEYS = 50;
|
||||||
Q_DECLARE_METATYPE(std::optional<mtx::crypto::EncryptedFile>)
|
Q_DECLARE_METATYPE(std::optional<mtx::crypto::EncryptedFile>)
|
||||||
Q_DECLARE_METATYPE(std::optional<RelatedInfo>)
|
Q_DECLARE_METATYPE(std::optional<RelatedInfo>)
|
||||||
Q_DECLARE_METATYPE(mtx::presence::PresenceState)
|
Q_DECLARE_METATYPE(mtx::presence::PresenceState)
|
||||||
|
Q_DECLARE_METATYPE(mtx::secret_storage::AesHmacSha2KeyDescription)
|
||||||
|
Q_DECLARE_METATYPE(SecretsToDecrypt)
|
||||||
|
|
||||||
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
|
@ -79,6 +82,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||||
qRegisterMetaType<std::optional<mtx::crypto::EncryptedFile>>();
|
qRegisterMetaType<std::optional<mtx::crypto::EncryptedFile>>();
|
||||||
qRegisterMetaType<std::optional<RelatedInfo>>();
|
qRegisterMetaType<std::optional<RelatedInfo>>();
|
||||||
qRegisterMetaType<mtx::presence::PresenceState>();
|
qRegisterMetaType<mtx::presence::PresenceState>();
|
||||||
|
qRegisterMetaType<mtx::secret_storage::AesHmacSha2KeyDescription>();
|
||||||
|
qRegisterMetaType<SecretsToDecrypt>();
|
||||||
|
|
||||||
topLayout_ = new QHBoxLayout(this);
|
topLayout_ = new QHBoxLayout(this);
|
||||||
topLayout_->setSpacing(0);
|
topLayout_->setSpacing(0);
|
||||||
|
@ -136,6 +141,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||||
splitter->addWidget(content_);
|
splitter->addWidget(content_);
|
||||||
splitter->restoreSizes(parent->width());
|
splitter->restoreSizes(parent->width());
|
||||||
|
|
||||||
|
connect(this,
|
||||||
|
&ChatPage::downloadedSecrets,
|
||||||
|
this,
|
||||||
|
&ChatPage::decryptDownloadedSecrets,
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
connect(this, &ChatPage::connectionLost, this, [this]() {
|
connect(this, &ChatPage::connectionLost, this, [this]() {
|
||||||
nhlog::net()->info("connectivity lost");
|
nhlog::net()->info("connectivity lost");
|
||||||
isConnected_ = false;
|
isConnected_ = false;
|
||||||
|
@ -372,9 +383,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||||
void
|
void
|
||||||
ChatPage::logout()
|
ChatPage::logout()
|
||||||
{
|
{
|
||||||
deleteConfigs();
|
|
||||||
|
|
||||||
resetUI();
|
resetUI();
|
||||||
|
deleteConfigs();
|
||||||
|
|
||||||
emit closing();
|
emit closing();
|
||||||
connectivityTimer_.stop();
|
connectivityTimer_.stop();
|
||||||
|
@ -385,12 +395,12 @@ ChatPage::dropToLoginPage(const QString &msg)
|
||||||
{
|
{
|
||||||
nhlog::ui()->info("dropping to the login page: {}", msg.toStdString());
|
nhlog::ui()->info("dropping to the login page: {}", msg.toStdString());
|
||||||
|
|
||||||
deleteConfigs();
|
|
||||||
resetUI();
|
|
||||||
|
|
||||||
http::client()->shutdown();
|
http::client()->shutdown();
|
||||||
connectivityTimer_.stop();
|
connectivityTimer_.stop();
|
||||||
|
|
||||||
|
resetUI();
|
||||||
|
deleteConfigs();
|
||||||
|
|
||||||
emit showLoginPage(msg);
|
emit showLoginPage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,8 +428,8 @@ ChatPage::deleteConfigs()
|
||||||
settings.remove("");
|
settings.remove("");
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
|
http::client()->shutdown();
|
||||||
cache::deleteData();
|
cache::deleteData();
|
||||||
http::client()->clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -1209,3 +1219,45 @@ ChatPage::connectCallMessage()
|
||||||
view_manager_,
|
view_manager_,
|
||||||
qOverload<const QString &, const T &>(&TimelineViewManager::queueCallMessage));
|
qOverload<const QString &, const T &>(&TimelineViewManager::queueCallMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
|
||||||
|
const SecretsToDecrypt &secrets)
|
||||||
|
{
|
||||||
|
QString text = QInputDialog::getText(
|
||||||
|
ChatPage::instance(),
|
||||||
|
QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"),
|
||||||
|
keyDesc.name.empty()
|
||||||
|
? QCoreApplication::translate(
|
||||||
|
"CrossSigningSecrets",
|
||||||
|
"Enter your recovery key or passphrase to decrypt your secrets:")
|
||||||
|
: QCoreApplication::translate(
|
||||||
|
"CrossSigningSecrets",
|
||||||
|
"Enter your recovery key or passphrase called %1 to decrypt your secrets:")
|
||||||
|
.arg(QString::fromStdString(keyDesc.name)),
|
||||||
|
QLineEdit::Password);
|
||||||
|
|
||||||
|
if (text.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto decryptionKey = mtx::crypto::key_from_recoverykey(text.toStdString(), keyDesc);
|
||||||
|
|
||||||
|
if (!decryptionKey)
|
||||||
|
decryptionKey = mtx::crypto::key_from_passphrase(text.toStdString(), keyDesc);
|
||||||
|
|
||||||
|
if (!decryptionKey) {
|
||||||
|
QMessageBox::information(
|
||||||
|
ChatPage::instance(),
|
||||||
|
QCoreApplication::translate("CrossSigningSecrets", "Decrytion failed"),
|
||||||
|
QCoreApplication::translate("CrossSigningSecrets",
|
||||||
|
"Failed to decrypt secrets with the "
|
||||||
|
"provided recovery key or passphrase"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &[secretName, encryptedSecret] : secrets) {
|
||||||
|
auto decrypted = mtx::crypto::decrypt(encryptedSecret, *decryptionKey, secretName);
|
||||||
|
if (!decrypted.empty())
|
||||||
|
cache::storeSecret(secretName, decrypted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include <mtx/events/encrypted.hpp>
|
#include <mtx/events/encrypted.hpp>
|
||||||
#include <mtx/events/member.hpp>
|
#include <mtx/events/member.hpp>
|
||||||
#include <mtx/events/presence.hpp>
|
#include <mtx/events/presence.hpp>
|
||||||
|
#include <mtx/secret_storage.hpp>
|
||||||
|
|
||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
|
@ -72,6 +73,8 @@ namespace popups {
|
||||||
class UserMentions;
|
class UserMentions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using SecretsToDecrypt = std::map<std::string, mtx::secret_storage::AesHmacSha2EncryptedData>;
|
||||||
|
|
||||||
class ChatPage : public QWidget
|
class ChatPage : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -117,6 +120,8 @@ public slots:
|
||||||
void unbanUser(QString userid, QString reason);
|
void unbanUser(QString userid, QString reason);
|
||||||
|
|
||||||
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
|
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
|
||||||
|
void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
|
||||||
|
const SecretsToDecrypt &secrets);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectionLost();
|
void connectionLost();
|
||||||
|
@ -185,6 +190,9 @@ signals:
|
||||||
void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
|
void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
|
||||||
void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
|
void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
|
||||||
|
|
||||||
|
void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
|
||||||
|
const SecretsToDecrypt &secrets);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void logout();
|
void logout();
|
||||||
void removeRoom(const QString &room_id);
|
void removeRoom(const QString &room_id);
|
||||||
|
|
|
@ -275,11 +275,66 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
|
||||||
req.signatures[utils::localUser().toStdString()]
|
req.signatures[utils::localUser().toStdString()]
|
||||||
[master_key.keys.at(mac.first)] =
|
[master_key.keys.at(mac.first)] =
|
||||||
master_key;
|
master_key;
|
||||||
|
} else if (mac.first ==
|
||||||
|
"ed25519:" + this->deviceId.toStdString()) {
|
||||||
|
// Sign their device key with self signing key
|
||||||
|
|
||||||
|
auto device_id = this->deviceId.toStdString();
|
||||||
|
|
||||||
|
if (their_keys.device_keys.count(device_id)) {
|
||||||
|
json j =
|
||||||
|
their_keys.device_keys.at(device_id);
|
||||||
|
j.erase("signatures");
|
||||||
|
j.erase("unsigned");
|
||||||
|
|
||||||
|
auto secret = cache::secret(
|
||||||
|
mtx::secret_storage::secrets::
|
||||||
|
cross_signing_self_signing);
|
||||||
|
if (!secret)
|
||||||
|
continue;
|
||||||
|
auto ssk =
|
||||||
|
mtx::crypto::PkSigning::from_seed(
|
||||||
|
*secret);
|
||||||
|
|
||||||
|
mtx::crypto::DeviceKeys dev = j;
|
||||||
|
dev.signatures
|
||||||
|
[utils::localUser().toStdString()]
|
||||||
|
["ed25519:" + ssk.public_key()] =
|
||||||
|
ssk.sign(j.dump());
|
||||||
|
|
||||||
|
req.signatures[utils::localUser()
|
||||||
|
.toStdString()]
|
||||||
|
[device_id] = dev;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO(Nico): Sign their device key with self signing key
|
|
||||||
} else {
|
} else {
|
||||||
// TODO(Nico): Sign their master key with user signing key
|
// Sign their master key with user signing key
|
||||||
|
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");
|
||||||
|
|
||||||
|
auto secret =
|
||||||
|
cache::secret(mtx::secret_storage::secrets::
|
||||||
|
cross_signing_user_signing);
|
||||||
|
if (!secret)
|
||||||
|
continue;
|
||||||
|
auto usk =
|
||||||
|
mtx::crypto::PkSigning::from_seed(*secret);
|
||||||
|
|
||||||
|
mtx::crypto::CrossSigningKeys master_key = j;
|
||||||
|
master_key
|
||||||
|
.signatures[utils::localUser().toStdString()]
|
||||||
|
["ed25519:" + usk.public_key()] =
|
||||||
|
usk.sign(j.dump());
|
||||||
|
|
||||||
|
req.signatures[toClient.to_string()]
|
||||||
|
[master_key.keys.at(mac.first)] =
|
||||||
|
master_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.signatures.empty()) {
|
if (!req.signatures.empty()) {
|
||||||
|
@ -706,6 +761,14 @@ DeviceVerificationFlow::acceptDevice()
|
||||||
cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString());
|
cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString());
|
||||||
this->sendVerificationDone();
|
this->sendVerificationDone();
|
||||||
setState(Success);
|
setState(Success);
|
||||||
|
|
||||||
|
// Request secrets. We should probably check somehow, if a device knowns about the
|
||||||
|
// secrets.
|
||||||
|
if (utils::localUser().toStdString() == this->toClient.to_string() &&
|
||||||
|
(!cache::secret(mtx::secret_storage::secrets::cross_signing_self_signing) ||
|
||||||
|
!cache::secret(mtx::secret_storage::secrets::cross_signing_user_signing))) {
|
||||||
|
olm::request_cross_signing_keys();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include <mtx/responses/login.hpp>
|
#include <mtx/responses/login.hpp>
|
||||||
|
|
||||||
#include "Cache.h"
|
#include "Cache.h"
|
||||||
|
#include "Cache_p.h"
|
||||||
#include "ChatPage.h"
|
#include "ChatPage.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
|
@ -294,6 +295,10 @@ MainWindow::showChatPage()
|
||||||
|
|
||||||
login_page_->reset();
|
login_page_->reset();
|
||||||
chat_page_->bootstrap(userid, homeserver, token);
|
chat_page_->bootstrap(userid, homeserver, token);
|
||||||
|
connect(cache::client(),
|
||||||
|
&Cache::secretChanged,
|
||||||
|
userSettingsPage_,
|
||||||
|
&UserSettingsPage::updateSecretStatus);
|
||||||
|
|
||||||
instance_ = this;
|
instance_ = this;
|
||||||
}
|
}
|
||||||
|
|
384
src/Olm.cpp
384
src/Olm.cpp
|
@ -1,9 +1,14 @@
|
||||||
#include "Olm.h"
|
#include "Olm.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
#include <mtx/responses/common.hpp>
|
||||||
|
#include <mtx/secret_storage.hpp>
|
||||||
|
|
||||||
#include "Cache.h"
|
#include "Cache.h"
|
||||||
#include "Cache_p.h"
|
#include "Cache_p.h"
|
||||||
#include "ChatPage.h"
|
#include "ChatPage.h"
|
||||||
|
@ -13,11 +18,13 @@
|
||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
static const std::string STORAGE_SECRET_KEY("secret");
|
|
||||||
constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2";
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
auto client_ = std::make_unique<mtx::crypto::OlmClient>();
|
auto client_ = std::make_unique<mtx::crypto::OlmClient>();
|
||||||
|
|
||||||
|
std::map<std::string, std::string> request_id_to_secret_name;
|
||||||
|
|
||||||
|
const std::string STORAGE_SECRET_KEY("secret");
|
||||||
|
constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2";
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace olm {
|
namespace olm {
|
||||||
|
@ -43,6 +50,54 @@ client()
|
||||||
return client_.get();
|
return client_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
handle_secret_request(const mtx::events::DeviceEvent<mtx::events::msg::SecretRequest> *e,
|
||||||
|
const std::string &sender)
|
||||||
|
{
|
||||||
|
using namespace mtx::events;
|
||||||
|
|
||||||
|
if (e->content.action != mtx::events::msg::RequestAction::Request)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto local_user = http::client()->user_id();
|
||||||
|
|
||||||
|
if (sender != local_user.to_string())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto verificationStatus = cache::verificationStatus(local_user.to_string());
|
||||||
|
|
||||||
|
if (!verificationStatus)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto deviceKeys = cache::userKeys(local_user.to_string());
|
||||||
|
if (!deviceKeys)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (std::find(verificationStatus->verified_devices.begin(),
|
||||||
|
verificationStatus->verified_devices.end(),
|
||||||
|
e->content.requesting_device_id) ==
|
||||||
|
verificationStatus->verified_devices.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// this is a verified device
|
||||||
|
mtx::events::DeviceEvent<mtx::events::msg::SecretSend> secretSend;
|
||||||
|
secretSend.type = EventType::SecretSend;
|
||||||
|
secretSend.content.request_id = e->content.request_id;
|
||||||
|
|
||||||
|
auto secret = cache::client()->secret(e->content.name);
|
||||||
|
if (!secret)
|
||||||
|
return;
|
||||||
|
secretSend.content.secret = secret.value();
|
||||||
|
|
||||||
|
send_encrypted_to_device_messages(
|
||||||
|
{{local_user.to_string(), {{e->content.requesting_device_id}}}}, secretSend);
|
||||||
|
|
||||||
|
nhlog::net()->info("Sent secret '{}' to ({},{})",
|
||||||
|
e->content.name,
|
||||||
|
local_user.to_string(),
|
||||||
|
e->content.requesting_device_id);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
handle_to_device_messages(const std::vector<mtx::events::collections::DeviceEvents> &msgs)
|
handle_to_device_messages(const std::vector<mtx::events::collections::DeviceEvents> &msgs)
|
||||||
{
|
{
|
||||||
|
@ -127,6 +182,10 @@ handle_to_device_messages(const std::vector<mtx::events::collections::DeviceEven
|
||||||
std::get<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationDone>>(
|
std::get<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationDone>>(
|
||||||
msg);
|
msg);
|
||||||
ChatPage::instance()->receivedDeviceVerificationDone(message.content);
|
ChatPage::instance()->receivedDeviceVerificationDone(message.content);
|
||||||
|
} else if (auto e =
|
||||||
|
std::get_if<mtx::events::DeviceEvent<mtx::events::msg::SecretRequest>>(
|
||||||
|
&msg)) {
|
||||||
|
handle_secret_request(e, e->sender);
|
||||||
} else {
|
} else {
|
||||||
nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2));
|
nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2));
|
||||||
}
|
}
|
||||||
|
@ -163,59 +222,137 @@ handle_olm_message(const OlmMessage &msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!payload.is_null()) {
|
if (!payload.is_null()) {
|
||||||
std::string msg_type = payload["type"];
|
mtx::events::collections::DeviceEvents device_event;
|
||||||
|
|
||||||
if (msg_type == to_string(mtx::events::EventType::KeyVerificationAccept)) {
|
{
|
||||||
ChatPage::instance()->receivedDeviceVerificationAccept(
|
std::string msg_type = payload["type"];
|
||||||
payload["content"]);
|
json event_array = json::array();
|
||||||
return;
|
event_array.push_back(payload);
|
||||||
} else if (msg_type ==
|
|
||||||
to_string(mtx::events::EventType::KeyVerificationRequest)) {
|
std::vector<mtx::events::collections::DeviceEvents> temp_events;
|
||||||
ChatPage::instance()->receivedDeviceVerificationRequest(
|
mtx::responses::utils::parse_device_events(event_array,
|
||||||
payload["content"], payload["sender"]);
|
temp_events);
|
||||||
return;
|
if (temp_events.empty()) {
|
||||||
} else if (msg_type ==
|
nhlog::crypto()->warn("Decrypted unknown event: {}",
|
||||||
to_string(mtx::events::EventType::KeyVerificationCancel)) {
|
payload.dump());
|
||||||
ChatPage::instance()->receivedDeviceVerificationCancel(
|
continue;
|
||||||
payload["content"]);
|
}
|
||||||
return;
|
device_event = temp_events.at(0);
|
||||||
} else if (msg_type ==
|
}
|
||||||
to_string(mtx::events::EventType::KeyVerificationKey)) {
|
|
||||||
ChatPage::instance()->receivedDeviceVerificationKey(
|
using namespace mtx::events;
|
||||||
payload["content"]);
|
if (auto e1 =
|
||||||
return;
|
std::get_if<DeviceEvent<msg::KeyVerificationAccept>>(&device_event)) {
|
||||||
} else if (msg_type ==
|
ChatPage::instance()->receivedDeviceVerificationAccept(e1->content);
|
||||||
to_string(mtx::events::EventType::KeyVerificationMac)) {
|
} else if (auto e2 = std::get_if<DeviceEvent<msg::KeyVerificationRequest>>(
|
||||||
ChatPage::instance()->receivedDeviceVerificationMac(
|
&device_event)) {
|
||||||
payload["content"]);
|
ChatPage::instance()->receivedDeviceVerificationRequest(e2->content,
|
||||||
return;
|
e2->sender);
|
||||||
} else if (msg_type ==
|
} else if (auto e3 = std::get_if<DeviceEvent<msg::KeyVerificationCancel>>(
|
||||||
to_string(mtx::events::EventType::KeyVerificationStart)) {
|
&device_event)) {
|
||||||
ChatPage::instance()->receivedDeviceVerificationStart(
|
ChatPage::instance()->receivedDeviceVerificationCancel(e3->content);
|
||||||
payload["content"], payload["sender"]);
|
} else if (auto e4 = std::get_if<DeviceEvent<msg::KeyVerificationKey>>(
|
||||||
return;
|
&device_event)) {
|
||||||
} else if (msg_type ==
|
ChatPage::instance()->receivedDeviceVerificationKey(e4->content);
|
||||||
to_string(mtx::events::EventType::KeyVerificationReady)) {
|
} else if (auto e5 = std::get_if<DeviceEvent<msg::KeyVerificationMac>>(
|
||||||
ChatPage::instance()->receivedDeviceVerificationReady(
|
&device_event)) {
|
||||||
payload["content"]);
|
ChatPage::instance()->receivedDeviceVerificationMac(e5->content);
|
||||||
return;
|
} else if (auto e6 = std::get_if<DeviceEvent<msg::KeyVerificationStart>>(
|
||||||
} else if (msg_type ==
|
&device_event)) {
|
||||||
to_string(mtx::events::EventType::KeyVerificationDone)) {
|
ChatPage::instance()->receivedDeviceVerificationStart(e6->content,
|
||||||
ChatPage::instance()->receivedDeviceVerificationDone(
|
e6->sender);
|
||||||
payload["content"]);
|
} else if (auto e7 = std::get_if<DeviceEvent<msg::KeyVerificationReady>>(
|
||||||
return;
|
&device_event)) {
|
||||||
} else if (msg_type == to_string(mtx::events::EventType::RoomKey)) {
|
ChatPage::instance()->receivedDeviceVerificationReady(e7->content);
|
||||||
mtx::events::DeviceEvent<mtx::events::msg::RoomKey> roomKey =
|
} else if (auto e8 = std::get_if<DeviceEvent<msg::KeyVerificationDone>>(
|
||||||
payload;
|
&device_event)) {
|
||||||
create_inbound_megolm_session(roomKey, msg.sender_key);
|
ChatPage::instance()->receivedDeviceVerificationDone(e8->content);
|
||||||
return;
|
} else if (auto roomKey =
|
||||||
} else if (msg_type ==
|
std::get_if<DeviceEvent<msg::RoomKey>>(&device_event)) {
|
||||||
to_string(mtx::events::EventType::ForwardedRoomKey)) {
|
create_inbound_megolm_session(*roomKey, msg.sender_key);
|
||||||
mtx::events::DeviceEvent<mtx::events::msg::ForwardedRoomKey>
|
} else if (auto forwardedRoomKey =
|
||||||
roomKey = payload;
|
std::get_if<DeviceEvent<msg::ForwardedRoomKey>>(
|
||||||
import_inbound_megolm_session(roomKey);
|
&device_event)) {
|
||||||
|
import_inbound_megolm_session(*forwardedRoomKey);
|
||||||
|
} else if (auto e =
|
||||||
|
std::get_if<DeviceEvent<msg::SecretSend>>(&device_event)) {
|
||||||
|
auto local_user = http::client()->user_id();
|
||||||
|
|
||||||
|
if (msg.sender != local_user.to_string())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto secret_name =
|
||||||
|
request_id_to_secret_name.find(e->content.request_id);
|
||||||
|
|
||||||
|
if (secret_name != request_id_to_secret_name.end()) {
|
||||||
|
nhlog::crypto()->info("Received secret: {}",
|
||||||
|
secret_name->second);
|
||||||
|
|
||||||
|
mtx::events::msg::SecretRequest secretRequest{};
|
||||||
|
secretRequest.action =
|
||||||
|
mtx::events::msg::RequestAction::Cancellation;
|
||||||
|
secretRequest.requesting_device_id =
|
||||||
|
http::client()->device_id();
|
||||||
|
secretRequest.request_id = e->content.request_id;
|
||||||
|
|
||||||
|
auto verificationStatus =
|
||||||
|
cache::verificationStatus(local_user.to_string());
|
||||||
|
|
||||||
|
if (!verificationStatus)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto deviceKeys = cache::userKeys(local_user.to_string());
|
||||||
|
std::string sender_device_id;
|
||||||
|
if (deviceKeys) {
|
||||||
|
for (auto &[dev, key] : deviceKeys->device_keys) {
|
||||||
|
if (key.keys["curve25519:" + dev] ==
|
||||||
|
msg.sender_key) {
|
||||||
|
sender_device_id = dev;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<
|
||||||
|
mtx::identifiers::User,
|
||||||
|
std::map<std::string, mtx::events::msg::SecretRequest>>
|
||||||
|
body;
|
||||||
|
|
||||||
|
for (const auto &dev :
|
||||||
|
verificationStatus->verified_devices) {
|
||||||
|
if (dev != secretRequest.requesting_device_id &&
|
||||||
|
dev != sender_device_id)
|
||||||
|
body[local_user][dev] = secretRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
http::client()
|
||||||
|
->send_to_device<mtx::events::msg::SecretRequest>(
|
||||||
|
http::client()->generate_txn_id(),
|
||||||
|
body,
|
||||||
|
[name =
|
||||||
|
secret_name->second](mtx::http::RequestErr err) {
|
||||||
|
if (err) {
|
||||||
|
nhlog::net()->error(
|
||||||
|
"Failed to send request cancellation "
|
||||||
|
"for secrect "
|
||||||
|
"'{}'",
|
||||||
|
name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cache::client()->storeSecret(secret_name->second,
|
||||||
|
e->content.secret);
|
||||||
|
|
||||||
|
request_id_to_secret_name.erase(secret_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (auto sec_req =
|
||||||
|
std::get_if<DeviceEvent<msg::SecretRequest>>(&device_event)) {
|
||||||
|
handle_secret_request(sec_req, msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,11 +469,13 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
||||||
// new member, send them the session at this index
|
// new member, send them the session at this index
|
||||||
sendSessionTo[member_it->first] = {};
|
sendSessionTo[member_it->first] = {};
|
||||||
|
|
||||||
|
if (member_it->second) {
|
||||||
for (const auto &dev : member_it->second->device_keys)
|
for (const auto &dev : member_it->second->device_keys)
|
||||||
if (member_it->first != own_user_id ||
|
if (member_it->first != own_user_id ||
|
||||||
dev.first != device_id)
|
dev.first != device_id)
|
||||||
sendSessionTo[member_it->first].push_back(
|
sendSessionTo[member_it->first].push_back(
|
||||||
dev.first);
|
dev.first);
|
||||||
|
}
|
||||||
|
|
||||||
++member_it;
|
++member_it;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1035,4 +1174,143 @@ send_encrypted_to_device_messages(const std::map<std::string, std::vector<std::s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
request_cross_signing_keys()
|
||||||
|
{
|
||||||
|
mtx::events::msg::SecretRequest secretRequest{};
|
||||||
|
secretRequest.action = mtx::events::msg::RequestAction::Request;
|
||||||
|
secretRequest.requesting_device_id = http::client()->device_id();
|
||||||
|
|
||||||
|
auto local_user = http::client()->user_id();
|
||||||
|
|
||||||
|
auto verificationStatus = cache::verificationStatus(local_user.to_string());
|
||||||
|
|
||||||
|
if (!verificationStatus)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto request = [&](std::string secretName) {
|
||||||
|
secretRequest.name = secretName;
|
||||||
|
secretRequest.request_id = "ss." + http::client()->generate_txn_id();
|
||||||
|
|
||||||
|
request_id_to_secret_name[secretRequest.request_id] = secretRequest.name;
|
||||||
|
|
||||||
|
std::map<mtx::identifiers::User,
|
||||||
|
std::map<std::string, mtx::events::msg::SecretRequest>>
|
||||||
|
body;
|
||||||
|
|
||||||
|
for (const auto &dev : verificationStatus->verified_devices) {
|
||||||
|
if (dev != secretRequest.requesting_device_id)
|
||||||
|
body[local_user][dev] = secretRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
http::client()->send_to_device<mtx::events::msg::SecretRequest>(
|
||||||
|
http::client()->generate_txn_id(),
|
||||||
|
body,
|
||||||
|
[request_id = secretRequest.request_id, secretName](mtx::http::RequestErr err) {
|
||||||
|
if (err) {
|
||||||
|
request_id_to_secret_name.erase(request_id);
|
||||||
|
nhlog::net()->error("Failed to send request for secrect '{}'",
|
||||||
|
secretName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto &dev : verificationStatus->verified_devices) {
|
||||||
|
if (dev != secretRequest.requesting_device_id)
|
||||||
|
body[local_user][dev].action =
|
||||||
|
mtx::events::msg::RequestAction::Cancellation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeout after 15 min
|
||||||
|
QTimer::singleShot(15 * 60 * 1000, [secretRequest, body]() {
|
||||||
|
if (request_id_to_secret_name.count(secretRequest.request_id)) {
|
||||||
|
request_id_to_secret_name.erase(secretRequest.request_id);
|
||||||
|
http::client()->send_to_device<mtx::events::msg::SecretRequest>(
|
||||||
|
http::client()->generate_txn_id(),
|
||||||
|
body,
|
||||||
|
[secretRequest](mtx::http::RequestErr err) {
|
||||||
|
if (err) {
|
||||||
|
nhlog::net()->error(
|
||||||
|
"Failed to cancel request for secrect '{}'",
|
||||||
|
secretRequest.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void
|
||||||
|
unlock_secrets(const std::string &key,
|
||||||
|
const std::map<std::string, mtx::secret_storage::AesHmacSha2EncryptedData> &secrets)
|
||||||
|
{
|
||||||
|
http::client()->secret_storage_key(
|
||||||
|
key,
|
||||||
|
[secrets](mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
|
||||||
|
mtx::http::RequestErr err) {
|
||||||
|
if (err) {
|
||||||
|
nhlog::net()->error("Failed to download secret storage key");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit ChatPage::instance()->downloadedSecrets(keyDesc, secrets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
download_cross_signing_keys()
|
||||||
|
{
|
||||||
|
using namespace mtx::secret_storage;
|
||||||
|
http::client()->secret_storage_secret(
|
||||||
|
secrets::megolm_backup_v1, [](Secret secret, mtx::http::RequestErr err) {
|
||||||
|
std::optional<Secret> backup_key;
|
||||||
|
if (!err)
|
||||||
|
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;
|
||||||
|
if (!err)
|
||||||
|
self_signing_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;
|
||||||
|
if (!err)
|
||||||
|
user_signing_key = secret;
|
||||||
|
|
||||||
|
std::map<std::string,
|
||||||
|
std::map<std::string, AesHmacSha2EncryptedData>>
|
||||||
|
secrets;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
for (const auto &[key, secrets] : secrets)
|
||||||
|
unlock_secrets(key, secrets);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace olm
|
} // namespace olm
|
||||||
|
|
|
@ -102,4 +102,11 @@ send_encrypted_to_device_messages(const std::map<std::string, std::vector<std::s
|
||||||
const mtx::events::collections::DeviceEvents &event,
|
const mtx::events::collections::DeviceEvents &event,
|
||||||
bool force_new_session = false);
|
bool force_new_session = false);
|
||||||
|
|
||||||
|
//! Request backup and signing keys and cache them locally
|
||||||
|
void
|
||||||
|
request_cross_signing_keys();
|
||||||
|
//! Download backup and signing keys and cache them locally
|
||||||
|
void
|
||||||
|
download_cross_signing_keys();
|
||||||
|
|
||||||
} // namespace olm
|
} // namespace olm
|
||||||
|
|
|
@ -43,7 +43,11 @@ public:
|
||||||
void initialize(const QMap<QString, RoomInfo> &info);
|
void initialize(const QMap<QString, RoomInfo> &info);
|
||||||
void sync(const std::map<QString, RoomInfo> &info);
|
void sync(const std::map<QString, RoomInfo> &info);
|
||||||
|
|
||||||
void clear() { rooms_.clear(); };
|
void clear()
|
||||||
|
{
|
||||||
|
rooms_.clear();
|
||||||
|
rooms_sort_cache_.clear();
|
||||||
|
};
|
||||||
void updateAvatar(const QString &room_id, const QString &url);
|
void updateAvatar(const QString &room_id, const QString &url);
|
||||||
|
|
||||||
void addRoom(const QString &room_id, const RoomInfo &info);
|
void addRoom(const QString &room_id, const RoomInfo &info);
|
||||||
|
|
|
@ -637,6 +637,15 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
||||||
|
|
||||||
deviceFingerprintValue_->setText(utils::humanReadableFingerprint(QString(44, 'X')));
|
deviceFingerprintValue_->setText(utils::humanReadableFingerprint(QString(44, 'X')));
|
||||||
|
|
||||||
|
backupSecretCached = new QLabel{this};
|
||||||
|
masterSecretCached = new QLabel{this};
|
||||||
|
selfSigningSecretCached = new QLabel{this};
|
||||||
|
userSigningSecretCached = new QLabel{this};
|
||||||
|
backupSecretCached->setFont(monospaceFont);
|
||||||
|
masterSecretCached->setFont(monospaceFont);
|
||||||
|
selfSigningSecretCached->setFont(monospaceFont);
|
||||||
|
userSigningSecretCached->setFont(monospaceFont);
|
||||||
|
|
||||||
auto sessionKeysLabel = new QLabel{tr("Session Keys"), this};
|
auto sessionKeysLabel = new QLabel{tr("Session Keys"), this};
|
||||||
sessionKeysLabel->setFont(font);
|
sessionKeysLabel->setFont(font);
|
||||||
sessionKeysLabel->setMargin(OptionMargin);
|
sessionKeysLabel->setMargin(OptionMargin);
|
||||||
|
@ -649,6 +658,18 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
||||||
sessionKeysLayout->addWidget(sessionKeysExportBtn, 0, Qt::AlignRight);
|
sessionKeysLayout->addWidget(sessionKeysExportBtn, 0, Qt::AlignRight);
|
||||||
sessionKeysLayout->addWidget(sessionKeysImportBtn, 0, Qt::AlignRight);
|
sessionKeysLayout->addWidget(sessionKeysImportBtn, 0, Qt::AlignRight);
|
||||||
|
|
||||||
|
auto crossSigningKeysLabel = new QLabel{tr("Cross Signing Keys"), this};
|
||||||
|
crossSigningKeysLabel->setFont(font);
|
||||||
|
crossSigningKeysLabel->setMargin(OptionMargin);
|
||||||
|
|
||||||
|
auto crossSigningRequestBtn = new QPushButton{tr("REQUEST"), this};
|
||||||
|
auto crossSigningDownloadBtn = new QPushButton{tr("DOWNLOAD"), this};
|
||||||
|
|
||||||
|
auto crossSigningKeysLayout = new QHBoxLayout;
|
||||||
|
crossSigningKeysLayout->addWidget(new QLabel{"", this}, 1, Qt::AlignRight);
|
||||||
|
crossSigningKeysLayout->addWidget(crossSigningRequestBtn, 0, Qt::AlignRight);
|
||||||
|
crossSigningKeysLayout->addWidget(crossSigningDownloadBtn, 0, Qt::AlignRight);
|
||||||
|
|
||||||
auto boxWrap = [this, &font](QString labelText, QWidget *field, QString tooltipText = "") {
|
auto boxWrap = [this, &font](QString labelText, QWidget *field, QString tooltipText = "") {
|
||||||
auto label = new QLabel{labelText, this};
|
auto label = new QLabel{labelText, this};
|
||||||
label->setFont(font);
|
label->setFont(font);
|
||||||
|
@ -787,6 +808,28 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
||||||
tr("Automatically replies to key requests from other users, if they are verified."));
|
tr("Automatically replies to key requests from other users, if they are verified."));
|
||||||
formLayout_->addRow(new HorizontalLine{this});
|
formLayout_->addRow(new HorizontalLine{this});
|
||||||
formLayout_->addRow(sessionKeysLabel, sessionKeysLayout);
|
formLayout_->addRow(sessionKeysLabel, sessionKeysLayout);
|
||||||
|
formLayout_->addRow(crossSigningKeysLabel, crossSigningKeysLayout);
|
||||||
|
|
||||||
|
boxWrap(tr("Master signing key"),
|
||||||
|
masterSecretCached,
|
||||||
|
tr("Your most important key. You don't need to have it cached, since not caching "
|
||||||
|
"it makes it less likely it can be stolen and it is only needed to rotate your "
|
||||||
|
"other signing keys."));
|
||||||
|
boxWrap(tr("User signing key"),
|
||||||
|
userSigningSecretCached,
|
||||||
|
tr("The key to verify other users. If it is cached, verifying a user will verify "
|
||||||
|
"all their devices."));
|
||||||
|
boxWrap(
|
||||||
|
tr("Self signing key"),
|
||||||
|
selfSigningSecretCached,
|
||||||
|
tr("The key to verify your own devices. If it is cached, verifying one of your devices "
|
||||||
|
"will mark it verified for all your other devices and for users, that have verified "
|
||||||
|
"you."));
|
||||||
|
boxWrap(tr("Backup key"),
|
||||||
|
backupSecretCached,
|
||||||
|
tr("The key to decrypt online key backups. If it is cached, you can enable online "
|
||||||
|
"key backup to store encryption keys securely encrypted on the server."));
|
||||||
|
updateSecretStatus();
|
||||||
|
|
||||||
auto scrollArea_ = new QScrollArea{this};
|
auto scrollArea_ = new QScrollArea{this};
|
||||||
scrollArea_->setFrameShape(QFrame::NoFrame);
|
scrollArea_->setFrameShape(QFrame::NoFrame);
|
||||||
|
@ -982,6 +1025,14 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
||||||
connect(
|
connect(
|
||||||
sessionKeysExportBtn, &QPushButton::clicked, this, &UserSettingsPage::exportSessionKeys);
|
sessionKeysExportBtn, &QPushButton::clicked, this, &UserSettingsPage::exportSessionKeys);
|
||||||
|
|
||||||
|
connect(crossSigningRequestBtn, &QPushButton::clicked, this, []() {
|
||||||
|
olm::request_cross_signing_keys();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(crossSigningDownloadBtn, &QPushButton::clicked, this, []() {
|
||||||
|
olm::download_cross_signing_keys();
|
||||||
|
});
|
||||||
|
|
||||||
connect(backBtn_, &QPushButton::clicked, this, [this]() {
|
connect(backBtn_, &QPushButton::clicked, this, [this]() {
|
||||||
settings_->save();
|
settings_->save();
|
||||||
emit moveBack();
|
emit moveBack();
|
||||||
|
@ -1137,3 +1188,30 @@ UserSettingsPage::exportSessionKeys()
|
||||||
QMessageBox::warning(this, tr("Error"), e.what());
|
QMessageBox::warning(this, tr("Error"), e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UserSettingsPage::updateSecretStatus()
|
||||||
|
{
|
||||||
|
QString ok = "QLabel { color : #00cc66; }";
|
||||||
|
QString notSoOk = "QLabel { color : #ff9933; }";
|
||||||
|
|
||||||
|
auto updateLabel = [&ok, ¬SoOk](QLabel *label, const std::string &secretName) {
|
||||||
|
if (cache::secret(secretName)) {
|
||||||
|
label->setStyleSheet(ok);
|
||||||
|
label->setText(tr("CACHED"));
|
||||||
|
} else {
|
||||||
|
if (secretName == mtx::secret_storage::secrets::cross_signing_master)
|
||||||
|
label->setStyleSheet(ok);
|
||||||
|
else
|
||||||
|
label->setStyleSheet(notSoOk);
|
||||||
|
label->setText(tr("NOT CACHED"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateLabel(masterSecretCached, mtx::secret_storage::secrets::cross_signing_master);
|
||||||
|
updateLabel(userSigningSecretCached,
|
||||||
|
mtx::secret_storage::secrets::cross_signing_user_signing);
|
||||||
|
updateLabel(selfSigningSecretCached,
|
||||||
|
mtx::secret_storage::secrets::cross_signing_self_signing);
|
||||||
|
updateLabel(backupSecretCached, mtx::secret_storage::secrets::megolm_backup_v1);
|
||||||
|
}
|
||||||
|
|
|
@ -253,6 +253,9 @@ signals:
|
||||||
void themeChanged();
|
void themeChanged();
|
||||||
void decryptSidebarChanged();
|
void decryptSidebarChanged();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateSecretStatus();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void importSessionKeys();
|
void importSessionKeys();
|
||||||
void exportSessionKeys();
|
void exportSessionKeys();
|
||||||
|
@ -285,6 +288,10 @@ private:
|
||||||
Toggle *mobileMode_;
|
Toggle *mobileMode_;
|
||||||
QLabel *deviceFingerprintValue_;
|
QLabel *deviceFingerprintValue_;
|
||||||
QLabel *deviceIdValue_;
|
QLabel *deviceIdValue_;
|
||||||
|
QLabel *backupSecretCached;
|
||||||
|
QLabel *masterSecretCached;
|
||||||
|
QLabel *selfSigningSecretCached;
|
||||||
|
QLabel *userSigningSecretCached;
|
||||||
|
|
||||||
QComboBox *themeCombo_;
|
QComboBox *themeCombo_;
|
||||||
QComboBox *scaleFactorCombo_;
|
QComboBox *scaleFactorCombo_;
|
||||||
|
|
|
@ -54,8 +54,9 @@ bool
|
||||||
utils::codepointIsEmoji(uint code)
|
utils::codepointIsEmoji(uint code)
|
||||||
{
|
{
|
||||||
// TODO: Be more precise here.
|
// TODO: Be more precise here.
|
||||||
return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x1f300 && code <= 0x1f3ff) ||
|
return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) ||
|
||||||
(code >= 0x1f000 && code <= 0x1faff);
|
(code >= 0x1f300 && code <= 0x1f3ff) || (code >= 0x1f000 && code <= 0x1faff) ||
|
||||||
|
code == 0x200d;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString
|
QString
|
||||||
|
|
|
@ -51,7 +51,12 @@ public:
|
||||||
void sync(const mtx::responses::Rooms &rooms);
|
void sync(const mtx::responses::Rooms &rooms);
|
||||||
void addRoom(const QString &room_id);
|
void addRoom(const QString &room_id);
|
||||||
|
|
||||||
void clearAll() { models.clear(); }
|
void clearAll()
|
||||||
|
{
|
||||||
|
timeline_ = nullptr;
|
||||||
|
emit activeTimelineChanged(nullptr);
|
||||||
|
models.clear();
|
||||||
|
}
|
||||||
|
|
||||||
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
|
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
|
||||||
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
|
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
|
||||||
|
|
Loading…
Reference in a new issue