[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
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.