From 77a13cfdace5cb27adb52b3a644a155aee522b12 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 28 Aug 2021 01:03:49 +0200 Subject: Implement download and decryption of encrypted files --- lib/jobs/downloadfilejob.cpp | 97 ++++++++++++++++++++++++++++++++++++++------ lib/jobs/downloadfilejob.h | 3 ++ 2 files changed, 88 insertions(+), 12 deletions(-) (limited to 'lib/jobs') diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 0b0531ad..7a7a46f6 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -7,8 +7,25 @@ #include #include -using namespace Quotient; +#ifdef Quotient_E2EE_ENABLED +# include +# include + +QByteArray decrypt(const QByteArray &ciphertext, const QByteArray &key, const QByteArray &iv) +{ + QByteArray plaintext(ciphertext.size(), 0); + EVP_CIPHER_CTX *ctx; + int length; + ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)key.data(), (const unsigned char *)iv.data()); + EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); + EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext; +} +#endif +using namespace Quotient; class DownloadFileJob::Private { public: Private() : tempFile(new QTemporaryFile()) {} @@ -20,6 +37,12 @@ public: QScopedPointer targetFile; QScopedPointer tempFile; + +#ifdef Quotient_E2EE_ENABLED + QByteArray key; + QByteArray iv; + QByteArray sha256; +#endif }; QUrl DownloadFileJob::makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri) @@ -37,6 +60,23 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, setObjectName(QStringLiteral("DownloadFileJob")); } +#ifdef Quotient_E2EE_ENABLED +DownloadFileJob::DownloadFileJob(const QString& serverName, + const QString& mediaId, + const QString& key, + const QString& iv, + const QString& sha256, + const QString& localFilename) + : GetContentJob(serverName, mediaId) + , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) +{ + setObjectName(QStringLiteral("DownloadFileJob")); + auto _key = key; + d->key = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); + d->iv = QByteArray::fromBase64(iv.toLatin1()); + d->sha256 = QByteArray::fromBase64(sha256.toLatin1()); +} +#endif QString DownloadFileJob::targetFileName() const { return (d->targetFile ? d->targetFile : d->tempFile)->fileName(); @@ -51,7 +91,7 @@ void DownloadFileJob::doPrepare() setStatus(FileError, "Could not open the target file for writing"); return; } - if (!d->tempFile->isReadable() && !d->tempFile->open(QIODevice::WriteOnly)) { + if (!d->tempFile->isReadable() && !d->tempFile->open(QIODevice::ReadWrite)) { qCWarning(JOBS) << "Couldn't open the temporary file" << d->tempFile->fileName() << "for writing"; setStatus(FileError, "Could not open the temporary download file"); @@ -99,18 +139,51 @@ void DownloadFileJob::beforeAbandon() BaseJob::Status DownloadFileJob::prepareResult() { if (d->targetFile) { - d->targetFile->close(); - if (!d->targetFile->remove()) { - qCWarning(JOBS) << "Failed to remove the target file placeholder"; - return { FileError, "Couldn't finalise the download" }; +#ifdef Quotient_E2EE_ENABLED + if(d->key.size() != 0) { + d->tempFile->seek(0); + QByteArray encrypted = d->tempFile->readAll(); + if(d->sha256 != QCryptographicHash::hash(encrypted, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return IncorrectResponse; + } + auto decrypted = decrypt(encrypted, d->key, d->iv); + d->targetFile->write(decrypted); + d->targetFile->remove(); + } else { +#endif + d->targetFile->close(); + if (!d->targetFile->remove()) { + qCWarning(JOBS) << "Failed to remove the target file placeholder"; + return { FileError, "Couldn't finalise the download" }; + } + if (!d->tempFile->rename(d->targetFile->fileName())) { + qCWarning(JOBS) << "Failed to rename" << d->tempFile->fileName() + << "to" << d->targetFile->fileName(); + return { FileError, "Couldn't finalise the download" }; + } +#ifdef Quotient_E2EE_ENABLED } - if (!d->tempFile->rename(d->targetFile->fileName())) { - qCWarning(JOBS) << "Failed to rename" << d->tempFile->fileName() - << "to" << d->targetFile->fileName(); - return { FileError, "Couldn't finalise the download" }; +#endif + } else { +#ifdef Quotient_E2EE_ENABLED + if(d->key.size() != 0) { + d->tempFile->seek(0); + auto encrypted = d->tempFile->readAll(); + + if(d->sha256 != QCryptographicHash::hash(encrypted, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return IncorrectResponse; + } + auto decrypted = decrypt(encrypted, d->key, d->iv); + d->tempFile->write(decrypted); + } else { +#endif + d->tempFile->close(); +#ifdef Quotient_E2EE_ENABLED } - } else - d->tempFile->close(); +#endif + } qCDebug(JOBS) << "Saved a file as" << targetFileName(); return Success; } diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index 0752af89..f000b991 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -14,6 +14,9 @@ public: DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& localFilename = {}); +#ifdef Quotient_E2EE_ENABLED + DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& key, const QString& iv, const QString& sha256, const QString& localFilename = {}); +#endif QString targetFileName() const; private: -- cgit v1.2.3 From b35a736da2b09fe5cc0091f9fbd370d057503a54 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 30 Aug 2021 00:53:29 +0200 Subject: Handle encrypted file download through existing API --- lib/jobs/downloadfilejob.cpp | 2 +- lib/room.cpp | 67 ++++++-------------------------------------- lib/room.h | 3 -- 3 files changed, 10 insertions(+), 62 deletions(-) (limited to 'lib/jobs') diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 7a7a46f6..e82271eb 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -149,7 +149,7 @@ BaseJob::Status DownloadFileJob::prepareResult() } auto decrypted = decrypt(encrypted, d->key, d->iv); d->targetFile->write(decrypted); - d->targetFile->remove(); + d->tempFile->remove(); } else { #endif d->targetFile->close(); diff --git a/lib/room.cpp b/lib/room.cpp index a4dfcb8f..d7ebe021 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2405,65 +2405,17 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) filePath = QDir::tempPath() % '/' % filePath; qDebug(MAIN) << "File path:" << filePath; } - auto job = connection()->downloadFile(fileUrl, filePath); - if (isJobPending(job)) { - // If there was a previous transfer (completed or failed), overwrite it. - d->fileTransfers[eventId] = { job, job->targetFileName() }; - connect(job, &BaseJob::downloadProgress, this, - [this, eventId](qint64 received, qint64 total) { - d->fileTransfers[eventId].update(received, total); - emit fileTransferProgress(eventId, received, total); - }); - connect(job, &BaseJob::success, this, [this, eventId, fileUrl, job] { - d->fileTransfers[eventId].status = FileTransferInfo::Completed; - emit fileTransferCompleted( - eventId, fileUrl, QUrl::fromLocalFile(job->targetFileName())); - }); - connect(job, &BaseJob::failure, this, - std::bind(&Private::failedTransfer, d, eventId, - job->errorString())); - } else - d->failedTransfer(eventId); -} - + DownloadFileJob *job = nullptr; +#ifdef Quotient_E2EE_ENABLED + if(fileInfo->file.has_value()) { + auto file = *fileInfo->file; + job = connection()->downloadFile(fileUrl, file.key.k, file.iv, file.hashes["sha256"], filePath); + } else { +#endif + job = connection()->downloadFile(fileUrl, filePath); #ifdef Quotient_E2EE_ENABLED -void Room::downloadFile(const QString& eventId, const QString& key, const QString& iv, const QString& sha256, const QUrl& localFilename) -{ - if (auto ongoingTransfer = d->fileTransfers.constFind(eventId); - ongoingTransfer != d->fileTransfers.cend() - && ongoingTransfer->status == FileTransferInfo::Started) { - qCWarning(MAIN) << "Transfer for" << eventId - << "is ongoing; download won't start"; - return; - } - - Q_ASSERT_X(localFilename.isEmpty() || localFilename.isLocalFile(), - __FUNCTION__, "localFilename should point at a local file"); - const auto* event = d->getEventWithFile(eventId); - if (!event) { - qCCritical(MAIN) - << eventId << "is not in the local timeline or has no file content"; - Q_ASSERT(false); - return; - } - if (!event->contentJson().contains(QStringLiteral("file"))) { - qCWarning(MAIN) << "Event" << eventId - << "has an empty or malformed mxc URL; won't download"; - return; - } - const auto fileUrl = QUrl(event->contentJson()["file"]["url"].toString()); - auto filePath = localFilename.toLocalFile(); - if (filePath.isEmpty()) { // Setup default file path - filePath = - fileUrl.path().mid(1) % '_' % d->fileNameToDownload(event); - - if (filePath.size() > 200) // If too long, elide in the middle - filePath.replace(128, filePath.size() - 192, "---"); - - filePath = QDir::tempPath() % '/' % filePath; - qDebug(MAIN) << "File path:" << filePath; } - auto job = connection()->downloadFile(fileUrl, key, iv, sha256, filePath); +#endif if (isJobPending(job)) { // If there was a previous transfer (completed or failed), overwrite it. d->fileTransfers[eventId] = { job, job->targetFileName() }; @@ -2483,7 +2435,6 @@ void Room::downloadFile(const QString& eventId, const QString& key, const QStrin } else d->failedTransfer(eventId); } -#endif void Room::cancelFileTransfer(const QString& id) { diff --git a/lib/room.h b/lib/room.h index ac21d08c..85c51a87 100644 --- a/lib/room.h +++ b/lib/room.h @@ -852,9 +852,6 @@ public Q_SLOTS: const QString& overrideContentType = {}); // If localFilename is empty a temporary file is created void downloadFile(const QString& eventId, const QUrl& localFilename = {}); -#ifdef Quotient_E2EE_ENABLED - void downloadFile(const QString& eventId, const QString& key, const QString &iv, const QString &sha256, const QUrl& localFilename = {}); -#endif void cancelFileTransfer(const QString& id); //! \brief Set a given event as last read and post a read receipt on it -- cgit v1.2.3 From 8636c7028b45ee8de3125bcf4df40ad60ed949a0 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 00:09:59 +0200 Subject: Add mxc protocol to the networkaccessmanager --- lib/connection.cpp | 6 ++---- lib/connection.h | 3 +-- lib/jobs/downloadfilejob.cpp | 48 +++++++++++--------------------------------- lib/jobs/downloadfilejob.h | 3 ++- lib/mxcreply.cpp | 36 +++++++++++++++++++++++++++++++-- lib/networkaccessmanager.cpp | 6 ++++++ lib/networkaccessmanager.h | 2 +- lib/room.cpp | 2 +- 8 files changed, 59 insertions(+), 47 deletions(-) (limited to 'lib/jobs') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4a1130ae..d8e98bb0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1019,15 +1019,13 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, #ifdef Quotient_E2EE_ENABLED DownloadFileJob* Connection::downloadFile(const QUrl& url, - const QString& key, - const QString& iv, - const QString& sha256, + const EncryptedFile file, const QString& localFilename) { auto mediaId = url.authority() + url.path(); auto idParts = splitMediaId(mediaId); auto* job = - callApi(idParts.front(), idParts.back(), key, iv, sha256, localFilename); + callApi(idParts.front(), idParts.back(), file, localFilename); return job; } #endif diff --git a/lib/connection.h b/lib/connection.h index f9143d3e..d0945aa4 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -568,8 +568,7 @@ public Q_SLOTS: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob* downloadFile(const QUrl& url, const QString &key, - const QString& iv, const QString& sha256, + DownloadFileJob* downloadFile(const QUrl& url, const EncryptedFile file, const QString& localFilename = {}); #endif /** diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index e82271eb..2fba1973 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -9,20 +9,8 @@ #ifdef Quotient_E2EE_ENABLED # include -# include - -QByteArray decrypt(const QByteArray &ciphertext, const QByteArray &key, const QByteArray &iv) -{ - QByteArray plaintext(ciphertext.size(), 0); - EVP_CIPHER_CTX *ctx; - int length; - ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)key.data(), (const unsigned char *)iv.data()); - EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); - EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext; -} +# include "encryptionmanager.h" +# include "events/encryptedfile.h" #endif using namespace Quotient; @@ -39,9 +27,7 @@ public: QScopedPointer tempFile; #ifdef Quotient_E2EE_ENABLED - QByteArray key; - QByteArray iv; - QByteArray sha256; + Omittable encryptedFile; #endif }; @@ -63,18 +49,13 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, #ifdef Quotient_E2EE_ENABLED DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, - const QString& key, - const QString& iv, - const QString& sha256, + const EncryptedFile file, const QString& localFilename) : GetContentJob(serverName, mediaId) , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) { setObjectName(QStringLiteral("DownloadFileJob")); - auto _key = key; - d->key = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); - d->iv = QByteArray::fromBase64(iv.toLatin1()); - d->sha256 = QByteArray::fromBase64(sha256.toLatin1()); + d->encryptedFile = file; } #endif QString DownloadFileJob::targetFileName() const @@ -140,14 +121,12 @@ BaseJob::Status DownloadFileJob::prepareResult() { if (d->targetFile) { #ifdef Quotient_E2EE_ENABLED - if(d->key.size() != 0) { + if (d->encryptedFile.has_value()) { d->tempFile->seek(0); QByteArray encrypted = d->tempFile->readAll(); - if(d->sha256 != QCryptographicHash::hash(encrypted, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return IncorrectResponse; - } - auto decrypted = decrypt(encrypted, d->key, d->iv); + + EncryptedFile file = *d->encryptedFile; + auto decrypted = EncryptionManager::decryptFile(encrypted, &file); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -167,15 +146,12 @@ BaseJob::Status DownloadFileJob::prepareResult() #endif } else { #ifdef Quotient_E2EE_ENABLED - if(d->key.size() != 0) { + if (d->encryptedFile.has_value()) { d->tempFile->seek(0); auto encrypted = d->tempFile->readAll(); - if(d->sha256 != QCryptographicHash::hash(encrypted, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return IncorrectResponse; - } - auto decrypted = decrypt(encrypted, d->key, d->iv); + EncryptedFile file = *d->encryptedFile; + auto decrypted = EncryptionManager::decryptFile(encrypted, &file); d->tempFile->write(decrypted); } else { #endif diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index f000b991..67a3e95f 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -4,6 +4,7 @@ #pragma once #include "csapi/content-repo.h" +#include "events/encryptedfile.h" namespace Quotient { class DownloadFileJob : public GetContentJob { @@ -15,7 +16,7 @@ public: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& key, const QString& iv, const QString& sha256, const QString& localFilename = {}); + DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFile file, const QString& localFilename = {}); #endif QString targetFileName() const; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 0b6643fc..65078301 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -3,8 +3,17 @@ #include "mxcreply.h" +#include +#include +#include "accountregistry.h" +#include "connection.h" #include "room.h" +#ifdef Quotient_E2EE_ENABLED +#include "encryptionmanager.h" +#include "events/encryptedfile.h" +#endif + using namespace Quotient; class MxcReply::Private @@ -14,6 +23,8 @@ public: : m_reply(r) {} QNetworkReply* m_reply; + Omittable m_encryptedFile; + QIODevice* m_device = nullptr; }; MxcReply::MxcReply(QNetworkReply* reply) @@ -31,11 +42,32 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) : d(std::make_unique(reply)) { reply->setParent(this); - connect(d->m_reply, &QNetworkReply::finished, this, [this, room, eventId]() { + connect(d->m_reply, &QNetworkReply::finished, this, [this]() { setError(d->m_reply->error(), d->m_reply->errorString()); + +#ifdef Quotient_E2EE_ENABLED + if(!d->m_encryptedFile.has_value()) { + d->m_device = d->m_reply; + } else { + EncryptedFile file = *d->m_encryptedFile; + auto buffer = new QBuffer(this); + buffer->setData(EncryptionManager::decryptFile(d->m_reply->readAll(), &file)); + d->m_device = buffer; + } setOpenMode(ReadOnly); emit finished(); +#else + d->m_device = d->m_reply; +#endif }); + +#ifdef Quotient_E2EE_ENABLED + auto eventIt = room->findInTimeline(eventId); + if(eventIt != room->historyEdge()) { + auto event = eventIt->viewAs(); + d->m_encryptedFile = event->content()->fileInfo()->file; + } +#endif } #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -61,7 +93,7 @@ MxcReply::MxcReply() qint64 MxcReply::readData(char *data, qint64 maxSize) { - return d->m_reply->read(data, maxSize); + return d->m_device->read(data, maxSize); } void MxcReply::abort() diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index d0380cec..c660cff8 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -12,6 +12,12 @@ #include #include #include +#include "accountregistry.h" +#include "mxcreply.h" +#include "connection.h" +#include "events/eventcontent.h" + +#include "room.h" using namespace Quotient; diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index 7643302f..efa41994 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -9,7 +9,7 @@ namespace Quotient { class Room; -class Connection; + class NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: diff --git a/lib/room.cpp b/lib/room.cpp index d7ebe021..688ba5d4 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2409,7 +2409,7 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) #ifdef Quotient_E2EE_ENABLED if(fileInfo->file.has_value()) { auto file = *fileInfo->file; - job = connection()->downloadFile(fileUrl, file.key.k, file.iv, file.hashes["sha256"], filePath); + job = connection()->downloadFile(fileUrl, file, filePath); } else { #endif job = connection()->downloadFile(fileUrl, filePath); -- cgit v1.2.3 From e99802772ebab9802e2f35d83ce1de9f83691d90 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 27 Nov 2021 00:11:36 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 8 ++++---- lib/encryptionmanager.cpp | 2 +- lib/jobs/downloadfilejob.cpp | 2 +- lib/jobs/downloadfilejob.h | 2 +- lib/mxcreply.cpp | 3 +-- lib/room.cpp | 11 +++++------ 6 files changed, 13 insertions(+), 15 deletions(-) (limited to 'lib/jobs') diff --git a/lib/connection.cpp b/lib/connection.cpp index 95ed1eb6..df9ff445 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -57,7 +57,7 @@ #include -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION_MAJOR >= 6 # include #else # include @@ -830,7 +830,7 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED - if(toDeviceEvents.size() > 0) { + if (!toDeviceEvents.empty()) { qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { @@ -1020,7 +1020,7 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, #ifdef Quotient_E2EE_ENABLED DownloadFileJob* Connection::downloadFile(const QUrl& url, - const EncryptedFile file, + const EncryptedFile& file, const QString& localFilename) { auto mediaId = url.authority() + url.path(); @@ -1996,7 +1996,7 @@ void Connection::Private::saveDevicesList() void Connection::Private::loadDevicesList() { - QFile file { q->e2eeDataDir() + QStringLiteral("/deviceslist.json") }; + QFile file { q->e2eeDataDir() % "/deviceslist.json" }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No devicesList cache exists. Creating new"; return; diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index b09e5260..c816eda7 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -51,7 +51,7 @@ public: } } void loadSessions() { - QFile file { static_cast(q->parent())->e2eeDataDir() + QStringLiteral("/olmsessions.json") }; + QFile file { static_cast(q->parent())->e2eeDataDir() % "/olmsessions.json" }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No sessions cache exists."; return; diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 2fba1973..0b4cf6d2 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -49,7 +49,7 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, #ifdef Quotient_E2EE_ENABLED DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, - const EncryptedFile file, + const EncryptedFile& file, const QString& localFilename) : GetContentJob(serverName, mediaId) , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index 67a3e95f..90d80478 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -16,7 +16,7 @@ public: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFile file, const QString& localFilename = {}); + DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFile& file, const QString& localFilename = {}); #endif QString targetFileName() const; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 639c1324..2ad49c2c 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -3,8 +3,7 @@ #include "mxcreply.h" -#include -#include +#include #include "accountregistry.h" #include "connection.h" #include "room.h" diff --git a/lib/room.cpp b/lib/room.cpp index 65ce82ac..fca1912f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -456,7 +456,7 @@ public: return false; } qCWarning(E2EE) << "Adding inbound session"; - groupSessions[qMakePair(senderKey, sessionId)] = std::move(megolmSession); + groupSessions[{senderKey, sessionId}] = std::move(megolmSession); saveMegOlmSessions(); return true; } @@ -469,13 +469,14 @@ public: { QPair senderSessionPairKey = qMakePair(senderKey, sessionId); - if (groupSessions.find(senderSessionPairKey) == groupSessions.end()) { + auto groupSessionIt = groupSessions.find(senderSessionPairKey); + if (groupSessionIt == groupSessions.end()) { qCWarning(E2EE) << "Unable to decrypt event" << eventId << "The sender's device has not sent us the keys for " "this message"; return QString(); } - auto& senderSession = groupSessions[senderSessionPairKey]; + auto& senderSession = *groupSessionIt; auto decryptResult = senderSession->decrypt(cipher); if(std::holds_alternative(decryptResult)) { qCWarning(E2EE) << "Unable to decrypt event" << eventId @@ -520,8 +521,6 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) emit baseStateLoaded(); return this == r; // loadedRoomState fires only once per room }); - qCDebug(STATE) << "New" << initialJoinState << "Room:" << id; - #ifdef Quotient_E2EE_ENABLED connectSingleShot(this, &Room::encryption, this, [=](){ connection->encryptionUpdate(this); @@ -1555,7 +1554,7 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) qCWarning(E2EE) << "Encrypted message is empty"; return {}; } - QJsonObject eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); + auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); eventObject["event_id"] = encryptedEvent.id(); eventObject["sender"] = encryptedEvent.senderId(); eventObject["origin_server_ts"] = encryptedEvent.originTimestamp().toMSecsSinceEpoch(); -- cgit v1.2.3 From 9217026e46d7ac0d761cc5206d7ef00978558c47 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:58:38 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- autotests/testolmsession.cpp | 2 +- lib/connection.cpp | 3 +-- lib/encryptionmanager.cpp | 2 +- lib/events/encryptedevent.cpp | 12 +++++------- lib/jobs/downloadfilejob.cpp | 2 +- 5 files changed, 9 insertions(+), 12 deletions(-) (limited to 'lib/jobs') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 750b804e..00d76d4e 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -30,7 +30,7 @@ std::pair createSessionPair() throw "Wrong first message type received, can't create session"; } auto inbound = std::get(accountB.createInboundSession(preKey)); - return std::make_pair(std::move(inbound), std::move(outbound)); + return { std::move(inbound), std::move(outbound) }; } void TestOlmSession::olmOutboundSessionCreation() diff --git a/lib/connection.cpp b/lib/connection.cpp index a7af1477..ac428a62 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -56,7 +56,6 @@ #include #include - #if QT_VERSION_MAJOR >= 6 # include #else @@ -1948,7 +1947,7 @@ void Connection::Private::saveDevicesList() QElapsedTimer et; et.start(); - QFile outFile { q->e2eeDataDir() + QStringLiteral("/deviceslist.json") }; + QFile outFile { q->e2eeDataDir() % "/deviceslist.json" }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index ed6ad20b..5c106e12 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -80,7 +80,7 @@ public: } } void saveSessions() { - QFile outFile { static_cast(q->parent())->e2eeDataDir() + QStringLiteral("/olmsessions.json") }; + QFile outFile { static_cast(q->parent())->e2eeDataDir() % "/olmsessions.json" }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index c9257584..2e0d7387 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -38,17 +38,15 @@ RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const eventObject["event_id"] = id(); eventObject["sender"] = senderId(); eventObject["origin_server_ts"] = originTimestamp().toMSecsSinceEpoch(); - if(contentJson().contains("m.relates_to")) { - auto relates = contentJson()["m.relates_to"].toObject(); + if (const auto relatesToJson = contentPart("m.relates_to"_ls); !relatesToJson.isUndefined()) { auto content = eventObject["content"].toObject(); - content["m.relates_to"] = relates; + content["m.relates_to"] = relatesToJson.toObject(); eventObject["content"] = content; } - if(unsignedJson().contains("redacts")) { - auto redacts = unsignedJson()["redacts"].toString(); + if (const auto redactsJson = unsignedPart("redacts"_ls); !redactsJson.isUndefined()) { auto unsign = eventObject["unsigned"].toObject(); - unsign["redacts"] = redacts; + unsign["redacts"] = redactsJson.toString(); eventObject["unsigned"] = unsign; } - return makeEvent(eventObject); + return loadEvent(eventObject); } diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 0b4cf6d2..2eea9d59 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -8,7 +8,7 @@ #include #ifdef Quotient_E2EE_ENABLED -# include +# include # include "encryptionmanager.h" # include "events/encryptedfile.h" #endif -- cgit v1.2.3 From 7b5edb737522b03d4f697e0e09f1771ad5edef89 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 7 Feb 2022 21:48:07 +0100 Subject: Remove encryptionmanager and various fixes --- CMakeLists.txt | 2 +- autotests/testolmaccount.cpp | 2 +- lib/connection.cpp | 108 ++++++++++++++++++++++++----- lib/connection.h | 2 - lib/e2ee/qolminboundsession.cpp | 2 + lib/encryptionmanager.cpp | 149 ---------------------------------------- lib/encryptionmanager.h | 33 --------- lib/events/encryptedfile.cpp | 27 ++++++++ lib/events/encryptedfile.h | 6 ++ lib/jobs/downloadfilejob.cpp | 5 +- lib/mxcreply.cpp | 3 +- 11 files changed, 130 insertions(+), 209 deletions(-) delete mode 100644 lib/encryptionmanager.cpp delete mode 100644 lib/encryptionmanager.h create mode 100644 lib/events/encryptedfile.cpp (limited to 'lib/jobs') diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ef3477e..69ac7e20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,7 @@ list(APPEND lib_SRCS lib/events/roomkeyevent.cpp lib/events/stickerevent.cpp lib/events/keyverificationevent.cpp + lib/events/encryptedfile.cpp lib/jobs/requestdata.cpp lib/jobs/basejob.cpp lib/jobs/syncjob.cpp @@ -174,7 +175,6 @@ if (${PROJECT_NAME}_ENABLE_E2EE) lib/e2ee/qolmerrors.cpp lib/e2ee/qolmsession.cpp lib/e2ee/qolmmessage.cpp - lib/encryptionmanager.cpp ) endif() diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 45d158eb..62b786d0 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -537,7 +537,7 @@ void TestOlmAccount::enableEncryption() QString joinedRoom; auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), {"@bob:localhost"}); - connect(alice.get(), &Connection::newRoom, this, [alice, bob, joinedRoom, this] (Quotient::Room *room) { + connect(alice.get(), &Connection::newRoom, this, [alice, bob, &joinedRoom, this] (Quotient::Room *room) { room->activateEncryption(); QSignalSpy spy(room, &Room::encryption); diff --git a/lib/connection.cpp b/lib/connection.cpp index 58e3a9f8..1a1b284d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -37,7 +37,6 @@ #ifdef Quotient_E2EE_ENABLED # include "e2ee/qolmaccount.h" # include "e2ee/qolmutils.h" -# include "encryptionmanager.h" # include "database.h" #if QT_VERSION_MAJOR >= 6 @@ -117,6 +116,10 @@ public: bool encryptionUpdateRequired = false; PicklingMode picklingMode = Unencrypted {}; Database *database = nullptr; + + // A map from SenderKey to vector of InboundSession + UnorderedMap> olmSessions; + #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -127,7 +130,6 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; bool isUploadingKeys = false; - EncryptionManager *encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -201,6 +203,85 @@ public: return q->stateCacheDir().filePath("state.json"); } +#ifdef Quotient_E2EE_ENABLED + void loadSessions() { + olmSessions = q->database()->loadOlmSessions(q->picklingMode()); + } + void saveSession(QOlmSessionPtr& session, const QString &senderKey) { + auto pickleResult = session->pickle(q->picklingMode()); + if (std::holds_alternative(pickleResult)) { + qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); + return; + } + q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult)); + } + QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) + { + Q_ASSERT(message.type() == QOlmMessage::PreKey); + for(auto& session : olmSessions[senderKey]) { + const auto matches = session->matchesInboundSessionFrom(senderKey, message); + if(std::holds_alternative(matches) && std::get(matches)) { + qCDebug(E2EE) << "Found inbound session"; + const auto result = session->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message"; + return {}; + } + } + } + qCDebug(E2EE) << "Creating new inbound session"; + auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); + if(std::holds_alternative(newSessionResult)) { + qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); + return {}; + } + auto newSession = std::move(std::get(newSessionResult)); + auto error = olmAccount->removeOneTimeKeys(newSession); + if (error) { + qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); + } + const auto result = newSession->decrypt(message); + saveSession(newSession, senderKey); + olmSessions[senderKey].push_back(std::move(newSession)); + if(std::holds_alternative(result)) { + return std::get(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; + return {}; + } + } + QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + { + Q_ASSERT(message.type() == QOlmMessage::General); + for(auto& session : olmSessions[senderKey]) { + const auto result = session->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); + } + } + qCWarning(E2EE) << "Failed to decrypt message"; + return {}; + } + + QString sessionDecryptMessage( + const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) + { + QString decrypted; + int type = personalCipherObject.value(TypeKeyL).toInt(-1); + QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); + if (type == 0) { + QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); + decrypted = sessionDecryptPrekey(preKeyMessage, senderKey, account); + } else if (type == 1) { + QOlmMessage message(body, QOlmMessage::General); + decrypted = sessionDecryptGeneral(message, senderKey); + } + return decrypted; + } +#endif + EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { #ifndef Quotient_E2EE_ENABLED @@ -217,7 +298,7 @@ public: qCDebug(E2EE) << "Encrypted event is not for the current device"; return {}; } - const auto decrypted = encryptionManager->sessionDecryptMessage( + const auto decrypted = sessionDecryptMessage( personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" @@ -443,6 +524,7 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #endif // Quotient_E2EE_ENABLED + database->clear(); }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -493,13 +575,15 @@ void Connection::Private::completeSetup(const QString& mxId) picklingMode = Encrypted { job.binaryData() }; } + database = new Database(data->userId(), q); + // init olmAccount olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); - database = new Database(data->userId(), q); - - encryptionManager = new EncryptionManager(q); +#ifdef Quotient_E2EE_ENABLED + loadSessions(); +#endif if (database->accountPickle().isEmpty()) { // create new account and save unpickle data @@ -2019,18 +2103,6 @@ void Connection::saveOlmAccount() #endif } -QString Connection::e2eeDataDir() const -{ - auto safeUserId = userId(); - safeUserId.replace(':', '_'); - const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) % '/' - % safeUserId % '/'; - QDir dir; - if (!dir.exists(path)) - dir.mkpath(path); - return path; -} - #ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { diff --git a/lib/connection.h b/lib/connection.h index 93ee496e..8dec2a0c 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -401,8 +401,6 @@ public: bool lazyLoading() const; void setLazyLoading(bool newValue); - QString e2eeDataDir() const; - /*! Start a pre-created job object on this connection */ Q_INVOKABLE BaseJob* run(BaseJob* job, RunningPolicy runningPolicy = ForegroundRequest); diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 9bc80eef..2e9cc716 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -4,6 +4,8 @@ #include "e2ee/qolminboundsession.h" #include +#include + using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp deleted file mode 100644 index abdcdcee..00000000 --- a/lib/encryptionmanager.cpp +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "encryptionmanager.h" - -#include "connection.h" -#include "events/encryptedfile.h" -#include "database.h" - -#include "csapi/keys.h" - -#include -#include -#include - -#include "e2ee/e2ee.h" -#include "e2ee/qolmaccount.h" -#include "e2ee/qolmsession.h" -#include "e2ee/qolmmessage.h" -#include "e2ee/qolmerrors.h" -#include "e2ee/qolmutils.h" -#include -#include - -#include - -using namespace Quotient; -using std::move; - -class EncryptionManager::Private { -public: - EncryptionManager* q; - - Connection* connection; - - // A map from SenderKey to vector of InboundSession - UnorderedMap> sessions; - - void loadSessions() { - sessions = connection->database()->loadOlmSessions(connection->picklingMode()); - } - void saveSession(QOlmSessionPtr& session, const QString &senderKey) { - auto pickleResult = session->pickle(connection->picklingMode()); - if (std::holds_alternative(pickleResult)) { - qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); - return; - } - connection->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult)); - } - QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) - { - Q_ASSERT(message.type() == QOlmMessage::PreKey); - for(auto& session : sessions[senderKey]) { - const auto matches = session->matchesInboundSessionFrom(senderKey, message); - if(std::holds_alternative(matches) && std::get(matches)) { - qCDebug(E2EE) << "Found inbound session"; - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - return std::get(result); - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message"; - return {}; - } - } - } - qCDebug(E2EE) << "Creating new inbound session"; - auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); - if(std::holds_alternative(newSessionResult)) { - qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); - return {}; - } - auto newSession = std::move(std::get(newSessionResult)); - auto error = olmAccount->removeOneTimeKeys(newSession); - if (error) { - qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); - } - const auto result = newSession->decrypt(message); - saveSession(newSession, senderKey); - sessions[senderKey].push_back(std::move(newSession)); - if(std::holds_alternative(result)) { - return std::get(result); - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; - return {}; - } - } - QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) - { - Q_ASSERT(message.type() == QOlmMessage::General); - for(auto& session : sessions[senderKey]) { - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - return std::get(result); - } - } - qCWarning(E2EE) << "Failed to decrypt message"; - return {}; - } -}; - -EncryptionManager::EncryptionManager(QObject* parent) - : QObject(parent) - , d(std::make_unique()) -{ - d->q = this; - d->connection = static_cast(parent); - d->loadSessions(); -} - -EncryptionManager::~EncryptionManager() = default; - -QString EncryptionManager::sessionDecryptMessage( - const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) -{ - QString decrypted; - int type = personalCipherObject.value(TypeKeyL).toInt(-1); - QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); - if (type == 0) { - QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); - decrypted = d->sessionDecryptPrekey(preKeyMessage, senderKey, account); - } else if (type == 1) { - QOlmMessage message(body, QOlmMessage::General); - decrypted = d->sessionDecryptGeneral(message, senderKey); - } - return decrypted; -} - -QByteArray EncryptionManager::decryptFile(const QByteArray &ciphertext, EncryptedFile* file) -{ - const auto key = QByteArray::fromBase64(file->key.k.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); - const auto iv = QByteArray::fromBase64(file->iv.toLatin1()); - const auto sha256 = QByteArray::fromBase64(file->hashes["sha256"].toLatin1()); - if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return QByteArray(); - } - QByteArray plaintext(ciphertext.size(), 0); - EVP_CIPHER_CTX *ctx; - int length; - ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)key.data(), (const unsigned char *)iv.data()); - EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); - EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext; -} -#endif // Quotient_E2EE_ENABLED diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h deleted file mode 100644 index 96569980..00000000 --- a/lib/encryptionmanager.h +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#pragma once - -#include - -#include -#include - -namespace Quotient { -class Connection; -class QOlmAccount; -struct EncryptedFile; - -class EncryptionManager : public QObject { - Q_OBJECT - -public: - explicit EncryptionManager(QObject* parent = nullptr); - ~EncryptionManager(); - QString sessionDecryptMessage(const QJsonObject& personalCipherObject, - const QByteArray& senderKey, std::unique_ptr& account); - static QByteArray decryptFile(const QByteArray &ciphertext, EncryptedFile* encryptedFile); - -private: - class Private; - std::unique_ptr d; -}; - -} // namespace Quotient -#endif // Quotient_E2EE_ENABLED diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp new file mode 100644 index 00000000..5ec344bb --- /dev/null +++ b/lib/events/encryptedfile.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "encryptedfile.h" + +using namespace Quotient; + +QByteArray EncryptedFile::decryptFile(const QByteArray &ciphertext) const +{ + QString _key = key.k; + _key = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); + const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); + if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return QByteArray(); + } + QByteArray plaintext(ciphertext.size(), 0); + EVP_CIPHER_CTX *ctx; + int length; + ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)_key.data(), (const unsigned char *)iv.toLatin1().data()); + EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); + EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext; +} diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 24ac9de1..f271d345 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -5,6 +5,10 @@ #pragma once #include "converters.h" +#include "logging.h" + +#include +#include namespace Quotient { /** @@ -44,6 +48,8 @@ public: QString iv; QHash hashes; QString v; + + QByteArray decryptFile(const QByteArray &ciphertext) const; }; template <> diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 2eea9d59..c5280770 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -9,7 +9,6 @@ #ifdef Quotient_E2EE_ENABLED # include -# include "encryptionmanager.h" # include "events/encryptedfile.h" #endif @@ -126,7 +125,7 @@ BaseJob::Status DownloadFileJob::prepareResult() QByteArray encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = EncryptionManager::decryptFile(encrypted, &file); + auto decrypted = file.decryptFile(encrypted); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -151,7 +150,7 @@ BaseJob::Status DownloadFileJob::prepareResult() auto encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = EncryptionManager::decryptFile(encrypted, &file); + auto decrypted = file.decryptFile(encrypted); d->tempFile->write(decrypted); } else { #endif diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index c7f27b0c..c666cce3 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -9,7 +9,6 @@ #include "room.h" #ifdef Quotient_E2EE_ENABLED -#include "encryptionmanager.h" #include "events/encryptedfile.h" #endif @@ -51,7 +50,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) } else { EncryptedFile file = *d->m_encryptedFile; auto buffer = new QBuffer(this); - buffer->setData(EncryptionManager::decryptFile(d->m_reply->readAll(), &file)); + buffer->setData(file.decryptFile(d->m_reply->readAll())); buffer->open(ReadOnly); d->m_device = buffer; } -- cgit v1.2.3 From b5e1fc7d8fcf9336db0dfb351403aa06dcb226a0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 16 Feb 2022 08:40:56 +0100 Subject: More cleanup, especially in EncryptedFile For EncryptedFile: - JSON converter bodies moved away to .cpp; - instead of C-style casts, reinterpret_cast is used to convert from (const) char* to (const) unsigned char*; - the size for the target plain text takes into account the case where the cipher block size can be larger than 1 (after reading https://www.openssl.org/docs/man1.1.1/man3/EVP_DecryptUpdate.html). - file decryption is wrapped in #ifdef Quotient_E2EE_ENABLED, to avoid OpenSSL linking errors when compiling without E2EE. --- lib/connection.cpp | 21 ++++++++--- lib/events/encryptedfile.cpp | 89 +++++++++++++++++++++++++++++++++++++------- lib/events/encryptedfile.h | 40 +++----------------- lib/jobs/downloadfilejob.cpp | 6 +-- 4 files changed, 99 insertions(+), 57 deletions(-) (limited to 'lib/jobs') diff --git a/lib/connection.cpp b/lib/connection.cpp index 87fc8e2c..1ef2495d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2007,8 +2007,10 @@ void Connection::Private::loadOutdatedUserDevices() << device.algorithms; continue; } - if(!verifyIdentitySignature(device, device.deviceId, device.userId)) { - qCWarning(E2EE) << "Failed to verify devicekeys signature. Skipping this device"; + if (!verifyIdentitySignature(device, device.deviceId, + device.userId)) { + qCWarning(E2EE) << "Failed to verify devicekeys signature. " + "Skipping this device"; continue; } deviceKeys[user][device.deviceId] = device; @@ -2022,9 +2024,11 @@ void Connection::Private::loadOutdatedUserDevices() void Connection::Private::saveDevicesList() { q->database()->transaction(); - auto query = q->database()->prepareQuery(QStringLiteral("DELETE FROM tracked_users")); + auto query = q->database()->prepareQuery( + QStringLiteral("DELETE FROM tracked_users")); q->database()->execute(query); - query.prepare(QStringLiteral("INSERT INTO tracked_users(matrixId) VALUES(:matrixId);")); + query.prepare(QStringLiteral( + "INSERT INTO tracked_users(matrixId) VALUES(:matrixId);")); for (const auto& user : trackedUsers) { query.bindValue(":matrixId", user); q->database()->execute(query); @@ -2032,13 +2036,18 @@ void Connection::Private::saveDevicesList() query.prepare(QStringLiteral("DELETE FROM outdated_users")); q->database()->execute(query); - query.prepare(QStringLiteral("INSERT INTO outdated_users(matrixId) VALUES(:matrixId);")); + query.prepare(QStringLiteral( + "INSERT INTO outdated_users(matrixId) VALUES(:matrixId);")); for (const auto& user : outdatedUsers) { query.bindValue(":matrixId", user); q->database()->execute(query); } - query.prepare(QStringLiteral("INSERT INTO tracked_devices(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey) VALUES(:matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey);")); + query.prepare(QStringLiteral( + "INSERT INTO tracked_devices" + "(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey) " + "VALUES(:matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey);" + )); for (const auto& user : deviceKeys.keys()) { for (const auto& device : deviceKeys[user]) { auto keys = device.keys.keys(); diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index dbb72af8..d4a517bd 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -5,27 +5,88 @@ #include "encryptedfile.h" #include "logging.h" +#ifdef Quotient_E2EE_ENABLED #include #include +#endif using namespace Quotient; -QByteArray EncryptedFile::decryptFile(const QByteArray &ciphertext) const +QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const { - QString _key = key.k; - auto keyBytes = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); +#ifdef Quotient_E2EE_ENABLED + auto _key = key.k; + const auto keyBytes = QByteArray::fromBase64( + _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); - if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { + if (sha256 + != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { qCWarning(E2EE) << "Hash verification failed for file"; - return QByteArray(); + return {}; } - QByteArray plaintext(ciphertext.size(), 0); - EVP_CIPHER_CTX *ctx; - int length; - ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)keyBytes.data(), (const unsigned char *)QByteArray::fromBase64(iv.toLatin1()).data()); - EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); - EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext; + { + int length; + auto* ctx = EVP_CIPHER_CTX_new(); + QByteArray plaintext(ciphertext.size() + EVP_CIPHER_CTX_block_size(ctx) + - 1, + '\0'); + EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, + reinterpret_cast( + keyBytes.data()), + reinterpret_cast( + QByteArray::fromBase64(iv.toLatin1()).data())); + EVP_DecryptUpdate( + ctx, reinterpret_cast(plaintext.data()), &length, + reinterpret_cast(ciphertext.data()), + ciphertext.size()); + EVP_DecryptFinal_ex(ctx, + reinterpret_cast(plaintext.data()) + + length, + &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext; + } +#else + qWarning(MAIN) << "This build of libQuotient doesn't support E2EE, " + "cannot decrypt the file"; + return ciphertext; +#endif +} + +void JsonObjectConverter::dumpTo(QJsonObject& jo, + const EncryptedFile& pod) +{ + addParam<>(jo, QStringLiteral("url"), pod.url); + addParam<>(jo, QStringLiteral("key"), pod.key); + addParam<>(jo, QStringLiteral("iv"), pod.iv); + addParam<>(jo, QStringLiteral("hashes"), pod.hashes); + addParam<>(jo, QStringLiteral("v"), pod.v); +} + +void JsonObjectConverter::fillFrom(const QJsonObject& jo, + EncryptedFile& pod) +{ + fromJson(jo.value("url"_ls), pod.url); + fromJson(jo.value("key"_ls), pod.key); + fromJson(jo.value("iv"_ls), pod.iv); + fromJson(jo.value("hashes"_ls), pod.hashes); + fromJson(jo.value("v"_ls), pod.v); +} + +void JsonObjectConverter::dumpTo(QJsonObject &jo, const JWK &pod) +{ + addParam<>(jo, QStringLiteral("kty"), pod.kty); + addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); + addParam<>(jo, QStringLiteral("alg"), pod.alg); + addParam<>(jo, QStringLiteral("k"), pod.k); + addParam<>(jo, QStringLiteral("ext"), pod.ext); +} + +void JsonObjectConverter::fillFrom(const QJsonObject &jo, JWK &pod) +{ + fromJson(jo.value("kty"_ls), pod.kty); + fromJson(jo.value("key_ops"_ls), pod.keyOps); + fromJson(jo.value("alg"_ls), pod.alg); + fromJson(jo.value("k"_ls), pod.k); + fromJson(jo.value("ext"_ls), pod.ext); } diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 43bafc49..0558563f 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -49,42 +49,14 @@ public: }; template <> -struct JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const EncryptedFile& pod) - { - addParam<>(jo, QStringLiteral("url"), pod.url); - addParam<>(jo, QStringLiteral("key"), pod.key); - addParam<>(jo, QStringLiteral("iv"), pod.iv); - addParam<>(jo, QStringLiteral("hashes"), pod.hashes); - addParam<>(jo, QStringLiteral("v"), pod.v); - } - static void fillFrom(const QJsonObject& jo, EncryptedFile& pod) - { - fromJson(jo.value("url"_ls), pod.url); - fromJson(jo.value("key"_ls), pod.key); - fromJson(jo.value("iv"_ls), pod.iv); - fromJson(jo.value("hashes"_ls), pod.hashes); - fromJson(jo.value("v"_ls), pod.v); - } +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const EncryptedFile& pod); + static void fillFrom(const QJsonObject& jo, EncryptedFile& pod); }; template <> -struct JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const JWK& pod) - { - addParam<>(jo, QStringLiteral("kty"), pod.kty); - addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); - addParam<>(jo, QStringLiteral("alg"), pod.alg); - addParam<>(jo, QStringLiteral("k"), pod.k); - addParam<>(jo, QStringLiteral("ext"), pod.ext); - } - static void fillFrom(const QJsonObject& jo, JWK& pod) - { - fromJson(jo.value("kty"_ls), pod.kty); - fromJson(jo.value("key_ops"_ls), pod.keyOps); - fromJson(jo.value("alg"_ls), pod.alg); - fromJson(jo.value("k"_ls), pod.k); - fromJson(jo.value("ext"_ls), pod.ext); - } +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const JWK& pod); + static void fillFrom(const QJsonObject& jo, JWK& pod); }; } // namespace Quotient diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 634e5fb9..d00fc5f4 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -127,7 +127,7 @@ BaseJob::Status DownloadFileJob::prepareResult() QByteArray encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = file.decryptFile(encrypted); + const auto decrypted = file.decryptFile(encrypted); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -149,10 +149,10 @@ BaseJob::Status DownloadFileJob::prepareResult() #ifdef Quotient_E2EE_ENABLED if (d->encryptedFile.has_value()) { d->tempFile->seek(0); - auto encrypted = d->tempFile->readAll(); + const auto encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = file.decryptFile(encrypted); + const auto decrypted = file.decryptFile(encrypted); d->tempFile->write(decrypted); } else { #endif -- cgit v1.2.3