Skip to content

Commit

Permalink
Merge branch '1.5'
Browse files Browse the repository at this point in the history
  • Loading branch information
lnjX committed Aug 20, 2023
2 parents 6d533aa + 75a8a10 commit 16895e6
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 22 deletions.
7 changes: 6 additions & 1 deletion src/client/QXmppEncryptedFileSharingProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ auto QXmppEncryptedFileSharingProvider::downloadFile(const std::any &source,
// find provider for source of encrypted file
std::any httpSource = encryptedSource.httpSources().front();
if (auto provider = d->manager->providerForSource(httpSource)) {
return provider->downloadFile(httpSource, std::move(output), std::move(reportProgress), std::move(reportFinished));
auto onFinished = [decryptDevice = output.get(), reportFinished = std::move(reportFinished)](DownloadResult result) {
decryptDevice->finish();
reportFinished(std::move(result));
};

return provider->downloadFile(httpSource, std::move(output), std::move(reportProgress), std::move(onFinished));
}

reportFinished(QXmppError { QStringLiteral("No basic file sharing provider available for encrypted file."), {} });
Expand Down
74 changes: 57 additions & 17 deletions src/client/QXmppFileEncryption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,35 @@ static std::size_t roundUpToBlockSize(qint64 size, std::size_t blockSize)
return (size / blockSize + 1) * blockSize;
}

bool isSupported(Cipher config)
{
auto cipherString = QCA::Cipher::withAlgorithms(cipherName(config), cipherMode(config), padding(config));
return QCA::isSupported({ cipherString });
}

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();
auto cipher = QCA::Cipher(cipherName(cipherConfig),
cipherMode(cipherConfig),
padding(cipherConfig),
toQcaDirection(direction),
SymmetricKey(key),
InitializationVector(iv));
auto output = cipher.update(MemoryRegion(data)).toByteArray();

switch (cipherConfig) {
case Aes128GcmNoPad:
case Aes256GcmNoPad:
// For GCM no-padding algorithms QCA / OpenSSL adds a '\0' byte at the end.
// We don't want that, it breaks our checksums.
// The unit tests verify that the data is still decrypted correctly.
break;
case Aes256CbcPkcs7:
output += cipher.final().toByteArray();
break;
}

return output;
}

QByteArray generateKey(QXmpp::Cipher cipher)
Expand Down Expand Up @@ -131,6 +150,9 @@ EncryptionDevice::EncryptionDevice(std::unique_ptr<QIODevice> input,
Q_ASSERT(m_outputBuffer.empty());

setOpenMode(m_input->openMode() & QIODevice::ReadOnly);

Q_ASSERT(m_cipher->validKeyLength(int(key.length())));
Q_ASSERT(m_cipher->ok());
}

EncryptionDevice::~EncryptionDevice() = default;
Expand Down Expand Up @@ -192,14 +214,11 @@ qint64 EncryptionDevice::readData(char *data, qint64 len)
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));
}();
auto processed = m_cipher->update(MemoryRegion(inputBuffer));
if (m_input->atEnd()) {
m_finalized = true;
processed = processed + m_cipher->final();
}

// split up into part for user and put rest into output buffer
auto processedReadBytes = std::min(qint64(processed.size()), len);
Expand Down Expand Up @@ -247,6 +266,9 @@ DecryptionDevice::DecryptionDevice(std::unique_ptr<QIODevice> input,
Q_ASSERT(m_outputBuffer.empty());

setOpenMode(m_output->openMode() & QIODevice::WriteOnly);

Q_ASSERT(m_cipher->validKeyLength(int(key.length())));
Q_ASSERT(m_cipher->ok());
}

DecryptionDevice::~DecryptionDevice() = default;
Expand All @@ -258,6 +280,7 @@ bool DecryptionDevice::open(OpenMode mode)

void DecryptionDevice::close()
{
finish();
m_output->close();
}

Expand All @@ -278,9 +301,26 @@ qint64 DecryptionDevice::readData(char *, qint64)

qint64 DecryptionDevice::writeData(const char *data, qint64 len)
{
auto decrypted = m_cipher->process(QByteArray(data, len));
auto decrypted = m_cipher->update(QByteArray(data, len));
m_output->write(decrypted.constData(), decrypted.size());
return len;
}

void DecryptionDevice::finish()
{
switch (m_cipherConfig) {
case Aes128GcmNoPad:
case Aes256GcmNoPad:
// For GCM no-padding algorithms QCA / OpenSSL adds a '\0' byte at the end.
// We don't want that, it breaks our checksums.
// The unit tests verify that the data is still decrypted correctly.
return;
case Aes256CbcPkcs7: {
auto decrypted = m_cipher->final();
m_output->write(decrypted.constData(), decrypted.size());
break;
}
}
}

} // namespace QXmpp::Private::Encryption
2 changes: 2 additions & 0 deletions src/client/QXmppFileEncryption.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ enum Direction {
Decode,
};

