From b0e78d76acf3deff037382f5a4b65b56b38380ed Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Fri, 6 Oct 2023 18:04:48 +0200 Subject: [PATCH] allow user to select and confirm the certificate to use for e2e Signed-off-by: Matthieu Gallien --- src/gui/EncryptionTokenSelectionWindow.qml | 12 +- src/gui/accountsettings.cpp | 3 +- src/gui/owncloudgui.cpp | 1 + src/gui/systray.cpp | 20 +- src/gui/systray.h | 6 +- src/libsync/clientsideencryption.cpp | 18 +- src/libsync/clientsideencryption.h | 10 +- src/libsync/clientsidetokenselector.cpp | 213 +++++++++++---------- src/libsync/clientsidetokenselector.h | 68 ++----- 9 files changed, 164 insertions(+), 187 deletions(-) diff --git a/src/gui/EncryptionTokenSelectionWindow.qml b/src/gui/EncryptionTokenSelectionWindow.qml index 95256e7d3d37d..6e3379badbe23 100644 --- a/src/gui/EncryptionTokenSelectionWindow.qml +++ b/src/gui/EncryptionTokenSelectionWindow.qml @@ -25,8 +25,9 @@ import "./tray" ApplicationWindow { id: encryptionKeyChooserDialog - required property var tokensInfo - required property var keysInfo + required property var certificatesInfo + required property ClientSideTokenSelector certificateSelector + property string selectedSerialNumber: '' flags: Qt.Window | Qt.Dialog visible: true @@ -94,18 +95,19 @@ ApplicationWindow { currentIndex: -1 model: DelegateModel { - model: keysInfo + model: certificatesInfo delegate: ItemDelegate { width: tokensListView.contentItem.width - text: modelData.label + text: modelData.subject highlighted: tokensListView.currentIndex === index onClicked: function() { tokensListView.currentIndex = index + selectedSerialNumber = modelData.serialNumber } } } @@ -126,10 +128,12 @@ ApplicationWindow { onAccepted: function() { Systray.destroyDialog(encryptionKeyChooserDialog) + certificateSelector.serialNumber = selectedSerialNumber } onRejected: function() { Systray.destroyDialog(encryptionKeyChooserDialog) + certificateSelector.serialNumber = '' } } } diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 7e11fecb35be0..b6f01e68ac197 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -307,8 +307,7 @@ 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()); + Systray::instance()->createTokenInitDialog(_accountState->account()->e2e()->discoveredCertificates(), _accountState->account()->e2e()->usbTokenInformation()); } void AccountSettings::slotEncryptFolderFinished(int status) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 1871b9c7d4ebf..e6cdbfa845692 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -132,6 +132,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, "ClientSideTokenSelector", "Access to the certificate selector"); qRegisterMetaTypeStreamOperators(); diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 4a32eaefd8b03..bc7abeca7bb7d 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 "clientsidetokenselector.h" #include #include @@ -35,6 +36,7 @@ #include #include #include +#include #ifdef USE_FDO_NOTIFICATIONS #include @@ -413,24 +415,32 @@ void Systray::createFileActivityDialog(const QString &localPath) Q_EMIT showFileDetailsPage(localPath, FileDetailsPage::Activity); } -void Systray::createTokenInitDialog(const QVariantList &tokensInfo, - const QVariantList &keysInfo) +void Systray::createTokenInitDialog(const QVariantList &certificatesInfo, + ClientSideTokenSelector *certificateSelector) { if(_tokenInitDialog) { destroyDialog(_tokenInitDialog); _tokenInitDialog = nullptr; } - qCDebug(lcSystray) << "Opening new token init dialog with " << tokensInfo.size() << "possible tokens"; + qCDebug(lcSystray) << "Opening new token init dialog with " << certificatesInfo.size() << "possible certificates"; if (!_trayEngine) { qCWarning(lcSystray) << "Could not open token init dialog as no tray engine was available"; return; } + if (certificatesInfo.isEmpty() || certificatesInfo.isEmpty()) { + QMessageBox errorDialog; + + errorDialog.show(); + + return; + } + const QVariantMap initialProperties{ - {"tokensInfo", tokensInfo}, - {"keysInfo", keysInfo} + {"certificatesInfo", certificatesInfo}, + {"certificateSelector", QVariant::fromValue(certificateSelector)}, }; QQmlComponent encryptionTokenDialog(_trayEngine, QStringLiteral("qrc:/qml/src/gui/EncryptionTokenSelectionWindow.qml")); diff --git a/src/gui/systray.h b/src/gui/systray.h index d0789bc47be3d..1a13b53809068 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -31,6 +31,8 @@ class QGuiApplication; namespace OCC { +class ClientSideTokenSelector; + class AccessManagerFactory : public QQmlNetworkAccessManagerFactory { public: @@ -147,8 +149,8 @@ public slots: void createShareDialog(const QString &localPath); void createFileActivityDialog(const QString &localPath); - void createTokenInitDialog(const QVariantList &tokensInfo, - const QVariantList &keysInfo); + void createTokenInitDialog(const QVariantList &certificatesInfo, + ClientSideTokenSelector *certificateSelector); void presentShareViewInTray(const QString &localPath); diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp index 28caaa88791f7..2c375c85c0f66 100644 --- a/src/libsync/clientsideencryption.cpp +++ b/src/libsync/clientsideencryption.cpp @@ -1014,7 +1014,7 @@ std::optional decryptStringAsymmetricWithToken(ENGINE *sslEngine, ClientSideEncryption::ClientSideEncryption() { - connect(&_usbTokenInformation, &ClientSideTokenSelector::discoveredTokensChanged, + connect(&_usbTokenInformation, &ClientSideTokenSelector::discoveredCertificatesChanged, this, &ClientSideEncryption::displayTokenInitDialog); } @@ -1023,14 +1023,9 @@ bool ClientSideEncryption::isInitialized() const return !getMnemonic().isEmpty(); } -QVariantList ClientSideEncryption::discoveredTokens() const +QVariantList ClientSideEncryption::discoveredCertificates() const { - return _usbTokenInformation.discoveredTokens(); -} - -QVariantList ClientSideEncryption::discoveredKeys() const -{ - return _usbTokenInformation.discoveredKeys(); + return _usbTokenInformation.discoveredCertificates(); } const QSslKey &ClientSideEncryption::getPublicKey() const @@ -1083,6 +1078,11 @@ ENGINE* ClientSideEncryption::sslEngine() const return ENGINE_get_default_RSA(); } +ClientSideTokenSelector *ClientSideEncryption::usbTokenInformation() +{ + return &_usbTokenInformation; +} + void ClientSideEncryption::initialize(const AccountPtr &account) { Q_ASSERT(account); @@ -1098,7 +1098,7 @@ void ClientSideEncryption::initialize(const AccountPtr &account) if (_usbTokenInformation.isSetup()) { initializeHardwareTokenEncryption(account); } else if (account->e2eEncryptionKeysGenerationAllowed() && account->askUserForMnemonic()) { - _usbTokenInformation.searchForToken(account); + _usbTokenInformation.searchForCertificates(account); if (_usbTokenInformation.isSetup()) { initializeHardwareTokenEncryption(account); } else { diff --git a/src/libsync/clientsideencryption.h b/src/libsync/clientsideencryption.h index 8078e3171c25c..969ebfb8fee58 100644 --- a/src/libsync/clientsideencryption.h +++ b/src/libsync/clientsideencryption.h @@ -143,9 +143,7 @@ class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject { [[nodiscard]] bool tokenIsSetup() const; - [[nodiscard]] QVariantList discoveredTokens() const; - - [[nodiscard]] QVariantList discoveredKeys() const; + [[nodiscard]] QVariantList discoveredCertificates() const; [[nodiscard]] const QSslKey& getPublicKey() const; @@ -165,7 +163,9 @@ class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject { void setCertificate(const QSslCertificate &certificate); - ENGINE* sslEngine() const; + [[nodiscard]] ENGINE* sslEngine() const; + + [[nodiscard]] ClientSideTokenSelector* usbTokenInformation(); signals: void initializationFinished(bool isNewMnemonicGenerated = false); @@ -178,7 +178,7 @@ class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject { public slots: void initialize(const OCC::AccountPtr &account); - void initializeHardwareTokenEncryption(const AccountPtr &account); + void initializeHardwareTokenEncryption(const OCC::AccountPtr &account); void forgetSensitiveData(const OCC::AccountPtr &account); private slots: diff --git a/src/libsync/clientsidetokenselector.cpp b/src/libsync/clientsidetokenselector.cpp index 6bb8d0857f9b3..757895e51510f 100644 --- a/src/libsync/clientsidetokenselector.cpp +++ b/src/libsync/clientsidetokenselector.cpp @@ -12,6 +12,7 @@ * for more details. */ +#include #define OPENSSL_SUPPRESS_DEPRECATED #include "clientsidetokenselector.h" @@ -22,58 +23,77 @@ #include -namespace OCC -{ +namespace { -Q_LOGGING_CATEGORY(lcCseSelector, "nextcloud.sync.clientsideencryption.selector", QtInfoMsg) +class Bio { +public: + Bio() + : _bio(BIO_new(BIO_s_mem())) + { + } -ClientSideTokenSelector::ClientSideTokenSelector(QObject *parent) - : QObject{parent} -{ + ~Bio() + { + BIO_free_all(_bio); + } -} + operator const BIO*() const + { + return _bio; + } -bool ClientSideTokenSelector::isSetup() const + operator BIO*() + { + return _bio; + } + +private: + Q_DISABLE_COPY(Bio) + + BIO* _bio; +}; + +static unsigned char* unsignedData(QByteArray& array) { - return false; + return (unsigned char*)array.data(); } -QVariantList ClientSideTokenSelector::discoveredTokens() const -{ - return _discoveredTokens; +static QByteArray BIO2ByteArray(Bio &b) { + auto pending = static_cast(BIO_ctrl_pending(b)); + QByteArray res(pending, '\0'); + BIO_read(b, unsignedData(res), pending); + return res; } -QVariantList ClientSideTokenSelector::discoveredKeys() const -{ - return _discoveredPrivateKeys; } -QString ClientSideTokenSelector::slotManufacturer() const +namespace OCC { - return _slotManufacturer; -} -QString ClientSideTokenSelector::tokenManufacturer() const +Q_LOGGING_CATEGORY(lcCseSelector, "nextcloud.sync.clientsideencryption.selector", QtInfoMsg) + +ClientSideTokenSelector::ClientSideTokenSelector(QObject *parent) + : QObject{parent} { - return _tokenManufacturer; + } -QString ClientSideTokenSelector::tokenModel() const +bool ClientSideTokenSelector::isSetup() const { - return _tokenModel; + return false; } -QString ClientSideTokenSelector::tokenSerialNumber() const +QVariantList ClientSideTokenSelector::discoveredCertificates() const { - return _tokenSerialNumber; + return _discoveredCertificates; } -int ClientSideTokenSelector::keyIndex() const +QString ClientSideTokenSelector::serialNumber() const { - return _keyIndex; + return _serialNumber; } -void ClientSideTokenSelector::searchForToken(const AccountPtr &account) +void ClientSideTokenSelector::searchForCertificates(const AccountPtr &account) { auto ctx = PKCS11_CTX_new(); @@ -95,8 +115,7 @@ void ClientSideTokenSelector::searchForToken(const AccountPtr &account) return; } - _discoveredTokens.clear(); - _discoveredPrivateKeys.clear(); + _discoveredCertificates.clear(); auto currentSlot = static_cast(nullptr); for(auto i = 0u; i < tokensCount; ++i) { currentSlot = PKCS11_find_next_token(ctx, tokenSlots, tokensCount, currentSlot); @@ -104,96 +123,84 @@ void ClientSideTokenSelector::searchForToken(const AccountPtr &account) 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)}, - }); + 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 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()); + 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 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)}, - }); + 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}; + + qCInfo(lcCseSelector()) << "newly found certificate" + << "subject:" << sslCertificate.subjectDisplayName() + << "issuer:" << sslCertificate.issuerDisplayName() + << "valid since:" << sslCertificate.effectiveDate() + << "valid until:" << sslCertificate.expiryDate() + << "serial number:" << sslCertificate.serialNumber(); + + if (sslCertificate.isSelfSigned()) { + qCInfo(lcCseSelector()) << "newly found certificate is self signed: goint to ignore it"; + 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()}, + }); } } - 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(); + Q_EMIT discoveredCertificatesChanged(); } -void ClientSideTokenSelector::setKeyIndex(int keyIndex) +void ClientSideTokenSelector::setSerialNumber(const QString &serialNumber) { - if (_keyIndex == keyIndex) { + if (_serialNumber == serialNumber) { return; } - _keyIndex = keyIndex; - Q_EMIT keyIndexChanged(); + _serialNumber = serialNumber; + Q_EMIT serialNumberChanged(); } } diff --git a/src/libsync/clientsidetokenselector.h b/src/libsync/clientsidetokenselector.h index a5f76db4b81fd..c66588624339a 100644 --- a/src/libsync/clientsidetokenselector.h +++ b/src/libsync/clientsidetokenselector.h @@ -28,88 +28,42 @@ class ClientSideTokenSelector : public QObject Q_PROPERTY(bool isSetup READ isSetup NOTIFY isSetupChanged) - Q_PROPERTY(QVariantList discoveredTokens READ discoveredTokens NOTIFY discoveredTokensChanged) + Q_PROPERTY(QVariantList discoveredCertificates READ discoveredCertificates NOTIFY discoveredCertificatesChanged) - 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) + Q_PROPERTY(QString serialNumber READ serialNumber WRITE setSerialNumber NOTIFY serialNumberChanged) public: explicit ClientSideTokenSelector(QObject *parent = nullptr); [[nodiscard]] bool isSetup() const; - [[nodiscard]] QVariantList discoveredTokens() const; - - [[nodiscard]] QVariantList discoveredKeys() const; + [[nodiscard]] QVariantList discoveredCertificates() const; - [[nodiscard]] QString slotManufacturer() const; - - [[nodiscard]] QString tokenManufacturer() const; - - [[nodiscard]] QString tokenModel() const; - - [[nodiscard]] QString tokenSerialNumber() const; - - [[nodiscard]] int keyIndex() const; + [[nodiscard]] QString serialNumber() const; public slots: - void searchForToken(const OCC::AccountPtr &account); - - void setSlotManufacturer(const QString &slotManufacturer); - - void setTokenManufacturer(const QString &tokenManufacturer); + void searchForCertificates(const OCC::AccountPtr &account); - void setTokenModel(const QString &tokenModel); - - void setTokenSerialNumber(const QString &tokenSerialNumber); - - void setKeyIndex(int keyIndex); + void setSerialNumber(const QString &serialNumber); signals: void isSetupChanged(); - void discoveredTokensChanged(); - - void discoveredKeysChanged(); - - void slotManufacturerChanged(); - - void tokenManufacturerChanged(); - - void tokenModelChanged(); + void discoveredCertificatesChanged(); - void tokenSerialNumberChanged(); + void certificateIndexChanged(); - void keyIndexChanged(); + void serialNumberChanged(); void failedToInitialize(const OCC::AccountPtr &account); private: - QVariantList _discoveredTokens; - - QVariantList _discoveredPrivateKeys; - - QString _slotManufacturer; - - QString _tokenManufacturer; - - QString _tokenModel; - - QString _tokenSerialNumber; + QVariantList _discoveredCertificates; - int _keyIndex = -1; + QString _serialNumber; }; }