mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-26 23:28:14 +03:00
Fix security vulnerability when receiving empty metadataKeys from the server.
Signed-off-by: alex-z <blackslayer4@gmail.com>
This commit is contained in:
parent
4ee22a91cc
commit
260ba0be46
7 changed files with 80 additions and 46 deletions
|
@ -1419,6 +1419,12 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata)
|
|||
QJsonDocument metaDataDoc = QJsonDocument::fromJson(metaDataStr.toLocal8Bit());
|
||||
QJsonObject metadataObj = metaDataDoc.object()["metadata"].toObject();
|
||||
QJsonObject metadataKeys = metadataObj["metadataKeys"].toObject();
|
||||
|
||||
if (metadataKeys.isEmpty()) {
|
||||
qCDebug(lcCse()) << "Could not setup existing metadata with missing metadataKeys!";
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray sharing = metadataObj["sharing"].toString().toLocal8Bit();
|
||||
QJsonObject files = metaDataDoc.object()["files"].toObject();
|
||||
|
||||
|
@ -1447,14 +1453,19 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata)
|
|||
// Cool, We actually have the key, we can decrypt the rest of the metadata.
|
||||
qCDebug(lcCse) << "Sharing: " << sharing;
|
||||
if (sharing.size()) {
|
||||
auto sharingDecrypted = decryptJsonObject(sharing, _metadataKeys.last());
|
||||
qCDebug(lcCse) << "Sharing Decrypted" << sharingDecrypted;
|
||||
const auto metaDataKey = !_metadataKeys.isEmpty() ? _metadataKeys.last() : QByteArray{};
|
||||
if (metaDataKey.isEmpty()) {
|
||||
qCDebug(lcCse) << "Failed to decrypt sharing! Empty metadata key!";
|
||||
} else {
|
||||
auto sharingDecrypted = decryptJsonObject(sharing, metaDataKey);
|
||||
qCDebug(lcCse) << "Sharing Decrypted" << sharingDecrypted;
|
||||
|
||||
//Sharing is also a JSON object, so extract it and populate.
|
||||
auto sharingDoc = QJsonDocument::fromJson(sharingDecrypted);
|
||||
auto sharingObj = sharingDoc.object();
|
||||
for (auto it = sharingObj.constBegin(), end = sharingObj.constEnd(); it != end; it++) {
|
||||
_sharing.push_back({it.key(), it.value().toString()});
|
||||
// Sharing is also a JSON object, so extract it and populate.
|
||||
auto sharingDoc = QJsonDocument::fromJson(sharingDecrypted);
|
||||
auto sharingObj = sharingDoc.object();
|
||||
for (auto it = sharingObj.constBegin(), end = sharingObj.constEnd(); it != end; it++) {
|
||||
_sharing.push_back({it.key(), it.value().toString()});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCDebug(lcCse) << "Skipping sharing section since it is empty";
|
||||
|
@ -1470,9 +1481,9 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata)
|
|||
file.initializationVector = QByteArray::fromBase64(fileObj["initializationVector"].toString().toLocal8Bit());
|
||||
|
||||
//Decrypt encrypted part
|
||||
QByteArray key = _metadataKeys[file.metadataKey];
|
||||
const auto key = _metadataKeys.value(file.metadataKey, {});
|
||||
auto encryptedFile = fileObj["encrypted"].toString().toLocal8Bit();
|
||||
auto decryptedFile = decryptJsonObject(encryptedFile, key);
|
||||
auto decryptedFile = !key.isEmpty() ? decryptJsonObject(encryptedFile, key) : QByteArray{};
|
||||
auto decryptedFileDoc = QJsonDocument::fromJson(decryptedFile);
|
||||
auto decryptedFileObj = decryptedFileDoc.object();
|
||||
|
||||
|
@ -1532,6 +1543,11 @@ QByteArray FolderMetadata::decryptJsonObject(const QByteArray& encryptedMetadata
|
|||
return EncryptionHelper::decryptStringSymmetric(pass, encryptedMetadata);
|
||||
}
|
||||
|
||||
bool FolderMetadata::isMetadataSetup() const
|
||||
{
|
||||
return !_metadataKeys.isEmpty();
|
||||
}
|
||||
|
||||
void FolderMetadata::setupEmptyMetadata() {
|
||||
qCDebug(lcCse) << "Settint up empty metadata";
|
||||
QByteArray newMetadataPass = EncryptionHelper::generateRandom(16);
|
||||
|
@ -1546,6 +1562,11 @@ void FolderMetadata::setupEmptyMetadata() {
|
|||
QByteArray FolderMetadata::encryptedMetadata() {
|
||||
qCDebug(lcCse) << "Generating metadata";
|
||||
|
||||
if (_metadataKeys.isEmpty()) {
|
||||
qCDebug(lcCse) << "Metadata generation failed! Empty metadata key!";
|
||||
return {};
|
||||
}
|
||||
|
||||
QJsonObject metadataKeys;
|
||||
for (auto it = _metadataKeys.constBegin(), end = _metadataKeys.constEnd(); it != end; it++) {
|
||||
/*
|
||||
|
@ -1556,16 +1577,6 @@ QByteArray FolderMetadata::encryptedMetadata() {
|
|||
metadataKeys.insert(QString::number(it.key()), QString(encryptedKey));
|
||||
}
|
||||
|
||||
/* NO SHARING IN V1
|
||||
QJsonObject recepients;
|
||||
for (auto it = _sharing.constBegin(), end = _sharing.constEnd(); it != end; it++) {
|
||||
recepients.insert(it->first, it->second);
|
||||
}
|
||||
QJsonDocument recepientDoc;
|
||||
recepientDoc.setObject(recepients);
|
||||
QString sharingEncrypted = encryptJsonObject(recepientDoc.toJson(QJsonDocument::Compact), _metadataKeys.last());
|
||||
*/
|
||||
|
||||
QJsonObject metadata = {
|
||||
{"metadataKeys", metadataKeys},
|
||||
// {"sharing", sharingEncrypted},
|
||||
|
|
|
@ -183,6 +183,7 @@ public:
|
|||
void removeEncryptedFile(const EncryptedFile& f);
|
||||
void removeAllEncryptedFiles();
|
||||
[[nodiscard]] QVector<EncryptedFile> files() const;
|
||||
[[nodiscard]] bool isMetadataSetup() const;
|
||||
|
||||
|
||||
private:
|
||||
|
|
|
@ -74,17 +74,19 @@ void PropagateDownloadEncrypted::checkFolderEncryptedMetadata(const QJsonDocumen
|
|||
<< _item->_instruction << _item->_file << _item->_encryptedFileName;
|
||||
const QString filename = _info.fileName();
|
||||
auto meta = new FolderMetadata(_propagator->account(), json.toJson(QJsonDocument::Compact));
|
||||
const QVector<EncryptedFile> files = meta->files();
|
||||
if (meta->isMetadataSetup()) {
|
||||
const QVector<EncryptedFile> files = meta->files();
|
||||
|
||||
const QString encryptedFilename = _item->_encryptedFileName.section(QLatin1Char('/'), -1);
|
||||
for (const EncryptedFile &file : files) {
|
||||
if (encryptedFilename == file.encryptedFilename) {
|
||||
_encryptedInfo = file;
|
||||
const QString encryptedFilename = _item->_encryptedFileName.section(QLatin1Char('/'), -1);
|
||||
for (const EncryptedFile &file : files) {
|
||||
if (encryptedFilename == file.encryptedFilename) {
|
||||
_encryptedInfo = file;
|
||||
|
||||
qCDebug(lcPropagateDownloadEncrypted) << "Found matching encrypted metadata for file, starting download";
|
||||
emit fileMetadataFound();
|
||||
return;
|
||||
}
|
||||
qCDebug(lcPropagateDownloadEncrypted) << "Found matching encrypted metadata for file, starting download";
|
||||
emit fileMetadataFound();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit failed();
|
||||
|
|
|
@ -53,6 +53,11 @@ void PropagateRemoteDeleteEncrypted::slotFolderEncryptedMetadataReceived(const Q
|
|||
|
||||
FolderMetadata metadata(_propagator->account(), json.toJson(QJsonDocument::Compact), statusCode);
|
||||
|
||||
if (!metadata.isMetadataSetup()) {
|
||||
taskFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata Received, preparing it for removal of the file";
|
||||
|
||||
const QFileInfo info(_propagator->fullLocalPath(_item->_file));
|
||||
|
|
|
@ -83,6 +83,11 @@ void PropagateRemoteDeleteEncryptedRootFolder::slotFolderEncryptedMetadataReceiv
|
|||
|
||||
FolderMetadata metadata(_propagator->account(), json.toJson(QJsonDocument::Compact), statusCode);
|
||||
|
||||
if (!metadata.isMetadataSetup()) {
|
||||
taskFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "It's a root encrypted folder. Let's remove nested items first.";
|
||||
|
||||
metadata.removeAllEncryptedFiles();
|
||||
|
|
|
@ -120,10 +120,19 @@ void PropagateUploadEncrypted::slotFolderEncryptedMetadataError(const QByteArray
|
|||
void PropagateUploadEncrypted::slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode)
|
||||
{
|
||||
qCDebug(lcPropagateUploadEncrypted) << "Metadata Received, Preparing it for the new file." << json.toVariant();
|
||||
|
||||
// Encrypt File!
|
||||
_metadata = new FolderMetadata(_propagator->account(), json.toJson(QJsonDocument::Compact), statusCode);
|
||||
|
||||
if (!_metadata->isMetadataSetup()) {
|
||||
if (_isFolderLocked) {
|
||||
connect(this, &PropagateUploadEncrypted::folderUnlocked, this, &PropagateUploadEncrypted::error);
|
||||
unlockFolder();
|
||||
} else {
|
||||
emit error();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QFileInfo info(_propagator->fullLocalPath(_item->_file));
|
||||
const QString fileName = info.fileName();
|
||||
|
||||
|
@ -197,15 +206,13 @@ void PropagateUploadEncrypted::slotFolderEncryptedMetadataReceived(const QJsonDo
|
|||
|
||||
if (statusCode == 404) {
|
||||
auto job = new StoreMetaDataApiJob(_propagator->account(),
|
||||
_folderId,
|
||||
_metadata->encryptedMetadata());
|
||||
_folderId, _metadata->encryptedMetadata());
|
||||
connect(job, &StoreMetaDataApiJob::success, this, &PropagateUploadEncrypted::slotUpdateMetadataSuccess);
|
||||
connect(job, &StoreMetaDataApiJob::error, this, &PropagateUploadEncrypted::slotUpdateMetadataError);
|
||||
job->start();
|
||||
} else {
|
||||
auto job = new UpdateMetadataApiJob(_propagator->account(),
|
||||
_folderId,
|
||||
_metadata->encryptedMetadata(),
|
||||
_folderId, _metadata->encryptedMetadata(),
|
||||
_folderToken);
|
||||
|
||||
connect(job, &UpdateMetadataApiJob::success, this, &PropagateUploadEncrypted::slotUpdateMetadataSuccess);
|
||||
|
|
|
@ -200,23 +200,26 @@ void OCC::HydrationJob::slotCheckFolderEncryptedMetadata(const QJsonDocument &js
|
|||
qCDebug(lcHydration) << "Metadata Received reading" << e2eMangledName();
|
||||
const QString filename = e2eMangledName();
|
||||
auto meta = new FolderMetadata(_account, json.toJson(QJsonDocument::Compact));
|
||||
const QVector<EncryptedFile> files = meta->files();
|
||||
|
||||
EncryptedFile encryptedInfo = {};
|
||||
if (meta->isMetadataSetup()) {
|
||||
const QVector<EncryptedFile> files = meta->files();
|
||||
|
||||
const QString encryptedFileExactName = e2eMangledName().section(QLatin1Char('/'), -1);
|
||||
for (const EncryptedFile &file : files) {
|
||||
if (encryptedFileExactName == file.encryptedFilename) {
|
||||
EncryptedFile encryptedInfo = file;
|
||||
encryptedInfo = file;
|
||||
EncryptedFile encryptedInfo = {};
|
||||
|
||||
qCDebug(lcHydration) << "Found matching encrypted metadata for file, starting download" << _requestId << _folderPath;
|
||||
_transferDataSocket = _transferDataServer->nextPendingConnection();
|
||||
_job = new GETEncryptedFileJob(_account, _remotePath + e2eMangledName(), _transferDataSocket, {}, {}, 0, encryptedInfo, this);
|
||||
const QString encryptedFileExactName = e2eMangledName().section(QLatin1Char('/'), -1);
|
||||
for (const EncryptedFile &file : files) {
|
||||
if (encryptedFileExactName == file.encryptedFilename) {
|
||||
EncryptedFile encryptedInfo = file;
|
||||
encryptedInfo = file;
|
||||
|
||||
connect(qobject_cast<GETEncryptedFileJob *>(_job), &GETEncryptedFileJob::finishedSignal, this, &HydrationJob::onGetFinished);
|
||||
_job->start();
|
||||
return;
|
||||
qCDebug(lcHydration) << "Found matching encrypted metadata for file, starting download" << _requestId << _folderPath;
|
||||
_transferDataSocket = _transferDataServer->nextPendingConnection();
|
||||
_job = new GETEncryptedFileJob(_account, _remotePath + e2eMangledName(), _transferDataSocket, {}, {}, 0, encryptedInfo, this);
|
||||
|
||||
connect(qobject_cast<GETEncryptedFileJob *>(_job), &GETEncryptedFileJob::finishedSignal, this, &HydrationJob::onGetFinished);
|
||||
_job->start();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue