Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 95 additions & 16 deletions src/libsync/foldermetadata.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
Expand All @@ -9,6 +9,7 @@
#include "clientsideencryption.h"
#include "clientsideencryptionjobs.h"
#include <common/checksums.h>
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QSslCertificate>
Expand Down Expand Up @@ -49,6 +50,27 @@
}
}

bool FolderMetadata::isOriginalFilenameValid(const QString &originalFilename)
{
if (originalFilename.isEmpty()) {
return false;
}

if (originalFilename == QStringLiteral(".")
|| originalFilename == QStringLiteral("..")) {
return false;
}

if (originalFilename.contains(QLatin1Char('/'))
|| originalFilename.contains(QLatin1Char('\\'))
|| originalFilename.contains(QChar(0))) {
return false;
}

const auto slashPrefixedName = QStringLiteral("/") + originalFilename;
return QDir::cleanPath(slashPrefixedName) == slashPrefixedName;
}

bool FolderMetadata::EncryptedFile::isDirectory() const
{
return mimetype.isEmpty() || mimetype == QByteArrayLiteral("inode/directory") || mimetype == QByteArrayLiteral("httpd/unix-directory");
Expand Down Expand Up @@ -121,6 +143,11 @@
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return;
}
if (_existingMetadataVersion < MetadataVersion::Version2_0 && !_initialSignature.isEmpty()) {
qCWarning(lcCseMetadata()) << "Could not setup legacy metadata with a V2 signature.";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return;
}
if (_existingMetadataVersion < MetadataVersion::Version2_0) {
setupExistingMetadataLegacy(metadata);
return;
Expand Down Expand Up @@ -250,22 +277,39 @@
_counter = counterVariantFromJson.value<quint64>();
}

QVector<EncryptedFile> parsedFiles;
parsedFiles.reserve(files.size() + folders.size());

