aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLinus Jahn <lnj@kaidan.im>2022-09-14 22:36:11 +0200
committerLinus Jahn <lnj@kaidan.im>2022-09-24 18:11:58 +0200
commitc8bc1db682c165853ad51e2806f932e4fd0b0597 (patch)
tree8bd06a8b72ef038ee2ffaca8881656df950d84b8 /src
parent9d9d0b22664c6860a005818e9e787670aec389ff (diff)
downloadqxmpp-c8bc1db682c165853ad51e2806f932e4fd0b0597.tar.gz
Add file encryption functions and Encryption/DecryptionDevice
The devices allow it to encrypt or decrypt data on the fly when reading or writing data.
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt6
-rw-r--r--src/client/QXmppFileEncryption.cpp279
-rw-r--r--src/client/QXmppFileEncryption.h74
3 files changed, 359 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9b82ff1d..bd79a5a7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -333,6 +333,12 @@ if(WITH_GSTREAMER)
)
endif()
+if(WITH_QCA)
+ target_sources(qxmpp PRIVATE client/QXmppFileEncryption.cpp client/QcaInitializer.cpp)
+ target_link_libraries(qxmpp PRIVATE qca-qt${QT_VERSION_MAJOR})
+ target_compile_definitions(qxmpp PRIVATE -DWITH_QCA)
+endif()
+
install(
TARGETS qxmpp
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
diff --git a/src/client/QXmppFileEncryption.cpp b/src/client/QXmppFileEncryption.cpp
new file mode 100644
index 00000000..99ccd474
--- /dev/null
+++ b/src/client/QXmppFileEncryption.cpp
@@ -0,0 +1,279 @@
+// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppFileEncryption.h"
+
+#include <QByteArray>
+#include <QtCrypto>
+
+using namespace QCA;
+
+constexpr std::size_t AES128_BLOCK_SIZE = 128 / 8;
+constexpr std::size_t AES256_BLOCK_SIZE = 256 / 8;
+constexpr int GCM_IV_SIZE = 12;
+
+namespace QXmpp::Private::Encryption {
+
+static QString cipherName(QXmpp::Cipher cipher)
+{
+ switch (cipher) {
+ case Aes128GcmNoPad:
+ return QStringLiteral("aes128");
+ case Aes256GcmNoPad:
+ case Aes256CbcPkcs7:
+ return QStringLiteral("aes256");
+ }
+ Q_UNREACHABLE();
+}
+
+static std::size_t blockSize(QXmpp::Cipher cipher)
+{
+ switch (cipher) {
+ case Aes128GcmNoPad:
+ return AES128_BLOCK_SIZE;
+ case Aes256GcmNoPad:
+ case Aes256CbcPkcs7:
+ return AES256_BLOCK_SIZE;
+ }
+ Q_UNREACHABLE();
+}
+
+static QCA::Cipher::Mode cipherMode(QXmpp::Cipher cipher)
+{
+ switch (cipher) {
+ case Aes128GcmNoPad:
+ case Aes256GcmNoPad:
+ return QCA::Cipher::GCM;
+ case Aes256CbcPkcs7:
+ return QCA::Cipher::CBC;
+ }
+ Q_UNREACHABLE();
+}
+
+static QCA::Cipher::Padding padding(QXmpp::Cipher cipher)
+{
+ switch (cipher) {
+ case Aes128GcmNoPad:
+ case Aes256GcmNoPad:
+ return QCA::Cipher::NoPadding;
+ case Aes256CbcPkcs7:
+ return QCA::Cipher::PKCS7;
+ }
+ Q_UNREACHABLE();
+}
+
+QCA::Direction toQcaDirection(Direction direction)
+{
+ switch (direction) {
+ case Encode:
+ return QCA::Encode;
+ case Decode:
+ return QCA::Decode;
+ }
+ Q_UNREACHABLE();
+}
+
+static std::size_t roundUpToBlockSize(qint64 size, std::size_t blockSize)
+{
+ Q_ASSERT(size >= 0);
+ return (size / blockSize + 1) * blockSize;
+}
+
+QByteArray process(const QByteArray &data, QXmpp::Cipher cipherConfig, Direction direction, const QByteArray &key, const QByteArray &iv)
+{
+ return QCA::Cipher(cipherName(cipherConfig),
+ cipherMode(cipherConfig),
+ padding(cipherConfig),
+ toQcaDirection(direction),
+ SymmetricKey(key),
+ InitializationVector(iv))
+ .process(MemoryRegion(data))
+ .toByteArray();
+}
+
+QByteArray generateKey(QXmpp::Cipher cipher)
+{
+ return Random::randomArray(int(blockSize(cipher))).toByteArray();
+}
+
+QByteArray generateInitializationVector(QXmpp::Cipher config)
+{
+ switch (config) {
+ case Aes128GcmNoPad:
+ case Aes256GcmNoPad:
+ return Random::randomArray(GCM_IV_SIZE).toByteArray();
+ case Aes256CbcPkcs7:
+ return Random::randomArray(int(blockSize(config))).toByteArray();
+ }
+ Q_UNREACHABLE();
+}
+
+EncryptionDevice::EncryptionDevice(std::unique_ptr<QIODevice> input,
+ Cipher config,
+ const QByteArray &key,
+ const QByteArray &iv)
+ : m_cipherConfig(config),
+ m_input(std::move(input)),
+ m_cipher(std::make_unique<QCA::Cipher>(
+ cipherName(config),
+ cipherMode(config),
+ padding(config),
+ QCA::Encode,
+ SymmetricKey(key),
+ InitializationVector(iv)))
+{
+ // output must not be sequential
+ Q_ASSERT(!m_input->isSequential());
+
+ Q_ASSERT(m_outputBuffer.empty());
+
+ setOpenMode(m_input->openMode() & QIODevice::ReadOnly);
+}
+
+EncryptionDevice::~EncryptionDevice() = default;
+
+bool EncryptionDevice::open(OpenMode mode)
+{
+ return m_input->open(mode);
+}
+
+void EncryptionDevice::close()
+{
+ m_input->close();
+}
+
+bool EncryptionDevice::isSequential() const
+{
+ return false;
+}
+
+qint64 EncryptionDevice::size() const
+{
+ switch (m_cipherConfig) {
+ case Aes128GcmNoPad:
+ case Aes256GcmNoPad:
+ return m_input->size();
+ case Aes256CbcPkcs7: {
+ // padding is done with 128 bits blocks
+ return roundUpToBlockSize(m_input->size(), 128 / 8);
+ }
+ }
+ Q_UNREACHABLE();
+}
+
+qint64 EncryptionDevice::readData(char *data, qint64 len)
+{
+ auto requestedLen = len;
+ qint64 read = 0;
+
+ {
+ // try to read from output buffer
+ qint64 outputBufferRead = std::min(qint64(m_outputBuffer.size()), len);
+ std::copy_n(m_outputBuffer.cbegin(), outputBufferRead, data);
+ m_outputBuffer.erase(m_outputBuffer.begin(), m_outputBuffer.begin() + outputBufferRead);
+ read += outputBufferRead;
+ len -= outputBufferRead;
+ }
+
+ if (len > 0) {
+ // read from input and encrypt new data
+
+ // output buffer is empty here
+ Q_ASSERT(m_outputBuffer.empty());
+
+ // read unencrypted data (may read one block more than needed)
+ auto inputBufferSize = roundUpToBlockSize(len, blockSize(m_cipherConfig));
+ Q_ASSERT(inputBufferSize > 0);
+ QByteArray inputBuffer;
+ inputBuffer.resize(inputBufferSize);
+ inputBuffer.resize(m_input->read(inputBuffer.data(), inputBufferSize));
+
+ // process input buffer
+ auto processed = [&]() {
+ if (inputBuffer.isEmpty()) {
+ m_finalized = true;
+ return m_cipher->final();
+ }
+ // encrypt data
+ return m_cipher->process(MemoryRegion(inputBuffer));
+ }();
+
+ // split up into part for user and put rest into output buffer
+ auto processedReadBytes = std::min(qint64(processed.size()), len);
+ std::copy_n(processed.constData(), processedReadBytes, data + read);
+ read += processedReadBytes;
+ len -= processedReadBytes;
+
+ Q_ASSERT(processed.size() >= processedReadBytes);
+ auto restBytes = size_t(processed.size() - processedReadBytes);
+ m_outputBuffer.resize(restBytes);
+ std::copy_n(processed.constData() + processedReadBytes, restBytes, m_outputBuffer.data());
+ }
+
+ Q_ASSERT((len + read) == requestedLen);
+ return read;
+}
+
+qint64 EncryptionDevice::writeData(const char *, qint64)
+{
+ return 0;
+}
+
+DecryptionDevice::DecryptionDevice(std::unique_ptr<QIODevice> input,
+ Cipher config,
+ const QByteArray &key,
+ const QByteArray &iv)
+ : m_cipherConfig(config),
+ m_output(std::move(input)),
+ m_cipher(std::make_unique<QCA::Cipher>(
+ cipherName(config),
+ cipherMode(config),
+ padding(config),
+ QCA::Decode,
+ SymmetricKey(key),
+ InitializationVector(iv)))
+{
+ // output must not be sequential
+ Q_ASSERT(!m_output->isSequential());
+
+ Q_ASSERT(m_outputBuffer.empty());
+
+ setOpenMode(m_output->openMode() & QIODevice::WriteOnly);
+}
+
+DecryptionDevice::~DecryptionDevice() = default;
+
+bool DecryptionDevice::open(OpenMode mode)
+{
+ return m_output->open(mode);
+}
+
+void DecryptionDevice::close()
+{
+ m_output->close();
+}
+
+bool DecryptionDevice::isSequential() const
+{
+ return true;
+}
+
+qint64 DecryptionDevice::size() const
+{
+ return 0;
+}
+
+qint64 DecryptionDevice::readData(char *, qint64)
+{
+ return 0;
+}
+
+qint64 DecryptionDevice::writeData(const char *data, qint64 len)
+{
+ auto decrypted = m_cipher->process(QByteArray(data, len));
+ m_output->write(decrypted.constData(), decrypted.size());
+ return len;
+}
+
+} // namespace QXmpp::Private::Encryption
diff --git a/src/client/QXmppFileEncryption.h b/src/client/QXmppFileEncryption.h
new file mode 100644
index 00000000..b1108b22
--- /dev/null
+++ b/src/client/QXmppFileEncryption.h
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifndef QXMPPFILEENCRYPTION_H
+#define QXMPPFILEENCRYPTION_H
+
+#include "QXmppGlobal.h"
+
+#include <memory>
+
+#include <QIODevice>
+
+namespace QCA {
+class Cipher;
+class Initializer;
+} // namespace QCA
+
+namespace QXmpp::Private::Encryption {
+
+enum Direction {
+ Encode,
+ Decode,
+};
+
+QByteArray process(const QByteArray &data, Cipher cipherConfig, Direction direction, const QByteArray &key, const QByteArray &iv);
+QByteArray generateKey(Cipher cipher);
+QByteArray generateInitializationVector(Cipher);
+
+// export for tests
+class QXMPP_EXPORT EncryptionDevice : public QIODevice
+{
+public:
+ EncryptionDevice(std::unique_ptr<QIODevice> input, Cipher config, const QByteArray &key, const QByteArray &iv);
+ ~EncryptionDevice() override;
+
+ bool open(QIODevice::OpenMode mode) override;
+ void close() override;
+ bool isSequential() const override;
+ qint64 size() const override;
+ qint64 readData(char *data, qint64 maxlen) override;
+ qint64 writeData(const char *data, qint64 len) override;
+
+private:
+ Cipher m_cipherConfig;
+ bool m_finalized = false;
+ std::vector<char> m_outputBuffer;
+ std::unique_ptr<QIODevice> m_input;
+ std::unique_ptr<QCA::Cipher> m_cipher;
+};
+
+class DecryptionDevice : public QIODevice
+{
+public:
+ DecryptionDevice(std::unique_ptr<QIODevice> output, Cipher config, const QByteArray &key, const QByteArray &iv);
+ ~DecryptionDevice() override;
+
+ bool open(QIODevice::OpenMode mode) override;
+ void close() override;
+ bool isSequential() const override;
+ qint64 size() const override;
+ qint64 readData(char *data, qint64 maxlen) override;
+ qint64 writeData(const char *data, qint64 len) override;
+
+private:
+ Cipher m_cipherConfig;
+ std::vector<char> m_outputBuffer;
+ std::unique_ptr<QIODevice> m_output;
+ std::unique_ptr<QCA::Cipher> m_cipher;
+};
+
+} // namespace QXmpp::Private::Encryption
+
+#endif // QXMPPFILEENCRYPTION_H