[network/ruqola] src: e2ekeymanager++
Laurent Montel <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 3803e633de8038d241173bfede8b0f6a128e2307 by Laurent Montel.
Committed on 03/08/2026 at 06:00.
Pushed by mlaurent into branch 'master'.
e2ekeymanager++
M +75 -0 src/core/autotests/e2ekeymanagertest.cpp
M +2 -0 src/core/autotests/e2ekeymanagertest.h
M +63 -0 src/core/encryption/e2ekeymanager.cpp
M +5 -0 src/core/encryption/e2ekeymanager.h
M +19 -0 src/core/rocketchataccount.cpp
M +3 -0 src/core/rocketchataccount.h
M +19 -2 src/widgets/room/roomwidget.cpp
https://invent.kde.org/network/ruqola/-/commit/3803e633de8038d241173bfede8b0f6a128e2307
diff --git a/src/core/autotests/e2ekeymanagertest.cpp b/src/core/autotests/e2ekeymanagertest.cpp
index 461c96fd20..527b56e65a 100644
--- a/src/core/autotests/e2ekeymanagertest.cpp
+++ b/src/core/autotests/e2ekeymanagertest.cpp
@@ -8,6 +8,7 @@
#include "encryption/e2ekeymanager.h"
#include "config-ruqola.h"
+#include "encryption/encryptionutils.h"
#include "localdatabase/e2edatabase.h"
#include "localdatabase/localdatabasemanager.h"
#include "rocketchataccount.h"
@@ -148,4 +149,78 @@ void E2eKeyManagerTest::shouldHandleMissingOrMalformedServerKeys()
QVERIFY(account.localDatabaseManager()->e2EDatabase()->deleteKey(u"test-e2e-user-generation"_s));
}
+void E2eKeyManagerTest::shouldDecodeEncryptionKeyWithValidPassword()
+{
+#if !USE_E2E_SUPPORT
+ QSKIP("E2E support is disabled");
+#else
+ QTemporaryDir tempDir;
+ QVERIFY(tempDir.isValid());
+
+ RocketChatAccount account(tempDir.filePath(u"account.ini"_s));
+ account.settings()->setUserId("test-e2e-user-decode-ok"_ba);
+
+ const QString userId = u"test-e2e-user-decode-ok"_s;
+ const QString password = u"my-test-password"_s;
+ const auto rsaKeyPair = EncryptionUtils::generateRSAKey();
+ QVERIFY(!rsaKeyPair.privateKey.isEmpty());
+
+ const QByteArray masterKey = EncryptionUtils::getMasterKey(password, userId);
+ QVERIFY(!masterKey.isEmpty());
+ const QByteArray encryptedPrivateKey = EncryptionUtils::encryptPrivateKey(rsaKeyPair.privateKey, masterKey);
+ QVERIFY(!encryptedPrivateKey.isEmpty());
+
+ QVERIFY(account.localDatabaseManager()->e2EDatabase()->saveKey(userId, encryptedPrivateKey, rsaKeyPair.publicKey));
+
+ E2eKeyManager manager(&account);
+ manager.setStatus(E2eKeyManager::Status::NeedToDecryptKey);
+
+ QSignalSpy doneSpy(&manager, &E2eKeyManager::decodeEncryptionKeyDone);
+ QSignalSpy failedSpy(&manager, &E2eKeyManager::failedDecodeEncryptionKey);
+ QVERIFY(manager.decodeEncryptionKey(password));
+ QCOMPARE(manager.status(), E2eKeyManager::Status::KeyDecrypted);
+ QCOMPARE(doneSpy.count(), 1);
+ QCOMPARE(failedSpy.count(), 0);
+
+ QVERIFY(account.localDatabaseManager()->e2EDatabase()->deleteKey(userId));
+#endif
+}
+
+void E2eKeyManagerTest::shouldFailDecodeEncryptionKeyWithWrongPassword()
+{
+#if !USE_E2E_SUPPORT
+ QSKIP("E2E support is disabled");
+#else
+ QTemporaryDir tempDir;
+ QVERIFY(tempDir.isValid());
+
+ RocketChatAccount account(tempDir.filePath(u"account.ini"_s));
+ account.settings()->setUserId("test-e2e-user-decode-ko"_ba);
+
+ const QString userId = u"test-e2e-user-decode-ko"_s;
+ const QString password = u"right-password"_s;
+ const auto rsaKeyPair = EncryptionUtils::generateRSAKey();
+ QVERIFY(!rsaKeyPair.privateKey.isEmpty());
+
+ const QByteArray masterKey = EncryptionUtils::getMasterKey(password, userId);
+ QVERIFY(!masterKey.isEmpty());
+ const QByteArray encryptedPrivateKey = EncryptionUtils::encryptPrivateKey(rsaKeyPair.privateKey, masterKey);
+ QVERIFY(!encryptedPrivateKey.isEmpty());
+
+ QVERIFY(account.localDatabaseManager()->e2EDatabase()->saveKey(userId, encryptedPrivateKey, rsaKeyPair.publicKey));
+
+ E2eKeyManager manager(&account);
+ manager.setStatus(E2eKeyManager::Status::NeedToDecryptKey);
+
+ QSignalSpy doneSpy(&manager, &E2eKeyManager::decodeEncryptionKeyDone);
+ QSignalSpy failedSpy(&manager, &E2eKeyManager::failedDecodeEncryptionKey);
+ QVERIFY(!manager.decodeEncryptionKey(u"wrong-password"_s));
+ QCOMPARE(manager.status(), E2eKeyManager::Status::NeedToDecryptKey);
+ QCOMPARE(doneSpy.count(), 0);
+ QCOMPARE(failedSpy.count(), 1);
+
+ QVERIFY(account.localDatabaseManager()->e2EDatabase()->deleteKey(userId));
+#endif
+}
+
#include "moc_e2ekeymanagertest.cpp"
diff --git a/src/core/autotests/e2ekeymanagertest.h b/src/core/autotests/e2ekeymanagertest.h
index 3827c7e71b..f691027f45 100644
--- a/src/core/autotests/e2ekeymanagertest.h
+++ b/src/core/autotests/e2ekeymanagertest.h
@@ -21,4 +21,6 @@ private Q_SLOTS:
void shouldSetNeedToDecryptStatusFromBase64StringPayload();
void shouldSetNeedToDecryptStatusFromBinaryObjectPayload();
void shouldHandleMissingOrMalformedServerKeys();
+ void shouldDecodeEncryptionKeyWithValidPassword();
+ void shouldFailDecodeEncryptionKeyWithWrongPassword();
};
diff --git a/src/core/encryption/e2ekeymanager.cpp b/src/core/encryption/e2ekeymanager.cpp
index dde197dc0b..161f6da43e 100644
--- a/src/core/encryption/e2ekeymanager.cpp
+++ b/src/core/encryption/e2ekeymanager.cpp
@@ -40,6 +40,69 @@ void E2eKeyManager::decodeEncryptionKey()
}
}
+bool E2eKeyManager::decodeEncryptionKey(const QString &password)
+{
+#if USE_E2E_SUPPORT
+ if (!mAccount || password.isEmpty()) {
+ setStatus(Status::NeedToDecryptKey);
+ Q_EMIT failedDecodeEncryptionKey();
+ return false;
+ }
+
+ const QString userId = QString::fromLatin1(mAccount->settings()->userId());
+ if (userId.isEmpty()) {
+ setStatus(Status::NeedToDecryptKey);
+ Q_EMIT failedDecodeEncryptionKey();
+ return false;
+ }
+
+ QByteArray encryptedPrivateKey;
+ QByteArray publicKey;
+ if (!mAccount->localDatabaseManager()->e2EDatabase()->loadKey(userId, encryptedPrivateKey, publicKey)) {
+ qCWarning(RUQOLA_ENCRYPTION_LOG) << "Unable to decode E2E key: no local encrypted private key found";
+ setStatus(Status::NeedToDecryptKey);
+ Q_EMIT failedDecodeEncryptionKey();
+ return false;
+ }
+
+ const QByteArray masterKey = EncryptionUtils::getMasterKey(password, userId);
+ if (masterKey.isEmpty()) {
+ setStatus(Status::NeedToDecryptKey);
+ Q_EMIT failedDecodeEncryptionKey();
+ return false;
+ }
+
+ const QByteArray privateKeyPem = EncryptionUtils::decryptPrivateKey(encryptedPrivateKey, masterKey);
+ if (privateKeyPem.isEmpty()) {
+ setStatus(Status::NeedToDecryptKey);
+ Q_EMIT failedDecodeEncryptionKey();
+ return false;
+ }
+
+ RSA *privateKey = EncryptionUtils::privateKeyFromPEM(privateKeyPem);
+ if (!privateKey) {
+ setStatus(Status::NeedToDecryptKey);
+ Q_EMIT failedDecodeEncryptionKey();
+ return false;
+ }
+
+ RSA_free(privateKey);
+ mDecodedPrivateKey = privateKeyPem;
+ setStatus(Status::KeyDecrypted);
+ Q_EMIT decodeEncryptionKeyDone();
+ return true;
+#else
+ Q_UNUSED(password)
+ return false;
+#endif
+}
+
+void E2eKeyManager::postponeDecryption()
+{
+ setStatus(Status::DecryptionPostponned);
+ Q_EMIT decodeEncryptionKeyPostponed();
+}
+
QString E2eKeyManager::generateRandomPassword() const
{
#if USE_E2E_SUPPORT
diff --git a/src/core/encryption/e2ekeymanager.h b/src/core/encryption/e2ekeymanager.h
index ad9839c6a5..2308ac1937 100644
--- a/src/core/encryption/e2ekeymanager.h
+++ b/src/core/encryption/e2ekeymanager.h
@@ -25,6 +25,8 @@ public:
~E2eKeyManager() override;
void decodeEncryptionKey();
+ [[nodiscard]] bool decodeEncryptionKey(const QString &password);
+ void postponeDecryption();
void fetchMyKeys();
@@ -43,11 +45,14 @@ public:
Q_SIGNALS:
void needDecodeEncryptionKey();
void failedDecodeEncryptionKey();
+ void decodeEncryptionKeyDone();
+ void decodeEncryptionKeyPostponed();
void verifyKeyDone();
private:
LIBRUQOLACORE_NO_EXPORT void verifyExistingKey(const QJsonObject &json);
Status mStatus = Status::Unknown;
QString mGeneratedPassword;
+ QByteArray mDecodedPrivateKey;
RocketChatAccount *const mAccount;
};
diff --git a/src/core/rocketchataccount.cpp b/src/core/rocketchataccount.cpp
index 2894c389c7..d035639bd9 100644
--- a/src/core/rocketchataccount.cpp
+++ b/src/core/rocketchataccount.cpp
@@ -302,6 +302,9 @@ RocketChatAccount::RocketChatAccount(const QString &accountFileName, QObject *pa
setDefaultAuthentication(mSettings->authMethodType());
mNotificationPreferences->setCustomSoundManager(mCustomSoundManager);
connect(mE2eKeyManager, &E2eKeyManager::verifyKeyDone, this, &RocketChatAccount::slotVerifyKeysDone);
+ connect(mE2eKeyManager, &E2eKeyManager::decodeEncryptionKeyDone, this, &RocketChatAccount::slotE2eDecodeKeyDone);
+ connect(mE2eKeyManager, &E2eKeyManager::failedDecodeEncryptionKey, this, &RocketChatAccount::slotE2eDecodeKeyFailed);
+ connect(mE2eKeyManager, &E2eKeyManager::decodeEncryptionKeyPostponed, this, &RocketChatAccount::slotE2eDecodeKeyPostponed);
connect(mMemoryManager, &MemoryManager::clearApplicationSettingsModelRequested, mAppsMarketPlaceModel, &AppsMarketPlaceModel::clear);
connect(mMemoryManager, &MemoryManager::cleanRoomHistoryRequested, mRoomModel, &RoomModel::cleanRoomHistory);
@@ -3339,6 +3342,22 @@ void RocketChatAccount::slotVerifyKeysDone()
#endif
}
+void RocketChatAccount::slotE2eDecodeKeyDone()
+{
+ setE2EPasswordMustBeDecrypt(false);
+}
+
+void RocketChatAccount::slotE2eDecodeKeyFailed()
+{
+ setE2EPasswordMustBeDecrypt(true);
+ Q_EMIT needToDecryptE2EPassword();
+}
+
+void RocketChatAccount::slotE2eDecodeKeyPostponed()
+{
+ setE2EPasswordMustBeDecrypt(true);
+}
+
MemoryManager *RocketChatAccount::memoryManager() const
{
return mMemoryManager;
diff --git a/src/core/rocketchataccount.h b/src/core/rocketchataccount.h
index 9712ef98ae..529d25f0e8 100644
--- a/src/core/rocketchataccount.h
+++ b/src/core/rocketchataccount.h
@@ -595,6 +595,9 @@ private:
LIBRUQOLACORE_NO_EXPORT void slotReconnectToDdpServer();
LIBRUQOLACORE_NO_EXPORT void resetDdp();
LIBRUQOLACORE_NO_EXPORT void slotVerifyKeysDone();
+ LIBRUQOLACORE_NO_EXPORT void slotE2eDecodeKeyDone();
+ LIBRUQOLACORE_NO_EXPORT void slotE2eDecodeKeyFailed();
+ LIBRUQOLACORE_NO_EXPORT void slotE2eDecodeKeyPostponed();
LIBRUQOLACORE_NO_EXPORT void slotDDpLoginStatusChanged();
LIBRUQOLACORE_NO_EXPORT void slotRESTLoginStatusChanged();
LIBRUQOLACORE_NO_EXPORT void slotRoomOpenChanged(const QByteArray &rid);
diff --git a/src/widgets/room/roomwidget.cpp b/src/widgets/room/roomwidget.cpp
index 2e830a2de3..9406a8bf39 100644
--- a/src/widgets/room/roomwidget.cpp
+++ b/src/widgets/room/roomwidget.cpp
@@ -254,11 +254,28 @@ void RoomWidget::createE2eDecodeEncryptionKeyFailedWidget()
void RoomWidget::slotDecodeEncryptionKey()
{
+ if (!mCurrentRocketChatAccount) {
+ return;
+ }
+
QPointer<E2ePasswordDecodeKeyDialog> dlg = new E2ePasswordDecodeKeyDialog(this);
if (dlg->exec()) {
- // TODO we saved it => don't ask it again
const QString password = dlg->password();
- // TODO generate private key
+ if (mCurrentRocketChatAccount->e2eKeyManager()->decodeEncryptionKey(password)) {
+ if (mE2eDecodeEncryptionKeyWidget) {
+ mE2eDecodeEncryptionKeyWidget->animatedHide();
+ }
+ if (mE2eDecodeEncryptionKeyFailedWidget) {
+ mE2eDecodeEncryptionKeyFailedWidget->animatedHide();
+ }
+ } else {
+ if (!mE2eDecodeEncryptionKeyFailedWidget) {
+ createE2eDecodeEncryptionKeyFailedWidget();
+ }
+ mE2eDecodeEncryptionKeyFailedWidget->animatedShow();
+ }
+ } else {
+ mCurrentRocketChatAccount->e2eKeyManager()->postponeDecryption();
}
delete dlg;
}