Skip to content

Commit

Permalink
End-to-End Encryption V2. Implemented sharing between users. Automati…
Browse files Browse the repository at this point in the history
…c migration from 1.0 to 2.0(only for flat folders). Improved secure filedrop.

Signed-off-by: alex-z <[email protected]>
  • Loading branch information
allexzander committed Jan 29, 2024
1 parent 615d592 commit af61252
Show file tree
Hide file tree
Showing 75 changed files with 5,384 additions and 1,958 deletions.
15 changes: 15 additions & 0 deletions src/common/checksums.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@ Q_LOGGING_CATEGORY(lcChecksums, "nextcloud.sync.checksums", QtInfoMsg)

#define BUFSIZE qint64(500 * 1024) // 500 KiB

static QByteArray calcCryptoHash(const QByteArray &data, QCryptographicHash::Algorithm algo)

Check warning on line 94 in src/common/checksums.cpp

View workflow job for this annotation

GitHub Actions / build

src/common/checksums.cpp:94:19 [modernize-use-trailing-return-type]

use a trailing return type for this function

Check warning on line 94 in src/common/checksums.cpp

View workflow job for this annotation

GitHub Actions / build

src/common/checksums.cpp:94:34 [bugprone-easily-swappable-parameters]

2 adjacent parameters of 'calcCryptoHash' of similar type are easily swapped by mistake
{
if (data.isEmpty()) {
return {};
}
QCryptographicHash crypto(algo);
crypto.addData(data);
return crypto.result().toHex();
}

QByteArray calcSha256(const QByteArray &data)

Check warning on line 104 in src/common/checksums.cpp

View workflow job for this annotation

GitHub Actions / build

src/common/checksums.cpp:104:12 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
return calcCryptoHash(data, QCryptographicHash::Sha256);
}

QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &checksum)
{
if (checksumType.isEmpty() || checksum.isEmpty())
Expand Down
2 changes: 2 additions & 0 deletions src/common/checksums.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ OCSYNC_EXPORT QByteArray parseChecksumHeaderType(const QByteArray &header);
/// Checks OWNCLOUD_DISABLE_CHECKSUM_UPLOAD
OCSYNC_EXPORT bool uploadChecksumEnabled();

OCSYNC_EXPORT QByteArray calcSha256(const QByteArray &data);

/**
* Computes the checksum of a file.
* \ingroup libsync
Expand Down
1 change: 1 addition & 0 deletions src/common/preparedsqlquerymanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class OCSYNC_EXPORT PreparedSqlQueryManager
GetE2EeLockedFolderQuery,
GetE2EeLockedFoldersQuery,
DeleteE2EeLockedFolderQuery,
ListAllTopLevelE2eeFoldersStatusLessThanQuery,

PreparedQueryCount
};
Expand Down
102 changes: 102 additions & 0 deletions src/common/syncjournaldb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,108 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
return {};
}

bool SyncJournalDb::getRootE2eFolderRecord(const QString &remoteFolderPath, SyncJournalFileRecord *rec)
{
Q_ASSERT(rec);
rec->_path.clear();
Q_ASSERT(!rec->isValid());

Q_ASSERT(!remoteFolderPath.isEmpty());

Q_ASSERT(!remoteFolderPath.isEmpty() && remoteFolderPath != QStringLiteral("/"));
if (remoteFolderPath.isEmpty() || remoteFolderPath == QStringLiteral("/")) {
qCWarning(lcDb) << "Invalid folder path!";
return false;
}

auto remoteFolderPathSplit = remoteFolderPath.split(QLatin1Char('/'), Qt::SkipEmptyParts);

if (remoteFolderPathSplit.isEmpty()) {
qCWarning(lcDb) << "Invalid folder path!";
return false;
}

while (!remoteFolderPathSplit.isEmpty()) {
const auto result = getFileRecord(remoteFolderPathSplit.join(QLatin1Char('/')), rec);
if (!result) {
return false;
}
if (rec->isE2eEncrypted() && rec->_e2eMangledName.isEmpty()) {
// it's a toplevel folder record
return true;
}
remoteFolderPathSplit.removeLast();
}

return true;
}

bool SyncJournalDb::listAllE2eeFoldersWithEncryptionStatusLessThan(const int status, const std::function<void(const SyncJournalFileRecord &)> &rowCallback)
{
QMutexLocker locker(&_mutex);

if (_metadataTableIsEmpty)
return true;

if (!checkConnect())
return false;
const auto query = _queryManager.get(PreparedSqlQueryManager::ListAllTopLevelE2eeFoldersStatusLessThanQuery,
QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE type == 2 AND isE2eEncrypted >= ?1 AND isE2eEncrypted < ?2 ORDER BY path||'/' ASC"),
_db);
if (!query) {
return false;
}
query->bindValue(1, SyncJournalFileRecord::EncryptionStatus::Encrypted);
query->bindValue(2, status);

if (!query->exec())
return false;

forever {
auto next = query->next();
if (!next.ok)
return false;
if (!next.hasData)
break;

SyncJournalFileRecord rec;
fillFileRecordFromGetQuery(rec, *query);

if (rec._type == ItemTypeSkip) {
continue;
}

rowCallback(rec);
}

return true;
}

bool SyncJournalDb::findEncryptedAncestorForRecord(const QString &filename, SyncJournalFileRecord *rec)
{
Q_ASSERT(rec);
rec->_path.clear();
Q_ASSERT(!rec->isValid());

const auto slashPosition = filename.lastIndexOf(QLatin1Char('/'));
const auto parentPath = slashPosition >= 0 ? filename.left(slashPosition) : QString();

auto pathComponents = parentPath.split(QLatin1Char('/'));
while (!pathComponents.isEmpty()) {
const auto pathCompontentsJointed = pathComponents.join(QLatin1Char('/'));
if (!getFileRecord(pathCompontentsJointed, rec)) {
qCDebug(lcDb) << "could not get file from local DB" << pathCompontentsJointed;
return false;
}

if (rec->isValid() && rec->isE2eEncrypted()) {
break;
}
pathComponents.removeLast();
}
return true;
}

void SyncJournalDb::keyValueStoreSet(const QString &key, QVariant value)
{
QMutexLocker locker(&_mutex);
Expand Down
3 changes: 3 additions & 0 deletions src/common/syncjournaldb.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject
[[nodiscard]] bool getFilesBelowPath(const QByteArray &path, const std::function<void(const SyncJournalFileRecord&)> &rowCallback);
[[nodiscard]] bool listFilesInPath(const QByteArray &path, const std::function<void(const SyncJournalFileRecord&)> &rowCallback);
[[nodiscard]] Result<void, QString> setFileRecord(const SyncJournalFileRecord &record);
[[nodiscard]] bool getRootE2eFolderRecord(const QString &remoteFolderPath, SyncJournalFileRecord *rec);
[[nodiscard]] bool listAllE2eeFoldersWithEncryptionStatusLessThan(const int status, const std::function<void(const SyncJournalFileRecord &)> &rowCallback);
[[nodiscard]] bool findEncryptedAncestorForRecord(const QString &filename, SyncJournalFileRecord *rec);

void keyValueStoreSet(const QString &key, QVariant value);
[[nodiscard]] qint64 keyValueStoreGetInt(const QString &key, qint64 defaultValue);
Expand Down
6 changes: 5 additions & 1 deletion src/csync/csync.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ class SyncJournalFileRecord;

namespace EncryptionStatusEnums {

Q_NAMESPACE
OCSYNC_EXPORT Q_NAMESPACE

enum class ItemEncryptionStatus : int {
NotEncrypted = 0,
Encrypted = 1,
EncryptedMigratedV1_2 = 2,
EncryptedMigratedV2_0 = 3,
};

Q_ENUM_NS(ItemEncryptionStatus)
Expand All @@ -65,6 +66,7 @@ enum class JournalDbEncryptionStatus : int {
Encrypted = 1,
EncryptedMigratedV1_2Invalid = 2,
EncryptedMigratedV1_2 = 3,
EncryptedMigratedV2_0 = 4,
};

Q_ENUM_NS(JournalDbEncryptionStatus)
Expand All @@ -73,6 +75,8 @@ ItemEncryptionStatus fromDbEncryptionStatus(JournalDbEncryptionStatus encryption

JournalDbEncryptionStatus toDbEncryptionStatus(ItemEncryptionStatus encryptionStatus);

ItemEncryptionStatus fromEndToEndEncryptionApiVersion(const double version);

}

}
Expand Down
3 changes: 2 additions & 1 deletion src/gui/accountsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,8 @@ void AccountSettings::slotMarkSubfolderEncrypted(FolderStatusModel::SubFolderInf
Q_ASSERT(!path.startsWith('/') && path.endsWith('/'));
// But EncryptFolderJob expects directory path Foo/Bar convention
const auto choppedPath = path.chopped(1);
auto job = new OCC::EncryptFolderJob(accountsState()->account(), folder->journalDb(), choppedPath, fileId, this);
auto job = new OCC::EncryptFolderJob(accountsState()->account(), folder->journalDb(), choppedPath, fileId);
job->setParent(this);
job->setProperty(propertyFolder, QVariant::fromValue(folder));
job->setProperty(propertyPath, QVariant::fromValue(path));
connect(job, &OCC::EncryptFolderJob::finished, this, &AccountSettings::slotEncryptFolderFinished);
Expand Down
2 changes: 1 addition & 1 deletion src/gui/filedetails/ShareView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ ColumnLayout {
Layout.rightMargin: root.horizontalPadding

visible: root.userGroupSharingPossible
enabled: visible && !root.loading
enabled: visible && !root.loading && !root.shareModel.isShareDisabledEncryptedFolder && !shareeSearchField.isShareeFetchOngoing

accountState: root.accountState
shareItemIsFolder: root.fileDetails && root.fileDetails.isFolder
Expand Down
4 changes: 2 additions & 2 deletions src/gui/filedetails/ShareeSearchField.qml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ TextField {
property var accountState: ({})
property bool shareItemIsFolder: false
property var shareeBlocklist: ({})
property bool isShareeFetchOngoing: shareeModel.fetchOngoing
property ShareeModel shareeModel: ShareeModel {
accountState: root.accountState
shareItemIsFolder: root.shareItemIsFolder
Expand All @@ -44,9 +45,8 @@ TextField {
shareeListView.count > 0 ? suggestionsPopup.open() : suggestionsPopup.close();
}

placeholderText: qsTr("Search for users or groups…")
placeholderText: enabled ? qsTr("Search for users or groups…") : qsTr("Sharing is not available for this folder")
placeholderTextColor: placeholderColor
enabled: !shareeModel.fetchOngoing

onActiveFocusChanged: triggerSuggestionsVisibility()
onTextChanged: triggerSuggestionsVisibility()
Expand Down
Loading

0 comments on commit af61252

Please sign in to comment.