diff --git a/NEXTCLOUD.cmake b/NEXTCLOUD.cmake
index 40d0f2ff403ca..42a6747bac1f2 100644
--- a/NEXTCLOUD.cmake
+++ b/NEXTCLOUD.cmake
@@ -80,3 +80,5 @@ endif()
if (APPLE)
option( BUILD_FILE_PROVIDER_MODULE "Build the macOS virtual files File Provider module" OFF )
endif()
+
+set (CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN true)
diff --git a/config.h.in b/config.h.in
index ca9907c52f294..9d0a9aef590dc 100644
--- a/config.h.in
+++ b/config.h.in
@@ -61,4 +61,6 @@
#cmakedefine CFAPI_SHELL_EXTENSIONS_LIB_NAME "@CFAPI_SHELL_EXTENSIONS_LIB_NAME@"
+#cmakedefine CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN @CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN@
+
#endif
diff --git a/resources.qrc b/resources.qrc
index fc65956940bf4..10d0d0ccc3523 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
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 66e140c222fca..922e3a561dc03 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -154,6 +154,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 0000000000000..95256e7d3d37d
--- /dev/null
+++ b/src/gui/EncryptionTokenSelectionWindow.qml
@@ -0,0 +1,142 @@
+/*
+ * 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 tokensInfo
+ required property var keysInfo
+
+ 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: keysInfo
+
+ delegate: ItemDelegate {
+ width: tokensListView.contentItem.width
+
+ text: modelData.label
+
+ highlighted: tokensListView.currentIndex === index
+
+ onClicked: function()
+ {
+ tokensListView.currentIndex = index
+ }
+ }
+ }
+ }
+ }
+
+ 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)
+ }
+
+ onRejected: function() {
+ Systray.destroyDialog(encryptionKeyChooserDialog)
+ }
+ }
+ }
+
+ Rectangle {
+ color: Style.backgroundColor
+ anchors.fill: parent
+ z: 1
+ }
+}
diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp
index 05e332f268650..7e11fecb35be0 100644
--- a/src/gui/accountsettings.cpp
+++ b/src/gui/accountsettings.cpp
@@ -283,6 +283,7 @@ void AccountSettings::slotE2eEncryptionMnemonicReady()
void AccountSettings::slotE2eEncryptionGenerateKeys()
{
connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotE2eEncryptionInitializationFinished);
+ connect(_accountState->account()->e2e(), &ClientSideEncryption::displayTokenInitDialog, this, &AccountSettings::slotDisplayTokenInitDialog);
_accountState->account()->setE2eEncryptionKeysGenerationAllowed(true);
_accountState->account()->setAskUserForMnemonic(true);
_accountState->account()->e2e()->initialize(_accountState->account());
@@ -291,6 +292,7 @@ void AccountSettings::slotE2eEncryptionGenerateKeys()
void AccountSettings::slotE2eEncryptionInitializationFinished(bool isNewMnemonicGenerated)
{
disconnect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotE2eEncryptionInitializationFinished);
+ disconnect(_accountState->account()->e2e(), &ClientSideEncryption::displayTokenInitDialog, this, &AccountSettings::slotDisplayTokenInitDialog);
if (_accountState->account()->e2e()->isInitialized()) {
removeActionFromEncryptionMessage(e2EeUiActionEnableEncryptionId);
slotE2eEncryptionMnemonicReady();
@@ -301,6 +303,14 @@ void AccountSettings::slotE2eEncryptionInitializationFinished(bool isNewMnemonic
_accountState->account()->setAskUserForMnemonic(false);
}
+void AccountSettings::slotDisplayTokenInitDialog()
+{
+ disconnect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotE2eEncryptionInitializationFinished);
+ disconnect(_accountState->account()->e2e(), &ClientSideEncryption::displayTokenInitDialog, this, &AccountSettings::slotDisplayTokenInitDialog);
+ Systray::instance()->createTokenInitDialog(_accountState->account()->e2e()->discoveredTokens(),
+ _accountState->account()->e2e()->discoveredKeys());
+}
+
void AccountSettings::slotEncryptFolderFinished(int status)
{
qCInfo(lcAccountSettings) << "Current folder encryption status code:" << status;
diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h
index 1ddbc097d413e..b24b48e6236b3 100644
--- a/src/gui/accountsettings.h
+++ b/src/gui/accountsettings.h
@@ -58,6 +58,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();
@@ -72,7 +73,6 @@ public slots:
void slotUpdateQuota(qint64 total, qint64 used);
void slotAccountStateChanged();
void slotStyleChanged();
- OCC::AccountState *accountsState() { return _accountState; }
void slotHideSelectiveSyncWidget();
protected slots:
@@ -106,6 +106,7 @@ protected slots:
void slotE2eEncryptionMnemonicReady();
void slotE2eEncryptionGenerateKeys();
void slotE2eEncryptionInitializationFinished(bool isNewMnemonicGenerated);
+ void slotDisplayTokenInitDialog();
void slotEncryptFolderFinished(int status);
void slotSelectiveSyncChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp
index 6749df8a6e91b..1871b9c7d4ebf 100644
--- a/src/gui/owncloudgui.cpp
+++ b/src/gui/owncloudgui.cpp
@@ -571,6 +571,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 130e5deac1c72..09bb5aad198f4 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/systray.cpp b/src/gui/systray.cpp
index 936b999557c64..4a32eaefd8b03 100644
--- a/src/gui/systray.cpp
+++ b/src/gui/systray.cpp
@@ -34,6 +34,7 @@
#include
#include
#include
+#include
#ifdef USE_FDO_NOTIFICATIONS
#include
@@ -412,6 +413,50 @@ void Systray::createFileActivityDialog(const QString &localPath)
Q_EMIT showFileDetailsPage(localPath, FileDetailsPage::Activity);
}
+void Systray::createTokenInitDialog(const QVariantList &tokensInfo,
+ const QVariantList &keysInfo)
+{
+ if(_tokenInitDialog) {
+ destroyDialog(_tokenInitDialog);
+ _tokenInitDialog = nullptr;
+ }
+
+ qCDebug(lcSystray) << "Opening new token init dialog with " << tokensInfo.size() << "possible tokens";
+
+ if (!_trayEngine) {
+ qCWarning(lcSystray) << "Could not open token init dialog as no tray engine was available";
+ return;
+ }
+
+ const QVariantMap initialProperties{
+ {"tokensInfo", tokensInfo},
+ {"keysInfo", keysInfo}
+ };
+
+ QQmlComponent encryptionTokenDialog(_trayEngine, QStringLiteral("qrc:/qml/src/gui/EncryptionTokenSelectionWindow.qml"));
+
+ if (!encryptionTokenDialog.isError()) {
+ const auto createdDialog = encryptionTokenDialog.createWithInitialProperties(initialProperties);
+ const auto dialog = qobject_cast(createdDialog);
+
+ if(!dialog) {
+ qCWarning(lcSystray) << "File details dialog window resulted in creation of object that was not a window!";
+ return;
+ }
+
+ _tokenInitDialog = dialog;
+
+ Q_EMIT hideSettingsDialog();
+
+ dialog->show();
+ dialog->raise();
+ dialog->requestActivate();
+
+ } else {
+ qCWarning(lcSystray) << encryptionTokenDialog.errorString();
+ }
+}
+
void Systray::presentShareViewInTray(const QString &localPath)
{
const auto folder = FolderMan::instance()->folderForPath(localPath);
diff --git a/src/gui/systray.h b/src/gui/systray.h
index 370bebc447923..d0789bc47be3d 100644
--- a/src/gui/systray.h
+++ b/src/gui/systray.h
@@ -15,11 +15,11 @@
#ifndef SYSTRAY_H
#define SYSTRAY_H
-#include
-
#include "accountmanager.h"
#include "tray/usermodel.h"
+#include
+#include
#include
class QScreen;
@@ -112,6 +112,8 @@ class Systray
void syncIsPausedChanged();
void isOpenChanged();
+ void hideSettingsDialog();
+
public slots:
void setTrayEngine(QQmlApplicationEngine *trayEngine);
void create();
@@ -145,6 +147,8 @@ public slots:
void createShareDialog(const QString &localPath);
void createFileActivityDialog(const QString &localPath);
+ void createTokenInitDialog(const QVariantList &tokensInfo,
+ const QVariantList &keysInfo);
void presentShareViewInTray(const QString &localPath);
@@ -185,6 +189,7 @@ private slots:
QSet _callsAlreadyNotified;
QPointer _editFileLocallyLoadingDialog;
QVector _fileDetailDialogs;
+ QQuickWindow* _tokenInitDialog = nullptr;
};
} // namespace OCC
diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt
index b74916c96338d..04e92e7117ea2 100644
--- a/src/libsync/CMakeLists.txt
+++ b/src/libsync/CMakeLists.txt
@@ -105,6 +105,8 @@ set(libsync_SRCS
clientsideencryption.cpp
clientsideencryptionjobs.h
clientsideencryptionjobs.cpp
+ clientsidetokenselector.h
+ clientsidetokenselector.cpp
datetimeprovider.h
datetimeprovider.cpp
ocsuserstatusconnector.h
diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp
index 62dab56beeccb..798dc68123994 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
@@ -1023,9 +1025,9 @@ bool Account::askUserForMnemonic() const
return _e2eAskUserForMnemonic;
}
-bool Account::useHardwareTokenEncryption() const
+bool Account::enforceUseHardwareTokenEncryption() const
{
- return !encryptionHardwareTokenDriverPath().isEmpty();
+ return CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN;
}
QString Account::encryptionHardwareTokenDriverPath() const
diff --git a/src/libsync/account.h b/src/libsync/account.h
index 47c1c0135adf9..6f67715b92e44 100644
--- a/src/libsync/account.h
+++ b/src/libsync/account.h
@@ -88,7 +88,7 @@ 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 useHardwareTokenEncryption READ useHardwareTokenEncryption NOTIFY useHardwareTokenEncryptionChanged)
+ Q_PROPERTY(bool enforceUseHardwareTokenEncryption READ enforceUseHardwareTokenEncryption NOTIFY enforceUseHardwareTokenEncryptionChanged)
Q_PROPERTY(QString encryptionHardwareTokenDriverPath READ encryptionHardwareTokenDriverPath NOTIFY encryptionHardwareTokenDriverPathChanged)
public:
@@ -328,7 +328,7 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject
[[nodiscard]] bool askUserForMnemonic() const;
- [[nodiscard]] bool useHardwareTokenEncryption() const;
+ [[nodiscard]] bool enforceUseHardwareTokenEncryption() const;
[[nodiscard]] QString encryptionHardwareTokenDriverPath() const;
@@ -360,7 +360,7 @@ public slots:
void accountChangedDisplayName();
void prettyNameChanged();
void askUserForMnemonicChanged();
- void useHardwareTokenEncryptionChanged();
+ void enforceUseHardwareTokenEncryptionChanged();
void encryptionHardwareTokenDriverPathChanged();
/// Used in RemoteWipe
diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp
index 254268c3afeb0..28caaa88791f7 100644
--- a/src/libsync/clientsideencryption.cpp
+++ b/src/libsync/clientsideencryption.cpp
@@ -1012,13 +1012,27 @@ std::optional decryptStringAsymmetricWithToken(ENGINE *sslEngine,
}
-ClientSideEncryption::ClientSideEncryption() = default;
+ClientSideEncryption::ClientSideEncryption()
+{
+ connect(&_usbTokenInformation, &ClientSideTokenSelector::discoveredTokensChanged,
+ this, &ClientSideEncryption::displayTokenInitDialog);
+}
bool ClientSideEncryption::isInitialized() const
{
return !getMnemonic().isEmpty();
}
+QVariantList ClientSideEncryption::discoveredTokens() const
+{
+ return _usbTokenInformation.discoveredTokens();
+}
+
+QVariantList ClientSideEncryption::discoveredKeys() const
+{
+ return _usbTokenInformation.discoveredKeys();
+}
+
const QSslKey &ClientSideEncryption::getPublicKey() const
{
return _publicKey;
@@ -1080,8 +1094,19 @@ void ClientSideEncryption::initialize(const AccountPtr &account)
return;
}
- if (account->useHardwareTokenEncryption()) {
- initializeHardwareTokenEncryption(account);
+ if (account->enforceUseHardwareTokenEncryption()) {
+ if (_usbTokenInformation.isSetup()) {
+ initializeHardwareTokenEncryption(account);
+ } else if (account->e2eEncryptionKeysGenerationAllowed() && account->askUserForMnemonic()) {
+ _usbTokenInformation.searchForToken(account);
+ if (_usbTokenInformation.isSetup()) {
+ initializeHardwareTokenEncryption(account);
+ } else {
+ emit initializationFinished();
+ }
+ } else {
+ emit initializationFinished();
+ }
} else {
fetchCertificateFromKeyChain(account);
}
diff --git a/src/libsync/clientsideencryption.h b/src/libsync/clientsideencryption.h
index 7dbc2b0636657..8078e3171c25c 100644
--- a/src/libsync/clientsideencryption.h
+++ b/src/libsync/clientsideencryption.h
@@ -16,7 +16,9 @@
#define CLIENTSIDEENCRYPTION_H
#include "accountfwd.h"
+
#include "networkjobs.h"
+#include "clientsidetokenselector.h"
#include
#include
@@ -139,6 +141,12 @@ class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject {
[[nodiscard]] bool isInitialized() const;
+ [[nodiscard]] bool tokenIsSetup() const;
+
+ [[nodiscard]] QVariantList discoveredTokens() const;
+
+ [[nodiscard]] QVariantList discoveredKeys() const;
+
[[nodiscard]] const QSslKey& getPublicKey() const;
void setPublicKey(const QSslKey &publicKey);
@@ -166,6 +174,7 @@ class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject {
void certificateDeleted();
void mnemonicDeleted();
void publicKeyDeleted();
+ void displayTokenInitDialog();
public slots:
void initialize(const OCC::AccountPtr &account);
@@ -248,6 +257,8 @@ private slots:
QString _mnemonic;
bool _newMnemonicGenerated = false;
+ ClientSideTokenSelector _usbTokenInformation;
+
PKCS11_KEY* _tokenPublicKey = nullptr;
PKCS11_KEY* _tokenPrivateKey = nullptr;
};
diff --git a/src/libsync/clientsidetokenselector.cpp b/src/libsync/clientsidetokenselector.cpp
new file mode 100644
index 0000000000000..6bb8d0857f9b3
--- /dev/null
+++ b/src/libsync/clientsidetokenselector.cpp
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+#define OPENSSL_SUPPRESS_DEPRECATED
+
+#include "clientsidetokenselector.h"
+
+#include "account.h"
+
+#include
+
+#include
+
+namespace OCC
+{
+
+Q_LOGGING_CATEGORY(lcCseSelector, "nextcloud.sync.clientsideencryption.selector", QtInfoMsg)
+
+ClientSideTokenSelector::ClientSideTokenSelector(QObject *parent)
+ : QObject{parent}
+{
+
+}
+
+bool ClientSideTokenSelector::isSetup() const
+{
+ return false;
+}
+
+QVariantList ClientSideTokenSelector::discoveredTokens() const
+{
+ return _discoveredTokens;
+}
+
+QVariantList ClientSideTokenSelector::discoveredKeys() const
+{
+ return _discoveredPrivateKeys;
+}
+
+QString ClientSideTokenSelector::slotManufacturer() const
+{
+ return _slotManufacturer;
+}
+
+QString ClientSideTokenSelector::tokenManufacturer() const
+{
+ return _tokenManufacturer;
+}
+
+QString ClientSideTokenSelector::tokenModel() const
+{
+ return _tokenModel;
+}
+
+QString ClientSideTokenSelector::tokenSerialNumber() const
+{
+ return _tokenSerialNumber;
+}
+
+int ClientSideTokenSelector::keyIndex() const
+{
+ return _keyIndex;
+}
+
+void ClientSideTokenSelector::searchForToken(const AccountPtr &account)
+{
+ auto ctx = PKCS11_CTX_new();
+
+ 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());
+
+ Q_EMIT failedToInitialize(account);
+ return;
+ }
+
+ auto tokensCount = 0u;
+ PKCS11_SLOT *tokenSlots = nullptr;
+ /* get information on all slots */
+ if (PKCS11_enumerate_slots(ctx, &tokenSlots, &tokensCount) < 0) {
+ qCWarning(lcCseSelector()) << "no slots available" << ERR_reason_error_string(ERR_get_error());
+
+ Q_EMIT failedToInitialize(account);
+ return;
+ }
+
+ _discoveredTokens.clear();
+ _discoveredPrivateKeys.clear();
+ auto currentSlot = static_cast(nullptr);
+ for(auto i = 0u; i < tokensCount; ++i) {
+ currentSlot = PKCS11_find_next_token(ctx, tokenSlots, tokensCount, currentSlot);
+ if (currentSlot == nullptr || currentSlot->token == nullptr) {
+ break;
+ }
+
+ qCInfo(lcCseSelector()) << "Slot manufacturer......:" << currentSlot->manufacturer;
+ qCInfo(lcCseSelector()) << "Slot description.......:" << currentSlot->description;
+ qCInfo(lcCseSelector()) << "Slot token label.......:" << currentSlot->token->label;
+ qCInfo(lcCseSelector()) << "Slot token manufacturer:" << currentSlot->token->manufacturer;
+ qCInfo(lcCseSelector()) << "Slot token model.......:" << currentSlot->token->model;
+ qCInfo(lcCseSelector()) << "Slot token serialnr....:" << currentSlot->token->serialnr;
+
+ _discoveredTokens.push_back(QVariantMap{
+ {QStringLiteral("slotManufacturer"), QString::fromLatin1(currentSlot->manufacturer)},
+ {QStringLiteral("slotDescription"), QString::fromLatin1(currentSlot->description)},
+ {QStringLiteral("tokenLabel"), QString::fromLatin1(currentSlot->token->label)},
+ {QStringLiteral("tokenManufacturer"), QString::fromLatin1(currentSlot->token->manufacturer)},
+ {QStringLiteral("tokenModel"), QString::fromLatin1(currentSlot->token->model)},
+ {QStringLiteral("tokenSerialNumber"), QString::fromLatin1(currentSlot->token->serialnr)},
+ });
+
+ auto keysCount = 0u;
+ auto tokenKeys = static_cast(nullptr);
+ if (PKCS11_enumerate_public_keys(currentSlot->token, &tokenKeys, &keysCount)) {
+ qCWarning(lcCseSelector()) << "PKCS11_enumerate_public_keys failed" << ERR_reason_error_string(ERR_get_error());
+
+ Q_EMIT failedToInitialize(account);
+ return;
+ }
+
+ for (auto keyIndex = 0u; keyIndex < keysCount; ++keyIndex) {
+ auto currentPrivateKey = &tokenKeys[0];
+ qCInfo(lcCseSelector()) << "key metadata:"
+ << "type:" << (currentPrivateKey->isPrivate ? "is private" : "is public")
+ << "label:" << currentPrivateKey->label
+ << "need login:" << (currentPrivateKey->needLogin ? "true" : "false");
+
+ _discoveredPrivateKeys.push_back(QVariantMap{
+ {QStringLiteral("label"), QString::fromLatin1(currentPrivateKey->label)},
+ {QStringLiteral("needLogin"), QVariant::fromValue(currentPrivateKey->needLogin)},
+ });
+ }
+ }
+ Q_EMIT discoveredTokensChanged();
+ Q_EMIT discoveredKeysChanged();
+}
+
+void ClientSideTokenSelector::setSlotManufacturer(const QString &slotManufacturer)
+{
+ if (_slotManufacturer == slotManufacturer) {
+ return;
+ }
+
+ _slotManufacturer = slotManufacturer;
+ Q_EMIT slotManufacturerChanged();
+}
+
+void ClientSideTokenSelector::setTokenManufacturer(const QString &tokenManufacturer)
+{
+ if (_tokenManufacturer == tokenManufacturer) {
+ return;
+ }
+
+ _tokenManufacturer = tokenManufacturer;
+ Q_EMIT tokenManufacturerChanged();
+}
+
+void ClientSideTokenSelector::setTokenModel(const QString &tokenModel)
+{
+ if (_tokenModel == tokenModel) {
+ return;
+ }
+
+ _tokenModel = tokenModel;
+ Q_EMIT tokenModelChanged();
+}
+
+void ClientSideTokenSelector::setTokenSerialNumber(const QString &tokenSerialNumber)
+{
+ if (_tokenSerialNumber == tokenSerialNumber) {
+ return;
+ }
+
+ _tokenSerialNumber = tokenSerialNumber;
+ Q_EMIT tokenSerialNumberChanged();
+}
+
+void ClientSideTokenSelector::setKeyIndex(int keyIndex)
+{
+ if (_keyIndex == keyIndex) {
+ return;
+ }
+
+ _keyIndex = keyIndex;
+ Q_EMIT keyIndexChanged();
+}
+
+}
diff --git a/src/libsync/clientsidetokenselector.h b/src/libsync/clientsidetokenselector.h
new file mode 100644
index 0000000000000..a5f76db4b81fd
--- /dev/null
+++ b/src/libsync/clientsidetokenselector.h
@@ -0,0 +1,117 @@
+/*
+ * 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
+
+namespace OCC
+{
+
+class ClientSideTokenSelector : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(bool isSetup READ isSetup NOTIFY isSetupChanged)
+
+ Q_PROPERTY(QVariantList discoveredTokens READ discoveredTokens NOTIFY discoveredTokensChanged)
+
+ Q_PROPERTY(QVariantList discoveredKeys READ discoveredKeys NOTIFY discoveredKeysChanged)
+
+ Q_PROPERTY(QString slotManufacturer READ slotManufacturer WRITE setSlotManufacturer NOTIFY slotManufacturerChanged)
+
+ Q_PROPERTY(QString tokenManufacturer READ tokenManufacturer WRITE setTokenManufacturer NOTIFY tokenManufacturerChanged)
+
+ Q_PROPERTY(QString tokenModel READ tokenModel WRITE setTokenModel NOTIFY tokenModelChanged)
+
+ Q_PROPERTY(QString tokenSerialNumber READ tokenSerialNumber WRITE setTokenSerialNumber NOTIFY tokenSerialNumberChanged)
+
+ Q_PROPERTY(int keyIndex READ keyIndex WRITE setKeyIndex NOTIFY keyIndexChanged)
+
+public:
+ explicit ClientSideTokenSelector(QObject *parent = nullptr);
+
+ [[nodiscard]] bool isSetup() const;
+
+ [[nodiscard]] QVariantList discoveredTokens() const;
+
+ [[nodiscard]] QVariantList discoveredKeys() const;
+
+ [[nodiscard]] QString slotManufacturer() const;
+
+ [[nodiscard]] QString tokenManufacturer() const;
+
+ [[nodiscard]] QString tokenModel() const;
+
+ [[nodiscard]] QString tokenSerialNumber() const;
+
+ [[nodiscard]] int keyIndex() const;
+
+public slots:
+
+ void searchForToken(const OCC::AccountPtr &account);
+
+ void setSlotManufacturer(const QString &slotManufacturer);
+
+ void setTokenManufacturer(const QString &tokenManufacturer);
+
+ void setTokenModel(const QString &tokenModel);
+
+ void setTokenSerialNumber(const QString &tokenSerialNumber);
+
+ void setKeyIndex(int keyIndex);
+
+signals:
+
+ void isSetupChanged();
+
+ void discoveredTokensChanged();
+
+ void discoveredKeysChanged();
+
+ void slotManufacturerChanged();
+
+ void tokenManufacturerChanged();
+
+ void tokenModelChanged();
+
+ void tokenSerialNumberChanged();
+
+ void keyIndexChanged();
+
+ void failedToInitialize(const OCC::AccountPtr &account);
+
+private:
+
+ QVariantList _discoveredTokens;
+
+ QVariantList _discoveredPrivateKeys;
+
+ QString _slotManufacturer;
+
+ QString _tokenManufacturer;
+
+ QString _tokenModel;
+
+ QString _tokenSerialNumber;
+
+ int _keyIndex = -1;
+};
+
+}
+
+#endif // CLIENTSIDETOKENSELECTOR_H