Skip to content

Commit

Permalink
Merge pull request #5167 from nextcloud/feature/disable-e2ee
Browse files Browse the repository at this point in the history
Add ability to disable E2EE
  • Loading branch information
claucambra authored Jan 24, 2023
2 parents bca3d94 + 3e00df4 commit 8ffaf34
Show file tree
Hide file tree
Showing 16 changed files with 443 additions and 79 deletions.
1 change: 0 additions & 1 deletion src/csync/csync_exclude.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ enum CSYNC_EXCLUDE_TYPE {
CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED,
CSYNC_FILE_EXCLUDE_LEADING_SPACE,
CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE,
CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED,
};

class ExcludedFilesTest;
Expand Down
74 changes: 63 additions & 11 deletions src/gui/accountsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ constexpr auto propertyFolder = "folder";
constexpr auto propertyPath = "path";
constexpr auto e2eUiActionIdKey = "id";
constexpr auto e2EeUiActionEnableEncryptionId = "enable_encryption";
constexpr auto e2EeUiActionDisableEncryptionId = "disable_encryption";
constexpr auto e2EeUiActionDisplayMnemonicId = "display_mnemonic";
}

Expand Down Expand Up @@ -243,7 +244,12 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)

void AccountSettings::slotE2eEncryptionMnemonicReady()
{
auto *const actionDisplayMnemonic = addActionToEncryptionMessage(tr("Display mnemonic"), e2EeUiActionDisplayMnemonicId);
const auto actionDisableEncryption = addActionToEncryptionMessage(tr("Disable encryption"), e2EeUiActionDisableEncryptionId);
connect(actionDisableEncryption, &QAction::triggered, this, [this] {
disableEncryptionForAccount(_accountState->account());
});

const auto actionDisplayMnemonic = addActionToEncryptionMessage(tr("Display mnemonic"), e2EeUiActionDisplayMnemonicId);
connect(actionDisplayMnemonic, &QAction::triggered, this, [this]() {
displayMnemonic(_accountState->account()->e2e()->_mnemonic);
});
Expand Down Expand Up @@ -1024,6 +1030,31 @@ void AccountSettings::displayMnemonic(const QString &mnemonic)
widget.exec();
}

void AccountSettings::disableEncryptionForAccount(const AccountPtr &account) const
{
QMessageBox dialog;
dialog.setWindowTitle(tr("Disable end-to-end encryption"));
dialog.setText(tr("Disable end-to-end encryption for %1?").arg(account->davUser()));
dialog.setInformativeText(tr("Removing end-to-end encryption will remove locally-synced files that are encrypted."
"<br>"
"Encrypted files will remain on the server."));
dialog.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
dialog.setDefaultButton(QMessageBox::Ok);
dialog.adjustSize();

const auto ret = dialog.exec();
switch(ret) {
case QMessageBox::Ok:
connect(account->e2e(), &ClientSideEncryption::sensitiveDataForgotten,
this, &AccountSettings::resetE2eEncryption);
account->e2e()->forgetSensitiveData(account);
break;
case QMessageBox::Cancel:
break;
Q_UNREACHABLE();
}
}

void AccountSettings::showConnectionLabel(const QString &message, QStringList errors)
{
const auto errStyle = QLatin1String("color:#ffffff; background-color:#bb4d4d;padding:5px;"
Expand Down Expand Up @@ -1257,16 +1288,21 @@ void AccountSettings::slotAccountStateChanged()
refreshSelectiveSyncStatus();

if (state == AccountState::State::Connected) {
/* TODO: We should probably do something better here.
* Verify if the user has a private key already uploaded to the server,
* if it has, do not offer to create one.
*/
qCInfo(lcAccountSettings) << "Account" << accountsState()->account()->displayName()
<< "Client Side Encryption" << accountsState()->account()->capabilities().clientSideEncryptionAvailable();

if (_accountState->account()->capabilities().clientSideEncryptionAvailable()) {
_ui->encryptionMessage->show();
}
checkClientSideEncryptionState();
}
}

void AccountSettings::checkClientSideEncryptionState()
{
/* TODO: We should probably do something better here.
* Verify if the user has a private key already uploaded to the server,
* if it has, do not offer to create one.
*/
qCInfo(lcAccountSettings) << "Account" << accountsState()->account()->displayName()
<< "Client Side Encryption" << accountsState()->account()->capabilities().clientSideEncryptionAvailable();

if (_accountState->account()->capabilities().clientSideEncryptionAvailable()) {
_ui->encryptionMessage->show();
}
}

Expand Down Expand Up @@ -1569,6 +1605,22 @@ void AccountSettings::initializeE2eEncryption()
}
}

void AccountSettings::resetE2eEncryption()
{
for (const auto action : _ui->encryptionMessage->actions()) {
_ui->encryptionMessage->removeAction(action);
}
_ui->encryptionMessage->setText({});
_ui->encryptionMessage->setIcon({});
initializeE2eEncryption();
checkClientSideEncryptionState();

const auto account = _accountState->account();
if (account->e2e()->_mnemonic.isEmpty()) {
FolderMan::instance()->removeE2eFiles(account);
}
}

void AccountSettings::removeActionFromEncryptionMessage(const QString &actionId)
{
const auto foundEnableEncryptionActionIt = std::find_if(std::cbegin(_ui->encryptionMessage->actions()), std::cend(_ui->encryptionMessage->actions()), [&actionId](const QAction *action) {
Expand Down
13 changes: 8 additions & 5 deletions src/gui/accountsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,20 @@ private slots:
void updateBlackListAndScheduleFolderSync(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist) const;
void folderTerminateSyncAndUpdateBlackList(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist);

private:
private slots:
void displayMnemonic(const QString &mnemonic);
void showConnectionLabel(const QString &message,
QStringList errors = QStringList());
bool event(QEvent *) override;
void createAccountToolbox();
void disableEncryptionForAccount(const AccountPtr &account) const;
void showConnectionLabel(const QString &message, QStringList errors = QStringList());
void openIgnoredFilesDialog(const QString & absFolderPath);
void customizeStyle();

void initializeE2eEncryption();
void resetE2eEncryption();
void checkClientSideEncryptionState();
void removeActionFromEncryptionMessage(const QString &actionId);

private:
bool event(QEvent *) override;
QAction *addActionToEncryptionMessage(const QString &actionTitle, const QString &actionId);

/// Returns the alias of the selected folder, empty string if none
Expand Down
8 changes: 6 additions & 2 deletions src/gui/accountstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <memory>

class QSettings;
class FakeAccountState;

namespace OCC {

Expand Down Expand Up @@ -182,10 +183,10 @@ class AccountState : public QObject, public QSharedData
public slots:
/// Triggers a ping to the server to update state and
/// connection status and errors.
void checkConnectivity();
virtual void checkConnectivity();

private:
void setState(State state);
virtual void setState(State state);
void fetchNavigationApps();

int retryCount() const;
Expand Down Expand Up @@ -261,6 +262,9 @@ private Q_SLOTS:
QTimer _checkConnectionTimer;
QElapsedTimer _lastCheckConnectionTimer;

explicit AccountState() = default;

friend class ::FakeAccountState;
};

class AccountApp : public QObject
Expand Down
75 changes: 75 additions & 0 deletions src/gui/folder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,81 @@ void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction dir, std::functio
msgBox->open();
}

void Folder::removeLocalE2eFiles()
{
qCDebug(lcFolder) << "Removing local E2EE files";

const QDir folderRootDir(path());
QStringList e2eFoldersToBlacklist;
const auto couldGetFiles = _journal.getFilesBelowPath("", [this, &e2eFoldersToBlacklist, &folderRootDir](const SyncJournalFileRecord &rec) {
// We only want to add the root-most encrypted folder to the blacklist
if (rec.isValid() && rec._isE2eEncrypted && rec.isDirectory()) {
QDir pathDir(_canonicalLocalPath + rec.path());
bool parentPathEncrypted = false;

while (pathDir.cdUp() && pathDir != folderRootDir) {
SyncJournalFileRecord dirRec;
const auto currentCanonicalPath = pathDir.canonicalPath();

if (!_journal.getFileRecord(currentCanonicalPath, &dirRec)) {
qCWarning(lcFolder) << "Failed to get file record for" << currentCanonicalPath;
}

if (dirRec._isE2eEncrypted) {
parentPathEncrypted = true;
break;
}
}

if (!parentPathEncrypted) {
const auto pathAdjusted = rec._path.endsWith('/') ? rec._path : QString(rec._path + QStringLiteral("/"));
e2eFoldersToBlacklist.append(pathAdjusted);
}
}
});

if (!couldGetFiles) {
qCWarning(lcFolder) << "Could not fetch E2EE folders to blacklist in this folder:" << path();
return;
} else if (e2eFoldersToBlacklist.isEmpty()) {
qCWarning(lcFolder) << "No E2EE folders found at path" << path();
return;
}

qCInfo(lcFolder) << "About to blacklist: " << e2eFoldersToBlacklist;

bool ok = false;
const auto existingBlacklist = _journal.getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok);
Q_ASSERT(ok);

const auto existingBlacklistSet = existingBlacklist.toSet();
auto expandedBlacklistSet = existingBlacklist.toSet();

for (const auto &path : qAsConst(e2eFoldersToBlacklist)) {
expandedBlacklistSet.insert(path);
}

// same as in void FolderStatusModel::slotApplySelectiveSync()
// only start sync if blackList has changed
// database lists will get updated during discovery
const auto changes = (existingBlacklistSet - expandedBlacklistSet) + (expandedBlacklistSet - existingBlacklistSet);

_journal.setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, expandedBlacklistSet.values());
_journal.setSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, changes.values());

if (!changes.isEmpty()) {
_journal.setSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, QStringList());
if (isBusy()) {
slotTerminateSync();
}
for (const auto &it : changes) {
_journal.schedulePathForRemoteDiscovery(it);
schedulePathForLocalDiscovery(it);
}
FolderMan::instance()->scheduleFolderForImmediateSync(this);
}
}

