[network/ruqola] src/core: Add method for decrypted message

Laurent Montel <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 0dd98c1c895865dce4d403326d9e7a5c096fc1ce by Laurent Montel.
Committed on 04/08/2026 at 06:51.
Pushed by mlaurent into branch 'master'.

Add method for decrypted message

M  +27   -0    src/core/autotests/messageencryptedtest.cpp
M  +1    -0    src/core/autotests/messageencryptedtest.h
M  +39   -5    src/core/messages/messageencrypted.cpp
M  +2    -0    src/core/messages/messageencrypted.h

https://invent.kde.org/network/ruqola/-/commit/0dd98c1c895865dce4d403326d9e7a5c096fc1ce

diff --git a/src/core/autotests/messageencryptedtest.cpp b/src/core/autotests/messageencryptedtest.cpp
index 1c8d0f8081..01df01d78e 100644
--- a/src/core/autotests/messageencryptedtest.cpp
+++ b/src/core/autotests/messageencryptedtest.cpp
@@ -4,8 +4,10 @@
    SPDX-License-Identifier: LGPL-2.0-or-later
 */
 #include "messageencryptedtest.h"
+#include "config-ruqola.h"
 #include "messages/messageencrypted.h"
 #include <QTest>
+
 QTEST_GUILESS_MAIN(MessageEncryptedTest)
 
 MessageEncryptedTest::MessageEncryptedTest(QObject *parent)
@@ -23,4 +25,29 @@ void MessageEncryptedTest::shouldHaveDefaultValues()
     QVERIFY(!w.isValid());
 }
 
+void MessageEncryptedTest::shouldDecryptV2Payload()
+{
+    // Test vector generated with AES-256-GCM, key='k'*32, iv="0123456789ab", plaintext={"msg":"hello e2e"}
+    const QByteArray sessionKey(32, 'k');
+    const QByteArray iv("0123456789ab");
+    const QByteArray plainText("{\"msg\":\"hello e2e\"}");
+    const QByteArray encryptedPayload = QByteArray::fromBase64("ej0KsqEKP7tIhPFauxLZfLCDiI6PY2Ex68Kv4kt2sCFIA24=");
+    QVERIFY(!encryptedPayload.isEmpty());
+
+    MessageEncrypted encrypted;
+    encrypted.setAlgorithm("rc.v2.aes-sha2");
+    encrypted.setKeyId("23e2720d-b3e0-4753-85ff-bad2caeb867b");
+    encrypted.setIv(iv.toBase64());
+    encrypted.setCiphertext(QString::fromLatin1(encryptedPayload.toBase64()));
+
+#if USE_E2E_SUPPORT
+    QCOMPARE(encrypted.decrypt(sessionKey), plainText);
+
+    const QByteArray wrongSessionKey(32, 'x');
+    QVERIFY(encrypted.decrypt(wrongSessionKey).isEmpty());
+#else
+    QVERIFY(encrypted.decrypt(sessionKey).isEmpty());
+#endif
+}
+
 #include "moc_messageencryptedtest.cpp"
diff --git a/src/core/autotests/messageencryptedtest.h b/src/core/autotests/messageencryptedtest.h
index ba550396dc..794c411550 100644
--- a/src/core/autotests/messageencryptedtest.h
+++ b/src/core/autotests/messageencryptedtest.h
@@ -15,4 +15,5 @@ public:
     ~MessageEncryptedTest() override = default;
 private Q_SLOTS:
     void shouldHaveDefaultValues();
+    void shouldDecryptV2Payload();
 };
diff --git a/src/core/messages/messageencrypted.cpp b/src/core/messages/messageencrypted.cpp
index 57a7ee407e..09a7effb67 100644
--- a/src/core/messages/messageencrypted.cpp
+++ b/src/core/messages/messageencrypted.cpp
@@ -5,6 +5,8 @@
 */
 
 #include "messageencrypted.h"
+#include "config-ruqola.h"
+#include "encryption/encryptionutils.h"
 #include "ruqola_encryption_debug.h"
 
 #include <QJsonObject>
@@ -76,6 +78,38 @@ void MessageEncrypted::setIv(const QByteArray &newIv)
     mIv = newIv;
 }
 
+QByteArray MessageEncrypted::decrypt(const QByteArray &sessionKey) const
+{
+#if USE_E2E_SUPPORT
+    if (sessionKey.isEmpty()) {
+        qCWarning(RUQOLA_ENCRYPTION_LOG) << "MessageEncrypted::decrypt: session key is empty";
+        return {};
+    }
+
+    if (mAlgorithm != "rc.v2.aes-sha2") {
+        qCWarning(RUQOLA_ENCRYPTION_LOG) << "MessageEncrypted::decrypt: unsupported algorithm" << mAlgorithm;
+        return {};
+    }
+
+    const QByteArray decodedIv = QByteArray::fromBase64(mIv);
+    if (decodedIv.isEmpty()) {
+        qCWarning(RUQOLA_ENCRYPTION_LOG) << "MessageEncrypted::decrypt: iv is invalid";
+        return {};
+    }
+
+    const QByteArray decodedCiphertext = QByteArray::fromBase64(mCiphertext.toLatin1());
+    if (decodedCiphertext.isEmpty()) {
+        qCWarning(RUQOLA_ENCRYPTION_LOG) << "MessageEncrypted::decrypt: ciphertext is invalid";
+        return {};
+    }
+
+    return EncryptionUtils::decryptAES_GCM_256(decodedCiphertext, sessionKey, decodedIv);
+#else
+    Q_UNUSED(sessionKey)
+    return {};
+#endif
+}
+
 bool MessageEncrypted::operator==(const MessageEncrypted &other) const
 {
     return mAlgorithm == other.algorithm() && mCiphertext == other.ciphertext() && mKeyId == other.keyId() && mIv == other.iv();
@@ -89,13 +123,13 @@ void MessageEncrypted::parse(const QJsonObject &o)
     mKeyId = o["kid"_L1].toString().toLatin1();
 }
 
-QJsonObject MessageEncrypted::serialize(const MessageEncrypted &encrypted)
+QJsonObject MessageEncrypted::serialize(const MessageEncrypted &message)
 {
     QJsonObject o;
-    o["algorithm"_L1] = QString::fromLatin1(encrypted.algorithm());
-    o["ciphertext"_L1] = encrypted.ciphertext();
-    o["iv"_L1] = QString::fromLatin1(encrypted.iv());
-    o["kid"_L1] = QString::fromLatin1(encrypted.keyId());
+    o["algorithm"_L1] = QString::fromLatin1(message.algorithm());
+    o["ciphertext"_L1] = message.ciphertext();
+    o["iv"_L1] = QString::fromLatin1(message.iv());
+    o["kid"_L1] = QString::fromLatin1(message.keyId());
     return o;
 }
 
diff --git a/src/core/messages/messageencrypted.h b/src/core/messages/messageencrypted.h
index 10ac881fdf..7da22e0253 100644
--- a/src/core/messages/messageencrypted.h
+++ b/src/core/messages/messageencrypted.h
@@ -38,6 +38,8 @@ public:
     [[nodiscard]] QByteArray iv() const;
     void setIv(const QByteArray &newIv);
 
+    [[nodiscard]] QByteArray decrypt(const QByteArray &sessionKey) const;
+
 private:
     QByteArray mAlgorithm;
     QByteArray mKeyId;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.