for (auto it = files.constBegin(), end = files.constEnd(); it != end; ++it) {
const auto parsedEncryptedFile = parseEncryptedFileFromJson(it.key(), it.value());
if (!parsedEncryptedFile.originalFilename.isEmpty()) {
_files.push_back(parsedEncryptedFile);
if (parsedEncryptedFile.originalFilename.isEmpty()) {
qCWarning(lcCseMetadata()) << "Could not setup metadata. Encrypted file" << it.key() << "metadata is invalid.";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return;
}
parsedFiles.push_back(parsedEncryptedFile);
}

for (auto it = folders.constBegin(); it != folders.constEnd(); ++it) {
const auto folderName = it.value().toString();
if (!folderName.isEmpty()) {
EncryptedFile file;
file.encryptedFilename = it.key();
file.originalFilename = folderName;
_files.push_back(file);
if (folderName.isEmpty()) {
qCWarning(lcCseMetadata()) << "Could not setup metadata. Encrypted folder" << it.key() << "metadata has an empty file name.";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return;
}

if (!isOriginalFilenameValid(folderName)) {
qCWarning(lcCseMetadata()) << "Could not setup metadata. Encrypted folder" << it.key() << "metadata has an invalid file name.";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return;
}

EncryptedFile file;
file.encryptedFilename = it.key();
file.originalFilename = folderName;
parsedFiles.push_back(file);
}
_files = parsedFiles;
_isMetadataValid = true;
}

Expand Down Expand Up @@ -349,12 +393,20 @@

const auto decryptedFileObj = decryptedFileDoc.object();

if (decryptedFileObj["filename"].toString().isEmpty()) {
qCWarning(lcCseMetadata) << "decrypted metadata" << decryptedFileDoc.toJson(QJsonDocument::Compact) << "skipping encrypted file" << file.encryptedFilename << "metadata has an empty file name";
continue;
const auto originalFilename = decryptedFileObj["filename"].toString();
if (originalFilename.isEmpty()) {
qCWarning(lcCseMetadata) << "decrypted metadata" << decryptedFileDoc.toJson(QJsonDocument::Compact) << "Could not setup legacy metadata. Encrypted file" << file.encryptedFilename << "metadata has an empty file name.";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return;
}

if (!isOriginalFilenameValid(originalFilename)) {
qCWarning(lcCseMetadata) << "Could not setup legacy metadata. Encrypted file" << file.encryptedFilename << "metadata has an invalid file name.";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return;
}

file.originalFilename = decryptedFileObj["filename"].toString();
file.originalFilename = originalFilename;
file.encryptionKey = QByteArray::fromBase64(decryptedFileObj["key"].toString().toLocal8Bit());
file.mimetype = decryptedFileObj["mimetype"].toString().toLocal8Bit();

Expand Down Expand Up @@ -508,10 +560,17 @@
FolderMetadata::EncryptedFile FolderMetadata::parseEncryptedFileFromJson(const QString &encryptedFilename, const QJsonValue &fileJSON) const
{
const auto fileObj = fileJSON.toObject();
if (fileObj["filename"].toString().isEmpty()) {
const auto originalFilename = fileObj["filename"].toString();
if (originalFilename.isEmpty()) {
qCWarning(lcCseMetadata()) << "skipping encrypted file" << encryptedFilename << "metadata has an empty file name";
return {};
}

if (!isOriginalFilenameValid(originalFilename)) {
qCWarning(lcCseMetadata()) << "skipping encrypted file" << encryptedFilename << "metadata has an invalid file name";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return {};
Comment thread
Rello marked this conversation as resolved.
}

EncryptedFile file;
file.encryptedFilename = encryptedFilename;
Expand All @@ -521,7 +580,7 @@
nonce = QByteArray::fromBase64(fileObj[nonceKey].toString().toLocal8Bit());
}
file.initializationVector = nonce;
file.originalFilename = fileObj["filename"].toString();
file.originalFilename = originalFilename;
file.encryptionKey = QByteArray::fromBase64(fileObj["key"].toString().toLocal8Bit());
file.mimetype = fileObj["mimetype"].toString().toLocal8Bit();

Expand All @@ -535,6 +594,11 @@

QJsonObject FolderMetadata::convertFileToJsonObject(const EncryptedFile *encryptedFile) const
{
if (!encryptedFile || !isOriginalFilenameValid(encryptedFile->originalFilename)) {
qCWarning(lcCseMetadata()) << "Metadata generation failed. Invalid original file name.";
return {};
}

QJsonObject file;
file.insert("key", QString(encryptedFile->encryptionKey.toBase64()));
file.insert("filename", encryptedFile->originalFilename);
Expand Down Expand Up @@ -728,6 +792,12 @@

QJsonObject files;
for (auto it = _files.constBegin(), end = _files.constEnd(); it != end; ++it) {
if (!isOriginalFilenameValid(it->originalFilename)) {
qCWarning(lcCseMetadata) << "Metadata generation failed. Invalid original file name for encrypted file" << it->encryptedFilename;
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return {};
}

QJsonObject encrypted;
encrypted.insert("key", QString(it->encryptionKey.toBase64()));
encrypted.insert("filename", it->originalFilename);
Expand Down Expand Up @@ -919,11 +989,17 @@
return metdataModified.toJson(QJsonDocument::Compact);
}

void FolderMetadata::addEncryptedFile(const EncryptedFile &f) {
bool FolderMetadata::addEncryptedFile(const EncryptedFile &f) {
Q_ASSERT(_isMetadataValid);
if (!_isMetadataValid) {
qCWarning(lcCseMetadata()) << "Could not add encrypted file to non-initialized metadata!";
return;
return false;
}

if (!isOriginalFilenameValid(f.originalFilename)) {
qCWarning(lcCseMetadata()) << "Could not add encrypted file with invalid original file name.";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return false;
}

for (int i = 0; i < _files.size(); ++i) {
Expand All @@ -934,6 +1010,7 @@
}

_files.append(f);
return true;
}

const QByteArray FolderMetadata::binaryMetadataKeyForDecryption() const
Expand Down Expand Up @@ -1006,7 +1083,9 @@
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
return false;
}
addEncryptedFile(parsedEncryptedFile);
if (!addEncryptedFile(parsedEncryptedFile)) {
return false;
}
}

_fileDropEntries.clear();
Expand Down
4 changes: 3 additions & 1 deletion src/libsync/foldermetadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class OWNCLOUDSYNC_EXPORT FolderMetadata : public QObject
static MetadataVersion setupVersionFromExistingMetadata(const QByteArray &metadata);

public slots:
void addEncryptedFile(const OCC::FolderMetadata::EncryptedFile &f);
[[nodiscard]] bool addEncryptedFile(const OCC::FolderMetadata::EncryptedFile &f);
void removeEncryptedFile(const OCC::FolderMetadata::EncryptedFile &f);
void removeAllEncryptedFiles();

Expand All @@ -172,6 +172,8 @@ public slots:

[[nodiscard]] QJsonObject convertFileToJsonObject(const EncryptedFile *encryptedFile) const;

[[nodiscard]] static bool isOriginalFilenameValid(const QString &originalFilename);

[[nodiscard]] MetadataVersion latestSupportedMetadataVersion() const;

[[nodiscard]] bool parseFileDropPart(const QJsonDocument &doc);
Expand Down
6 changes: 5 additions & 1 deletion src/libsync/propagateremotemkdir.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2014 ownCloud GmbH
Expand Down Expand Up @@ -210,7 +210,11 @@
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::finalized,
this, &PropagateRemoteMkdir::slotStartEncryptedMkcolJob);
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::error,
[]{ qCWarning(lcPropagateRemoteMkdir) << "Error setting up encryption."; });
this, [this] {
qCWarning(lcPropagateRemoteMkdir) << "Error setting up encryption.";
propagator()->_activeJobList.removeOne(this);
done(SyncFileItem::FatalError, tr("Failed to create encrypted folder."), ErrorCategory::GenericError);
});
_uploadEncryptedHelper->start();
}

Expand Down
6 changes: 5 additions & 1 deletion src/libsync/propagateuploadencrypted.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,11 @@ void PropagateUploadEncrypted::slotFetchMetadataJobFinished(int statusCode, cons

qCDebug(lcPropagateUploadEncrypted) << "Creating the metadata for the encrypted file.";

metadata->addEncryptedFile(encryptedFile);
if (!metadata->addEncryptedFile(encryptedFile)) {
qCWarning(lcPropagateUploadEncrypted()) << "There was an error encrypting the file, aborting upload. Invalid metadata file name.";
emit error();
Comment thread
Rello marked this conversation as resolved.
Comment thread
mgallien marked this conversation as resolved.
return;
}

qCDebug(lcPropagateUploadEncrypted) << "Metadata created, sending to the server.";

Expand Down
Loading
Loading