From 0f71c3418eba6af0bddd138aecf0e9032e48255e Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Mon, 11 Dec 2023 08:42:01 +0100 Subject: [PATCH] allow user to migrate to a new certificate when needed Signed-off-by: Matthieu Gallien --- src/gui/accountsettings.cpp | 23 ++++++++++ src/gui/accountsettings.h | 3 ++ src/gui/tray/activitylistmodel.cpp | 6 +++ src/gui/tray/activitylistmodel.h | 2 + src/gui/tray/usermodel.cpp | 15 ++++++- src/libsync/account.cpp | 3 ++ src/libsync/account.h | 1 + src/libsync/clientsideencryption.cpp | 43 ++++++++++++++++++- src/libsync/clientsideencryption.h | 3 ++ .../clientsideencryptiontokenselector.cpp | 6 +++ .../clientsideencryptiontokenselector.h | 2 + 11 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 44959b6925bf6..419e7ce54fbcc 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -69,6 +69,7 @@ constexpr auto e2eUiActionIdKey = "id"; constexpr auto e2EeUiActionEnableEncryptionId = "enable_encryption"; constexpr auto e2EeUiActionDisableEncryptionId = "disable_encryption"; constexpr auto e2EeUiActionDisplayMnemonicId = "display_mnemonic"; +constexpr auto e2EeUiActionMigrateCertificateId = "migrate_certificate"; } namespace OCC { @@ -274,6 +275,10 @@ void AccountSettings::slotE2eEncryptionMnemonicReady() disableEncryptionForAccount(_accountState->account()); }); + if (_accountState->account()->e2e()->userCertificateNeedsMigration()) { + slotE2eEncryptionCertificateNeedMigration(); + } + if (!_accountState->account()->e2e()->getMnemonic().isEmpty()) { const auto actionDisplayMnemonic = addActionToEncryptionMessage(tr("Display mnemonic"), e2EeUiActionDisplayMnemonicId); connect(actionDisplayMnemonic, &QAction::triggered, this, [this]() { @@ -1098,6 +1103,16 @@ void AccountSettings::disableEncryptionForAccount(const AccountPtr &account) con } } +void AccountSettings::migrateCertificateForAccount(const AccountPtr &account) +{ + for (const auto action : _ui->encryptionMessage->actions()) { + _ui->encryptionMessage->removeAction(action); + } + + account->e2e()->migrateCertificate(this, account); + slotE2eEncryptionGenerateKeys(); +} + void AccountSettings::showConnectionLabel(const QString &message, QStringList errors) { const auto errStyle = QLatin1String("color:#ffffff; background-color:#bb4d4d;padding:5px;" @@ -1475,6 +1490,14 @@ void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync() } } +void AccountSettings::slotE2eEncryptionCertificateNeedMigration() +{ + const auto actionMigrateCertificate = addActionToEncryptionMessage(tr("Migrate certificate to a new one"), e2EeUiActionMigrateCertificateId); + connect(actionMigrateCertificate, &QAction::triggered, this, [this] { + migrateCertificateForAccount(_accountState->account()); + }); +} + void AccountSettings::updateBlackListAndScheduleFolderSync(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist) const { folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList); diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h index 64908a10294c0..c4f471cbe7fae 100644 --- a/src/gui/accountsettings.h +++ b/src/gui/accountsettings.h @@ -112,6 +112,8 @@ protected slots: const QVector &roles); void slotPossiblyUnblacklistE2EeFoldersAndRestartSync(); + void slotE2eEncryptionCertificateNeedMigration(); + 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); @@ -119,6 +121,7 @@ private slots: private slots: void displayMnemonic(const QString &mnemonic); void disableEncryptionForAccount(const OCC::AccountPtr &account) const; + void migrateCertificateForAccount(const OCC::AccountPtr &account); void showConnectionLabel(const QString &message, QStringList errors = QStringList()); void openIgnoredFilesDialog(const QString & absFolderPath); void customizeStyle(); diff --git a/src/gui/tray/activitylistmodel.cpp b/src/gui/tray/activitylistmodel.cpp index 09336e0c8a443..9cc7cd3bd3fbb 100644 --- a/src/gui/tray/activitylistmodel.cpp +++ b/src/gui/tray/activitylistmodel.cpp @@ -41,6 +41,8 @@ Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg) ActivityListModel::ActivityListModel(QObject *parent) : QAbstractListModel(parent) { + connect(this, &ActivityListModel::showSettingsDialog, + Systray::instance(), &Systray::openSettings); } ActivityListModel::ActivityListModel(AccountState *accountState, @@ -48,6 +50,8 @@ ActivityListModel::ActivityListModel(AccountState *accountState, : QAbstractListModel(parent) , _accountState(accountState) { + connect(this, &ActivityListModel::showSettingsDialog, + Systray::instance(), &Systray::openSettings); if (_accountState) { connect(_accountState, &AccountState::stateChanged, this, &ActivityListModel::accountStateChanged); @@ -740,6 +744,8 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex) _currentInvalidFilenameDialog->open(); ownCloudGui::raiseDialog(_currentInvalidFilenameDialog); return; + } else if (activity._id == qHash(_accountState->account()->encryptionCertificateFingerprint())) { + Q_EMIT showSettingsDialog(); } if (!path.isEmpty()) { diff --git a/src/gui/tray/activitylistmodel.h b/src/gui/tray/activitylistmodel.h index 6251a12e9a1ff..c0dcb58ef3ebf 100644 --- a/src/gui/tray/activitylistmodel.h +++ b/src/gui/tray/activitylistmodel.h @@ -153,6 +153,8 @@ public slots: void interactiveActivityReceived(); + void showSettingsDialog(); + protected: [[nodiscard]] bool currentlyFetching() const; diff --git a/src/gui/tray/usermodel.cpp b/src/gui/tray/usermodel.cpp index da175d3e28bf8..fcc4b0bfd12c6 100644 --- a/src/gui/tray/usermodel.cpp +++ b/src/gui/tray/usermodel.cpp @@ -92,8 +92,21 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent) connect(_account->account().data(), &Account::capabilitiesChanged, this, &User::slotAccountCapabilitiesChangedRefreshGroupFolders); connect(_activityModel, &ActivityListModel::sendNotificationRequest, this, &User::slotSendNotificationRequest); - + connect(this, &User::sendReplyMessage, this, &User::slotSendReplyMessage); + + connect(_account->account().data(), &Account::userCertificateNeedsMigrationChanged, this, [this] () { + auto certificateNeedMigration = Activity{}; + certificateNeedMigration._type = Activity::NotificationType; + certificateNeedMigration._subject = tr("End-to-end certificate needs to be migrated to a new one"); + certificateNeedMigration._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate); + certificateNeedMigration._message = tr("Trigger the migration"); + certificateNeedMigration._accName = _account->account()->displayName(); + certificateNeedMigration._id = qHash(_account->account()->encryptionCertificateFingerprint()); + + _activityModel->addNotificationToActivityList(certificateNeedMigration); + showDesktopNotification(certificateNeedMigration); + }); } void User::checkNotifiedNotifications() diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index 2e26348b9a26a..3256ebad8947f 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -80,6 +80,9 @@ Account::Account(QObject *parent) _pushNotificationsReconnectTimer.setInterval(pushNotificationsReconnectInterval); connect(&_pushNotificationsReconnectTimer, &QTimer::timeout, this, &Account::trySetupPushNotifications); + + connect(&_e2e, &ClientSideEncryption::userCertificateNeedsMigrationChanged, + this, &Account::userCertificateNeedsMigrationChanged); } AccountPtr Account::create() diff --git a/src/libsync/account.h b/src/libsync/account.h index d80d2cde04eae..a3d2b706d9d97 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -384,6 +384,7 @@ public slots: void lockFileError(const QString&); void encryptionCertificateFingerprintChanged(); + void userCertificateNeedsMigrationChanged(); protected Q_SLOTS: void slotCredentialsFetched(); diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp index ee39e2ffea3f5..ff87d0dab143f 100644 --- a/src/libsync/clientsideencryption.cpp +++ b/src/libsync/clientsideencryption.cpp @@ -133,6 +133,34 @@ unsigned char* unsignedData(QByteArray& array) // data structures // +class Pkcs11Context { +public: + Pkcs11Context() + : _pkcsS11Ctx(PKCS11_CTX_new()) + { + } + + ~Pkcs11Context() + { + PKCS11_CTX_free(_pkcsS11Ctx); + } + + operator const PKCS11_CTX*() const + { + return _pkcsS11Ctx; + } + + operator PKCS11_CTX*() + { + return _pkcsS11Ctx; + } + +private: + Q_DISABLE_COPY(Pkcs11Context) + + PKCS11_CTX* _pkcsS11Ctx = nullptr; +}; + class CipherCtx { public: CipherCtx() @@ -1297,7 +1325,7 @@ void ClientSideEncryption::initializeHardwareTokenEncryption(QWidget *settingsDi continue; } - if (oneCertificateInformation.sha256Fingerprint() != _usbTokenInformation.sha256Fingerprint()) { + if (!_usbTokenInformation.sha256Fingerprint().isEmpty() && oneCertificateInformation.sha256Fingerprint() != _usbTokenInformation.sha256Fingerprint()) { qCInfo(lcCse()) << "skipping certificate from" << "with fingerprint" << oneCertificateInformation.sha256Fingerprint() << "different from" << _usbTokenInformation.sha256Fingerprint(); continue; } @@ -1316,6 +1344,10 @@ void ClientSideEncryption::initializeHardwareTokenEncryption(QWidget *settingsDi return; } + if (!canEncrypt()) { + Q_EMIT userCertificateNeedsMigrationChanged(); + } + saveCertificateIdentification(account); emit initializationFinished(); @@ -1688,6 +1720,10 @@ void ClientSideEncryption::forgetSensitiveData(const AccountPtr &account) return job; }; + if (!account->credentials()) { + return; + } + const auto user = account->credentials()->user(); const auto deletePrivateKeyJob = createDeleteJob(user + e2e_private); const auto deleteCertJob = createDeleteJob(user + e2e_cert); @@ -1707,6 +1743,11 @@ void ClientSideEncryption::forgetSensitiveData(const AccountPtr &account) Q_EMIT userCertificateNeedsMigrationChanged(); } +void ClientSideEncryption::migrateCertificate(QWidget *settingsDialog, const AccountPtr &account) +{ + _usbTokenInformation.clear(); +} + void ClientSideEncryption::handlePrivateKeyDeleted(const QKeychain::Job* const incoming) { const auto error = incoming->error(); diff --git a/src/libsync/clientsideencryption.h b/src/libsync/clientsideencryption.h index 02cd33250ef03..0a32c36f21d4c 100644 --- a/src/libsync/clientsideencryption.h +++ b/src/libsync/clientsideencryption.h @@ -252,6 +252,9 @@ public slots: const OCC::AccountPtr &account); void forgetSensitiveData(const OCC::AccountPtr &account); + void migrateCertificate(QWidget *settingsDialog, + const OCC::AccountPtr &account); + private slots: void generateKeyPair(const OCC::AccountPtr &account); void encryptPrivateKey(const OCC::AccountPtr &account); diff --git a/src/libsync/clientsideencryptiontokenselector.cpp b/src/libsync/clientsideencryptiontokenselector.cpp index 7596f6c82cf75..a9b9bfc46e251 100644 --- a/src/libsync/clientsideencryptiontokenselector.cpp +++ b/src/libsync/clientsideencryptiontokenselector.cpp @@ -122,6 +122,12 @@ QByteArray ClientSideEncryptionTokenSelector::sha256Fingerprint() const return _sha256Fingerprint; } +void ClientSideEncryptionTokenSelector::clear() +{ + _discoveredCertificates.clear(); + _sha256Fingerprint.clear(); +} + QFuture ClientSideEncryptionTokenSelector::searchForCertificates(const AccountPtr &account) { return QtConcurrent::run([this, account] () -> void { diff --git a/src/libsync/clientsideencryptiontokenselector.h b/src/libsync/clientsideencryptiontokenselector.h index 4b16fb819eed0..36ec320c366d9 100644 --- a/src/libsync/clientsideencryptiontokenselector.h +++ b/src/libsync/clientsideencryptiontokenselector.h @@ -43,6 +43,8 @@ class OWNCLOUDSYNC_EXPORT ClientSideEncryptionTokenSelector : public QObject [[nodiscard]] QByteArray sha256Fingerprint() const; + void clear(); + public slots: QFuture searchForCertificates(const OCC::AccountPtr &account);