[network/ruqola] src/core: continue to implement e2e support
Laurent Montel <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 51153fa2c65605942f4ec93910f8d4efcabd8d40 by Laurent Montel.
Committed on 03/08/2026 at 05:42.
Pushed by mlaurent into branch 'master'.
continue to implement e2e support
M +99 -7 src/core/encryption/e2ekeymanager.cpp
M +1 -0 src/core/encryption/e2ekeymanager.h
M +12 -14 src/core/encryption/encryptionutils.cpp
M +17 -6 src/core/rocketchataccount.cpp
https://invent.kde.org/network/ruqola/-/commit/51153fa2c65605942f4ec93910f8d4efcabd8d40
diff --git a/src/core/encryption/e2ekeymanager.cpp b/src/core/encryption/e2ekeymanager.cpp
index f2ca8e24c0..4b677ff4f5 100644
--- a/src/core/encryption/e2ekeymanager.cpp
+++ b/src/core/encryption/e2ekeymanager.cpp
@@ -8,14 +8,22 @@
#include "config-ruqola.h"
#include "connection.h"
#include "e2e/fetchmykeysjob.h"
+#include "e2e/setuserpublicandprivatekeysjob.h"
#if USE_E2E_SUPPORT
#include "encryptionutils.h"
#endif
+#include "localdatabase/e2edatabase.h"
+#include "localdatabase/localdatabasemanager.h"
#include "rocketchataccount.h"
#include "rocketchataccountsettings.h"
#include "ruqola_encryption_debug.h"
#include "ruqolaserverconfig.h"
+#include <QByteArray>
+#include <QJsonValue>
+
+using namespace Qt::Literals::StringLiterals;
+
// https://docs.rocket.chat/docs/end-to-end-encryption-specifications
E2eKeyManager::E2eKeyManager(RocketChatAccount *account, QObject *parent)
: QObject{parent}
@@ -27,12 +35,17 @@ E2eKeyManager::~E2eKeyManager() = default;
void E2eKeyManager::decodeEncryptionKey()
{
- // TODO
+ if (mStatus == Status::NeedToDecryptKey || mStatus == Status::DecryptionPostponned) {
+ Q_EMIT needDecodeEncryptionKey();
+ }
}
QString E2eKeyManager::generateRandomPassword() const
{
#if USE_E2E_SUPPORT
+ if (!mGeneratedPassword.isEmpty()) {
+ return mGeneratedPassword;
+ }
return EncryptionUtils::generateRandomPassword();
#else
return {};
@@ -65,8 +78,89 @@ void E2eKeyManager::fetchMyKeys()
void E2eKeyManager::verifyExistingKey(const QJsonObject &json)
{
- // TODO
- // return status value
+ const auto decodeEncryptedPrivateKey = [](const QJsonValue &privateKeyValue) -> QByteArray {
+ if (privateKeyValue.isString()) {
+ const QByteArray privateKey = privateKeyValue.toString().toUtf8();
+ const QByteArray decoded = QByteArray::fromBase64(privateKey);
+ // Some server payloads can already be raw bytes serialized as UTF-8.
+ return decoded.isEmpty() ? privateKey : decoded;
+ }
+ if (privateKeyValue.isObject()) {
+ const QString binaryValue = privateKeyValue.toObject().value(QStringLiteral("$binary")).toString();
+ if (!binaryValue.isEmpty()) {
+ return QByteArray::fromBase64(binaryValue.toUtf8());
+ }
+ }
+ return {};
+ };
+
+ if (!mAccount) {
+ setStatus(Status::Unknown);
+ return;
+ }
+
+ const QString publicKey = json.value("public_key"_L1).toString();
+ const QByteArray encryptedPrivateKey = decodeEncryptedPrivateKey(json.value("private_key"_L1));
+
+ if (!publicKey.isEmpty() && !encryptedPrivateKey.isEmpty()) {
+ const QString userId = QString::fromLatin1(mAccount->settings()->userId());
+ if (!userId.isEmpty()) {
+ (void)mAccount->localDatabaseManager()->e2EDatabase()->saveKey(userId, encryptedPrivateKey, publicKey.toUtf8());
+ }
+ setStatus(Status::NeedToDecryptKey);
+ return;
+ }
+
+#if USE_E2E_SUPPORT
+ const QString userId = QString::fromLatin1(mAccount->settings()->userId());
+ if (userId.isEmpty()) {
+ qCWarning(RUQOLA_ENCRYPTION_LOG) << "Unable to generate E2E keys: user id is empty";
+ setStatus(Status::Unknown);
+ return;
+ }
+
+ mGeneratedPassword = EncryptionUtils::generateRandomPassword();
+ if (mGeneratedPassword.isEmpty()) {
+ qCWarning(RUQOLA_ENCRYPTION_LOG) << "Unable to generate E2E keys: random password generation failed";
+ setStatus(Status::Unknown);
+ return;
+ }
+
+ const QByteArray masterKey = EncryptionUtils::getMasterKey(mGeneratedPassword, userId);
+ const EncryptionUtils::RSAKeyPair rsaKeyPair = EncryptionUtils::generateRSAKey();
+ if (masterKey.isEmpty() || rsaKeyPair.privateKey.isEmpty() || rsaKeyPair.publicKey.isEmpty()) {
+ qCWarning(RUQOLA_ENCRYPTION_LOG) << "Unable to generate E2E keys: prerequisite generation failed";
+ setStatus(Status::Unknown);
+ return;
+ }
+
+ const QByteArray encryptedGeneratedPrivateKey = EncryptionUtils::encryptPrivateKey(rsaKeyPair.privateKey, masterKey);
+ if (encryptedGeneratedPrivateKey.isEmpty()) {
+ qCWarning(RUQOLA_ENCRYPTION_LOG) << "Unable to generate E2E keys: private key encryption failed";
+ setStatus(Status::Unknown);
+ return;
+ }
+
+ (void)mAccount->localDatabaseManager()->e2EDatabase()->saveKey(userId, encryptedGeneratedPrivateKey, rsaKeyPair.publicKey);
+
+ auto setJob = new RocketChatRestApi::SetUserPublicAndPrivateKeysJob(this);
+ mAccount->restApi()->initializeRestApiJob(setJob);
+
+ RocketChatRestApi::SetUserPublicAndPrivateKeysJob::SetUserPublicAndPrivateKeysInfo info;
+ info.rsaPublicKey = QString::fromUtf8(rsaKeyPair.publicKey);
+ info.rsaPrivateKey = QString::fromLatin1(encryptedGeneratedPrivateKey.toBase64());
+ setJob->setSetUserPublicAndPrivateKeysInfo(info);
+
+ if (!setJob->start()) {
+ qCWarning(RUQOLA_ENCRYPTION_LOG) << "Unable to upload generated E2E keypair";
+ setStatus(Status::Unknown);
+ return;
+ }
+
+ setStatus(Status::NeedToGenerateKey);
+#else
+ setStatus(Status::Unknown);
+#endif
}
bool E2eKeyManager::keySaved() const
@@ -90,11 +184,9 @@ E2eKeyManager::Status E2eKeyManager::needToDecodeEncryptionKey() const
return Status::Unknown;
}
if (mAccount->ruqolaServerConfig()->encryptionEnabled()) {
- // TODO check if we have decoded key stored.
- // TODO check NeedToDecryptKey
- return Status::NeedToGenerateKey;
+ return mStatus;
}
- return mStatus;
+ return Status::Unknown;
}
#include "moc_e2ekeymanager.cpp"
diff --git a/src/core/encryption/e2ekeymanager.h b/src/core/encryption/e2ekeymanager.h
index 5499cb426f..88bc89596a 100644
--- a/src/core/encryption/e2ekeymanager.h
+++ b/src/core/encryption/e2ekeymanager.h
@@ -46,5 +46,6 @@ Q_SIGNALS:
private:
LIBRUQOLACORE_NO_EXPORT void verifyExistingKey(const QJsonObject &json);
Status mStatus = Status::Unknown;
+ QString mGeneratedPassword;
RocketChatAccount *const mAccount;
};
diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp
index 8161b3621e..31085f4a49 100644
--- a/src/core/encryption/encryptionutils.cpp
+++ b/src/core/encryption/encryptionutils.cpp
@@ -454,12 +454,8 @@ QByteArray EncryptionUtils::decryptMessage(const QByteArray &encrypted, const QB
const QByteArray iv = encrypted.left(16);
const QByteArray cipherText = encrypted.mid(16);
- qDebug() << cipherText << "QByteArray cipherText = encrypted.mid(16)";
-
const QByteArray plainText = decryptAES_CBC_128(cipherText, sessionKey, iv);
- qDebug() << plainText << "QByteArray plainText = decryptAES_CBC_128(cipherText, sessionKey, iv);";
-
if (plainText.isEmpty()) {
qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::decryptMessage, message decryption failed, plain text is empty";
return {};
@@ -825,10 +821,9 @@ qDebug() << "Decrypted Text:" << decryptedText;
EncryptionUtils::EncryptionInfo EncryptionUtils::splitVectorAndEcryptedData(const QByteArray &cipherText)
{
EncryptionUtils::EncryptionInfo info;
- if (!cipherText.isEmpty()) {
- // TODO add more check
+ if (cipherText.size() > 16) {
info.vector = cipherText.left(16);
- info.encryptedData = cipherText.last(16);
+ info.encryptedData = cipherText.mid(16);
}
return info;
}
@@ -907,14 +902,17 @@ bool EncryptionUtils::EncryptionInfo::operator==(const EncryptionUtils::Encrypti
QString EncryptionUtils::generateRandomPassword()
{
const int numberChar = 30;
+ const QByteArray charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:,.<>?";
+ const QByteArray randomBytes = generateRandomIV(numberChar);
+ if (randomBytes.isEmpty()) {
+ return {};
+ }
+
QString randomStr;
- for (int i = 0; i < numberChar; i++) {
- const int d = rand() % 200; // Generate a random ASCII value between 0 and 199
- if (d >= 33 && d <= 123) {
- randomStr.append(QLatin1Char(static_cast<char>(d))); // Convert the ASCII value to a character for valid range
- } else {
- randomStr.append(QString::number(d % 10)); // Keep the last digit for numbers outside the valid range
- }
+ randomStr.reserve(numberChar);
+ for (int i = 0; i < numberChar; ++i) {
+ const int index = static_cast<unsigned char>(randomBytes.at(i)) % charset.size();
+ randomStr.append(QLatin1Char(charset.at(index)));
}
return randomStr;
}
diff --git a/src/core/rocketchataccount.cpp b/src/core/rocketchataccount.cpp
index e17813585e..2894c389c7 100644
--- a/src/core/rocketchataccount.cpp
+++ b/src/core/rocketchataccount.cpp
@@ -3318,13 +3318,24 @@ void RocketChatAccount::loadAppMarketPlace()
void RocketChatAccount::slotVerifyKeysDone()
{
- // TODO reactivate it when we will have full support
#if USE_E2E_SUPPORT
- Q_EMIT needToSaveE2EPassword();
- Q_EMIT needToDecryptE2EPassword();
- // TODO verify it!!!!!
- setE2EPasswordMustBeDecrypt(true);
- // TODO verify if we must decode it
+ setE2EPasswordMustBeSave(false);
+ setE2EPasswordMustBeDecrypt(false);
+
+ switch (mE2eKeyManager->status()) {
+ case E2eKeyManager::Status::NeedToGenerateKey:
+ setE2EPasswordMustBeSave(true);
+ Q_EMIT needToSaveE2EPassword();
+ break;
+ case E2eKeyManager::Status::NeedToDecryptKey:
+ case E2eKeyManager::Status::DecryptionPostponned:
+ setE2EPasswordMustBeDecrypt(true);
+ Q_EMIT needToDecryptE2EPassword();
+ break;
+ case E2eKeyManager::Status::KeyDecrypted:
+ case E2eKeyManager::Status::Unknown:
+ break;
+ }
#endif
}