From 914195c5f49fa972eae6771c2ca1b10972397f25 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Tue, 27 May 2025 12:56:24 +0300 Subject: [PATCH 01/67] Init commit,'generateRSAkey()' returns a 'keyPair' and log it to console --- src/core/encryption/encryptionutils.cpp | 36 +++++++++++++++---- src/core/encryption/encryptionutils.h | 7 +++- tests/encryptiontestgui/encryptiontestgui.cpp | 6 ++-- tests/encryptiontestgui/encryptiontestgui.h | 1 + 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 78261c9a69..4bee01e467 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -1,5 +1,6 @@ /* SPDX-FileCopyrightText: 2024-2025 Laurent Montel + SPDX-FileCopyrightText: 2025 Andro Ranogajec SPDX-License-Identifier: GPL-2.0-or-later */ @@ -56,14 +57,19 @@ QByteArray EncryptionUtils::exportJWKKey(RSA *rsaKey) return doc.toJson(QJsonDocument::Compact); } -void EncryptionUtils::generateRSAKey() +EncryptionUtils::RSAKeyPair EncryptionUtils::generateRSAKey() { + RSAKeyPair keyPair; + int ret = 0; RSA *rsa = nullptr; BIGNUM *bne = nullptr; BIO *bp_public = nullptr; BIO *bp_private = nullptr; + BIO *pubBio = BIO_new(BIO_s_mem()); + BIO *privBio = BIO_new(BIO_s_mem()); + int bits = 2048; unsigned long e = RSA_F4; // équivalent à 0x10001 @@ -71,17 +77,17 @@ void EncryptionUtils::generateRSAKey() ret = BN_set_word(bne, e); if (ret != 1) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Error when generating exponent"; - return; + return {}; } rsa = RSA_new(); ret = RSA_generate_key_ex(rsa, bits, bne, nullptr); if (ret != 1) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Error during generate key"; - return; + return {}; } - bp_public = BIO_new_file("public_key.pem", "w+"); + /* bp_public = BIO_new_file("public_key.pem", "w+"); ret = PEM_write_bio_RSAPublicKey(bp_public, rsa); if (ret != 1) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Error when saving public key"; @@ -93,13 +99,29 @@ void EncryptionUtils::generateRSAKey() if (ret != 1) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Error when saving private key"; return; - } + } */ + + PEM_write_bio_RSA_PUBKEY(pubBio, rsa); + PEM_write_bio_RSAPrivateKey(privBio, rsa, nullptr, nullptr, 0, nullptr, nullptr); + + BUF_MEM *pubBuf = nullptr; + BUF_MEM *privBuf = nullptr; + + BIO_get_mem_ptr(pubBio, &pubBuf); + BIO_get_mem_ptr(privBio, &privBuf); + + keyPair.publicKey = QString::fromUtf8(pubBuf->data, pubBuf->length); + keyPair.privateKey = QString::fromUtf8(privBuf->data, privBuf->length); // Libérer la mémoire - BIO_free_all(bp_public); - BIO_free_all(bp_private); + // BIO_free_all(bp_public); + // BIO_free_all(bp_private); + BIO_free_all(pubBio); + BIO_free_all(privBio); RSA_free(rsa); BN_free(bne); + + return keyPair; } QString EncryptionUtils::encodePrivateKey(const QString &privateKey, const QString &password, const QString &userId) diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index e307739171..b1a0bc423b 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -1,5 +1,6 @@ /* SPDX-FileCopyrightText: 2024-2025 Laurent Montel + SPDX-FileCopyrightText: 2025 Andro Ranogajec SPDX-License-Identifier: GPL-2.0-or-later */ @@ -19,9 +20,13 @@ struct LIBRUQOLACORE_TESTS_EXPORT EncryptionInfo { [[nodiscard]] bool isValid() const; [[nodiscard]] bool operator==(const EncryptionInfo &other) const; }; +struct RSAKeyPair { + QString publicKey; + QString privateKey; +}; [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray exportJWKKey(RSA *rsaKey); -LIBRUQOLACORE_TESTS_EXPORT void generateRSAKey(); +[[nondiscard]] LIBRUQOLACORE_TESTS_EXPORT RSAKeyPair generateRSAKey(); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString encodePrivateKey(const QString &privateKey, const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString getMasterKey(const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 9492859697..882a2984ba 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -60,8 +60,10 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonGenerateRSAKey = new QPushButton(QStringLiteral("Generate RSA Pair"), this); mainLayout->addWidget(pushButtonGenerateRSAKey); - connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, []() { - // test + connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, [this]() { + EncryptionUtils::RSAKeyPair rsaKeyPair = EncryptionUtils::generateRSAKey(); + qDebug() << rsaKeyPair.publicKey; + qDebug() << rsaKeyPair.privateKey; }); auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this); mainLayout->addWidget(pushButtonGenerateSessionKey); diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index a83cfcd504..62d39b129e 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -1,5 +1,6 @@ /* SPDX-FileCopyrightText: 2024-2025 Laurent Montel + SPDX-FileCopyrightText: 2025 Andro Ranogajec SPDX-License-Identifier: LGPL-2.0-or-later */ From 3e67c5ec950608204865240cba1c2e24fd6db3ff Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Wed, 28 May 2025 13:25:57 +0300 Subject: [PATCH 02/67] Add decode function for private key --- src/core/encryption/encryptionutils.cpp | 6 ++++++ src/core/encryption/encryptionutils.h | 1 + tests/encryptiontestgui/encryptiontestgui.cpp | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 4bee01e467..2c9859b5ff 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -130,6 +130,12 @@ QString EncryptionUtils::encodePrivateKey(const QString &privateKey, const QStri return {}; } +QString EncryptionUtils::decodePrivateKey(const QString &privateKey, const QString &password, const QString &userId) +{ + const QString masterKey = getMasterKey(password, userId); + return {}; +} + QString EncryptionUtils::getMasterKey(const QString &password, const QString &userId) { if (password.isEmpty()) { diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index b1a0bc423b..194c9908cb 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -28,6 +28,7 @@ struct RSAKeyPair { [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray exportJWKKey(RSA *rsaKey); [[nondiscard]] LIBRUQOLACORE_TESTS_EXPORT RSAKeyPair generateRSAKey(); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString encodePrivateKey(const QString &privateKey, const QString &password, const QString &userId); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString decodePrivateKey(const QString &privateKey, const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString getMasterKey(const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray deriveKey(const QByteArray &salt, const QByteArray &baseKey, int iterations = 1000, int keyLength = 32); diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 882a2984ba..6b6dcfcaeb 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -61,7 +61,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonGenerateRSAKey = new QPushButton(QStringLiteral("Generate RSA Pair"), this); mainLayout->addWidget(pushButtonGenerateRSAKey); connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, [this]() { - EncryptionUtils::RSAKeyPair rsaKeyPair = EncryptionUtils::generateRSAKey(); + auto rsaKeyPair = EncryptionUtils::generateRSAKey(); qDebug() << rsaKeyPair.publicKey; qDebug() << rsaKeyPair.privateKey; }); From 6ba694dc2474d595fbaf7144dc03602bd152885b Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Wed, 28 May 2025 13:36:47 +0300 Subject: [PATCH 03/67] Copy 'generateRandomIV()' from a cherry-pick commit from 'init-test...' --- src/core/encryption/encryptionutils.cpp | 7 ++++++- src/core/encryption/encryptionutils.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 2c9859b5ff..f290de85c1 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -217,7 +217,12 @@ QByteArray EncryptionUtils::encryptAES_CBC(const QByteArray &data, const QByteAr QByteArray EncryptionUtils::generateRandomIV(int size) { QByteArray iv(size, 0); - // TODO QRandomGenerator::global()->generate(reinterpret_cast(iv.data()), size); + + if (RAND_bytes(reinterpret_cast(iv.data()), size) != 1) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Failed to generate random IV using OpenSSL!"; + return {}; + } + return iv; } diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index 194c9908cb..a6d2b76b3b 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -10,6 +10,7 @@ extern "C" { #include #include +#include #include } namespace EncryptionUtils From 81d0fcd337d1e3d3aa68969bc6b35488ee9a5b0c Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Wed, 28 May 2025 15:29:24 +0300 Subject: [PATCH 04/67] 'encodePrivateKey' implimentation without testing --- src/core/encryption/encryptionutils.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index f290de85c1..04e59ebd15 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -126,13 +126,26 @@ EncryptionUtils::RSAKeyPair EncryptionUtils::generateRSAKey() QString EncryptionUtils::encodePrivateKey(const QString &privateKey, const QString &password, const QString &userId) { - const QString masterKey = getMasterKey(password, userId); - return {}; + const QByteArray masterKey = QStringLiteral("qwertyuiopasdfgh").toUtf8(); + const QByteArray iv = generateRandomIV(16); + const QByteArray data = privateKey.toUtf8(); + const QByteArray ciphertext = encryptAES_CBC(data, masterKey, iv); + + if (ciphertext.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Encryption of the private key failed, cipherText is empty"; + return {}; + } + + QByteArray encoded; + encoded.append(iv); + encoded.append(ciphertext); + + return QString::fromUtf8(encoded); } -QString EncryptionUtils::decodePrivateKey(const QString &privateKey, const QString &password, const QString &userId) +QString EncryptionUtils::decodePrivateKey(const QString &encoded, const QString &password, const QString &userId) { - const QString masterKey = getMasterKey(password, userId); + const QString masterKey = QStringLiteral("key"); return {}; } From 1d21d65ae9364a492ebb7d3bfc43ae768186cfb0 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Thu, 29 May 2025 14:55:13 +0300 Subject: [PATCH 05/67] Add new UI buttons and rearrange them in 'test ui' --- tests/encryptiontestgui/encryptiontestgui.cpp | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 6b6dcfcaeb..de3f608886 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -65,27 +65,40 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) qDebug() << rsaKeyPair.publicKey; qDebug() << rsaKeyPair.privateKey; }); + + auto pushButtonEncodePrivateKey = new QPushButton(QStringLiteral("Encode Private Key"), this); + mainLayout->addWidget(pushButtonEncodePrivateKey); + connect(pushButtonEncodePrivateKey, &QPushButton::clicked, this, []() { + // test + }); + auto pushButtonDecodePrivateKey = new QPushButton(QStringLiteral("Decode Private Key"), this); + mainLayout->addWidget(pushButtonDecodePrivateKey); + connect(pushButtonDecodePrivateKey, &QPushButton::clicked, this, []() { + // test + }); + auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this); mainLayout->addWidget(pushButtonGenerateSessionKey); connect(pushButtonGenerateSessionKey, &QPushButton::clicked, this, []() { // test }); - auto pushButtonEncode = new QPushButton(QStringLiteral("Encode"), this); - mainLayout->addWidget(pushButtonEncode); - connect(pushButtonEncode, &QPushButton::clicked, this, []() { + auto pushButtonEncodeMessage = new QPushButton(QStringLiteral("Encode Message"), this); + mainLayout->addWidget(pushButtonEncodeMessage); + connect(pushButtonEncodeMessage, &QPushButton::clicked, this, []() { // test }); - auto pushButtonDecode = new QPushButton(QStringLiteral("Decode"), this); - mainLayout->addWidget(pushButtonDecode); - connect(pushButtonDecode, &QPushButton::clicked, this, []() { + + auto pushButtonDecodeMessage = new QPushButton(QStringLiteral("Decode Message"), this); + mainLayout->addWidget(pushButtonDecodeMessage); + connect(pushButtonDecodeMessage, &QPushButton::clicked, this, []() { // test }); auto pushButtonReset = new QPushButton(QStringLiteral("Reset"), this); mainLayout->addWidget(pushButtonReset); connect(pushButtonReset, &QPushButton::clicked, this, []() { - EncryptionUtils::generateRSAKey(); + // test }); mTextEditResult->setReadOnly(true); From 7c389994d6345b9b86221606f311d08d432d0497 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Thu, 29 May 2025 15:59:56 +0300 Subject: [PATCH 06/67] Display RSA pair within UI and update 'qdebug()' --- tests/encryptiontestgui/encryptiontestgui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index de3f608886..cc3ecc09e2 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -62,8 +62,8 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mainLayout->addWidget(pushButtonGenerateRSAKey); connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, [this]() { auto rsaKeyPair = EncryptionUtils::generateRSAKey(); - qDebug() << rsaKeyPair.publicKey; - qDebug() << rsaKeyPair.privateKey; + qDebug() << "Public Key: " << rsaKeyPair.publicKey << "Private Key: " << rsaKeyPair.privateKey; + mTextEditResult->setPlainText(rsaKeyPair.publicKey + rsaKeyPair.privateKey); }); auto pushButtonEncodePrivateKey = new QPushButton(QStringLiteral("Encode Private Key"), this); From 38c8d6f01aba9a844dbcdd29b9cc035affc0e541 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 2 Jun 2025 16:35:19 +0300 Subject: [PATCH 07/67] WIP segmentation error --- src/core/encryption/encryptionutils.cpp | 25 +++++++++++++------ src/core/encryption/encryptionutils.h | 6 ++--- tests/encryptiontestgui/encryptiontestgui.cpp | 9 ++++--- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 04e59ebd15..d37610eb05 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -110,8 +110,8 @@ EncryptionUtils::RSAKeyPair EncryptionUtils::generateRSAKey() BIO_get_mem_ptr(pubBio, &pubBuf); BIO_get_mem_ptr(privBio, &privBuf); - keyPair.publicKey = QString::fromUtf8(pubBuf->data, pubBuf->length); - keyPair.privateKey = QString::fromUtf8(privBuf->data, privBuf->length); + keyPair.publicKey = QByteArray(pubBuf->data, pubBuf->length); + keyPair.privateKey = QByteArray(privBuf->data, privBuf->length); // Libérer la mémoire // BIO_free_all(bp_public); @@ -124,12 +124,23 @@ EncryptionUtils::RSAKeyPair EncryptionUtils::generateRSAKey() return keyPair; } -QString EncryptionUtils::encodePrivateKey(const QString &privateKey, const QString &password, const QString &userId) +QByteArray EncryptionUtils::encodePrivateKey(const QByteArray &privateKey, const QString &password, const QString &userId) { - const QByteArray masterKey = QStringLiteral("qwertyuiopasdfgh").toUtf8(); + const QByteArray masterKey = QByteArray("qwertyuiopasdfghqwertyuiopasdfgh", 32); + + if (masterKey.size() != 32) { + qDebug() << masterKey; + return {}; + } + const QByteArray iv = generateRandomIV(16); - const QByteArray data = privateKey.toUtf8(); - const QByteArray ciphertext = encryptAES_CBC(data, masterKey, iv); + + if (privateKey.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Private key is empty"; + return {}; + } + + const QByteArray ciphertext = encryptAES_CBC(privateKey, masterKey, iv); if (ciphertext.isEmpty()) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Encryption of the private key failed, cipherText is empty"; @@ -140,7 +151,7 @@ QString EncryptionUtils::encodePrivateKey(const QString &privateKey, const QStri encoded.append(iv); encoded.append(ciphertext); - return QString::fromUtf8(encoded); + return encoded.toBase64(); } QString EncryptionUtils::decodePrivateKey(const QString &encoded, const QString &password, const QString &userId) diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index a6d2b76b3b..e16563bb7b 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -22,13 +22,13 @@ struct LIBRUQOLACORE_TESTS_EXPORT EncryptionInfo { [[nodiscard]] bool operator==(const EncryptionInfo &other) const; }; struct RSAKeyPair { - QString publicKey; - QString privateKey; + QByteArray publicKey; + QByteArray privateKey; }; [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray exportJWKKey(RSA *rsaKey); [[nondiscard]] LIBRUQOLACORE_TESTS_EXPORT RSAKeyPair generateRSAKey(); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString encodePrivateKey(const QString &privateKey, const QString &password, const QString &userId); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encodePrivateKey(const QByteArray &privateKey, const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString decodePrivateKey(const QString &privateKey, const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString getMasterKey(const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index cc3ecc09e2..cbcbff2af7 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -63,13 +63,16 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, [this]() { auto rsaKeyPair = EncryptionUtils::generateRSAKey(); qDebug() << "Public Key: " << rsaKeyPair.publicKey << "Private Key: " << rsaKeyPair.privateKey; - mTextEditResult->setPlainText(rsaKeyPair.publicKey + rsaKeyPair.privateKey); + mTextEditResult->setPlainText(QString::fromUtf8(rsaKeyPair.publicKey) + QString::fromUtf8(rsaKeyPair.privateKey)); }); auto pushButtonEncodePrivateKey = new QPushButton(QStringLiteral("Encode Private Key"), this); mainLayout->addWidget(pushButtonEncodePrivateKey); - connect(pushButtonEncodePrivateKey, &QPushButton::clicked, this, []() { - // test + connect(pushButtonEncodePrivateKey, &QPushButton::clicked, this, [this]() { + QByteArray privateKey = EncryptionUtils::generateRSAKey().privateKey; + QByteArray testKey = QStringLiteral("hello world test key").toUtf8(); + auto encodedPrivateKey = EncryptionUtils::encodePrivateKey(testKey, QStringLiteral("root"), QStringLiteral("admin")); + qDebug() << encodedPrivateKey; }); auto pushButtonDecodePrivateKey = new QPushButton(QStringLiteral("Decode Private Key"), this); mainLayout->addWidget(pushButtonDecodePrivateKey); From 9b0bff642d1702d652df3871a566ccc2ce0188f7 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Tue, 3 Jun 2025 14:57:33 +0300 Subject: [PATCH 08/67] Fix segmentation error, dynamic memory allocation --- src/core/encryption/encryptionutils.cpp | 26 +++++++++---------- tests/encryptiontestgui/encryptiontestgui.cpp | 3 +-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index d37610eb05..26ed185a8a 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -126,20 +126,13 @@ EncryptionUtils::RSAKeyPair EncryptionUtils::generateRSAKey() QByteArray EncryptionUtils::encodePrivateKey(const QByteArray &privateKey, const QString &password, const QString &userId) { - const QByteArray masterKey = QByteArray("qwertyuiopasdfghqwertyuiopasdfgh", 32); - - if (masterKey.size() != 32) { - qDebug() << masterKey; - return {}; - } - - const QByteArray iv = generateRandomIV(16); - if (privateKey.isEmpty()) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Private key is empty"; return {}; } + const QByteArray masterKey = QByteArray("qwertyuiopasdfghqwertyuiopasdfgh", 32); + const QByteArray iv = generateRandomIV(16); const QByteArray ciphertext = encryptAES_CBC(privateKey, masterKey, iv); if (ciphertext.isEmpty()) { @@ -212,7 +205,9 @@ QByteArray EncryptionUtils::encryptAES_CBC(const QByteArray &data, const QByteAr EVP_CIPHER_CTX *ctx; int len; int ciphertext_len; - unsigned char ciphertext[128]; + + int max_out_len = data.size() + EVP_CIPHER_block_size(EVP_aes_256_cbc()); + QByteArray cipherText(max_out_len, 0); if (!(ctx = EVP_CIPHER_CTX_new())) return {}; @@ -225,17 +220,22 @@ QByteArray EncryptionUtils::encryptAES_CBC(const QByteArray &data, const QByteAr reinterpret_cast(iv.data()))) return {}; - if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, reinterpret_cast(data.data()), data.size())) + if (1 + != EVP_EncryptUpdate(ctx, + reinterpret_cast(cipherText.data()), + &len, + reinterpret_cast(data.data()), + data.size())) return {}; ciphertext_len = len; - if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) + if (1 != EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + len, &len)) return {}; ciphertext_len += len; EVP_CIPHER_CTX_free(ctx); - return QByteArray(reinterpret_cast(ciphertext), ciphertext_len); + return cipherText; } QByteArray EncryptionUtils::generateRandomIV(int size) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index cbcbff2af7..23faed3a6a 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -70,8 +70,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mainLayout->addWidget(pushButtonEncodePrivateKey); connect(pushButtonEncodePrivateKey, &QPushButton::clicked, this, [this]() { QByteArray privateKey = EncryptionUtils::generateRSAKey().privateKey; - QByteArray testKey = QStringLiteral("hello world test key").toUtf8(); - auto encodedPrivateKey = EncryptionUtils::encodePrivateKey(testKey, QStringLiteral("root"), QStringLiteral("admin")); + auto encodedPrivateKey = EncryptionUtils::encodePrivateKey(privateKey, QStringLiteral("root"), QStringLiteral("admin")); qDebug() << encodedPrivateKey; }); auto pushButtonDecodePrivateKey = new QPushButton(QStringLiteral("Decode Private Key"), this); From b43c1893a38a150b0f007bdcdfb67f7cb8f9eb24 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Tue, 3 Jun 2025 14:58:37 +0300 Subject: [PATCH 09/67] Add const qualifier --- tests/encryptiontestgui/encryptiontestgui.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 23faed3a6a..62c6f83105 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -61,7 +61,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonGenerateRSAKey = new QPushButton(QStringLiteral("Generate RSA Pair"), this); mainLayout->addWidget(pushButtonGenerateRSAKey); connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, [this]() { - auto rsaKeyPair = EncryptionUtils::generateRSAKey(); + const auto rsaKeyPair = EncryptionUtils::generateRSAKey(); qDebug() << "Public Key: " << rsaKeyPair.publicKey << "Private Key: " << rsaKeyPair.privateKey; mTextEditResult->setPlainText(QString::fromUtf8(rsaKeyPair.publicKey) + QString::fromUtf8(rsaKeyPair.privateKey)); }); @@ -69,8 +69,8 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonEncodePrivateKey = new QPushButton(QStringLiteral("Encode Private Key"), this); mainLayout->addWidget(pushButtonEncodePrivateKey); connect(pushButtonEncodePrivateKey, &QPushButton::clicked, this, [this]() { - QByteArray privateKey = EncryptionUtils::generateRSAKey().privateKey; - auto encodedPrivateKey = EncryptionUtils::encodePrivateKey(privateKey, QStringLiteral("root"), QStringLiteral("admin")); + const QByteArray privateKey = EncryptionUtils::generateRSAKey().privateKey; + const auto encodedPrivateKey = EncryptionUtils::encodePrivateKey(privateKey, QStringLiteral("root"), QStringLiteral("admin")); qDebug() << encodedPrivateKey; }); auto pushButtonDecodePrivateKey = new QPushButton(QStringLiteral("Decode Private Key"), this); From 8678eaf2e9f0051a7f97afa844f74d4f96b5cf6c Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Thu, 5 Jun 2025 11:49:20 +0300 Subject: [PATCH 10/67] Display encoded private key in the UI --- tests/encryptiontestgui/encryptiontestgui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 62c6f83105..482054b2bd 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -72,6 +72,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) const QByteArray privateKey = EncryptionUtils::generateRSAKey().privateKey; const auto encodedPrivateKey = EncryptionUtils::encodePrivateKey(privateKey, QStringLiteral("root"), QStringLiteral("admin")); qDebug() << encodedPrivateKey; + mTextEditResult->setPlainText(QString::fromUtf8(encodedPrivateKey)); }); auto pushButtonDecodePrivateKey = new QPushButton(QStringLiteral("Decode Private Key"), this); mainLayout->addWidget(pushButtonDecodePrivateKey); From 832a6e598fa4336bed33f7e6bb0a3093aaa23799 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Thu, 5 Jun 2025 15:46:20 +0300 Subject: [PATCH 11/67] WIP 'decodePrivateKey()' 'decryptAES_CBC()' and other changes --- src/core/encryption/encryptionutils.cpp | 85 ++++++++++++++++++- src/core/encryption/encryptionutils.h | 3 +- tests/encryptiontestgui/encryptiontestgui.cpp | 15 ++-- tests/encryptiontestgui/encryptiontestgui.h | 3 + 4 files changed, 95 insertions(+), 11 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 26ed185a8a..04bf78c05c 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -144,13 +144,46 @@ QByteArray EncryptionUtils::encodePrivateKey(const QByteArray &privateKey, const encoded.append(iv); encoded.append(ciphertext); - return encoded.toBase64(); + return encoded; } -QString EncryptionUtils::decodePrivateKey(const QString &encoded, const QString &password, const QString &userId) +QByteArray EncryptionUtils::decodePrivateKey(const QByteArray &encoded) { - const QString masterKey = QStringLiteral("key"); - return {}; + if (encoded.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Encoded private key is empty"; + return {}; + } + + qDebug() << "decodePrivateKey: encoded.size() =" << encoded.size(); + qDebug() << "decodePrivateKey: first 32 bytes (hex) =" << encoded.left(32).toHex(); + + const QByteArray masterKey = QByteArray("qwertyuiopasdfghqwertyuiopasdfgh", 32); + const QByteArray iv = encoded.left(16); + const QByteArray cipherText = encoded.mid(16); + + qDebug() << "decodePrivateKey: iv.size() =" << iv.size(); + qDebug() << "decodePrivateKey: cipherText.size() =" << cipherText.size(); + qDebug() << "decodePrivateKey: iv (hex) =" << iv.toHex(); + qDebug() << "decodePrivateKey: first 32 bytes of cipherText (hex) =" << cipherText.left(32).toHex(); + + if (iv.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Decryption of the private key failed, 'iv' is empty"; + return {}; + } + if (cipherText.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Decryption of the private key failed, 'cipherText' is empty"; + return {}; + } + /* + >>>>>>>>> ERROR HERE, plainText is empty + */ + const QByteArray plainText = decryptAES_CBC(cipherText, masterKey, iv); + + if (plainText.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Decryption of the cipherText failed, plainText is empty"; + return {}; + } + return plainText; } QString EncryptionUtils::getMasterKey(const QString &password, const QString &userId) @@ -200,6 +233,50 @@ QString EncryptionUtils::getMasterKey(const QString &password, const QString &us return {}; } +QByteArray EncryptionUtils::decryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv) +{ + EVP_CIPHER_CTX *ctx; + int len; + int plaintext_len; + + QByteArray plaintext(data.size(), 0); + + ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + return {}; + + if (1 + != EVP_DecryptInit_ex(ctx, + EVP_aes_256_cbc(), + nullptr, + reinterpret_cast(key.data()), + reinterpret_cast(iv.data()))) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + + if (1 + != EVP_DecryptUpdate(ctx, + reinterpret_cast(plaintext.data()), + &len, + reinterpret_cast(data.data()), + data.size())) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + plaintext_len = len; + + if (1 != EVP_DecryptFinal_ex(ctx, reinterpret_cast(plaintext.data()) + len, &len)) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + plaintext_len += len; + plaintext.resize(plaintext_len); + + EVP_CIPHER_CTX_free(ctx); + return plaintext; +} + QByteArray EncryptionUtils::encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv) { EVP_CIPHER_CTX *ctx; diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index e16563bb7b..8eefb72ca1 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -29,9 +29,10 @@ struct RSAKeyPair { [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray exportJWKKey(RSA *rsaKey); [[nondiscard]] LIBRUQOLACORE_TESTS_EXPORT RSAKeyPair generateRSAKey(); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encodePrivateKey(const QByteArray &privateKey, const QString &password, const QString &userId); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString decodePrivateKey(const QString &privateKey, const QString &password, const QString &userId); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decodePrivateKey(const QByteArray &encoded); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString getMasterKey(const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray deriveKey(const QByteArray &salt, const QByteArray &baseKey, int iterations = 1000, int keyLength = 32); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray generateRandomIV(int size); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT EncryptionUtils::EncryptionInfo splitVectorAndEcryptedData(const QByteArray &cipherText); diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 482054b2bd..0758a6c10e 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -69,15 +69,18 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonEncodePrivateKey = new QPushButton(QStringLiteral("Encode Private Key"), this); mainLayout->addWidget(pushButtonEncodePrivateKey); connect(pushButtonEncodePrivateKey, &QPushButton::clicked, this, [this]() { - const QByteArray privateKey = EncryptionUtils::generateRSAKey().privateKey; - const auto encodedPrivateKey = EncryptionUtils::encodePrivateKey(privateKey, QStringLiteral("root"), QStringLiteral("admin")); - qDebug() << encodedPrivateKey; - mTextEditResult->setPlainText(QString::fromUtf8(encodedPrivateKey)); + mPrivateKey = EncryptionUtils::generateRSAKey().privateKey; + mEncodedPrivateKey = EncryptionUtils::encodePrivateKey(mPrivateKey, QStringLiteral("root"), QStringLiteral("admin")); + qDebug() << mEncodedPrivateKey.toBase64(); + mTextEditResult->setPlainText(QString::fromUtf8(mEncodedPrivateKey.toBase64())); }); + auto pushButtonDecodePrivateKey = new QPushButton(QStringLiteral("Decode Private Key"), this); mainLayout->addWidget(pushButtonDecodePrivateKey); - connect(pushButtonDecodePrivateKey, &QPushButton::clicked, this, []() { - // test + connect(pushButtonDecodePrivateKey, &QPushButton::clicked, this, [this]() { + qDebug() << mEncodedPrivateKey.toBase64() << "encoded private key"; + qDebug() << mEncodedPrivateKey.size() << "encoded private key size"; + mDecodedPrivateKey = EncryptionUtils::decodePrivateKey(mEncodedPrivateKey); }); auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this); diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index 62d39b129e..c5bbdeb2d8 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -22,4 +22,7 @@ class EncryptionTestGui : public QWidget QByteArray mMasterKey = nullptr; QString password; QString userId; + QByteArray mEncodedPrivateKey = nullptr; + QByteArray mDecodedPrivateKey = nullptr; + QByteArray mPrivateKey = nullptr; }; From 4642f8489ca3dae4872eec4a23c6fdfc4b837f8b Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 6 Jun 2025 15:48:27 +0300 Subject: [PATCH 12/67] Fix resize for 'ciphertext' and 'plaintext' --- src/core/encryption/encryptionutils.cpp | 3 ++- tests/encryptiontestgui/encryptiontestgui.cpp | 16 ++++++++-------- tests/encryptiontestgui/encryptiontestgui.h | 4 +++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 04bf78c05c..fb70a7a7c2 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -235,6 +235,7 @@ QString EncryptionUtils::getMasterKey(const QString &password, const QString &us QByteArray EncryptionUtils::decryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv) { + qDebug() << "decryptAES_CBC: data.size() =" << data.size() << "data.size() % 16 =" << (data.size() % 16); EVP_CIPHER_CTX *ctx; int len; int plaintext_len; @@ -309,7 +310,7 @@ QByteArray EncryptionUtils::encryptAES_CBC(const QByteArray &data, const QByteAr if (1 != EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + len, &len)) return {}; ciphertext_len += len; - + cipherText.resize(ciphertext_len); EVP_CIPHER_CTX_free(ctx); return cipherText; diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 0758a6c10e..7752938cce 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -61,26 +61,26 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonGenerateRSAKey = new QPushButton(QStringLiteral("Generate RSA Pair"), this); mainLayout->addWidget(pushButtonGenerateRSAKey); connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, [this]() { - const auto rsaKeyPair = EncryptionUtils::generateRSAKey(); - qDebug() << "Public Key: " << rsaKeyPair.publicKey << "Private Key: " << rsaKeyPair.privateKey; - mTextEditResult->setPlainText(QString::fromUtf8(rsaKeyPair.publicKey) + QString::fromUtf8(rsaKeyPair.privateKey)); + mRsaKeyPair = EncryptionUtils::generateRSAKey(); + qDebug() << "Public Key: " << mRsaKeyPair.publicKey << "Private Key: " << mRsaKeyPair.privateKey; + mTextEditResult->setPlainText(QString::fromUtf8(mRsaKeyPair.publicKey) + QString::fromUtf8(mRsaKeyPair.privateKey)); }); auto pushButtonEncodePrivateKey = new QPushButton(QStringLiteral("Encode Private Key"), this); mainLayout->addWidget(pushButtonEncodePrivateKey); connect(pushButtonEncodePrivateKey, &QPushButton::clicked, this, [this]() { - mPrivateKey = EncryptionUtils::generateRSAKey().privateKey; - mEncodedPrivateKey = EncryptionUtils::encodePrivateKey(mPrivateKey, QStringLiteral("root"), QStringLiteral("admin")); - qDebug() << mEncodedPrivateKey.toBase64(); + mEncodedPrivateKey = EncryptionUtils::encodePrivateKey(mRsaKeyPair.privateKey, QStringLiteral("root"), QStringLiteral("admin")); + qDebug() << mEncodedPrivateKey.toBase64() << "encoded private key "; mTextEditResult->setPlainText(QString::fromUtf8(mEncodedPrivateKey.toBase64())); }); auto pushButtonDecodePrivateKey = new QPushButton(QStringLiteral("Decode Private Key"), this); mainLayout->addWidget(pushButtonDecodePrivateKey); connect(pushButtonDecodePrivateKey, &QPushButton::clicked, this, [this]() { - qDebug() << mEncodedPrivateKey.toBase64() << "encoded private key"; - qDebug() << mEncodedPrivateKey.size() << "encoded private key size"; mDecodedPrivateKey = EncryptionUtils::decodePrivateKey(mEncodedPrivateKey); + /* qDebug() << mDecodedPrivateKey.toBase64() << "decoded private key '\n' "; + qDebug() << mRsaKeyPair.privateKey << "init private key '\n' "; */ + // mTextEditResult->setPlainText(QString::fromUtf8(mDecodedPrivateKey.toBase64())); }); auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this); diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index c5bbdeb2d8..7ab470235b 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -7,7 +7,9 @@ #pragma once +#include "encryption/encryptionutils.h" #include + class QTextEdit; class EncryptionTestGui : public QWidget { @@ -24,5 +26,5 @@ class EncryptionTestGui : public QWidget QString userId; QByteArray mEncodedPrivateKey = nullptr; QByteArray mDecodedPrivateKey = nullptr; - QByteArray mPrivateKey = nullptr; + EncryptionUtils::RSAKeyPair mRsaKeyPair; }; From 5c9f9253adef847dda92f0d553c9cf71f5463d64 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 6 Jun 2025 16:10:11 +0300 Subject: [PATCH 13/67] WIP show in debug log that init privat key matches decoded private key --- tests/encryptiontestgui/encryptiontestgui.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 7752938cce..1048ca3491 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -78,8 +78,14 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mainLayout->addWidget(pushButtonDecodePrivateKey); connect(pushButtonDecodePrivateKey, &QPushButton::clicked, this, [this]() { mDecodedPrivateKey = EncryptionUtils::decodePrivateKey(mEncodedPrivateKey); - /* qDebug() << mDecodedPrivateKey.toBase64() << "decoded private key '\n' "; - qDebug() << mRsaKeyPair.privateKey << "init private key '\n' "; */ + qDebug() << mDecodedPrivateKey << "decoded private key '\n' "; + qDebug() << mRsaKeyPair.privateKey << "init private key '\n' "; + + /* QString orig = QString::fromUtf8(mRsaKeyPair.privateKey).trimmed().replace("\r\n", "\n"); + QString decoded = QString::fromUtf8(mDecodedPrivateKey).trimmed().replace("\r\n", "\n"); + qDebug() << orig << "decoded private key '\n' "; + qDebug() << mRsaKeyPair.privateKey << "init private key '\n' "; */ + // mTextEditResult->setPlainText(QString::fromUtf8(mDecodedPrivateKey.toBase64())); }); From 0193ca4e7a57dc7479e1d4bac019db579fcf5b4c Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 9 Jun 2025 14:50:49 +0300 Subject: [PATCH 14/67] Fix memory leaks, update UI messages --- src/core/encryption/encryptionutils.cpp | 12 +++++++++--- tests/encryptiontestgui/encryptiontestgui.cpp | 6 ++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index fb70a7a7c2..6a90236e63 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -295,20 +295,26 @@ QByteArray EncryptionUtils::encryptAES_CBC(const QByteArray &data, const QByteAr EVP_aes_256_cbc(), NULL, reinterpret_cast(key.data()), - reinterpret_cast(iv.data()))) + reinterpret_cast(iv.data()))) { + EVP_CIPHER_CTX_free(ctx); return {}; + } if (1 != EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), &len, reinterpret_cast(data.data()), - data.size())) + data.size())) { + EVP_CIPHER_CTX_free(ctx); return {}; + } ciphertext_len = len; - if (1 != EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + len, &len)) + if (1 != EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + len, &len)) { + EVP_CIPHER_CTX_free(ctx); return {}; + } ciphertext_len += len; cipherText.resize(ciphertext_len); EVP_CIPHER_CTX_free(ctx); diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 1048ca3491..7ea0bed4a8 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -63,7 +63,8 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, [this]() { mRsaKeyPair = EncryptionUtils::generateRSAKey(); qDebug() << "Public Key: " << mRsaKeyPair.publicKey << "Private Key: " << mRsaKeyPair.privateKey; - mTextEditResult->setPlainText(QString::fromUtf8(mRsaKeyPair.publicKey) + QString::fromUtf8(mRsaKeyPair.privateKey)); + mTextEditResult->setPlainText(QStringLiteral("RSA pair generation succeded!\n") + QString::fromUtf8(mRsaKeyPair.publicKey) + + QString::fromUtf8(mRsaKeyPair.privateKey)); }); auto pushButtonEncodePrivateKey = new QPushButton(QStringLiteral("Encode Private Key"), this); @@ -71,13 +72,14 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(pushButtonEncodePrivateKey, &QPushButton::clicked, this, [this]() { mEncodedPrivateKey = EncryptionUtils::encodePrivateKey(mRsaKeyPair.privateKey, QStringLiteral("root"), QStringLiteral("admin")); qDebug() << mEncodedPrivateKey.toBase64() << "encoded private key "; - mTextEditResult->setPlainText(QString::fromUtf8(mEncodedPrivateKey.toBase64())); + mTextEditResult->setPlainText(QStringLiteral("Private key encryption succeded!\n") + QString::fromUtf8(mEncodedPrivateKey.toBase64())); }); auto pushButtonDecodePrivateKey = new QPushButton(QStringLiteral("Decode Private Key"), this); mainLayout->addWidget(pushButtonDecodePrivateKey); connect(pushButtonDecodePrivateKey, &QPushButton::clicked, this, [this]() { mDecodedPrivateKey = EncryptionUtils::decodePrivateKey(mEncodedPrivateKey); + mTextEditResult->setPlainText(QStringLiteral("Private key decryption succeded!\n") + QString::fromUtf8(mDecodedPrivateKey)); qDebug() << mDecodedPrivateKey << "decoded private key '\n' "; qDebug() << mRsaKeyPair.privateKey << "init private key '\n' "; From 61e6df2e5b4f01cabc23631f9ff7c610c7ee7f6e Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 9 Jun 2025 15:39:05 +0300 Subject: [PATCH 15/67] Init autotests and brief for rsa pair --- src/core/autotests/rsapairtest.cpp | 46 ++++++++++++++++++++++++++++++ src/core/autotests/rsapairtest.h | 22 ++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/core/autotests/rsapairtest.cpp create mode 100644 src/core/autotests/rsapairtest.h diff --git a/src/core/autotests/rsapairtest.cpp b/src/core/autotests/rsapairtest.cpp new file mode 100644 index 0000000000..1c8559a839 --- /dev/null +++ b/src/core/autotests/rsapairtest.cpp @@ -0,0 +1,46 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "rsapairtest.h" +#include "encryption/encryptionutils.h" +#include + +QTEST_GUILESS_MAIN(RsaPairTest) +RsaPairTest::RsaPairTest(QObject *parent) + : QObject(parent) +{ +} + +void RsaPairTest::rsaPairGenerationNonDeterminismTest() +{ +} + +/** + * @brief Tests the determinism of private key encoding and decoding using the master key. + * + * Definitions: + * - x = master key + * + * - y = initial private key + * + * - z = encode(x, y) = encoded private key + * + * - w = decode(x, z) = decoded private key + * + * The test verifies: + * + * If the same master key x and private key y are used, + * then decoding the encoded key yields the original key: + * + * - decode(x, encode(x, y)) = y = initial private key + * + * In other words, w = y iff x and y are unchanged. + */ +void RsaPairTest::encodeDecodeDeterminismTest() +{ +} + +#include "moc_rsapairtest.cpp" \ No newline at end of file diff --git a/src/core/autotests/rsapairtest.h b/src/core/autotests/rsapairtest.h new file mode 100644 index 0000000000..e857aafc4b --- /dev/null +++ b/src/core/autotests/rsapairtest.h @@ -0,0 +1,22 @@ + +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#pragma once + +#include + +class RsaPairTest : public QObject +{ + Q_OBJECT +public: + explicit RsaPairTest(QObject *parent = nullptr); + ~RsaPairTest() override = default; + +private Q_SLOTS: + void rsaPairGenerationNonDeterminismTest(); + void encodeDecodeDeterminismTest(); +}; From 990b7a19cc02700f2cbdf254f907c9d6d6cac07c Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Tue, 10 Jun 2025 15:18:22 +0300 Subject: [PATCH 16/67] Fix merge bugs and rename the parameter for 'decodeMasterKey()' --- src/core/encryption/encryptionutils.cpp | 12 ++++++------ src/core/encryption/encryptionutils.h | 2 +- tests/encryptiontestgui/encryptiontestgui.h | 10 ++-------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 93ba9b38ae..a3175f0dfe 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -147,19 +147,19 @@ QByteArray EncryptionUtils::encodePrivateKey(const QByteArray &privateKey, const return encoded; } -QByteArray EncryptionUtils::decodePrivateKey(const QByteArray &encoded) +QByteArray EncryptionUtils::decodePrivateKey(const QByteArray &encodedPrivateKey) { - if (encoded.isEmpty()) { + if (encodedPrivateKey.isEmpty()) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Encoded private key is empty"; return {}; } - qDebug() << "decodePrivateKey: encoded.size() =" << encoded.size(); - qDebug() << "decodePrivateKey: first 32 bytes (hex) =" << encoded.left(32).toHex(); + qDebug() << "decodePrivateKey: encoded.size() =" << encodedPrivateKey.size(); + qDebug() << "decodePrivateKey: first 32 bytes (hex) =" << encodedPrivateKey.left(32).toHex(); const QByteArray masterKey = QByteArray("qwertyuiopasdfghqwertyuiopasdfgh", 32); - const QByteArray iv = encoded.left(16); - const QByteArray cipherText = encoded.mid(16); + const QByteArray iv = encodedPrivateKey.left(16); + const QByteArray cipherText = encodedPrivateKey.mid(16); qDebug() << "decodePrivateKey: iv.size() =" << iv.size(); qDebug() << "decodePrivateKey: cipherText.size() =" << cipherText.size(); diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index 528dde6028..235c55c089 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -29,7 +29,7 @@ struct RSAKeyPair { [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray exportJWKKey(RSA *rsaKey); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT RSAKeyPair generateRSAKey(); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encodePrivateKey(const QByteArray &privateKey, const QString &password, const QString &userId); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decodePrivateKey(const QByteArray &encoded); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decodePrivateKey(const QByteArray &encodedPrivateKey); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray getMasterKey(const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index b59c97ad21..7aae646b27 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -25,16 +25,10 @@ class EncryptionTestGui : public QWidget private: QTextEdit *const mTextEdit; QTextEdit *const mTextEditResult; -<<<<<<< HEAD QByteArray mMasterKey = nullptr; - QString password; - QString userId; + QString mPassword; + QString mUserId; QByteArray mEncodedPrivateKey = nullptr; QByteArray mDecodedPrivateKey = nullptr; EncryptionUtils::RSAKeyPair mRsaKeyPair; -======= - QByteArray mMasterKey; - QString mPassword; - QString mUserId; ->>>>>>> gsoc2025 }; From 42c83eea34dd49d42e3c9d481a40bc59fdcac2b5 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Tue, 10 Jun 2025 16:27:22 +0300 Subject: [PATCH 17/67] Fix another merge bugs, add masterKey to param of encode and decode private key function --- src/core/encryption/encryptionutils.cpp | 16 ++++++++++++---- src/core/encryption/encryptionutils.h | 4 ++-- tests/encryptiontestgui/encryptiontestgui.cpp | 6 +++--- tests/encryptiontestgui/encryptiontestgui.h | 4 ---- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index a3175f0dfe..c7194daa9c 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -124,14 +124,18 @@ EncryptionUtils::RSAKeyPair EncryptionUtils::generateRSAKey() return keyPair; } -QByteArray EncryptionUtils::encodePrivateKey(const QByteArray &privateKey, const QString &password, const QString &userId) +QByteArray EncryptionUtils::encodePrivateKey(const QByteArray &privateKey, const QByteArray &masterKey) { if (privateKey.isEmpty()) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Private key is empty"; return {}; } - const QByteArray masterKey = QByteArray("qwertyuiopasdfghqwertyuiopasdfgh", 32); + if (masterKey.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Master key is empty"; + return {}; + } + const QByteArray iv = generateRandomIV(16); const QByteArray ciphertext = encryptAES_CBC(privateKey, masterKey, iv); @@ -147,17 +151,21 @@ QByteArray EncryptionUtils::encodePrivateKey(const QByteArray &privateKey, const return encoded; } -QByteArray EncryptionUtils::decodePrivateKey(const QByteArray &encodedPrivateKey) +QByteArray EncryptionUtils::decodePrivateKey(const QByteArray &encodedPrivateKey, const QByteArray &masterKey) { if (encodedPrivateKey.isEmpty()) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Encoded private key is empty"; return {}; } + if (masterKey.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Master key is empty"; + return {}; + } + qDebug() << "decodePrivateKey: encoded.size() =" << encodedPrivateKey.size(); qDebug() << "decodePrivateKey: first 32 bytes (hex) =" << encodedPrivateKey.left(32).toHex(); - const QByteArray masterKey = QByteArray("qwertyuiopasdfghqwertyuiopasdfgh", 32); const QByteArray iv = encodedPrivateKey.left(16); const QByteArray cipherText = encodedPrivateKey.mid(16); diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index 235c55c089..6d9af420d1 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -28,8 +28,8 @@ struct RSAKeyPair { [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray exportJWKKey(RSA *rsaKey); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT RSAKeyPair generateRSAKey(); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encodePrivateKey(const QByteArray &privateKey, const QString &password, const QString &userId); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decodePrivateKey(const QByteArray &encodedPrivateKey); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encodePrivateKey(const QByteArray &privateKey, const QByteArray &masterKey); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decodePrivateKey(const QByteArray &encodedPrivateKey, const QByteArray &masterKey); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray getMasterKey(const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 901eea7089..b17e9be6aa 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -65,7 +65,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mainLayout->addWidget(pushButtonGenerateRSAKey); connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, [this]() { mRsaKeyPair = EncryptionUtils::generateRSAKey(); - qDebug() << "Public Key: " << mRsaKeyPair.publicKey << "Private Key: " << mRsaKeyPair.privateKey; + qDebug() << "Public Key:\n " << mRsaKeyPair.publicKey << "Private Key:\n " << mRsaKeyPair.privateKey; mTextEditResult->setPlainText(QStringLiteral("RSA pair generation succeded!\n") + QString::fromUtf8(mRsaKeyPair.publicKey) + QString::fromUtf8(mRsaKeyPair.privateKey)); }); @@ -73,7 +73,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonEncodePrivateKey = new QPushButton(QStringLiteral("Encode Private Key"), this); mainLayout->addWidget(pushButtonEncodePrivateKey); connect(pushButtonEncodePrivateKey, &QPushButton::clicked, this, [this]() { - mEncodedPrivateKey = EncryptionUtils::encodePrivateKey(mRsaKeyPair.privateKey, QStringLiteral("root"), QStringLiteral("admin")); + mEncodedPrivateKey = EncryptionUtils::encodePrivateKey(mRsaKeyPair.privateKey, mMasterKey); qDebug() << mEncodedPrivateKey.toBase64() << "encoded private key "; mTextEditResult->setPlainText(QStringLiteral("Private key encryption succeded!\n") + QString::fromUtf8(mEncodedPrivateKey.toBase64())); }); @@ -81,7 +81,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonDecodePrivateKey = new QPushButton(QStringLiteral("Decode Private Key"), this); mainLayout->addWidget(pushButtonDecodePrivateKey); connect(pushButtonDecodePrivateKey, &QPushButton::clicked, this, [this]() { - mDecodedPrivateKey = EncryptionUtils::decodePrivateKey(mEncodedPrivateKey); + mDecodedPrivateKey = EncryptionUtils::decodePrivateKey(mEncodedPrivateKey, mMasterKey); mTextEditResult->setPlainText(QStringLiteral("Private key decryption succeded!\n") + QString::fromUtf8(mDecodedPrivateKey)); qDebug() << mDecodedPrivateKey << "decoded private key '\n' "; qDebug() << mRsaKeyPair.privateKey << "init private key '\n' "; diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index 7aae646b27..2d80e7de9b 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -1,10 +1,6 @@ /* SPDX-FileCopyrightText: 2024-2025 Laurent Montel -<<<<<<< HEAD - SPDX-FileCopyrightText: 2025 Andro Ranogajec -======= SPDX-FileCopyrightText: 2025 Andro Ranogajec ->>>>>>> gsoc2025 SPDX-License-Identifier: LGPL-2.0-or-later */ From fbb7c0ab02188395e0eed503f4565e47da128e57 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Tue, 10 Jun 2025 16:44:40 +0300 Subject: [PATCH 18/67] Remove '= nullptr' from QByteArray variables --- tests/encryptiontestgui/encryptiontestgui.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index 2d80e7de9b..cac31579aa 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -21,10 +21,10 @@ class EncryptionTestGui : public QWidget private: QTextEdit *const mTextEdit; QTextEdit *const mTextEditResult; - QByteArray mMasterKey = nullptr; + QByteArray mMasterKey; QString mPassword; QString mUserId; - QByteArray mEncodedPrivateKey = nullptr; - QByteArray mDecodedPrivateKey = nullptr; + QByteArray mEncodedPrivateKey; + QByteArray mDecodedPrivateKey; EncryptionUtils::RSAKeyPair mRsaKeyPair; }; From 4946ef08cc5fea303d954d572e557ef7bf138dc5 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Tue, 10 Jun 2025 17:02:14 +0300 Subject: [PATCH 19/67] Implement autotest functions --- src/core/autotests/CMakeLists.txt | 1 + src/core/autotests/rsapairtest.cpp | 40 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/core/autotests/CMakeLists.txt b/src/core/autotests/CMakeLists.txt index 47164d9b48..d6cf352739 100644 --- a/src/core/autotests/CMakeLists.txt +++ b/src/core/autotests/CMakeLists.txt @@ -167,6 +167,7 @@ add_ruqola_test(textconvertertest.cpp) if(USE_E2E_SUPPORT) add_ruqola_test(encryptionutilstest.cpp) add_ruqola_test(masterkeytest.cpp) + add_ruqola_test(rsapairtest.cpp) endif() add_ruqola_test(channelstest.cpp) diff --git a/src/core/autotests/rsapairtest.cpp b/src/core/autotests/rsapairtest.cpp index 1c8559a839..c13183ad67 100644 --- a/src/core/autotests/rsapairtest.cpp +++ b/src/core/autotests/rsapairtest.cpp @@ -16,6 +16,15 @@ RsaPairTest::RsaPairTest(QObject *parent) void RsaPairTest::rsaPairGenerationNonDeterminismTest() { + EncryptionUtils::RSAKeyPair rsaPair1; + EncryptionUtils::RSAKeyPair rsaPair2; + + for (int i = 0; i <= 10; i++) { + rsaPair1 = EncryptionUtils::generateRSAKey(); + rsaPair2 = EncryptionUtils::generateRSAKey(); + QVERIFY(rsaPair1.publicKey != rsaPair2.publicKey); + QVERIFY(rsaPair1.privateKey != rsaPair2.privateKey); + } } /** @@ -41,6 +50,37 @@ void RsaPairTest::rsaPairGenerationNonDeterminismTest() */ void RsaPairTest::encodeDecodeDeterminismTest() { + EncryptionUtils::RSAKeyPair rsaPair1; + EncryptionUtils::RSAKeyPair rsaPair2; + + QByteArray privateKey1; + QByteArray privateKey2; + + QByteArray masterKey; + + QByteArray encodedPrivateKey1; + QByteArray encodedPrivateKey2; + QByteArray decodedPrivateKey1; + QByteArray decodedPrivateKey2; + + for (int i = 0; i <= 10; i++) { + rsaPair1 = EncryptionUtils::generateRSAKey(); + rsaPair2 = EncryptionUtils::generateRSAKey(); + + privateKey1 = rsaPair1.privateKey; + privateKey2 = rsaPair2.privateKey; + + masterKey = EncryptionUtils::getMasterKey(EncryptionUtils::generateRandomText(32), EncryptionUtils::generateRandomText(32)); + + encodedPrivateKey1 = EncryptionUtils::encodePrivateKey(rsaPair1.privateKey, masterKey); + encodedPrivateKey2 = EncryptionUtils::encodePrivateKey(rsaPair2.privateKey, masterKey); + + decodedPrivateKey1 = EncryptionUtils::decodePrivateKey(encodedPrivateKey1, masterKey); + decodedPrivateKey2 = EncryptionUtils::decodePrivateKey(encodedPrivateKey2, masterKey); + + QVERIFY(decodedPrivateKey1 == privateKey1); + QVERIFY(decodedPrivateKey2 == privateKey2); + } } #include "moc_rsapairtest.cpp" \ No newline at end of file From 053533f083b1a2565aa8c069abc1387ae575232d Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Wed, 11 Jun 2025 15:34:57 +0300 Subject: [PATCH 20/67] Remove 'qDebug()' and comments --- src/core/encryption/encryptionutils.cpp | 13 +------------ tests/encryptiontestgui/encryptiontestgui.cpp | 7 ------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index c7194daa9c..79668037c4 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -163,17 +163,9 @@ QByteArray EncryptionUtils::decodePrivateKey(const QByteArray &encodedPrivateKey return {}; } - qDebug() << "decodePrivateKey: encoded.size() =" << encodedPrivateKey.size(); - qDebug() << "decodePrivateKey: first 32 bytes (hex) =" << encodedPrivateKey.left(32).toHex(); - const QByteArray iv = encodedPrivateKey.left(16); const QByteArray cipherText = encodedPrivateKey.mid(16); - qDebug() << "decodePrivateKey: iv.size() =" << iv.size(); - qDebug() << "decodePrivateKey: cipherText.size() =" << cipherText.size(); - qDebug() << "decodePrivateKey: iv (hex) =" << iv.toHex(); - qDebug() << "decodePrivateKey: first 32 bytes of cipherText (hex) =" << cipherText.left(32).toHex(); - if (iv.isEmpty()) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Decryption of the private key failed, 'iv' is empty"; return {}; @@ -182,9 +174,7 @@ QByteArray EncryptionUtils::decodePrivateKey(const QByteArray &encodedPrivateKey qCWarning(RUQOLA_ENCRYPTION_LOG) << "Decryption of the private key failed, 'cipherText' is empty"; return {}; } - /* - >>>>>>>>> ERROR HERE, plainText is empty - */ + const QByteArray plainText = decryptAES_CBC(cipherText, masterKey, iv); if (plainText.isEmpty()) { @@ -250,7 +240,6 @@ QByteArray EncryptionUtils::getMasterKey(const QString &password, const QString QByteArray EncryptionUtils::decryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv) { - qDebug() << "decryptAES_CBC: data.size() =" << data.size() << "data.size() % 16 =" << (data.size() % 16); EVP_CIPHER_CTX *ctx; int len; int plaintext_len; diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index b17e9be6aa..a062b5b067 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -85,13 +85,6 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mTextEditResult->setPlainText(QStringLiteral("Private key decryption succeded!\n") + QString::fromUtf8(mDecodedPrivateKey)); qDebug() << mDecodedPrivateKey << "decoded private key '\n' "; qDebug() << mRsaKeyPair.privateKey << "init private key '\n' "; - - /* QString orig = QString::fromUtf8(mRsaKeyPair.privateKey).trimmed().replace("\r\n", "\n"); - QString decoded = QString::fromUtf8(mDecodedPrivateKey).trimmed().replace("\r\n", "\n"); - qDebug() << orig << "decoded private key '\n' "; - qDebug() << mRsaKeyPair.privateKey << "init private key '\n' "; */ - - // mTextEditResult->setPlainText(QString::fromUtf8(mDecodedPrivateKey.toBase64())); }); auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this); From 056cfaab9b095bed287f008a64e9826931cf86c8 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Tue, 27 May 2025 12:56:24 +0300 Subject: [PATCH 21/67] cherry-pick Init commit,'generateRSAkey()' returns a 'keyPair' and log it to console 914195c5f49fa972eae6771c2ca1b10972397f25 --- src/core/encryption/encryptionutils.cpp | 36 +++++++++++++++---- src/core/encryption/encryptionutils.h | 7 +++- tests/encryptiontestgui/encryptiontestgui.cpp | 6 ++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index d518ce6c22..043e404f9d 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -1,6 +1,7 @@ /* SPDX-FileCopyrightText: 2024-2025 Laurent Montel SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: GPL-2.0-or-later */ @@ -56,14 +57,19 @@ QByteArray EncryptionUtils::exportJWKKey(RSA *rsaKey) return doc.toJson(QJsonDocument::Compact); } -void EncryptionUtils::generateRSAKey() +EncryptionUtils::RSAKeyPair EncryptionUtils::generateRSAKey() { + RSAKeyPair keyPair; + int ret = 0; RSA *rsa = nullptr; BIGNUM *bne = nullptr; BIO *bp_public = nullptr; BIO *bp_private = nullptr; + BIO *pubBio = BIO_new(BIO_s_mem()); + BIO *privBio = BIO_new(BIO_s_mem()); + int bits = 2048; unsigned long e = RSA_F4; // équivalent à 0x10001 @@ -71,17 +77,17 @@ void EncryptionUtils::generateRSAKey() ret = BN_set_word(bne, e); if (ret != 1) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Error when generating exponent"; - return; + return {}; } rsa = RSA_new(); ret = RSA_generate_key_ex(rsa, bits, bne, nullptr); if (ret != 1) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Error during generate key"; - return; + return {}; } - bp_public = BIO_new_file("public_key.pem", "w+"); + /* bp_public = BIO_new_file("public_key.pem", "w+"); ret = PEM_write_bio_RSAPublicKey(bp_public, rsa); if (ret != 1) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Error when saving public key"; @@ -93,13 +99,29 @@ void EncryptionUtils::generateRSAKey() if (ret != 1) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Error when saving private key"; return; - } + } */ + + PEM_write_bio_RSA_PUBKEY(pubBio, rsa); + PEM_write_bio_RSAPrivateKey(privBio, rsa, nullptr, nullptr, 0, nullptr, nullptr); + + BUF_MEM *pubBuf = nullptr; + BUF_MEM *privBuf = nullptr; + + BIO_get_mem_ptr(pubBio, &pubBuf); + BIO_get_mem_ptr(privBio, &privBuf); + + keyPair.publicKey = QString::fromUtf8(pubBuf->data, pubBuf->length); + keyPair.privateKey = QString::fromUtf8(privBuf->data, privBuf->length); // Libérer la mémoire - BIO_free_all(bp_public); - BIO_free_all(bp_private); + // BIO_free_all(bp_public); + // BIO_free_all(bp_private); + BIO_free_all(pubBio); + BIO_free_all(privBio); RSA_free(rsa); BN_free(bne); + + return keyPair; } QString EncryptionUtils::encodePrivateKey(const QString &privateKey, const QString &password, const QString &userId) diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index 9a17a58521..9a72a45510 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -1,5 +1,6 @@ /* SPDX-FileCopyrightText: 2024-2025 Laurent Montel + SPDX-FileCopyrightText: 2025 Andro Ranogajec SPDX-License-Identifier: GPL-2.0-or-later */ @@ -20,9 +21,13 @@ struct LIBRUQOLACORE_TESTS_EXPORT EncryptionInfo { [[nodiscard]] bool isValid() const; [[nodiscard]] bool operator==(const EncryptionInfo &other) const; }; +struct RSAKeyPair { + QString publicKey; + QString privateKey; +}; [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray exportJWKKey(RSA *rsaKey); -LIBRUQOLACORE_TESTS_EXPORT void generateRSAKey(); +[[nondiscard]] LIBRUQOLACORE_TESTS_EXPORT RSAKeyPair generateRSAKey(); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString encodePrivateKey(const QString &privateKey, const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray getMasterKey(const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 722c01ebc6..52daa74248 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -63,8 +63,10 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonGenerateRSAKey = new QPushButton(QStringLiteral("Generate RSA Pair"), this); mainLayout->addWidget(pushButtonGenerateRSAKey); - connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, []() { - // test + connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, [this]() { + EncryptionUtils::RSAKeyPair rsaKeyPair = EncryptionUtils::generateRSAKey(); + qDebug() << rsaKeyPair.publicKey; + qDebug() << rsaKeyPair.privateKey; }); auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this); mainLayout->addWidget(pushButtonGenerateSessionKey); From 8521574cb4a2a3a8d545918e458d670dd5ae6e3b Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Thu, 12 Jun 2025 13:14:22 +0300 Subject: [PATCH 22/67] Generate session key, encrypt and decrypt it --- src/core/encryption/encryptionutils.cpp | 74 +++++++++++++++++++ src/core/encryption/encryptionutils.h | 5 ++ tests/encryptiontestgui/encryptiontestgui.cpp | 39 +++++++++- tests/encryptiontestgui/encryptiontestgui.h | 3 + 4 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 043e404f9d..fb0ce556e9 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -215,6 +215,80 @@ QByteArray EncryptionUtils::encryptAES_CBC(const QByteArray &data, const QByteAr return QByteArray(reinterpret_cast(ciphertext), ciphertext_len); } +QByteArray EncryptionUtils::generateSessionKey() +{ + return generateRandomIV(16); +} + +RSA *EncryptionUtils::publicKeyFromPEM(const QByteArray &pem) +{ + BIO *bio = BIO_new_mem_buf(pem.constData(), pem.size()); + if (!bio) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "BIO_new_mem_buf failed!"; + return nullptr; + } + + RSA *rsa = PEM_read_bio_RSA_PUBKEY(bio, nullptr, nullptr, nullptr); + if (!rsa) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "PEM_read_bio_RSA_PUBKEY failed!"; + return nullptr; + } + + BIO_free(bio); + return rsa; +} + +RSA *EncryptionUtils::privateKeyFromPEM(const QByteArray &pem) +{ + BIO *bio = BIO_new_mem_buf(pem.constData(), pem.size()); + if (!bio) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "BIO_new_mem_buf failed!"; + return nullptr; + } + + RSA *rsa = PEM_read_bio_RSAPrivateKey(bio, nullptr, nullptr, nullptr); + if (!rsa) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "PEM_read_bio_RSAPrivateKey failed!"; + BIO_free(bio); + return nullptr; + } + + BIO_free(bio); + return rsa; +} + +QByteArray EncryptionUtils::encryptSessionKey(const QByteArray &sessionKey, RSA *publicKey) +{ + QByteArray encryptedSessionKey(RSA_size(publicKey), 0); + int bytes = RSA_public_encrypt(sessionKey.size(), + reinterpret_cast(sessionKey.constData()), + reinterpret_cast(encryptedSessionKey.data()), + publicKey, + RSA_PKCS1_OAEP_PADDING); + if (bytes == -1) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Session key encryption failed!"; + return {}; + } + encryptedSessionKey.resize(bytes); + return encryptedSessionKey; +} + +QByteArray EncryptionUtils::decryptSessionKey(const QByteArray &encryptedSessionKey, RSA *privateKey) +{ + QByteArray decryptedSessionKey(RSA_size(privateKey), 0); + int bytes = RSA_private_decrypt(encryptedSessionKey.size(), + reinterpret_cast(encryptedSessionKey.constData()), + reinterpret_cast(decryptedSessionKey.data()), + privateKey, + RSA_PKCS1_OAEP_PADDING); + if (bytes == -1) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Session key decryption failed!"; + return {}; + } + decryptedSessionKey.resize(bytes); + return decryptedSessionKey; +} + QByteArray EncryptionUtils::generateRandomIV(int size) { QByteArray iv(size, 0); diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index 9a72a45510..75d0de707b 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -33,6 +33,11 @@ struct RSAKeyPair { [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray deriveKey(const QByteArray &salt, const QByteArray &baseKey, int iterations = 1000, int keyLength = 32); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray generateRandomIV(int size); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray generateSessionKey(); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptSessionKey(const QByteArray &sessionKey, RSA *publicKey); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptSessionKey(const QByteArray &encryptedSessionKey, RSA *privateKey); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT RSA *publicKeyFromPEM(const QByteArray &pem); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT RSA *privateKeyFromPEM(const QByteArray &pem); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString generateRandomText(int size); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT EncryptionUtils::EncryptionInfo splitVectorAndEcryptedData(const QByteArray &cipherText); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray joinVectorAndEcryptedData(const EncryptionUtils::EncryptionInfo &info); diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 52daa74248..01075ceff7 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -68,12 +68,47 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) qDebug() << rsaKeyPair.publicKey; qDebug() << rsaKeyPair.privateKey; }); + auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this); mainLayout->addWidget(pushButtonGenerateSessionKey); - connect(pushButtonGenerateSessionKey, &QPushButton::clicked, this, []() { - // test + connect(pushButtonGenerateSessionKey, &QPushButton::clicked, this, [this]() { + mSessionKey = EncryptionUtils::generateSessionKey(); + qDebug() << "Derived Session Key:" << mSessionKey.toBase64(); + mTextEditResult->setPlainText((QStringLiteral("Session key generation succeeded!\n") + QString::fromUtf8(mSessionKey.toBase64()))); + }); + + auto pushButtonEncryptSessionKey = new QPushButton(QStringLiteral("Encrypt Session Key"), this); + mainLayout->addWidget(pushButtonEncryptSessionKey); + connect(pushButtonEncryptSessionKey, &QPushButton::clicked, this, [this]() { + auto rsaPair = EncryptionUtils::generateRSAKey(); + auto privateKey = rsaPair.privateKey.toUtf8(); + auto publicKey = rsaPair.publicKey.toUtf8(); + + RSA *publicKeyfromPem = EncryptionUtils::publicKeyFromPEM(publicKey); + mEncryptedSessionKey = EncryptionUtils::encryptSessionKey(mSessionKey, publicKeyfromPem); + + qDebug() << "Public Key from PEM:" << publicKeyfromPem; + + qDebug() << "Encrypted Session Key:" << mEncryptedSessionKey.toBase64(); + // mTextEditResult->setPlainText((QStringLiteral("Session key encryption succeeded!\n") + QString::fromUtf8(mEncryptedSessionKey.toBase64()))); + + RSA *privateKeyfromPem = EncryptionUtils::privateKeyFromPEM(privateKey); + + qDebug() << "Private Key from PEM:" << privateKeyfromPem; + mDecryptedSessionKey = EncryptionUtils::decryptSessionKey(mEncryptedSessionKey, privateKeyfromPem); + + qDebug() << "Decrypted Session Key:" << mDecryptedSessionKey.toBase64(); + + // mTextEditResult->setPlainText((QStringLiteral("Session key decryption succeeded!\n") + QString::fromUtf8(mDecryptedSessionKey.toBase64()))); }); + auto pushButtonDecryptSessionKey = new QPushButton(QStringLiteral("Decrypt Session Key"), this); + mainLayout->addWidget(pushButtonDecryptSessionKey); + connect(pushButtonDecryptSessionKey, &QPushButton::clicked, this, [this]() { + mDecryptedSessionKey = EncryptionUtils::generateSessionKey(); + qDebug() << "Decrypted Session Key:" << mDecryptedSessionKey.toBase64(); + mTextEditResult->setPlainText((QStringLiteral("Session key decryption succeeded!\n") + QString::fromUtf8(mDecryptedSessionKey.toBase64()))); + }); auto pushButtonEncode = new QPushButton(QStringLiteral("Encode"), this); mainLayout->addWidget(pushButtonEncode); connect(pushButtonEncode, &QPushButton::clicked, this, []() { diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index 5ba977755b..3e7741da5c 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -22,4 +22,7 @@ class EncryptionTestGui : public QWidget QByteArray mMasterKey; QString mPassword; QString mUserId; + QByteArray mSessionKey; + QByteArray mEncryptedSessionKey; + QByteArray mDecryptedSessionKey; }; From 2a2fcbc05b2a0949cbdeb43c31ad81e371f9add8 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 13 Jun 2025 12:29:43 +0300 Subject: [PATCH 23/67] Switch '//test' to '//TODO' --- tests/encryptiontestgui/encryptiontestgui.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index a062b5b067..1137f2cef4 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -90,25 +90,25 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this); mainLayout->addWidget(pushButtonGenerateSessionKey); connect(pushButtonGenerateSessionKey, &QPushButton::clicked, this, []() { - // test + // TODO }); auto pushButtonEncodeMessage = new QPushButton(QStringLiteral("Encode Message"), this); mainLayout->addWidget(pushButtonEncodeMessage); connect(pushButtonEncodeMessage, &QPushButton::clicked, this, []() { - // test + // TODO }); auto pushButtonDecodeMessage = new QPushButton(QStringLiteral("Decode Message"), this); mainLayout->addWidget(pushButtonDecodeMessage); connect(pushButtonDecodeMessage, &QPushButton::clicked, this, []() { - // test + // TODO }); auto pushButtonReset = new QPushButton(QStringLiteral("Reset"), this); mainLayout->addWidget(pushButtonReset); connect(pushButtonReset, &QPushButton::clicked, this, []() { - // test + // TODO }); mTextEditResult->setReadOnly(true); From 18145c4162531bd90aa5ebc2c4e5207beb5efe9b Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 13 Jun 2025 13:29:08 +0300 Subject: [PATCH 24/67] Rename Encode/Decode to Encrypt/Decrypt add if stataments to the UI --- src/core/autotests/rsapairtest.cpp | 32 +++++++------- src/core/autotests/rsapairtest.h | 2 +- src/core/encryption/encryptionutils.cpp | 20 ++++----- src/core/encryption/encryptionutils.h | 4 +- tests/encryptiontestgui/encryptiontestgui.cpp | 43 +++++++++++++------ tests/encryptiontestgui/encryptiontestgui.h | 4 +- 6 files changed, 61 insertions(+), 44 deletions(-) diff --git a/src/core/autotests/rsapairtest.cpp b/src/core/autotests/rsapairtest.cpp index c13183ad67..762252ebc3 100644 --- a/src/core/autotests/rsapairtest.cpp +++ b/src/core/autotests/rsapairtest.cpp @@ -28,27 +28,27 @@ void RsaPairTest::rsaPairGenerationNonDeterminismTest() } /** - * @brief Tests the determinism of private key encoding and decoding using the master key. + * @brief Tests the determinism of private key encryption and decryption using the master key. * * Definitions: * - x = master key * * - y = initial private key * - * - z = encode(x, y) = encoded private key + * - z = encrypt(x, y) = encrypted private key * - * - w = decode(x, z) = decoded private key + * - w = decrypt(x, z) = decrypted private key * * The test verifies: * * If the same master key x and private key y are used, - * then decoding the encoded key yields the original key: + * then decrypting the encrypted key yields the original key: * - * - decode(x, encode(x, y)) = y = initial private key + * - decrypt(x, encrypt(x, y)) = y = initial private key * * In other words, w = y iff x and y are unchanged. */ -void RsaPairTest::encodeDecodeDeterminismTest() +void RsaPairTest::encryptDecryptDeterminismTest() { EncryptionUtils::RSAKeyPair rsaPair1; EncryptionUtils::RSAKeyPair rsaPair2; @@ -58,10 +58,10 @@ void RsaPairTest::encodeDecodeDeterminismTest() QByteArray masterKey; - QByteArray encodedPrivateKey1; - QByteArray encodedPrivateKey2; - QByteArray decodedPrivateKey1; - QByteArray decodedPrivateKey2; + QByteArray encryptedPrivateKey1; + QByteArray encryptedPrivateKey2; + QByteArray decryptedPrivateKey1; + QByteArray decryptedPrivateKey2; for (int i = 0; i <= 10; i++) { rsaPair1 = EncryptionUtils::generateRSAKey(); @@ -72,14 +72,14 @@ void RsaPairTest::encodeDecodeDeterminismTest() masterKey = EncryptionUtils::getMasterKey(EncryptionUtils::generateRandomText(32), EncryptionUtils::generateRandomText(32)); - encodedPrivateKey1 = EncryptionUtils::encodePrivateKey(rsaPair1.privateKey, masterKey); - encodedPrivateKey2 = EncryptionUtils::encodePrivateKey(rsaPair2.privateKey, masterKey); + encryptedPrivateKey1 = EncryptionUtils::encryptPrivateKey(rsaPair1.privateKey, masterKey); + encryptedPrivateKey2 = EncryptionUtils::decryptPrivateKey(rsaPair2.privateKey, masterKey); - decodedPrivateKey1 = EncryptionUtils::decodePrivateKey(encodedPrivateKey1, masterKey); - decodedPrivateKey2 = EncryptionUtils::decodePrivateKey(encodedPrivateKey2, masterKey); + decryptedPrivateKey1 = EncryptionUtils::decryptPrivateKey(encryptedPrivateKey1, masterKey); + decryptedPrivateKey2 = EncryptionUtils::decryptPrivateKey(encryptedPrivateKey2, masterKey); - QVERIFY(decodedPrivateKey1 == privateKey1); - QVERIFY(decodedPrivateKey2 == privateKey2); + QVERIFY(decryptedPrivateKey1 == privateKey1); + QVERIFY(decryptedPrivateKey2 == privateKey2); } } diff --git a/src/core/autotests/rsapairtest.h b/src/core/autotests/rsapairtest.h index e857aafc4b..d8a6fa9987 100644 --- a/src/core/autotests/rsapairtest.h +++ b/src/core/autotests/rsapairtest.h @@ -18,5 +18,5 @@ class RsaPairTest : public QObject private Q_SLOTS: void rsaPairGenerationNonDeterminismTest(); - void encodeDecodeDeterminismTest(); + void encryptDecryptDeterminismTest(); }; diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 79668037c4..130d78b24b 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -124,7 +124,7 @@ EncryptionUtils::RSAKeyPair EncryptionUtils::generateRSAKey() return keyPair; } -QByteArray EncryptionUtils::encodePrivateKey(const QByteArray &privateKey, const QByteArray &masterKey) +QByteArray EncryptionUtils::encryptPrivateKey(const QByteArray &privateKey, const QByteArray &masterKey) { if (privateKey.isEmpty()) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Private key is empty"; @@ -144,17 +144,17 @@ QByteArray EncryptionUtils::encodePrivateKey(const QByteArray &privateKey, const return {}; } - QByteArray encoded; - encoded.append(iv); - encoded.append(ciphertext); + QByteArray encrypted; + encrypted.append(iv); + encrypted.append(ciphertext); - return encoded; + return encrypted; } -QByteArray EncryptionUtils::decodePrivateKey(const QByteArray &encodedPrivateKey, const QByteArray &masterKey) +QByteArray EncryptionUtils::decryptPrivateKey(const QByteArray &encryptedPrivateKey, const QByteArray &masterKey) { - if (encodedPrivateKey.isEmpty()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "Encoded private key is empty"; + if (encryptedPrivateKey.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Encrypted private key is empty"; return {}; } @@ -163,8 +163,8 @@ QByteArray EncryptionUtils::decodePrivateKey(const QByteArray &encodedPrivateKey return {}; } - const QByteArray iv = encodedPrivateKey.left(16); - const QByteArray cipherText = encodedPrivateKey.mid(16); + const QByteArray iv = encryptedPrivateKey.left(16); + const QByteArray cipherText = encryptedPrivateKey.mid(16); if (iv.isEmpty()) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Decryption of the private key failed, 'iv' is empty"; diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index 6d9af420d1..e7f9ece1ed 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -28,8 +28,8 @@ struct RSAKeyPair { [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray exportJWKKey(RSA *rsaKey); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT RSAKeyPair generateRSAKey(); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encodePrivateKey(const QByteArray &privateKey, const QByteArray &masterKey); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decodePrivateKey(const QByteArray &encodedPrivateKey, const QByteArray &masterKey); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptPrivateKey(const QByteArray &privateKey, const QByteArray &masterKey); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptPrivateKey(const QByteArray &encryptedPrivateKey, const QByteArray &masterKey); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray getMasterKey(const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 1137f2cef4..db7e856b57 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -55,8 +55,13 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mUserId = userIdEdit->text(); mPassword = passwordEdit->text(); mMasterKey = EncryptionUtils::getMasterKey(mPassword, mUserId); - qDebug() << "Derived Master Key:" << mMasterKey.toBase64(); - mTextEditResult->setPlainText((QStringLiteral("Master Key derivation succeeded!\n") + QString::fromUtf8(mMasterKey.toBase64()))); + + if (mMasterKey.isEmpty()) + mTextEditResult->setPlainText(QStringLiteral("Master key is empty, generation failed!")); + else { + qDebug() << "Derived Master Key:" << mMasterKey.toBase64(); + mTextEditResult->setPlainText((QStringLiteral("Master Key derivation succeeded!\n") + QString::fromUtf8(mMasterKey.toBase64()))); + } } delete dialog; }); @@ -70,21 +75,33 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) + QString::fromUtf8(mRsaKeyPair.privateKey)); }); - auto pushButtonEncodePrivateKey = new QPushButton(QStringLiteral("Encode Private Key"), this); + auto pushButtonEncodePrivateKey = new QPushButton(QStringLiteral("Encrypt Private Key"), this); mainLayout->addWidget(pushButtonEncodePrivateKey); connect(pushButtonEncodePrivateKey, &QPushButton::clicked, this, [this]() { - mEncodedPrivateKey = EncryptionUtils::encodePrivateKey(mRsaKeyPair.privateKey, mMasterKey); - qDebug() << mEncodedPrivateKey.toBase64() << "encoded private key "; - mTextEditResult->setPlainText(QStringLiteral("Private key encryption succeded!\n") + QString::fromUtf8(mEncodedPrivateKey.toBase64())); + if (mMasterKey.isEmpty()) + mTextEditResult->setPlainText(QStringLiteral("Master key is empty, encryption of the private key failed!")); + else if (mRsaKeyPair.privateKey.isEmpty()) + mTextEditResult->setPlainText(QStringLiteral("Private key is empty, encryption of the private key failed!")); + else { + mEncryptedPrivateKey = EncryptionUtils::encryptPrivateKey(mRsaKeyPair.privateKey, mMasterKey); + qDebug() << mEncryptedPrivateKey.toBase64() << "encrypted and encoded to 'base64()' private key "; + mTextEditResult->setPlainText(QStringLiteral("Private key encryption succeded!\n") + QString::fromUtf8(mEncryptedPrivateKey.toBase64())); + } }); - auto pushButtonDecodePrivateKey = new QPushButton(QStringLiteral("Decode Private Key"), this); + auto pushButtonDecodePrivateKey = new QPushButton(QStringLiteral("Decrypt Private Key"), this); mainLayout->addWidget(pushButtonDecodePrivateKey); connect(pushButtonDecodePrivateKey, &QPushButton::clicked, this, [this]() { - mDecodedPrivateKey = EncryptionUtils::decodePrivateKey(mEncodedPrivateKey, mMasterKey); - mTextEditResult->setPlainText(QStringLiteral("Private key decryption succeded!\n") + QString::fromUtf8(mDecodedPrivateKey)); - qDebug() << mDecodedPrivateKey << "decoded private key '\n' "; - qDebug() << mRsaKeyPair.privateKey << "init private key '\n' "; + if (mMasterKey.isEmpty()) + mTextEditResult->setPlainText(QStringLiteral("Master key is empty, encryption of the private key failed!")); + else if (mEncryptedPrivateKey.isEmpty()) + mTextEditResult->setPlainText(QStringLiteral("Encrypted private key is empty, decryption of the private key failed!")); + else { + mDecryptedPrivateKey = EncryptionUtils::decryptPrivateKey(mEncryptedPrivateKey, mMasterKey); + mTextEditResult->setPlainText(QStringLiteral("Private key decryption succeded!\n") + QString::fromUtf8(mDecryptedPrivateKey)); + qDebug() << mDecryptedPrivateKey << "decrypted private key '\n' "; + qDebug() << mRsaKeyPair.privateKey << "init private key '\n' "; + } }); auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this); @@ -93,13 +110,13 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) // TODO }); - auto pushButtonEncodeMessage = new QPushButton(QStringLiteral("Encode Message"), this); + auto pushButtonEncodeMessage = new QPushButton(QStringLiteral("Encrypt Message"), this); mainLayout->addWidget(pushButtonEncodeMessage); connect(pushButtonEncodeMessage, &QPushButton::clicked, this, []() { // TODO }); - auto pushButtonDecodeMessage = new QPushButton(QStringLiteral("Decode Message"), this); + auto pushButtonDecodeMessage = new QPushButton(QStringLiteral("Decrypt Message"), this); mainLayout->addWidget(pushButtonDecodeMessage); connect(pushButtonDecodeMessage, &QPushButton::clicked, this, []() { // TODO diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index cac31579aa..b09f631089 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -24,7 +24,7 @@ class EncryptionTestGui : public QWidget QByteArray mMasterKey; QString mPassword; QString mUserId; - QByteArray mEncodedPrivateKey; - QByteArray mDecodedPrivateKey; + QByteArray mEncryptedPrivateKey; + QByteArray mDecryptedPrivateKey; EncryptionUtils::RSAKeyPair mRsaKeyPair; }; From e481e29220f4ec977da7269e11d4e266d0ea574c Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 13 Jun 2025 13:47:23 +0300 Subject: [PATCH 25/67] Show encryption and decryption of the session key in the UI --- tests/encryptiontestgui/encryptiontestgui.cpp | 29 +++++++------------ tests/encryptiontestgui/encryptiontestgui.h | 3 ++ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 01075ceff7..e6d8174c2b 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -64,9 +64,9 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonGenerateRSAKey = new QPushButton(QStringLiteral("Generate RSA Pair"), this); mainLayout->addWidget(pushButtonGenerateRSAKey); connect(pushButtonGenerateRSAKey, &QPushButton::clicked, this, [this]() { - EncryptionUtils::RSAKeyPair rsaKeyPair = EncryptionUtils::generateRSAKey(); - qDebug() << rsaKeyPair.publicKey; - qDebug() << rsaKeyPair.privateKey; + mRsaKeyPair = EncryptionUtils::generateRSAKey(); + qDebug() << mRsaKeyPair.publicKey; + qDebug() << mRsaKeyPair.privateKey; }); auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this); @@ -80,33 +80,26 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonEncryptSessionKey = new QPushButton(QStringLiteral("Encrypt Session Key"), this); mainLayout->addWidget(pushButtonEncryptSessionKey); connect(pushButtonEncryptSessionKey, &QPushButton::clicked, this, [this]() { - auto rsaPair = EncryptionUtils::generateRSAKey(); - auto privateKey = rsaPair.privateKey.toUtf8(); - auto publicKey = rsaPair.publicKey.toUtf8(); - + auto publicKey = mRsaKeyPair.publicKey.toUtf8(); RSA *publicKeyfromPem = EncryptionUtils::publicKeyFromPEM(publicKey); mEncryptedSessionKey = EncryptionUtils::encryptSessionKey(mSessionKey, publicKeyfromPem); qDebug() << "Public Key from PEM:" << publicKeyfromPem; - qDebug() << "Encrypted Session Key:" << mEncryptedSessionKey.toBase64(); - // mTextEditResult->setPlainText((QStringLiteral("Session key encryption succeeded!\n") + QString::fromUtf8(mEncryptedSessionKey.toBase64()))); - - RSA *privateKeyfromPem = EncryptionUtils::privateKeyFromPEM(privateKey); - - qDebug() << "Private Key from PEM:" << privateKeyfromPem; - mDecryptedSessionKey = EncryptionUtils::decryptSessionKey(mEncryptedSessionKey, privateKeyfromPem); - qDebug() << "Decrypted Session Key:" << mDecryptedSessionKey.toBase64(); - - // mTextEditResult->setPlainText((QStringLiteral("Session key decryption succeeded!\n") + QString::fromUtf8(mDecryptedSessionKey.toBase64()))); + mTextEditResult->setPlainText((QStringLiteral("Session key encryption succeeded!\n") + QString::fromUtf8(mEncryptedSessionKey.toBase64()))); }); auto pushButtonDecryptSessionKey = new QPushButton(QStringLiteral("Decrypt Session Key"), this); mainLayout->addWidget(pushButtonDecryptSessionKey); connect(pushButtonDecryptSessionKey, &QPushButton::clicked, this, [this]() { - mDecryptedSessionKey = EncryptionUtils::generateSessionKey(); + auto privateKey = mRsaKeyPair.privateKey.toUtf8(); + RSA *privateKeyfromPem = EncryptionUtils::privateKeyFromPEM(privateKey); + mDecryptedSessionKey = EncryptionUtils::decryptSessionKey(mEncryptedSessionKey, privateKeyfromPem); + + qDebug() << "Private Key from PEM:" << privateKeyfromPem; qDebug() << "Decrypted Session Key:" << mDecryptedSessionKey.toBase64(); + mTextEditResult->setPlainText((QStringLiteral("Session key decryption succeeded!\n") + QString::fromUtf8(mDecryptedSessionKey.toBase64()))); }); auto pushButtonEncode = new QPushButton(QStringLiteral("Encode"), this); diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index 3e7741da5c..7a77333411 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -8,6 +8,8 @@ #pragma once #include +#include + class QTextEdit; class EncryptionTestGui : public QWidget { @@ -22,6 +24,7 @@ class EncryptionTestGui : public QWidget QByteArray mMasterKey; QString mPassword; QString mUserId; + EncryptionUtils::RSAKeyPair mRsaKeyPair; QByteArray mSessionKey; QByteArray mEncryptedSessionKey; QByteArray mDecryptedSessionKey; From bab29533e80e79981152d5e6b4f24ba01614f1a2 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 13 Jun 2025 14:14:02 +0300 Subject: [PATCH 26/67] Some if statements to avoid minor errors in the UI --- tests/encryptiontestgui/encryptiontestgui.cpp | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index e6d8174c2b..f982bfa9e7 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -81,26 +81,30 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mainLayout->addWidget(pushButtonEncryptSessionKey); connect(pushButtonEncryptSessionKey, &QPushButton::clicked, this, [this]() { auto publicKey = mRsaKeyPair.publicKey.toUtf8(); - RSA *publicKeyfromPem = EncryptionUtils::publicKeyFromPEM(publicKey); - mEncryptedSessionKey = EncryptionUtils::encryptSessionKey(mSessionKey, publicKeyfromPem); - - qDebug() << "Public Key from PEM:" << publicKeyfromPem; - qDebug() << "Encrypted Session Key:" << mEncryptedSessionKey.toBase64(); - - mTextEditResult->setPlainText((QStringLiteral("Session key encryption succeeded!\n") + QString::fromUtf8(mEncryptedSessionKey.toBase64()))); + if (publicKey.isEmpty()) + mTextEditResult->setPlainText((QStringLiteral("Public key is empty, session key encryption failed!\n"))); + else { + RSA *publicKeyfromPem = EncryptionUtils::publicKeyFromPEM(publicKey); + mEncryptedSessionKey = EncryptionUtils::encryptSessionKey(mSessionKey, publicKeyfromPem); + qDebug() << "Public Key from PEM:" << publicKeyfromPem; + qDebug() << "Encrypted Session Key:" << mEncryptedSessionKey.toBase64(); + mTextEditResult->setPlainText((QStringLiteral("Session key encryption succeeded!\n") + QString::fromUtf8(mEncryptedSessionKey.toBase64()))); + } }); auto pushButtonDecryptSessionKey = new QPushButton(QStringLiteral("Decrypt Session Key"), this); mainLayout->addWidget(pushButtonDecryptSessionKey); connect(pushButtonDecryptSessionKey, &QPushButton::clicked, this, [this]() { auto privateKey = mRsaKeyPair.privateKey.toUtf8(); - RSA *privateKeyfromPem = EncryptionUtils::privateKeyFromPEM(privateKey); - mDecryptedSessionKey = EncryptionUtils::decryptSessionKey(mEncryptedSessionKey, privateKeyfromPem); - - qDebug() << "Private Key from PEM:" << privateKeyfromPem; - qDebug() << "Decrypted Session Key:" << mDecryptedSessionKey.toBase64(); - - mTextEditResult->setPlainText((QStringLiteral("Session key decryption succeeded!\n") + QString::fromUtf8(mDecryptedSessionKey.toBase64()))); + if (privateKey.isEmpty()) + mTextEditResult->setPlainText((QStringLiteral("Private key is empty, session key decryption failed!\n"))); + else { + RSA *privateKeyfromPem = EncryptionUtils::privateKeyFromPEM(privateKey); + mDecryptedSessionKey = EncryptionUtils::decryptSessionKey(mEncryptedSessionKey, privateKeyfromPem); + qDebug() << "Private Key from PEM:" << privateKeyfromPem; + qDebug() << "Decrypted Session Key:" << mDecryptedSessionKey.toBase64(); + mTextEditResult->setPlainText((QStringLiteral("Session key decryption succeeded!\n") + QString::fromUtf8(mDecryptedSessionKey.toBase64()))); + } }); auto pushButtonEncode = new QPushButton(QStringLiteral("Encode"), this); mainLayout->addWidget(pushButtonEncode); From b4e139c273dc0d2246b00a1c9e3ab48aada3734a Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 13 Jun 2025 14:48:21 +0300 Subject: [PATCH 27/67] Add autotests for session key encryption decryption and generation --- src/core/autotests/CMakeLists.txt | 1 + src/core/autotests/sessionkeytest.cpp | 46 +++++++++++++++++++++++++++ src/core/autotests/sessionkeytest.h | 21 ++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 src/core/autotests/sessionkeytest.cpp create mode 100644 src/core/autotests/sessionkeytest.h diff --git a/src/core/autotests/CMakeLists.txt b/src/core/autotests/CMakeLists.txt index 47164d9b48..70fce92967 100644 --- a/src/core/autotests/CMakeLists.txt +++ b/src/core/autotests/CMakeLists.txt @@ -167,6 +167,7 @@ add_ruqola_test(textconvertertest.cpp) if(USE_E2E_SUPPORT) add_ruqola_test(encryptionutilstest.cpp) add_ruqola_test(masterkeytest.cpp) + add_ruqola_test(sessionkeytest.cpp) endif() add_ruqola_test(channelstest.cpp) diff --git a/src/core/autotests/sessionkeytest.cpp b/src/core/autotests/sessionkeytest.cpp new file mode 100644 index 0000000000..5d3c0354b0 --- /dev/null +++ b/src/core/autotests/sessionkeytest.cpp @@ -0,0 +1,46 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "sessionkeytest.h" +#include "encryption/encryptionutils.h" +#include + +QTEST_GUILESS_MAIN(SessionKeyTest) +SessionKeyTest::SessionKeyTest(QObject *parent) + : QObject(parent) +{ +} + +void SessionKeyTest::sessionKeyGenerationTest() +{ + QVERIFY(!(EncryptionUtils::generateSessionKey().isEmpty())); +} + +/** + * @brief Tests the encryption and decryption of the session key. + * + * This test verifies that a randomly generated 128-bit (16 bytes) AES session key, + * when encrypted with an RSA public key and then decrypted with the corresponding + * RSA private key, results in the original session key. + */ +void SessionKeyTest::sessionKeyEncryptionDecryptionTest() +{ + QByteArray sessionKey; + QByteArray encryptedSessionKey; + QByteArray decryptedSessionKey; + auto rsaKeyPair = EncryptionUtils::generateRSAKey(); + auto privateKey = rsaKeyPair.privateKey.toUtf8(); + auto publicKey = rsaKeyPair.publicKey.toUtf8(); + + for (int i = 0; i <= 10; i++) { + sessionKey = EncryptionUtils::generateSessionKey(); + encryptedSessionKey = EncryptionUtils::encryptSessionKey(sessionKey, EncryptionUtils::publicKeyFromPEM(publicKey)); + decryptedSessionKey = EncryptionUtils::decryptSessionKey(encryptedSessionKey, EncryptionUtils::privateKeyFromPEM(privateKey)); + QVERIFY(sessionKey == decryptedSessionKey); + } +} + +#include "moc_sessionkeytest.cpp" \ No newline at end of file diff --git a/src/core/autotests/sessionkeytest.h b/src/core/autotests/sessionkeytest.h new file mode 100644 index 0000000000..fc04d9ec2f --- /dev/null +++ b/src/core/autotests/sessionkeytest.h @@ -0,0 +1,21 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#pragma once + +#include + +class SessionKeyTest : public QObject +{ + Q_OBJECT +public: + explicit SessionKeyTest(QObject *parent = nullptr); + ~SessionKeyTest() override = default; + +private Q_SLOTS: + void sessionKeyGenerationTest(); + void sessionKeyEncryptionDecryptionTest(); +}; From 9030fae35980918d507f3dcc8d815acb06a68631 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 16 Jun 2025 10:53:25 +0300 Subject: [PATCH 28/67] Redundancy cleanup in encryp decrypt test for rsa key pair --- src/core/autotests/rsapairtest.cpp | 34 ++++++++---------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/src/core/autotests/rsapairtest.cpp b/src/core/autotests/rsapairtest.cpp index 762252ebc3..1970e365f2 100644 --- a/src/core/autotests/rsapairtest.cpp +++ b/src/core/autotests/rsapairtest.cpp @@ -50,36 +50,20 @@ void RsaPairTest::rsaPairGenerationNonDeterminismTest() */ void RsaPairTest::encryptDecryptDeterminismTest() { - EncryptionUtils::RSAKeyPair rsaPair1; - EncryptionUtils::RSAKeyPair rsaPair2; - - QByteArray privateKey1; - QByteArray privateKey2; - + EncryptionUtils::RSAKeyPair rsaPair; + QByteArray privateKey; QByteArray masterKey; - - QByteArray encryptedPrivateKey1; - QByteArray encryptedPrivateKey2; - QByteArray decryptedPrivateKey1; - QByteArray decryptedPrivateKey2; + QByteArray encryptedPrivateKey; + QByteArray decryptedPrivateKey; for (int i = 0; i <= 10; i++) { - rsaPair1 = EncryptionUtils::generateRSAKey(); - rsaPair2 = EncryptionUtils::generateRSAKey(); - - privateKey1 = rsaPair1.privateKey; - privateKey2 = rsaPair2.privateKey; - + rsaPair = EncryptionUtils::generateRSAKey(); + privateKey = rsaPair.privateKey; masterKey = EncryptionUtils::getMasterKey(EncryptionUtils::generateRandomText(32), EncryptionUtils::generateRandomText(32)); + encryptedPrivateKey = EncryptionUtils::encryptPrivateKey(rsaPair.privateKey, masterKey); + decryptedPrivateKey = EncryptionUtils::decryptPrivateKey(encryptedPrivateKey, masterKey); - encryptedPrivateKey1 = EncryptionUtils::encryptPrivateKey(rsaPair1.privateKey, masterKey); - encryptedPrivateKey2 = EncryptionUtils::decryptPrivateKey(rsaPair2.privateKey, masterKey); - - decryptedPrivateKey1 = EncryptionUtils::decryptPrivateKey(encryptedPrivateKey1, masterKey); - decryptedPrivateKey2 = EncryptionUtils::decryptPrivateKey(encryptedPrivateKey2, masterKey); - - QVERIFY(decryptedPrivateKey1 == privateKey1); - QVERIFY(decryptedPrivateKey2 == privateKey2); + QVERIFY(decryptedPrivateKey == privateKey); } } From ca2e2a42fd5f6760729b3e10e9dfd20550c2bede Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 16 Jun 2025 12:33:50 +0300 Subject: [PATCH 29/67] Add AES_CBC_128 for encryption and decryptions switch previous AES_CBC to AES_CBC_256 --- src/core/encryption/encryptionutils.cpp | 211 ++++++++++++++++++++---- src/core/encryption/encryptionutils.h | 5 +- 2 files changed, 184 insertions(+), 32 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index fb0ce556e9..c325f14f86 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -184,37 +184,6 @@ QByteArray EncryptionUtils::getMasterKey(const QString &password, const QString #endif } -QByteArray EncryptionUtils::encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv) -{ - EVP_CIPHER_CTX *ctx; - int len; - int ciphertext_len; - unsigned char ciphertext[128]; - - if (!(ctx = EVP_CIPHER_CTX_new())) - return {}; - - if (1 - != EVP_EncryptInit_ex(ctx, - EVP_aes_256_cbc(), - NULL, - reinterpret_cast(key.data()), - reinterpret_cast(iv.data()))) - return {}; - - if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, reinterpret_cast(data.data()), data.size())) - return {}; - ciphertext_len = len; - - if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) - return {}; - ciphertext_len += len; - - EVP_CIPHER_CTX_free(ctx); - - return QByteArray(reinterpret_cast(ciphertext), ciphertext_len); -} - QByteArray EncryptionUtils::generateSessionKey() { return generateRandomIV(16); @@ -289,6 +258,186 @@ QByteArray EncryptionUtils::decryptSessionKey(const QByteArray &encryptedSession return decryptedSessionKey; } +QByteArray EncryptionUtils::decryptAES_CBC_256(const QByteArray &data, const QByteArray &key, const QByteArray &iv) +{ + EVP_CIPHER_CTX *ctx; + int len; + int plaintext_len; + + QByteArray plaintext(data.size(), 0); + + ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + return {}; + + if (1 + != EVP_DecryptInit_ex(ctx, + EVP_aes_256_cbc(), + nullptr, + reinterpret_cast(key.data()), + reinterpret_cast(iv.data()))) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + + if (1 + != EVP_DecryptUpdate(ctx, + reinterpret_cast(plaintext.data()), + &len, + reinterpret_cast(data.data()), + data.size())) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + plaintext_len = len; + + if (1 != EVP_DecryptFinal_ex(ctx, reinterpret_cast(plaintext.data()) + len, &len)) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + plaintext_len += len; + plaintext.resize(plaintext_len); + + EVP_CIPHER_CTX_free(ctx); + return plaintext; +} + +QByteArray EncryptionUtils::encryptAES_CBC_256(const QByteArray &data, const QByteArray &key, const QByteArray &iv) +{ + EVP_CIPHER_CTX *ctx; + int len; + int ciphertext_len; + + int max_out_len = data.size() + EVP_CIPHER_block_size(EVP_aes_256_cbc()); + QByteArray cipherText(max_out_len, 0); + + if (!(ctx = EVP_CIPHER_CTX_new())) + return {}; + + if (1 + != EVP_EncryptInit_ex(ctx, + EVP_aes_256_cbc(), + NULL, + reinterpret_cast(key.data()), + reinterpret_cast(iv.data()))) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + + if (1 + != EVP_EncryptUpdate(ctx, + reinterpret_cast(cipherText.data()), + &len, + reinterpret_cast(data.data()), + data.size())) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + ciphertext_len = len; + + if (1 != EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + len, &len)) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + ciphertext_len += len; + cipherText.resize(ciphertext_len); + EVP_CIPHER_CTX_free(ctx); + + return cipherText; +} + +QByteArray EncryptionUtils::encryptAES_CBC_128(const QByteArray &data, const QByteArray &key, const QByteArray &iv) +{ + if (key.size() != 16) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Session key must be 16 bytes for AES-128"; + return {}; + } + + EVP_CIPHER_CTX *ctx; + int len; + int ciphertext_len; + + int max_out_len = data.size() + EVP_CIPHER_block_size(EVP_aes_128_cbc()); + QByteArray cipherText(max_out_len, 0); + + if (!(ctx = EVP_CIPHER_CTX_new())) + return {}; + + if (1 + != EVP_EncryptInit_ex(ctx, + EVP_aes_128_cbc(), + NULL, + reinterpret_cast(key.data()), + reinterpret_cast(iv.data()))) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + + if (1 + != EVP_EncryptUpdate(ctx, + reinterpret_cast(cipherText.data()), + &len, + reinterpret_cast(data.data()), + data.size())) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + ciphertext_len = len; + + if (1 != EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + len, &len)) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + ciphertext_len += len; + cipherText.resize(ciphertext_len); + EVP_CIPHER_CTX_free(ctx); + + return cipherText; +} + +QByteArray EncryptionUtils::decryptAES_CBC_128(const QByteArray &cipherText, const QByteArray &key, const QByteArray &iv) +{ + EVP_CIPHER_CTX *ctx; + int len; + int plainTextLen; + + QByteArray plainText(cipherText.size(), 0); + + if (!(ctx = EVP_CIPHER_CTX_new())) + return {}; + + if (1 + != EVP_DecryptInit_ex(ctx, + EVP_aes_128_cbc(), + NULL, + reinterpret_cast(key.data()), + reinterpret_cast(iv.data()))) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + + if (1 + != EVP_DecryptUpdate(ctx, + reinterpret_cast(plainText.data()), + &len, + reinterpret_cast(cipherText.data()), + cipherText.size())) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + plainTextLen = len; + + if (1 != EVP_DecryptFinal_ex(ctx, reinterpret_cast(plainText.data()) + len, &len)) { + EVP_CIPHER_CTX_free(ctx); + return {}; + } + plainTextLen += len; + plainText.resize(plainTextLen); + EVP_CIPHER_CTX_free(ctx); + + return plainText; +} + QByteArray EncryptionUtils::generateRandomIV(int size) { QByteArray iv(size, 0); diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index 75d0de707b..101ac6c0f3 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -30,7 +30,10 @@ struct RSAKeyPair { [[nondiscard]] LIBRUQOLACORE_TESTS_EXPORT RSAKeyPair generateRSAKey(); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString encodePrivateKey(const QString &privateKey, const QString &password, const QString &userId); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray getMasterKey(const QString &password, const QString &userId); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC(const QByteArray &data, const QByteArray &key, const QByteArray &iv); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC_256(const QByteArray &data, const QByteArray &key, const QByteArray &iv); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptAES_CBC_256(const QByteArray &data, const QByteArray &key, const QByteArray &iv); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC_128(const QByteArray &data, const QByteArray &key, const QByteArray &iv); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptAES_CBC_128(const QByteArray &data, const QByteArray &key, const QByteArray &iv); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray deriveKey(const QByteArray &salt, const QByteArray &baseKey, int iterations = 1000, int keyLength = 32); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray generateRandomIV(int size); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray generateSessionKey(); From 5311d57c83385119a708c346edf065afd9062bf7 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 16 Jun 2025 12:46:05 +0300 Subject: [PATCH 30/67] Encrypt decrypt messages using AES_CBC_128 --- src/core/encryption/encryptionutils.cpp | 54 ++++++++++++++++++++++--- src/core/encryption/encryptionutils.h | 2 + 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index c325f14f86..7053a39090 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -258,6 +258,55 @@ QByteArray EncryptionUtils::decryptSessionKey(const QByteArray &encryptedSession return decryptedSessionKey; } +QByteArray EncryptionUtils::encryptMessage(const QByteArray &plainText, const QByteArray &sessionKey) +{ + if (plainText.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Plaintext is empty"; + return {}; + } + if (sessionKey.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Session key is empty"; + return {}; + } + + QByteArray iv = generateRandomIV(16); + QByteArray cipherText = encryptAES_CBC_128(plainText, sessionKey, iv); + + if (cipherText.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Message encryption failed!"; + return {}; + } + + QByteArray result; + result.append(iv); + result.append(cipherText); + return result; +} + +QByteArray EncryptionUtils::decryptMessage(const QByteArray &encrypted, const QByteArray &sessionKey) +{ + if (encrypted.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Encrypted message is empty"; + return {}; + } + if (sessionKey.size()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Session key is empty"; + return {}; + } + + QByteArray iv = encrypted.left(16); + QByteArray cipherText = encrypted.mid(16); + + QByteArray plainText = decryptAES_CBC_128(cipherText, sessionKey, iv); + + if (plainText.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Message decryption failed!"; + return {}; + } + + return plainText; +} + QByteArray EncryptionUtils::decryptAES_CBC_256(const QByteArray &data, const QByteArray &key, const QByteArray &iv) { EVP_CIPHER_CTX *ctx; @@ -348,11 +397,6 @@ QByteArray EncryptionUtils::encryptAES_CBC_256(const QByteArray &data, const QBy QByteArray EncryptionUtils::encryptAES_CBC_128(const QByteArray &data, const QByteArray &key, const QByteArray &iv) { - if (key.size() != 16) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "Session key must be 16 bytes for AES-128"; - return {}; - } - EVP_CIPHER_CTX *ctx; int len; int ciphertext_len; diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index 101ac6c0f3..46433254ed 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -34,6 +34,8 @@ struct RSAKeyPair { [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptAES_CBC_256(const QByteArray &data, const QByteArray &key, const QByteArray &iv); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC_128(const QByteArray &data, const QByteArray &key, const QByteArray &iv); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptAES_CBC_128(const QByteArray &data, const QByteArray &key, const QByteArray &iv); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptMessage(const QByteArray &plainText, const QByteArray &sessionKey); +[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptMessage(const QByteArray &plainText, const QByteArray &sessionKey); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray deriveKey(const QByteArray &salt, const QByteArray &baseKey, int iterations = 1000, int keyLength = 32); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray generateRandomIV(int size); [[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray generateSessionKey(); From bb31d92d5f3d699a185e47f603a530e53910ab57 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 16 Jun 2025 12:55:44 +0300 Subject: [PATCH 31/67] Change label names from encode decode to encryp decrypt message --- tests/encryptiontestgui/encryptiontestgui.cpp | 6 +++--- tests/encryptiontestgui/encryptiontestgui.h | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index f982bfa9e7..4c144205dd 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -106,12 +106,12 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mTextEditResult->setPlainText((QStringLiteral("Session key decryption succeeded!\n") + QString::fromUtf8(mDecryptedSessionKey.toBase64()))); } }); - auto pushButtonEncode = new QPushButton(QStringLiteral("Encode"), this); + auto pushButtonEncode = new QPushButton(QStringLiteral("Encrypt message"), this); mainLayout->addWidget(pushButtonEncode); connect(pushButtonEncode, &QPushButton::clicked, this, []() { - // test + }); - auto pushButtonDecode = new QPushButton(QStringLiteral("Decode"), this); + auto pushButtonDecode = new QPushButton(QStringLiteral("Decrypt message"), this); mainLayout->addWidget(pushButtonDecode); connect(pushButtonDecode, &QPushButton::clicked, this, []() { // test diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index 7a77333411..5341d81d4d 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -28,4 +28,6 @@ class EncryptionTestGui : public QWidget QByteArray mSessionKey; QByteArray mEncryptedSessionKey; QByteArray mDecryptedSessionKey; + QByteArray mEncryptedMessage; + QByteArray mDecryptedMessage; }; From 909d126a8719bb41444e6032f6115337e9e918cc Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 16 Jun 2025 13:45:18 +0300 Subject: [PATCH 32/67] Update error messages and show encryption decryption in qdebug --- src/core/encryption/encryptionutils.cpp | 14 +++++++------- tests/encryptiontestgui/encryptiontestgui.cpp | 14 +++++++++----- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 7053a39090..1db3c5f0fe 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -261,11 +261,11 @@ QByteArray EncryptionUtils::decryptSessionKey(const QByteArray &encryptedSession QByteArray EncryptionUtils::encryptMessage(const QByteArray &plainText, const QByteArray &sessionKey) { if (plainText.isEmpty()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "Plaintext is empty"; + qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::encryptMessage, plaintext is empty!"; return {}; } if (sessionKey.isEmpty()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "Session key is empty"; + qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::encryptMessage, session key is empty!"; return {}; } @@ -273,7 +273,7 @@ QByteArray EncryptionUtils::encryptMessage(const QByteArray &plainText, const QB QByteArray cipherText = encryptAES_CBC_128(plainText, sessionKey, iv); if (cipherText.isEmpty()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "Message encryption failed!"; + qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::encryptMessage, message encryption failed!"; return {}; } @@ -286,11 +286,11 @@ QByteArray EncryptionUtils::encryptMessage(const QByteArray &plainText, const QB QByteArray EncryptionUtils::decryptMessage(const QByteArray &encrypted, const QByteArray &sessionKey) { if (encrypted.isEmpty()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "Encrypted message is empty"; + qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::decryptMessage, encrypted message is empty!"; return {}; } - if (sessionKey.size()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "Session key is empty"; + if (sessionKey.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::decryptMessage, mession key is empty!"; return {}; } @@ -300,7 +300,7 @@ QByteArray EncryptionUtils::decryptMessage(const QByteArray &encrypted, const QB QByteArray plainText = decryptAES_CBC_128(cipherText, sessionKey, iv); if (plainText.isEmpty()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "Message decryption failed!"; + qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::decryptMessage, message decryption failed!"; return {}; } diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 4c144205dd..a29cb7b768 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -108,19 +108,23 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) }); auto pushButtonEncode = new QPushButton(QStringLiteral("Encrypt message"), this); mainLayout->addWidget(pushButtonEncode); - connect(pushButtonEncode, &QPushButton::clicked, this, []() { - + connect(pushButtonEncode, &QPushButton::clicked, this, [this]() { + mEncryptedMessage = EncryptionUtils::encryptMessage(QStringLiteral("This is GSoC 2025!").toUtf8(), mSessionKey); + qDebug() << "Encrypted message:" << mEncryptedMessage.toBase64(); }); auto pushButtonDecode = new QPushButton(QStringLiteral("Decrypt message"), this); mainLayout->addWidget(pushButtonDecode); - connect(pushButtonDecode, &QPushButton::clicked, this, []() { - // test + connect(pushButtonDecode, &QPushButton::clicked, this, [this]() { + qDebug() << "Session key:" << mSessionKey; + mDecryptedMessage = EncryptionUtils::decryptMessage(mEncryptedMessage, mSessionKey); + qDebug() << "Decrypted message:" << mDecryptedMessage; + qDebug() << "Init message: " << "This is GSoC 2025!"; }); auto pushButtonReset = new QPushButton(QStringLiteral("Reset"), this); mainLayout->addWidget(pushButtonReset); connect(pushButtonReset, &QPushButton::clicked, this, []() { - EncryptionUtils::generateRSAKey(); + // TODO }); mTextEditResult->setReadOnly(true); From 3edac4db1a93cd5f9e187093b82f85baa0e4c722 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 16 Jun 2025 14:17:11 +0300 Subject: [PATCH 33/67] Display in the UI --- tests/encryptiontestgui/encryptiontestgui.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index a29cb7b768..b33c30de7e 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -111,6 +111,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(pushButtonEncode, &QPushButton::clicked, this, [this]() { mEncryptedMessage = EncryptionUtils::encryptMessage(QStringLiteral("This is GSoC 2025!").toUtf8(), mSessionKey); qDebug() << "Encrypted message:" << mEncryptedMessage.toBase64(); + mTextEditResult->setPlainText((QStringLiteral("Message encryption succeeded!\n") + QString::fromUtf8(mEncryptedMessage.toBase64()))); }); auto pushButtonDecode = new QPushButton(QStringLiteral("Decrypt message"), this); mainLayout->addWidget(pushButtonDecode); @@ -119,6 +120,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mDecryptedMessage = EncryptionUtils::decryptMessage(mEncryptedMessage, mSessionKey); qDebug() << "Decrypted message:" << mDecryptedMessage; qDebug() << "Init message: " << "This is GSoC 2025!"; + mTextEditResult->setPlainText((QStringLiteral("Message decryption succeeded!\n") + QString::fromUtf8(mDecryptedMessage))); }); auto pushButtonReset = new QPushButton(QStringLiteral("Reset"), this); From 72bb7bdf3dedcd9a8d545667b5f2cfbca0e5a3a3 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 16 Jun 2025 14:53:00 +0300 Subject: [PATCH 34/67] Read from mTextEdit and clear() after user clicks encrypt message --- tests/encryptiontestgui/encryptiontestgui.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index b33c30de7e..520bffa0a4 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -109,9 +109,15 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonEncode = new QPushButton(QStringLiteral("Encrypt message"), this); mainLayout->addWidget(pushButtonEncode); connect(pushButtonEncode, &QPushButton::clicked, this, [this]() { - mEncryptedMessage = EncryptionUtils::encryptMessage(QStringLiteral("This is GSoC 2025!").toUtf8(), mSessionKey); - qDebug() << "Encrypted message:" << mEncryptedMessage.toBase64(); - mTextEditResult->setPlainText((QStringLiteral("Message encryption succeeded!\n") + QString::fromUtf8(mEncryptedMessage.toBase64()))); + const auto text = mTextEdit->toPlainText(); + if (text.isEmpty()) { + mTextEditResult->setPlainText((QStringLiteral("Text cannot be null, message encryption failed!\n") + text)); + } else { + mEncryptedMessage = EncryptionUtils::encryptMessage(text.toUtf8(), mSessionKey); + qDebug() << "Encrypted message:" << mEncryptedMessage.toBase64(); + mTextEditResult->setPlainText((QStringLiteral("Message encryption succeeded!\n") + QString::fromUtf8(mEncryptedMessage.toBase64()))); + mTextEdit->clear(); + } }); auto pushButtonDecode = new QPushButton(QStringLiteral("Decrypt message"), this); mainLayout->addWidget(pushButtonDecode); From 5e622a9a62e483b03a60b91fe378bf89acfc06cc Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Tue, 17 Jun 2025 11:22:06 +0300 Subject: [PATCH 35/67] Doxygen for some functions --- src/core/encryption/encryptionutils.cpp | 23 +++++++++++++++++++ tests/encryptiontestgui/encryptiontestgui.cpp | 1 - 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 1db3c5f0fe..831b9c3d9a 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -130,6 +130,17 @@ QString EncryptionUtils::encodePrivateKey(const QString &privateKey, const QStri return {}; } +/** + * @brief Derives the master key from the user's password and user ID. + * + * This function uses a password-based key derivation function (PBKDF2) to generate + * a 256-bit (32-byte) AES master key from the provided password and user ID. + * The master key is used to encrypt and decrypt the user's private RSA key. + * + * @param password The user's E2EE password. + * @param userId The user's unique identifier (used as salt). + * @return A 32-byte (256-bit) master key as a QByteArray, or an empty QByteArray on failure. + */ QByteArray EncryptionUtils::getMasterKey(const QString &password, const QString &userId) { if (password.isEmpty()) { @@ -258,6 +269,12 @@ QByteArray EncryptionUtils::decryptSessionKey(const QByteArray &encryptedSession return decryptedSessionKey; } +/** + * @brief Encrypts a message using AES-128-CBC. + * @param plainText The message to encrypt. + * @param sessionKey The 16-byte session key. + * @return The IV prepended to the ciphertext. + */ QByteArray EncryptionUtils::encryptMessage(const QByteArray &plainText, const QByteArray &sessionKey) { if (plainText.isEmpty()) { @@ -283,6 +300,12 @@ QByteArray EncryptionUtils::encryptMessage(const QByteArray &plainText, const QB return result; } +/** + * @brief Decrypts a message using AES-128-CBC. + * @param encrypted The message to decrypt. + * @param sessionKey The 16-byte session key. + * @return The decrypted message. + */ QByteArray EncryptionUtils::decryptMessage(const QByteArray &encrypted, const QByteArray &sessionKey) { if (encrypted.isEmpty()) { diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 520bffa0a4..9d221dcf51 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -125,7 +125,6 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) qDebug() << "Session key:" << mSessionKey; mDecryptedMessage = EncryptionUtils::decryptMessage(mEncryptedMessage, mSessionKey); qDebug() << "Decrypted message:" << mDecryptedMessage; - qDebug() << "Init message: " << "This is GSoC 2025!"; mTextEditResult->setPlainText((QStringLiteral("Message decryption succeeded!\n") + QString::fromUtf8(mDecryptedMessage))); }); From e4a0d5f94a4534c7354c626d6545d82d185b3050 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 23 Jun 2025 19:13:08 +0300 Subject: [PATCH 36/67] Autotests and if statements to improve the UI --- src/core/autotests/CMakeLists.txt | 1 + .../messageencryptiondecryptiontest.cpp | 43 +++++++++++++++++++ .../messageencryptiondecryptiontest.h | 20 +++++++++ tests/encryptiontestgui/encryptiontestgui.cpp | 6 ++- 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/core/autotests/messageencryptiondecryptiontest.cpp create mode 100644 src/core/autotests/messageencryptiondecryptiontest.h diff --git a/src/core/autotests/CMakeLists.txt b/src/core/autotests/CMakeLists.txt index 70fce92967..e04cb03db2 100644 --- a/src/core/autotests/CMakeLists.txt +++ b/src/core/autotests/CMakeLists.txt @@ -168,6 +168,7 @@ if(USE_E2E_SUPPORT) add_ruqola_test(encryptionutilstest.cpp) add_ruqola_test(masterkeytest.cpp) add_ruqola_test(sessionkeytest.cpp) + add_ruqola_test(messageencryptiondecryptiontest.cpp) endif() add_ruqola_test(channelstest.cpp) diff --git a/src/core/autotests/messageencryptiondecryptiontest.cpp b/src/core/autotests/messageencryptiondecryptiontest.cpp new file mode 100644 index 0000000000..c05924d8e0 --- /dev/null +++ b/src/core/autotests/messageencryptiondecryptiontest.cpp @@ -0,0 +1,43 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "messageencryptiondecryptiontest.h" +#include "encryption/encryptionutils.h" +#include + +QTEST_GUILESS_MAIN(MessageEncryptionDecryptionTest) +MessageEncryptionDecryptionTest::MessageEncryptionDecryptionTest(QObject *parent) + : QObject(parent) +{ +} + +/** + * @brief Tests symmetric encryption and decryption of a message using a session key. + * + * Verifies that for a message `m` and session key `k`, decryption function `D` + * and encryption function `E` the property holds: + * + * `D(E(m, k), k) = m` + */ +void MessageEncryptionDecryptionTest::messageEncryptionDecryptionTest() +{ + const QByteArray sessionKey1 = EncryptionUtils::generateSessionKey(); + const QByteArray sessionKey2 = EncryptionUtils::generateSessionKey(); + auto message = QStringLiteral("This is GSoC 2025, Andro Ranogajec got to the end of 'phase1' :)").toUtf8(); + QByteArray encryptedMessage = EncryptionUtils::encryptMessage(message, sessionKey1); + QByteArray decryptedMessage = EncryptionUtils::decryptMessage(encryptedMessage, sessionKey1); + + QVERIFY(message == decryptedMessage); + + for (int i = 1; i <= 10; ++i) { + message = EncryptionUtils::generateRandomText(i).toUtf8(); + encryptedMessage = EncryptionUtils::encryptMessage(message, sessionKey1); + decryptedMessage = EncryptionUtils::decryptMessage(encryptedMessage, sessionKey2); + QVERIFY(message != decryptedMessage); + } +} + +#include "moc_messageencryptiondecryptiontest.cpp" \ No newline at end of file diff --git a/src/core/autotests/messageencryptiondecryptiontest.h b/src/core/autotests/messageencryptiondecryptiontest.h new file mode 100644 index 0000000000..3dd1e45f24 --- /dev/null +++ b/src/core/autotests/messageencryptiondecryptiontest.h @@ -0,0 +1,20 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#pragma once + +#include + +class MessageEncryptionDecryptionTest : public QObject +{ + Q_OBJECT +public: + explicit MessageEncryptionDecryptionTest(QObject *parent = nullptr); + ~MessageEncryptionDecryptionTest() override = default; + +private Q_SLOTS: + void messageEncryptionDecryptionTest(); +}; \ No newline at end of file diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 9d221dcf51..7b59920c5d 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -111,7 +111,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(pushButtonEncode, &QPushButton::clicked, this, [this]() { const auto text = mTextEdit->toPlainText(); if (text.isEmpty()) { - mTextEditResult->setPlainText((QStringLiteral("Text cannot be null, message encryption failed!\n") + text)); + mTextEditResult->setPlainText((QStringLiteral("Text cannot be null, message encryption failed!\n"))); } else { mEncryptedMessage = EncryptionUtils::encryptMessage(text.toUtf8(), mSessionKey); qDebug() << "Encrypted message:" << mEncryptedMessage.toBase64(); @@ -123,6 +123,10 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mainLayout->addWidget(pushButtonDecode); connect(pushButtonDecode, &QPushButton::clicked, this, [this]() { qDebug() << "Session key:" << mSessionKey; + if (QString::fromUtf8(mEncryptedMessage).isEmpty()) { + mTextEditResult->setPlainText((QStringLiteral("Encrypted message is null, message decryption failed!\n"))); + return; + } mDecryptedMessage = EncryptionUtils::decryptMessage(mEncryptedMessage, mSessionKey); qDebug() << "Decrypted message:" << mDecryptedMessage; mTextEditResult->setPlainText((QStringLiteral("Message decryption succeeded!\n") + QString::fromUtf8(mDecryptedMessage))); From 001daae15bbf3f45db7d307f8e9a5022617a6055 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 23 Jun 2025 19:48:49 +0300 Subject: [PATCH 37/67] Reset functionality and cleanup --- tests/encryptiontestgui/encryptiontestgui.cpp | 38 +++++++++++++------ tests/encryptiontestgui/encryptiontestgui.h | 2 +- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 7b59920c5d..9e19974400 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -40,7 +40,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) passwordEdit->setEchoMode(QLineEdit::Password); auto *layout = new QGridLayout(dialog); - layout->addWidget(new QLabel(QStringLiteral("UserId: "), dialog), 0, 0); + layout->addWidget(new QLabel(QStringLiteral("Username: "), dialog), 0, 0); layout->addWidget(userIdEdit, 0, 1); layout->addWidget(new QLabel(QStringLiteral("Password: "), dialog), 1, 0); layout->addWidget(passwordEdit, 1, 1); @@ -52,9 +52,9 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); if (dialog->exec()) { - mUserId = userIdEdit->text(); + mUsername = userIdEdit->text(); mPassword = passwordEdit->text(); - mMasterKey = EncryptionUtils::getMasterKey(mPassword, mUserId); + mMasterKey = EncryptionUtils::getMasterKey(mPassword, mUsername); qDebug() << "Derived Master Key:" << mMasterKey.toBase64(); mTextEditResult->setPlainText((QStringLiteral("Master Key derivation succeeded!\n") + QString::fromUtf8(mMasterKey.toBase64()))); } @@ -106,9 +106,9 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mTextEditResult->setPlainText((QStringLiteral("Session key decryption succeeded!\n") + QString::fromUtf8(mDecryptedSessionKey.toBase64()))); } }); - auto pushButtonEncode = new QPushButton(QStringLiteral("Encrypt message"), this); - mainLayout->addWidget(pushButtonEncode); - connect(pushButtonEncode, &QPushButton::clicked, this, [this]() { + auto pushButtonEncrypt = new QPushButton(QStringLiteral("Encrypt message"), this); + mainLayout->addWidget(pushButtonEncrypt); + connect(pushButtonEncrypt, &QPushButton::clicked, this, [this]() { const auto text = mTextEdit->toPlainText(); if (text.isEmpty()) { mTextEditResult->setPlainText((QStringLiteral("Text cannot be null, message encryption failed!\n"))); @@ -119,9 +119,9 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mTextEdit->clear(); } }); - auto pushButtonDecode = new QPushButton(QStringLiteral("Decrypt message"), this); - mainLayout->addWidget(pushButtonDecode); - connect(pushButtonDecode, &QPushButton::clicked, this, [this]() { + auto pushButtonDecrypt = new QPushButton(QStringLiteral("Decrypt message"), this); + mainLayout->addWidget(pushButtonDecrypt); + connect(pushButtonDecrypt, &QPushButton::clicked, this, [this]() { qDebug() << "Session key:" << mSessionKey; if (QString::fromUtf8(mEncryptedMessage).isEmpty()) { mTextEditResult->setPlainText((QStringLiteral("Encrypted message is null, message decryption failed!\n"))); @@ -134,8 +134,24 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonReset = new QPushButton(QStringLiteral("Reset"), this); mainLayout->addWidget(pushButtonReset); - connect(pushButtonReset, &QPushButton::clicked, this, []() { - // TODO + connect(pushButtonReset, &QPushButton::clicked, this, [this]() { + mTextEdit->clear(); + mTextEditResult->clear(); + mMasterKey.clear(); + mPassword = QStringLiteral(""); + mUsername = QStringLiteral(""); + mRsaKeyPair.privateKey.clear(); + mRsaKeyPair.publicKey.clear(); + mSessionKey.clear(); + mEncryptedSessionKey.clear(); + mDecryptedSessionKey.clear(); + mEncryptedMessage.clear(); + mDecryptedMessage.clear(); + qDebug() << "Master Key: " << mMasterKey << "\nusername: " << mUsername << "\npassword: " << mPassword << "\nprivatekey: " << mRsaKeyPair.privateKey + << "\npublickey: " << mRsaKeyPair.publicKey << "\nencrypted session key: " << mEncryptedSessionKey + << "\ndecrypted session key: " << mDecryptedSessionKey << "\nencrypted message: " << mEncryptedMessage + << "\ndecrypted message: " << mDecryptedMessage; + mTextEditResult->setPlainText((QStringLiteral("Reset succeded!\n"))); }); mTextEditResult->setReadOnly(true); diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index 5341d81d4d..20da0e491b 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -23,7 +23,7 @@ class EncryptionTestGui : public QWidget QTextEdit *const mTextEditResult; QByteArray mMasterKey; QString mPassword; - QString mUserId; + QString mUsername; EncryptionUtils::RSAKeyPair mRsaKeyPair; QByteArray mSessionKey; QByteArray mEncryptedSessionKey; From 1a1fd1d951639d96b3d4817604e82605a57ee17b Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 30 Jun 2025 14:43:49 +0300 Subject: [PATCH 38/67] Some renaming after merge and small fixes --- src/core/autotests/masterkeytest.cpp | 5 ++++ .../messageencryptiondecryptiontest.cpp | 14 +++++----- .../messageencryptiondecryptiontest.h | 2 +- src/core/autotests/sessionkeytest.cpp | 4 +-- src/core/encryption/encryptionutils.cpp | 26 +++++++++---------- tests/encryptiontestgui/encryptiontestgui.cpp | 14 +++++----- tests/encryptiontestgui/encryptiontestgui.h | 2 +- 7 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/core/autotests/masterkeytest.cpp b/src/core/autotests/masterkeytest.cpp index 922356972d..d6c43fc7a6 100644 --- a/src/core/autotests/masterkeytest.cpp +++ b/src/core/autotests/masterkeytest.cpp @@ -17,7 +17,12 @@ MasterKeyTest::MasterKeyTest(QObject *parent) /** * @brief This methods to going to test the determinism of the master key. * + * n, n1 = salt + * + * m1, m2 = password + * * if + * * n == n1 and m == m1 * then * getMasterKey(n, m) == getMasterKey(n1, m1) diff --git a/src/core/autotests/messageencryptiondecryptiontest.cpp b/src/core/autotests/messageencryptiondecryptiontest.cpp index c05924d8e0..dce08de82e 100644 --- a/src/core/autotests/messageencryptiondecryptiontest.cpp +++ b/src/core/autotests/messageencryptiondecryptiontest.cpp @@ -24,19 +24,17 @@ MessageEncryptionDecryptionTest::MessageEncryptionDecryptionTest(QObject *parent */ void MessageEncryptionDecryptionTest::messageEncryptionDecryptionTest() { + auto message = QStringLiteral("This is GSoC 2025, Andro Ranogajec got to the end of 'Phase 1' :)"); const QByteArray sessionKey1 = EncryptionUtils::generateSessionKey(); const QByteArray sessionKey2 = EncryptionUtils::generateSessionKey(); - auto message = QStringLiteral("This is GSoC 2025, Andro Ranogajec got to the end of 'phase1' :)").toUtf8(); - QByteArray encryptedMessage = EncryptionUtils::encryptMessage(message, sessionKey1); - QByteArray decryptedMessage = EncryptionUtils::decryptMessage(encryptedMessage, sessionKey1); - + QString decryptedMessage = QString::fromUtf8(EncryptionUtils::decryptMessage(EncryptionUtils::encryptMessage(message.toUtf8(), sessionKey1), sessionKey1)); QVERIFY(message == decryptedMessage); for (int i = 1; i <= 10; ++i) { - message = EncryptionUtils::generateRandomText(i).toUtf8(); - encryptedMessage = EncryptionUtils::encryptMessage(message, sessionKey1); - decryptedMessage = EncryptionUtils::decryptMessage(encryptedMessage, sessionKey2); - QVERIFY(message != decryptedMessage); + QByteArray message = EncryptionUtils::generateRandomText(i).toUtf8(); + QByteArray encrypted = EncryptionUtils::encryptMessage(message, sessionKey1); + QByteArray decryptedWithWrongKey = EncryptionUtils::decryptMessage(encrypted, sessionKey2); + QVERIFY(decryptedWithWrongKey.isEmpty() && decryptedWithWrongKey != message); } } diff --git a/src/core/autotests/messageencryptiondecryptiontest.h b/src/core/autotests/messageencryptiondecryptiontest.h index 3dd1e45f24..442bd6443b 100644 --- a/src/core/autotests/messageencryptiondecryptiontest.h +++ b/src/core/autotests/messageencryptiondecryptiontest.h @@ -17,4 +17,4 @@ class MessageEncryptionDecryptionTest : public QObject private Q_SLOTS: void messageEncryptionDecryptionTest(); -}; \ No newline at end of file +}; diff --git a/src/core/autotests/sessionkeytest.cpp b/src/core/autotests/sessionkeytest.cpp index 5d3c0354b0..77c6615448 100644 --- a/src/core/autotests/sessionkeytest.cpp +++ b/src/core/autotests/sessionkeytest.cpp @@ -32,8 +32,8 @@ void SessionKeyTest::sessionKeyEncryptionDecryptionTest() QByteArray encryptedSessionKey; QByteArray decryptedSessionKey; auto rsaKeyPair = EncryptionUtils::generateRSAKey(); - auto privateKey = rsaKeyPair.privateKey.toUtf8(); - auto publicKey = rsaKeyPair.publicKey.toUtf8(); + auto privateKey = rsaKeyPair.privateKey; + auto publicKey = rsaKeyPair.publicKey; for (int i = 0; i <= 10; i++) { sessionKey = EncryptionUtils::generateSessionKey(); diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 2340a883ca..df092e9559 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -192,28 +192,22 @@ QByteArray EncryptionUtils::decryptPrivateKey(const QByteArray &encryptedPrivate * The master key is used to encrypt and decrypt the user's private RSA key. * * @param password The user's E2EE password. - * @param userId The user's unique identifier (used as salt). + * @param salt user's unique identifier (used as salt). * @return A 32-byte (256-bit) master key as a QByteArray, or an empty QByteArray on failure. */ -QByteArray EncryptionUtils::getMasterKey(const QString &password, const QString &userId) +QByteArray EncryptionUtils::getMasterKey(const QString &password, const QString &salt) { if (password.isEmpty()) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Password can't be null. It's a bug"; return {}; } - if (userId.isEmpty()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "UserId can't be null. It's a bug"; + if (salt.isEmpty()) { + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Salt(username) can't be null. It's a bug"; return {}; } - const QByteArray baseKey = importRawKey(password.toUtf8(), userId.toUtf8(), 1000); - if (baseKey.isEmpty()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "Failed to derive base key from password!"; - return {}; - } - - const QByteArray masterKey = deriveKey(userId.toUtf8(), baseKey, 1000, 32); + const QByteArray masterKey = deriveKey(salt.toUtf8(), password.toUtf8(), 1000, 32); if (masterKey.isEmpty()) { qCWarning(RUQOLA_ENCRYPTION_LOG) << "Master key derivation failed!"; return {}; @@ -344,7 +338,7 @@ QByteArray EncryptionUtils::encryptMessage(const QByteArray &plainText, const QB QByteArray cipherText = encryptAES_CBC_128(plainText, sessionKey, iv); if (cipherText.isEmpty()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::encryptMessage, message encryption failed!"; + qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::encryptMessage, message encryption failed, cipher text is empty!"; return {}; } @@ -367,17 +361,21 @@ QByteArray EncryptionUtils::decryptMessage(const QByteArray &encrypted, const QB return {}; } if (sessionKey.isEmpty()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::decryptMessage, mession key is empty!"; + qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::decryptMessage, session key is empty!"; return {}; } QByteArray iv = encrypted.left(16); QByteArray cipherText = encrypted.mid(16); + qDebug() << cipherText << "QByteArray cipherText = encrypted.mid(16)"; + 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!"; + qCWarning(RUQOLA_ENCRYPTION_LOG) << "QByteArray EncryptionUtils::decryptMessage, message decryption failed, plain text is empty"; return {}; } diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontestgui/encryptiontestgui.cpp index 2b7b11b914..699c507f8e 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontestgui/encryptiontestgui.cpp @@ -35,13 +35,13 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto *dialog = new QDialog(this); dialog->setWindowTitle(QStringLiteral("Credentials")); - auto *userIdEdit = new QLineEdit(dialog); + auto *saltEdit = new QLineEdit(dialog); auto *passwordEdit = new QLineEdit(dialog); passwordEdit->setEchoMode(QLineEdit::Password); auto *layout = new QGridLayout(dialog); - layout->addWidget(new QLabel(QStringLiteral("Username: "), dialog), 0, 0); - layout->addWidget(userIdEdit, 0, 1); + layout->addWidget(new QLabel(QStringLiteral("Salt: "), dialog), 0, 0); + layout->addWidget(saltEdit, 0, 1); layout->addWidget(new QLabel(QStringLiteral("Password: "), dialog), 1, 0); layout->addWidget(passwordEdit, 1, 1); @@ -52,9 +52,9 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); if (dialog->exec()) { - mUsername = userIdEdit->text(); + mSalt = saltEdit->text(); mPassword = passwordEdit->text(); - mMasterKey = EncryptionUtils::getMasterKey(mPassword, mUsername); + mMasterKey = EncryptionUtils::getMasterKey(mPassword, mSalt); if (mMasterKey.isEmpty()) mTextEditResult->setPlainText(QStringLiteral("Master key is empty, generation failed!")); @@ -174,7 +174,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mTextEditResult->clear(); mMasterKey.clear(); mPassword = QStringLiteral(""); - mUsername = QStringLiteral(""); + mSalt = QStringLiteral(""); mRsaKeyPair.privateKey.clear(); mRsaKeyPair.publicKey.clear(); mSessionKey.clear(); @@ -182,7 +182,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mDecryptedSessionKey.clear(); mEncryptedMessage.clear(); mDecryptedMessage.clear(); - qDebug() << "Master Key: " << mMasterKey << "\nusername: " << mUsername << "\npassword: " << mPassword << "\nprivatekey: " << mRsaKeyPair.privateKey + qDebug() << "Master Key: " << mMasterKey << "\nsalt: " << mSalt << "\npassword: " << mPassword << "\nprivatekey: " << mRsaKeyPair.privateKey << "\npublickey: " << mRsaKeyPair.publicKey << "\nencrypted session key: " << mEncryptedSessionKey << "\ndecrypted session key: " << mDecryptedSessionKey << "\nencrypted message: " << mEncryptedMessage << "\ndecrypted message: " << mDecryptedMessage; diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontestgui/encryptiontestgui.h index 51c769b63a..1fc669421d 100644 --- a/tests/encryptiontestgui/encryptiontestgui.h +++ b/tests/encryptiontestgui/encryptiontestgui.h @@ -23,7 +23,7 @@ class EncryptionTestGui : public QWidget QTextEdit *const mTextEditResult; QByteArray mMasterKey; QString mPassword; - QString mUsername; + QString mSalt; QByteArray mEncryptedPrivateKey; QByteArray mDecryptedPrivateKey; EncryptionUtils::RSAKeyPair mRsaKeyPair; From 0aea4d2e761a2eb2a6302ce95482d592286ecdb4 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Thu, 17 Jul 2025 15:48:44 +0300 Subject: [PATCH 39/67] Cli entry point, login programmatically, upload/download key (without testing, might not workgit add) --- .gitignore | 3 +- src/apps/CMakeLists.txt | 27 ++++ src/apps/e2ekeytool.cpp | 151 ++++++++++++++++++ src/core/encryption/encryptionutils.h | 57 +++---- src/core/encryption/ruqolaencryptiondebug.cpp | 3 + 5 files changed, 212 insertions(+), 29 deletions(-) create mode 100644 src/apps/e2ekeytool.cpp create mode 100644 src/core/encryption/ruqolaencryptiondebug.cpp diff --git a/.gitignore b/.gitignore index 09a07f80c9..d627f59066 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,5 @@ compile_commands.json /.vscode/ .qtcreator/ .env -compose.yml +docker-compose.yml +credentials.txt diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index 394c493695..b0f9a74013 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -1,8 +1,22 @@ # SPDX-FileCopyrightText: 2020-2025 Laurent Montel # SPDX-License-Identifier: BSD-3-Clause + include(ECMAddAppIcon) +find_package(OpenSSL REQUIRED) + add_executable(ruqola main.cpp) +add_executable(e2ekeytool + ${CMAKE_SOURCE_DIR}/src/apps/e2ekeytool.cpp + ${CMAKE_SOURCE_DIR}/src/core/encryption/encryptionutils.cpp + ${CMAKE_SOURCE_DIR}/src/core/encryption/ruqolaencryptiondebug.cpp) + +target_include_directories(e2ekeytool PRIVATE + ${CMAKE_SOURCE_DIR}/src/core/encryption + ${CMAKE_BINARY_DIR}/src/core + ${CMAKE_SOURCE_DIR}/src/core/connection + ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt +) target_link_libraries(ruqola libruqolawidgets @@ -11,10 +25,22 @@ target_link_libraries(ruqola KF6::XmlGui KF6::IconThemes ) + +target_link_libraries(e2ekeytool + librocketchatrestapi-qt + libruqolawidgets + KF6::Crash + Qt::Core + Qt::Network + OpenSSL::Crypto +) + if(USE_DBUS) target_link_libraries(ruqola KF6::DBusAddons) + target_link_libraries(e2ekeytool KF6::DBusAddons) else() target_link_libraries(ruqola KDAB::kdsingleapplication) + target_link_libraries(e2ekeytool KDAB::kdsingleapplication) if (NOT WIN32 AND NOT APPLE) target_link_libraries(ruqola Qt::GuiPrivate) endif() @@ -25,3 +51,4 @@ ecm_add_app_icon(appIcons ICONS "${RUQOLA_ICONS}") target_sources(ruqola PRIVATE ${appIcons}) install(TARGETS ruqola ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) +install(TARGETS e2ekeytool ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/apps/e2ekeytool.cpp b/src/apps/e2ekeytool.cpp new file mode 100644 index 0000000000..b0bb4e95b4 --- /dev/null +++ b/src/apps/e2ekeytool.cpp @@ -0,0 +1,151 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +/* + +test my existance...of course within server, otherwise I still exist :) + +curl -X POST http://localhost:3000/api/v1/login \ + -H "Content-Type: application/json" \ + -d '{"user": "", "password": ""}' +*/ + +#include "authentication/loginjob.h" +#include "e2e/fetchmykeysjob.h" +#include "e2e/setuserpublicandprivatekeysjob.h" +#include "encryptionutils.h" +#include "restapimethod.h" +#include +#include +#include +#include +#include +#include + +using namespace EncryptionUtils; +using namespace RocketChatRestApi; + +const QString url = QStringLiteral("http://localhost:3000"); + +QHash loadCredentials(const QString &filePath) +{ + QHash creds; + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "Could not open credentials file:" << filePath; + return creds; + } + + while (!file.atEnd()) { + const QString line = QString::fromUtf8(file.readLine()).trimmed(); + + if (line.startsWith(QLatin1Char('#')) || !line.contains(QLatin1Char('='))) + continue; + + const auto parts = line.split(QLatin1Char('='), Qt::KeepEmptyParts); + if (parts.size() == 2) + creds[parts[0].trimmed()] = parts[1].trimmed(); + } + + return creds; +} + +void uploadKeys(const QString &authToken, const QString &userId, const QString &password, QNetworkAccessManager *networkManager) +{ + RSAKeyPair keyPair = generateRSAKey(); + QByteArray masterKey = getMasterKey(password, QStringLiteral("salt")); + QByteArray encryptedPrivateKey = encryptPrivateKey(keyPair.privateKey, masterKey); + + auto *uploadJob = new SetUserPublicAndPrivateKeysJob(); + + auto *restApiMethod = new RestApiMethod(); + restApiMethod->setServerUrl(url); + + uploadJob->setRestApiMethod(restApiMethod); + uploadJob->setNetworkAccessManager(networkManager); + + uploadJob->setAuthToken(authToken); + uploadJob->setUserId(userId); + + SetUserPublicAndPrivateKeysJob::SetUserPublicAndPrivateKeysInfo info; + info.rsaPublicKey = QString::fromUtf8(keyPair.publicKey); + info.rsaPrivateKey = QString::fromUtf8(encryptedPrivateKey.toBase64()); + info.force = true; + uploadJob->setSetUserPublicAndPrivateKeysInfo(info); + + QObject::connect(uploadJob, &SetUserPublicAndPrivateKeysJob::setUserPublicAndPrivateKeysDone, uploadJob, []() { + qDebug() << "Key upload successful!"; + QCoreApplication::quit(); + }); + + QObject::connect(uploadJob, &SetUserPublicAndPrivateKeysJob::failed, uploadJob, [](const QString &err) { + qCritical() << "Key upload failed: " << err; + QCoreApplication::quit(); + }); + + uploadJob->start(); +} + +void downloadKeys(const QString &password) +{ + auto *fetchJob = new FetchMyKeysJob(); + + QObject::connect(fetchJob, &FetchMyKeysJob::fetchMyKeysDone, fetchJob, [password](const QJsonObject &jsonObj) { + QString publicKey = jsonObj["public_key"_L1].toString(); + QString encryptedPrivateKeyB64 = jsonObj["private_key"_L1].toString(); + + qDebug() << "Downloaded Public Key:" << publicKey; + + QByteArray encryptedPrivateKey = QByteArray::fromBase64(encryptedPrivateKeyB64.toUtf8()); + QByteArray masterKey = getMasterKey(password, QStringLiteral("salt")); + QByteArray decryptedPrivateKey = decryptPrivateKey(encryptedPrivateKey, masterKey); + + qDebug() << "Decrypted Private Key:\n" << QString::fromUtf8(decryptedPrivateKey); + + QCoreApplication::quit(); + }); + + QObject::connect(fetchJob, &RestApiAbstractJob::failed, nullptr, [=](const QString &err) { + qCritical() << "Key fetch failed:" << err; + QCoreApplication::quit(); + }); + + fetchJob->start(); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QNetworkAccessManager *networkManager = new QNetworkAccessManager(&app); + + auto *loginJob = new LoginJob(&app); + auto *restApiMethod = new RestApiMethod(); + + auto creds = loadCredentials(QStringLiteral("/home/edc/rocketchat/ruqola/src/apps/credentials.txt")); + + restApiMethod->setServerUrl(url); + + loginJob->setRestApiMethod(restApiMethod); + loginJob->setNetworkAccessManager(networkManager); + + loginJob->setUserName(creds.value(QStringLiteral("USERNAME"))); + loginJob->setPassword(creds.value(QStringLiteral("PASSWORD"))); + + QObject::connect(loginJob, &LoginJob::loginDone, &app, [networkManager](const QString &authToken, const QString &userId) { + qDebug() << "Login successful! Auth token:" << authToken << "UserId:" << userId; + // uploadKeys(authToken, userId, QStringLiteral("mypassword123"), networkManager); + }); + + QObject::connect(loginJob, &RestApiAbstractJob::failed, &app, [](const QString &err) { + qCritical() << "Login failed:" << err; + QCoreApplication::quit(); + }); + + loginJob->start(); + + return app.exec(); +} \ No newline at end of file diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index 220b70a81c..17e4944ac9 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -5,7 +5,7 @@ SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include "libruqola_private_export.h" +#include "libruqolacore_export.h" #include extern "C" { #include @@ -13,9 +13,10 @@ extern "C" { #include #include } + namespace EncryptionUtils { -struct LIBRUQOLACORE_TESTS_EXPORT EncryptionInfo { +struct LIBRUQOLACORE_EXPORT EncryptionInfo { QByteArray vector; QByteArray encryptedData; [[nodiscard]] bool isValid() const; @@ -26,31 +27,31 @@ struct RSAKeyPair { QByteArray privateKey; }; -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray exportJWKKey(RSA *rsaKey); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT RSAKeyPair generateRSAKey(); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptPrivateKey(const QByteArray &privateKey, const QByteArray &masterKey); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptPrivateKey(const QByteArray &encryptedPrivateKey, const QByteArray &masterKey); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray getMasterKey(const QString &password, const QString &userId); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC_256(const QByteArray &data, const QByteArray &key, const QByteArray &iv); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptAES_CBC_256(const QByteArray &data, const QByteArray &key, const QByteArray &iv); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptAES_CBC_128(const QByteArray &data, const QByteArray &key, const QByteArray &iv); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptAES_CBC_128(const QByteArray &data, const QByteArray &key, const QByteArray &iv); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptMessage(const QByteArray &plainText, const QByteArray &sessionKey); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptMessage(const QByteArray &plainText, const QByteArray &sessionKey); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray deriveKey(const QByteArray &salt, const QByteArray &baseKey, int iterations = 1000, int keyLength = 32); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray generateRandomIV(int size); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray generateSessionKey(); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray encryptSessionKey(const QByteArray &sessionKey, RSA *publicKey); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray decryptSessionKey(const QByteArray &encryptedSessionKey, RSA *privateKey); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT RSA *publicKeyFromPEM(const QByteArray &pem); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT RSA *privateKeyFromPEM(const QByteArray &pem); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString generateRandomText(int size); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT EncryptionUtils::EncryptionInfo splitVectorAndEcryptedData(const QByteArray &cipherText); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray joinVectorAndEcryptedData(const EncryptionUtils::EncryptionInfo &info); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QVector toArrayBuffer(const QByteArray &ba); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QByteArray importRawKey(const QByteArray &keyData, const QByteArray &salt, int iterations); -LIBRUQOLACORE_TESTS_EXPORT void importRSAKey(); -LIBRUQOLACORE_TESTS_EXPORT void importAESKey(); -[[nodiscard]] LIBRUQOLACORE_TESTS_EXPORT QString generateRandomPassword(); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray exportJWKKey(RSA *rsaKey); +[[nodiscard]] LIBRUQOLACORE_EXPORT RSAKeyPair generateRSAKey(); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray encryptPrivateKey(const QByteArray &privateKey, const QByteArray &masterKey); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray decryptPrivateKey(const QByteArray &encryptedPrivateKey, const QByteArray &masterKey); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray getMasterKey(const QString &password, const QString &userId); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray encryptAES_CBC_256(const QByteArray &data, const QByteArray &key, const QByteArray &iv); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray decryptAES_CBC_256(const QByteArray &data, const QByteArray &key, const QByteArray &iv); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray encryptAES_CBC_128(const QByteArray &data, const QByteArray &key, const QByteArray &iv); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray decryptAES_CBC_128(const QByteArray &data, const QByteArray &key, const QByteArray &iv); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray encryptMessage(const QByteArray &plainText, const QByteArray &sessionKey); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray decryptMessage(const QByteArray &plainText, const QByteArray &sessionKey); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray deriveKey(const QByteArray &salt, const QByteArray &baseKey, int iterations = 1000, int keyLength = 32); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray generateRandomIV(int size); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray generateSessionKey(); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray encryptSessionKey(const QByteArray &sessionKey, RSA *publicKey); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray decryptSessionKey(const QByteArray &encryptedSessionKey, RSA *privateKey); +[[nodiscard]] LIBRUQOLACORE_EXPORT RSA *publicKeyFromPEM(const QByteArray &pem); +[[nodiscard]] LIBRUQOLACORE_EXPORT RSA *privateKeyFromPEM(const QByteArray &pem); +[[nodiscard]] LIBRUQOLACORE_EXPORT QString generateRandomText(int size); +[[nodiscard]] LIBRUQOLACORE_EXPORT EncryptionUtils::EncryptionInfo splitVectorAndEcryptedData(const QByteArray &cipherText); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray joinVectorAndEcryptedData(const EncryptionUtils::EncryptionInfo &info); +[[nodiscard]] LIBRUQOLACORE_EXPORT QVector toArrayBuffer(const QByteArray &ba); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray importRawKey(const QByteArray &keyData, const QByteArray &salt, int iterations); +LIBRUQOLACORE_EXPORT void importRSAKey(); +LIBRUQOLACORE_EXPORT void importAESKey(); +[[nodiscard]] LIBRUQOLACORE_EXPORT QString generateRandomPassword(); }; Q_DECLARE_TYPEINFO(EncryptionUtils::EncryptionInfo, Q_RELOCATABLE_TYPE); diff --git a/src/core/encryption/ruqolaencryptiondebug.cpp b/src/core/encryption/ruqolaencryptiondebug.cpp new file mode 100644 index 0000000000..681ef9263a --- /dev/null +++ b/src/core/encryption/ruqolaencryptiondebug.cpp @@ -0,0 +1,3 @@ +#include "ruqola_encryption_debug.h" + +Q_LOGGING_CATEGORY(RUQOLA_ENCRYPTION_LOG, "ruqola.encryption") \ No newline at end of file From 2bfd12266c0b114ed75581fff22f2e6101aec4b1 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Wed, 23 Jul 2025 14:19:11 +0300 Subject: [PATCH 40/67] Remove .txt cred file and add cred to .env update the func --- .gitignore | 1 - src/apps/e2ekeytool.cpp | 44 +++++++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index d627f59066..d8b2d2697d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,3 @@ compile_commands.json .qtcreator/ .env docker-compose.yml -credentials.txt diff --git a/src/apps/e2ekeytool.cpp b/src/apps/e2ekeytool.cpp index b0bb4e95b4..e58b3da406 100644 --- a/src/apps/e2ekeytool.cpp +++ b/src/apps/e2ekeytool.cpp @@ -28,36 +28,42 @@ curl -X POST http://localhost:3000/api/v1/login \ using namespace EncryptionUtils; using namespace RocketChatRestApi; -const QString url = QStringLiteral("http://localhost:3000"); +const auto url = QStringLiteral("http://localhost:3000"); -QHash loadCredentials(const QString &filePath) +QHash loadEnvFile(const QString &filePath) { - QHash creds; + QHash env; QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - qWarning() << "Could not open credentials file:" << filePath; - return creds; + qWarning() << "Could not open .env file:" << filePath; + return env; } while (!file.atEnd()) { const QString line = QString::fromUtf8(file.readLine()).trimmed(); - if (line.startsWith(QLatin1Char('#')) || !line.contains(QLatin1Char('='))) + if (line.startsWith(QLatin1Char('#')) || line.isEmpty()) continue; - const auto parts = line.split(QLatin1Char('='), Qt::KeepEmptyParts); - if (parts.size() == 2) - creds[parts[0].trimmed()] = parts[1].trimmed(); + const int equalSignIndex = line.indexOf(QLatin1Char('=')); + + if (equalSignIndex == -1) + continue; + + const QString key = line.left(equalSignIndex).trimmed(); + const QString value = line.mid(equalSignIndex + 1).trimmed(); + + env[key] = value; } - return creds; + return env; } void uploadKeys(const QString &authToken, const QString &userId, const QString &password, QNetworkAccessManager *networkManager) { - RSAKeyPair keyPair = generateRSAKey(); - QByteArray masterKey = getMasterKey(password, QStringLiteral("salt")); - QByteArray encryptedPrivateKey = encryptPrivateKey(keyPair.privateKey, masterKey); + const auto keyPair = generateRSAKey(); + const auto masterKey = getMasterKey(password, QStringLiteral("salt")); + const auto encryptedPrivateKey = encryptPrivateKey(keyPair.privateKey, masterKey); auto *uploadJob = new SetUserPublicAndPrivateKeysJob(); @@ -94,14 +100,14 @@ void downloadKeys(const QString &password) auto *fetchJob = new FetchMyKeysJob(); QObject::connect(fetchJob, &FetchMyKeysJob::fetchMyKeysDone, fetchJob, [password](const QJsonObject &jsonObj) { - QString publicKey = jsonObj["public_key"_L1].toString(); - QString encryptedPrivateKeyB64 = jsonObj["private_key"_L1].toString(); + const auto publicKey = jsonObj["public_key"_L1].toString(); + const auto encryptedPrivateKeyB64 = jsonObj["private_key"_L1].toString(); qDebug() << "Downloaded Public Key:" << publicKey; - QByteArray encryptedPrivateKey = QByteArray::fromBase64(encryptedPrivateKeyB64.toUtf8()); - QByteArray masterKey = getMasterKey(password, QStringLiteral("salt")); - QByteArray decryptedPrivateKey = decryptPrivateKey(encryptedPrivateKey, masterKey); + const auto encryptedPrivateKey = QByteArray::fromBase64(encryptedPrivateKeyB64.toUtf8()); + const auto masterKey = getMasterKey(password, QStringLiteral("salt")); + const auto decryptedPrivateKey = decryptPrivateKey(encryptedPrivateKey, masterKey); qDebug() << "Decrypted Private Key:\n" << QString::fromUtf8(decryptedPrivateKey); @@ -125,7 +131,7 @@ int main(int argc, char *argv[]) auto *loginJob = new LoginJob(&app); auto *restApiMethod = new RestApiMethod(); - auto creds = loadCredentials(QStringLiteral("/home/edc/rocketchat/ruqola/src/apps/credentials.txt")); + auto creds = loadEnvFile(QStringLiteral("/home/edc/rocketchat/ruqola/.env")); restApiMethod->setServerUrl(url); From 0a182684898e6c457e4eba3a7a89771031984066 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 28 Jul 2025 16:34:35 +0300 Subject: [PATCH 41/67] Mv and rename e2ekeytool to encryptiontestcli, update CMakeLists, and rollback the apps/CMakeLists --- src/apps/CMakeLists.txt | 26 +------------------ tests/encryptiontestgui/CMakeLists.txt | 22 +++++++++++++--- .../encryptiontestgui/encryptiontestcli.cpp | 0 3 files changed, 20 insertions(+), 28 deletions(-) rename src/apps/e2ekeytool.cpp => tests/encryptiontestgui/encryptiontestcli.cpp (100%) diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index b0f9a74013..6b4dfb3747 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -3,20 +3,7 @@ include(ECMAddAppIcon) -find_package(OpenSSL REQUIRED) - add_executable(ruqola main.cpp) -add_executable(e2ekeytool - ${CMAKE_SOURCE_DIR}/src/apps/e2ekeytool.cpp - ${CMAKE_SOURCE_DIR}/src/core/encryption/encryptionutils.cpp - ${CMAKE_SOURCE_DIR}/src/core/encryption/ruqolaencryptiondebug.cpp) - -target_include_directories(e2ekeytool PRIVATE - ${CMAKE_SOURCE_DIR}/src/core/encryption - ${CMAKE_BINARY_DIR}/src/core - ${CMAKE_SOURCE_DIR}/src/core/connection - ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt -) target_link_libraries(ruqola libruqolawidgets @@ -26,21 +13,10 @@ target_link_libraries(ruqola KF6::IconThemes ) -target_link_libraries(e2ekeytool - librocketchatrestapi-qt - libruqolawidgets - KF6::Crash - Qt::Core - Qt::Network - OpenSSL::Crypto -) - if(USE_DBUS) target_link_libraries(ruqola KF6::DBusAddons) - target_link_libraries(e2ekeytool KF6::DBusAddons) else() target_link_libraries(ruqola KDAB::kdsingleapplication) - target_link_libraries(e2ekeytool KDAB::kdsingleapplication) if (NOT WIN32 AND NOT APPLE) target_link_libraries(ruqola Qt::GuiPrivate) endif() @@ -51,4 +27,4 @@ ecm_add_app_icon(appIcons ICONS "${RUQOLA_ICONS}") target_sources(ruqola PRIVATE ${appIcons}) install(TARGETS ruqola ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) -install(TARGETS e2ekeytool ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) + diff --git a/tests/encryptiontestgui/CMakeLists.txt b/tests/encryptiontestgui/CMakeLists.txt index 0b67046cc0..acb4602637 100644 --- a/tests/encryptiontestgui/CMakeLists.txt +++ b/tests/encryptiontestgui/CMakeLists.txt @@ -1,9 +1,9 @@ -# SPDX-FileCopyrightText: 2024-2025 Laurent Montel +# SPDX-FileCopyrightText: 2024-2025 Laurent Montel , 2025 Andro Ranogajec # SPDX-License-Identifier: BSD-3-Clause +find_package(OpenSSL REQUIRED) add_executable(encryptiontestgui) target_sources(encryptiontestgui PRIVATE encryptiontestgui.h encryptiontestgui.cpp) - target_link_libraries(encryptiontestgui Qt::Widgets Qt::Gui @@ -11,4 +11,20 @@ target_link_libraries(encryptiontestgui ) set_target_properties(encryptiontestgui PROPERTIES DISABLE_PRECOMPILE_HEADERS ON) - +add_executable(encryptiontestcli + ${CMAKE_CURRENT_SOURCE_DIR}/encryptiontestcli.cpp +) +target_include_directories(encryptiontestcli PRIVATE + ${CMAKE_SOURCE_DIR}/src/core/encryption + ${CMAKE_BINARY_DIR}/src/core + ${CMAKE_SOURCE_DIR}/src/core/connection + ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt + ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/e2e +) +target_link_libraries(encryptiontestcli + librocketchatrestapi-qt + libruqolawidgets + KF6::Crash + Qt::Network + OpenSSL::Crypto +) diff --git a/src/apps/e2ekeytool.cpp b/tests/encryptiontestgui/encryptiontestcli.cpp similarity index 100% rename from src/apps/e2ekeytool.cpp rename to tests/encryptiontestgui/encryptiontestcli.cpp From 04c394637d5e7d8fa2e61fb755cbef521577dd20 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 1 Aug 2025 18:28:17 +0300 Subject: [PATCH 42/67] Refactor the files to use them for tests, upload/download rsa keypair done --- src/core/autotests/CMakeLists.txt | 19 +++ .../uploaddownloadrsakeypairtest.cpp | 41 +++++ .../autotests/uploaddownloadrsakeypairtest.h | 10 ++ tests/CMakeLists.txt | 2 +- .../CMakeLists.txt | 12 +- tests/encryptiontest/encryptiontestcli.cpp | 69 ++++++++ .../encryptiontestgui.cpp | 20 +-- .../encryptiontestgui.h | 0 tests/encryptiontest/envutils.cpp | 28 ++++ tests/encryptiontest/envutils.h | 10 ++ tests/encryptiontest/loginmanager.cpp | 36 ++++ tests/encryptiontest/loginmanager.h | 31 ++++ .../uploaddownloadrsakeypair.cpp | 88 ++++++++++ .../encryptiontest/uploaddownloadrsakeypair.h | 28 ++++ tests/encryptiontestgui/encryptiontestcli.cpp | 157 ------------------ 15 files changed, 379 insertions(+), 172 deletions(-) create mode 100644 src/core/autotests/uploaddownloadrsakeypairtest.cpp create mode 100644 src/core/autotests/uploaddownloadrsakeypairtest.h rename tests/{encryptiontestgui => encryptiontest}/CMakeLists.txt (70%) create mode 100644 tests/encryptiontest/encryptiontestcli.cpp rename tests/{encryptiontestgui => encryptiontest}/encryptiontestgui.cpp (88%) rename tests/{encryptiontestgui => encryptiontest}/encryptiontestgui.h (100%) create mode 100644 tests/encryptiontest/envutils.cpp create mode 100644 tests/encryptiontest/envutils.h create mode 100644 tests/encryptiontest/loginmanager.cpp create mode 100644 tests/encryptiontest/loginmanager.h create mode 100644 tests/encryptiontest/uploaddownloadrsakeypair.cpp create mode 100644 tests/encryptiontest/uploaddownloadrsakeypair.h delete mode 100644 tests/encryptiontestgui/encryptiontestcli.cpp diff --git a/src/core/autotests/CMakeLists.txt b/src/core/autotests/CMakeLists.txt index f6ab68b15d..8d9e0b127d 100644 --- a/src/core/autotests/CMakeLists.txt +++ b/src/core/autotests/CMakeLists.txt @@ -170,6 +170,24 @@ if(USE_E2E_SUPPORT) add_ruqola_test(rsapairtest.cpp) add_ruqola_test(sessionkeytest.cpp) add_ruqola_test(messageencryptiondecryptiontest.cpp) +# add_ruqola_test(uploaddownloadrsakeypairtest.cpp +# ${CMAKE_SOURCE_DIR}/tests/encryptiontest/uploaddownloadrsakeypair.cpp +# ${CMAKE_SOURCE_DIR}/tests/encryptiontest/loginmanager.cpp +# ${CMAKE_SOURCE_DIR}/tests/encryptiontest/envutils.cpp +## ${CMAKE_SOURCE_DIR}/src/core/encryption/encryptionutils.cpp +# ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/restapimethod.cpp +# ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/e2e/fetchmykeysjob.cpp +# ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/e2e/setuserpublicandprivatekeysjob.cpp +# ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/authentication/loginjob.cpp) +# +# target_include_directories(uploaddownloadrsakeypairtest PRIVATE +## ${CMAKE_SOURCE_DIR}/tests/encryptiontest + # ${CMAKE_SOURCE_DIR}/src/core/encryption + # ${CMAKE_SOURCE_DIR}/src/core + # ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt + # ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/e2e + # ) + endif() add_ruqola_test(channelstest.cpp) @@ -187,3 +205,4 @@ add_ruqola_test(actionbuttonsmanagertest.cpp) add_ruqola_test(previewcommandtest.cpp) add_ruqola_test(previewcommandutilstest.cpp) + diff --git a/src/core/autotests/uploaddownloadrsakeypairtest.cpp b/src/core/autotests/uploaddownloadrsakeypairtest.cpp new file mode 100644 index 0000000000..96eb787797 --- /dev/null +++ b/src/core/autotests/uploaddownloadrsakeypairtest.cpp @@ -0,0 +1,41 @@ +#include "uploaddownloadrsakeypair.h" +#include "loginmanager.h" +#include "uploaddownloadrsakeypairtest.h" +#include +#include +#include + +void UploadDownloadRsaKeyPairTest::uploadDownloadCompare() +{ + auto app = QCoreApplication::instance(); + + LoginManager loginManager; + QNetworkAccessManager networkManager; + const QString url = QStringLiteral("http://localhost:3000"); + const QString password = QStringLiteral("mypassword123"); + bool testPassed = false; + + QObject::connect(&loginManager, &LoginManager::loginSucceeded, this, [&](const QString &authToken, const QString &userId) { + uploadKeys(authToken, url, userId, password, &networkManager, [&](const QString &message, const EncryptionUtils::RSAKeyPair &keypair) { + downloadKeys(authToken, url, userId, password, &networkManager, [&](const QString &publicKey, const QString &decryptedPrivateKey) { + QCOMPARE(publicKey, QString::fromUtf8(keypair.publicKey)); + QCOMPARE(decryptedPrivateKey, QString::fromUtf8(keypair.privateKey)); + testPassed = true; + app->quit(); + }); + }); + }); + + QObject::connect(&loginManager, &LoginManager::loginFailed, this, [&](const QString &err) { + QFAIL(qPrintable(QStringLiteral("Login failed: %1").arg(err))); + app->quit(); + }); + + loginManager.login(url, &networkManager); + app->exec(); + + QVERIFY(testPassed); +} + +QTEST_GUILESS_MAIN(UploadDownloadRsaKeyPairTest) +#include "uploaddownloadrsakeypairtest.moc" diff --git a/src/core/autotests/uploaddownloadrsakeypairtest.h b/src/core/autotests/uploaddownloadrsakeypairtest.h new file mode 100644 index 0000000000..16da2ea85b --- /dev/null +++ b/src/core/autotests/uploaddownloadrsakeypairtest.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class UploadDownloadRsaKeyPairTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void uploadDownloadCompare(); +}; \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9875d661cd..08d41c9828 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,7 +13,7 @@ if(TEXT_CONVERTER_CMARK_SUPPORT) add_subdirectory(cmarktestgui) endif() if(OPTION_USE_E2E_SUPPORT) - add_subdirectory(encryptiontestgui) + add_subdirectory(encryptiontest) endif() add_subdirectory(passwordvalidategui) diff --git a/tests/encryptiontestgui/CMakeLists.txt b/tests/encryptiontest/CMakeLists.txt similarity index 70% rename from tests/encryptiontestgui/CMakeLists.txt rename to tests/encryptiontest/CMakeLists.txt index acb4602637..4e42744ffd 100644 --- a/tests/encryptiontestgui/CMakeLists.txt +++ b/tests/encryptiontest/CMakeLists.txt @@ -13,18 +13,22 @@ set_target_properties(encryptiontestgui PROPERTIES DISABLE_PRECOMPILE_HEADERS ON add_executable(encryptiontestcli ${CMAKE_CURRENT_SOURCE_DIR}/encryptiontestcli.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/uploaddownloadrsakeypair.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/loginmanager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/envutils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/uploaddownloadrsakeypair.h + ${CMAKE_CURRENT_SOURCE_DIR}/loginmanager.h + ${CMAKE_CURRENT_SOURCE_DIR}/envutils.h ) target_include_directories(encryptiontestcli PRIVATE ${CMAKE_SOURCE_DIR}/src/core/encryption ${CMAKE_BINARY_DIR}/src/core - ${CMAKE_SOURCE_DIR}/src/core/connection ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt - ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/e2e + ${CMAKE_CURRENT_SOURCE_DIR} ) target_link_libraries(encryptiontestcli librocketchatrestapi-qt - libruqolawidgets - KF6::Crash + libruqolacore Qt::Network OpenSSL::Crypto ) diff --git a/tests/encryptiontest/encryptiontestcli.cpp b/tests/encryptiontest/encryptiontestcli.cpp new file mode 100644 index 0000000000..f66ca67c60 --- /dev/null +++ b/tests/encryptiontest/encryptiontestcli.cpp @@ -0,0 +1,69 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +/* + +test my existance...of course within server, otherwise I still exist :) + +curl -X POST http://localhost:3000/api/v1/login \ + -H "Content-Type: application/json" \ + -d '{"user": "", "password": ""}' +*/ + +#include "authentication/loginjob.h" +#include "e2e/fetchmykeysjob.h" +#include "e2e/setuserpublicandprivatekeysjob.h" +#include "encryptionutils.h" +#include "loginmanager.h" +#include "restapimethod.h" +#include "uploaddownloadrsakeypair.h" +#include +#include +#include +#include + +using namespace EncryptionUtils; + +const auto url = QStringLiteral("http://localhost:3000"); + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + QNetworkAccessManager *networkManager = new QNetworkAccessManager(&app); + LoginManager *loginManager = new LoginManager(&app); + loginManager->login(url, networkManager); + + QObject::connect(loginManager, &LoginManager::loginSucceeded, &app, [=](const QString &authToken, const QString &userId) { + qDebug() << "Login successful! Auth token:" << authToken << "UserId:" << userId << "\n"; + + uploadKeys(authToken, + url, + userId, + QStringLiteral("mypassword123"), + networkManager, + [authToken, userId, networkManager](const QString &message, const RSAKeyPair &keypair) { + qDebug() << message; + qDebug() << keypair.publicKey << keypair.privateKey; + + downloadKeys(authToken, + url, + userId, + QStringLiteral("mypassword123"), + networkManager, + [](const QString &publicKey, const QString &decryptedPrivateKey) { + qDebug() << "Downloaded Public Key:" << publicKey; + qDebug() << "Decrypted Private Key:" << decryptedPrivateKey; + QCoreApplication::quit(); + }); + }); + }); + + QObject::connect(loginManager, &LoginManager::loginFailed, &app, [](const QString &err) { + qCritical() << "Login failed:" << err; + QCoreApplication::quit(); + }); + + return app.exec(); +} \ No newline at end of file diff --git a/tests/encryptiontestgui/encryptiontestgui.cpp b/tests/encryptiontest/encryptiontestgui.cpp similarity index 88% rename from tests/encryptiontestgui/encryptiontestgui.cpp rename to tests/encryptiontest/encryptiontestgui.cpp index 699c507f8e..ec38c21f78 100644 --- a/tests/encryptiontestgui/encryptiontestgui.cpp +++ b/tests/encryptiontest/encryptiontestgui.cpp @@ -109,7 +109,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(pushButtonGenerateSessionKey, &QPushButton::clicked, this, [this]() { mSessionKey = EncryptionUtils::generateSessionKey(); qDebug() << "Derived Session Key:" << mSessionKey.toBase64(); - mTextEditResult->setPlainText((QStringLiteral("Session key generation succeeded!\n") + QString::fromUtf8(mSessionKey.toBase64()))); + mTextEditResult->setPlainText(QStringLiteral("Session key generation succeeded!\n") + QString::fromUtf8(mSessionKey.toBase64())); }); auto pushButtonEncryptSessionKey = new QPushButton(QStringLiteral("Encrypt Session Key"), this); @@ -117,13 +117,13 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(pushButtonEncryptSessionKey, &QPushButton::clicked, this, [this]() { auto publicKey = mRsaKeyPair.publicKey; if (publicKey.isEmpty()) - mTextEditResult->setPlainText((QStringLiteral("Public key is empty, session key encryption failed!\n"))); + mTextEditResult->setPlainText(QStringLiteral("Public key is empty, session key encryption failed!\n")); else { RSA *publicKeyfromPem = EncryptionUtils::publicKeyFromPEM(publicKey); mEncryptedSessionKey = EncryptionUtils::encryptSessionKey(mSessionKey, publicKeyfromPem); qDebug() << "Public Key from PEM:" << publicKeyfromPem; qDebug() << "Encrypted Session Key:" << mEncryptedSessionKey.toBase64(); - mTextEditResult->setPlainText((QStringLiteral("Session key encryption succeeded!\n") + QString::fromUtf8(mEncryptedSessionKey.toBase64()))); + mTextEditResult->setPlainText(QStringLiteral("Session key encryption succeeded!\n") + QString::fromUtf8(mEncryptedSessionKey.toBase64())); } }); @@ -132,13 +132,13 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(pushButtonDecryptSessionKey, &QPushButton::clicked, this, [this]() { auto privateKey = mRsaKeyPair.privateKey; if (privateKey.isEmpty()) - mTextEditResult->setPlainText((QStringLiteral("Private key is empty, session key decryption failed!\n"))); + mTextEditResult->setPlainText(QStringLiteral("Private key is empty, session key decryption failed!\n")); else { RSA *privateKeyfromPem = EncryptionUtils::privateKeyFromPEM(privateKey); mDecryptedSessionKey = EncryptionUtils::decryptSessionKey(mEncryptedSessionKey, privateKeyfromPem); qDebug() << "Private Key from PEM:" << privateKeyfromPem; qDebug() << "Decrypted Session Key:" << mDecryptedSessionKey.toBase64(); - mTextEditResult->setPlainText((QStringLiteral("Session key decryption succeeded!\n") + QString::fromUtf8(mDecryptedSessionKey.toBase64()))); + mTextEditResult->setPlainText(QStringLiteral("Session key decryption succeeded!\n") + QString::fromUtf8(mDecryptedSessionKey.toBase64())); } }); auto pushButtonEncryptMessage = new QPushButton(QStringLiteral("Encrypt message"), this); @@ -146,11 +146,11 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(pushButtonEncryptMessage, &QPushButton::clicked, this, [this]() { const auto text = mTextEdit->toPlainText(); if (text.isEmpty()) { - mTextEditResult->setPlainText((QStringLiteral("Text cannot be null, message encryption failed!\n"))); + mTextEditResult->setPlainText(QStringLiteral("Text cannot be null, message encryption failed!\n")); } else { mEncryptedMessage = EncryptionUtils::encryptMessage(text.toUtf8(), mSessionKey); qDebug() << "Encrypted message:" << mEncryptedMessage.toBase64(); - mTextEditResult->setPlainText((QStringLiteral("Message encryption succeeded!\n") + QString::fromUtf8(mEncryptedMessage.toBase64()))); + mTextEditResult->setPlainText(QStringLiteral("Message encryption succeeded!\n") + QString::fromUtf8(mEncryptedMessage.toBase64())); mTextEdit->clear(); } }); @@ -159,12 +159,12 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) connect(pushButtonDecryptMessage, &QPushButton::clicked, this, [this]() { qDebug() << "Session key:" << mSessionKey; if (QString::fromUtf8(mEncryptedMessage).isEmpty()) { - mTextEditResult->setPlainText((QStringLiteral("Encrypted message is null, message decryption failed!\n"))); + mTextEditResult->setPlainText(QStringLiteral("Encrypted message is null, message decryption failed!\n")); return; } mDecryptedMessage = EncryptionUtils::decryptMessage(mEncryptedMessage, mSessionKey); qDebug() << "Decrypted message:" << mDecryptedMessage; - mTextEditResult->setPlainText((QStringLiteral("Message decryption succeeded!\n") + QString::fromUtf8(mDecryptedMessage))); + mTextEditResult->setPlainText(QStringLiteral("Message decryption succeeded!\n") + QString::fromUtf8(mDecryptedMessage)); }); auto pushButtonReset = new QPushButton(QStringLiteral("Reset"), this); @@ -186,7 +186,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) << "\npublickey: " << mRsaKeyPair.publicKey << "\nencrypted session key: " << mEncryptedSessionKey << "\ndecrypted session key: " << mDecryptedSessionKey << "\nencrypted message: " << mEncryptedMessage << "\ndecrypted message: " << mDecryptedMessage; - mTextEditResult->setPlainText((QStringLiteral("Reset succeded!\n"))); + mTextEditResult->setPlainText(QStringLiteral("Reset succeded!\n")); }); mTextEditResult->setReadOnly(true); diff --git a/tests/encryptiontestgui/encryptiontestgui.h b/tests/encryptiontest/encryptiontestgui.h similarity index 100% rename from tests/encryptiontestgui/encryptiontestgui.h rename to tests/encryptiontest/encryptiontestgui.h diff --git a/tests/encryptiontest/envutils.cpp b/tests/encryptiontest/envutils.cpp new file mode 100644 index 0000000000..f6672ce2c6 --- /dev/null +++ b/tests/encryptiontest/envutils.cpp @@ -0,0 +1,28 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "envutils.h" +#include + +QHash loadEnvFile(const QString &filePath) +{ + QHash env; + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return env; + } + while (!file.atEnd()) { + const QString line = QString::fromUtf8(file.readLine()).trimmed(); + if (line.startsWith(QLatin1Char('#')) || line.isEmpty()) + continue; + const int equalSignIndex = line.indexOf(QLatin1Char('=')); + if (equalSignIndex == -1) + continue; + const QString key = line.left(equalSignIndex).trimmed(); + const QString value = line.mid(equalSignIndex + 1).trimmed(); + env[key] = value; + } + return env; +} \ No newline at end of file diff --git a/tests/encryptiontest/envutils.h b/tests/encryptiontest/envutils.h new file mode 100644 index 0000000000..c65b590931 --- /dev/null +++ b/tests/encryptiontest/envutils.h @@ -0,0 +1,10 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#pragma once +#include +#include + +QHash loadEnvFile(const QString &filePath); \ No newline at end of file diff --git a/tests/encryptiontest/loginmanager.cpp b/tests/encryptiontest/loginmanager.cpp new file mode 100644 index 0000000000..243f4ffe91 --- /dev/null +++ b/tests/encryptiontest/loginmanager.cpp @@ -0,0 +1,36 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "loginmanager.h" + +LoginManager::LoginManager(QObject *parent) + : QObject(parent) +{ +} + +void LoginManager::login(const QString &serverUrl, QNetworkAccessManager *networkManager) +{ + auto envPath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("../../.env")); + auto creds = loadEnvFile(envPath); + + loginJob = new RocketChatRestApi::LoginJob(this); + restApiMethod = new RocketChatRestApi::RestApiMethod(); + restApiMethod->setServerUrl(serverUrl); + + loginJob->setRestApiMethod(restApiMethod); + loginJob->setNetworkAccessManager(networkManager); + loginJob->setUserName(creds.value(QStringLiteral("USERNAME"))); + loginJob->setPassword(creds.value(QStringLiteral("PASSWORD"))); + + QObject::connect(loginJob, &RocketChatRestApi::LoginJob::loginDone, this, [this](const QString &authToken, const QString &userId) { + Q_EMIT loginSucceeded(authToken, userId); + }); + + QObject::connect(loginJob, &RocketChatRestApi::RestApiAbstractJob::failed, this, [this](const QString &err) { + Q_EMIT loginFailed(err); + }); + + loginJob->start(); +} \ No newline at end of file diff --git a/tests/encryptiontest/loginmanager.h b/tests/encryptiontest/loginmanager.h new file mode 100644 index 0000000000..8670973382 --- /dev/null +++ b/tests/encryptiontest/loginmanager.h @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#pragma once + +#include "authentication/loginjob.h" +#include "envutils.h" +#include "restapimethod.h" +#include +#include +#include +#include +#include + +class LoginManager : public QObject +{ + Q_OBJECT +public: + explicit LoginManager(QObject *parent = nullptr); + void login(const QString &serverUrl, QNetworkAccessManager *networkManager); + +Q_SIGNALS: + void loginSucceeded(const QString &authToken, const QString &userId); + void loginFailed(const QString &error); + +private: + RocketChatRestApi::LoginJob *loginJob = nullptr; + RocketChatRestApi::RestApiMethod *restApiMethod = nullptr; +}; \ No newline at end of file diff --git a/tests/encryptiontest/uploaddownloadrsakeypair.cpp b/tests/encryptiontest/uploaddownloadrsakeypair.cpp new file mode 100644 index 0000000000..07e5e79268 --- /dev/null +++ b/tests/encryptiontest/uploaddownloadrsakeypair.cpp @@ -0,0 +1,88 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "uploaddownloadrsakeypair.h" +#include +#include +#include + +using namespace EncryptionUtils; +using namespace RocketChatRestApi; + +void uploadKeys(const QString &authToken, + const QString &url, + const QString &userId, + const QString &password, + QNetworkAccessManager *networkManager, + std::function onSuccess) +{ + const auto keyPair = generateRSAKey(); + const auto masterKey = getMasterKey(password, QStringLiteral("salt")); + const auto encryptedPrivateKey = encryptPrivateKey(keyPair.privateKey, masterKey); + + auto *uploadJob = new SetUserPublicAndPrivateKeysJob(); + auto *restApiMethod = new RestApiMethod(); + restApiMethod->setServerUrl(url); + + uploadJob->setRestApiMethod(restApiMethod); + uploadJob->setNetworkAccessManager(networkManager); + uploadJob->setAuthToken(authToken); + uploadJob->setUserId(userId); + + SetUserPublicAndPrivateKeysJob::SetUserPublicAndPrivateKeysInfo info; + info.rsaPublicKey = QString::fromUtf8(keyPair.publicKey); + info.rsaPrivateKey = QString::fromUtf8(encryptedPrivateKey.toBase64()); + info.force = true; + uploadJob->setSetUserPublicAndPrivateKeysInfo(info); + + QObject::connect(uploadJob, &SetUserPublicAndPrivateKeysJob::setUserPublicAndPrivateKeysDone, uploadJob, [onSuccess, keyPair]() { + if (onSuccess) { + onSuccess(QStringLiteral("Key upload successful!"), keyPair); + } + }); + + QObject::connect(uploadJob, &SetUserPublicAndPrivateKeysJob::failed, uploadJob, [](const QString &err) { + qCritical() << "Key upload failed!: " << err; + QCoreApplication::quit(); + }); + + uploadJob->start(); +} + +void downloadKeys(const QString &authToken, + const QString &url, + const QString &userId, + const QString &password, + QNetworkAccessManager *networkManager, + std::function onSuccess) +{ + auto *fetchJob = new FetchMyKeysJob(); + auto *restApiMethod = new RestApiMethod(); + restApiMethod->setServerUrl(url); + + fetchJob->setRestApiMethod(restApiMethod); + fetchJob->setNetworkAccessManager(networkManager); + fetchJob->setAuthToken(authToken); + fetchJob->setUserId(userId); + + QObject::connect(fetchJob, &FetchMyKeysJob::fetchMyKeysDone, fetchJob, [password, onSuccess](const QJsonObject &jsonObj) { + const auto publicKey = jsonObj["public_key"_L1].toString(); + const auto encryptedPrivateKeyB64 = jsonObj["private_key"_L1].toString(); + const auto encryptedPrivateKey = QByteArray::fromBase64(encryptedPrivateKeyB64.toUtf8()); + const auto masterKey = getMasterKey(password, QStringLiteral("salt")); + const auto decryptedPrivateKey = QString::fromUtf8(decryptPrivateKey(encryptedPrivateKey, masterKey)); + + if (onSuccess) { + onSuccess(publicKey, decryptedPrivateKey); + } + }); + + QObject::connect(fetchJob, &RestApiAbstractJob::failed, fetchJob, [=](const QString &err) { + qCritical() << "Key fetch failed:" << err; + QCoreApplication::quit(); + }); + + fetchJob->start(); +} \ No newline at end of file diff --git a/tests/encryptiontest/uploaddownloadrsakeypair.h b/tests/encryptiontest/uploaddownloadrsakeypair.h new file mode 100644 index 0000000000..f7f48e7821 --- /dev/null +++ b/tests/encryptiontest/uploaddownloadrsakeypair.h @@ -0,0 +1,28 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#pragma once + +#include "e2e/fetchmykeysjob.h" +#include "e2e/setuserpublicandprivatekeysjob.h" +#include "encryptionutils.h" +#include "restapimethod.h" +#include +#include +#include + +void uploadKeys(const QString &authToken, + const QString &url, + const QString &userId, + const QString &password, + QNetworkAccessManager *networkManager, + std::function onSuccess); + +void downloadKeys(const QString &authToken, + const QString &url, + const QString &userId, + const QString &password, + QNetworkAccessManager *networkManager, + std::function onSuccess); \ No newline at end of file diff --git a/tests/encryptiontestgui/encryptiontestcli.cpp b/tests/encryptiontestgui/encryptiontestcli.cpp deleted file mode 100644 index e58b3da406..0000000000 --- a/tests/encryptiontestgui/encryptiontestcli.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* - SPDX-FileCopyrightText: 2025 Andro Ranogajec - - SPDX-License-Identifier: LGPL-2.0-or-later -*/ - -/* - -test my existance...of course within server, otherwise I still exist :) - -curl -X POST http://localhost:3000/api/v1/login \ - -H "Content-Type: application/json" \ - -d '{"user": "", "password": ""}' -*/ - -#include "authentication/loginjob.h" -#include "e2e/fetchmykeysjob.h" -#include "e2e/setuserpublicandprivatekeysjob.h" -#include "encryptionutils.h" -#include "restapimethod.h" -#include -#include -#include -#include -#include -#include - -using namespace EncryptionUtils; -using namespace RocketChatRestApi; - -const auto url = QStringLiteral("http://localhost:3000"); - -QHash loadEnvFile(const QString &filePath) -{ - QHash env; - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - qWarning() << "Could not open .env file:" << filePath; - return env; - } - - while (!file.atEnd()) { - const QString line = QString::fromUtf8(file.readLine()).trimmed(); - - if (line.startsWith(QLatin1Char('#')) || line.isEmpty()) - continue; - - const int equalSignIndex = line.indexOf(QLatin1Char('=')); - - if (equalSignIndex == -1) - continue; - - const QString key = line.left(equalSignIndex).trimmed(); - const QString value = line.mid(equalSignIndex + 1).trimmed(); - - env[key] = value; - } - - return env; -} - -void uploadKeys(const QString &authToken, const QString &userId, const QString &password, QNetworkAccessManager *networkManager) -{ - const auto keyPair = generateRSAKey(); - const auto masterKey = getMasterKey(password, QStringLiteral("salt")); - const auto encryptedPrivateKey = encryptPrivateKey(keyPair.privateKey, masterKey); - - auto *uploadJob = new SetUserPublicAndPrivateKeysJob(); - - auto *restApiMethod = new RestApiMethod(); - restApiMethod->setServerUrl(url); - - uploadJob->setRestApiMethod(restApiMethod); - uploadJob->setNetworkAccessManager(networkManager); - - uploadJob->setAuthToken(authToken); - uploadJob->setUserId(userId); - - SetUserPublicAndPrivateKeysJob::SetUserPublicAndPrivateKeysInfo info; - info.rsaPublicKey = QString::fromUtf8(keyPair.publicKey); - info.rsaPrivateKey = QString::fromUtf8(encryptedPrivateKey.toBase64()); - info.force = true; - uploadJob->setSetUserPublicAndPrivateKeysInfo(info); - - QObject::connect(uploadJob, &SetUserPublicAndPrivateKeysJob::setUserPublicAndPrivateKeysDone, uploadJob, []() { - qDebug() << "Key upload successful!"; - QCoreApplication::quit(); - }); - - QObject::connect(uploadJob, &SetUserPublicAndPrivateKeysJob::failed, uploadJob, [](const QString &err) { - qCritical() << "Key upload failed: " << err; - QCoreApplication::quit(); - }); - - uploadJob->start(); -} - -void downloadKeys(const QString &password) -{ - auto *fetchJob = new FetchMyKeysJob(); - - QObject::connect(fetchJob, &FetchMyKeysJob::fetchMyKeysDone, fetchJob, [password](const QJsonObject &jsonObj) { - const auto publicKey = jsonObj["public_key"_L1].toString(); - const auto encryptedPrivateKeyB64 = jsonObj["private_key"_L1].toString(); - - qDebug() << "Downloaded Public Key:" << publicKey; - - const auto encryptedPrivateKey = QByteArray::fromBase64(encryptedPrivateKeyB64.toUtf8()); - const auto masterKey = getMasterKey(password, QStringLiteral("salt")); - const auto decryptedPrivateKey = decryptPrivateKey(encryptedPrivateKey, masterKey); - - qDebug() << "Decrypted Private Key:\n" << QString::fromUtf8(decryptedPrivateKey); - - QCoreApplication::quit(); - }); - - QObject::connect(fetchJob, &RestApiAbstractJob::failed, nullptr, [=](const QString &err) { - qCritical() << "Key fetch failed:" << err; - QCoreApplication::quit(); - }); - - fetchJob->start(); -} - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - - QNetworkAccessManager *networkManager = new QNetworkAccessManager(&app); - - auto *loginJob = new LoginJob(&app); - auto *restApiMethod = new RestApiMethod(); - - auto creds = loadEnvFile(QStringLiteral("/home/edc/rocketchat/ruqola/.env")); - - restApiMethod->setServerUrl(url); - - loginJob->setRestApiMethod(restApiMethod); - loginJob->setNetworkAccessManager(networkManager); - - loginJob->setUserName(creds.value(QStringLiteral("USERNAME"))); - loginJob->setPassword(creds.value(QStringLiteral("PASSWORD"))); - - QObject::connect(loginJob, &LoginJob::loginDone, &app, [networkManager](const QString &authToken, const QString &userId) { - qDebug() << "Login successful! Auth token:" << authToken << "UserId:" << userId; - // uploadKeys(authToken, userId, QStringLiteral("mypassword123"), networkManager); - }); - - QObject::connect(loginJob, &RestApiAbstractJob::failed, &app, [](const QString &err) { - qCritical() << "Login failed:" << err; - QCoreApplication::quit(); - }); - - loginJob->start(); - - return app.exec(); -} \ No newline at end of file From 92e8606f6e4b1c5a968d5b1ada6d1dd9d3048a23 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 4 Aug 2025 12:38:12 +0300 Subject: [PATCH 43/67] Coding style fixes and const in 'encryptionutils' 'encryptiontestgui' --- src/core/encryption/encryptionutils.cpp | 6 ++--- tests/encryptiontest/encryptiontestgui.cpp | 30 +++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index df092e9559..b5cb3af7f7 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -365,12 +365,12 @@ QByteArray EncryptionUtils::decryptMessage(const QByteArray &encrypted, const QB return {}; } - QByteArray iv = encrypted.left(16); - QByteArray cipherText = encrypted.mid(16); + const QByteArray iv = encrypted.left(16); + const QByteArray cipherText = encrypted.mid(16); qDebug() << cipherText << "QByteArray cipherText = encrypted.mid(16)"; - QByteArray plainText = decryptAES_CBC_128(cipherText, sessionKey, iv); + const QByteArray plainText = decryptAES_CBC_128(cipherText, sessionKey, iv); qDebug() << plainText << "QByteArray plainText = decryptAES_CBC_128(cipherText, sessionKey, iv);"; diff --git a/tests/encryptiontest/encryptiontestgui.cpp b/tests/encryptiontest/encryptiontestgui.cpp index ec38c21f78..6c5c7ae96b 100644 --- a/tests/encryptiontest/encryptiontestgui.cpp +++ b/tests/encryptiontest/encryptiontestgui.cpp @@ -56,9 +56,9 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mPassword = passwordEdit->text(); mMasterKey = EncryptionUtils::getMasterKey(mPassword, mSalt); - if (mMasterKey.isEmpty()) + if (mMasterKey.isEmpty()) { mTextEditResult->setPlainText(QStringLiteral("Master key is empty, generation failed!")); - else { + } else { qDebug() << "Derived Master Key:" << mMasterKey.toBase64(); mTextEditResult->setPlainText((QStringLiteral("Master Key derivation succeeded!\n") + QString::fromUtf8(mMasterKey.toBase64()))); } @@ -78,11 +78,11 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonEncryptPrivateKey = new QPushButton(QStringLiteral("Encrypt Private Key"), this); mainLayout->addWidget(pushButtonEncryptPrivateKey); connect(pushButtonEncryptPrivateKey, &QPushButton::clicked, this, [this]() { - if (mMasterKey.isEmpty()) + if (mMasterKey.isEmpty()) { mTextEditResult->setPlainText(QStringLiteral("Master key is empty, encryption of the private key failed!")); - else if (mRsaKeyPair.privateKey.isEmpty()) + } else if (mRsaKeyPair.privateKey.isEmpty()) { mTextEditResult->setPlainText(QStringLiteral("Private key is empty, encryption of the private key failed!")); - else { + } else { mEncryptedPrivateKey = EncryptionUtils::encryptPrivateKey(mRsaKeyPair.privateKey, mMasterKey); qDebug() << mEncryptedPrivateKey.toBase64() << "encrypted and encoded to 'base64()' private key "; mTextEditResult->setPlainText(QStringLiteral("Private key encryption succeded!\n") + QString::fromUtf8(mEncryptedPrivateKey.toBase64())); @@ -92,11 +92,11 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonDecryptPrivateKey = new QPushButton(QStringLiteral("Decrypt Private Key"), this); mainLayout->addWidget(pushButtonDecryptPrivateKey); connect(pushButtonDecryptPrivateKey, &QPushButton::clicked, this, [this]() { - if (mMasterKey.isEmpty()) + if (mMasterKey.isEmpty()) { mTextEditResult->setPlainText(QStringLiteral("Master key is empty, encryption of the private key failed!")); - else if (mEncryptedPrivateKey.isEmpty()) + } else if (mEncryptedPrivateKey.isEmpty()) { mTextEditResult->setPlainText(QStringLiteral("Encrypted private key is empty, decryption of the private key failed!")); - else { + } else { mDecryptedPrivateKey = EncryptionUtils::decryptPrivateKey(mEncryptedPrivateKey, mMasterKey); mTextEditResult->setPlainText(QStringLiteral("Private key decryption succeded!\n") + QString::fromUtf8(mDecryptedPrivateKey)); qDebug() << mDecryptedPrivateKey << "decrypted private key '\n' "; @@ -115,10 +115,10 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonEncryptSessionKey = new QPushButton(QStringLiteral("Encrypt Session Key"), this); mainLayout->addWidget(pushButtonEncryptSessionKey); connect(pushButtonEncryptSessionKey, &QPushButton::clicked, this, [this]() { - auto publicKey = mRsaKeyPair.publicKey; - if (publicKey.isEmpty()) + const auto publicKey = mRsaKeyPair.publicKey; + if (publicKey.isEmpty()) { mTextEditResult->setPlainText(QStringLiteral("Public key is empty, session key encryption failed!\n")); - else { + } else { RSA *publicKeyfromPem = EncryptionUtils::publicKeyFromPEM(publicKey); mEncryptedSessionKey = EncryptionUtils::encryptSessionKey(mSessionKey, publicKeyfromPem); qDebug() << "Public Key from PEM:" << publicKeyfromPem; @@ -131,9 +131,9 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mainLayout->addWidget(pushButtonDecryptSessionKey); connect(pushButtonDecryptSessionKey, &QPushButton::clicked, this, [this]() { auto privateKey = mRsaKeyPair.privateKey; - if (privateKey.isEmpty()) + if (privateKey.isEmpty()) { mTextEditResult->setPlainText(QStringLiteral("Private key is empty, session key decryption failed!\n")); - else { + } else { RSA *privateKeyfromPem = EncryptionUtils::privateKeyFromPEM(privateKey); mDecryptedSessionKey = EncryptionUtils::decryptSessionKey(mEncryptedSessionKey, privateKeyfromPem); qDebug() << "Private Key from PEM:" << privateKeyfromPem; @@ -173,8 +173,8 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mTextEdit->clear(); mTextEditResult->clear(); mMasterKey.clear(); - mPassword = QStringLiteral(""); - mSalt = QStringLiteral(""); + mPassword = QString(); + mSalt = QString(); mRsaKeyPair.privateKey.clear(); mRsaKeyPair.publicKey.clear(); mSessionKey.clear(); From 3f58196ccda1d2b2860bf77b1d3398bc4b2c69e0 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 4 Aug 2025 13:28:25 +0300 Subject: [PATCH 44/67] Remove unused includes, header files from add_executable add copyright text --- src/core/autotests/uploaddownloadrsakeypairtest.cpp | 5 +++++ src/core/autotests/uploaddownloadrsakeypairtest.h | 5 +++++ tests/encryptiontest/CMakeLists.txt | 3 --- tests/encryptiontest/encryptiontestcli.cpp | 7 ++----- tests/encryptiontest/loginmanager.h | 1 - 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/core/autotests/uploaddownloadrsakeypairtest.cpp b/src/core/autotests/uploaddownloadrsakeypairtest.cpp index 96eb787797..5c7da953ce 100644 --- a/src/core/autotests/uploaddownloadrsakeypairtest.cpp +++ b/src/core/autotests/uploaddownloadrsakeypairtest.cpp @@ -1,3 +1,8 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + #include "uploaddownloadrsakeypair.h" #include "loginmanager.h" #include "uploaddownloadrsakeypairtest.h" diff --git a/src/core/autotests/uploaddownloadrsakeypairtest.h b/src/core/autotests/uploaddownloadrsakeypairtest.h index 16da2ea85b..e6368a67a9 100644 --- a/src/core/autotests/uploaddownloadrsakeypairtest.h +++ b/src/core/autotests/uploaddownloadrsakeypairtest.h @@ -1,3 +1,8 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + #pragma once #include diff --git a/tests/encryptiontest/CMakeLists.txt b/tests/encryptiontest/CMakeLists.txt index 4e42744ffd..1a8d1a9a63 100644 --- a/tests/encryptiontest/CMakeLists.txt +++ b/tests/encryptiontest/CMakeLists.txt @@ -16,9 +16,6 @@ add_executable(encryptiontestcli ${CMAKE_CURRENT_SOURCE_DIR}/uploaddownloadrsakeypair.cpp ${CMAKE_CURRENT_SOURCE_DIR}/loginmanager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/envutils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/uploaddownloadrsakeypair.h - ${CMAKE_CURRENT_SOURCE_DIR}/loginmanager.h - ${CMAKE_CURRENT_SOURCE_DIR}/envutils.h ) target_include_directories(encryptiontestcli PRIVATE ${CMAKE_SOURCE_DIR}/src/core/encryption diff --git a/tests/encryptiontest/encryptiontestcli.cpp b/tests/encryptiontest/encryptiontestcli.cpp index f66ca67c60..d5e7f2b64e 100644 --- a/tests/encryptiontest/encryptiontestcli.cpp +++ b/tests/encryptiontest/encryptiontestcli.cpp @@ -12,7 +12,6 @@ curl -X POST http://localhost:3000/api/v1/login \ -d '{"user": "", "password": ""}' */ -#include "authentication/loginjob.h" #include "e2e/fetchmykeysjob.h" #include "e2e/setuserpublicandprivatekeysjob.h" #include "encryptionutils.h" @@ -21,8 +20,6 @@ curl -X POST http://localhost:3000/api/v1/login \ #include "uploaddownloadrsakeypair.h" #include #include -#include -#include using namespace EncryptionUtils; @@ -31,8 +28,8 @@ const auto url = QStringLiteral("http://localhost:3000"); int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); - QNetworkAccessManager *networkManager = new QNetworkAccessManager(&app); - LoginManager *loginManager = new LoginManager(&app); + auto *networkManager = new QNetworkAccessManager(&app); + auto *loginManager = new LoginManager(&app); loginManager->login(url, networkManager); QObject::connect(loginManager, &LoginManager::loginSucceeded, &app, [=](const QString &authToken, const QString &userId) { diff --git a/tests/encryptiontest/loginmanager.h b/tests/encryptiontest/loginmanager.h index 8670973382..9629219c6b 100644 --- a/tests/encryptiontest/loginmanager.h +++ b/tests/encryptiontest/loginmanager.h @@ -12,7 +12,6 @@ #include #include #include -#include class LoginManager : public QObject { From ed7b58801805eef997851937e60ebc46237a03cd Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 4 Aug 2025 13:33:04 +0300 Subject: [PATCH 45/67] Rm 'ruqolaencryptiondebug.cpp' --- src/core/encryption/ruqolaencryptiondebug.cpp | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 src/core/encryption/ruqolaencryptiondebug.cpp diff --git a/src/core/encryption/ruqolaencryptiondebug.cpp b/src/core/encryption/ruqolaencryptiondebug.cpp deleted file mode 100644 index 681ef9263a..0000000000 --- a/src/core/encryption/ruqolaencryptiondebug.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "ruqola_encryption_debug.h" - -Q_LOGGING_CATEGORY(RUQOLA_ENCRYPTION_LOG, "ruqola.encryption") \ No newline at end of file From ec98e06fb51ef3e3be7616984afa614437a42cc7 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 4 Aug 2025 18:03:01 +0300 Subject: [PATCH 46/67] Fix linking issue for the test, spot and spot a runtime bug --- src/core/autotests/CMakeLists.txt | 33 +++++++++---------- .../uploaddownloadrsakeypairtest.cpp | 23 +++++++------ .../restapiabstractjob.cpp | 1 + tests/encryptiontest/loginmanager.cpp | 5 +++ .../uploaddownloadrsakeypair.cpp | 1 + 5 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/core/autotests/CMakeLists.txt b/src/core/autotests/CMakeLists.txt index 8d9e0b127d..c2580c5142 100644 --- a/src/core/autotests/CMakeLists.txt +++ b/src/core/autotests/CMakeLists.txt @@ -170,23 +170,22 @@ if(USE_E2E_SUPPORT) add_ruqola_test(rsapairtest.cpp) add_ruqola_test(sessionkeytest.cpp) add_ruqola_test(messageencryptiondecryptiontest.cpp) -# add_ruqola_test(uploaddownloadrsakeypairtest.cpp -# ${CMAKE_SOURCE_DIR}/tests/encryptiontest/uploaddownloadrsakeypair.cpp -# ${CMAKE_SOURCE_DIR}/tests/encryptiontest/loginmanager.cpp -# ${CMAKE_SOURCE_DIR}/tests/encryptiontest/envutils.cpp -## ${CMAKE_SOURCE_DIR}/src/core/encryption/encryptionutils.cpp -# ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/restapimethod.cpp -# ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/e2e/fetchmykeysjob.cpp -# ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/e2e/setuserpublicandprivatekeysjob.cpp -# ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/authentication/loginjob.cpp) -# -# target_include_directories(uploaddownloadrsakeypairtest PRIVATE -## ${CMAKE_SOURCE_DIR}/tests/encryptiontest - # ${CMAKE_SOURCE_DIR}/src/core/encryption - # ${CMAKE_SOURCE_DIR}/src/core - # ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt - # ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/e2e - # ) + add_ruqola_test(uploaddownloadrsakeypairtest.cpp) + +target_include_directories(uploaddownloadrsakeypairtest PRIVATE + ${CMAKE_SOURCE_DIR}/tests/encryptiontest + ${CMAKE_SOURCE_DIR}/src/core/encryption + ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt + ${CMAKE_SOURCE_DIR}/src/rocketchatrestapi-qt/e2e + ${CMAKE_BINARY_DIR}/src/core + ${CMAKE_BINARY_DIR}/src/rocketchatrestapi-qt +) + +target_sources(uploaddownloadrsakeypairtest PRIVATE + ${CMAKE_SOURCE_DIR}/tests/encryptiontest/loginmanager.cpp + ${CMAKE_SOURCE_DIR}/tests/encryptiontest/uploaddownloadrsakeypair.cpp + ${CMAKE_SOURCE_DIR}/tests/encryptiontest/envutils.cpp +) endif() diff --git a/src/core/autotests/uploaddownloadrsakeypairtest.cpp b/src/core/autotests/uploaddownloadrsakeypairtest.cpp index 5c7da953ce..9d6ff4301b 100644 --- a/src/core/autotests/uploaddownloadrsakeypairtest.cpp +++ b/src/core/autotests/uploaddownloadrsakeypairtest.cpp @@ -7,22 +7,26 @@ #include "loginmanager.h" #include "uploaddownloadrsakeypairtest.h" #include +#include #include #include +QTEST_GUILESS_MAIN(UploadDownloadRsaKeyPairTest) + void UploadDownloadRsaKeyPairTest::uploadDownloadCompare() { auto app = QCoreApplication::instance(); - LoginManager loginManager; - QNetworkAccessManager networkManager; - const QString url = QStringLiteral("http://localhost:3000"); - const QString password = QStringLiteral("mypassword123"); + auto loginManager = new LoginManager(app); + auto networkManager = new QNetworkAccessManager(app); + const auto url = QStringLiteral("http://localhost:3000"); + const auto password = QStringLiteral("mypassword123"); bool testPassed = false; - QObject::connect(&loginManager, &LoginManager::loginSucceeded, this, [&](const QString &authToken, const QString &userId) { - uploadKeys(authToken, url, userId, password, &networkManager, [&](const QString &message, const EncryptionUtils::RSAKeyPair &keypair) { - downloadKeys(authToken, url, userId, password, &networkManager, [&](const QString &publicKey, const QString &decryptedPrivateKey) { + QObject::connect(loginManager, &LoginManager::loginSucceeded, this, [&](const QString &authToken, const QString &userId) { + qDebug() << "Login succeeded! authToken:" << authToken << "userId:" << userId; + uploadKeys(authToken, url, userId, password, networkManager, [&](const QString &message, const EncryptionUtils::RSAKeyPair &keypair) { + downloadKeys(authToken, url, userId, password, networkManager, [&](const QString &publicKey, const QString &decryptedPrivateKey) { QCOMPARE(publicKey, QString::fromUtf8(keypair.publicKey)); QCOMPARE(decryptedPrivateKey, QString::fromUtf8(keypair.privateKey)); testPassed = true; @@ -31,16 +35,15 @@ void UploadDownloadRsaKeyPairTest::uploadDownloadCompare() }); }); - QObject::connect(&loginManager, &LoginManager::loginFailed, this, [&](const QString &err) { + QObject::connect(loginManager, &LoginManager::loginFailed, this, [&](const QString &err) { QFAIL(qPrintable(QStringLiteral("Login failed: %1").arg(err))); app->quit(); }); - loginManager.login(url, &networkManager); + loginManager->login(url, networkManager); app->exec(); QVERIFY(testPassed); } -QTEST_GUILESS_MAIN(UploadDownloadRsaKeyPairTest) #include "uploaddownloadrsakeypairtest.moc" diff --git a/src/rocketchatrestapi-qt/restapiabstractjob.cpp b/src/rocketchatrestapi-qt/restapiabstractjob.cpp index 38194f424e..27c490d3da 100644 --- a/src/rocketchatrestapi-qt/restapiabstractjob.cpp +++ b/src/rocketchatrestapi-qt/restapiabstractjob.cpp @@ -95,6 +95,7 @@ void RestApiAbstractJob::setEnforcePasswordFallback(bool enforce) bool RestApiAbstractJob::canStart() const { + qDebug() << "canStart() called, mAuthToken:" << mAuthToken << "mUserId:" << mUserId; if (requireTwoFactorAuthentication() && mEnforcePasswordFallBack) { if (mAuthMethod.isEmpty() || mAuthCode.isEmpty()) { qCWarning(ROCKETCHATQTRESTAPI_LOG) << "Job required two factor auth but mAuthMethod or mAuthCode is empty"; diff --git a/tests/encryptiontest/loginmanager.cpp b/tests/encryptiontest/loginmanager.cpp index 243f4ffe91..426e0c250f 100644 --- a/tests/encryptiontest/loginmanager.cpp +++ b/tests/encryptiontest/loginmanager.cpp @@ -21,6 +21,11 @@ void LoginManager::login(const QString &serverUrl, QNetworkAccessManager *networ loginJob->setRestApiMethod(restApiMethod); loginJob->setNetworkAccessManager(networkManager); + + if (creds.value(QStringLiteral("USERNAME")).isEmpty() || creds.value(QStringLiteral("PASSWORD")).isEmpty()) { + qDebug() << "Username or password are empty!"; + } + loginJob->setUserName(creds.value(QStringLiteral("USERNAME"))); loginJob->setPassword(creds.value(QStringLiteral("PASSWORD"))); diff --git a/tests/encryptiontest/uploaddownloadrsakeypair.cpp b/tests/encryptiontest/uploaddownloadrsakeypair.cpp index 07e5e79268..793c7f4138 100644 --- a/tests/encryptiontest/uploaddownloadrsakeypair.cpp +++ b/tests/encryptiontest/uploaddownloadrsakeypair.cpp @@ -22,6 +22,7 @@ void uploadKeys(const QString &authToken, const auto masterKey = getMasterKey(password, QStringLiteral("salt")); const auto encryptedPrivateKey = encryptPrivateKey(keyPair.privateKey, masterKey); + qDebug() << "uploadKeys called with authToken:" << authToken; auto *uploadJob = new SetUserPublicAndPrivateKeysJob(); auto *restApiMethod = new RestApiMethod(); restApiMethod->setServerUrl(url); From b8ab123473cdb63f7abdc42aeaaa040a930df807 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 8 Aug 2025 18:21:52 +0300 Subject: [PATCH 47/67] Fix tests, fix variables, remove spaces, add qualifiers --- src/core/autotests/uploaddownloadrsakeypairtest.cpp | 13 ++++++------- tests/encryptiontest/encryptiontestcli.cpp | 4 ++-- tests/encryptiontest/encryptiontestgui.cpp | 11 +++++------ tests/encryptiontest/envutils.h | 2 +- tests/encryptiontest/loginmanager.cpp | 4 ++-- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/core/autotests/uploaddownloadrsakeypairtest.cpp b/src/core/autotests/uploaddownloadrsakeypairtest.cpp index 9d6ff4301b..f91397c307 100644 --- a/src/core/autotests/uploaddownloadrsakeypairtest.cpp +++ b/src/core/autotests/uploaddownloadrsakeypairtest.cpp @@ -15,18 +15,17 @@ QTEST_GUILESS_MAIN(UploadDownloadRsaKeyPairTest) void UploadDownloadRsaKeyPairTest::uploadDownloadCompare() { - auto app = QCoreApplication::instance(); - - auto loginManager = new LoginManager(app); - auto networkManager = new QNetworkAccessManager(app); + const auto app = QCoreApplication::instance(); + const auto loginManager = new LoginManager(app); + const auto networkManager = new QNetworkAccessManager(app); const auto url = QStringLiteral("http://localhost:3000"); const auto password = QStringLiteral("mypassword123"); bool testPassed = false; - QObject::connect(loginManager, &LoginManager::loginSucceeded, this, [&](const QString &authToken, const QString &userId) { + QObject::connect(loginManager, &LoginManager::loginSucceeded, this, [=, &testPassed](const QString &authToken, const QString &userId) { qDebug() << "Login succeeded! authToken:" << authToken << "userId:" << userId; - uploadKeys(authToken, url, userId, password, networkManager, [&](const QString &message, const EncryptionUtils::RSAKeyPair &keypair) { - downloadKeys(authToken, url, userId, password, networkManager, [&](const QString &publicKey, const QString &decryptedPrivateKey) { + uploadKeys(authToken, url, userId, password, networkManager, [=, &testPassed](const QString &message, const EncryptionUtils::RSAKeyPair &keypair) { + downloadKeys(authToken, url, userId, password, networkManager, [=, &testPassed](const QString &publicKey, const QString &decryptedPrivateKey) { QCOMPARE(publicKey, QString::fromUtf8(keypair.publicKey)); QCOMPARE(decryptedPrivateKey, QString::fromUtf8(keypair.privateKey)); testPassed = true; diff --git a/tests/encryptiontest/encryptiontestcli.cpp b/tests/encryptiontest/encryptiontestcli.cpp index d5e7f2b64e..22bb1c3fb5 100644 --- a/tests/encryptiontest/encryptiontestcli.cpp +++ b/tests/encryptiontest/encryptiontestcli.cpp @@ -28,8 +28,8 @@ const auto url = QStringLiteral("http://localhost:3000"); int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); - auto *networkManager = new QNetworkAccessManager(&app); - auto *loginManager = new LoginManager(&app); + auto networkManager = new QNetworkAccessManager(&app); + auto loginManager = new LoginManager(&app); loginManager->login(url, networkManager); QObject::connect(loginManager, &LoginManager::loginSucceeded, &app, [=](const QString &authToken, const QString &userId) { diff --git a/tests/encryptiontest/encryptiontestgui.cpp b/tests/encryptiontest/encryptiontestgui.cpp index 6c5c7ae96b..29ddfc85ad 100644 --- a/tests/encryptiontest/encryptiontestgui.cpp +++ b/tests/encryptiontest/encryptiontestgui.cpp @@ -20,7 +20,6 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) : QWidget{parent} , mTextEdit(new QTextEdit(this)) , mTextEditResult(new QTextEdit(this)) - { auto mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins({}); @@ -32,20 +31,20 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) mainLayout->addWidget(pushButtonDeriveMasterKey); connect(pushButtonDeriveMasterKey, &QPushButton::clicked, this, [this]() { - auto *dialog = new QDialog(this); + auto dialog = new QDialog(this); dialog->setWindowTitle(QStringLiteral("Credentials")); - auto *saltEdit = new QLineEdit(dialog); - auto *passwordEdit = new QLineEdit(dialog); + auto saltEdit = new QLineEdit(dialog); + auto passwordEdit = new QLineEdit(dialog); passwordEdit->setEchoMode(QLineEdit::Password); - auto *layout = new QGridLayout(dialog); + auto layout = new QGridLayout(dialog); layout->addWidget(new QLabel(QStringLiteral("Salt: "), dialog), 0, 0); layout->addWidget(saltEdit, 0, 1); layout->addWidget(new QLabel(QStringLiteral("Password: "), dialog), 1, 0); layout->addWidget(passwordEdit, 1, 1); - auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog); + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog); layout->addWidget(buttonBox, 2, 0, 1, 2); connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); diff --git a/tests/encryptiontest/envutils.h b/tests/encryptiontest/envutils.h index c65b590931..1b1e0b5317 100644 --- a/tests/encryptiontest/envutils.h +++ b/tests/encryptiontest/envutils.h @@ -7,4 +7,4 @@ #include #include -QHash loadEnvFile(const QString &filePath); \ No newline at end of file +[[nodiscard]] QHash loadEnvFile(const QString &filePath); \ No newline at end of file diff --git a/tests/encryptiontest/loginmanager.cpp b/tests/encryptiontest/loginmanager.cpp index 426e0c250f..ff571b5c25 100644 --- a/tests/encryptiontest/loginmanager.cpp +++ b/tests/encryptiontest/loginmanager.cpp @@ -12,8 +12,8 @@ LoginManager::LoginManager(QObject *parent) void LoginManager::login(const QString &serverUrl, QNetworkAccessManager *networkManager) { - auto envPath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("../../.env")); - auto creds = loadEnvFile(envPath); + const auto envPath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("../../.env")); + const auto creds = loadEnvFile(envPath); loginJob = new RocketChatRestApi::LoginJob(this); restApiMethod = new RocketChatRestApi::RestApiMethod(); From c061b17821a583f2854d0838779e00966333918c Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 11 Aug 2025 12:25:18 +0300 Subject: [PATCH 48/67] Fix small 'auto' bug again --- tests/encryptiontest/uploaddownloadrsakeypair.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/encryptiontest/uploaddownloadrsakeypair.cpp b/tests/encryptiontest/uploaddownloadrsakeypair.cpp index 793c7f4138..1f8939b9f7 100644 --- a/tests/encryptiontest/uploaddownloadrsakeypair.cpp +++ b/tests/encryptiontest/uploaddownloadrsakeypair.cpp @@ -23,8 +23,8 @@ void uploadKeys(const QString &authToken, const auto encryptedPrivateKey = encryptPrivateKey(keyPair.privateKey, masterKey); qDebug() << "uploadKeys called with authToken:" << authToken; - auto *uploadJob = new SetUserPublicAndPrivateKeysJob(); - auto *restApiMethod = new RestApiMethod(); + const auto uploadJob = new SetUserPublicAndPrivateKeysJob(); + const auto restApiMethod = new RestApiMethod(); restApiMethod->setServerUrl(url); uploadJob->setRestApiMethod(restApiMethod); @@ -59,8 +59,8 @@ void downloadKeys(const QString &authToken, QNetworkAccessManager *networkManager, std::function onSuccess) { - auto *fetchJob = new FetchMyKeysJob(); - auto *restApiMethod = new RestApiMethod(); + const auto fetchJob = new FetchMyKeysJob(); + const auto restApiMethod = new RestApiMethod(); restApiMethod->setServerUrl(url); fetchJob->setRestApiMethod(restApiMethod); From 749e27ade5b3b109a9654f773397707f1c07974c Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Thu, 14 Aug 2025 14:30:41 +0300 Subject: [PATCH 49/67] Add e2e db and e2e db tests, WIP undefined reference --- src/core/CMakeLists.txt | 7 +- .../localdatabase/autotests/CMakeLists.txt | 1 + .../autotests/e2ekeystoretest.cpp | 76 ++++++++++++++++ .../localdatabase/autotests/e2ekeystoretest.h | 18 ++++ src/core/localdatabase/e2ekeystore.cpp | 87 +++++++++++++++++++ src/core/localdatabase/e2ekeystore.h | 25 ++++++ src/core/localdatabase/localdatabasebase.h | 1 + 7 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 src/core/localdatabase/autotests/e2ekeystoretest.cpp create mode 100644 src/core/localdatabase/autotests/e2ekeystoretest.h create mode 100644 src/core/localdatabase/e2ekeystore.cpp create mode 100644 src/core/localdatabase/e2ekeystore.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f5f977130f..ff712103e9 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -509,6 +509,8 @@ target_sources(libruqolacore PRIVATE servererrorinfohistorymanager.cpp servererrorinfohistorymanager.h + + localdatabase/localmessagelogger.cpp localdatabase/localmessagelogger.h @@ -526,10 +528,13 @@ target_sources(libruqolacore PRIVATE localdatabase/localdatabasebase.h localdatabase/localdatabasebase.cpp - + localdatabase/localaccountdatabase.h localdatabase/localaccountdatabase.cpp + localdatabase/e2ekeystore.h + localdatabase/e2ekeystore.cpp + localdatabase/globaldatabase.h localdatabase/globaldatabase.cpp diff --git a/src/core/localdatabase/autotests/CMakeLists.txt b/src/core/localdatabase/autotests/CMakeLists.txt index f9b853d8f4..234ac1d002 100644 --- a/src/core/localdatabase/autotests/CMakeLists.txt +++ b/src/core/localdatabase/autotests/CMakeLists.txt @@ -21,3 +21,4 @@ add_ruqola_localdatabase_test(globaldatabasetest.cpp) add_ruqola_localdatabase_test(localaccountdatabasetest.cpp) add_ruqola_localdatabase_test(localroomsdatabasetest.cpp) add_ruqola_localdatabase_test(localdatabasebasetest.cpp) +add_ruqola_localdatabase_test(e2ekeystoretest.cpp ) #../e2ekeystore.cpp \ No newline at end of file diff --git a/src/core/localdatabase/autotests/e2ekeystoretest.cpp b/src/core/localdatabase/autotests/e2ekeystoretest.cpp new file mode 100644 index 0000000000..eb12309a15 --- /dev/null +++ b/src/core/localdatabase/autotests/e2ekeystoretest.cpp @@ -0,0 +1,76 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "e2ekeystoretest.h" +#include "encryption/encryptionutils.h" +#include "localdatabase/e2ekeystore.h" +#include + +QTEST_GUILESS_MAIN(E2EKeyStoreTest) + +const auto testUser = QStringLiteral("testuser"); +const auto otherUser = QStringLiteral("otheruser"); + +void E2EKeyStoreTest::initTestCase() +{ + E2EKeyStore store; + store.deleteKey(testUser); + store.deleteKey(otherUser); +} + +void E2EKeyStoreTest::testSaveLoadDelete() +{ + E2EKeyStore store; + const auto userId = testUser; + const auto rsaKeyPair = EncryptionUtils::generateRSAKey(); + const auto priv = rsaKeyPair.publicKey; + const auto pub = rsaKeyPair.privateKey; + + QVERIFY(store.saveKey(userId, priv, pub)); + QVERIFY(store.hasKey(userId)); + + QByteArray loadedPriv, loadedPub; + QVERIFY(store.loadKey(userId, loadedPriv, loadedPub)); + QCOMPARE(loadedPriv, priv); + QCOMPARE(loadedPub, pub); + + QVERIFY(store.deleteKey(userId)); + QVERIFY(!store.hasKey(userId)); +} + +void E2EKeyStoreTest::testOverwrite() +{ + E2EKeyStore store; + const auto userId = testUser; + const auto rsaKeyPair1 = EncryptionUtils::generateRSAKey(); + const auto rsaKeyPair2 = EncryptionUtils::generateRSAKey(); + + const auto priv1 = rsaKeyPair1.privateKey; + const auto pub1 = rsaKeyPair1.publicKey; + const auto priv2 = rsaKeyPair2.privateKey; + const auto pub2 = rsaKeyPair2.publicKey; + + QVERIFY(store.saveKey(userId, priv1, pub1)); + QVERIFY(store.saveKey(userId, priv2, pub2)); + + QByteArray loadedPriv, loadedPub; + QVERIFY(store.loadKey(userId, loadedPriv, loadedPub)); + QCOMPARE(loadedPriv, priv2); + QCOMPARE(loadedPub, pub2); + + store.deleteKey(userId); +} + +void E2EKeyStoreTest::testNonexistentKey() +{ + E2EKeyStore store; + const auto userId = otherUser; + QByteArray priv, pub; + QVERIFY(!store.hasKey(userId)); + QVERIFY(!store.loadKey(userId, priv, pub)); + QVERIFY(!store.deleteKey(userId)); +} + +#include "e2ekeystoretest.moc" \ No newline at end of file diff --git a/src/core/localdatabase/autotests/e2ekeystoretest.h b/src/core/localdatabase/autotests/e2ekeystoretest.h new file mode 100644 index 0000000000..75605a19a5 --- /dev/null +++ b/src/core/localdatabase/autotests/e2ekeystoretest.h @@ -0,0 +1,18 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#pragma once + +#include + +class E2EKeyStoreTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void testSaveLoadDelete(); + void testOverwrite(); + void testNonexistentKey(); +}; \ No newline at end of file diff --git a/src/core/localdatabase/e2ekeystore.cpp b/src/core/localdatabase/e2ekeystore.cpp new file mode 100644 index 0000000000..ef6a75932a --- /dev/null +++ b/src/core/localdatabase/e2ekeystore.cpp @@ -0,0 +1,87 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "e2ekeystore.h" +#include "localdatabaseutils.h" +#include "ruqola_database_debug.h" +#include +#include + +const char E2EKeyStore::s_schemaE2EKeyStore[] = "CREATE TABLE E2EKEYS (userId TEXT PRIMARY KEY NOT NULL, encryptedPrivateKey BLOB, publicKey BLOB)"; + +E2EKeyStore::E2EKeyStore() + : LocalDatabaseBase(LocalDatabaseUtils::localDatabasePath() + QStringLiteral("e2e/"), LocalDatabaseBase::DatabaseType::E2E) +{ +} + +E2EKeyStore::~E2EKeyStore() = default; + +QString E2EKeyStore::schemaDataBase() const +{ + return QString::fromLatin1(s_schemaE2EKeyStore); +} + +bool E2EKeyStore::saveKey(const QString &userId, const QByteArray &encryptedPrivateKey, const QByteArray &publicKey) +{ + QSqlDatabase db; + if (!initializeDataBase(userId, db)) { + return false; + } + QSqlQuery query(db); + query.prepare(QStringLiteral("INSERT OR REPLACE INTO E2EKEYS (userId, encryptedPrivateKey, publicKey) VALUES (?, ?, ?)")); + query.addBindValue(userId); + query.addBindValue(encryptedPrivateKey); + query.addBindValue(publicKey); + if (!query.exec()) { + qCWarning(RUQOLA_DATABASE_LOG) << "Couldn't insert-or-replace in E2EKEYS table" << db.databaseName() << query.lastError(); + return false; + } + return true; +} + +bool E2EKeyStore::loadKey(const QString &userId, QByteArray &encryptedPrivateKey, QByteArray &publicKey) +{ + QSqlDatabase db; + if (!initializeDataBase(userId, db)) { + return false; + } + QSqlQuery query(db); + query.prepare(QStringLiteral("SELECT encryptedPrivateKey, publicKey FROM E2EKEYS WHERE userId = ?")); + query.addBindValue(userId); + if (query.exec() && query.first()) { + encryptedPrivateKey = query.value(0).toByteArray(); + publicKey = query.value(1).toByteArray(); + return true; + } + return false; +} + +bool E2EKeyStore::deleteKey(const QString &userId) +{ + QSqlDatabase db; + if (!initializeDataBase(userId, db)) { + return false; + } + QSqlQuery query(db); + query.prepare(QStringLiteral("DELETE FROM E2EKEYS WHERE userId = ?")); + query.addBindValue(userId); + if (!query.exec()) { + qCWarning(RUQOLA_DATABASE_LOG) << "Couldn't delete from E2EKEYS table" << db.databaseName() << query.lastError(); + return false; + } + return true; +} + +bool E2EKeyStore::hasKey(const QString &userId) +{ + QSqlDatabase db; + if (!initializeDataBase(userId, db)) { + return false; + } + QSqlQuery query(db); + query.prepare(QStringLiteral("SELECT 1 FROM E2EKEYS WHERE userId = ?")); + query.addBindValue(userId); + return query.exec() && query.first(); +} \ No newline at end of file diff --git a/src/core/localdatabase/e2ekeystore.h b/src/core/localdatabase/e2ekeystore.h new file mode 100644 index 0000000000..f78fa42619 --- /dev/null +++ b/src/core/localdatabase/e2ekeystore.h @@ -0,0 +1,25 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#pragma once + +#include "localdatabasebase.h" + +class E2EKeyStore : public LocalDatabaseBase +{ +public: + E2EKeyStore(); + ~E2EKeyStore() override; + + bool saveKey(const QString &userId, const QByteArray &encryptedPrivateKey, const QByteArray &publicKey); + bool loadKey(const QString &userId, QByteArray &encryptedPrivateKey, QByteArray &publicKey); + bool deleteKey(const QString &userId); + bool hasKey(const QString &userId); + + QString schemaDataBase() const override; + +private: + static const char s_schemaE2EKeyStore[]; +}; \ No newline at end of file diff --git a/src/core/localdatabase/localdatabasebase.h b/src/core/localdatabase/localdatabasebase.h index 5beb47feb0..426e1d4efa 100644 --- a/src/core/localdatabase/localdatabasebase.h +++ b/src/core/localdatabase/localdatabasebase.h @@ -19,6 +19,7 @@ class LIBRUQOLACORE_EXPORT LocalDatabaseBase Message, Logger, Global, + E2E }; explicit LocalDatabaseBase(const QString &basePath, DatabaseType type); virtual ~LocalDatabaseBase(); From 8013d8352a02ae6cf7a0617088ce9697f7319376 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 15 Aug 2025 12:33:36 +0300 Subject: [PATCH 50/67] Remove qdebug from 'restapiabstractjob.cpp' --- src/rocketchatrestapi-qt/restapiabstractjob.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rocketchatrestapi-qt/restapiabstractjob.cpp b/src/rocketchatrestapi-qt/restapiabstractjob.cpp index 27c490d3da..38194f424e 100644 --- a/src/rocketchatrestapi-qt/restapiabstractjob.cpp +++ b/src/rocketchatrestapi-qt/restapiabstractjob.cpp @@ -95,7 +95,6 @@ void RestApiAbstractJob::setEnforcePasswordFallback(bool enforce) bool RestApiAbstractJob::canStart() const { - qDebug() << "canStart() called, mAuthToken:" << mAuthToken << "mUserId:" << mUserId; if (requireTwoFactorAuthentication() && mEnforcePasswordFallBack) { if (mAuthMethod.isEmpty() || mAuthCode.isEmpty()) { qCWarning(ROCKETCHATQTRESTAPI_LOG) << "Job required two factor auth but mAuthMethod or mAuthCode is empty"; From 3b89bfee761fc0c43df1ccf34d4052ebf54f7783 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 15 Aug 2025 12:57:39 +0300 Subject: [PATCH 51/67] Add nodiscard attribute to function declarations --- src/core/localdatabase/e2ekeystore.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/localdatabase/e2ekeystore.h b/src/core/localdatabase/e2ekeystore.h index f78fa42619..0cd8166c30 100644 --- a/src/core/localdatabase/e2ekeystore.h +++ b/src/core/localdatabase/e2ekeystore.h @@ -13,12 +13,12 @@ class E2EKeyStore : public LocalDatabaseBase E2EKeyStore(); ~E2EKeyStore() override; - bool saveKey(const QString &userId, const QByteArray &encryptedPrivateKey, const QByteArray &publicKey); - bool loadKey(const QString &userId, QByteArray &encryptedPrivateKey, QByteArray &publicKey); - bool deleteKey(const QString &userId); - bool hasKey(const QString &userId); + [[nodiscard]] bool saveKey(const QString &userId, const QByteArray &encryptedPrivateKey, const QByteArray &publicKey); + [[nodiscard]] bool loadKey(const QString &userId, QByteArray &encryptedPrivateKey, QByteArray &publicKey); + [[nodiscard]] bool deleteKey(const QString &userId); + [[nodiscard]] bool hasKey(const QString &userId); - QString schemaDataBase() const override; + [[nodiscard]] QString schemaDataBase() const override; private: static const char s_schemaE2EKeyStore[]; From 56dac26a45d71d8f424d93a1a4c486509fd66ed5 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 15 Aug 2025 13:46:22 +0300 Subject: [PATCH 52/67] Fix undefined reference bug, big thx to Laurent Montel --- src/core/localdatabase/autotests/CMakeLists.txt | 5 ++++- src/core/localdatabase/e2ekeystore.h | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/core/localdatabase/autotests/CMakeLists.txt b/src/core/localdatabase/autotests/CMakeLists.txt index 234ac1d002..667ca09391 100644 --- a/src/core/localdatabase/autotests/CMakeLists.txt +++ b/src/core/localdatabase/autotests/CMakeLists.txt @@ -21,4 +21,7 @@ add_ruqola_localdatabase_test(globaldatabasetest.cpp) add_ruqola_localdatabase_test(localaccountdatabasetest.cpp) add_ruqola_localdatabase_test(localroomsdatabasetest.cpp) add_ruqola_localdatabase_test(localdatabasebasetest.cpp) -add_ruqola_localdatabase_test(e2ekeystoretest.cpp ) #../e2ekeystore.cpp \ No newline at end of file + +if(USE_E2E_SUPPORT) + add_ruqola_localdatabase_test(e2ekeystoretest.cpp) +endif() \ No newline at end of file diff --git a/src/core/localdatabase/e2ekeystore.h b/src/core/localdatabase/e2ekeystore.h index 0cd8166c30..9081b20040 100644 --- a/src/core/localdatabase/e2ekeystore.h +++ b/src/core/localdatabase/e2ekeystore.h @@ -5,9 +5,10 @@ #pragma once +#include "libruqolacore_export.h" #include "localdatabasebase.h" -class E2EKeyStore : public LocalDatabaseBase +class LIBRUQOLACORE_EXPORT E2EKeyStore : public LocalDatabaseBase { public: E2EKeyStore(); From 32d42dd4d39e7ed822eb89158fc914ebffec9c49 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 15 Aug 2025 14:02:47 +0300 Subject: [PATCH 53/67] Fix testNonexistentKey --- src/core/localdatabase/autotests/e2ekeystoretest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/localdatabase/autotests/e2ekeystoretest.cpp b/src/core/localdatabase/autotests/e2ekeystoretest.cpp index eb12309a15..a3a54e6d71 100644 --- a/src/core/localdatabase/autotests/e2ekeystoretest.cpp +++ b/src/core/localdatabase/autotests/e2ekeystoretest.cpp @@ -70,7 +70,7 @@ void E2EKeyStoreTest::testNonexistentKey() QByteArray priv, pub; QVERIFY(!store.hasKey(userId)); QVERIFY(!store.loadKey(userId, priv, pub)); - QVERIFY(!store.deleteKey(userId)); + QVERIFY(store.deleteKey(userId)); } #include "e2ekeystoretest.moc" \ No newline at end of file From a2ad0a3f12703b98d0c81211e335b540e145144a Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 18 Aug 2025 00:35:23 +0300 Subject: [PATCH 54/67] Extend login manager to accept multiple credentials --- .../uploaddownloadrsakeypairtest.cpp | 2 +- tests/encryptiontest/encryptiontestcli.cpp | 2 +- tests/encryptiontest/loginmanager.cpp | 11 +++--- tests/encryptiontest/loginmanager.h | 34 ++++++++++++++++++- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/core/autotests/uploaddownloadrsakeypairtest.cpp b/src/core/autotests/uploaddownloadrsakeypairtest.cpp index f91397c307..5d2090981e 100644 --- a/src/core/autotests/uploaddownloadrsakeypairtest.cpp +++ b/src/core/autotests/uploaddownloadrsakeypairtest.cpp @@ -39,7 +39,7 @@ void UploadDownloadRsaKeyPairTest::uploadDownloadCompare() app->quit(); }); - loginManager->login(url, networkManager); + loginManager->login(url, networkManager, 0); app->exec(); QVERIFY(testPassed); diff --git a/tests/encryptiontest/encryptiontestcli.cpp b/tests/encryptiontest/encryptiontestcli.cpp index 22bb1c3fb5..9e395653bc 100644 --- a/tests/encryptiontest/encryptiontestcli.cpp +++ b/tests/encryptiontest/encryptiontestcli.cpp @@ -30,7 +30,7 @@ int main(int argc, char *argv[]) QCoreApplication app(argc, argv); auto networkManager = new QNetworkAccessManager(&app); auto loginManager = new LoginManager(&app); - loginManager->login(url, networkManager); + loginManager->login(url, networkManager, 1); QObject::connect(loginManager, &LoginManager::loginSucceeded, &app, [=](const QString &authToken, const QString &userId) { qDebug() << "Login successful! Auth token:" << authToken << "UserId:" << userId << "\n"; diff --git a/tests/encryptiontest/loginmanager.cpp b/tests/encryptiontest/loginmanager.cpp index ff571b5c25..2923923ee2 100644 --- a/tests/encryptiontest/loginmanager.cpp +++ b/tests/encryptiontest/loginmanager.cpp @@ -10,11 +10,14 @@ LoginManager::LoginManager(QObject *parent) { } -void LoginManager::login(const QString &serverUrl, QNetworkAccessManager *networkManager) +void LoginManager::login(const QString &serverUrl, QNetworkAccessManager *networkManager, int userIndex) { const auto envPath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("../../.env")); const auto creds = loadEnvFile(envPath); + const auto usernameKey = userIndex == 0 ? QStringLiteral("USERNAME") : QStringLiteral("USERNAME%1").arg(userIndex); + const auto passwordKey = userIndex == 0 ? QStringLiteral("PASSWORD") : QStringLiteral("PASSWORD%1").arg(userIndex); + loginJob = new RocketChatRestApi::LoginJob(this); restApiMethod = new RocketChatRestApi::RestApiMethod(); restApiMethod->setServerUrl(serverUrl); @@ -22,12 +25,12 @@ void LoginManager::login(const QString &serverUrl, QNetworkAccessManager *networ loginJob->setRestApiMethod(restApiMethod); loginJob->setNetworkAccessManager(networkManager); - if (creds.value(QStringLiteral("USERNAME")).isEmpty() || creds.value(QStringLiteral("PASSWORD")).isEmpty()) { + if (creds.value(usernameKey).isEmpty() || creds.value(passwordKey).isEmpty()) { qDebug() << "Username or password are empty!"; } - loginJob->setUserName(creds.value(QStringLiteral("USERNAME"))); - loginJob->setPassword(creds.value(QStringLiteral("PASSWORD"))); + loginJob->setUserName(creds.value(usernameKey)); + loginJob->setPassword(creds.value(passwordKey)); QObject::connect(loginJob, &RocketChatRestApi::LoginJob::loginDone, this, [this](const QString &authToken, const QString &userId) { Q_EMIT loginSucceeded(authToken, userId); diff --git a/tests/encryptiontest/loginmanager.h b/tests/encryptiontest/loginmanager.h index 9629219c6b..ef9c543b9d 100644 --- a/tests/encryptiontest/loginmanager.h +++ b/tests/encryptiontest/loginmanager.h @@ -13,12 +13,44 @@ #include #include +/** + * @class LoginManager + * @brief Utility class for logging into Rocket.Chat as a test user. + * + * LoginManager simplifies the process of authenticating test users in integration and autotests. + * It reads user credentials from a `.env` file located at the project root, supporting multiple users + * by using environment variable suffixes (e.g., USERNAME1, PASSWORD1, USERNAME2, PASSWORD2, etc.). + * + * Usage: + * + * LoginManager lm; + * lm.login(serverUrl, networkManager, index); + * + * + * LoginManager lm2; + * lm2.login(serverUrl, networkManager, 2); // Uses USERNAME2, PASSWORD2 + * + * .env file: + * + * The .env file should be placed at the project root and contain lines like: + * USERNAME=alice + * PASSWORD=alicepass + * + * USERNAME1=bob + * PASSWORD1=bobpass + * + * USERNAME2=carol + * PASSWORD2=carolpass + * + * This allows you to run tests with multiple users by specifying their credentials. + * + **/ class LoginManager : public QObject { Q_OBJECT public: explicit LoginManager(QObject *parent = nullptr); - void login(const QString &serverUrl, QNetworkAccessManager *networkManager); + void login(const QString &serverUrl, QNetworkAccessManager *networkManager, int userIndex); Q_SIGNALS: void loginSucceeded(const QString &authToken, const QString &userId); From 9dc94690599511769042b390e18298192ae2b051 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 18 Aug 2025 18:35:12 +0300 Subject: [PATCH 55/67] Session key distribution .h .cpp and tests .h .cpp --- src/core/autotests/CMakeLists.txt | 4 +- .../autotests/sessionkeydistributiontest.cpp | 135 ++++++++++++++++++ .../autotests/sessionkeydistributiontest.h | 37 +++++ src/core/encryption/encryptionutils.cpp | 10 ++ .../provideuserswithsuggestedgroupkeysjob.cpp | 107 ++++++++++++++ .../provideuserswithsuggestedgroupkeysjob.h | 58 ++++++++ 6 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 src/core/autotests/sessionkeydistributiontest.cpp create mode 100644 src/core/autotests/sessionkeydistributiontest.h create mode 100644 src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp create mode 100644 src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h diff --git a/src/core/autotests/CMakeLists.txt b/src/core/autotests/CMakeLists.txt index c2580c5142..8fcd1ff642 100644 --- a/src/core/autotests/CMakeLists.txt +++ b/src/core/autotests/CMakeLists.txt @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2020-2025 Laurent Montel +# SPDX-FileCopyrightText: 2020-2025 Laurent Montel , +# SPDX-FileCopyrightText: 2025 Andro Ranogajec # SPDX-License-Identifier: BSD-3-Clause add_definitions(-DRUQOLA_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data") add_definitions(-DRUQOLA_BINARY_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data") @@ -171,6 +172,7 @@ if(USE_E2E_SUPPORT) add_ruqola_test(sessionkeytest.cpp) add_ruqola_test(messageencryptiondecryptiontest.cpp) add_ruqola_test(uploaddownloadrsakeypairtest.cpp) + add_ruqola_test(sessionkeydistributiontest.cpp) target_include_directories(uploaddownloadrsakeypairtest PRIVATE ${CMAKE_SOURCE_DIR}/tests/encryptiontest diff --git a/src/core/autotests/sessionkeydistributiontest.cpp b/src/core/autotests/sessionkeydistributiontest.cpp new file mode 100644 index 0000000000..7aa4e2a5e4 --- /dev/null +++ b/src/core/autotests/sessionkeydistributiontest.cpp @@ -0,0 +1,135 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "sessionkeydistributiontest.h" +#include "e2e/acceptsuggestedgroupkeyjob.h" +#include "e2e/provideuserswithsuggestedgroupkeysjob.h" +#include "e2e/rejectsuggestedgroupkeyjob.h" +#include "encryption/encryptionutils.h" +#include "loginmanager.h" +#include "uploaddownloadrsakeypair.h" +#include +#include +#include +QTEST_GUILESS_MAIN(SessionKeyDistributionTest) + +class SessionKeyDistributionTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testSessionKeyDistribution(); +}; + +void SessionKeyDistributionTest::testSessionKeyDistribution() +{ + const auto app = QCoreApplication::instance(); + const auto networkManager = new QNetworkAccessManager(app); + const auto url = QStringLiteral("http://localhost:3000"); + const auto password = QStringLiteral("mypassword123"); + const auto roomId = QStringLiteral("123"); // Replace with a real room ID + + // Step 1: Login as two user + auto loginManager1 = new LoginManager(app); + auto loginManager2 = new LoginManager(app); + QString user1Id, user2Id, user1Auth, user2Auth; + EncryptionUtils::RSAKeyPair user1KeyPair, user2KeyPair; + QByteArray sessionKey; + auto testPassed = false; + + // Helper: proceed when both users are ready + int readyCount = 0; + auto proceed = [&]() { + if (++readyCount < 2) + return; + + // Step 2: Generate session key (AES-128) + sessionKey = EncryptionUtils::generateSessionKey(); + + // Step 3: Encrypt session key with each user's public key + QVector suggestedKeys; + QByteArray encryptedSessionKeyForUser1 = EncryptionUtils::encryptSessionKey(sessionKey, EncryptionUtils::publicKeyFromPEM(user1KeyPair.publicKey)); + QByteArray encryptedSessionKeyForUser2 = EncryptionUtils::encryptSessionKey(sessionKey, EncryptionUtils::publicKeyFromPEM(user2KeyPair.publicKey)); + suggestedKeys.append({user1Id, encryptedSessionKeyForUser1.toBase64()}); + suggestedKeys.append({user2Id, encryptedSessionKeyForUser2.toBase64()}); + + // Step 4: Distribute encrypted keys using API + auto provideJob = new RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob(app); + provideJob->setRoomId(roomId); + provideJob->setKeys(suggestedKeys); + QObject::connect( + provideJob, + &RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob::provideUsersWithSuggestedGroupKeysDone, + app, + [&](const QJsonObject &) { + // Simulate user1 receiving and accepting the key + QByteArray encKey1 = QByteArray::fromBase64(suggestedKeys[0].encryptedKey.toUtf8()); + QByteArray decKey1 = EncryptionUtils::decryptSessionKey(encKey1, EncryptionUtils::privateKeyFromPEM(user1KeyPair.privateKey)); + QCOMPARE(decKey1, sessionKey); + + auto acceptJob1 = new RocketChatRestApi::AcceptSuggestedGroupKeyJob(app); + acceptJob1->setRoomId(roomId); + QObject::connect(acceptJob1, &RocketChatRestApi::AcceptSuggestedGroupKeyJob::acceptSuggestedGroupKeyDone, app, [&](const QJsonObject &) { + // Simulate user2 receiving and rejecting the key + QByteArray encKey2 = QByteArray::fromBase64(suggestedKeys[1].encryptedKey.toUtf8()); + QByteArray decKey2 = EncryptionUtils::decryptSessionKey(encKey2, EncryptionUtils::privateKeyFromPEM(user2KeyPair.privateKey)); + QCOMPARE(decKey2, sessionKey); + + auto rejectJob2 = new RocketChatRestApi::RejectSuggestedGroupKeyJob(app); + rejectJob2->setRoomId(roomId); + QObject::connect(rejectJob2, &RocketChatRestApi::RejectSuggestedGroupKeyJob::rejectSuggestedGroupKeyDone, app, [&](const QJsonObject &) { + testPassed = true; + app->quit(); + }); + rejectJob2->start(); + }); + acceptJob1->start(); + }); + provideJob->start(); + }; + + // Step 1a: Login and upload keys for user1 + QObject::connect(loginManager1, + &LoginManager::loginSucceeded, + this, + [=, &user1Id, &user1Auth, &user1KeyPair](const QString &authToken, const QString &userId) { + user1Id = userId; + user1Auth = authToken; + uploadKeys(authToken, url, userId, password, networkManager, [&](const QString &, const EncryptionUtils::RSAKeyPair &keypair) { + user1KeyPair = keypair; + proceed(); + }); + }); + loginManager1->login(url, networkManager, 0); + + // Step 1b: Login and upload keys for user2 + QObject::connect(loginManager2, + &LoginManager::loginSucceeded, + this, + [=, &user2Id, &user2Auth, &user2KeyPair](const QString &authToken, const QString &userId) { + user2Id = userId; + user2Auth = authToken; + uploadKeys(authToken, url, userId, password, networkManager, [&](const QString &, const EncryptionUtils::RSAKeyPair &keypair) { + user2KeyPair = keypair; + proceed(); + }); + }); + loginManager2->login(url, networkManager, 1); + + // Handle login failures + QObject::connect(loginManager1, &LoginManager::loginFailed, this, [=](const QString &err) { + QFAIL(qPrintable(QStringLiteral("User1 login failed: %1").arg(err))); + app->quit(); + }); + QObject::connect(loginManager2, &LoginManager::loginFailed, this, [=](const QString &err) { + QFAIL(qPrintable(QStringLiteral("User2 login failed: %1").arg(err))); + app->quit(); + }); + + app->exec(); + QVERIFY(testPassed); +} + +#include "sessionkeydistributiontest.moc" \ No newline at end of file diff --git a/src/core/autotests/sessionkeydistributiontest.h b/src/core/autotests/sessionkeydistributiontest.h new file mode 100644 index 0000000000..dca1854ff3 --- /dev/null +++ b/src/core/autotests/sessionkeydistributiontest.h @@ -0,0 +1,37 @@ +/* + SPDX-FileCopyrightText: 2025 Andro Ranogajec + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#pragma once + +#include + +/** + * @class SessionKeyDistributionTest + * @brief Autotest for E2EE session key management and distribution in Rocket.Chat. + * + * This test verifies: + * - Session key encryption for multiple users using their public keys. + * + * - Distribution of encrypted session keys via the Rocket.Chat API. + * + * - Decryption of suggested keys with private keys. + * + * - Accepting and rejecting suggested group keys. + * + * - Proper assignment and verification of session keys. + * + * Prerequisites: + * + * - The .env file must contain credentials for at least two users (USERNAME1, PASSWORD1, USERNAME2, PASSWORD2). + * + * - The test room must exist and be accessible by both users. + */ +class SessionKeyDistributionTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testSessionKeyDistribution(); +}; \ No newline at end of file diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index b5cb3af7f7..8c496ba50f 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -248,6 +248,11 @@ QByteArray EncryptionUtils::generateSessionKey() return generateRandomIV(16); } +/** + * @brief Converts public key to an RSA. + * + * + */ RSA *EncryptionUtils::publicKeyFromPEM(const QByteArray &pem) { BIO *bio = BIO_new_mem_buf(pem.constData(), pem.size()); @@ -266,6 +271,11 @@ RSA *EncryptionUtils::publicKeyFromPEM(const QByteArray &pem) return rsa; } +/** + * @brief Converts private key to an RSA. + * + * + */ RSA *EncryptionUtils::privateKeyFromPEM(const QByteArray &pem) { BIO *bio = BIO_new_mem_buf(pem.constData(), pem.size()); diff --git a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp new file mode 100644 index 0000000000..430c5fa02d --- /dev/null +++ b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp @@ -0,0 +1,107 @@ +/* + SPDX-FileCopyrightText: 2025 Andor Ranogajec + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "provideuserswithsuggestedgroupkeysjob.h" +#include "restapimethod.h" +#include "rocketchatqtrestapi_debug.h" + +#include +#include +#include +using namespace RocketChatRestApi; +using namespace Qt::Literals::StringLiterals; + +ProvideUsersWithSuggestedGroupKeysJob::ProvideUsersWithSuggestedGroupKeysJob(QObject *parent) + : RestApiAbstractJob(parent) +{ +} + +ProvideUsersWithSuggestedGroupKeysJob::~ProvideUsersWithSuggestedGroupKeysJob() = default; + +void ProvideUsersWithSuggestedGroupKeysJob::setRoomId(const QString &roomId) +{ + mRoomId = roomId; +} + +void ProvideUsersWithSuggestedGroupKeysJob::setKeys(const QVector &keys) +{ + mKeys = keys; +} + +QString ProvideUsersWithSuggestedGroupKeysJob::roomId() const +{ + return mRoomId; +} + +QVector ProvideUsersWithSuggestedGroupKeysJob::keys() const +{ + return mKeys; +} + +bool ProvideUsersWithSuggestedGroupKeysJob::requireHttpAuthentication() const +{ + return true; +} + +bool ProvideUsersWithSuggestedGroupKeysJob::start() +{ + if (!canStart()) { + deleteLater(); + return false; + } + addStartRestApiInfo("ProvideUsersWithSuggestedGroupKeysJob::start"); + submitPostRequest(json()); + return true; +} + +void ProvideUsersWithSuggestedGroupKeysJob::onPostRequestResponse(const QString &replyErrorString, const QJsonDocument &replyJson) +{ + const QJsonObject replyObject = replyJson.object(); + + if (replyObject["success"_L1].toBool()) { + addLoggerInfo("ProvideUsersWithSuggestedGroupKeysJob: success: "_ba + replyJson.toJson(QJsonDocument::Indented)); + Q_EMIT provideUsersWithSuggestedGroupKeysDone(replyObject); + } else { + emitFailedMessage(replyErrorString, replyObject); + addLoggerWarning("ProvideUsersWithSuggestedGroupKeysJob: Problem: "_ba + replyJson.toJson(QJsonDocument::Indented)); + } +} + +QNetworkRequest ProvideUsersWithSuggestedGroupKeysJob::request() const +{ + const QUrl url = mRestApiMethod->generateUrl(RestApiUtil::RestApiUrlType::E2EProvideUsersWithSuggestedGroupKeys); + QNetworkRequest req(url); + addAuthRawHeader(req); + addRequestAttribute(req); + return req; +} + +QJsonDocument ProvideUsersWithSuggestedGroupKeysJob::json() const +{ + QJsonObject obj; + obj["rid"_L1] = mRoomId; + QJsonArray keysArr; + for (const auto &k : mKeys) { + QJsonObject keyObj; + keyObj["userId"_L1] = k.userId; + keyObj["key"_L1] = k.encryptedKey; + keysArr.append(keyObj); + } + obj["keys"_L1] = keysArr; + return QJsonDocument(obj); +} + +bool ProvideUsersWithSuggestedGroupKeysJob::canStart() const +{ + if (!RestApiAbstractJob::canStart()) { + return false; + } + if (mRoomId.isEmpty() || mKeys.isEmpty()) { + qCWarning(ROCKETCHATQTRESTAPI_LOG) << "ProvideUsersWithSuggestedGroupKeysJob: roomId or keys is empty"; + return false; + } + return true; +} \ No newline at end of file diff --git a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h new file mode 100644 index 0000000000..d3636b26ce --- /dev/null +++ b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h @@ -0,0 +1,58 @@ +/* + SPDX-FileCopyrightText: 2025 Andor Ranogajec + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#pragma once + +#include "restapiabstractjob.h" +#include +#include + +namespace RocketChatRestApi +{ + +/** + * QString userId; + * + *QString encryptedKey; + */ +struct SuggestedGroupKey { + QString userId; + QString encryptedKey; // base64 ???? +}; + +class ProvideUsersWithSuggestedGroupKeysJob : public RestApiAbstractJob +{ + Q_OBJECT +public: + explicit ProvideUsersWithSuggestedGroupKeysJob(QObject *parent = nullptr); + ~ProvideUsersWithSuggestedGroupKeysJob() override; + + void setRoomId(const QString &roomId); + void setKeys(const QVector &keys); + + QString roomId() const; + QVector keys() const; + + bool start() override; + QNetworkRequest request() const override; + QJsonDocument json() const; + + bool canStart() const override; + + bool requireHttpAuthentication() const override; + +protected: + void onPostRequestResponse(const QString &replyErrorString, const QJsonDocument &replyJson) override; + +Q_SIGNALS: + void provideUsersWithSuggestedGroupKeysDone(const QJsonObject &result); + +private: + QString mRoomId; + QVector mKeys; +}; + +} \ No newline at end of file From 56b85638f01c1f0282c879f7a1f88e75c847c5aa Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Wed, 20 Aug 2025 14:13:13 +0300 Subject: [PATCH 56/67] Fix '[[nodiscard]]' to make methods more explicit that they are returning values --- .../provideuserswithsuggestedgroupkeysjob.cpp | 1 - .../provideuserswithsuggestedgroupkeysjob.h | 18 ++++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp index 430c5fa02d..ad277db467 100644 --- a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp +++ b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp @@ -7,7 +7,6 @@ #include "provideuserswithsuggestedgroupkeysjob.h" #include "restapimethod.h" #include "rocketchatqtrestapi_debug.h" - #include #include #include diff --git a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h index d3636b26ce..390de72a06 100644 --- a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h +++ b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h @@ -33,16 +33,14 @@ class ProvideUsersWithSuggestedGroupKeysJob : public RestApiAbstractJob void setRoomId(const QString &roomId); void setKeys(const QVector &keys); - QString roomId() const; - QVector keys() const; - - bool start() override; - QNetworkRequest request() const override; - QJsonDocument json() const; - - bool canStart() const override; - - bool requireHttpAuthentication() const override; + [[nodiscard]] QString roomId() const; + [[nodiscard]] QVector keys() const; + [[nodiscard]] QNetworkRequest request() const override; + [[nodiscard]] QJsonDocument json() const; + + [[nodiscard]] bool start() override; + [[nodiscard]] bool canStart() const override; + [[nodiscard]] bool requireHttpAuthentication() const override; protected: void onPostRequestResponse(const QString &replyErrorString, const QJsonDocument &replyJson) override; From 7bacacf2e70cc2230fe82b626a72cfd82be9d255 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Wed, 20 Aug 2025 14:51:14 +0300 Subject: [PATCH 57/67] Added the export macro the class header to make it available outside of current library --- .../e2e/provideuserswithsuggestedgroupkeysjob.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h index 390de72a06..c74555bb34 100644 --- a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h +++ b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h @@ -6,6 +6,7 @@ #pragma once +#include "librocketchatrestapi-qt_export.h" #include "restapiabstractjob.h" #include #include @@ -23,7 +24,7 @@ struct SuggestedGroupKey { QString encryptedKey; // base64 ???? }; -class ProvideUsersWithSuggestedGroupKeysJob : public RestApiAbstractJob +class LIBROCKETCHATRESTAPI_QT_EXPORT ProvideUsersWithSuggestedGroupKeysJob : public RestApiAbstractJob { Q_OBJECT public: @@ -52,5 +53,4 @@ class ProvideUsersWithSuggestedGroupKeysJob : public RestApiAbstractJob QString mRoomId; QVector mKeys; }; - } \ No newline at end of file From 3f55e891ec9f2c6ab0965c779245ee34609f8e37 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Wed, 20 Aug 2025 15:33:15 +0300 Subject: [PATCH 58/67] Update the docs and coding style --- .../autotests/sessionkeydistributiontest.cpp | 32 +++++++++---------- .../autotests/sessionkeydistributiontest.h | 24 +++++++++----- src/core/encryption/encryptionutils.cpp | 13 +++++--- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/core/autotests/sessionkeydistributiontest.cpp b/src/core/autotests/sessionkeydistributiontest.cpp index 7aa4e2a5e4..c4045af031 100644 --- a/src/core/autotests/sessionkeydistributiontest.cpp +++ b/src/core/autotests/sessionkeydistributiontest.cpp @@ -32,8 +32,8 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() const auto roomId = QStringLiteral("123"); // Replace with a real room ID // Step 1: Login as two user + auto loginManager0 = new LoginManager(app); auto loginManager1 = new LoginManager(app); - auto loginManager2 = new LoginManager(app); QString user1Id, user2Id, user1Auth, user2Auth; EncryptionUtils::RSAKeyPair user1KeyPair, user2KeyPair; QByteArray sessionKey; @@ -50,13 +50,13 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() // Step 3: Encrypt session key with each user's public key QVector suggestedKeys; - QByteArray encryptedSessionKeyForUser1 = EncryptionUtils::encryptSessionKey(sessionKey, EncryptionUtils::publicKeyFromPEM(user1KeyPair.publicKey)); - QByteArray encryptedSessionKeyForUser2 = EncryptionUtils::encryptSessionKey(sessionKey, EncryptionUtils::publicKeyFromPEM(user2KeyPair.publicKey)); + const auto encryptedSessionKeyForUser1 = EncryptionUtils::encryptSessionKey(sessionKey, EncryptionUtils::publicKeyFromPEM(user1KeyPair.publicKey)); + const auto encryptedSessionKeyForUser2 = EncryptionUtils::encryptSessionKey(sessionKey, EncryptionUtils::publicKeyFromPEM(user2KeyPair.publicKey)); suggestedKeys.append({user1Id, encryptedSessionKeyForUser1.toBase64()}); suggestedKeys.append({user2Id, encryptedSessionKeyForUser2.toBase64()}); // Step 4: Distribute encrypted keys using API - auto provideJob = new RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob(app); + const auto provideJob = new RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob(app); provideJob->setRoomId(roomId); provideJob->setKeys(suggestedKeys); QObject::connect( @@ -65,19 +65,19 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() app, [&](const QJsonObject &) { // Simulate user1 receiving and accepting the key - QByteArray encKey1 = QByteArray::fromBase64(suggestedKeys[0].encryptedKey.toUtf8()); - QByteArray decKey1 = EncryptionUtils::decryptSessionKey(encKey1, EncryptionUtils::privateKeyFromPEM(user1KeyPair.privateKey)); + const auto encKey1 = QByteArray::fromBase64(suggestedKeys[0].encryptedKey.toUtf8()); + const auto decKey1 = EncryptionUtils::decryptSessionKey(encKey1, EncryptionUtils::privateKeyFromPEM(user1KeyPair.privateKey)); QCOMPARE(decKey1, sessionKey); - auto acceptJob1 = new RocketChatRestApi::AcceptSuggestedGroupKeyJob(app); + const auto acceptJob1 = new RocketChatRestApi::AcceptSuggestedGroupKeyJob(app); acceptJob1->setRoomId(roomId); QObject::connect(acceptJob1, &RocketChatRestApi::AcceptSuggestedGroupKeyJob::acceptSuggestedGroupKeyDone, app, [&](const QJsonObject &) { // Simulate user2 receiving and rejecting the key - QByteArray encKey2 = QByteArray::fromBase64(suggestedKeys[1].encryptedKey.toUtf8()); - QByteArray decKey2 = EncryptionUtils::decryptSessionKey(encKey2, EncryptionUtils::privateKeyFromPEM(user2KeyPair.privateKey)); + const auto encKey2 = QByteArray::fromBase64(suggestedKeys[1].encryptedKey.toUtf8()); + const auto decKey2 = EncryptionUtils::decryptSessionKey(encKey2, EncryptionUtils::privateKeyFromPEM(user2KeyPair.privateKey)); QCOMPARE(decKey2, sessionKey); - auto rejectJob2 = new RocketChatRestApi::RejectSuggestedGroupKeyJob(app); + const auto rejectJob2 = new RocketChatRestApi::RejectSuggestedGroupKeyJob(app); rejectJob2->setRoomId(roomId); QObject::connect(rejectJob2, &RocketChatRestApi::RejectSuggestedGroupKeyJob::rejectSuggestedGroupKeyDone, app, [&](const QJsonObject &) { testPassed = true; @@ -91,7 +91,7 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() }; // Step 1a: Login and upload keys for user1 - QObject::connect(loginManager1, + QObject::connect(loginManager0, &LoginManager::loginSucceeded, this, [=, &user1Id, &user1Auth, &user1KeyPair](const QString &authToken, const QString &userId) { @@ -102,10 +102,10 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() proceed(); }); }); - loginManager1->login(url, networkManager, 0); + loginManager0->login(url, networkManager, 0); // Step 1b: Login and upload keys for user2 - QObject::connect(loginManager2, + QObject::connect(loginManager1, &LoginManager::loginSucceeded, this, [=, &user2Id, &user2Auth, &user2KeyPair](const QString &authToken, const QString &userId) { @@ -116,14 +116,14 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() proceed(); }); }); - loginManager2->login(url, networkManager, 1); + loginManager1->login(url, networkManager, 1); // Handle login failures - QObject::connect(loginManager1, &LoginManager::loginFailed, this, [=](const QString &err) { + QObject::connect(loginManager0, &LoginManager::loginFailed, this, [=](const QString &err) { QFAIL(qPrintable(QStringLiteral("User1 login failed: %1").arg(err))); app->quit(); }); - QObject::connect(loginManager2, &LoginManager::loginFailed, this, [=](const QString &err) { + QObject::connect(loginManager1, &LoginManager::loginFailed, this, [=](const QString &err) { QFAIL(qPrintable(QStringLiteral("User2 login failed: %1").arg(err))); app->quit(); }); diff --git a/src/core/autotests/sessionkeydistributiontest.h b/src/core/autotests/sessionkeydistributiontest.h index dca1854ff3..3090bcd228 100644 --- a/src/core/autotests/sessionkeydistributiontest.h +++ b/src/core/autotests/sessionkeydistributiontest.h @@ -10,20 +10,28 @@ /** * @class SessionKeyDistributionTest - * @brief Autotest for E2EE session key management and distribution in Rocket.Chat. + * @brief Autotest for Rocket.Chat E2EE session key distribution and acceptance/rejection flows. * - * This test verifies: - * - Session key encryption for multiple users using their public keys. + * This test simulates two users in an end-to-end encrypted room: * - * - Distribution of encrypted session keys via the Rocket.Chat API. + * - User1: + * - Receives the suggested group (session) key. + * - Decrypts it with their private key. + * - Accepts it using AcceptSuggestedGroupKeyJob (acceptJob1). * - * - Decryption of suggested keys with private keys. + * - User2: + * - Receives the suggested group (session) key. + * - Decrypts it with their private key. + * - Rejects it using RejectSuggestedGroupKeyJob (rejectJob2). * - * - Accepting and rejecting suggested group keys. + * The test verifies: + * - Correct encryption and decryption of the session key for both users. + * - Proper API communication for distributing, accepting, and rejecting session keys. + * - That only users with the correct private key can decrypt the session key. + * - That the session key is correctly assigned or rejected in the users’ room subscriptions. * - * - Proper assignment and verification of session keys. * - * Prerequisites: + * * Prerequisites: * * - The .env file must contain credentials for at least two users (USERNAME1, PASSWORD1, USERNAME2, PASSWORD2). * diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 8c496ba50f..7b515c7646 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -243,14 +243,19 @@ QByteArray EncryptionUtils::getMasterKey(const QString &password, const QString #endif } +/** + * @brief Generates a random 16-byte (128-bit) session key for AES encryption. + * + * @return A QByteArray containing 16 random bytes suitable for use as an AES-128 session key. + */ QByteArray EncryptionUtils::generateSessionKey() { return generateRandomIV(16); } /** - * @brief Converts public key to an RSA. - * + * @brief Converts public key from QByteArray to RSA. + * @param QByteArray &pem * */ RSA *EncryptionUtils::publicKeyFromPEM(const QByteArray &pem) @@ -272,8 +277,8 @@ RSA *EncryptionUtils::publicKeyFromPEM(const QByteArray &pem) } /** - * @brief Converts private key to an RSA. - * + * @brief Converts private key from QByteArray to RSA. + * @param QByteArray &pem * */ RSA *EncryptionUtils::privateKeyFromPEM(const QByteArray &pem) From 4d4ca9c7122726c65ffce06ac888fbf902449dd1 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Wed, 20 Aug 2025 21:47:22 +0300 Subject: [PATCH 59/67] Session key distribution, code style fix, autotests for 'ProvideUsersWithSuggestedGroupKeysJob' --- .../autotests/sessionkeydistributiontest.cpp | 34 +++++++++++++++++++ .../autotests/sessionkeydistributiontest.h | 2 ++ .../provideuserswithsuggestedgroupkeysjob.cpp | 8 ++--- .../provideuserswithsuggestedgroupkeysjob.h | 2 +- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/core/autotests/sessionkeydistributiontest.cpp b/src/core/autotests/sessionkeydistributiontest.cpp index c4045af031..4b332d56a8 100644 --- a/src/core/autotests/sessionkeydistributiontest.cpp +++ b/src/core/autotests/sessionkeydistributiontest.cpp @@ -12,6 +12,9 @@ #include "loginmanager.h" #include "uploaddownloadrsakeypair.h" #include +#include +#include +#include #include #include QTEST_GUILESS_MAIN(SessionKeyDistributionTest) @@ -21,6 +24,8 @@ class SessionKeyDistributionTest : public QObject Q_OBJECT private Q_SLOTS: void testSessionKeyDistribution(); + void testJsonPayload(); + void testCanStartValidation(); }; void SessionKeyDistributionTest::testSessionKeyDistribution() @@ -132,4 +137,33 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() QVERIFY(testPassed); } +void SessionKeyDistributionTest::testJsonPayload() +{ + RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob job; + job.setRoomId("123"); + const QVector suggestedGroupKeys = {{"userA", "base64keyA"}, {"userB", "base64keyB"}}; + job.setKeys(suggestedGroupKeys); + + const QJsonDocument doc = job.json(); + const QJsonObject obj = doc.object(); + QCOMPARE(obj["rid"].toString(), QStringLiteral("123")); + QJsonArray arr = obj["keys"].toArray(); + QCOMPARE(arr.size(), 2); + QCOMPARE(arr[0].toObject()["userId"].toString(), QStringLiteral("userA")); + QCOMPARE(arr[0].toObject()["key"].toString(), QStringLiteral("base64keyA")); +} + +void SessionKeyDistributionTest::testCanStartValidation() +{ + RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob job; + QVERIFY(!job.canStart()); // No roomId or keys + + job.setRoomId("room123"); + QVERIFY(!job.canStart()); // No keys + + QVector keys = {{"userA", "base64keyA"}}; + job.setKeys(keys); + QVERIFY(job.canStart()); // Now valid +} + #include "sessionkeydistributiontest.moc" \ No newline at end of file diff --git a/src/core/autotests/sessionkeydistributiontest.h b/src/core/autotests/sessionkeydistributiontest.h index 3090bcd228..e892cfeb30 100644 --- a/src/core/autotests/sessionkeydistributiontest.h +++ b/src/core/autotests/sessionkeydistributiontest.h @@ -42,4 +42,6 @@ class SessionKeyDistributionTest : public QObject Q_OBJECT private Q_SLOTS: void testSessionKeyDistribution(); + void testJsonPayload(); + void testCanStartValidation(); }; \ No newline at end of file diff --git a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp index ad277db467..00b80d15de 100644 --- a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp +++ b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.cpp @@ -27,7 +27,7 @@ void ProvideUsersWithSuggestedGroupKeysJob::setRoomId(const QString &roomId) void ProvideUsersWithSuggestedGroupKeysJob::setKeys(const QVector &keys) { - mKeys = keys; + mSuggestedGroupKeys = keys; } QString ProvideUsersWithSuggestedGroupKeysJob::roomId() const @@ -37,7 +37,7 @@ QString ProvideUsersWithSuggestedGroupKeysJob::roomId() const QVector ProvideUsersWithSuggestedGroupKeysJob::keys() const { - return mKeys; + return mSuggestedGroupKeys; } bool ProvideUsersWithSuggestedGroupKeysJob::requireHttpAuthentication() const @@ -83,7 +83,7 @@ QJsonDocument ProvideUsersWithSuggestedGroupKeysJob::json() const QJsonObject obj; obj["rid"_L1] = mRoomId; QJsonArray keysArr; - for (const auto &k : mKeys) { + for (const auto &k : mSuggestedGroupKeys) { QJsonObject keyObj; keyObj["userId"_L1] = k.userId; keyObj["key"_L1] = k.encryptedKey; @@ -98,7 +98,7 @@ bool ProvideUsersWithSuggestedGroupKeysJob::canStart() const if (!RestApiAbstractJob::canStart()) { return false; } - if (mRoomId.isEmpty() || mKeys.isEmpty()) { + if (mRoomId.isEmpty() || mSuggestedGroupKeys.isEmpty()) { qCWarning(ROCKETCHATQTRESTAPI_LOG) << "ProvideUsersWithSuggestedGroupKeysJob: roomId or keys is empty"; return false; } diff --git a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h index c74555bb34..d3a098f30a 100644 --- a/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h +++ b/src/rocketchatrestapi-qt/e2e/provideuserswithsuggestedgroupkeysjob.h @@ -51,6 +51,6 @@ class LIBROCKETCHATRESTAPI_QT_EXPORT ProvideUsersWithSuggestedGroupKeysJob : pub private: QString mRoomId; - QVector mKeys; + QVector mSuggestedGroupKeys; }; } \ No newline at end of file From f8e117731b2f616c9f63c212c51b2d1685d74d16 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Thu, 21 Aug 2025 20:00:06 +0300 Subject: [PATCH 60/67] Remove duplicate class, fix compile with strict compile flags, add conditions --- src/core/autotests/sessionkeydistributiontest.cpp | 13 ++----------- tests/encryptiontest/encryptiontestgui.cpp | 2 ++ 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/core/autotests/sessionkeydistributiontest.cpp b/src/core/autotests/sessionkeydistributiontest.cpp index 4b332d56a8..3de40f1531 100644 --- a/src/core/autotests/sessionkeydistributiontest.cpp +++ b/src/core/autotests/sessionkeydistributiontest.cpp @@ -19,15 +19,6 @@ #include QTEST_GUILESS_MAIN(SessionKeyDistributionTest) -class SessionKeyDistributionTest : public QObject -{ - Q_OBJECT -private Q_SLOTS: - void testSessionKeyDistribution(); - void testJsonPayload(); - void testCanStartValidation(); -}; - void SessionKeyDistributionTest::testSessionKeyDistribution() { const auto app = QCoreApplication::instance(); @@ -57,8 +48,8 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() QVector suggestedKeys; const auto encryptedSessionKeyForUser1 = EncryptionUtils::encryptSessionKey(sessionKey, EncryptionUtils::publicKeyFromPEM(user1KeyPair.publicKey)); const auto encryptedSessionKeyForUser2 = EncryptionUtils::encryptSessionKey(sessionKey, EncryptionUtils::publicKeyFromPEM(user2KeyPair.publicKey)); - suggestedKeys.append({user1Id, encryptedSessionKeyForUser1.toBase64()}); - suggestedKeys.append({user2Id, encryptedSessionKeyForUser2.toBase64()}); + suggestedKeys.append({user1Id, QString::fromLatin1(encryptedSessionKeyForUser1.toBase64())}); + suggestedKeys.append({user2Id, QString::fromLatin1(encryptedSessionKeyForUser2.toBase64())}); // Step 4: Distribute encrypted keys using API const auto provideJob = new RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob(app); diff --git a/tests/encryptiontest/encryptiontestgui.cpp b/tests/encryptiontest/encryptiontestgui.cpp index 29ddfc85ad..41051de1aa 100644 --- a/tests/encryptiontest/encryptiontestgui.cpp +++ b/tests/encryptiontest/encryptiontestgui.cpp @@ -146,6 +146,8 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) const auto text = mTextEdit->toPlainText(); if (text.isEmpty()) { mTextEditResult->setPlainText(QStringLiteral("Text cannot be null, message encryption failed!\n")); + } else if (!text.isEmpty() && mSessionKey.isEmpty()) { + mTextEditResult->setPlainText(QStringLiteral("Session key is empty, message encryption failed!\n")); } else { mEncryptedMessage = EncryptionUtils::encryptMessage(text.toUtf8(), mSessionKey); qDebug() << "Encrypted message:" << mEncryptedMessage.toBase64(); From 41932a76e1ee232d2020876b4171f69c3287f430 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Thu, 21 Aug 2025 20:01:49 +0300 Subject: [PATCH 61/67] Build provideuserswithsuggestedgroupkeysjob.cpp --- src/rocketchatrestapi-qt/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rocketchatrestapi-qt/CMakeLists.txt b/src/rocketchatrestapi-qt/CMakeLists.txt index 41e18fa729..a656095d71 100644 --- a/src/rocketchatrestapi-qt/CMakeLists.txt +++ b/src/rocketchatrestapi-qt/CMakeLists.txt @@ -182,6 +182,8 @@ target_sources(librocketchatrestapi-qt PRIVATE e2e/acceptsuggestedgroupkeyjob.cpp e2e/acceptsuggestedgroupkeyjob.h + e2e/provideuserswithsuggestedgroupkeysjob.cpp + e2e/provideuserswithsuggestedgroupkeysjob.h e2e/rejectsuggestedgroupkeyjob.cpp e2e/rejectsuggestedgroupkeyjob.h e2e/resetroomkeyjob.cpp From 8fb106d90e9acdd7e67ab2373f5d23d8cab946ca Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Thu, 21 Aug 2025 23:03:18 +0300 Subject: [PATCH 62/67] WIP session key distribution integration test segmentation fail --- src/core/autotests/CMakeLists.txt | 11 +++ .../autotests/sessionkeydistributiontest.cpp | 93 +++++++++++-------- 2 files changed, 67 insertions(+), 37 deletions(-) diff --git a/src/core/autotests/CMakeLists.txt b/src/core/autotests/CMakeLists.txt index 8fcd1ff642..49af9502b1 100644 --- a/src/core/autotests/CMakeLists.txt +++ b/src/core/autotests/CMakeLists.txt @@ -174,6 +174,17 @@ if(USE_E2E_SUPPORT) add_ruqola_test(uploaddownloadrsakeypairtest.cpp) add_ruqola_test(sessionkeydistributiontest.cpp) + +target_sources(sessionkeydistributiontest PRIVATE + ${CMAKE_SOURCE_DIR}/tests/encryptiontest/loginmanager.cpp + ${CMAKE_SOURCE_DIR}/tests/encryptiontest/envutils.cpp + ${CMAKE_SOURCE_DIR}/tests/encryptiontest/uploaddownloadrsakeypair.cpp +) + +target_include_directories(sessionkeydistributiontest PRIVATE + ${CMAKE_SOURCE_DIR}/tests/encryptiontest + ${CMAKE_SOURCE_DIR}/src/core/encryption) + target_include_directories(uploaddownloadrsakeypairtest PRIVATE ${CMAKE_SOURCE_DIR}/tests/encryptiontest ${CMAKE_SOURCE_DIR}/src/core/encryption diff --git a/src/core/autotests/sessionkeydistributiontest.cpp b/src/core/autotests/sessionkeydistributiontest.cpp index 3de40f1531..1ec6f89fb8 100644 --- a/src/core/autotests/sessionkeydistributiontest.cpp +++ b/src/core/autotests/sessionkeydistributiontest.cpp @@ -37,7 +37,7 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() // Helper: proceed when both users are ready int readyCount = 0; - auto proceed = [&]() { + auto proceed = [=, &readyCount, &sessionKey, &testPassed]() { if (++readyCount < 2) return; @@ -52,37 +52,55 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() suggestedKeys.append({user2Id, QString::fromLatin1(encryptedSessionKeyForUser2.toBase64())}); // Step 4: Distribute encrypted keys using API + const auto provideMethod = new RocketChatRestApi::RestApiMethod; + provideMethod->setServerUrl(url); const auto provideJob = new RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob(app); + provideJob->setNetworkAccessManager(networkManager); + provideJob->setRestApiMethod(provideMethod); provideJob->setRoomId(roomId); provideJob->setKeys(suggestedKeys); - QObject::connect( - provideJob, - &RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob::provideUsersWithSuggestedGroupKeysDone, - app, - [&](const QJsonObject &) { - // Simulate user1 receiving and accepting the key - const auto encKey1 = QByteArray::fromBase64(suggestedKeys[0].encryptedKey.toUtf8()); - const auto decKey1 = EncryptionUtils::decryptSessionKey(encKey1, EncryptionUtils::privateKeyFromPEM(user1KeyPair.privateKey)); - QCOMPARE(decKey1, sessionKey); - - const auto acceptJob1 = new RocketChatRestApi::AcceptSuggestedGroupKeyJob(app); - acceptJob1->setRoomId(roomId); - QObject::connect(acceptJob1, &RocketChatRestApi::AcceptSuggestedGroupKeyJob::acceptSuggestedGroupKeyDone, app, [&](const QJsonObject &) { - // Simulate user2 receiving and rejecting the key - const auto encKey2 = QByteArray::fromBase64(suggestedKeys[1].encryptedKey.toUtf8()); - const auto decKey2 = EncryptionUtils::decryptSessionKey(encKey2, EncryptionUtils::privateKeyFromPEM(user2KeyPair.privateKey)); - QCOMPARE(decKey2, sessionKey); - - const auto rejectJob2 = new RocketChatRestApi::RejectSuggestedGroupKeyJob(app); - rejectJob2->setRoomId(roomId); - QObject::connect(rejectJob2, &RocketChatRestApi::RejectSuggestedGroupKeyJob::rejectSuggestedGroupKeyDone, app, [&](const QJsonObject &) { - testPassed = true; - app->quit(); - }); - rejectJob2->start(); - }); - acceptJob1->start(); - }); + QObject::connect(provideJob, + &RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob::provideUsersWithSuggestedGroupKeysDone, + app, + [=, &testPassed](const QJsonObject &) { + // Simulate user1 receiving and accepting the key + const auto encKey1 = QByteArray::fromBase64(suggestedKeys[0].encryptedKey.toUtf8()); + const auto decKey1 = EncryptionUtils::decryptSessionKey(encKey1, EncryptionUtils::privateKeyFromPEM(user1KeyPair.privateKey)); + QCOMPARE(decKey1, sessionKey); + + const auto acceptMethod = new RocketChatRestApi::RestApiMethod; + acceptMethod->setServerUrl(url); + const auto acceptJob1 = new RocketChatRestApi::AcceptSuggestedGroupKeyJob(app); + acceptJob1->setRestApiMethod(acceptMethod); + acceptJob1->setNetworkAccessManager(networkManager); + acceptJob1->setRoomId(roomId); + QObject::connect(acceptJob1, + &RocketChatRestApi::AcceptSuggestedGroupKeyJob::acceptSuggestedGroupKeyDone, + app, + [=, &testPassed](const QJsonObject &) { + // Simulate user2 receiving and rejecting the key + const auto encKey2 = QByteArray::fromBase64(suggestedKeys[1].encryptedKey.toUtf8()); + const auto decKey2 = + EncryptionUtils::decryptSessionKey(encKey2, EncryptionUtils::privateKeyFromPEM(user2KeyPair.privateKey)); + QCOMPARE(decKey2, sessionKey); + + const auto rejectJob2 = new RocketChatRestApi::RejectSuggestedGroupKeyJob(app); + auto rejectMethod = new RocketChatRestApi::RestApiMethod; + rejectMethod->setServerUrl(url); + rejectJob2->setRestApiMethod(rejectMethod); + rejectJob2->setNetworkAccessManager(networkManager); + rejectJob2->setRoomId(roomId); + QObject::connect(rejectJob2, + &RocketChatRestApi::RejectSuggestedGroupKeyJob::rejectSuggestedGroupKeyDone, + app, + [&](const QJsonObject &) { + testPassed = true; + app->quit(); + }); + rejectJob2->start(); + }); + acceptJob1->start(); + }); provideJob->start(); }; @@ -131,17 +149,18 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() void SessionKeyDistributionTest::testJsonPayload() { RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob job; - job.setRoomId("123"); - const QVector suggestedGroupKeys = {{"userA", "base64keyA"}, {"userB", "base64keyB"}}; + job.setRoomId(QStringLiteral("123")); + const QVector suggestedGroupKeys = {{QStringLiteral("userA"), QStringLiteral("base64keyA")}, + {QStringLiteral("userB"), QStringLiteral("base64keyB")}}; job.setKeys(suggestedGroupKeys); const QJsonDocument doc = job.json(); const QJsonObject obj = doc.object(); - QCOMPARE(obj["rid"].toString(), QStringLiteral("123")); - QJsonArray arr = obj["keys"].toArray(); + QCOMPARE(obj[QStringLiteral("rid")].toString(), QStringLiteral("123")); + QJsonArray arr = obj[QStringLiteral("keys")].toArray(); QCOMPARE(arr.size(), 2); - QCOMPARE(arr[0].toObject()["userId"].toString(), QStringLiteral("userA")); - QCOMPARE(arr[0].toObject()["key"].toString(), QStringLiteral("base64keyA")); + QCOMPARE(arr[0].toObject()[QStringLiteral("userId")].toString(), QStringLiteral("userA")); + QCOMPARE(arr[0].toObject()[QStringLiteral("key")].toString(), QStringLiteral("base64keyA")); } void SessionKeyDistributionTest::testCanStartValidation() @@ -149,10 +168,10 @@ void SessionKeyDistributionTest::testCanStartValidation() RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob job; QVERIFY(!job.canStart()); // No roomId or keys - job.setRoomId("room123"); + job.setRoomId(QStringLiteral("room123")); QVERIFY(!job.canStart()); // No keys - QVector keys = {{"userA", "base64keyA"}}; + QVector keys = {{QStringLiteral("userA"), QStringLiteral("base64keyA")}}; job.setKeys(keys); QVERIFY(job.canStart()); // Now valid } From a8739920a721540da58dfaf2b889e3891153c784 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 22 Aug 2025 01:26:44 +0300 Subject: [PATCH 63/67] WIP 'auth settings is empty' --- .../autotests/sessionkeydistributiontest.cpp | 139 +++++++++--------- .../autotests/sessionkeydistributiontest.h | 4 +- 2 files changed, 69 insertions(+), 74 deletions(-) diff --git a/src/core/autotests/sessionkeydistributiontest.cpp b/src/core/autotests/sessionkeydistributiontest.cpp index 1ec6f89fb8..8811affc8d 100644 --- a/src/core/autotests/sessionkeydistributiontest.cpp +++ b/src/core/autotests/sessionkeydistributiontest.cpp @@ -18,6 +18,10 @@ #include #include QTEST_GUILESS_MAIN(SessionKeyDistributionTest) +SessionKeyDistributionTest::SessionKeyDistributionTest(QObject *parent) + : QObject(parent) +{ +} void SessionKeyDistributionTest::testSessionKeyDistribution() { @@ -28,24 +32,25 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() const auto roomId = QStringLiteral("123"); // Replace with a real room ID // Step 1: Login as two user - auto loginManager0 = new LoginManager(app); - auto loginManager1 = new LoginManager(app); + const auto loginManager0 = new LoginManager(app); + const auto loginManager1 = new LoginManager(app); QString user1Id, user2Id, user1Auth, user2Auth; - EncryptionUtils::RSAKeyPair user1KeyPair, user2KeyPair; - QByteArray sessionKey; + auto user1KeyPair = EncryptionUtils::RSAKeyPair(); + auto user2KeyPair = EncryptionUtils::RSAKeyPair(); + + // Step 2: Generate session key (AES-128) + const auto sessionKey = EncryptionUtils::generateSessionKey(); auto testPassed = false; - // Helper: proceed when both users are ready + // Step 2.5 Helper: proceed when both users are ready int readyCount = 0; - auto proceed = [=, &readyCount, &sessionKey, &testPassed]() { + auto proceed = [&]() { if (++readyCount < 2) return; - // Step 2: Generate session key (AES-128) - sessionKey = EncryptionUtils::generateSessionKey(); - // Step 3: Encrypt session key with each user's public key QVector suggestedKeys; + qDebug() << "user1KeyPair.publicKey size:" << user1KeyPair.publicKey.size(); const auto encryptedSessionKeyForUser1 = EncryptionUtils::encryptSessionKey(sessionKey, EncryptionUtils::publicKeyFromPEM(user1KeyPair.publicKey)); const auto encryptedSessionKeyForUser2 = EncryptionUtils::encryptSessionKey(sessionKey, EncryptionUtils::publicKeyFromPEM(user2KeyPair.publicKey)); suggestedKeys.append({user1Id, QString::fromLatin1(encryptedSessionKeyForUser1.toBase64())}); @@ -59,77 +64,65 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() provideJob->setRestApiMethod(provideMethod); provideJob->setRoomId(roomId); provideJob->setKeys(suggestedKeys); - QObject::connect(provideJob, - &RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob::provideUsersWithSuggestedGroupKeysDone, - app, - [=, &testPassed](const QJsonObject &) { - // Simulate user1 receiving and accepting the key - const auto encKey1 = QByteArray::fromBase64(suggestedKeys[0].encryptedKey.toUtf8()); - const auto decKey1 = EncryptionUtils::decryptSessionKey(encKey1, EncryptionUtils::privateKeyFromPEM(user1KeyPair.privateKey)); - QCOMPARE(decKey1, sessionKey); - - const auto acceptMethod = new RocketChatRestApi::RestApiMethod; - acceptMethod->setServerUrl(url); - const auto acceptJob1 = new RocketChatRestApi::AcceptSuggestedGroupKeyJob(app); - acceptJob1->setRestApiMethod(acceptMethod); - acceptJob1->setNetworkAccessManager(networkManager); - acceptJob1->setRoomId(roomId); - QObject::connect(acceptJob1, - &RocketChatRestApi::AcceptSuggestedGroupKeyJob::acceptSuggestedGroupKeyDone, - app, - [=, &testPassed](const QJsonObject &) { - // Simulate user2 receiving and rejecting the key - const auto encKey2 = QByteArray::fromBase64(suggestedKeys[1].encryptedKey.toUtf8()); - const auto decKey2 = - EncryptionUtils::decryptSessionKey(encKey2, EncryptionUtils::privateKeyFromPEM(user2KeyPair.privateKey)); - QCOMPARE(decKey2, sessionKey); - - const auto rejectJob2 = new RocketChatRestApi::RejectSuggestedGroupKeyJob(app); - auto rejectMethod = new RocketChatRestApi::RestApiMethod; - rejectMethod->setServerUrl(url); - rejectJob2->setRestApiMethod(rejectMethod); - rejectJob2->setNetworkAccessManager(networkManager); - rejectJob2->setRoomId(roomId); - QObject::connect(rejectJob2, - &RocketChatRestApi::RejectSuggestedGroupKeyJob::rejectSuggestedGroupKeyDone, - app, - [&](const QJsonObject &) { - testPassed = true; - app->quit(); - }); - rejectJob2->start(); - }); - acceptJob1->start(); - }); + QObject::connect( + provideJob, + &RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob::provideUsersWithSuggestedGroupKeysDone, + app, + [&](const QJsonObject &) { + // Simulate user1 receiving and accepting the key + const auto encKey1 = QByteArray::fromBase64(suggestedKeys[0].encryptedKey.toUtf8()); + const auto decKey1 = EncryptionUtils::decryptSessionKey(encKey1, EncryptionUtils::privateKeyFromPEM(user1KeyPair.privateKey)); + QCOMPARE(decKey1, sessionKey); + + const auto acceptMethod = new RocketChatRestApi::RestApiMethod; + acceptMethod->setServerUrl(url); + const auto acceptJob1 = new RocketChatRestApi::AcceptSuggestedGroupKeyJob(app); + acceptJob1->setRestApiMethod(acceptMethod); + acceptJob1->setNetworkAccessManager(networkManager); + acceptJob1->setRoomId(roomId); + QObject::connect(acceptJob1, &RocketChatRestApi::AcceptSuggestedGroupKeyJob::acceptSuggestedGroupKeyDone, app, [&](const QJsonObject &) { + // Simulate user2 receiving and rejecting the key + const auto encKey2 = QByteArray::fromBase64(suggestedKeys[1].encryptedKey.toUtf8()); + const auto decKey2 = EncryptionUtils::decryptSessionKey(encKey2, EncryptionUtils::privateKeyFromPEM(user2KeyPair.privateKey)); + QCOMPARE(decKey2, sessionKey); + + const auto rejectJob2 = new RocketChatRestApi::RejectSuggestedGroupKeyJob(app); + const auto rejectMethod = new RocketChatRestApi::RestApiMethod; + rejectMethod->setServerUrl(url); + rejectJob2->setRestApiMethod(rejectMethod); + rejectJob2->setNetworkAccessManager(networkManager); + rejectJob2->setRoomId(roomId); + QObject::connect(rejectJob2, &RocketChatRestApi::RejectSuggestedGroupKeyJob::rejectSuggestedGroupKeyDone, app, [&](const QJsonObject &) { + testPassed = true; + app->quit(); + }); + rejectJob2->start(); + }); + acceptJob1->start(); + }); provideJob->start(); }; // Step 1a: Login and upload keys for user1 - QObject::connect(loginManager0, - &LoginManager::loginSucceeded, - this, - [=, &user1Id, &user1Auth, &user1KeyPair](const QString &authToken, const QString &userId) { - user1Id = userId; - user1Auth = authToken; - uploadKeys(authToken, url, userId, password, networkManager, [&](const QString &, const EncryptionUtils::RSAKeyPair &keypair) { - user1KeyPair = keypair; - proceed(); - }); - }); + QObject::connect(loginManager0, &LoginManager::loginSucceeded, this, [&](const QString &authToken, const QString &userId) { + user1Id = userId; + user1Auth = authToken; + uploadKeys(authToken, url, userId, password, networkManager, [&](const QString &, const EncryptionUtils::RSAKeyPair &keypair) { + user1KeyPair = keypair; + proceed(); + }); + }); loginManager0->login(url, networkManager, 0); // Step 1b: Login and upload keys for user2 - QObject::connect(loginManager1, - &LoginManager::loginSucceeded, - this, - [=, &user2Id, &user2Auth, &user2KeyPair](const QString &authToken, const QString &userId) { - user2Id = userId; - user2Auth = authToken; - uploadKeys(authToken, url, userId, password, networkManager, [&](const QString &, const EncryptionUtils::RSAKeyPair &keypair) { - user2KeyPair = keypair; - proceed(); - }); - }); + QObject::connect(loginManager1, &LoginManager::loginSucceeded, this, [&](const QString &authToken, const QString &userId) { + user2Id = userId; + user2Auth = authToken; + uploadKeys(authToken, url, userId, password, networkManager, [&](const QString &, const EncryptionUtils::RSAKeyPair &keypair) { + user2KeyPair = keypair; + proceed(); + }); + }); loginManager1->login(url, networkManager, 1); // Handle login failures diff --git a/src/core/autotests/sessionkeydistributiontest.h b/src/core/autotests/sessionkeydistributiontest.h index e892cfeb30..fdcab41c4c 100644 --- a/src/core/autotests/sessionkeydistributiontest.h +++ b/src/core/autotests/sessionkeydistributiontest.h @@ -5,7 +5,6 @@ */ #pragma once - #include /** @@ -40,6 +39,9 @@ class SessionKeyDistributionTest : public QObject { Q_OBJECT +public: + explicit SessionKeyDistributionTest(QObject *parent = nullptr); + ~SessionKeyDistributionTest() override = default; private Q_SLOTS: void testSessionKeyDistribution(); void testJsonPayload(); From 282fd7ec20e34018d8f0f94138e59e3a732152e3 Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Fri, 22 Aug 2025 13:03:44 +0300 Subject: [PATCH 64/67] Rm integration tests, keep autotests and update --- .../autotests/sessionkeydistributiontest.cpp | 17 ++++++++++++----- src/core/autotests/sessionkeydistributiontest.h | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/core/autotests/sessionkeydistributiontest.cpp b/src/core/autotests/sessionkeydistributiontest.cpp index 8811affc8d..d89dfbcc25 100644 --- a/src/core/autotests/sessionkeydistributiontest.cpp +++ b/src/core/autotests/sessionkeydistributiontest.cpp @@ -23,7 +23,7 @@ SessionKeyDistributionTest::SessionKeyDistributionTest(QObject *parent) { } -void SessionKeyDistributionTest::testSessionKeyDistribution() +/* void SessionKeyDistributionTest::testSessionKeyDistribution() { const auto app = QCoreApplication::instance(); const auto networkManager = new QNetworkAccessManager(app); @@ -137,7 +137,7 @@ void SessionKeyDistributionTest::testSessionKeyDistribution() app->exec(); QVERIFY(testPassed); -} +} */ void SessionKeyDistributionTest::testJsonPayload() { @@ -159,14 +159,21 @@ void SessionKeyDistributionTest::testJsonPayload() void SessionKeyDistributionTest::testCanStartValidation() { RocketChatRestApi::ProvideUsersWithSuggestedGroupKeysJob job; - QVERIFY(!job.canStart()); // No roomId or keys + const auto networkManager = new QNetworkAccessManager(this); + job.setNetworkAccessManager(networkManager); + job.setAuthToken(QStringLiteral("dummyToken")); + job.setUserId(QStringLiteral("dummyUserId")); + const auto restApiMethod = new RocketChatRestApi::RestApiMethod; + restApiMethod->setServerUrl(QStringLiteral("http://localhost:3000")); + job.setRestApiMethod(restApiMethod); + QVERIFY(!job.canStart()); job.setRoomId(QStringLiteral("room123")); - QVERIFY(!job.canStart()); // No keys + QVERIFY(!job.canStart()); QVector keys = {{QStringLiteral("userA"), QStringLiteral("base64keyA")}}; job.setKeys(keys); - QVERIFY(job.canStart()); // Now valid + QVERIFY(job.canStart()); } #include "sessionkeydistributiontest.moc" \ No newline at end of file diff --git a/src/core/autotests/sessionkeydistributiontest.h b/src/core/autotests/sessionkeydistributiontest.h index fdcab41c4c..19a10f96c0 100644 --- a/src/core/autotests/sessionkeydistributiontest.h +++ b/src/core/autotests/sessionkeydistributiontest.h @@ -43,7 +43,7 @@ class SessionKeyDistributionTest : public QObject explicit SessionKeyDistributionTest(QObject *parent = nullptr); ~SessionKeyDistributionTest() override = default; private Q_SLOTS: - void testSessionKeyDistribution(); + // void testSessionKeyDistribution(); void testJsonPayload(); void testCanStartValidation(); }; \ No newline at end of file From 8230c9dc70c4a278005a3177bd80fcccb2c9e0db Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Tue, 16 Sep 2025 18:39:46 +0300 Subject: [PATCH 65/67] Export RSA public key and docstrings and UI update --- src/core/encryption/encryptionutils.cpp | 125 +++++++++++++++++---- src/core/encryption/encryptionutils.h | 2 +- tests/encryptiontest/encryptiontestgui.cpp | 8 ++ 3 files changed, 111 insertions(+), 24 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 7b515c7646..43b0bb0372 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -11,31 +11,56 @@ // https://docs.rocket.chat/customer-center/security-center/end-to-end-encryption-specifications #include +#include #include #include #include using namespace Qt::Literals::StringLiterals; -QByteArray EncryptionUtils::exportJWKKey(RSA *rsaKey) -{ -#if 0 - code javascript - const key = await crypto.subtle.generateKey( - { name: 'AES-CBC', length: 256 }, - true, - ['encrypt', 'decrypt'] - ); - - const jwkKey = await exportJWKKey(key); - console.log(jwkKey); - -#endif +/** + * @brief Exports an RSA public key in JWK (JSON Web Key) format. + * + * This function extracts the modulus and public exponent from the given OpenSSL RSA key, + * encodes them using base64url (without padding), and constructs a JWK-compliant JSON object. + * The resulting JSON contains all fields required for interoperability with the Web Crypto API, + * and is returned as a compact UTF-8 encoded QByteArray. + * + * @param rsaKey Pointer to the OpenSSL RSA key. + * @return A QByteArray containing the JWK JSON representation of the public key, + * or an empty QByteArray on error. + * + * Example output: + * + * { + * "kty": "RSA", + * + * "n": "", + * + * "e": "", + * + * "alg": "RSA-OAEP-256", + * + * "key_ops": ["encrypt"], + * + * "ext": true + * } + * + * General steps of encoding/decoding for E2EE of the RSA public key part: + * + * use generateRsaKey() => QByteArray(PEM) + * + * use publicKeyFromPEM() => RSA(QByteArray(PEM)) + * + * use exportJWKPublicKey() => JWK(RSA) + */ +QByteArray EncryptionUtils::exportJWKPublicKey(RSA *rsaKey) +{ const BIGNUM *n, *e, *d; RSA_get0_key(rsaKey, &n, &e, &d); if (!n || !e) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << " Impossible to get RSA"; + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Impossible to get RSA"; return {}; } @@ -49,9 +74,12 @@ QByteArray EncryptionUtils::exportJWKKey(RSA *rsaKey) const QString eBase64Url = QString::fromLatin1(eBytes.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); QJsonObject jwkObj; - jwkObj["kty"_L1] = "RSA"_L1; - jwkObj["n"_L1] = nBase64Url; - jwkObj["e"_L1] = eBase64Url; + jwkObj[QStringLiteral("kty")] = QStringLiteral("RSA"); + jwkObj[QStringLiteral("n")] = nBase64Url; + jwkObj[QStringLiteral("e")] = eBase64Url; + jwkObj[QStringLiteral("alg")] = QStringLiteral("RSA-OAEP-256"); + jwkObj[QStringLiteral("key_ops")] = QJsonArray() << QStringLiteral("encrypt"); + jwkObj[QStringLiteral("ext")] = true; QJsonDocument doc(jwkObj); return doc.toJson(QJsonDocument::Compact); @@ -192,7 +220,7 @@ QByteArray EncryptionUtils::decryptPrivateKey(const QByteArray &encryptedPrivate * The master key is used to encrypt and decrypt the user's private RSA key. * * @param password The user's E2EE password. - * @param salt user's unique identifier (used as salt). + * @param salt user's unique identifier, sometimes called pepper if its constant. * @return A 32-byte (256-bit) master key as a QByteArray, or an empty QByteArray on failure. */ QByteArray EncryptionUtils::getMasterKey(const QString &password, const QString &salt) @@ -203,7 +231,7 @@ QByteArray EncryptionUtils::getMasterKey(const QString &password, const QString } if (salt.isEmpty()) { - qCWarning(RUQOLA_ENCRYPTION_LOG) << "Salt(username) can't be null. It's a bug"; + qCWarning(RUQOLA_ENCRYPTION_LOG) << "Salt(userId) can't be null. It's a bug"; return {}; } @@ -598,15 +626,27 @@ QString EncryptionUtils::generateRandomText(int length) return randomText; } -QByteArray EncryptionUtils::deriveKey(const QByteArray &salt, const QByteArray &baseKey, int iterations, int keyLength) +/** + * @brief Derives a cryptographic key using PBKDF2 (Password-Based Key Derivation Function 2). + * + * This function uses OpenSSL's PKCS5_PBKDF2_HMAC to generate a key from a password and a salt. + * It is typically used to derive an AES key from a user's password and unique identifier (salt). + * + * @param pepper The constant salt value (user's id). + * @param baseKey The base key ( user's password). + * @param iterations Number of PBKDF2 iterations (higher is more secure but slower). + * @param keyLength Desired length of the derived key in bytes (e.g., 32 for AES-256). + * @return The derived key as a QByteArray, or an empty QByteArray on failure. + */ +QByteArray EncryptionUtils::deriveKey(const QByteArray &pepper, const QByteArray &baseKey, int iterations, int keyLength) { QByteArray derivedKey(keyLength, 0); // Allocate memory for the derived key // Use OpenSSL's PKCS5_PBKDF2_HMAC for PBKDF2 key derivation const int result = PKCS5_PBKDF2_HMAC(baseKey.data(), baseKey.size(), // Input key (password) - reinterpret_cast(salt.data()), - salt.size(), // Salt + reinterpret_cast(pepper.data()), + pepper.size(), // Salt iterations, // Number of iterations EVP_sha256(), // Hash function (SHA-256) keyLength, // Output key length (in bytes) @@ -621,6 +661,45 @@ QByteArray EncryptionUtils::deriveKey(const QByteArray &salt, const QByteArray & return derivedKey; } +/* QJsonObject EncryptionUtils::exportPublicKeyJWK(const RSA *rsaKey) +{ + const BIGNUM *n, *e; + RSA_get0_key(rsaKey, &n, &e, nullptr); + + auto b64url = [](const BIGNUM *bn) { + QByteArray bytes(BN_num_bytes(bn), 0); + BN_bn2bin(bn, reinterpret_cast(bytes.data())); + return QString::fromLatin1(bytes.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + }; + + QJsonObject jwk; + jwk["kty"] = "RSA"; + jwk["n"] = b64url(n); + jwk["e"] = b64url(e); + jwk["alg"] = "RSA-OAEP-256"; + jwk["key_ops"] = QJsonArray{"encrypt"}; + jwk["ext"] = true; + return jwk; +} */ + +/* QJsonObject EncryptionUtils::exportEncryptedPrivateKeyJWK(const QByteArray &encryptedPrivateKey) +{ + QJsonObject jwk; + jwk["kty"] = "oct"; // "oct" for a symmetric (opaque) blob + jwk["alg"] = "A256CBC"; // or whatever encryption you used + jwk["ciphertext"] = QString::fromLatin1(encryptedPrivateKey.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + jwk["ext"] = true; + return jwk; +} + +QJsonObject EncryptionUtils::exportKeyPairJWK(RSA *rsaKey, const QByteArray &encryptedPrivateKey) +{ + QJsonObject bundle; + bundle["public_key"] = exportPublicKeyJWK(rsaKey); + bundle["encrypted_private_key"] = exportEncryptedPrivateKeyJWK(encryptedPrivateKey); + return bundle; +} */ + #if 0 QByteArray aesEncrypt(const QByteArray& plaintext, const QByteArray& key, const QByteArray& iv) { EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index 17e4944ac9..f732c361cc 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -27,7 +27,7 @@ struct RSAKeyPair { QByteArray privateKey; }; -[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray exportJWKKey(RSA *rsaKey); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray exportJWKPublicKey(RSA *rsaKey); [[nodiscard]] LIBRUQOLACORE_EXPORT RSAKeyPair generateRSAKey(); [[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray encryptPrivateKey(const QByteArray &privateKey, const QByteArray &masterKey); [[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray decryptPrivateKey(const QByteArray &encryptedPrivateKey, const QByteArray &masterKey); diff --git a/tests/encryptiontest/encryptiontestgui.cpp b/tests/encryptiontest/encryptiontestgui.cpp index 41051de1aa..8e92f44446 100644 --- a/tests/encryptiontest/encryptiontestgui.cpp +++ b/tests/encryptiontest/encryptiontestgui.cpp @@ -103,6 +103,14 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) } }); + auto pushButtonExportPublicKey = new QPushButton(QStringLiteral("Export Public Key"), this); + mainLayout->addWidget(pushButtonExportPublicKey); + connect(pushButtonExportPublicKey, &QPushButton::clicked, this, [this]() { + const auto expPublicKey = EncryptionUtils::exportJWKPublicKey(EncryptionUtils::publicKeyFromPEM(mRsaKeyPair.publicKey)); + qDebug() << "Public Key:\n " << mRsaKeyPair.publicKey << "Exported Public Key:\n " << expPublicKey; + mTextEditResult->setPlainText(QStringLiteral("Public key export succeded!\n") + QString::fromUtf8(expPublicKey)); + }); + auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this); mainLayout->addWidget(pushButtonGenerateSessionKey); connect(pushButtonGenerateSessionKey, &QPushButton::clicked, this, [this]() { From 84cd7e0c66af7237f5e646bf9b0ba020d86fcb3b Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Tue, 16 Sep 2025 18:44:52 +0300 Subject: [PATCH 66/67] Switch pepper to salt as Aaron Ogle recommended to avoid confusion in mental model --- src/core/encryption/encryptionutils.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 43b0bb0372..5d02ab5f3b 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -638,15 +638,15 @@ QString EncryptionUtils::generateRandomText(int length) * @param keyLength Desired length of the derived key in bytes (e.g., 32 for AES-256). * @return The derived key as a QByteArray, or an empty QByteArray on failure. */ -QByteArray EncryptionUtils::deriveKey(const QByteArray &pepper, const QByteArray &baseKey, int iterations, int keyLength) +QByteArray EncryptionUtils::deriveKey(const QByteArray &salt, const QByteArray &baseKey, int iterations, int keyLength) { QByteArray derivedKey(keyLength, 0); // Allocate memory for the derived key // Use OpenSSL's PKCS5_PBKDF2_HMAC for PBKDF2 key derivation const int result = PKCS5_PBKDF2_HMAC(baseKey.data(), baseKey.size(), // Input key (password) - reinterpret_cast(pepper.data()), - pepper.size(), // Salt + reinterpret_cast(salt.data()), + salt.size(), // Salt iterations, // Number of iterations EVP_sha256(), // Hash function (SHA-256) keyLength, // Output key length (in bytes) From 1aa6c20bac67bcf86cb7e4bbc61f6388ad1edd4f Mon Sep 17 00:00:00 2001 From: Andro Ranogajec Date: Mon, 29 Sep 2025 21:26:18 +0300 Subject: [PATCH 67/67] Add export encrypted private key, add more test ui, add readme --- src/core/encryption/encryptionutils.cpp | 16 ++++++ src/core/encryption/encryptionutils.h | 1 + tests/encryptiontest/PLEASEREADME.md | 58 ++++++++++++++++++++++ tests/encryptiontest/encryptiontestgui.cpp | 24 +++++++-- 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 tests/encryptiontest/PLEASEREADME.md diff --git a/src/core/encryption/encryptionutils.cpp b/src/core/encryption/encryptionutils.cpp index 5d02ab5f3b..728332bcc4 100644 --- a/src/core/encryption/encryptionutils.cpp +++ b/src/core/encryption/encryptionutils.cpp @@ -85,6 +85,22 @@ QByteArray EncryptionUtils::exportJWKPublicKey(RSA *rsaKey) return doc.toJson(QJsonDocument::Compact); } +QByteArray EncryptionUtils::exportJWKEncryptedPrivateKey(const QByteArray &encryptedPrivateKey) +{ + QJsonObject jwkObj; + jwkObj[QStringLiteral("kty")] = QStringLiteral("RSA"); + jwkObj[QStringLiteral("alg")] = QStringLiteral("RSA-OAEP-256"); + jwkObj[QStringLiteral("key_ops")] = QJsonArray() << QStringLiteral("decrypt"); + jwkObj[QStringLiteral("ext")] = true; + + // Store the encrypted private key as base64url + const QString ePrivKeyBase64Url = QString::fromLatin1(encryptedPrivateKey.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + jwkObj[QStringLiteral("RSA-EPrivKey")] = ePrivKeyBase64Url; + + QJsonDocument doc(jwkObj); + return doc.toJson(QJsonDocument::Compact); +} + EncryptionUtils::RSAKeyPair EncryptionUtils::generateRSAKey() { RSAKeyPair keyPair; diff --git a/src/core/encryption/encryptionutils.h b/src/core/encryption/encryptionutils.h index f732c361cc..a9005b700c 100644 --- a/src/core/encryption/encryptionutils.h +++ b/src/core/encryption/encryptionutils.h @@ -28,6 +28,7 @@ struct RSAKeyPair { }; [[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray exportJWKPublicKey(RSA *rsaKey); +[[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray exportJWKEncryptedPrivateKey(const QByteArray &encryptedPrivateKey); [[nodiscard]] LIBRUQOLACORE_EXPORT RSAKeyPair generateRSAKey(); [[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray encryptPrivateKey(const QByteArray &privateKey, const QByteArray &masterKey); [[nodiscard]] LIBRUQOLACORE_EXPORT QByteArray decryptPrivateKey(const QByteArray &encryptedPrivateKey, const QByteArray &masterKey); diff --git a/tests/encryptiontest/PLEASEREADME.md b/tests/encryptiontest/PLEASEREADME.md new file mode 100644 index 0000000000..546716d956 --- /dev/null +++ b/tests/encryptiontest/PLEASEREADME.md @@ -0,0 +1,58 @@ + +# E2EE TEST GUI + +## Table of Contents + +- [Overview](#overview) +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) + +--- + +## Overview + +This test-gui provides a simple interface to experiment with: + +- Master key derivation using a password and userId as salt. +- RSA key pair generation. +- Encryption and decryption of private keys using the master key. +- Session key generation and encryption with RSA keys. +- Message encryption/decryption using a session key. +- Export/Import of public and encrypted private keys in **JWK** format. + +The GUI uses **QTextEdit** for input/output, **QPushButton** for triggering actions, +and leverages the `EncryptionUtils` library for all cryptographic operations. + +--- + +## Features + +- **Master Key Derivation**: Derive a symmetric master key from a password and user ID (used as salt). +- **RSA Key Pair**: Generate a new RSA public/private key pair. +- **Private Key Encryption**: Encrypt the private key using the derived master key. +- **Session Key**: Generate a random session key for encrypting messages. +- **Message Encryption/Decryption**: Encrypt or decrypt arbitrary messages using the session key. +- **Export Keys**: Export the public key (JWK format) and the encrypted private key (JWK format). +- **Reset**: Clear all keys, messages, and intermediate data. + +--- + +## Usage + +1. Launch the application. (if you new ./build/bin/encryptiontestgui, if on Windows don't forget XLaunch). +2. Derive a master key using your password and user ID (salt). +3. Generate an RSA key pair. +4. Encrypt the private key using the master key. +5. Generate a session key and encrypt it with the RSA public key. +6. Encrypt a message with the session key. +7. Decrypt the session key with the RSA private key. +8. Decrypt the message with the session key. +9. Export public and encrypted private keys in **JWK** format if needed. + +All outputs and results appear in the **Output** field. + + diff --git a/tests/encryptiontest/encryptiontestgui.cpp b/tests/encryptiontest/encryptiontestgui.cpp index 8e92f44446..a8eb12c363 100644 --- a/tests/encryptiontest/encryptiontestgui.cpp +++ b/tests/encryptiontest/encryptiontestgui.cpp @@ -39,7 +39,7 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) passwordEdit->setEchoMode(QLineEdit::Password); auto layout = new QGridLayout(dialog); - layout->addWidget(new QLabel(QStringLiteral("Salt: "), dialog), 0, 0); + layout->addWidget(new QLabel(QStringLiteral("UserId as salt: "), dialog), 0, 0); layout->addWidget(saltEdit, 0, 1); layout->addWidget(new QLabel(QStringLiteral("Password: "), dialog), 1, 0); layout->addWidget(passwordEdit, 1, 1); @@ -106,9 +106,25 @@ EncryptionTestGui::EncryptionTestGui(QWidget *parent) auto pushButtonExportPublicKey = new QPushButton(QStringLiteral("Export Public Key"), this); mainLayout->addWidget(pushButtonExportPublicKey); connect(pushButtonExportPublicKey, &QPushButton::clicked, this, [this]() { - const auto expPublicKey = EncryptionUtils::exportJWKPublicKey(EncryptionUtils::publicKeyFromPEM(mRsaKeyPair.publicKey)); - qDebug() << "Public Key:\n " << mRsaKeyPair.publicKey << "Exported Public Key:\n " << expPublicKey; - mTextEditResult->setPlainText(QStringLiteral("Public key export succeded!\n") + QString::fromUtf8(expPublicKey)); + if (mRsaKeyPair.publicKey.isEmpty()) { + mTextEditResult->setPlainText(QStringLiteral("Public key is empty, exporting failed!\n")); + } else { + const auto expPublicKey = EncryptionUtils::exportJWKPublicKey(EncryptionUtils::publicKeyFromPEM(mRsaKeyPair.publicKey)); + qDebug() << "Public Key:\n " << mRsaKeyPair.publicKey << "Exported Public Key:\n " << expPublicKey; + mTextEditResult->setPlainText(QStringLiteral("Public key export succeded!\n") + QString::fromUtf8(expPublicKey)); + } + }); + + auto pushButtonExportEncryptedPrivateKey = new QPushButton(QStringLiteral("Export Encrypted Private Key"), this); + mainLayout->addWidget(pushButtonExportEncryptedPrivateKey); + connect(pushButtonExportEncryptedPrivateKey, &QPushButton::clicked, this, [this]() { + if (mEncryptedPrivateKey.isEmpty()) { + mTextEditResult->setPlainText(QStringLiteral("Encrypted private key is empty, exporting failed!\n")); + } else { + const auto expPrivKey = EncryptionUtils::exportJWKEncryptedPrivateKey(mEncryptedPrivateKey); + qDebug() << "Private Key:\n " << mRsaKeyPair.privateKey << "Exported Encrypted Private Key:\n " << expPrivKey; + mTextEditResult->setPlainText(QStringLiteral("Encrypted private key export succeded!\n") + QString::fromUtf8(expPrivKey)); + } }); auto pushButtonGenerateSessionKey = new QPushButton(QStringLiteral("Generate Session Key"), this);