QString Folder::fileFromLocalPath(const QString &localPath) const
{
return localPath.mid(cleanPath().length() + 1);
Expand Down
5 changes: 5 additions & 0 deletions src/gui/folder.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,11 @@ public slots:

void setSilenceErrorsUntilNextSync(bool silenceErrors);

/** Deletes local copies of E2EE files.
* Intended for clean-up after disabling E2EE for an account.
*/
void removeLocalE2eFiles();

private slots:
void slotSyncStarted();
void slotSyncFinished(bool);
Expand Down
10 changes: 10 additions & 0 deletions src/gui/folderman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,16 @@ void FolderMan::forceSyncForFolder(Folder *folder)
scheduleFolderNext(folder);
}

void FolderMan::removeE2eFiles(const AccountPtr &account) const
{
Q_ASSERT(account->e2e()->_mnemonic.isEmpty());
for (const auto folder : map()) {
if(folder->accountState()->account()->id() == account->id()) {
folder->removeLocalE2eFiles();
}
}
}

void FolderMan::slotScheduleAppRestart()
{
_appRestartRequired = true;
Expand Down
2 changes: 2 additions & 0 deletions src/gui/folderman.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ public slots:

void forceSyncForFolder(OCC::Folder *folder);

void removeE2eFiles(const AccountPtr &account) const;

private slots:
void slotFolderSyncPaused(OCC::Folder *, bool paused);
void slotFolderCanSyncChanged();
Expand Down
74 changes: 65 additions & 9 deletions src/libsync/clientsideencryption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1064,22 +1064,78 @@ void ClientSideEncryption::writeMnemonic(const AccountPtr &account)

void ClientSideEncryption::forgetSensitiveData(const AccountPtr &account)
{
_privateKey = QByteArray();
_certificate = QSslCertificate();
_publicKey = QSslKey();
_mnemonic = QString();

auto startDeleteJob = [account](QString user) {
const auto createDeleteJob = [account](const QString user) {
auto *job = new DeletePasswordJob(Theme::instance()->appName());
job->setInsecureFallback(false);
job->setKey(AbstractCredentials::keychainKey(account->url().toString(), user, account->id()));
job->start();
return job;
};

auto user = account->credentials()->user();
startDeleteJob(user + e2e_private);
startDeleteJob(user + e2e_cert);
startDeleteJob(user + e2e_mnemonic);
const auto user = account->credentials()->user();
const auto deletePrivateKeyJob = createDeleteJob(user + e2e_private);
const auto deleteCertJob = createDeleteJob(user + e2e_cert);
const auto deleteMnemonicJob = createDeleteJob(user + e2e_mnemonic);

connect(deletePrivateKeyJob, &DeletePasswordJob::finished, this, &ClientSideEncryption::handlePrivateKeyDeleted);
connect(deleteCertJob, &DeletePasswordJob::finished, this, &ClientSideEncryption::handleCertificateDeleted);
connect(deleteMnemonicJob, &DeletePasswordJob::finished, this, &ClientSideEncryption::handleMnemonicDeleted);
deletePrivateKeyJob->start();
deleteCertJob->start();
deleteMnemonicJob->start();
}

void ClientSideEncryption::handlePrivateKeyDeleted(const QKeychain::Job* const incoming)
{
if (incoming->error() != QKeychain::NoError) {
qCWarning(lcCse) << "Private key could not be deleted:" << incoming->errorString();
return;
}

qCDebug(lcCse) << "Private key successfully deleted from keychain. Clearing.";
_privateKey = QByteArray();
Q_EMIT privateKeyDeleted();
checkAllSensitiveDataDeleted();
}

void ClientSideEncryption::handleCertificateDeleted(const QKeychain::Job* const incoming)
{
if (incoming->error() != QKeychain::NoError) {
qCWarning(lcCse) << "Certificate could not be deleted:" << incoming->errorString();
return;
}

qCDebug(lcCse) << "Certificate successfully deleted from keychain. Clearing.";
_certificate = QSslCertificate();
Q_EMIT certificateDeleted();
checkAllSensitiveDataDeleted();
}

void ClientSideEncryption::handleMnemonicDeleted(const QKeychain::Job* const incoming)
{
if (incoming->error() != QKeychain::NoError) {
qCWarning(lcCse) << "Mnemonic could not be deleted:" << incoming->errorString();
return;
}

qCDebug(lcCse) << "Mnemonic successfully deleted from keychain. Clearing.";
_mnemonic = QString();
Q_EMIT mnemonicDeleted();
checkAllSensitiveDataDeleted();
}

void ClientSideEncryption::checkAllSensitiveDataDeleted()
{
if (_privateKey.isEmpty() && _certificate.isNull() && _mnemonic.isEmpty()) {
qCDebug(lcCse) << "All sensitive encryption data has been deleted.";
Q_EMIT sensitiveDataForgotten();
}

qCDebug(lcCse) << "Some sensitive data emaining:"
<< "Private key:" << _privateKey
<< "Certificate is null:" << _certificate.isNull()
<< "Mnemonic:" << _mnemonic;
}

void ClientSideEncryption::generateKeyPair(const AccountPtr &account)
Expand Down
Loading

0 comments on commit 8ffaf34

Please sign in to comment.