diff --git a/.drone.yml b/.drone.yml index cbb73a64c8e2..f85786b2e128 100644 --- a/.drone.yml +++ b/.drone.yml @@ -208,4 +208,3 @@ trigger: kind: signature hmac: 418915bcd7a880be3656a8c09aef0f6e7b3721d13f196838f8c5a9de6020f786 -... diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c278aa19943..53646d027eb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,20 +227,25 @@ if(BUILD_CLIENT) find_package(Sphinx) find_package(PdfLatex) find_package(OpenSSL 1.1 REQUIRED ) + find_package(PkgConfig REQUIRED) + pkg_check_modules(OPENSC-LIBP11 libp11 IMPORTED_TARGET) - find_package(ZLIB REQUIRED) - find_package(SQLite3 3.9.0 REQUIRED) + set(ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH "c:/Windows/System32/eTPKCS11.dll" CACHE PATH "Path to the driver for end-to-end encryption token") + option(CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN "Enforce use of an hardware token for end-to-end encryption" true) - if(NOT WIN32 AND NOT APPLE) - find_package(PkgConfig REQUIRED) - pkg_check_modules(CLOUDPROVIDERS cloudproviders IMPORTED_TARGET) + find_package(ZLIB REQUIRED) + find_package(SQLite3 3.9.0 REQUIRED) - if(CLOUDPROVIDERS_FOUND) - pkg_check_modules(DBUS-1 REQUIRED dbus-1 IMPORTED_TARGET) - pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET) - pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET) - endif() - endif() + if(NOT WIN32 AND NOT APPLE) + find_package(PkgConfig REQUIRED) + pkg_check_modules(CLOUDPROVIDERS cloudproviders IMPORTED_TARGET) + + if(CLOUDPROVIDERS_FOUND) + pkg_check_modules(DBUS-1 REQUIRED dbus-1 IMPORTED_TARGET) + pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET) + pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET) + endif() + endif() endif() if (APPLE) diff --git a/NEXTCLOUD.cmake b/NEXTCLOUD.cmake index e2632dbcfb6d..982b8cb51575 100644 --- a/NEXTCLOUD.cmake +++ b/NEXTCLOUD.cmake @@ -61,7 +61,6 @@ set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR ${NEXTCLOUD_BACKGROUND_COLOR} CA set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#ffffff" CACHE STRING "Hex color of the text in the wizard header") option( APPLICATION_WIZARD_USE_CUSTOM_LOGO "Use the logo from ':/client/theme/colored/wizard_logo.(png|svg)' else the default application icon is used" ON ) - # ## Windows Shell Extensions & MSI - IMPORTANT: Generate new GUIDs for custom builds with "guidgen" or "uuidgen" # diff --git a/config.h.in b/config.h.in index ca9907c52f29..5baea3153fc5 100644 --- a/config.h.in +++ b/config.h.in @@ -61,4 +61,8 @@ #cmakedefine CFAPI_SHELL_EXTENSIONS_LIB_NAME "@CFAPI_SHELL_EXTENSIONS_LIB_NAME@" +#cmakedefine01 CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN + +#cmakedefine ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH "@ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH@" + #endif diff --git a/resources.qrc b/resources.qrc index 7222e8c9ff0e..270c85b9e7d4 100644 --- a/resources.qrc +++ b/resources.qrc @@ -7,6 +7,7 @@ src/gui/PredefinedStatusButton.qml src/gui/BasicComboBox.qml src/gui/ErrorBox.qml + src/gui/EncryptionTokenSelectionWindow.qml src/gui/filedetails/FileActivityView.qml src/gui/filedetails/FileDetailsPage.qml src/gui/filedetails/FileDetailsView.qml @@ -48,6 +49,7 @@ src/gui/tray/TalkReplyTextField.qml src/gui/tray/CallNotificationDialog.qml src/gui/tray/EditFileLocallyLoadingDialog.qml + src/gui/tray/EncryptionTokenDiscoveryDialog.qml src/gui/tray/NCBusyIndicator.qml src/gui/tray/NCIconWithBackgroundImage.qml src/gui/tray/NCToolTip.qml diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index 895ad115c4d8..5683d2d4907a 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -48,7 +48,7 @@ Q_LOGGING_CATEGORY(lcDb, "nextcloud.sync.database", QtInfoMsg) #define GET_FILE_RECORD_QUERY \ "SELECT path, inode, modtime, type, md5, fileid, remotePerm, filesize," \ - " ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, " \ + " ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, e2eCertificateFingerprint, " \ " lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe" \ " FROM metadata" \ " LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id" @@ -67,16 +67,18 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que rec._checksumHeader = query.baValue(9); rec._e2eMangledName = query.baValue(10); rec._e2eEncryptionStatus = static_cast(query.intValue(11)); - rec._lockstate._locked = query.intValue(12) > 0; - rec._lockstate._lockOwnerDisplayName = query.stringValue(13); - rec._lockstate._lockOwnerId = query.stringValue(14); - rec._lockstate._lockOwnerType = query.int64Value(15); - rec._lockstate._lockEditorApp = query.stringValue(16); - rec._lockstate._lockTime = query.int64Value(17); - rec._lockstate._lockTimeout = query.int64Value(18); - rec._isShared = query.intValue(19) > 0; - rec._lastShareStateFetchedTimestamp = query.int64Value(20); - rec._sharedByMe = query.intValue(21) > 0; + rec._e2eCertificateFingerprint = query.baValue(12); + Q_ASSERT(rec._e2eEncryptionStatus == SyncJournalFileRecord::EncryptionStatus::NotEncrypted || !rec._e2eCertificateFingerprint.isEmpty()); + rec._lockstate._locked = query.intValue(13) > 0; + rec._lockstate._lockOwnerDisplayName = query.stringValue(14); + rec._lockstate._lockOwnerId = query.stringValue(15); + rec._lockstate._lockOwnerType = query.int64Value(16); + rec._lockstate._lockEditorApp = query.stringValue(17); + rec._lockstate._lockTime = query.int64Value(18); + rec._lockstate._lockTimeout = query.int64Value(19); + rec._isShared = query.intValue(20) > 0; + rec._lastShareStateFetchedTimestamp = query.int64Value(21); + rec._sharedByMe = query.intValue(22) > 0; } static QByteArray defaultJournalMode(const QString &dbPath) @@ -777,6 +779,7 @@ bool SyncJournalDb::updateMetadataTableStructure() addColumn(QStringLiteral("contentChecksumTypeId"), QStringLiteral("INTEGER")); addColumn(QStringLiteral("e2eMangledName"), QStringLiteral("TEXT")); addColumn(QStringLiteral("isE2eEncrypted"), QStringLiteral("INTEGER")); + addColumn(QStringLiteral("e2eCertificateFingerprint"), QStringLiteral("TEXT")); addColumn(QStringLiteral("isShared"), QStringLiteral("INTEGER")); addColumn(QStringLiteral("lastShareStateFetchedTimestmap"), QStringLiteral("INTEGER")); addColumn(QStringLiteral("sharedByMe"), QStringLiteral("INTEGER")); @@ -983,9 +986,9 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO metadata " "(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, " - "contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted, lock, lockType, lockOwnerDisplayName, lockOwnerId, " + "contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted, e2eCertificateFingerprint, lock, lockType, lockOwnerDisplayName, lockOwnerId, " "lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe) " - "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28);"), + "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29);"), _db); if (!query) { return query->error(); @@ -1009,16 +1012,17 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & query->bindValue(16, contentChecksumTypeId); query->bindValue(17, record._e2eMangledName); query->bindValue(18, static_cast(record._e2eEncryptionStatus)); - query->bindValue(19, record._lockstate._locked ? 1 : 0); - query->bindValue(20, record._lockstate._lockOwnerType); - query->bindValue(21, record._lockstate._lockOwnerDisplayName); - query->bindValue(22, record._lockstate._lockOwnerId); - query->bindValue(23, record._lockstate._lockEditorApp); - query->bindValue(24, record._lockstate._lockTime); - query->bindValue(25, record._lockstate._lockTimeout); - query->bindValue(26, record._isShared); - query->bindValue(27, record._lastShareStateFetchedTimestamp); - query->bindValue(28, record._sharedByMe); + query->bindValue(19, record._e2eCertificateFingerprint); + query->bindValue(20, record._lockstate._locked ? 1 : 0); + query->bindValue(21, record._lockstate._lockOwnerType); + query->bindValue(22, record._lockstate._lockOwnerDisplayName); + query->bindValue(23, record._lockstate._lockOwnerId); + query->bindValue(24, record._lockstate._lockEditorApp); + query->bindValue(25, record._lockstate._lockTime); + query->bindValue(26, record._lockstate._lockTimeout); + query->bindValue(27, record._isShared); + query->bindValue(28, record._lastShareStateFetchedTimestamp); + query->bindValue(29, record._sharedByMe); if (!query->exec()) { return query->error(); diff --git a/src/common/syncjournalfilerecord.h b/src/common/syncjournalfilerecord.h index 7270fac13796..38abaf5255c4 100644 --- a/src/common/syncjournalfilerecord.h +++ b/src/common/syncjournalfilerecord.h @@ -83,6 +83,7 @@ class OCSYNC_EXPORT SyncJournalFileRecord QByteArray _checksumHeader; QByteArray _e2eMangledName; EncryptionStatus _e2eEncryptionStatus = EncryptionStatus::NotEncrypted; + QByteArray _e2eCertificateFingerprint; SyncJournalFileLockInfo _lockstate; bool _isShared = false; qint64 _lastShareStateFetchedTimestamp = 0; diff --git a/src/csync/csync.h b/src/csync/csync.h index ee5cee050241..eecc3ce8d10b 100644 --- a/src/csync/csync.h +++ b/src/csync/csync.h @@ -138,24 +138,25 @@ Q_ENUM_NS(csync_status_codes_e) * the csync state of a file. */ enum SyncInstructions { - CSYNC_INSTRUCTION_NONE = 0, /* Nothing to do (UPDATE|RECONCILE) */ - CSYNC_INSTRUCTION_EVAL = 1 << 0, /* There was changed compared to the DB (UPDATE) */ - CSYNC_INSTRUCTION_REMOVE = 1 << 1, /* The file need to be removed (RECONCILE) */ - CSYNC_INSTRUCTION_RENAME = 1 << 2, /* The file need to be renamed (RECONCILE) */ - CSYNC_INSTRUCTION_EVAL_RENAME = 1 << 11, /* The file is new, it is the destination of a rename (UPDATE) */ - CSYNC_INSTRUCTION_NEW = 1 << 3, /* The file is new compared to the db (UPDATE) */ - CSYNC_INSTRUCTION_CONFLICT = 1 << 4, /* The file need to be downloaded because it is a conflict (RECONCILE) */ - CSYNC_INSTRUCTION_IGNORE = 1 << 5, /* The file is ignored (UPDATE|RECONCILE) */ - CSYNC_INSTRUCTION_SYNC = 1 << 6, /* The file need to be pushed to the other remote (RECONCILE) */ - CSYNC_INSTRUCTION_STAT_ERROR = 1 << 7, - CSYNC_INSTRUCTION_ERROR = 1 << 8, - CSYNC_INSTRUCTION_TYPE_CHANGE = 1 << 9, /* Like NEW, but deletes the old entity first (RECONCILE) - Used when the type of something changes from directory to file - or back. */ - CSYNC_INSTRUCTION_UPDATE_METADATA = 1 << 10, /* If the etag has been updated and need to be writen to the db, - but without any propagation (UPDATE|RECONCILE) */ - CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT = 1 << 12, /* The file need to be downloaded because it is a case clash conflict (RECONCILE) */ - CSYNC_INSTRUCTION_UPDATE_VFS_METADATA = 1 << 13, /* vfs item metadata are out of sync and we need to tell operating system about it */ + CSYNC_INSTRUCTION_NONE = 0, /* Nothing to do (UPDATE|RECONCILE) */ + CSYNC_INSTRUCTION_EVAL = 1 << 0, /* There was changed compared to the DB (UPDATE) */ + CSYNC_INSTRUCTION_REMOVE = 1 << 1, /* The file need to be removed (RECONCILE) */ + CSYNC_INSTRUCTION_RENAME = 1 << 2, /* The file need to be renamed (RECONCILE) */ + CSYNC_INSTRUCTION_EVAL_RENAME = 1 << 11, /* The file is new, it is the destination of a rename (UPDATE) */ + CSYNC_INSTRUCTION_NEW = 1 << 3, /* The file is new compared to the db (UPDATE) */ + CSYNC_INSTRUCTION_CONFLICT = 1 << 4, /* The file need to be downloaded because it is a conflict (RECONCILE) */ + CSYNC_INSTRUCTION_IGNORE = 1 << 5, /* The file is ignored (UPDATE|RECONCILE) */ + CSYNC_INSTRUCTION_SYNC = 1 << 6, /* The file need to be pushed to the other remote (RECONCILE) */ + CSYNC_INSTRUCTION_STAT_ERROR = 1 << 7, + CSYNC_INSTRUCTION_ERROR = 1 << 8, + CSYNC_INSTRUCTION_TYPE_CHANGE = 1 << 9, /* Like NEW, but deletes the old entity first (RECONCILE) + Used when the type of something changes from directory to file + or back. */ + CSYNC_INSTRUCTION_UPDATE_METADATA = 1 << 10, /* If the etag has been updated and need to be writen to the db, + but without any propagation (UPDATE|RECONCILE) */ + CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT = 1 << 12, /* The file need to be downloaded because it is a case clash conflict (RECONCILE) */ + CSYNC_INSTRUCTION_UPDATE_VFS_METADATA = 1 << 13, /* vfs item metadata are out of sync and we need to tell operating system about it */ + CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA = 1 << 14, /* encryption metadata needs update after certificate was migrated */ }; Q_ENUM_NS(SyncInstructions) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index a10ccbe1c563..2d1eda4dae6d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -144,6 +144,7 @@ set(client_SRCS syncrunfilelog.cpp systray.h systray.cpp + EncryptionTokenSelectionWindow.qml thumbnailjob.h thumbnailjob.cpp userinfo.h diff --git a/src/gui/EncryptionTokenSelectionWindow.qml b/src/gui/EncryptionTokenSelectionWindow.qml new file mode 100644 index 000000000000..8872d978335a --- /dev/null +++ b/src/gui/EncryptionTokenSelectionWindow.qml @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2023 by Matthieu Gallien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import QtQml.Models 2.15 + +import com.nextcloud.desktopclient 1.0 +import Style 1.0 + +import "./tray" + +ApplicationWindow { + id: encryptionKeyChooserDialog + + required property var certificatesInfo + required property ClientSideEncryptionTokenSelector certificateSelector + property string selectedSerialNumber: '' + + flags: Qt.Window | Qt.Dialog + visible: true + modality: Qt.ApplicationModal + + width: 400 + height: 600 + minimumWidth: 400 + minimumHeight: 600 + + title: qsTr('Token Encryption Key Chooser') + + // TODO: Rather than setting all these palette colours manually, + // create a custom style and do it for all components globally + palette { + text: Style.ncTextColor + windowText: Style.ncTextColor + buttonText: Style.ncTextColor + brightText: Style.ncTextBrightColor + highlight: Style.lightHover + highlightedText: Style.ncTextColor + light: Style.lightHover + midlight: Style.ncSecondaryTextColor + mid: Style.darkerHover + dark: Style.menuBorder + button: Style.buttonBackgroundColor + window: Style.backgroundColor + base: Style.backgroundColor + toolTipBase: Style.backgroundColor + toolTipText: Style.ncTextColor + } + + onClosing: function(close) { + Systray.destroyDialog(self); + close.accepted = true + } + + ColumnLayout { + anchors.fill: parent + anchors.leftMargin: 20 + anchors.rightMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + spacing: 15 + z: 2 + + EnforcedPlainTextLabel { + text: qsTr("Available Keys for end-to-end Encryption:") + font.bold: true + font.pixelSize: Style.bigFontPixelSizeResolveConflictsDialog + Layout.fillWidth: true + } + + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + + clip: true + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + ListView { + id: tokensListView + + currentIndex: -1 + + model: DelegateModel { + model: certificatesInfo + + delegate: ItemDelegate { + width: tokensListView.contentItem.width + + text: modelData.subject + + highlighted: tokensListView.currentIndex === index + + onClicked: function() + { + tokensListView.currentIndex = index + selectedSerialNumber = modelData.serialNumber + } + } + } + } + } + + DialogButtonBox { + Layout.fillWidth: true + + Button { + text: qsTr("Choose") + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + } + Button { + text: qsTr("Cancel") + DialogButtonBox.buttonRole: DialogButtonBox.RejectRole + } + + onAccepted: function() { + Systray.destroyDialog(encryptionKeyChooserDialog) + certificateSelector.serialNumber = selectedSerialNumber + } + + onRejected: function() { + Systray.destroyDialog(encryptionKeyChooserDialog) + certificateSelector.serialNumber = '' + } + } + } + + Rectangle { + color: Style.backgroundColor + anchors.fill: parent + z: 1 + } +} diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index ac1dcf81e557..1f724404ac88 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -46,6 +46,7 @@ constexpr auto serverVersionC = "serverVersion"; constexpr auto serverColorC = "serverColor"; constexpr auto serverTextColorC = "serverTextColor"; constexpr auto skipE2eeMetadataChecksumValidationC = "skipE2eeMetadataChecksumValidation"; +constexpr auto encryptionCertificateSha256FingerprintC = "encryptionCertificateSha256Fingerprint"; constexpr auto generalC = "General"; constexpr auto dummyAuthTypeC = "dummy"; @@ -322,6 +323,7 @@ void AccountManager::saveAccountHelper(Account *acc, QSettings &settings, bool s settings.setValue(QLatin1String(serverVersionC), acc->_serverVersion); settings.setValue(QLatin1String(serverColorC), acc->_serverColor); settings.setValue(QLatin1String(serverTextColorC), acc->_serverTextColor); + settings.setValue(QLatin1String(encryptionCertificateSha256FingerprintC), acc->encryptionCertificateFingerprint()); if (!acc->_skipE2eeMetadataChecksumValidation) { settings.remove(QLatin1String(skipE2eeMetadataChecksumValidationC)); } else { @@ -448,6 +450,8 @@ AccountPtr AccountManager::loadAccountHelper(QSettings &settings) acc->setCredentials(CredentialsFactory::create(authType)); + acc->setEncryptionCertificateFingerprint(settings.value(QLatin1String(encryptionCertificateSha256FingerprintC)).toByteArray()); + // now the server cert, it is in the general group settings.beginGroup(QLatin1String(generalC)); const auto certs = QSslCertificate::fromData(settings.value(caCertsKeyC).toByteArray()); diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 1517986e32d5..2f140ca69540 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -73,6 +73,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 { @@ -282,6 +283,11 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) this, &AccountSettings::slotUpdateQuota); customizeStyle(); + + connect(_accountState->account()->e2e(), &ClientSideEncryption::startingDiscoveryEncryptionUsbToken, + Systray::instance(), &Systray::createEncryptionTokenDiscoveryDialog); + connect(_accountState->account()->e2e(), &ClientSideEncryption::finishedDiscoveryEncryptionUsbToken, + Systray::instance(), &Systray::destroyEncryptionTokenDiscoveryDialog); } void AccountSettings::slotE2eEncryptionMnemonicReady() @@ -291,10 +297,16 @@ void AccountSettings::slotE2eEncryptionMnemonicReady() disableEncryptionForAccount(_accountState->account()); }); - const auto actionDisplayMnemonic = addActionToEncryptionMessage(tr("Display mnemonic"), e2EeUiActionDisplayMnemonicId); - connect(actionDisplayMnemonic, &QAction::triggered, this, [this]() { - displayMnemonic(_accountState->account()->e2e()->_mnemonic); - }); + 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]() { + displayMnemonic(_accountState->account()->e2e()->getMnemonic()); + }); + } _ui->encryptionMessage->setMessageType(KMessageWidget::Positive); _ui->encryptionMessage->setText(tr("End-to-end encryption has been enabled for this account")); @@ -307,18 +319,19 @@ void AccountSettings::slotE2eEncryptionGenerateKeys() connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotE2eEncryptionInitializationFinished); _accountState->account()->setE2eEncryptionKeysGenerationAllowed(true); _accountState->account()->setAskUserForMnemonic(true); - _accountState->account()->e2e()->initialize(_accountState->account()); + _accountState->account()->e2e()->initialize(this, _accountState->account()); } void AccountSettings::slotE2eEncryptionInitializationFinished(bool isNewMnemonicGenerated) { disconnect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotE2eEncryptionInitializationFinished); - if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) { + if (_accountState->account()->e2e()->isInitialized()) { removeActionFromEncryptionMessage(e2EeUiActionEnableEncryptionId); slotE2eEncryptionMnemonicReady(); if (isNewMnemonicGenerated) { - displayMnemonic(_accountState->account()->e2e()->_mnemonic); + displayMnemonic(_accountState->account()->e2e()->getMnemonic()); } + Q_EMIT _accountState->account()->wantsFoldersSynced(); } _accountState->account()->setAskUserForMnemonic(false); } @@ -388,7 +401,7 @@ bool AccountSettings::canEncryptOrDecrypt(const FolderStatusModel::SubFolderInfo return false; } - if (!_accountState->account()->e2e() || _accountState->account()->e2e()->_mnemonic.isEmpty()) { + if (!_accountState->account()->e2e() || !_accountState->account()->e2e()->isInitialized()) { QMessageBox msgBox; msgBox.setText(tr("End-to-end encryption is not configured on this device. " "Once it is configured, you will be able to encrypt this folder.\n" @@ -1114,6 +1127,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(); + slotE2eEncryptionGenerateKeys(); +} + void AccountSettings::showConnectionLabel(const QString &message, QStringList errors) { const auto errStyle = QLatin1String("color:#ffffff; background-color:#bb4d4d;padding:5px;" @@ -1458,7 +1481,7 @@ void AccountSettings::slotSelectiveSyncChanged(const QModelIndex &topLeft, void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync() { - if (_accountState->account()->e2e()->_mnemonic.isEmpty()) { + if (!_accountState->account()->e2e()->isInitialized()) { return; } @@ -1491,6 +1514,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); @@ -1636,13 +1667,13 @@ void AccountSettings::initializeE2eEncryption() { connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync); - if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) { + if (_accountState->account()->e2e()->isInitialized()) { slotE2eEncryptionMnemonicReady(); } else { initializeE2eEncryptionSettingsMessage(); connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, [this] { - if (!_accountState->account()->e2e()->_publicKey.isNull()) { + if (!_accountState->account()->e2e()->getPublicKey().isNull()) { _ui->encryptionMessage->setText(tr("End-to-end encryption has been enabled on this account with another device." "
" "It can be enabled on this device by entering your mnemonic." @@ -1651,7 +1682,7 @@ void AccountSettings::initializeE2eEncryption() } }); _accountState->account()->setE2eEncryptionKeysGenerationAllowed(false); - _accountState->account()->e2e()->initialize(_accountState->account()); + _accountState->account()->e2e()->initialize(this, _accountState->account()); } } @@ -1666,7 +1697,7 @@ void AccountSettings::resetE2eEncryption() checkClientSideEncryptionState(); const auto account = _accountState->account(); - if (account->e2e()->_mnemonic.isEmpty()) { + if (!account->e2e()->isInitialized()) { FolderMan::instance()->removeE2eFiles(account); } } diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h index 117a3aa536a0..2e41856f38b6 100644 --- a/src/gui/accountsettings.h +++ b/src/gui/accountsettings.h @@ -62,6 +62,7 @@ class AccountSettings : public QWidget ~AccountSettings() override; [[nodiscard]] QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); } bool canEncryptOrDecrypt(const FolderStatusModel::SubFolderInfo* folderInfo); + [[nodiscard]] OCC::AccountState *accountsState() const { return _accountState; } signals: void folderChanged(); @@ -76,7 +77,6 @@ public slots: void slotUpdateQuota(qint64 total, qint64 used); void slotAccountStateChanged(); void slotStyleChanged(); - OCC::AccountState *accountsState() { return _accountState; } void slotHideSelectiveSyncWidget(); protected slots: @@ -116,6 +116,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); @@ -123,6 +125,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/application.cpp b/src/gui/application.cpp index d15f470cfb0a..8537b58644c5 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -701,6 +701,12 @@ void Application::setupLogging() logger->setLogExpire(_logExpire > 0 ? _logExpire : ConfigFile().logExpire()); logger->setLogFlush(_logFlush || ConfigFile().logFlush()); logger->setLogDebug(_logDebug || ConfigFile().logDebug()); + +#if defined(QT_DEBUG) + logger->setLogDebug(true); + logger->setLogFlush(true); +#endif + if (!logger->isLoggingToFile() && ConfigFile().automaticLogDir()) { logger->setupTemporaryFolderLogDir(); } diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index 9cc24de4a752..e60010340946 100644 --- a/src/gui/connectionvalidator.cpp +++ b/src/gui/connectionvalidator.cpp @@ -315,7 +315,7 @@ void ConnectionValidator::slotUserFetched(UserInfo *userInfo) #ifndef TOKEN_AUTH_ONLY connect(_account->e2e(), &ClientSideEncryption::initializationFinished, this, &ConnectionValidator::reportConnected); - _account->e2e()->initialize(_account); + _account->e2e()->initialize(nullptr, _account); #else reportResult(Connected); #endif diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index aa755fdf1e6a..ce7b3a2ef93e 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -129,6 +129,11 @@ Folder::Folder(const FolderDefinition &definition, connect(_accountState->account().data(), &Account::capabilitiesChanged, this, &Folder::slotCapabilitiesChanged); + connect(_accountState->account().data(), &Account::wantsFoldersSynced, this, [this] () { + _engine->setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::FilesystemOnly); + QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection); + }); + // Potentially upgrade suffix vfs to windows vfs ENFORCE(_vfs); if (_definition.virtualFilesMode == Vfs::WithSuffix diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index a5d68f1b70a3..75bb857cb356 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -668,7 +668,7 @@ void FolderMan::forceSyncForFolder(Folder *folder) void FolderMan::removeE2eFiles(const AccountPtr &account) const { - Q_ASSERT(account->e2e()->_mnemonic.isEmpty()); + Q_ASSERT(!account->e2e()->isInitialized()); for (const auto folder : map()) { if(folder->accountState()->account()->id() == account->id()) { folder->removeLocalE2eFiles(); diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp index 08b4276fbd09..73bc4ca593c7 100644 --- a/src/gui/folderstatusmodel.cpp +++ b/src/gui/folderstatusmodel.cpp @@ -754,8 +754,7 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list) newInfo._isNonDecryptable = newInfo.isEncrypted() && _accountState->account()->e2e() - && !_accountState->account()->e2e()->_publicKey.isNull() - && _accountState->account()->e2e()->_privateKey.isNull(); + && !_accountState->account()->e2e()->isInitialized(); SyncJournalFileRecord rec; if (!parentInfo->_folder->journalDb()->getFileRecordByE2eMangledName(removeTrailingSlash(relativePath), &rec)) { diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 321e43b600d8..56ba0b573e2b 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -136,6 +136,7 @@ ownCloudGui::ownCloudGui(Application *parent) qmlRegisterUncreatableType("com.nextcloud.desktopclient", 1, 0, "UnifiedSearchResultsListModel", "UnifiedSearchResultsListModel"); qmlRegisterUncreatableType("com.nextcloud.desktopclient", 1, 0, "UserStatus", "Access to Status enum"); qmlRegisterUncreatableType("com.nextcloud.desktopclient", 1, 0, "Sharee", "Access to Type enum"); + qmlRegisterUncreatableType("com.nextcloud.desktopclient", 1, 0, "ClientSideEncryptionTokenSelector", "Access to the certificate selector"); qRegisterMetaType("ActivityListModel*"); qRegisterMetaType("UnifiedSearchResultsListModel*"); @@ -578,6 +579,10 @@ void ownCloudGui::slotShowSettings() if (_settingsDialog.isNull()) { _settingsDialog = new SettingsDialog(this); _settingsDialog->setAttribute(Qt::WA_DeleteOnClose, true); + + connect(_tray.data(), &Systray::hideSettingsDialog, + _settingsDialog.data(), &SettingsDialog::close); + _settingsDialog->show(); } raiseDialog(_settingsDialog.data()); diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp index a59314a15ce7..6a1cc80023d9 100644 --- a/src/gui/settingsdialog.cpp +++ b/src/gui/settingsdialog.cpp @@ -137,10 +137,6 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) _actionGroupWidgets.insert(generalAction, generalSettings); _actionGroupWidgets.insert(networkAction, networkSettings); - foreach(auto ai, AccountManager::instance()->accounts()) { - accountAdded(ai.data()); - } - QTimer::singleShot(1, this, &SettingsDialog::showFirstPage); auto *showLogWindow = new QAction(this); @@ -215,6 +211,10 @@ void SettingsDialog::slotSwitchPage(QAction *action) void SettingsDialog::showFirstPage() { + foreach(auto ai, AccountManager::instance()->accounts()) { + accountAdded(ai.data()); + } + QList actions = _toolBar->actions(); if (!actions.empty()) { actions.first()->trigger(); diff --git a/src/gui/socketapi/socketapi.cpp b/src/gui/socketapi/socketapi.cpp index 3ff2b0eea847..d48bc2e0d327 100644 --- a/src/gui/socketapi/socketapi.cpp +++ b/src/gui/socketapi/socketapi.cpp @@ -538,7 +538,7 @@ void SocketApi::processEncryptRequest(const QString &localFile) const auto rec = fileData.journalRecord(); Q_ASSERT(rec.isValid()); - if (!account->e2e() || account->e2e()->_mnemonic.isEmpty()) { + if (!account->e2e() || !account->e2e()->isInitialized()) { const int ret = QMessageBox::critical(nullptr, tr("Failed to encrypt folder at \"%1\"").arg(fileData.folderRelativePath), tr("The account %1 does not have end-to-end encryption configured. " diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index c4d4672fe621..ea06c0eac27e 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -24,6 +24,7 @@ #include "configfile.h" #include "accessmanager.h" #include "callstatechecker.h" +#include "clientsideencryptiontokenselector.h" #include #include @@ -34,6 +35,8 @@ #include #include #include +#include +#include #ifdef USE_FDO_NOTIFICATIONS #include @@ -316,6 +319,34 @@ void Systray::createResolveConflictsDialog(const OCC::ActivityList &allConflicts dialog.take(); } +void Systray::createEncryptionTokenDiscoveryDialog() +{ + if (_encryptionTokenDiscoveryDialog) { + return; + } + + qCDebug(lcSystray) << "Opening an encryption token discovery dialog..."; + + const auto encryptionTokenDiscoveryDialog = new QQmlComponent(_trayEngine.get(), QStringLiteral("qrc:/qml/src/gui/tray/EncryptionTokenDiscoveryDialog.qml")); + + if (encryptionTokenDiscoveryDialog->isError()) { + qCWarning(lcSystray) << encryptionTokenDiscoveryDialog->errorString(); + return; + } + + _encryptionTokenDiscoveryDialog = encryptionTokenDiscoveryDialog->createWithInitialProperties(QVariantMap{}); +} + +void Systray::destroyEncryptionTokenDiscoveryDialog() +{ + if (!_encryptionTokenDiscoveryDialog) { + return; + } + qCDebug(lcSystray) << "Closing an encryption token discovery dialog..."; + _encryptionTokenDiscoveryDialog->deleteLater(); + _encryptionTokenDiscoveryDialog = nullptr; +} + bool Systray::raiseDialogs() { return raiseFileDetailDialogs(); diff --git a/src/gui/systray.h b/src/gui/systray.h index cff48f33d5b3..5e30cf4119cf 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -19,6 +19,7 @@ #include "tray/usermodel.h" #include +#include #include #include @@ -31,6 +32,8 @@ class QGuiApplication; namespace OCC { +class ClientSideEncryptionTokenSelector; + class AccessManagerFactory : public QQmlNetworkAccessManagerFactory { public: @@ -115,6 +118,8 @@ class Systray : public QSystemTrayIcon void syncIsPausedChanged(); void isOpenChanged(); + void hideSettingsDialog(); + public slots: void setTrayEngine(QQmlApplicationEngine *trayEngine); void create(); @@ -128,6 +133,8 @@ public slots: void createEditFileLocallyLoadingDialog(const QString &fileName); void destroyEditFileLocallyLoadingDialog(); void createResolveConflictsDialog(const OCC::ActivityList &allConflicts); + void createEncryptionTokenDiscoveryDialog(); + void destroyEncryptionTokenDiscoveryDialog(); void slotCurrentUserChanged(); @@ -188,7 +195,9 @@ private slots: QSet _callsAlreadyNotified; QPointer _editFileLocallyLoadingDialog; + QPointer _encryptionTokenDiscoveryDialog; QVector _fileDetailDialogs; + QQuickWindow* _tokenInitDialog = nullptr; QStringListModel _fakeActivityModel; }; diff --git a/src/gui/tray/EncryptionTokenDiscoveryDialog.qml b/src/gui/tray/EncryptionTokenDiscoveryDialog.qml new file mode 100644 index 000000000000..123cc750f7aa --- /dev/null +++ b/src/gui/tray/EncryptionTokenDiscoveryDialog.qml @@ -0,0 +1,89 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import Style 1.0 +import com.nextcloud.desktopclient 1.0 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 + +ApplicationWindow { + id: root + flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint + + color: "transparent" + + width: 320 + height: contentLayout.implicitHeight + modality: Qt.ApplicationModal + + readonly property real fontPixelSize: Style.topLinePixelSize * 1.5 + readonly property real iconWidth: fontPixelSize * 2 + + // TODO: Rather than setting all these palette colours manually, + // create a custom style and do it for all components globally + palette { + text: Style.ncTextColor + windowText: Style.ncTextColor + buttonText: Style.ncTextColor + brightText: Style.ncTextBrightColor + highlight: Style.lightHover + highlightedText: Style.ncTextColor + light: Style.lightHover + midlight: Style.ncSecondaryTextColor + mid: Style.darkerHover + dark: Style.menuBorder + button: Style.buttonBackgroundColor + window: Style.backgroundColor + base: Style.backgroundColor + toolTipBase: Style.backgroundColor + toolTipText: Style.ncTextColor + } + + Component.onCompleted: { + Systray.forceWindowInit(root); + x = Screen.width / 2 - width / 2 + y = Screen.height / 2 - height / 2 + root.show(); + root.raise(); + root.requestActivate(); + } + + Rectangle { + id: windowBackground + color: Style.backgroundColor + radius: Style.trayWindowRadius + border.color: palette.dark + anchors.fill: parent + } + + ColumnLayout { + id: contentLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: Style.standardSpacing + anchors.rightMargin: Style.standardSpacing + spacing: Style.standardSpacing + + NCBusyIndicator { + id: busyIndicator + Layout.topMargin: Style.standardSpacing + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: root.iconWidth + Layout.preferredHeight: root.iconWidth + imageSourceSizeHeight: root.iconWidth + imageSourceSizeWidth: root.iconWidth + padding: 0 + color: palette.windowText + running: true + } + EnforcedPlainTextLabel { + id: labelMessage + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.bottomMargin: Style.standardSpacing + text: qsTr("Discovering the certificates stored on your USB token") + elide: Text.ElideRight + font.pixelSize: root.fontPixelSize + horizontalAlignment: Text.AlignHCenter + } + } +} diff --git a/src/gui/tray/activitydata.h b/src/gui/tray/activitydata.h index e613ad5bf5dd..3bcd4281eeb5 100644 --- a/src/gui/tray/activitydata.h +++ b/src/gui/tray/activitydata.h @@ -149,6 +149,7 @@ class Activity // Note that these are in the order we want to present them in the model! enum Type { DummyFetchingActivityType, + OpenSettingsNotificationType, NotificationType, SyncResultType, SyncFileItemType, diff --git a/src/gui/tray/activitylistmodel.cpp b/src/gui/tray/activitylistmodel.cpp index 3976e9950c3c..ee534237945d 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); @@ -227,7 +231,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const const auto generateIconPath = [&]() { auto colorIconPath = role == DarkIconRole ? QStringLiteral("qrc:///client/theme/white/") : QStringLiteral("qrc:///client/theme/black/"); - if (a._type == Activity::NotificationType && !a._talkNotificationData.userAvatar.isEmpty()) { + if ((a._type == Activity::NotificationType || a._type == Activity::OpenSettingsNotificationType) && + !a._talkNotificationData.userAvatar.isEmpty()) { return QStringLiteral("qrc:///client/theme/colored/talk-bordered.svg"); } else if (a._type == Activity::SyncResultType) { colorIconPath.append("state-error.svg"); @@ -317,14 +322,14 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const case Activity::DummyMoreActivitiesAvailableType: return "Activity"; case Activity::NotificationType: + case Activity::OpenSettingsNotificationType: return "Notification"; case Activity::SyncFileItemType: return "File"; case Activity::SyncResultType: return "Sync"; - default: - return QVariant(); } + break; } case ActionTextRole: if(a._subjectDisplay.isEmpty()) { @@ -368,7 +373,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const case IsCurrentUserFileActivityRole: return a._isCurrentUserFileActivity; case ThumbnailRole: { - if (a._type == Activity::NotificationType && !a._talkNotificationData.userAvatar.isEmpty()) { + if ((a._type == Activity::NotificationType || a._type == Activity::OpenSettingsNotificationType) && + !a._talkNotificationData.userAvatar.isEmpty()) { return generateAvatarThumbnailMap(a._talkNotificationData.userAvatar); } @@ -393,7 +399,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const return QVariant::fromValue(a); } - return QVariant(); + return {}; } int ActivityListModel::rowCount(const QModelIndex &parent) const @@ -674,9 +680,10 @@ void ActivityListModel::removeActivityFromActivityList(const Activity &activity) } if (activity._type != Activity::ActivityType && - activity._type != Activity::DummyFetchingActivityType && - activity._type != Activity::DummyMoreActivitiesAvailableType && - activity._type != Activity::NotificationType) { + activity._type != Activity::DummyFetchingActivityType && + activity._type != Activity::DummyMoreActivitiesAvailableType && + activity._type != Activity::NotificationType && + activity._type != Activity::OpenSettingsNotificationType) { const auto notificationErrorsListIndex = _notificationErrorsLists.indexOf(activity); if (notificationErrorsListIndex != -1) @@ -742,6 +749,8 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex) _currentInvalidFilenameDialog->open(); ownCloudGui::raiseDialog(_currentInvalidFilenameDialog); return; + } else if (activity._type == Activity::OpenSettingsNotificationType) { + Q_EMIT showSettingsDialog(); } if (!path.isEmpty()) { diff --git a/src/gui/tray/activitylistmodel.h b/src/gui/tray/activitylistmodel.h index 6251a12e9a1f..c0dcb58ef3eb 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 7cb3b08e7afa..b6f4a8364e17 100644 --- a/src/gui/tray/usermodel.cpp +++ b/src/gui/tray/usermodel.cpp @@ -92,8 +92,25 @@ 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::OpenSettingsNotificationType; + 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("migrate-certificate"); + + _activityModel->removeActivityFromActivityList(certificateNeedMigration); + + if (_account->account()->e2e()->userCertificateNeedsMigration()) { + _activityModel->addNotificationToActivityList(certificateNeedMigration); + showDesktopNotification(certificateNeedMigration); + } + }); } void User::checkNotifiedNotifications() diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 299e62a088fe..6e64b48fc5d1 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -12,6 +12,12 @@ if ( APPLE ) ) endif() +if (WIN32) + list(APPEND OS_SPECIFIC_LINK_LIBRARIES + Crypt32 + ) +endif() + set(libsync_SRCS account.h account.cpp @@ -123,6 +129,8 @@ set(libsync_SRCS clientsideencryptionjobs.cpp clientsideencryptionprimitives.h clientsideencryptionprimitives.cpp + clientsideencryptiontokenselector.h + clientsideencryptiontokenselector.cpp datetimeprovider.h datetimeprovider.cpp rootencryptedfolderinfo.h @@ -205,6 +213,7 @@ target_link_libraries(nextcloudsync Nextcloud::csync OpenSSL::Crypto OpenSSL::SSL + PkgConfig::OPENSC-LIBP11 ${OS_SPECIFIC_LINK_LIBRARIES} Qt::Core Qt::Network diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index 2bd4fdd2e9b5..3fd0b907121c 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -33,6 +33,8 @@ #include "clientsideencryption.h" #include "ocsuserstatusconnector.h" +#include "config.h" + #include #include #include @@ -84,6 +86,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() @@ -1062,6 +1067,41 @@ bool Account::askUserForMnemonic() const return _e2eAskUserForMnemonic; } +bool Account::enforceUseHardwareTokenEncryption() const +{ +#if defined CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN + return CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN; +#else + return false; +#endif +} + +QString Account::encryptionHardwareTokenDriverPath() const +{ +#if defined ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH + return ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH; +#else + return {}; +#endif +} + +QByteArray Account::encryptionCertificateFingerprint() const +{ + return _encryptionCertificateFingerprint; +} + +void Account::setEncryptionCertificateFingerprint(const QByteArray &fingerprint) +{ + if (_encryptionCertificateFingerprint == fingerprint) { + return; + } + + _encryptionCertificateFingerprint = fingerprint; + _e2e.usbTokenInformation()->setSha256Fingerprint(fingerprint); + Q_EMIT encryptionCertificateFingerprintChanged(); + Q_EMIT wantsAccountSaved(this); +} + void Account::setAskUserForMnemonic(const bool ask) { _e2eAskUserForMnemonic = ask; diff --git a/src/libsync/account.h b/src/libsync/account.h index ebc53dbda31e..c986b3e309ce 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -91,6 +91,9 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject Q_PROPERTY(QUrl url MEMBER _url) Q_PROPERTY(bool e2eEncryptionKeysGenerationAllowed MEMBER _e2eEncryptionKeysGenerationAllowed) Q_PROPERTY(bool askUserForMnemonic READ askUserForMnemonic WRITE setAskUserForMnemonic NOTIFY askUserForMnemonicChanged) + Q_PROPERTY(bool enforceUseHardwareTokenEncryption READ enforceUseHardwareTokenEncryption NOTIFY enforceUseHardwareTokenEncryptionChanged) + Q_PROPERTY(QString encryptionHardwareTokenDriverPath READ encryptionHardwareTokenDriverPath NOTIFY encryptionHardwareTokenDriverPathChanged) + Q_PROPERTY(QByteArray encryptionCertificateFingerprint READ encryptionCertificateFingerprint WRITE setEncryptionCertificateFingerprint NOTIFY encryptionCertificateFingerprintChanged) public: static AccountPtr create(); @@ -336,6 +339,14 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject [[nodiscard]] bool askUserForMnemonic() const; + [[nodiscard]] bool enforceUseHardwareTokenEncryption() const; + + [[nodiscard]] QString encryptionHardwareTokenDriverPath() const; + + [[nodiscard]] QByteArray encryptionCertificateFingerprint() const; + + void setEncryptionCertificateFingerprint(const QByteArray &fingerprint); + public slots: /// Used when forgetting credentials void clearQNAMCache(); @@ -358,12 +369,16 @@ public slots: // e.g. when the approved SSL certificates changed void wantsAccountSaved(OCC::Account *acc); + void wantsFoldersSynced(); + void serverVersionChanged(OCC::Account *account, const QString &newVersion, const QString &oldVersion); void accountChangedAvatar(); void accountChangedDisplayName(); void prettyNameChanged(); void askUserForMnemonicChanged(); + void enforceUseHardwareTokenEncryptionChanged(); + void encryptionHardwareTokenDriverPathChanged(); /// Used in RemoteWipe void appPasswordRetrieved(QString); @@ -380,6 +395,9 @@ public slots: void lockFileSuccess(); void lockFileError(const QString&); + void encryptionCertificateFingerprintChanged(); + void userCertificateNeedsMigrationChanged(); + protected Q_SLOTS: void slotCredentialsFetched(); void slotCredentialsAsked(); @@ -455,6 +473,8 @@ private slots: QHash> _lockStatusChangeInprogress; + QByteArray _encryptionCertificateFingerprint; + /* IMPORTANT - remove later - FIXME MS@2019-12-07 --> * TODO: For "Log out" & "Remove account": Remove client CA certs and KEY! * diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp index a5c0364c2b7a..8f6c30a33193 100644 --- a/src/libsync/clientsideencryption.cpp +++ b/src/libsync/clientsideencryption.cpp @@ -1,12 +1,19 @@ -#include "clientsideencryption.h" +/* + * Copyright © 2017, Tomaz Canabrava + * Copyright © 2020, Andreas Jellinghaus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ -#include -#include -#include -#include -#include -#include -#include +#include "clientsideencryption.h" #include "account.h" #include "capabilities.h" @@ -31,6 +38,8 @@ #include #include #include +#include +#include #include #include #include @@ -38,11 +47,22 @@ #include #include #include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include #include #include #include - +#include #include QDebug operator<<(QDebug out, const std::string& str) @@ -57,7 +77,8 @@ namespace OCC { Q_LOGGING_CATEGORY(lcCse, "nextcloud.sync.clientsideencryption", QtInfoMsg) -Q_LOGGING_CATEGORY(lcCseDecryption, "nextcloud.e2e", QtInfoMsg) +Q_LOGGING_CATEGORY(lcCseDecryption, "nextcloud.e2e.decryption", QtInfoMsg) +Q_LOGGING_CATEGORY(lcCseEncryption, "nextcloud.e2e.encryption", QtInfoMsg) QString e2eeBaseUrl(const OCC::AccountPtr &account) { @@ -80,6 +101,9 @@ constexpr char e2e_private[] = "_e2e-private"; constexpr char e2e_public[] = "_e2e-public"; constexpr char e2e_mnemonic[] = "_e2e-mnemonic"; +constexpr auto metadataKeyJsonKey = "metadataKey"; +constexpr auto certificateSha256FingerprintKey = "certificateSha256Fingerprint"; + constexpr qint64 blockSize = 1024; QList oldCipherFormatSplit(const QByteArray &cipher) @@ -265,14 +289,12 @@ QByteArray encryptPrivateKey( /* Create and initialise the context */ if(!ctx) { - qCInfo(lcCse()) << "Error creating cipher"; - handleErrors(); + qCInfo(lcCse()) << "Error creating cipher" << handleErrors(); } /* Initialise the decryption operation. */ if(!EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr)) { - qCInfo(lcCse()) << "Error initializing context with aes_256"; - handleErrors(); + qCInfo(lcCse()) << "Error initializing context with aes_256" << handleErrors(); } // No padding @@ -280,14 +302,12 @@ QByteArray encryptPrivateKey( /* Set IV length. */ if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr)) { - qCInfo(lcCse()) << "Error setting iv length"; - handleErrors(); + qCInfo(lcCse()) << "Error setting iv length" << handleErrors(); } /* Initialise key and IV */ if(!EVP_EncryptInit_ex(ctx, nullptr, nullptr, (unsigned char *)key.constData(), (unsigned char *)iv.constData())) { - qCInfo(lcCse()) << "Error initialising key and iv"; - handleErrors(); + qCInfo(lcCse()) << "Error initialising key and iv" << handleErrors(); } // We write the base64 encoded private key @@ -299,8 +319,7 @@ QByteArray encryptPrivateKey( // Do the actual encryption int len = 0; if(!EVP_EncryptUpdate(ctx, unsignedData(ctext), &len, (unsigned char *)privateKeyB64.constData(), privateKeyB64.size())) { - qCInfo(lcCse()) << "Error encrypting"; - handleErrors(); + qCInfo(lcCse()) << "Error encrypting" << handleErrors(); } int clen = len; @@ -309,16 +328,14 @@ QByteArray encryptPrivateKey( * this stage, but this does not occur in GCM mode */ if(1 != EVP_EncryptFinal_ex(ctx, unsignedData(ctext) + len, &len)) { - qCInfo(lcCse()) << "Error finalizing encryption"; - handleErrors(); + qCInfo(lcCse()) << "Error finalizing encryption" << handleErrors(); } clen += len; /* Get the e2EeTag */ QByteArray e2EeTag(OCC::Constants::e2EeTagSize, '\0'); if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, OCC::Constants::e2EeTagSize, unsignedData(e2EeTag))) { - qCInfo(lcCse()) << "Error getting the e2EeTag"; - handleErrors(); + qCInfo(lcCse()) << "Error getting the e2EeTag" << handleErrors(); } QByteArray cipherTXT; @@ -515,40 +532,136 @@ QByteArray privateKeyToPem(const QByteArray key) { return pem; } -QByteArray encryptStringAsymmetric(const QSslKey key, const QByteArray &data) +namespace internals { + +[[nodiscard]] std::optional encryptStringAsymmetric(ENGINE *sslEngine, + EVP_PKEY *publicKey, + int pad_mode, + const QByteArray& binaryData); + +[[nodiscard]] std::optional encryptStringAsymmetricWithToken(ENGINE *sslEngine, + PKCS11_KEY *publicKey, + const QByteArray& binaryData); + +[[nodiscard]] std::optional decryptStringAsymmetric(ENGINE *sslEngine, + EVP_PKEY *privateKey, + int pad_mode, + const QByteArray& binaryData); + +[[nodiscard]] std::optional decryptStringAsymmetricWithToken(ENGINE *sslEngine, + PKCS11_KEY *privateKey, + const QByteArray &binaryData); + +} + +std::optional encryptStringAsymmetric(const CertificateInformation &selectedCertificate, + const ClientSideEncryption &encryptionEngine, + const QSslKey &key, + const QByteArray &binaryData) { - Q_ASSERT(!key.isNull()); - if (key.isNull()) { - qCDebug(lcCse) << "Public key is null. Could not encrypt."; - return {}; + Q_UNUSED(key) + + if (encryptionEngine.useTokenBasedEncryption()) { + auto encryptedBase64Result = internals::encryptStringAsymmetricWithToken(encryptionEngine.sslEngine(), + selectedCertificate.getPublicKey(), + binaryData); + + if (!encryptedBase64Result) { + qCWarning(lcCseEncryption()) << "encrypt failed"; + return {}; + } + + if (encryptedBase64Result->isEmpty()) { + qCDebug(lcCseEncryption()) << "ERROR. Could not encrypt data"; + return {}; + } + + return encryptedBase64Result; + } else { + const auto key = encryptionEngine.getPublicKey(); + Q_ASSERT(!key.isNull()); + if (key.isNull()) { + qCDebug(lcCse) << "Public key is null. Could not encrypt."; + return {}; + } + Bio publicKeyBio; + const auto publicKeyPem = key.toPem(); + BIO_write(publicKeyBio, publicKeyPem.constData(), publicKeyPem.size()); + const auto publicKey = PKey::readPublicKey(publicKeyBio); + + auto encryptedBase64Result = internals::encryptStringAsymmetric(encryptionEngine.sslEngine(), publicKey, RSA_PKCS1_OAEP_PADDING, binaryData); + + if (!encryptedBase64Result) { + qCWarning(lcCseEncryption()) << "encrypt failed"; + return {}; + } + + if (encryptedBase64Result->isEmpty()) { + qCDebug(lcCseEncryption()) << "ERROR. Could not encrypt data"; + return {}; + } + + return encryptedBase64Result; } - Bio publicKeyBio; - const auto publicKeyPem = key.toPem(); - BIO_write(publicKeyBio, publicKeyPem.constData(), publicKeyPem.size()); - const auto publicKey = PKey::readPublicKey(publicKeyBio); - return EncryptionHelper::encryptStringAsymmetric(publicKey, data); } -QByteArray decryptStringAsymmetric(const QByteArray &privateKeyPem, const QByteArray &data) +std::optional decryptStringAsymmetric(const CertificateInformation &selectedCertificate, + const ClientSideEncryption &encryptionEngine, + const QByteArray &base64Data, + const QByteArray &expectedCertificateSha256Fingerprint) { - Q_ASSERT(!privateKeyPem.isEmpty()); - if (privateKeyPem.isEmpty()) { - qCDebug(lcCse) << "Private key is empty. Could not encrypt."; + if (!encryptionEngine.isInitialized()) { + qCWarning(lcCse()) << "end-to-end encryption is disabled"; return {}; } - Bio privateKeyBio; - BIO_write(privateKeyBio, privateKeyPem.constData(), privateKeyPem.size()); - const auto key = PKey::readPrivateKey(privateKeyBio); + if (encryptionEngine.useTokenBasedEncryption()) { + if (selectedCertificate.sha256Fingerprint() != expectedCertificateSha256Fingerprint) { + qCWarning(lcCse()) << "wrong certificate: cannot decrypt what has been encrypted with another certificate:" << expectedCertificateSha256Fingerprint << "current certificate" << selectedCertificate.sha256Fingerprint(); + return {}; + } - // Also base64 decode the result - const auto decryptResult = EncryptionHelper::decryptStringAsymmetric(key, data); + const auto decryptBase64Result = internals::decryptStringAsymmetricWithToken(encryptionEngine.sslEngine(), + selectedCertificate.getPrivateKey(), + QByteArray::fromBase64(base64Data)); + if (!decryptBase64Result) { + qCWarning(lcCse()) << "decrypt failed"; + return {}; + } - if (decryptResult.isEmpty()) { - qCDebug(lcCse()) << "ERROR. Could not decrypt data"; - return {}; + if (decryptBase64Result->isEmpty()) { + qCDebug(lcCse()) << "ERROR. Could not decrypt data"; + return {}; + } + return decryptBase64Result; + } else { + const auto privateKeyPem = encryptionEngine.getPrivateKey(); + Q_ASSERT(!privateKeyPem.isEmpty()); + if (privateKeyPem.isEmpty()) { + qCDebug(lcCse) << "Private key is empty. Could not encrypt."; + return {}; + } + + Bio privateKeyBio; + BIO_write(privateKeyBio, privateKeyPem.constData(), privateKeyPem.size()); + const auto key = PKey::readPrivateKey(privateKeyBio); + + qCInfo(lcCseDecryption()) << "decryptStringAsymmetric:" + << "private key:" << privateKeyPem.toBase64() + << "data:" << base64Data; + + const auto decryptBase64Result = internals::decryptStringAsymmetric(encryptionEngine.sslEngine(), key, RSA_PKCS1_OAEP_PADDING, QByteArray::fromBase64(base64Data)); + if (!decryptBase64Result) { + qCWarning(lcCse()) << "decrypt failed"; + return {}; + } + + if (decryptBase64Result->isEmpty()) { + qCDebug(lcCse()) << "ERROR. Could not decrypt data"; + return {}; + } + return decryptBase64Result; } - return decryptResult; } QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data) { @@ -558,15 +671,13 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data) /* Create and initialise the context */ if(!ctx) { - qCInfo(lcCse()) << "Error creating cipher"; - handleErrors(); + qCInfo(lcCse()) << "Error creating cipher" << handleErrors(); return {}; } /* Initialise the decryption operation. */ if(!EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), nullptr, nullptr, nullptr)) { - qCInfo(lcCse()) << "Error initializing context with aes_128"; - handleErrors(); + qCInfo(lcCse()) << "Error initializing context with aes_128" << handleErrors(); return {}; } @@ -575,15 +686,13 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data) /* Set IV length. */ if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr)) { - qCInfo(lcCse()) << "Error setting iv length"; - handleErrors(); + qCInfo(lcCse()) << "Error setting iv length" << handleErrors(); return {}; } /* Initialise key and IV */ if(!EVP_EncryptInit_ex(ctx, nullptr, nullptr, (unsigned char *)key.constData(), (unsigned char *)iv.constData())) { - qCInfo(lcCse()) << "Error initialising key and iv"; - handleErrors(); + qCInfo(lcCse()) << "Error initialising key and iv" << handleErrors(); return {}; } @@ -596,8 +705,7 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data) // Do the actual encryption int len = 0; if(!EVP_EncryptUpdate(ctx, unsignedData(ctext), &len, (unsigned char *)dataB64.constData(), dataB64.size())) { - qCInfo(lcCse()) << "Error encrypting"; - handleErrors(); + qCInfo(lcCse()) << "Error encrypting" << handleErrors(); return {}; } @@ -607,8 +715,7 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data) * this stage, but this does not occur in GCM mode */ if(1 != EVP_EncryptFinal_ex(ctx, unsignedData(ctext) + len, &len)) { - qCInfo(lcCse()) << "Error finalizing encryption"; - handleErrors(); + qCInfo(lcCse()) << "Error finalizing encryption" << handleErrors(); return {}; } clen += len; @@ -616,8 +723,7 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data) /* Get the e2EeTag */ QByteArray e2EeTag(OCC::Constants::e2EeTagSize, '\0'); if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, OCC::Constants::e2EeTagSize, unsignedData(e2EeTag))) { - qCInfo(lcCse()) << "Error getting the e2EeTag"; - handleErrors(); + qCInfo(lcCse()) << "Error getting the e2EeTag" << handleErrors(); return {}; } @@ -633,122 +739,269 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data) return result; } -QByteArray decryptStringAsymmetric(EVP_PKEY *privateKey, const QByteArray& data) { +namespace internals { + +std::optional decryptStringAsymmetric(ENGINE *sslEngine, + EVP_PKEY *privateKey, + int pad_mode, + const QByteArray& binaryData) { int err = -1; - qCInfo(lcCseDecryption()) << "Start to work the decryption."; - auto ctx = PKeyCtx::forKey(privateKey, ENGINE_get_default_RSA()); + auto ctx = PKeyCtx::forKey(privateKey, sslEngine); if (!ctx) { - qCInfo(lcCseDecryption()) << "Could not create the PKEY context."; - handleErrors(); + qCInfo(lcCseDecryption()) << "Could not create the PKEY context." << handleErrors(); return {}; } err = EVP_PKEY_decrypt_init(ctx); if (err <= 0) { - qCInfo(lcCseDecryption()) << "Could not init the decryption of the metadata"; - handleErrors(); + qCInfo(lcCseDecryption()) << "Could not init the decryption of the metadata" << handleErrors(); return {}; } - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) { - qCInfo(lcCseDecryption()) << "Error setting the encryption padding."; - handleErrors(); + if (EVP_PKEY_CTX_set_rsa_padding(ctx, pad_mode) <= 0) { + qCInfo(lcCseDecryption()) << "Error setting the encryption padding." << handleErrors(); return {}; } - if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha256()) <= 0) { - qCInfo(lcCseDecryption()) << "Error setting OAEP SHA 256"; - handleErrors(); + if (pad_mode != RSA_PKCS1_PADDING && EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha1()) <= 0) { + qCInfo(lcCseDecryption()) << "Error setting OAEP SHA 256" << handleErrors(); return {}; } - if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256()) <= 0) { - qCInfo(lcCseDecryption()) << "Error setting MGF1 padding"; - handleErrors(); + if (pad_mode != RSA_PKCS1_PADDING && EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha1()) <= 0) { + qCInfo(lcCseDecryption()) << "Error setting MGF1 padding" << handleErrors(); return {}; } size_t outlen = 0; - err = EVP_PKEY_decrypt(ctx, nullptr, &outlen, (unsigned char *)data.constData(), data.size()); + err = EVP_PKEY_decrypt(ctx, nullptr, &outlen, (unsigned char *)binaryData.constData(), binaryData.size()); if (err <= 0) { - qCInfo(lcCseDecryption()) << "Could not determine the buffer length"; - handleErrors(); + qCInfo(lcCseDecryption()) << "Could not determine the buffer length" << handleErrors(); return {}; - } else { - qCInfo(lcCseDecryption()) << "Size of output is: " << outlen; - qCInfo(lcCseDecryption()) << "Size of data is: " << data.size(); } QByteArray out(static_cast(outlen), '\0'); - if (EVP_PKEY_decrypt(ctx, unsignedData(out), &outlen, (unsigned char *)data.constData(), data.size()) <= 0) { + if (EVP_PKEY_decrypt(ctx, unsignedData(out), &outlen, (unsigned char *)binaryData.constData(), binaryData.size()) <= 0) { const auto error = handleErrors(); qCCritical(lcCseDecryption()) << "Could not decrypt the data." << error; return {}; - } else { - qCInfo(lcCseDecryption()) << "data decrypted successfully"; } // we don't need extra zeroes in out, so let's only return meaningful data out = QByteArray(out.constData(), outlen); - - qCInfo(lcCse()) << out; - return out; + return out.toBase64(); } -QByteArray encryptStringAsymmetric(EVP_PKEY *publicKey, const QByteArray& data) { - int err = -1; - - auto ctx = PKeyCtx::forKey(publicKey, ENGINE_get_default_RSA()); +std::optional encryptStringAsymmetric(ENGINE *sslEngine, + EVP_PKEY *publicKey, + int pad_mode, + const QByteArray& binaryData) { + auto ctx = PKeyCtx::forKey(publicKey, sslEngine); if (!ctx) { - qCInfo(lcCse()) << "Could not initialize the pkey context."; - exit(1); + qCInfo(lcCseEncryption()) << "Could not initialize the pkey context." << publicKey << sslEngine; + return {}; } if (EVP_PKEY_encrypt_init(ctx) != 1) { - qCInfo(lcCse()) << "Error initilaizing the encryption."; - exit(1); + qCInfo(lcCseEncryption()) << "Error initilaizing the encryption." << handleErrors(); + return {}; } - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) { - qCInfo(lcCse()) << "Error setting the encryption padding."; - exit(1); + if (EVP_PKEY_CTX_set_rsa_padding(ctx, pad_mode) <= 0) { + qCInfo(lcCseEncryption()) << "Error setting the encryption padding." << handleErrors(); + return {}; } - if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha256()) <= 0) { - qCInfo(lcCse()) << "Error setting OAEP SHA 256"; - exit(1); + if (pad_mode != RSA_PKCS1_PADDING && EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha1()) <= 0) { + qCInfo(lcCseEncryption()) << "Error setting OAEP SHA 256" << handleErrors(); + return {}; } - if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256()) <= 0) { - qCInfo(lcCse()) << "Error setting MGF1 padding"; - exit(1); + if (pad_mode != RSA_PKCS1_PADDING && EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha1()) <= 0) { + qCInfo(lcCseEncryption()) << "Error setting MGF1 padding" << handleErrors(); + return {}; } size_t outLen = 0; - if (EVP_PKEY_encrypt(ctx, nullptr, &outLen, (unsigned char *)data.constData(), data.size()) != 1) { - qCInfo(lcCse()) << "Error retrieving the size of the encrypted data"; - exit(1); - } else { - qCInfo(lcCse()) << "Encryption Length:" << outLen; + if (EVP_PKEY_encrypt(ctx, nullptr, &outLen, (unsigned char *)binaryData.constData(), binaryData.size()) != 1) { + qCInfo(lcCseEncryption()) << "Error retrieving the size of the encrypted data" << handleErrors(); + return {}; } QByteArray out(static_cast(outLen), '\0'); - if (EVP_PKEY_encrypt(ctx, unsignedData(out), &outLen, (unsigned char *)data.constData(), data.size()) != 1) { - qCInfo(lcCse()) << "Could not encrypt key." << err; - exit(1); + if (EVP_PKEY_encrypt(ctx, unsignedData(out), &outLen, (unsigned char *)binaryData.constData(), binaryData.size()) != 1) { + qCInfo(lcCseEncryption()) << "Could not encrypt key." << handleErrors(); + return {}; } - qCInfo(lcCse()) << out.toBase64(); - return out; + // Transform the encrypted data into base64. + return out.toBase64(); +} + +} + +void debugOpenssl() +{ + if (ERR_peek_error() == 0) { + return; + } + + const char *file; + char errorMessage[255]; + int line; + while (const auto errorNumber = ERR_get_error_line(&file, &line)) { + ERR_error_string(errorNumber, errorMessage); + qCWarning(lcCse()) << errorMessage << file << line; + } +} + +namespace internals { + +std::optional encryptStringAsymmetricWithToken(ENGINE *sslEngine, + PKCS11_KEY *publicKey, + const QByteArray& binaryData) +{ + return encryptStringAsymmetric(sslEngine, PKCS11_get_public_key(publicKey), RSA_PKCS1_PADDING, binaryData); +} + +std::optional decryptStringAsymmetricWithToken(ENGINE *sslEngine, + PKCS11_KEY *privateKey, + const QByteArray &binaryData) +{ + return decryptStringAsymmetric(sslEngine, PKCS11_get_private_key(privateKey), RSA_PKCS1_PADDING, binaryData); +} + +} + +} + + +ClientSideEncryption::ClientSideEncryption() +{ +} + +bool ClientSideEncryption::isInitialized() const +{ + return useTokenBasedEncryption() || !getMnemonic().isEmpty(); +} + +const QSslKey &ClientSideEncryption::getPublicKey() const +{ + return _publicKey; +} + +void ClientSideEncryption::setPublicKey(const QSslKey &publicKey) +{ + _publicKey = publicKey; +} + +const QByteArray &ClientSideEncryption::getPrivateKey() const +{ + return _privateKey; +} + +void ClientSideEncryption::setPrivateKey(const QByteArray &privateKey) +{ + _privateKey = privateKey; +} + +const CertificateInformation &ClientSideEncryption::getTokenCertificate() const +{ + return _encryptionCertificate; +} + +CertificateInformation ClientSideEncryption::getTokenCertificateByFingerprint(const QByteArray &expectedFingerprint) const +{ + CertificateInformation result; + + if (_encryptionCertificate.sha256Fingerprint() == expectedFingerprint) { + result = _encryptionCertificate; + return result; + } + + const auto itCertificate = std::find_if(_otherCertificates.begin(), _otherCertificates.end(), [expectedFingerprint] (const auto &oneCertificate) { + return oneCertificate.sha256Fingerprint() == expectedFingerprint; + }); + if (itCertificate != _otherCertificates.end()) { + result = *itCertificate; + return result; + } + + return result; } +bool ClientSideEncryption::useTokenBasedEncryption() const +{ + return _encryptionCertificate.getPublicKey() && _encryptionCertificate.getPrivateKey(); } -ClientSideEncryption::ClientSideEncryption() = default; +const QString &ClientSideEncryption::getMnemonic() const +{ + return _mnemonic; +} -void ClientSideEncryption::initialize(const AccountPtr &account) +void ClientSideEncryption::setCertificate(const QSslCertificate &certificate) +{ + _certificate = certificate; +} + +const QSslCertificate& ClientSideEncryption::getCertificate() const +{ + return _certificate; +} + +ENGINE* ClientSideEncryption::sslEngine() const +{ + return ENGINE_get_default_RSA(); +} + +ClientSideEncryptionTokenSelector *ClientSideEncryption::usbTokenInformation() +{ + return &_usbTokenInformation; +} + +bool ClientSideEncryption::canEncrypt() const +{ + if (!isInitialized()) { + return false; + } + if (useTokenBasedEncryption()) { + return _encryptionCertificate.canEncrypt(); + } + + return true; +} + +bool ClientSideEncryption::canDecrypt() const +{ + return isInitialized(); +} + +bool ClientSideEncryption::userCertificateNeedsMigration() const +{ + if (!isInitialized()) { + return false; + } + if (useTokenBasedEncryption()) { + return _encryptionCertificate.userCertificateNeedsMigration(); + } + + return false; +} + +QByteArray ClientSideEncryption::certificateSha256Fingerprint() const +{ + if (useTokenBasedEncryption()) { + return _encryptionCertificate.sha256Fingerprint(); + } + + return {}; +} + +void ClientSideEncryption::initialize(QWidget *settingsDialog, + const AccountPtr &account) { Q_ASSERT(account); @@ -759,7 +1012,216 @@ void ClientSideEncryption::initialize(const AccountPtr &account) return; } - fetchCertificateFromKeyChain(account); + if (account->enforceUseHardwareTokenEncryption()) { + if (_usbTokenInformation.isSetup()) { + initializeHardwareTokenEncryption(settingsDialog, account); + } else if (account->e2eEncryptionKeysGenerationAllowed() && account->askUserForMnemonic()) { + Q_EMIT startingDiscoveryEncryptionUsbToken(); + auto futureTokenDiscoveryResult = new QFutureWatcher(this); + auto tokenDiscoveryResult = _usbTokenInformation.searchForCertificates(account); + futureTokenDiscoveryResult->setFuture(tokenDiscoveryResult); + connect(futureTokenDiscoveryResult, &QFutureWatcher::finished, + this, [this, settingsDialog, account, futureTokenDiscoveryResult] () { + completeHardwareTokenInitialization(settingsDialog, account); + delete futureTokenDiscoveryResult; + Q_EMIT finishedDiscoveryEncryptionUsbToken(); + }); + } else { + emit initializationFinished(); + } + } else { + fetchCertificateFromKeyChain(account); + } +} + +void ClientSideEncryption::initializeHardwareTokenEncryption(QWidget *settingsDialog, + const AccountPtr &account) +{ + auto ctx = Pkcs11Context{Pkcs11Context::State::CreateContext}; + + if (PKCS11_CTX_load(ctx, account->encryptionHardwareTokenDriverPath().toLatin1().constData())) { + qCWarning(lcCse()) << "loading pkcs11 engine failed:" << ERR_reason_error_string(ERR_get_error()); + + failedToInitialize(account); + return; + } + + auto tokensCount = 0u; + PKCS11_SLOT *tempTokenSlots = nullptr; + /* get information on all slots */ + if (PKCS11_enumerate_slots(ctx, &tempTokenSlots, &tokensCount) < 0) { + qCWarning(lcCse()) << "no slots available" << ERR_reason_error_string(ERR_get_error()); + + failedToInitialize(account); + return; + } + + auto deleter = [&ctx, tokensCount] (PKCS11_SLOT* pointer) noexcept -> void { + qCWarning(lcCse()) << "destructor" << pointer << ctx; + PKCS11_release_all_slots(ctx, pointer, tokensCount); + }; + + auto tokenSlots = decltype(_tokenSlots){tempTokenSlots, deleter}; + + auto currentSlot = static_cast(nullptr); + for(auto i = 0u; i < tokensCount; ++i) { + currentSlot = PKCS11_find_next_token(ctx, tokenSlots.get(), tokensCount, currentSlot); + if (currentSlot == nullptr || currentSlot->token == nullptr) { + break; + } + + qCDebug(lcCse()) << "Slot manufacturer......:" << currentSlot->manufacturer; + qCDebug(lcCse()) << "Slot description.......:" << currentSlot->description; + qCDebug(lcCse()) << "Slot token label.......:" << currentSlot->token->label; + qCDebug(lcCse()) << "Slot token manufacturer:" << currentSlot->token->manufacturer; + qCDebug(lcCse()) << "Slot token model.......:" << currentSlot->token->model; + qCDebug(lcCse()) << "Slot token serialnr....:" << currentSlot->token->serialnr; + + auto logged_in = 0; + if (PKCS11_is_logged_in(currentSlot, 0, &logged_in) != 0) { + qCWarning(lcCse()) << "PKCS11_is_logged_in failed" << ERR_reason_error_string(ERR_get_error()); + + failedToInitialize(account); + return; + } + + while (true) { + auto pinHasToBeCached = false; + auto newPin = _cachedPin; + + if (newPin.isEmpty()) { + /* perform pkcs #11 login */ + bool ok; + newPin = QInputDialog::getText(settingsDialog, + tr("PIN needed to login to token"), + tr("Enter Certificate USB Token PIN:"), + QLineEdit::Password, + {}, + &ok); + if (!ok || newPin.isEmpty()) { + qCWarning(lcCse()) << "an USER pin is required"; + + Q_EMIT initializationFinished(); + return; + } + + pinHasToBeCached = true; + } + + const auto newPinData = newPin.toLatin1(); + if (PKCS11_login(currentSlot, 0, newPinData.data()) != 0) { + QMessageBox::warning(settingsDialog, + tr("Invalid PIN. Login failed"), + tr("Login to the token failed after providing the user PIN. It may be invalid or wrong. Please try again !"), + QMessageBox::Ok); + _cachedPin.clear(); + continue; + } + + /* check if user is logged in */ + if (PKCS11_is_logged_in(currentSlot, 0, &logged_in) != 0) { + qCWarning(lcCse()) << "PKCS11_is_logged_in failed" << ERR_reason_error_string(ERR_get_error()); + + _cachedPin.clear(); + failedToInitialize(account); + return; + } + if (!logged_in) { + qCWarning(lcCse()) << "PKCS11_is_logged_in says user is not logged in, expected to be logged in"; + + _cachedPin.clear(); + failedToInitialize(account); + return; + } + + if (pinHasToBeCached) { + cacheTokenPin(newPin); + } + + break; + } + + auto keysCount = 0u; + auto certificatesFromToken = static_cast(nullptr); + if (PKCS11_enumerate_certs(currentSlot->token, &certificatesFromToken, &keysCount)) { + qCWarning(lcCse()) << "PKCS11_enumerate_certs failed" << ERR_reason_error_string(ERR_get_error()); + + failedToInitialize(account); + return; + } + + for (auto certificateIndex = 0u; certificateIndex < keysCount; ++certificateIndex) { + const auto currentCertificate = &certificatesFromToken[certificateIndex]; + + Bio out; + const auto ret = PEM_write_bio_X509(out, currentCertificate->x509); + if (ret <= 0){ + qCWarning(lcCse()) << "PEM_write_bio_X509 failed" << ERR_reason_error_string(ERR_get_error()); + + failedToInitialize(account); + return; + } + + const auto result = BIO2ByteArray(out); + auto sslCertificate = QSslCertificate{result, QSsl::Pem}; + + if (sslCertificate.isSelfSigned()) { + qCDebug(lcCse()) << "newly found certificate is self signed: goint to ignore it"; + continue; + } + + const auto certificateKey = PKCS11_find_key(currentCertificate); + if (!certificateKey) { + qCWarning(lcCse()) << "PKCS11_find_key failed" << ERR_reason_error_string(ERR_get_error()); + + failedToInitialize(account); + return; + } + + _otherCertificates.emplace_back(certificateKey, certificateKey, std::move(sslCertificate)); + } + } + + for (const auto &oneCertificateInformation : _otherCertificates) { + if (oneCertificateInformation.isSelfSigned()) { + qCDebug(lcCse()) << "newly found certificate is self signed: goint to ignore it"; + continue; + } + + if (!_usbTokenInformation.sha256Fingerprint().isEmpty() && oneCertificateInformation.sha256Fingerprint() != _usbTokenInformation.sha256Fingerprint()) { + qCInfo(lcCse()) << "skipping certificate from" << "with fingerprint" << oneCertificateInformation.sha256Fingerprint() << "different from" << _usbTokenInformation.sha256Fingerprint(); + continue; + } + + const auto &sslErrors = oneCertificateInformation.verify(); + for (const auto &sslError : sslErrors) { + qCInfo(lcCse()) << "certificate validation error" << sslError; + } + + setEncryptionCertificate(oneCertificateInformation); + + if (canEncrypt() && !checkEncryptionIsWorking()) { + qCWarning(lcCse()) << "encryption is not properly setup"; + + failedToInitialize(account); + return; + } + + if (!canEncrypt()) { + Q_EMIT userCertificateNeedsMigrationChanged(); + } + + saveCertificateIdentification(account); + + emit initializationFinished(); + + _context = std::move(ctx); + _tokenSlots = std::move(tokenSlots); + + return; + } + + failedToInitialize(account); } void ClientSideEncryption::fetchCertificateFromKeyChain(const AccountPtr &account) @@ -815,14 +1277,48 @@ bool ClientSideEncryption::checkPublicKeyValidity(const AccountPtr &account) con BIO_write(publicKeyBio, publicKeyPem.constData(), publicKeyPem.size()); auto publicKey = PKey::readPublicKey(publicKeyBio); - auto encryptedData = EncryptionHelper::encryptStringAsymmetric(publicKey, data.toBase64()); + auto encryptedData = EncryptionHelper::encryptStringAsymmetric(account->e2e()->getTokenCertificate(), *account->e2e(), account->e2e()->_publicKey, data.toBase64()); + if (!encryptedData) { + qCWarning(lcCse()) << "encryption error"; + return false; + } Bio privateKeyBio; QByteArray privateKeyPem = account->e2e()->_privateKey; BIO_write(privateKeyBio, privateKeyPem.constData(), privateKeyPem.size()); auto key = PKey::readPrivateKey(privateKeyBio); - QByteArray decryptResult = QByteArray::fromBase64(EncryptionHelper::decryptStringAsymmetric(key, encryptedData)); + const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(account->e2e()->getTokenCertificate(), *account->e2e(), *encryptedData, account->e2e()->certificateSha256Fingerprint()); + if (!decryptionResult) { + qCWarning(lcCse()) << "encryption error"; + return false; + } + const auto decryptResult = QByteArray::fromBase64(*decryptionResult); + + if (data != decryptResult) { + qCInfo(lcCse()) << "invalid private key"; + return false; + } + + return true; +} + +bool ClientSideEncryption::checkEncryptionIsWorking() const +{ + QByteArray data = EncryptionHelper::generateRandom(64); + + auto encryptedData = EncryptionHelper::encryptStringAsymmetric(getTokenCertificate(), *this, getPublicKey(), data); + if (!encryptedData) { + qCWarning(lcCse()) << "encryption error"; + return false; + } + + const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(getTokenCertificate(), *this, *encryptedData, getTokenCertificate().sha256Fingerprint()); + if (!decryptionResult) { + qCWarning(lcCse()) << "encryption error"; + return false; + } + QByteArray decryptResult = QByteArray::fromBase64(*decryptionResult); if (data != decryptResult) { qCInfo(lcCse()) << "invalid private key"; @@ -1070,9 +1566,9 @@ void ClientSideEncryption::mnemonicKeyFetched(QKeychain::Job *incoming) return; } - _mnemonic = readJob->textData(); + setMnemonic(readJob->textData()); - qCInfo(lcCse()) << "Mnemonic key fetched from keychain: " << _mnemonic; + qCInfo(lcCse()) << "Mnemonic key fetched from keychain"; checkServerHasSavedKeys(account); } @@ -1131,10 +1627,45 @@ void ClientSideEncryption::writeCertificate(const AccountPtr &account, const QSt job->start(); } +void ClientSideEncryption::completeHardwareTokenInitialization(QWidget *settingsDialog, + const OCC::AccountPtr &account) +{ + if (_usbTokenInformation.isSetup()) { + initializeHardwareTokenEncryption(settingsDialog, account); + } else { + emit initializationFinished(); + } +} + +void ClientSideEncryption::setMnemonic(const QString &mnemonic) +{ + if (_mnemonic == mnemonic) { + return; + } + + _mnemonic = mnemonic; + + Q_EMIT canEncryptChanged(); + Q_EMIT canDecryptChanged(); +} + +void ClientSideEncryption::setEncryptionCertificate(CertificateInformation certificateInfo) +{ + if (_encryptionCertificate == certificateInfo) { + return; + } + + _encryptionCertificate = std::move(certificateInfo); + + Q_EMIT canEncryptChanged(); + Q_EMIT canDecryptChanged(); + Q_EMIT userCertificateNeedsMigrationChanged(); +} + void ClientSideEncryption::generateMnemonic() { const auto list = WordList::getRandomWords(12); - _mnemonic = list.join(' '); + setMnemonic(list.join(' ')); } template @@ -1176,6 +1707,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); @@ -1187,6 +1722,12 @@ void ClientSideEncryption::forgetSensitiveData(const AccountPtr &account) deletePrivateKeyJob->start(); deleteCertJob->start(); deleteMnemonicJob->start(); + _usbTokenInformation.setSha256Fingerprint({}); + account->setEncryptionCertificateFingerprint({}); + _encryptionCertificate.clear(); + Q_EMIT canDecryptChanged(); + Q_EMIT canEncryptChanged(); + Q_EMIT userCertificateNeedsMigrationChanged(); } void ClientSideEncryption::getUsersPublicKeyFromServer(const AccountPtr &account, const QStringList &userIds) @@ -1218,6 +1759,11 @@ void ClientSideEncryption::getUsersPublicKeyFromServer(const AccountPtr &account job->start(); } +void ClientSideEncryption::migrateCertificate() +{ + _usbTokenInformation.clear(); +} + void ClientSideEncryption::handlePrivateKeyDeleted(const QKeychain::Job* const incoming) { const auto error = incoming->error(); @@ -1255,7 +1801,7 @@ void ClientSideEncryption::handleMnemonicDeleted(const QKeychain::Job* const inc } qCDebug(lcCse) << "Mnemonic successfully deleted from keychain. Clearing."; - _mnemonic = QString(); + setMnemonic({}); Q_EMIT mnemonicDeleted(); checkAllSensitiveDataDeleted(); } @@ -1275,7 +1821,7 @@ void ClientSideEncryption::handlePublicKeyDeleted(const QKeychain::Job * const i bool ClientSideEncryption::sensitiveDataRemaining() const { - return !_privateKey.isEmpty() || !_certificate.isNull() || !_mnemonic.isEmpty(); + return !_privateKey.isEmpty() || !_certificate.isNull() || !_mnemonic.isEmpty() || !_usbTokenInformation.sha256Fingerprint().isEmpty() || _encryptionCertificate.sensitiveDataRemaining(); } void ClientSideEncryption::failedToInitialize(const AccountPtr &account) @@ -1284,6 +1830,19 @@ void ClientSideEncryption::failedToInitialize(const AccountPtr &account) Q_EMIT initializationFinished(); } +void ClientSideEncryption::saveCertificateIdentification(const AccountPtr &account) const +{ + account->setEncryptionCertificateFingerprint(_usbTokenInformation.sha256Fingerprint()); +} + +void ClientSideEncryption::cacheTokenPin(const QString pin) +{ + _cachedPin = pin; + QTimer::singleShot(86400000, [this] () { + _cachedPin.clear(); + }); +} + void ClientSideEncryption::checkAllSensitiveDataDeleted() { if (sensitiveDataRemaining()) { @@ -1633,7 +2192,7 @@ void ClientSideEncryption::decryptPrivateKey(const AccountPtr &account, const QB if (ok) { prev = dialog.textValue(); - _mnemonic = prev; + setMnemonic(prev); QString mnemonic = prev.split(" ").join(QString()).toLower(); // split off salt @@ -2355,4 +2914,189 @@ OCC::NextcloudSslCertificate::operator QSslCertificate() const return _certificate; } +CertificateInformation::CertificateInformation() +{ + checkEncryptionCertificate(); +} + +CertificateInformation::CertificateInformation(PKCS11_KEY *publicKey, + PKCS11_KEY *privateKey, + QSslCertificate &&certificate) + : _publicKey(publicKey) + , _privateKey(privateKey) + , _certificate(std::move(certificate)) +{ + qCInfo(lcCse()) << "key metadata:" + << "type:" << (_privateKey->isPrivate ? "is private" : "is public") + << "label:" << _privateKey->label + << "need login:" << (_privateKey->needLogin ? "true" : "false"); + + checkEncryptionCertificate(); +} + +bool CertificateInformation::operator==(const CertificateInformation &other) const +{ + return _certificate.digest(QCryptographicHash::Sha256) == other._certificate.digest(QCryptographicHash::Sha256); +} + +void CertificateInformation::clear() +{ + _publicKey = nullptr; + _privateKey = nullptr; + _certificate.clear(); + _certificateExpired = true; + _certificateNotYetValid = true; + _certificateRevoked = true; + _certificateInvalid = true; +} + +QList CertificateInformation::verify() const +{ + auto result = QSslCertificate::verify({_certificate}); + + auto hasNeededExtendedKeyUsageExtension = false; + for (const auto &oneExtension : _certificate.extensions()) { + if (oneExtension.oid() == QStringLiteral("2.5.29.37")) { + const auto extendedKeyUsageList = oneExtension.value().toList(); + for (const auto &oneExtendedKeyUsageValue : extendedKeyUsageList) { + if (oneExtendedKeyUsageValue == QStringLiteral("E-mail Protection")) { + hasNeededExtendedKeyUsageExtension = true; + break; + } + } + } + } + if (!hasNeededExtendedKeyUsageExtension) { + result.append(QSslError{QSslError::InvalidPurpose}); + } + + return result; +} + +bool CertificateInformation::isSelfSigned() const +{ + return _certificate.isSelfSigned(); +} + +PKCS11_KEY *CertificateInformation::getPublicKey() const +{ + return _publicKey; +} + +PKCS11_KEY *CertificateInformation::getPrivateKey() const +{ + return canDecrypt() ? _privateKey : nullptr; +} + +bool CertificateInformation::canEncrypt() const +{ + return _publicKey && !_certificateExpired && !_certificateNotYetValid && !_certificateRevoked && !_certificateInvalid; +} + +bool CertificateInformation::canDecrypt() const +{ + return _privateKey; +} + +bool CertificateInformation::userCertificateNeedsMigration() const +{ + return _publicKey && _privateKey && + (_certificateExpired || _certificateNotYetValid || _certificateRevoked || _certificateInvalid); +} + +bool CertificateInformation::sensitiveDataRemaining() const +{ + return _publicKey && _privateKey && !_certificate.isNull(); +} + +QByteArray CertificateInformation::sha256Fingerprint() const +{ + return _certificate.digest(QCryptographicHash::Sha256).toBase64(); +} + +void CertificateInformation::checkEncryptionCertificate() +{ + _certificateExpired = false; + _certificateNotYetValid = false; + _certificateRevoked = false; + _certificateInvalid = false; + + const auto sslErrors = QSslCertificate::verify({_certificate}); + for (const auto &sslError : sslErrors) { + qCDebug(lcCse()) << "certificate validation error" << sslError; + switch (sslError.error()) + { + case QSslError::CertificateExpired: + _certificateExpired = true; + break; + case QSslError::CertificateNotYetValid: + _certificateNotYetValid = true; + break; + case QSslError::CertificateRevoked: + _certificateRevoked = true; + break; + case QSslError::UnableToGetIssuerCertificate: + case QSslError::UnableToDecryptCertificateSignature: + case QSslError::UnableToDecodeIssuerPublicKey: + case QSslError::CertificateSignatureFailed: + case QSslError::InvalidNotBeforeField: + case QSslError::InvalidNotAfterField: + case QSslError::SelfSignedCertificate: + case QSslError::SelfSignedCertificateInChain: + case QSslError::UnableToGetLocalIssuerCertificate: + case QSslError::UnableToVerifyFirstCertificate: + case QSslError::InvalidCaCertificate: + case QSslError::PathLengthExceeded: + case QSslError::InvalidPurpose: + case QSslError::CertificateUntrusted: + case QSslError::CertificateRejected: + case QSslError::SubjectIssuerMismatch: + case QSslError::AuthorityIssuerSerialNumberMismatch: + case QSslError::NoPeerCertificate: + case QSslError::HostNameMismatch: + case QSslError::NoSslSupport: + case QSslError::CertificateBlacklisted: + case QSslError::CertificateStatusUnknown: + case QSslError::OcspNoResponseFound: + case QSslError::OcspMalformedRequest: + case QSslError::OcspMalformedResponse: + case QSslError::OcspInternalError: + case QSslError::OcspTryLater: + case QSslError::OcspSigRequred: + case QSslError::OcspUnauthorized: + case QSslError::OcspResponseCannotBeTrusted: + case QSslError::OcspResponseCertIdUnknown: + case QSslError::OcspResponseExpired: + case QSslError::OcspStatusUnknown: + case QSslError::UnspecifiedError: + _certificateInvalid = true; + break; + case QSslError::NoError: + break; + } + } +} + +Pkcs11Context::Pkcs11Context(State initState) + : _pkcsS11Ctx(initState == State::CreateContext ? PKCS11_CTX_new() : nullptr) +{ +} + +Pkcs11Context::Pkcs11Context(Pkcs11Context &&otherContext) + : _pkcsS11Ctx(otherContext._pkcsS11Ctx) +{ + otherContext._pkcsS11Ctx = nullptr; +} + +Pkcs11Context::~Pkcs11Context() +{ + qCWarning(lcCse()) << "destructor" << this; + if (_pkcsS11Ctx) { + PKCS11_CTX_free(_pkcsS11Ctx); + _pkcsS11Ctx = nullptr; + } else { + qCWarning(lcCse()) << "destructor" << this << "nullptr"; + } +} + } diff --git a/src/libsync/clientsideencryption.h b/src/libsync/clientsideencryption.h index 936b144423a9..5d38b8a60ecd 100644 --- a/src/libsync/clientsideencryption.h +++ b/src/libsync/clientsideencryption.h @@ -1,3 +1,17 @@ +/* + * Copyright © 2017, Tomaz Canabrava + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + #ifndef CLIENTSIDEENCRYPTION_H #define CLIENTSIDEENCRYPTION_H @@ -6,6 +20,9 @@ #include "clientsideencryptionprimitives.h" #include "accountfwd.h" +#include "networkjobs.h" +#include "clientsideencryptiontokenselector.h" + #include #include #include @@ -17,8 +34,15 @@ #include #include +#include + #include +#include +#include + +class QWidget; + namespace QKeychain { class Job; class WritePasswordJob; @@ -29,48 +53,97 @@ namespace OCC { QString e2eeBaseUrl(const OCC::AccountPtr &account); +class ClientSideEncryption; + +class CertificateInformation { +public: + CertificateInformation(); + + CertificateInformation(PKCS11_KEY *publicKey, + PKCS11_KEY *privateKey, + QSslCertificate &&certificate); + + [[nodiscard]] bool operator==(const CertificateInformation &other) const; + + void clear(); + + [[nodiscard]] QList verify() const; + + [[nodiscard]] bool isSelfSigned() const; + + [[nodiscard]] PKCS11_KEY* getPublicKey() const; + + [[nodiscard]] PKCS11_KEY* getPrivateKey() const; + + [[nodiscard]] bool canEncrypt() const; + + [[nodiscard]] bool canDecrypt() const; + + [[nodiscard]] bool userCertificateNeedsMigration() const; + + [[nodiscard]] bool sensitiveDataRemaining() const; + + [[nodiscard]] QByteArray sha256Fingerprint() const; + +private: + void checkEncryptionCertificate(); + + PKCS11_KEY* _publicKey = nullptr; + + PKCS11_KEY* _privateKey = nullptr; + + QSslCertificate _certificate; + + bool _certificateExpired = true; + + bool _certificateNotYetValid = true; + + bool _certificateRevoked = true; + + bool _certificateInvalid = true; +}; + namespace EncryptionHelper { - OWNCLOUDSYNC_EXPORT QByteArray generateRandomFilename(); - OWNCLOUDSYNC_EXPORT QByteArray generateRandom(int size); - QByteArray generatePassword(const QString &wordlist, const QByteArray& salt); - OWNCLOUDSYNC_EXPORT QByteArray encryptPrivateKey( - const QByteArray& key, - const QByteArray& privateKey, - const QByteArray &salt - ); - OWNCLOUDSYNC_EXPORT QByteArray decryptPrivateKey( - const QByteArray& key, - const QByteArray& data - ); - OWNCLOUDSYNC_EXPORT QByteArray extractPrivateKeySalt(const QByteArray &data); - OWNCLOUDSYNC_EXPORT QByteArray encryptStringSymmetric( - const QByteArray& key, - const QByteArray& data - ); - OWNCLOUDSYNC_EXPORT QByteArray decryptStringSymmetric( - const QByteArray& key, - const QByteArray& data - ); - OWNCLOUDSYNC_EXPORT QByteArray encryptStringAsymmetric(const QSslKey key, const QByteArray &data); - OWNCLOUDSYNC_EXPORT QByteArray decryptStringAsymmetric(const QByteArray &privateKeyPem, const QByteArray &data); - - QByteArray privateKeyToPem(const QByteArray key); - - //TODO: change those two EVP_PKEY into QSslKey. - QByteArray encryptStringAsymmetric( - EVP_PKEY *publicKey, - const QByteArray& data - ); - QByteArray decryptStringAsymmetric( - EVP_PKEY *privateKey, - const QByteArray& data - ); - - OWNCLOUDSYNC_EXPORT bool fileEncryption(const QByteArray &key, const QByteArray &iv, - QFile *input, QFile *output, QByteArray& returnTag); - - OWNCLOUDSYNC_EXPORT bool fileDecryption(const QByteArray &key, const QByteArray &iv, - QFile *input, QFile *output); + +QByteArray generateRandomFilename(); +OWNCLOUDSYNC_EXPORT QByteArray generateRandom(int size); +QByteArray generatePassword(const QString &wordlist, const QByteArray& salt); +OWNCLOUDSYNC_EXPORT QByteArray encryptPrivateKey( + const QByteArray& key, + const QByteArray& privateKey, + const QByteArray &salt +); +OWNCLOUDSYNC_EXPORT QByteArray decryptPrivateKey( + const QByteArray& key, + const QByteArray& data +); +OWNCLOUDSYNC_EXPORT QByteArray extractPrivateKeySalt(const QByteArray &data); +OWNCLOUDSYNC_EXPORT QByteArray encryptStringSymmetric( + const QByteArray& key, + const QByteArray& data +); +OWNCLOUDSYNC_EXPORT QByteArray decryptStringSymmetric( + const QByteArray& key, + const QByteArray& data +); + +[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional encryptStringAsymmetric(const CertificateInformation &selectedCertificate, + const ClientSideEncryption &encryptionEngine, + const QSslKey &key, + const QByteArray &binaryData); + +[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional decryptStringAsymmetric(const CertificateInformation &selectedCertificate, + const ClientSideEncryption &encryptionEngine, + const QByteArray &base64Data, + const QByteArray &expectedCertificateSha256Fingerprint); + +QByteArray privateKeyToPem(const QByteArray key); + +OWNCLOUDSYNC_EXPORT bool fileEncryption(const QByteArray &key, const QByteArray &iv, + QFile *input, QFile *output, QByteArray& returnTag); + +OWNCLOUDSYNC_EXPORT bool fileDecryption(const QByteArray &key, const QByteArray &iv, + QFile *input, QFile *output); OWNCLOUDSYNC_EXPORT bool dataEncryption(const QByteArray &key, const QByteArray &iv, const QByteArray &input, QByteArray &output, QByteArray &returnTag); OWNCLOUDSYNC_EXPORT bool dataDecryption(const QByteArray &key, const QByteArray &iv, const QByteArray &input, QByteArray &output); @@ -148,14 +221,52 @@ class OWNCLOUDSYNC_EXPORT NextcloudSslCertificate class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject { Q_OBJECT + + Q_PROPERTY(bool canEncrypt READ canEncrypt NOTIFY canEncryptChanged FINAL) + Q_PROPERTY(bool canDecrypt READ canDecrypt NOTIFY canDecryptChanged FINAL) + Q_PROPERTY(bool userCertificateNeedsMigration READ userCertificateNeedsMigration NOTIFY userCertificateNeedsMigrationChanged FINAL) public: ClientSideEncryption(); - QByteArray _privateKey; - QSslKey _publicKey; - QSslCertificate _certificate; - QString _mnemonic; - bool _newMnemonicGenerated = false; + [[nodiscard]] bool isInitialized() const; + + [[nodiscard]] bool tokenIsSetup() const; + + [[nodiscard]] const QSslKey& getPublicKey() const; + + void setPublicKey(const QSslKey &publicKey); + + [[nodiscard]] const QByteArray& getPrivateKey() const; + + void setPrivateKey(const QByteArray &privateKey); + + [[nodiscard]] const CertificateInformation& getTokenCertificate() const; + + [[nodiscard]] CertificateInformation getTokenCertificateByFingerprint(const QByteArray &expectedFingerprint) const; + + [[nodiscard]] bool useTokenBasedEncryption() const; + + [[nodiscard]] const QString &getMnemonic() const; + + void setCertificate(const QSslCertificate &certificate); + + [[nodiscard]] const QSslCertificate& getCertificate() const; + + [[nodiscard]] ENGINE* sslEngine() const; + + [[nodiscard]] QByteArray generateSignatureCryptographicMessageSyntax(const QByteArray &data) const; + + [[nodiscard]] bool verifySignatureCryptographicMessageSyntax(const QByteArray &cmsContent, const QByteArray &data, const QVector &certificatePems) const; + + [[nodiscard]] ClientSideEncryptionTokenSelector* usbTokenInformation(); + + [[nodiscard]] bool canEncrypt() const; + + [[nodiscard]] bool canDecrypt() const; + + [[nodiscard]] bool userCertificateNeedsMigration() const; + + [[nodiscard]] QByteArray certificateSha256Fingerprint() const; signals: void initializationFinished(bool isNewMnemonicGenerated = false); @@ -167,18 +278,27 @@ class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject { void certificateFetchedFromKeychain(QSslCertificate certificate); void certificatesFetchedFromServer(const QHash &results); void certificateWriteComplete(const QSslCertificate &certificate); + void displayTokenInitDialog(); -public: - [[nodiscard]] QByteArray generateSignatureCryptographicMessageSyntax(const QByteArray &data) const; - [[nodiscard]] bool verifySignatureCryptographicMessageSyntax(const QByteArray &cmsContent, const QByteArray &data, const QVector &certificatePems) const; + void startingDiscoveryEncryptionUsbToken(); + void finishedDiscoveryEncryptionUsbToken(); + + void canEncryptChanged(); + void canDecryptChanged(); + void userCertificateNeedsMigrationChanged(); public slots: - void initialize(const OCC::AccountPtr &account); + void initialize(QWidget *settingsDialog, + const OCC::AccountPtr &account); + void initializeHardwareTokenEncryption(QWidget* settingsDialog, + const OCC::AccountPtr &account); void forgetSensitiveData(const OCC::AccountPtr &account); void getUsersPublicKeyFromServer(const OCC::AccountPtr &account, const QStringList &userIds); void fetchCertificateFromKeyChain(const OCC::AccountPtr &account, const QString &userId); void writeCertificate(const OCC::AccountPtr &account, const QString &userId, const QSslCertificate &certificate); + void migrateCertificate(); + private slots: void generateKeyPair(const OCC::AccountPtr &account); void encryptPrivateKey(const OCC::AccountPtr &account); @@ -205,6 +325,13 @@ private slots: void writePrivateKey(const OCC::AccountPtr &account); void writeCertificate(const OCC::AccountPtr &account); + void completeHardwareTokenInitialization(QWidget *settingsDialog, + const OCC::AccountPtr &account); + + void setMnemonic(const QString &mnemonic); + + void setEncryptionCertificate(CertificateInformation certificateInfo); + private: void generateMnemonic(); @@ -246,9 +373,30 @@ private slots: [[nodiscard]] bool checkServerPublicKeyValidity(const QByteArray &serverPublicKeyString) const; [[nodiscard]] bool sensitiveDataRemaining() const; + [[nodiscard]] bool checkEncryptionIsWorking() const; + void failedToInitialize(const AccountPtr &account); - bool isInitialized = false; + void saveCertificateIdentification(const AccountPtr &account) const; + void cacheTokenPin(const QString pin); + + QByteArray _privateKey; + QSslKey _publicKey; + QSslCertificate _certificate; + QString _mnemonic; + bool _newMnemonicGenerated = false; + + QString _cachedPin; + + ClientSideEncryptionTokenSelector _usbTokenInformation; + + CertificateInformation _encryptionCertificate; + std::vector _otherCertificates; + + Pkcs11Context _context{Pkcs11Context::State::EmptyContext}; + std::unique_ptr> _tokenSlots; }; + } // namespace OCC + #endif diff --git a/src/libsync/clientsideencryptionjobs.cpp b/src/libsync/clientsideencryptionjobs.cpp index d39c393d1777..761a7d5ffdb2 100644 --- a/src/libsync/clientsideencryptionjobs.cpp +++ b/src/libsync/clientsideencryptionjobs.cpp @@ -299,14 +299,16 @@ bool DeleteMetadataApiJob::finished() LockEncryptFolderApiJob::LockEncryptFolderApiJob(const AccountPtr &account, const QByteArray &fileId, + const QByteArray &certificateSha256Fingerprint, SyncJournalDb *journalDb, - const QSslKey publicKey, + const QSslKey &sslkey, QObject *parent) : AbstractNetworkJob(account, e2eeBaseUrl(account) + QStringLiteral("lock/") + fileId, parent) , _fileId(fileId) + , _certificateSha256Fingerprint(certificateSha256Fingerprint) , _journalDb(journalDb) - , _publicKey(publicKey) { + Q_UNUSED(sslkey) } void LockEncryptFolderApiJob::start() @@ -315,8 +317,12 @@ void LockEncryptFolderApiJob::start() if (!folderTokenEncrypted.isEmpty()) { qCInfo(lcCseJob()) << "lock folder started for:" << path() << " for fileId: " << _fileId << " but we need to first lift the previous lock"; - const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->_privateKey, folderTokenEncrypted); - const auto unlockJob = new OCC::UnlockEncryptFolderApiJob(_account, _fileId, folderToken, _journalDb, this); + const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), folderTokenEncrypted, _certificateSha256Fingerprint); + if (!folderToken) { + qCWarning(lcCseJob()) << "decrypt failed"; + return; + } + const auto unlockJob = new OCC::UnlockEncryptFolderApiJob(_account, _fileId, *folderToken, _journalDb, this); unlockJob->setShouldRollbackMetadataChanges(true); connect(unlockJob, &UnlockEncryptFolderApiJob::done, this, [this]() { this->start(); @@ -364,9 +370,13 @@ bool LockEncryptFolderApiJob::finished() qCInfo(lcCseJob()) << "lock folder finished with code" << retCode << " for:" << path() << " for fileId: " << _fileId << " token:" << token; - if (!_publicKey.isNull()) { - const auto folderTokenEncrypted = EncryptionHelper::encryptStringAsymmetric(_publicKey, token); - _journalDb->setE2EeLockedFolder(_fileId, folderTokenEncrypted); + if (!_account->e2e()->getPublicKey().isNull()) { + const auto folderTokenEncrypted = EncryptionHelper::encryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), _account->e2e()->getPublicKey(), token); + if (!folderTokenEncrypted) { + qCWarning(lcCseJob()) << "decrypt failed"; + return false; + } + _journalDb->setE2EeLockedFolder(_fileId, *folderTokenEncrypted); } //TODO: Parse the token and submit. @@ -431,7 +441,7 @@ void StorePrivateKeyApiJob::start() QUrl url = Utility::concatUrlPath(account()->url(), path()); url.setQuery(query); - qCInfo(lcStorePrivateKeyApiJob) << "Sending the private key" << _privKey.data(); + qCDebug(lcStorePrivateKeyApiJob) << "Sending the private key"; sendRequest("POST", url, req, &_privKey); AbstractNetworkJob::start(); } @@ -470,7 +480,7 @@ void SignPublicKeyApiJob::start() QUrl url = Utility::concatUrlPath(account()->url(), path()); url.setQuery(query); - qCInfo(lcSignPublicKeyApiJob) << "Sending the CSR" << _csr.data(); + qCDebug(lcSignPublicKeyApiJob) << "Sending the CSR"; sendRequest("POST", url, req, &_csr); AbstractNetworkJob::start(); } diff --git a/src/libsync/clientsideencryptionjobs.h b/src/libsync/clientsideencryptionjobs.h index 9052e9bbcb42..d76c7b5f7a69 100644 --- a/src/libsync/clientsideencryptionjobs.h +++ b/src/libsync/clientsideencryptionjobs.h @@ -3,9 +3,10 @@ #include "networkjobs.h" #include "accountfwd.h" + +#include #include #include -#include namespace OCC { /* Here are all of the network jobs for the client side encryption. @@ -145,7 +146,12 @@ class OWNCLOUDSYNC_EXPORT LockEncryptFolderApiJob : public AbstractNetworkJob { Q_OBJECT public: - explicit LockEncryptFolderApiJob(const AccountPtr &account, const QByteArray &fileId, SyncJournalDb *journalDb, const QSslKey publicKey, QObject *parent = nullptr); + explicit LockEncryptFolderApiJob(const AccountPtr &account, + const QByteArray &fileId, + const QByteArray &certificateSha256Fingerprint, + SyncJournalDb *journalDb, + const QSslKey &sslkey, + QObject *parent = nullptr); void setCounter(const quint64 counter); @@ -163,6 +169,7 @@ public slots: private: QByteArray _fileId; + QByteArray _certificateSha256Fingerprint; QPointer _journalDb; QSslKey _publicKey; quint64 _counter = 0; diff --git a/src/libsync/clientsideencryptionprimitives.cpp b/src/libsync/clientsideencryptionprimitives.cpp index 92210df5fc1e..00bf7995daae 100644 --- a/src/libsync/clientsideencryptionprimitives.cpp +++ b/src/libsync/clientsideencryptionprimitives.cpp @@ -16,14 +16,6 @@ namespace OCC { -Bio::Bio() - : _bio(BIO_new(BIO_s_mem())) -{ -} -Bio::~Bio() -{ - BIO_free_all(_bio); -} Bio::operator const BIO *() const { return _bio; @@ -104,4 +96,4 @@ PKey::operator EVP_PKEY *() const return _pkey; } -} \ No newline at end of file +} diff --git a/src/libsync/clientsideencryptionprimitives.h b/src/libsync/clientsideencryptionprimitives.h index 28efc9b29c3d..54248246d4c6 100644 --- a/src/libsync/clientsideencryptionprimitives.h +++ b/src/libsync/clientsideencryptionprimitives.h @@ -11,18 +11,28 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ + #pragma once -#include + #include +#include +#include + namespace OCC { class Bio { public: - Bio(); + Bio() + : _bio(BIO_new(BIO_s_mem())) + { + } - ~Bio(); + ~Bio() + { + BIO_free_all(_bio); + } operator const BIO *() const; operator BIO *(); @@ -89,4 +99,50 @@ class PKey EVP_PKEY *_pkey = nullptr; }; -} \ No newline at end of file + +class Pkcs11Context { +public: + enum class State { + CreateContext, + EmptyContext, + }; + + explicit Pkcs11Context(State initState); + + Pkcs11Context(Pkcs11Context &&otherContext); + + Pkcs11Context(const Pkcs11Context&) = delete; + + + ~Pkcs11Context(); + + Pkcs11Context& operator=(Pkcs11Context &&otherContext) + { + if (&otherContext != this) { + if (_pkcsS11Ctx) { + PKCS11_CTX_free(_pkcsS11Ctx); + _pkcsS11Ctx = nullptr; + } + std::swap(_pkcsS11Ctx, otherContext._pkcsS11Ctx); + } + + return *this; + } + + Pkcs11Context& operator=(const Pkcs11Context&) = delete; + + operator const PKCS11_CTX*() const + { + return _pkcsS11Ctx; + } + + operator PKCS11_CTX*() + { + return _pkcsS11Ctx; + } + +private: + PKCS11_CTX* _pkcsS11Ctx = nullptr; +}; + +} diff --git a/src/libsync/clientsideencryptiontokenselector.cpp b/src/libsync/clientsideencryptiontokenselector.cpp new file mode 100644 index 000000000000..d7550fd117be --- /dev/null +++ b/src/libsync/clientsideencryptiontokenselector.cpp @@ -0,0 +1,328 @@ +/* + * Copyright © 2023, Matthieu Gallien + * Copyright (C) 2017 The Qt Company Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * Commercial License Usage + * Licensees holding valid commercial Qt licenses may use this file in + * accordance with the commercial license agreement provided with the + * Software or, alternatively, in accordance with the terms contained in + * a written agreement between you and The Qt Company. For licensing terms + * and conditions see https://www.qt.io/terms-conditions. For further + * information use the contact form at https://www.qt.io/contact-us. + * + * GNU Lesser General Public License Usage + * Alternatively, this file may be used under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software + * Foundation and appearing in the file LICENSE.LGPL3 included in the + * packaging of this file. Please review the following information to + * ensure the GNU Lesser General Public License version 3 requirements + * will be met: https://www.gnu.org/licenses/lgpl-3.0.html. + * + * GNU General Public License Usage + * Alternatively, this file may be used under the terms of the GNU + * General Public License version 2.0 or (at your option) the GNU General + * Public license version 3 or any later version approved by the KDE Free + * Qt Foundation. The licenses are as published by the Free Software + * Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 + * included in the packaging of this file. Please review the following + * information to ensure the GNU General Public License requirements will + * be met: https://www.gnu.org/licenses/gpl-2.0.html and + * https://www.gnu.org/licenses/gpl-3.0.html. + */ + +#include "clientsideencryptiontokenselector.h" + +#include "clientsideencryptionprimitives.h" +#include "account.h" + +#include +#include +#include + +#if defined(Q_OS_WIN) +#include +#endif + +#include + +#include + +namespace { + +static unsigned char* unsignedData(QByteArray& array) +{ + return (unsigned char*)array.data(); +} + +static QByteArray BIO2ByteArray(OCC::Bio &b) { + auto pending = static_cast(BIO_ctrl_pending(b)); + QByteArray res(pending, '\0'); + BIO_read(b, unsignedData(res), pending); + return res; +} + +} + +namespace OCC +{ + +Q_LOGGING_CATEGORY(lcCseSelector, "nextcloud.sync.clientsideencryption.selector", QtInfoMsg) + +ClientSideEncryptionTokenSelector::ClientSideEncryptionTokenSelector(QObject *parent) + : QObject{parent} +{ + +} + +bool ClientSideEncryptionTokenSelector::isSetup() const +{ + return !_sha256Fingerprint.isEmpty(); +} + +QVariantList ClientSideEncryptionTokenSelector::discoveredCertificates() const +{ + return _discoveredCertificates; +} + +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 { + discoverCertificates(account); + }); +} + +void ClientSideEncryptionTokenSelector::setSha256Fingerprint(const QByteArray &sha256Fingerprint) +{ + if (_sha256Fingerprint == sha256Fingerprint) { + return; + } + + _sha256Fingerprint = sha256Fingerprint; + Q_EMIT sha256FingerprintChanged(); +} + +void ClientSideEncryptionTokenSelector::discoverCertificates(const AccountPtr &account) +{ +#if defined(Q_OS_WIN) + auto sslConfig = QSslConfiguration::defaultConfiguration(); + + for (const auto &storeName : std::vector{L"CA"}) { + auto systemStore = CertOpenSystemStore(0, storeName.data()); + if (systemStore) { + auto certificatePointer = PCCERT_CONTEXT{nullptr}; + while (true) { + certificatePointer = CertFindCertificateInStore(systemStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, nullptr, certificatePointer); + if (!certificatePointer) { + break; + } + const auto der = QByteArray{reinterpret_cast(certificatePointer->pbCertEncoded), + static_cast(certificatePointer->cbCertEncoded)}; + const auto cert = QSslCertificate{der, QSsl::Der}; + + qCDebug(lcCseSelector()) << "found certificate" << cert.subjectDisplayName() << cert.issuerDisplayName() << "from store" << storeName; + + sslConfig.addCaCertificate(cert); + } + CertCloseStore(systemStore, 0); + } + } + + QSslConfiguration::setDefaultConfiguration(sslConfig); +#endif + + qCDebug(lcCseSelector()) << "existing CA certificates"; + const auto currentSslConfig = QSslConfiguration::defaultConfiguration(); + const auto &caCertificates = currentSslConfig.caCertificates(); + for (const auto &oneCaCertificate : caCertificates) { + qCDebug(lcCseSelector()) << oneCaCertificate.subjectDisplayName() << oneCaCertificate.issuerDisplayName(); + } + + auto ctx = Pkcs11Context{Pkcs11Context::State::CreateContext}; + + auto rc = PKCS11_CTX_load(ctx, account->encryptionHardwareTokenDriverPath().toLatin1().constData()); + if (rc) { + qCWarning(lcCseSelector()) << "loading pkcs11 engine failed:" << ERR_reason_error_string(ERR_get_error()) << account->encryptionHardwareTokenDriverPath(); + + Q_EMIT failedToInitialize(account); + return; + } + + auto tokensCount = 0u; + PKCS11_SLOT *tempTokenSlots = nullptr; + /* get information on all slots */ + if (PKCS11_enumerate_slots(ctx, &tempTokenSlots, &tokensCount) < 0) { + qCWarning(lcCseSelector()) << "no slots available" << ERR_reason_error_string(ERR_get_error()); + + Q_EMIT failedToInitialize(account); + return; + } + + auto deleter = [&ctx, tokensCount] (PKCS11_SLOT* pointer) noexcept -> void { + PKCS11_release_all_slots(ctx, pointer, tokensCount); + }; + + auto tokenSlots = std::unique_ptr{tempTokenSlots, deleter}; + + if (!tokensCount) { + qCWarning(lcCseSelector()) << "no tokens found"; + + Q_EMIT failedToInitialize(account); + return; + } + + _discoveredCertificates.clear(); + auto currentSlot = static_cast(nullptr); + for(auto tokenIndex = 0u; tokenIndex < tokensCount; ++tokenIndex) { + currentSlot = PKCS11_find_next_token(ctx, tokenSlots.get(), tokensCount, currentSlot); + if (currentSlot == nullptr || currentSlot->token == nullptr) { + break; + } + + qCDebug(lcCseSelector()) << "Slot manufacturer......:" << currentSlot->manufacturer; + qCDebug(lcCseSelector()) << "Slot description.......:" << currentSlot->description; + qCDebug(lcCseSelector()) << "Slot token label.......:" << currentSlot->token->label; + qCDebug(lcCseSelector()) << "Slot token manufacturer:" << currentSlot->token->manufacturer; + qCDebug(lcCseSelector()) << "Slot token model.......:" << currentSlot->token->model; + qCDebug(lcCseSelector()) << "Slot token serialnr....:" << currentSlot->token->serialnr; + + auto keysCount = 0u; + auto certificatesFromToken = static_cast(nullptr); + if (PKCS11_enumerate_certs(currentSlot->token, &certificatesFromToken, &keysCount)) { + qCWarning(lcCseSelector()) << "PKCS11_enumerate_certs failed" << ERR_reason_error_string(ERR_get_error()); + + Q_EMIT failedToInitialize(account); + return; + } + + for (auto certificateIndex = 0u; certificateIndex < keysCount; ++certificateIndex) { + const auto currentCertificate = &certificatesFromToken[certificateIndex]; + qCInfo(lcCseSelector()) << "certificate metadata:" + << "label:" << currentCertificate->label; + + const auto certificateId = QByteArray{reinterpret_cast(currentCertificate->id), static_cast(currentCertificate->id_len)}; + qCInfo(lcCseSelector()) << "new certificate ID:" << certificateId.toBase64(); + + const auto certificateSubjectName = X509_get_subject_name(currentCertificate->x509); + if (!certificateSubjectName) { + qCWarning(lcCseSelector()) << "X509_get_subject_name failed" << ERR_reason_error_string(ERR_get_error()); + + Q_EMIT failedToInitialize(account); + return; + } + + Bio out; + const auto ret = PEM_write_bio_X509(out, currentCertificate->x509); + if (ret <= 0){ + qCWarning(lcCseSelector()) << "PEM_write_bio_X509 failed" << ERR_reason_error_string(ERR_get_error()); + + Q_EMIT failedToInitialize(account); + return; + } + + const auto result = BIO2ByteArray(out); + const auto sslCertificate = QSslCertificate{result, QSsl::Pem}; + const auto certificateDigest = sslCertificate.digest(QCryptographicHash::Sha256).toBase64(); + + qCInfo(lcCseSelector()) << "newly found certificate" + << "subject:" << sslCertificate.subjectDisplayName() + << "issuer:" << sslCertificate.issuerDisplayName() + << "valid since:" << sslCertificate.effectiveDate() + << "valid until:" << sslCertificate.expiryDate() + << "serial number:" << sslCertificate.serialNumber() + << "SHA256 fingerprint:" << certificateDigest; + + if (sslCertificate.isSelfSigned()) { + qCDebug(lcCseSelector()) << "newly found certificate is self signed: goint to ignore it"; + continue; + } + + auto hasNeededExtendedKeyUsageExtension = false; + const auto &allExtensions = sslCertificate.extensions(); + for (const auto &oneExtension : allExtensions) { + qCDebug(lcCseSelector()) << "extension:" << (oneExtension.isCritical() ? "is critical" : "") << (oneExtension.isSupported() ? "is supported" : "") << oneExtension.name() << oneExtension.value() << oneExtension.oid(); + if (oneExtension.oid() == QStringLiteral("2.5.29.37")) { + const auto extendedKeyUsageList = oneExtension.value().toList(); + for (const auto &oneExtendedKeyUsageValue : extendedKeyUsageList) { + qCDebug(lcCseSelector()) << "EKU:" << oneExtendedKeyUsageValue; + if (oneExtendedKeyUsageValue == QStringLiteral("E-mail Protection")) { + hasNeededExtendedKeyUsageExtension = true; + break; + } + } + } + } + if (!hasNeededExtendedKeyUsageExtension) { + qCDebug(lcCseSelector()) << "newly found certificate is missing the required EKU extension: Secure Email (1.3.6.1.5.5.7.3.4)"; + continue; + } + + _discoveredCertificates.push_back(QVariantMap{ + {QStringLiteral("label"), QString::fromLatin1(currentCertificate->label)}, + {QStringLiteral("subject"), sslCertificate.subjectDisplayName()}, + {QStringLiteral("issuer"), sslCertificate.issuerDisplayName()}, + {QStringLiteral("serialNumber"), sslCertificate.serialNumber()}, + {QStringLiteral("validSince"), sslCertificate.effectiveDate()}, + {QStringLiteral("validUntil"), sslCertificate.expiryDate()}, + {QStringLiteral("sha256Fingerprint"), certificateDigest}, + {QStringLiteral("certificate"), QVariant::fromValue(sslCertificate)}, + }); + + std::sort(_discoveredCertificates.begin(), _discoveredCertificates.end(), [] (const auto &first, const auto &second) -> bool { + return first.toMap()[QStringLiteral("validSince")].toDateTime() > second.toMap()[QStringLiteral("validSince")].toDateTime(); + }); + } + } + + Q_EMIT discoveredCertificatesChanged(); + processDiscoveredCertificates(); +} + +void ClientSideEncryptionTokenSelector::processDiscoveredCertificates() +{ + const auto &allCertificates = discoveredCertificates(); + for (const auto &oneCertificate : allCertificates) { + const auto certificateData = oneCertificate.toMap(); + const auto sslCertificate = certificateData[QStringLiteral("certificate")].value(); + if (sslCertificate.isNull()) { + qCDebug(lcCseSelector()) << "null certificate"; + continue; + } + const auto sslErrors = QSslCertificate::verify({sslCertificate}); + if (!sslErrors.isEmpty()) { + for (const auto &oneError : sslErrors) { + qCInfo(lcCseSelector()) << oneError; + } + continue; + } + + const auto &sha256Fingerprint = sslCertificate.digest(QCryptographicHash::Sha256).toBase64(); + qCInfo(lcCseSelector()) << "certificate is valid" << certificateData[QStringLiteral("subject")] << "from" << certificateData[QStringLiteral("issuer")] << "fingerprint" << sha256Fingerprint; + + setSha256Fingerprint(sha256Fingerprint); + Q_EMIT isSetupChanged(); + return; + } +} + +} diff --git a/src/libsync/clientsideencryptiontokenselector.h b/src/libsync/clientsideencryptiontokenselector.h new file mode 100644 index 000000000000..36ec320c366d --- /dev/null +++ b/src/libsync/clientsideencryptiontokenselector.h @@ -0,0 +1,77 @@ +/* + * Copyright © 2023, Matthieu Gallien + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifndef CLIENTSIDETOKENSELECTOR_H +#define CLIENTSIDETOKENSELECTOR_H + +#include "accountfwd.h" +#include "owncloudlib.h" + +#include +#include + +namespace OCC +{ + +class OWNCLOUDSYNC_EXPORT ClientSideEncryptionTokenSelector : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool isSetup READ isSetup NOTIFY isSetupChanged) + + Q_PROPERTY(QVariantList discoveredCertificates READ discoveredCertificates NOTIFY discoveredCertificatesChanged) + + Q_PROPERTY(QByteArray sha256Fingerprint READ sha256Fingerprint WRITE setSha256Fingerprint NOTIFY sha256FingerprintChanged) + +public: + explicit ClientSideEncryptionTokenSelector(QObject *parent = nullptr); + + [[nodiscard]] bool isSetup() const; + + [[nodiscard]] QVariantList discoveredCertificates() const; + + [[nodiscard]] QByteArray sha256Fingerprint() const; + + void clear(); + +public slots: + QFuture searchForCertificates(const OCC::AccountPtr &account); + + void setSha256Fingerprint(const QByteArray &sha256Fingerprint); + +signals: + + void isSetupChanged(); + + void discoveredCertificatesChanged(); + + void certificateIndexChanged(); + + void sha256FingerprintChanged(); + + void failedToInitialize(const OCC::AccountPtr &account); + +private: + void discoverCertificates(const OCC::AccountPtr &account); + + void processDiscoveredCertificates(); + + QVariantList _discoveredCertificates; + + QByteArray _sha256Fingerprint; +}; + +} + +#endif // CLIENTSIDETOKENSELECTOR_H diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 964b0ee74154..45135d04c68f 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -82,6 +82,16 @@ void ProcessDirectoryJob::start() { qCInfo(lcDisco) << "STARTING" << _currentFolder._server << _queryServer << _currentFolder._local << _queryLocal; + if (isInsideEncryptedTree()) { + auto folderDbRecord = SyncJournalFileRecord{}; + if (_discoveryData->_statedb->getFileRecord(_currentFolder._local, &folderDbRecord) && folderDbRecord.isValid()) { + if (_discoveryData->_account->encryptionCertificateFingerprint() != folderDbRecord._e2eCertificateFingerprint) { + qCDebug(lcDisco) << "encryption certificate needs update. Forcing full discovery"; + _queryServer = NormalQuery; + } + } + } + _discoveryData->_noCaseConflictRecordsInDb = _discoveryData->_statedb->caseClashConflictRecordPaths().isEmpty(); if (_queryServer == NormalQuery) { @@ -228,7 +238,7 @@ void ProcessDirectoryJob::process() continue; const auto isEncryptedFolderButE2eIsNotSetup = e.serverEntry.isValid() && e.serverEntry.isE2eEncrypted() && - _discoveryData->_account->e2e() && !_discoveryData->_account->e2e()->_publicKey.isNull() && _discoveryData->_account->e2e()->_privateKey.isNull(); + _discoveryData->_account->e2e() && !_discoveryData->_account->e2e()->getPublicKey().isNull() && _discoveryData->_account->e2e()->getPrivateKey().isNull(); if (isEncryptedFolderButE2eIsNotSetup) { checkAndUpdateSelectiveSyncListsForE2eeFolders(path._server + "/"); @@ -646,6 +656,8 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(const SyncFileItemPtr &it item->_e2eEncryptionStatus = serverEntry.isE2eEncrypted() ? SyncFileItem::EncryptionStatus::Encrypted : SyncFileItem::EncryptionStatus::NotEncrypted; if (serverEntry.isE2eEncrypted()) { item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(_discoveryData->_account->capabilities().clientSideEncryptionVersion()); + item->_e2eCertificateFingerprint = serverEntry.e2eCertificateFingerprint; + //Q_ASSERT(item->_e2eEncryptionStatus == SyncFileItem::EncryptionStatus::NotEncrypted || !item->_e2eCertificateFingerprint.isEmpty()); } item->_encryptedFileName = [=] { if (serverEntry.e2eMangledName.isEmpty()) { @@ -1113,7 +1125,10 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( if (dbEntry.isValid()) { bool typeChange = localEntry.isDirectory != dbEntry.isDirectory(); - if (!typeChange && localEntry.isVirtualFile) { + if (localEntry.isDirectory && dbEntry.isValid() && dbEntry.isE2eEncrypted() && dbEntry._e2eCertificateFingerprint != _discoveryData->_account->encryptionCertificateFingerprint()) { + item->_instruction = CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA; + item->_direction = SyncFileItem::Up; + } else if (!typeChange && localEntry.isVirtualFile) { if (noServerEntry) { item->_instruction = CSYNC_INSTRUCTION_REMOVE; item->_direction = SyncFileItem::Down; @@ -1424,6 +1439,8 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( // base is a record in the SyncJournal database that contains the data about the being-renamed folder with it's old name and encryption information item->_e2eEncryptionStatus = EncryptionStatusEnums::fromDbEncryptionStatus(base._e2eEncryptionStatus); item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(_discoveryData->_account->capabilities().clientSideEncryptionVersion()); + item->_e2eCertificateFingerprint = base._e2eCertificateFingerprint; + Q_ASSERT(item->_e2eEncryptionStatus == SyncFileItem::EncryptionStatus::NotEncrypted || !item->_e2eCertificateFingerprint.isEmpty()); } postProcessLocalNew(); /*if (item->isDirectory() && item->_instruction == CSYNC_INSTRUCTION_NEW && item->_direction == SyncFileItem::Up @@ -1711,6 +1728,12 @@ void ProcessDirectoryJob::processFileFinalize( } } + if (item->_direction == SyncFileItem::Up && item->isEncrypted() && !_discoveryData->_account->e2e()->canEncrypt()) { + item->_instruction = CSYNC_INSTRUCTION_ERROR; + item->_errorString = tr("Cannot modify encrypted item because the selected certificate is not valid."); + item->_status = SyncFileItem::Status::NormalError; + } + if (item->isDirectory() && item->_instruction == CSYNC_INSTRUCTION_SYNC) item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; bool removed = item->_instruction == CSYNC_INSTRUCTION_REMOVE; @@ -2073,6 +2096,8 @@ DiscoverySingleDirectoryJob *ProcessDirectoryJob::startAsyncServerQuery() const auto alreadyDownloaded = _discoveryData->_statedb->getFileRecord(_dirItem->_file, &record) && record.isValid(); // we need to make sure we first download all e2ee files/folders before migrating _dirItem->_isEncryptedMetadataNeedUpdate = alreadyDownloaded && serverJob->encryptedMetadataNeedUpdate(); + _dirItem->_e2eCertificateFingerprint = serverJob->certificateSha256Fingerprint(); + Q_ASSERT(_dirItem->_e2eEncryptionStatus == SyncFileItem::EncryptionStatus::NotEncrypted || !_dirItem->_e2eCertificateFingerprint.isEmpty()); _dirItem->_e2eEncryptionStatus = serverJob->currentEncryptionStatus(); _dirItem->_e2eEncryptionStatusRemote = serverJob->currentEncryptionStatus(); _dirItem->_e2eEncryptionServerCapability = serverJob->requiredEncryptionStatus(); diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index 2f1b67c4881a..8ead30795e5e 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -459,6 +459,11 @@ SyncFileItem::EncryptionStatus DiscoverySingleDirectoryJob::requiredEncryptionSt return _encryptionStatusRequired; } +QByteArray DiscoverySingleDirectoryJob::certificateSha256Fingerprint() const +{ + return _e2eCertificateFingerprint; +} + static void propertyMapToRemoteInfo(const QMap &map, RemotePermissions::MountedPermissionAlgorithm algorithm, RemoteInfo &result) { for (auto it = map.constBegin(); it != map.constEnd(); ++it) { diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index 89012df25f87..ef01dbe0f78a 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -71,6 +71,7 @@ struct RemoteInfo bool _isE2eEncrypted = false; bool isFileDropDetected = false; QString e2eMangledName; + QByteArray e2eCertificateFingerprint; bool sharedByMe = false; [[nodiscard]] bool isValid() const { return !name.isNull(); } @@ -157,6 +158,7 @@ class DiscoverySingleDirectoryJob : public QObject void abort(); [[nodiscard]] bool isFileDropDetected() const; [[nodiscard]] bool encryptedMetadataNeedUpdate() const; + [[nodiscard]] QByteArray certificateSha256Fingerprint() const; [[nodiscard]] SyncFileItem::EncryptionStatus currentEncryptionStatus() const; [[nodiscard]] SyncFileItem::EncryptionStatus requiredEncryptionStatus() const; @@ -197,6 +199,8 @@ private slots: bool _isFileDropDetected = false; bool _encryptedMetadataNeedUpdate = false; SyncFileItem::EncryptionStatus _encryptionStatusRequired = SyncFileItem::EncryptionStatus::NotEncrypted; + QByteArray _e2eCertificateFingerprint; + // If set, the discovery will finish with an error int64_t _size = 0; QString _error; diff --git a/src/libsync/encryptedfoldermetadatahandler.cpp b/src/libsync/encryptedfoldermetadatahandler.cpp index 65a394646cc8..c09fb7f94cea 100644 --- a/src/libsync/encryptedfoldermetadatahandler.cpp +++ b/src/libsync/encryptedfoldermetadatahandler.cpp @@ -97,7 +97,7 @@ void EncryptedFolderMetadataHandler::lockFolder() return; } - const auto lockJob = new LockEncryptFolderApiJob(_account, _folderId, _journalDb, _account->e2e()->_publicKey, this); + const auto lockJob = new LockEncryptFolderApiJob(_account, _folderId, _account->e2e()->certificateSha256Fingerprint(), _journalDb, _account->e2e()->getPublicKey(), this); connect(lockJob, &LockEncryptFolderApiJob::success, this, &EncryptedFolderMetadataHandler::slotFolderLockedSuccessfully); connect(lockJob, &LockEncryptFolderApiJob::error, this, &EncryptedFolderMetadataHandler::slotFolderLockedError); if (_account->capabilities().clientSideEncryptionVersion() >= 2.0) { diff --git a/src/libsync/encryptfolderjob.cpp b/src/libsync/encryptfolderjob.cpp index 295e24dd0c60..e7740a85b638 100644 --- a/src/libsync/encryptfolderjob.cpp +++ b/src/libsync/encryptfolderjob.cpp @@ -83,6 +83,7 @@ void EncryptFolderJob::slotEncryptionFlagSuccess(const QByteArray &fileId) if (!rec.isE2eEncrypted()) { rec._e2eEncryptionStatus = SyncJournalFileRecord::EncryptionStatus::Encrypted; + rec._e2eCertificateFingerprint = _account->e2e()->certificateSha256Fingerprint(); const auto result = _journal->setFileRecord(rec); if (!result) { qCWarning(lcEncryptFolderJob) << "Error when setting the file record to the database" << rec._path << result.error(); diff --git a/src/libsync/foldermetadata.cpp b/src/libsync/foldermetadata.cpp index 6536e283f439..6ef61953e57b 100644 --- a/src/libsync/foldermetadata.cpp +++ b/src/libsync/foldermetadata.cpp @@ -188,7 +188,7 @@ void FolderMetadata::setupExistingMetadata(const QByteArray &metadata) if (_folderUsers.contains(_account->davUser())) { const auto currentFolderUser = _folderUsers.value(_account->davUser()); - _metadataKeyForEncryption = decryptDataWithPrivateKey(currentFolderUser.encryptedMetadataKey); + _metadataKeyForEncryption = decryptDataWithPrivateKey(_account->e2e()->sslEngine(), currentFolderUser.encryptedMetadataKey); _metadataKeyForDecryption = _metadataKeyForEncryption; } @@ -278,7 +278,7 @@ void FolderMetadata::setupExistingMetadataLegacy(const QByteArray &metadata) const auto metadataKeyFromJson = metadataObj[metadataKeyKey].toString().toLocal8Bit(); if (!metadataKeyFromJson.isEmpty()) { // parse version 1.1 and 1.2 (both must have a single "metadataKey"), not "metadataKeys" as 1.0 - const auto decryptedMetadataKeyBase64 = decryptDataWithPrivateKey(QByteArray::fromBase64(metadataKeyFromJson)); + const auto decryptedMetadataKeyBase64 = decryptDataWithPrivateKey(_account->e2e()->sslEngine(), QByteArray::fromBase64(metadataKeyFromJson)); if (!decryptedMetadataKeyBase64.isEmpty()) { // fromBase64() multiple times just to stick with the old wrong way _metadataKeyForDecryption = QByteArray::fromBase64(QByteArray::fromBase64(decryptedMetadataKeyBase64)); @@ -300,7 +300,7 @@ void FolderMetadata::setupExistingMetadataLegacy(const QByteArray &metadata) if (!lastMetadataKeyFromJson.isEmpty()) { const auto lastMetadataKeyValueFromJson = metadataKeys.value(lastMetadataKeyFromJson).toString().toLocal8Bit(); if (!lastMetadataKeyValueFromJson.isEmpty()) { - const auto lastMetadataKeyValueFromJsonBase64 = decryptDataWithPrivateKey(QByteArray::fromBase64(lastMetadataKeyValueFromJson)); + const auto lastMetadataKeyValueFromJsonBase64 = decryptDataWithPrivateKey(_account->e2e()->sslEngine(), QByteArray::fromBase64(lastMetadataKeyValueFromJson)); if (!lastMetadataKeyValueFromJsonBase64.isEmpty()) { _metadataKeyForDecryption = QByteArray::fromBase64(QByteArray::fromBase64(lastMetadataKeyValueFromJsonBase64)); } @@ -423,29 +423,38 @@ void FolderMetadata::emitSetupComplete() } // RSA/ECB/OAEPWithSHA-256AndMGF1Padding using private / public key. -QByteArray FolderMetadata::encryptDataWithPublicKey(const QByteArray &data, const QSslKey &key) const +QByteArray FolderMetadata::encryptDataWithPublicKey(ENGINE *sslEngine, + const QByteArray &data, + const QSslKey &key) const { Bio publicKeyBio; const auto publicKeyPem = key.toPem(); BIO_write(publicKeyBio, publicKeyPem.constData(), publicKeyPem.size()); const auto publicKey = PKey::readPublicKey(publicKeyBio); + Q_UNUSED(publicKey) + Q_UNUSED(sslEngine) + // The metadata key is binary so base64 encode it first - return EncryptionHelper::encryptStringAsymmetric(publicKey, data); + return *EncryptionHelper::encryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), key, data); } -QByteArray FolderMetadata::decryptDataWithPrivateKey(const QByteArray &data) const +QByteArray FolderMetadata::decryptDataWithPrivateKey(ENGINE *sslEngine, + const QByteArray &data) const { Bio privateKeyBio; - BIO_write(privateKeyBio, _account->e2e()->_privateKey.constData(), _account->e2e()->_privateKey.size()); + BIO_write(privateKeyBio, _account->e2e()->getPrivateKey().constData(), _account->e2e()->getPrivateKey().size()); const auto privateKey = PKey::readPrivateKey(privateKeyBio); - const auto decryptResult = EncryptionHelper::decryptStringAsymmetric(privateKey, data); - if (decryptResult.isEmpty()) { + Q_UNUSED(privateKey) + Q_UNUSED(sslEngine) + + const auto decryptResult = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), data, _account->e2e()->certificateSha256Fingerprint()); + if (!decryptResult) { qCDebug(lcCseMetadata()) << "ERROR. Could not decrypt the metadata key"; _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError); } - return decryptResult; + return *decryptResult; } // AES/GCM/NoPadding (128 bit key size) @@ -470,7 +479,8 @@ QByteArray FolderMetadata::computeMetadataKeyChecksum(const QByteArray &metadata { auto hashAlgorithm = QCryptographicHash{QCryptographicHash::Sha256}; - hashAlgorithm.addData(_account->e2e()->_mnemonic.remove(' ').toUtf8()); + auto mnemonic = _account->e2e()->getMnemonic(); + hashAlgorithm.addData(mnemonic.remove(' ').toUtf8()); auto sortedFiles = _files; std::sort(sortedFiles.begin(), sortedFiles.end(), [](const auto &first, const auto &second) { return first.encryptedFilename < second.encryptedFilename; @@ -548,7 +558,7 @@ void FolderMetadata::initEmptyMetadata() } qCDebug(lcCseMetadata()) << "Setting up empty metadata v2"; if (_isRootEncryptedFolder) { - if (!addUser(_account->davUser(), _account->e2e()->_certificate)) { + if (!addUser(_account->davUser(), _account->e2e()->getCertificate())) { qCDebug(lcCseMetadata) << "Empty metadata setup failed. Could not add first user."; _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError); return; @@ -565,7 +575,7 @@ void FolderMetadata::initEmptyMetadataLegacy() qCDebug(lcCseMetadata) << "Settint up legacy empty metadata"; _metadataKeyForEncryption = EncryptionHelper::generateRandom(metadataKeySize); _metadataKeyForDecryption = _metadataKeyForEncryption; - QString publicKey = _account->e2e()->_publicKey.toPem().toBase64(); + QString publicKey = _account->e2e()->getPublicKey().toPem().toBase64(); QString displayName = _account->displayName(); _isMetadataValid = true; @@ -697,7 +707,7 @@ QByteArray FolderMetadata::encryptedMetadataLegacy() } const auto version = _account->capabilities().clientSideEncryptionVersion(); // multiple toBase64() just to keep with the old (wrong way) - const auto encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption().toBase64().toBase64(), _account->e2e()->_publicKey).toBase64(); + const auto encryptedMetadataKey = encryptDataWithPublicKey(_account->e2e()->sslEngine(), metadataKeyForEncryption().toBase64().toBase64(), _account->e2e()->getPublicKey()).toBase64(); const QJsonObject metadata{ {versionKey, version}, {metadataKeyKey, QJsonValue::fromVariant(encryptedMetadataKey)}, @@ -790,7 +800,7 @@ bool FolderMetadata::parseFileDropPart(const QJsonDocument &doc) if (userParsedId == _account->davUser()) { const auto fileDropEntryUser = UserWithFileDropEntryAccess{ userParsedId, - decryptDataWithPrivateKey(QByteArray::fromBase64(userParsed.value(usersEncryptedFiledropKey).toByteArray()))}; + decryptDataWithPrivateKey(_account->e2e()->sslEngine(), QByteArray::fromBase64(userParsed.value(usersEncryptedFiledropKey).toByteArray()))}; if (!fileDropEntryUser.isValid()) { qCDebug(lcCseMetadata()) << "Could not parse filedrop data. encryptedFiledropKey decryption failed"; _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError); @@ -951,6 +961,11 @@ bool FolderMetadata::encryptedMetadataNeedUpdate() const return !foundNestedFoldersOrIsNestedFolder; } +QByteArray FolderMetadata::certificateSha256Fingerprint() const +{ + return _e2eCertificateFingerprint; +} + bool FolderMetadata::moveFromFileDropToFiles() { if (_fileDropEntries.isEmpty()) { @@ -1046,7 +1061,7 @@ bool FolderMetadata::addUser(const QString &userId, const QSslCertificate &certi UserWithFolderAccess newFolderUser; newFolderUser.userId = userId; newFolderUser.certificatePem = certificate.toPem(); - newFolderUser.encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption(), certificatePublicKey); + newFolderUser.encryptedMetadataKey = encryptDataWithPublicKey(_account->e2e()->sslEngine(), metadataKeyForEncryption(), certificatePublicKey); _folderUsers[userId] = newFolderUser; updateUsersEncryptedMetadataKey(); @@ -1095,7 +1110,7 @@ void FolderMetadata::updateUsersEncryptedMetadataKey() continue; } - const auto encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption(), certificatePublicKey); + const auto encryptedMetadataKey = encryptDataWithPublicKey(_account->e2e()->sslEngine(), metadataKeyForEncryption(), certificatePublicKey); if (encryptedMetadataKey.isEmpty()) { qCWarning(lcCseMetadata()) << "Could not update folder users with empty encryptedMetadataKey!"; continue; diff --git a/src/libsync/foldermetadata.h b/src/libsync/foldermetadata.h index 0cdba5236eff..ad12219f77d2 100644 --- a/src/libsync/foldermetadata.h +++ b/src/libsync/foldermetadata.h @@ -116,6 +116,8 @@ class OWNCLOUDSYNC_EXPORT FolderMetadata : public QObject [[nodiscard]] bool encryptedMetadataNeedUpdate() const; + [[nodiscard]] QByteArray certificateSha256Fingerprint() const; + [[nodiscard]] bool moveFromFileDropToFiles(); // adds a user to have access to this folder (always generates new metadata key) @@ -150,8 +152,11 @@ public slots: [[nodiscard]] bool verifyMetadataKey(const QByteArray &metadataKey) const; - [[nodiscard]] QByteArray encryptDataWithPublicKey(const QByteArray &data, const QSslKey &key) const; - [[nodiscard]] QByteArray decryptDataWithPrivateKey(const QByteArray &data) const; + [[nodiscard]] QByteArray encryptDataWithPublicKey(ENGINE *sslEngine, + const QByteArray &data, + const QSslKey &key) const; + [[nodiscard]] QByteArray decryptDataWithPrivateKey(ENGINE *sslEngine, + const QByteArray &data) const; [[nodiscard]] QByteArray encryptJsonObject(const QByteArray& obj, const QByteArray pass) const; [[nodiscard]] QByteArray decryptJsonObject(const QByteArray& encryptedJsonBlob, const QByteArray& pass) const; @@ -232,6 +237,8 @@ private slots: // signature from server-side metadata QByteArray _initialSignature; + QByteArray _e2eCertificateFingerprint; + // both files and folders info QVector _files; diff --git a/src/libsync/progressdispatcher.cpp b/src/libsync/progressdispatcher.cpp index ec189b1c9703..5809d87c9857 100644 --- a/src/libsync/progressdispatcher.cpp +++ b/src/libsync/progressdispatcher.cpp @@ -58,6 +58,8 @@ QString Progress::asResultString(const SyncFileItem &item) return QCoreApplication::translate("progress", "Updated local metadata"); case CSYNC_INSTRUCTION_UPDATE_VFS_METADATA: return QCoreApplication::translate("progress", "Updated local virtual files metadata"); + case CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA: + return QCoreApplication::translate("progress", "Updated end-to-end encryption metadata"); case CSYNC_INSTRUCTION_NONE: case CSYNC_INSTRUCTION_EVAL: return QCoreApplication::translate("progress", "Unknown"); @@ -91,6 +93,8 @@ QString Progress::asActionString(const SyncFileItem &item) return QCoreApplication::translate("progress", "updating local metadata"); case CSYNC_INSTRUCTION_UPDATE_VFS_METADATA: return QCoreApplication::translate("progress", "updating local virtual files metadata"); + case CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA: + return QCoreApplication::translate("progress", "updating end-to-end encryption metadata"); case CSYNC_INSTRUCTION_NONE: case CSYNC_INSTRUCTION_EVAL: break; diff --git a/src/libsync/propagateremotemkdir.cpp b/src/libsync/propagateremotemkdir.cpp index e301beed5a72..868614e68f8f 100644 --- a/src/libsync/propagateremotemkdir.cpp +++ b/src/libsync/propagateremotemkdir.cpp @@ -261,6 +261,7 @@ void PropagateRemoteMkdir::slotEncryptFolderFinished(int status, EncryptionStatu qCDebug(lcPropagateRemoteMkdir) << "Success making the new folder encrypted"; propagator()->_activeJobList.removeOne(this); _item->_e2eEncryptionStatus = encryptionStatus; + _item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint(); _item->_e2eEncryptionStatusRemote = encryptionStatus; if (_item->isEncrypted()) { _item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(propagator()->account()->capabilities().clientSideEncryptionVersion()); diff --git a/src/libsync/propagateupload.cpp b/src/libsync/propagateupload.cpp index d486f861da03..7f4e6402480d 100644 --- a/src/libsync/propagateupload.cpp +++ b/src/libsync/propagateupload.cpp @@ -803,6 +803,10 @@ void PropagateUploadFileCommon::finalize() if (quotaIt != propagator()->_folderQuota.end()) quotaIt.value() -= _fileToUpload._size; + if (_item->isEncrypted() && _uploadingEncrypted) { + _item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint(); + } + // Update the database entry const auto result = propagator()->updateMetadata(*_item, Vfs::DatabaseMetadata); if (!result) { diff --git a/src/libsync/propagateuploadencrypted.cpp b/src/libsync/propagateuploadencrypted.cpp index c64483ed6f1e..3e873816cb63 100644 --- a/src/libsync/propagateuploadencrypted.cpp +++ b/src/libsync/propagateuploadencrypted.cpp @@ -185,4 +185,4 @@ void PropagateUploadEncrypted::slotUploadMetadataFinished(int statusCode, const outputInfo.size()); } -} // namespace OCC \ No newline at end of file +} // namespace OCC diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 4a9f656a4712..a2ff9cfbdee0 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -509,9 +509,13 @@ void SyncEngine::startSync() for (const auto &e2EeLockedFolder : e2EeLockedFolders) { const auto folderId = e2EeLockedFolder.first; qCInfo(lcEngine()) << "start unlock job for folderId:" << folderId; - const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->_privateKey, e2EeLockedFolder.second); + const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), e2EeLockedFolder.second, _account->e2e()->certificateSha256Fingerprint()); + if (!folderToken) { + qCWarning(lcEngine()) << "decrypt failed"; + return; + } // TODO: We need to rollback changes done to metadata in case we have an active lock, this needs to be implemented on the server first - const auto unlockJob = new OCC::UnlockEncryptFolderApiJob(_account, folderId, folderToken, _journal, this); + const auto unlockJob = new OCC::UnlockEncryptFolderApiJob(_account, folderId, *folderToken, _journal, this); unlockJob->setShouldRollbackMetadataChanges(true); unlockJob->start(); } diff --git a/src/libsync/syncfileitem.cpp b/src/libsync/syncfileitem.cpp index 6bf10cd7af1b..b653ef2b21b1 100644 --- a/src/libsync/syncfileitem.cpp +++ b/src/libsync/syncfileitem.cpp @@ -118,6 +118,8 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri rec._checksumHeader = _checksumHeader; rec._e2eMangledName = _encryptedFileName.toUtf8(); rec._e2eEncryptionStatus = EncryptionStatusEnums::toDbEncryptionStatus(_e2eEncryptionStatus); + rec._e2eCertificateFingerprint = _e2eCertificateFingerprint; + Q_ASSERT(rec._e2eEncryptionStatus == SyncJournalFileRecord::EncryptionStatus::NotEncrypted || !rec._e2eCertificateFingerprint.isEmpty()); rec._lockstate._locked = _locked == LockStatus::LockedItem; rec._lockstate._lockOwnerDisplayName = _lockOwnerDisplayName; rec._lockstate._lockOwnerId = _lockOwnerId; @@ -156,6 +158,8 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec item->_encryptedFileName = rec.e2eMangledName(); item->_e2eEncryptionStatus = EncryptionStatusEnums::fromDbEncryptionStatus(rec._e2eEncryptionStatus); item->_e2eEncryptionServerCapability = item->_e2eEncryptionStatus; + Q_ASSERT(rec._e2eEncryptionStatus == SyncJournalFileRecord::EncryptionStatus::NotEncrypted || !rec._e2eCertificateFingerprint.isEmpty()); + item->_e2eCertificateFingerprint = rec._e2eCertificateFingerprint; item->_locked = rec._lockstate._locked ? LockStatus::LockedItem : LockStatus::UnlockedItem; item->_lockOwnerDisplayName = rec._lockstate._lockOwnerDisplayName; item->_lockOwnerId = rec._lockstate._lockOwnerId; diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h index 3f6a52898611..4b224dc6b43a 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -286,6 +286,7 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem EncryptionStatus _e2eEncryptionStatus = EncryptionStatus::NotEncrypted; // The file is E2EE or the content of the directory should be E2EE EncryptionStatus _e2eEncryptionServerCapability = EncryptionStatus::NotEncrypted; EncryptionStatus _e2eEncryptionStatusRemote = EncryptionStatus::NotEncrypted; + QByteArray _e2eCertificateFingerprint; quint16 _httpErrorCode = 0; RemotePermissions _remotePerm; QString _errorString; // Contains a string only in case of error diff --git a/src/libsync/updatemigratede2eemetadatajob.cpp b/src/libsync/updatemigratede2eemetadatajob.cpp index 1dbf5bdddf45..fa1865736224 100644 --- a/src/libsync/updatemigratede2eemetadatajob.cpp +++ b/src/libsync/updatemigratede2eemetadatajob.cpp @@ -48,7 +48,7 @@ void UpdateMigratedE2eeMetadataJob::start() UpdateE2eeFolderUsersMetadataJob::Add, _fullRemotePath, propagator()->account()->davUser(), - propagator()->account()->e2e()->_certificate); + propagator()->account()->e2e()->getCertificate()); updateMedatadaAndSubfoldersJob->setParent(this); updateMedatadaAndSubfoldersJob->setSubJobSyncItems(_subJobItems); _subJobItems.clear(); diff --git a/test/testclientsideencryptionv2.cpp b/test/testclientsideencryptionv2.cpp index 3aa3066de02f..ebd59a49e902 100644 --- a/test/testclientsideencryptionv2.cpp +++ b/test/testclientsideencryptionv2.cpp @@ -84,13 +84,13 @@ private slots: QVERIFY(!publicKey.isNull()); QVERIFY(!privateKey.isEmpty()); - _account->e2e()->_certificate = cert; - _account->e2e()->_publicKey = publicKey; - _account->e2e()->_privateKey = privateKey; + _account->e2e()->setCertificate(cert); + _account->e2e()->setPublicKey(publicKey); + _account->e2e()->setPrivateKey(privateKey); - _secondAccount->e2e()->_certificate = cert; - _secondAccount->e2e()->_publicKey = publicKey; - _secondAccount->e2e()->_privateKey = privateKey; + _secondAccount->e2e()->setCertificate(cert); + _secondAccount->e2e()->setPublicKey(publicKey); + _secondAccount->e2e()->setPrivateKey(privateKey); } @@ -135,7 +135,7 @@ private slots: const auto encryptedMetadataKey = QByteArray::fromBase64(folderUserObject.value("encryptedMetadataKey").toString().toUtf8()); if (!encryptedMetadataKey.isEmpty()) { - const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey); + const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(_account->e2e()->sslEngine(), encryptedMetadataKey); if (decryptedMetadataKey.isEmpty()) { break; } @@ -206,11 +206,11 @@ private slots: encryptedFile.initializationVector = EncryptionHelper::generateRandom(16); metadata->addEncryptedFile(encryptedFile); - QVERIFY(metadata->addUser(_secondAccount->davUser(), _secondAccount->e2e()->_certificate)); + QVERIFY(metadata->addUser(_secondAccount->davUser(), _secondAccount->e2e()->getCertificate())); QVERIFY(metadata->removeUser(_secondAccount->davUser())); - QVERIFY(metadata->addUser(_secondAccount->davUser(), _secondAccount->e2e()->_certificate)); + QVERIFY(metadata->addUser(_secondAccount->davUser(), _secondAccount->e2e()->getCertificate())); const auto encryptedMetadata = metadata->encryptedMetadata(); QVERIFY(!encryptedMetadata.isEmpty()); @@ -236,7 +236,7 @@ private slots: const auto encryptedMetadataKey = QByteArray::fromBase64(folderUserObject.value("encryptedMetadataKey").toString().toUtf8()); if (!encryptedMetadataKey.isEmpty()) { - const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey); + const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(_account->e2e()->sslEngine(), encryptedMetadataKey); if (decryptedMetadataKey.isEmpty()) { break; } @@ -334,7 +334,7 @@ private slots: const auto encryptedMetadataKey = QByteArray::fromBase64(folderUserObject.value("encryptedMetadataKey").toString().toUtf8()); if (!encryptedMetadataKey.isEmpty()) { - const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey); + const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(_account->e2e()->sslEngine(), encryptedMetadataKey); if (decryptedMetadataKey.isEmpty()) { break; } diff --git a/test/testsecurefiledrop.cpp b/test/testsecurefiledrop.cpp index 0eccc4d4805e..ca552cb95c20 100644 --- a/test/testsecurefiledrop.cpp +++ b/test/testsecurefiledrop.cpp @@ -75,9 +75,9 @@ private slots: QVERIFY(!publicKey.isNull()); QVERIFY(!privateKey.isEmpty()); - _account->e2e()->_certificate = cert; - _account->e2e()->_publicKey = publicKey; - _account->e2e()->_privateKey = privateKey; + _account->e2e()->setCertificate(cert); + _account->e2e()->setPublicKey(publicKey); + _account->e2e()->setPrivateKey(privateKey); QScopedPointer metadata(new FolderMetadata(_account, "/", FolderMetadata::FolderType::Root)); QSignalSpy metadataSetupCompleteSpy(metadata.data(), &FolderMetadata::setupComplete);