QXMPP_EXPORT bool isSupported(Cipher);
QXMPP_EXPORT QByteArray process(const QByteArray &data, Cipher cipherConfig, Direction direction, const QByteArray &key, const QByteArray &iv);
QXMPP_EXPORT QByteArray generateKey(Cipher cipher);
QXMPP_EXPORT QByteArray generateInitializationVector(Cipher);
Expand Down Expand Up @@ -62,6 +63,7 @@ class QXMPP_EXPORT DecryptionDevice : public QIODevice
qint64 size() const override;
qint64 readData(char *data, qint64 maxlen) override;
qint64 writeData(const char *data, qint64 len) override;
void finish();

private:
Cipher m_cipherConfig;
Expand Down
53 changes: 49 additions & 4 deletions tests/qxmppfileencryption/tst_qxmppfileencryption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class tst_QXmppFileEncryption : public QObject

private:
Q_SLOT void basic();
Q_SLOT void qcaFeatures();
Q_SLOT void deviceEncrypt();
Q_SLOT void deviceDecrypt_data();
Q_SLOT void deviceDecrypt();
Q_SLOT void paddingSize();
};
Expand All @@ -37,6 +39,14 @@ void tst_QXmppFileEncryption::basic()
QCOMPARE(decrypted, data);
}

void tst_QXmppFileEncryption::qcaFeatures()
{
QcaInitializer init;
QVERIFY(isSupported(Aes128GcmNoPad));
QVERIFY(isSupported(Aes256GcmNoPad));
QVERIFY(isSupported(Aes256CbcPkcs7));
}

void tst_QXmppFileEncryption::deviceEncrypt()
{
QcaInitializer encInit;
Expand All @@ -57,24 +67,59 @@ void tst_QXmppFileEncryption::deviceEncrypt()
QCOMPARE(decrypted, data);
}

void tst_QXmppFileEncryption::deviceDecrypt_data()
{
QTest::addColumn<int>("cipherId");
QTest::addColumn<QByteArray>("key");

QTest::newRow("aes128-gcm")
<< int(Aes128GcmNoPad)
<< QByteArray("1234567890123456");
QTest::newRow("aes256-gcm")
<< int(Aes256GcmNoPad)
<< QByteArray("12345678901234567890123456789012");
QTest::newRow("aes256-cbc-pkcs7")
<< int(Aes256CbcPkcs7)
<< QByteArray("12345678901234567890123456789012");
}

void tst_QXmppFileEncryption::deviceDecrypt()
{
QFETCH(int, cipherId);
QFETCH(QByteArray, key);
auto cipher = Cipher(cipherId);

QcaInitializer encInit;

QByteArray data =
"v2qtI8tx5DxM6axUAZ+xsEwrtb0VYafAPlMWqpVMG+5PBE5wbZ7MZhDUEIdFkxchOIJqt";
QByteArray key = "12345678901234567890123456789012";
QByteArray iv = "12345678901234567890123456789012";

auto encrypted = process(data, Aes256CbcPkcs7, Encode, key, iv);
// setup input io device
auto buffer = std::make_unique<QBuffer>(&data);
buffer->open(QIODevice::ReadOnly);

// encrypt data
EncryptionDevice encDevice(std::move(buffer), cipher, key, iv);
auto encrypted = encDevice.readAll();
QVERIFY(!encrypted.isEmpty());

// compare with process() function
QCOMPARE(encrypted, process(data, cipher, Encode, key, iv));

qDebug() << "Encrypted:" << data.size() << "->" << encrypted.size();

// decrypt data with decryption device
QByteArray decrypted;
auto buffer = std::make_unique<QBuffer>(&decrypted);
buffer = std::make_unique<QBuffer>(&decrypted);
buffer->open(QIODevice::WriteOnly);

DecryptionDevice decDev(std::move(buffer), Aes256CbcPkcs7, key, iv);
DecryptionDevice decDev(std::move(buffer), cipher, key, iv);
decDev.write(encrypted);
decDev.close();

qDebug() << "Decrypted:" << encrypted.size() << "->" << decrypted.size();
QCOMPARE(decrypted, process(encrypted, cipher, Decode, key, iv));
QCOMPARE(decrypted, data);
}

Expand Down

0 comments on commit 16895e6

Please sign in to comment.