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