From d772338fb419c806266ad478420ee3620a15a94e Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 17 Jul 2026 20:05:31 +0300 Subject: [PATCH] Event based polling IB-8767 Signed-off-by: Raul Metsma --- client/Application.cpp | 10 +- client/Application.h | 4 +- client/CDocSupport.cpp | 15 +- client/CDocSupport.h | 2 +- client/CryptoDoc.cpp | 11 +- client/DigiDoc.cpp | 8 +- client/MainWindow.cpp | 40 +++--- client/QCryptoBackend.cpp | 201 ++++++++++++++++++++++---- client/QCryptoBackend.h | 54 ++++--- client/QSigner.cpp | 227 +++++------------------------- client/QSigner.h | 34 ++--- client/dialogs/AddRecipients.cpp | 8 +- client/dialogs/SettingsDialog.cpp | 4 +- client/translations/en.ts | 12 +- client/translations/et.ts | 12 +- client/widgets/ContainerPage.cpp | 4 +- 16 files changed, 318 insertions(+), 328 deletions(-) diff --git a/client/Application.cpp b/client/Application.cpp index 66b4107d5..75f246650 100644 --- a/client/Application.cpp +++ b/client/Application.cpp @@ -25,7 +25,7 @@ #include "Configuration.h" #include "CDocSupport.h" #include "MainWindow.h" -#include "QSigner.h" +#include "QCryptoBackend.h" #include "QSmartCard.h" #include "DigiDoc.h" #include "Settings.h" @@ -296,7 +296,7 @@ class Application::Private Configuration *conf {}; QAction *closeAction {}, *newClientAction {}, *helpAction {}; std::unique_ptr bar; - QSigner *signer {}; + QCryptoManager *cryptoManager {}; QTranslator appTranslator, qtTranslator; QString lang; @@ -307,7 +307,7 @@ class Application::Private #endif // Q_OS_WIN ~Private() { - delete signer; + delete cryptoManager; } }; @@ -462,7 +462,7 @@ Application::Application( int &argc, char **argv ) try { digidoc::Conf::init( new DigidocConf ); - d->signer = new QSigner(this); + d->cryptoManager = new QCryptoManager(); updateTSLCache(QDateTime::currentDateTimeUtc().addDays(-7)); digidoc::initialize(applicationName().toUtf8().constData(), QStringLiteral("%1/%2 (%3)") @@ -968,7 +968,7 @@ void Application::showWarning(const QString &title, const digidoc::Exception &e) WarningDialog::create()->withTitle(title)->withDetails(causes.join('\n'))->open(); } -QSigner* Application::signer() const { return d->signer; } +QCryptoManager* Application::cryptoManager() const { return d->cryptoManager; } void Application::updateTSLCache(const QDateTime &tslTime) { diff --git a/client/Application.h b/client/Application.h index c458af40a..4118e7352 100644 --- a/client/Application.h +++ b/client/Application.h @@ -37,7 +37,7 @@ using BaseApplication = QtSingleApplication; namespace digidoc { class Exception; } class Configuration; class QAction; -class QSigner; +class QCryptoManager; class Application final: public BaseApplication { Q_OBJECT @@ -61,7 +61,7 @@ class Application final: public BaseApplication Configuration *conf(); void loadTranslation( const QString &lang ); bool notify(QObject *object, QEvent *event ) final; - QSigner* signer() const; + QCryptoManager* cryptoManager() const; int run(); void waitForTSL( const QString &file ); diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 19cf47812..8cd01aede 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -32,7 +32,6 @@ #include "Application.h" #include "CheckConnection.h" #include "QCryptoBackend.h" -#include "QSigner.h" #include "Settings.h" #include "TokenData.h" #include "Utils.h" @@ -124,7 +123,7 @@ libcdoc::result_t DDCryptoBackend::decryptRSA(std::vector& dst, const std::vector &data, bool oaep, unsigned int idx) { if (!backend) { - auto val = QCryptoBackend::getBackend(qApp->signer()->tokenauth()); + auto val = QCryptoBackend::getBackend(qApp->cryptoManager()->tokenauth()); if (!val) return getDecryptStatus(val.error()); backend.reset(val.value()); @@ -144,7 +143,7 @@ DDCryptoBackend::deriveConcatKDF(std::vector& dst, const std::vectorsigner()->tokenauth()); + auto val = QCryptoBackend::getBackend(qApp->cryptoManager()->tokenauth()); if (!val) return getDecryptStatus(val.error()); backend.reset(val.value()); @@ -159,7 +158,7 @@ libcdoc::result_t DDCryptoBackend::deriveHMACExtract(std::vector& dst, const std::vector &key_material, const std::vector &salt, unsigned int idx) { if (!backend) { - auto val = QCryptoBackend::getBackend(qApp->signer()->tokenauth()); + auto val = QCryptoBackend::getBackend(qApp->cryptoManager()->tokenauth()); if (!val) return getDecryptStatus(val.error()); backend.reset(val.value()); @@ -189,7 +188,7 @@ DDCryptoBackend::getLastErrorStr(libcdoc::result_t code) const case IN_PROGRESS: return "Signing/decrypting is already in progress another window."; case BACKEND_ERROR: - return qApp->signer()->getLastErrorStr().toStdString(); + return "Backend error"; } return libcdoc::CryptoBackend::getLastErrorStr(code); } @@ -321,8 +320,8 @@ DDNetworkBackend::fetchKey(std::vector &result, const std::string &url, return BACKEND_ERROR; } - TokenData auth = qApp->signer()->tokenauth(); - auto val = QCryptoBackend::getBackend(qApp->signer()->tokenauth()); + TokenData auth = qApp->cryptoManager()->tokenauth(); + auto val = QCryptoBackend::getBackend(auth); if (!val) return getDecryptStatus(val.error()); std::unique_ptr backend(val.value()); @@ -333,7 +332,7 @@ DDNetworkBackend::fetchKey(std::vector &result, const std::string &url, return BACKEND_ERROR; } QScopedPointer nam( - CheckConnection::setupNAM(req, qApp->signer()->tokenauth().cert(), authKey, Settings::CDOC2_GET_CERT)); + CheckConnection::setupNAM(req, auth.cert(), authKey, Settings::CDOC2_GET_CERT)); QEventLoop e; QNetworkReply *reply = nam->get(req); connect(reply, &QNetworkReply::finished, &e, &QEventLoop::quit); diff --git a/client/CDocSupport.h b/client/CDocSupport.h index 7a9fe5573..d0d0f2b72 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -52,7 +52,7 @@ struct DDConfiguration : public libcdoc::Configuration { // // CryptoBackend // -// Bridges to qApp->signer() +// Bridges to qApp->cryptoManager() // struct DDCryptoBackend final : public libcdoc::CryptoBackend { diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index a83b10d91..99b1a805d 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -23,7 +23,6 @@ #include "CDocSupport.h" #include "TokenData.h" #include "QCryptoBackend.h" -#include "QSigner.h" #include "Settings.h" #include "SslCertificate.h" #include "Utils.h" @@ -283,7 +282,7 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) if(!d->reader) { WarningDialog::create() - ->withTitle(QSigner::tr("Failed to decrypt document")) + ->withTitle(tr("Failed to decrypt document")) ->withText(tr("Container is not open")) ->open(); return false; @@ -292,12 +291,12 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) int lock_idx = -1; const std::vector &locks = d->reader->getLocks(); if (lock == nullptr) { - QByteArray der = qApp->signer()->tokenauth().cert().toDer(); + QByteArray der = qApp->cryptoManager()->tokenauth().cert().toDer(); lock_idx = d->reader->getLockForCert( std::vector(der.cbegin(), der.cend())); if (lock_idx < 0) { WarningDialog::create() - ->withTitle(QSigner::tr("Failed to decrypt document")) + ->withTitle(tr("Failed to decrypt document")) ->withText(tr("You do not have the key to decrypt this document")) ->open(); return false; @@ -315,7 +314,7 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) } if (!lock || (lock->isSymmetric() && secret.isEmpty())) { WarningDialog::create() - ->withTitle(QSigner::tr("Failed to decrypt document")) + ->withTitle(tr("Failed to decrypt document")) ->withText(tr("You do not have the key to decrypt this document")) ->open(); return false; @@ -368,7 +367,7 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) break; } WarningDialog::create() - ->withTitle(QSigner::tr("Failed to decrypt document")) + ->withTitle(tr("Failed to decrypt document")) ->withText(str) ->withDetails(QString::fromStdString(msg)) ->open(); diff --git a/client/DigiDoc.cpp b/client/DigiDoc.cpp index 8409b55b4..7f340b5d2 100644 --- a/client/DigiDoc.cpp +++ b/client/DigiDoc.cpp @@ -424,8 +424,8 @@ bool DigiDoc::extend() { QWidget *parent = parentWidget(); try { - auto *signer = qApp->signer(); - signer->setUserAgent(QStringLiteral("%1/%2 (%3) Devices: %4").arg( + QSigner signer(qApp->cryptoManager()->tokensign()); + signer.setUserAgent(QStringLiteral("%1/%2 (%3) Devices: %4").arg( QCoreApplication::applicationName(), QCoreApplication::applicationVersion(), Common::applicationOs(), @@ -434,12 +434,10 @@ bool DigiDoc::extend() ServiceConfirmation cb(parent); QString current = m_fileName; size_t extendCount = 0; - bool wrapped = false; if(std::unique_ptr extended = waitFor([&] { - return Container::extendContainerValidity(*b, signer, extendCount); + return Container::extendContainerValidity(*b, &signer, extendCount); })) { - wrapped = true; const QString asics = QCoreApplication::translate("MainWindow", "Documents (%1)").arg(QLatin1String("*.asics *.scs")); QFileInfo f(current); QString name = f.absolutePath() + '/' + f.completeBaseName() + QStringLiteral(".asics"); diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index bd2d7f318..5dfd519ef 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -24,6 +24,7 @@ #include "CheckConnection.h" #include "CryptoDoc.h" #include "DigiDoc.h" +#include "QCryptoBackend.h" #include "QPCSC.h" #include "QSigner.h" #include "Settings.h" @@ -92,13 +93,13 @@ MainWindow::MainWindow( QWidget *parent ) #endif // Refresh ID card info in card widget - connect(qApp->signer(), &QSigner::cacheChanged, this, &MainWindow::updateSelector); - connect(qApp->signer(), &QSigner::signDataChanged, ui->signContainerPage, &ContainerPage::tokenChanged); - connect(qApp->signer(), &QSigner::authDataChanged, ui->cryptoContainerPage, &ContainerPage::tokenChanged); + connect(qApp->cryptoManager(), &QCryptoManager::cacheChanged, this, &MainWindow::updateSelector); + connect(qApp->cryptoManager(), &QCryptoManager::signDataChanged, ui->signContainerPage, &ContainerPage::tokenChanged); + connect(qApp->cryptoManager(), &QCryptoManager::authDataChanged, ui->cryptoContainerPage, &ContainerPage::tokenChanged); // Refresh card info on "My EID" page - connect(qApp->signer()->smartcard(), &QSmartCard::tokenChanged, this, &MainWindow::updateMyEID); - connect(qApp->signer()->smartcard(), &QSmartCard::dataChanged, this, &MainWindow::updateMyEid); + connect(qApp->cryptoManager()->smartcard(), &QSmartCard::tokenChanged, this, &MainWindow::updateMyEID); + connect(qApp->cryptoManager()->smartcard(), &QSmartCard::dataChanged, this, &MainWindow::updateMyEid); connect(ui->signIntroButton, &QPushButton::clicked, this, [this] { openContainer(true); }); connect(ui->cryptoIntroButton, &QPushButton::clicked, this, [this] { openContainer(false); }); @@ -119,10 +120,10 @@ MainWindow::MainWindow( QWidget *parent ) connect(ui->accordion, &Accordion::changePinClicked, this, &MainWindow::changePinClicked); connect(ui->cardInfo, &CardWidget::selected, ui->selector, &QToolButton::toggle); - ui->signContainerPage->tokenChanged(qApp->signer()->tokensign()); - ui->cryptoContainerPage->tokenChanged(qApp->signer()->tokenauth()); - updateMyEID(qApp->signer()->smartcard()->tokenData()); - updateMyEid(qApp->signer()->smartcard()->data()); + ui->signContainerPage->tokenChanged(qApp->cryptoManager()->tokensign()); + ui->cryptoContainerPage->tokenChanged(qApp->cryptoManager()->tokenauth()); + updateMyEID(qApp->cryptoManager()->smartcard()->tokenData()); + updateMyEid(qApp->cryptoManager()->smartcard()->data()); } MainWindow::~MainWindow() noexcept = default; @@ -157,8 +158,8 @@ void MainWindow::changeEvent(QEvent* event) void MainWindow::changePinClicked(QSmartCardData::PinType type, QSmartCard::PinAction action) { - if(qApp->signer()->smartcard()->pinChange(type, action, ui->topBar)) - updateMyEid(qApp->signer()->smartcard()->data()); + if(qApp->cryptoManager()->smartcard()->pinChange(type, action, ui->topBar)) + updateMyEid(qApp->cryptoManager()->smartcard()->data()); } void MainWindow::closeEvent(QCloseEvent * /*event*/) @@ -274,7 +275,7 @@ void MainWindow::navigateToPage( Pages page, const QStringList &files, bool crea if(navigate) { cryptoDoc = std::move(cryptoContainer); - ui->cryptoContainerPage->transition(cryptoDoc.get(), qApp->signer()->tokenauth().cert()); + ui->cryptoContainerPage->transition(cryptoDoc.get(), qApp->cryptoManager()->tokenauth().cert()); } } @@ -289,7 +290,8 @@ void MainWindow::onSignAction(int action, const QString &idCode, const QString & case SignatureAdd: case SignatureToken: sign([this](const QString &city, const QString &state, const QString &zip, const QString &country, const QString &role) { - return digiDoc->sign(city, state, zip, country, role, qApp->signer()); + QSigner signer(qApp->cryptoManager()->tokensign()); + return digiDoc->sign(city, state, zip, country, role, &signer); }); break; case SignatureMobile: @@ -344,7 +346,7 @@ void MainWindow::convertToCDoc() else cryptoContainer->documentModel()->copyModel(digiDoc->documentModel()); - auto cardData = qApp->signer()->tokenauth(); + auto cardData = qApp->cryptoManager()->tokenauth(); if (!cardData.cert().isNull()) { cryptoContainer->addEncryptionKey(CKey(cardData.cert())); } @@ -733,22 +735,22 @@ void MainWindow::updateSelector() { case SignIntro: case SignDetails: - selected = qApp->signer()->tokensign(); + selected = qApp->cryptoManager()->tokensign(); filter = Signing; break; case CryptoIntro: case CryptoDetails: - selected = qApp->signer()->tokenauth(); + selected = qApp->cryptoManager()->tokenauth(); filter = Decrypting; break; case MyEid: default: - selected = qApp->signer()->smartcard()->tokenData(); + selected = qApp->cryptoManager()->smartcard()->tokenData(); filter = MyEID; break; } QVector list; - for(const TokenData &token: qApp->signer()->cache()) + for(const TokenData &token: qApp->cryptoManager()->cache()) { if(token.card() == selected.card()) continue; @@ -783,7 +785,7 @@ void MainWindow::updateSelector() if(show) { auto *cardPopup = new CardPopup(list, this); - connect(cardPopup, &CardPopup::activated, qApp->signer(), &QSigner::selectCard); + connect(cardPopup, &CardPopup::activated, qApp->cryptoManager(), &QCryptoManager::selectCard); connect(cardPopup, &CardPopup::activated, this, [this] { ui->selector->setChecked(false); }); cardPopup->show(); } diff --git a/client/QCryptoBackend.cpp b/client/QCryptoBackend.cpp index 4c7640fe8..d6b67589f 100644 --- a/client/QCryptoBackend.cpp +++ b/client/QCryptoBackend.cpp @@ -20,18 +20,23 @@ #include "QCryptoBackend.h" #include "Application.h" -#include "TokenData.h" #ifdef Q_OS_WIN #include "QCNG.h" #endif +#include "QPCSC.h" #include "QPKCS11.h" -#include "QSigner.h" #include "QSmartCard.h" +#include "SslCertificate.h" +#include "TokenData.h" +#include "dialogs/WarningDialog.h" +#include #include -#include +#include #include +static Q_LOGGING_CATEGORY(CryptoLog, "qdigidoc4.QCryptoManager") + // TODO: Port everything to the new OpenSSL API #define OPENSSL_SUPPRESS_DEPRECATED @@ -39,15 +44,32 @@ #include #include +struct QCryptoManager::Private +{ + QSmartCard *smartcard {}; + TokenData auth, sign; + QList cache; + QSemaphore operationLock {1}; + QReadWriteLock lock; +}; + QCryptoBackend::~QCryptoBackend() { - qApp->signer()->sessionLock().unlock(); - qApp->signer()->smartcard()->reloadCard(token, true); + auto *manager = qApp->cryptoManager(); + // Reload counters while the operation semaphore is still held, otherwise + // operationLock.release() would release it and queue refresh() first, letting the + // manager thread enumerate tokens concurrently with this PCSC reload. + manager->smartcard()->reloadCard(token, true); + + manager->d->operationLock.release(); + // A card change may have been skipped by refresh() while the operation held + // the lock; re-sync on the main thread now that the card session is free. + QMetaObject::invokeMethod(manager, [manager] { manager->refresh(); }, Qt::QueuedConnection); } std::expected QCryptoBackend::getBackend(const TokenData& token) { - if(!qApp->signer()->sessionLock().tryLockForWrite(10 * 1000)) + if(!qApp->cryptoManager()->d->operationLock.tryAcquire(1, (10 * 1000))) return std::unexpected(InProgress); #ifdef Q_OS_WIN auto backend = std::make_unique(); @@ -73,28 +95,18 @@ QSslCertificate QCryptoBackend::cert() const return token.cert(); } -QList -QCryptoBackend::getTokens() -{ -#ifdef Q_OS_WIN - return QCNG::tokens(); -#else - return QPKCS11::tokens(); -#endif -} - QString QCryptoBackend::errorString(Status error) { switch( error ) { case PinOK: return QString(); - case PinCanceled: return QCoreApplication::translate("QCryptoBackend", "PIN entry canceled"); - case PinLocked: return QCoreApplication::translate("QCryptoBackend", "PIN locked"); - case PinIncorrect: return QCoreApplication::translate("QCryptoBackend", "PIN incorrect"); - case InProgress: return QCoreApplication::translate("QCryptoBackend", "Signing/decrypting is already in progress another window."); - case GeneralError: return QCoreApplication::translate("QCryptoBackend", "PKCS11 general error"); - case DeviceError: return QCoreApplication::translate("QCryptoBackend", "PKCS11 device error"); - default: return QCoreApplication::translate("QCryptoBackend", "Unknown error"); + case PinCanceled: return tr("PIN entry canceled"); + case PinLocked: return tr("PIN locked"); + case PinIncorrect: return tr("PIN incorrect"); + case InProgress: return tr("Signing/decrypting is already in progress another window."); + case GeneralError: return tr("PKCS11 general error"); + case DeviceError: return tr("PKCS11 device error"); + default: return tr("Unknown error"); } } @@ -189,9 +201,148 @@ QCryptoBackend::getKey() const return key; } -void -QCryptoBackend::shutDown() +QCryptoManager::QCryptoManager() + : d(new Private) { + // QSmartCard keeps main-thread affinity (its PIN dialogs are UI); it is not a + // child of the manager so moveToThread() below does not drag it onto the worker. + d->smartcard = new QSmartCard(); + // Run the manager on its own thread with an event loop, so token enumeration + // and reloadCard happen off the UI thread. cardChanged/selectCard are delivered + // via queued connections and processed by run()'s exec() loop. + moveToThread(this); + connect(&QPCSC::instance(), &QPCSC::cardChanged, this, &QCryptoManager::refresh); + QPCSC::instance().start(); + start(); +} + +QCryptoManager::~QCryptoManager() +{ + quit(); + wait(); get_rsa_method(true); get_ec_method(true); + delete d->smartcard; + delete d; +} + +void QCryptoManager::run() +{ + refresh(); + exec(); +} + +QList QCryptoManager::cache() const { QReadLocker locker(&d->lock); return d->cache; } +QSmartCard *QCryptoManager::smartcard() const { return d->smartcard; } +TokenData QCryptoManager::tokenauth() const { QReadLocker locker(&d->lock); return d->auth; } +TokenData QCryptoManager::tokensign() const { QReadLocker locker(&d->lock); return d->sign; } + +void QCryptoManager::selectCard(const TokenData &token) +{ + bool isSign = SslCertificate(token.cert()).keyUsage().contains(SslCertificate::NonRepudiation); + TokenData other; + { + QWriteLocker locker(&d->lock); + if(isSign) + d->sign = token; + else + d->auth = token; + for(const TokenData &t: d->cache) + { + if(t == token || + t.card() != token.card() || + isSign == SslCertificate(t.cert()).keyUsage().contains(SslCertificate::NonRepudiation)) + continue; + if(isSign) + d->auth = t; + else + d->sign = t; + other = t; + break; + } + } + if(isSign) + Q_EMIT signDataChanged(token); + else + Q_EMIT authDataChanged(token); + if(!other.isNull()) + { + if(isSign) + Q_EMIT authDataChanged(other); + else + Q_EMIT signDataChanged(other); + } + d->smartcard->reloadCard(token, false); +} + +void QCryptoManager::refresh() +{ + // Don't enumerate tokens while a sign/decrypt operation holds the card + // session — the operation runs on a worker thread and PKCS11/PCSC access + // from two threads at once is unsafe. unlockOperation() queues a catch-up + // refresh when the operation finishes, so a skip here is not lost. + if(!d->operationLock.tryAcquire()) + return; + +#ifdef Q_OS_WIN + QList newCache = QCNG::tokens(); +#else + QList newCache = QPKCS11::tokens(); +#endif + + QList acards, scards; + for(const TokenData &t: newCache) + { + SslCertificate c(t.cert()); + if(c.keyUsage().contains(SslCertificate::KeyEncipherment) || + c.keyUsage().contains(SslCertificate::KeyAgreement)) + acards.append(t); + if(c.keyUsage().contains(SslCertificate::NonRepudiation)) + scards.append(t); + } + + bool cacheChangedFlag = false; + TokenData aold, sold, anew, snew; + { + QWriteLocker locker(&d->lock); + if(newCache != d->cache) + { + d->cache = std::move(newCache); + cacheChangedFlag = true; + } + + aold = d->auth; + sold = d->sign; + + if(!d->auth.isNull() && !acards.contains(d->auth)) + { + qCDebug(CryptoLog) << "Disconnected from auth card" << d->auth.card(); + d->auth.clear(); + } + if(!d->sign.isNull() && !scards.contains(d->sign)) + { + qCDebug(CryptoLog) << "Disconnected from sign card" << d->sign.card(); + d->sign.clear(); + } + + if(d->sign.isNull() && !scards.isEmpty()) + d->sign = scards.first(); + if(d->auth.isNull() && !acards.isEmpty()) + d->auth = acards.first(); + + anew = d->auth; + snew = d->sign; + } + + if(cacheChangedFlag) + Q_EMIT cacheChanged(); + TokenData update; + if(aold != anew) + Q_EMIT authDataChanged(update = anew); + if(sold != snew) + Q_EMIT signDataChanged(update = snew); + if(aold != anew || sold != snew) + d->smartcard->reloadCard(update, false); + + d->operationLock.release(); } diff --git a/client/QCryptoBackend.h b/client/QCryptoBackend.h index cac9b42ca..0e6616733 100644 --- a/client/QCryptoBackend.h +++ b/client/QCryptoBackend.h @@ -21,15 +21,18 @@ #include "TokenData.h" +#include #include +#include #include -class TokenData; +class QSmartCard; class QSslKey; class QCryptoBackend { + Q_DECLARE_TR_FUNCTIONS(QCryptoBackend); public: enum Status : quint8 { @@ -53,41 +56,56 @@ class QCryptoBackend /** * @brief Get the SSL key for the certificate - * + * * @return the Qt SSL key */ QSslKey getKey() const; QSslCertificate cert() const; /** * @brief Get a new Backend object and log in with the given token - * + * * @param token the token to use - * @return the new backend object or an error code + * @return the new backend object or an error code */ - static std::expected getBackend(const TokenData& token); - /** - * @brief Shut down all backends - * - * This should be called when the application is about to exit. It releases all static data held by backend(s) (e.g. PKCS11 library) - */ - static void shutDown(); + static std::expected getBackend(const TokenData &token); /** * @brief The status of the last operation */ mutable Status status = PinOK; - /** - * @brief Get a list of all available tokens - * - * @return list of all available tokens - */ - static QList getTokens(); - static QString errorString(Status error); + protected: virtual Status login(const TokenData &cert) = 0; private: TokenData token; }; + +class QCryptoManager final : public QThread +{ + Q_OBJECT +public: + explicit QCryptoManager(); + ~QCryptoManager() final; + + QList cache() const; + QSmartCard *smartcard() const; + void selectCard(const TokenData &token); + TokenData tokenauth() const; + TokenData tokensign() const; + +Q_SIGNALS: + void cacheChanged(); + void authDataChanged(const TokenData &token); + void signDataChanged(const TokenData &token); + +private: + friend class QCryptoBackend; + void refresh(); + void run() final; + + struct Private; + Private *d; +}; diff --git a/client/QSigner.cpp b/client/QSigner.cpp index 0ccd17fc0..e7c8811e9 100644 --- a/client/QSigner.cpp +++ b/client/QSigner.cpp @@ -20,92 +20,43 @@ #include "QSigner.h" #include "Application.h" -#include "QPCSC.h" +#include "QCryptoBackend.h" #include "QSmartCard.h" #include "TokenData.h" -#include "QCryptoBackend.h" -#include "SslCertificate.h" #include "Utils.h" -#include "dialogs/WarningDialog.h" #include +#include #include -#include -#include -#include -#include -#include - -#include -#include -#include - -static Q_LOGGING_CATEGORY(SLog, "qdigidoc4.QSigner") - -class QSigner::Private final -{ -public: - QSmartCard *smartcard {}; - TokenData auth, sign; - QList cache; - QReadWriteLock lock; - QMutex sleepMutex; - QWaitCondition sleepCond; -}; +#include using namespace digidoc; -QSigner::QSigner(QObject *parent) - : QThread(parent) - , d(new Private) +QSigner::QSigner(const TokenData &token) + : m_token(token) { - d->smartcard = new QSmartCard(parent); - connect(this, &QSigner::error, this, [](const QString &title, const QString &msg) { - WarningDialog::create() - ->withTitle(title) - ->withText(msg) - ->open(); - }); - connect(this, &QSigner::signDataChanged, this, [this](const TokenData &token) { + if(m_token.data(QStringLiteral("PSS")).toBool()) + { std::string method; - if(token.data(QStringLiteral("PSS")).toBool()) + switch(methodToNID(CONF(signatureDigestUri))) { - switch(methodToNID(CONF(signatureDigestUri))) - { - case QCryptographicHash::Sha224: method = "http://www.w3.org/2007/05/xmldsig-more#sha224-rsa-MGF1"; break; - case QCryptographicHash::Sha256: method = "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1"; break; - case QCryptographicHash::Sha384: method = "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1"; break; - case QCryptographicHash::Sha512: method = "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1"; break; - default: break; - } + case QCryptographicHash::Sha224: method = "http://www.w3.org/2007/05/xmldsig-more#sha224-rsa-MGF1"; break; + case QCryptographicHash::Sha256: method = "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1"; break; + case QCryptographicHash::Sha384: method = "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1"; break; + case QCryptographicHash::Sha512: method = "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1"; break; + default: break; } setMethod(method); - }); - connect(&QPCSC::instance(), &QPCSC::cardChanged, this, [this] { - qCDebug(SLog) << "Card change detected"; - d->sleepCond.wakeAll(); - }); - start(); - QPCSC::instance().start(); -} - -QSigner::~QSigner() -{ - requestInterruption(); - d->sleepCond.wakeAll(); - wait(); - delete d->smartcard; - delete d; + } } -QList QSigner::cache() const { return d->cache; } - X509Cert QSigner::cert() const { - if( d->sign.cert().isNull() ) - throw Exception(__FILE__, __LINE__, QSigner::tr("Sign certificate is not selected").toStdString()); - QByteArray der = d->sign.cert().toDer(); + if(m_token.cert().isNull()) + throw Exception(__FILE__, __LINE__, + tr("Sign certificate is not selected").toStdString()); + QByteArray der = m_token.cert().toDer(); return X509Cert((const unsigned char*)der.constData(), size_t(der.size()), X509Cert::Der); } @@ -125,98 +76,12 @@ QCryptographicHash::Algorithm QSigner::methodToNID(const std::string &method) method == "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384") return QCryptographicHash::Sha384; if(method == "http://www.w3.org/2001/04/xmlenc#sha512" || method == "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" || - method == "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1" || + method == "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1" || method == "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512") return QCryptographicHash::Sha512; return QCryptographicHash::Sha256; } -void QSigner::run() -{ - d->auth.clear(); - d->sign.clear(); - - while(!isInterruptionRequested()) { - if(d->lock.tryLockForRead()) { - QList acards, scards; - QList cache = QCryptoBackend::getTokens(); - if(cache != d->cache) - { - d->cache = std::move(cache); - Q_EMIT cacheChanged(); - } - for(const TokenData &t: d->cache) - { - SslCertificate c(t.cert()); - if(c.keyUsage().contains(SslCertificate::KeyEncipherment) || - c.keyUsage().contains(SslCertificate::KeyAgreement)) - acards.append(t); - if(c.keyUsage().contains(SslCertificate::NonRepudiation)) - scards.append(t); - } - - TokenData aold = d->auth; - TokenData sold = d->sign; - // check if selected card is still in slot - if(!d->auth.isNull() && !acards.contains(d->auth)) - { - qCDebug(SLog) << "Disconnected from auth card" << d->auth.card(); - d->auth.clear(); - } - if(!d->sign.isNull() && !scards.contains(d->sign)) - { - qCDebug(SLog) << "Disconnected from sign card" << d->sign.card(); - d->sign.clear(); - } - - // if none is selected then pick first card with signing cert; - // if no signing certs then pick first card with auth cert - if(d->sign.isNull() && !scards.isEmpty()) - d->sign = scards.first(); - if(d->auth.isNull() && !acards.isEmpty()) - d->auth = acards.first(); - - // update data if something has changed - TokenData update; - if(aold != d->auth) - Q_EMIT authDataChanged(update = d->auth); - if(sold != d->sign) - Q_EMIT signDataChanged(update = d->sign); - if(aold != d->auth || sold != d->sign) - d->smartcard->reloadCard(update, false); - d->lock.unlock(); - } - - QMutexLocker locker(&d->sleepMutex); - if (isInterruptionRequested()) - break; - d->sleepCond.wait(&d->sleepMutex, 5000); - } - QCryptoBackend::shutDown(); -} - -void QSigner::selectCard(const TokenData &token) -{ - bool isSign = SslCertificate(token.cert()).keyUsage().contains(SslCertificate::NonRepudiation); - if(isSign) - Q_EMIT signDataChanged(d->sign = token); - else - Q_EMIT authDataChanged(d->auth = token); - for(const TokenData &other: cache()) - { - if(other == token || - other.card() != token.card() || - isSign == SslCertificate(other.cert()).keyUsage().contains(SslCertificate::NonRepudiation)) - continue; - if(isSign) // Select other cert if they are on same card - Q_EMIT authDataChanged(d->auth = other); - else - Q_EMIT signDataChanged(d->sign = other); - break; - } - d->smartcard->reloadCard(token, false); -} - -std::vector QSigner::sign(const std::string &method, const std::vector &digest ) const +std::vector QSigner::sign(const std::string &method, const std::vector &digest) const { #define throwException(msg, code) { \ Exception e(__FILE__, __LINE__, (msg).toStdString()); \ @@ -224,50 +89,32 @@ std::vector QSigner::sign(const std::string &method, const std::v throw e; \ } - auto val = QCryptoBackend::getBackend(d->sign); - if (!val) { + auto val = QCryptoBackend::getBackend(m_token); + if(!val) + { + auto err = tr("Failed to login token") + ' ' + + QCryptoBackend::errorString(val.error()); switch(val.error()) { - case QCryptoBackend::PinCanceled: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(val.error())), Exception::PINCanceled); - case QCryptoBackend::PinLocked: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(val.error())), Exception::PINLocked); - case QCryptoBackend::InProgress: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(val.error())), Exception::General); - default: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(val.error())), Exception::PINFailed); + case QCryptoBackend::PinCanceled: throwException(err, Exception::PINCanceled); + case QCryptoBackend::PinLocked: throwException(err, Exception::PINLocked); + case QCryptoBackend::InProgress: throwException(err, Exception::General); + default: throwException(err, Exception::PINFailed); } } std::unique_ptr backend(val.value()); - if(backend->cert().isNull()) - throwException(tr("Signing certificate is not selected."), Exception::General) QByteArray sig = waitFor(&QCryptoBackend::sign, backend.get(), methodToNID(method), QByteArray::fromRawData((const char*)digest.data(), int(digest.size()))); - if (sig.isEmpty()) { + if(sig.isEmpty()) + { + auto err = tr("Failed to login token") + ' ' + + QCryptoBackend::errorString(backend->status); switch(backend->status) { - case QCryptoBackend::PinCanceled: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(backend->status)), Exception::PINCanceled); - case QCryptoBackend::PinLocked: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(backend->status)), Exception::PINLocked) - default: - break; + case QCryptoBackend::PinCanceled: throwException(err, Exception::PINCanceled); + case QCryptoBackend::PinLocked: throwException(err, Exception::PINLocked); + default: break; } - throwException(tr("Failed to sign document"), Exception::General) + throwException(tr("Failed to sign document"), Exception::General); } return {sig.constBegin(), sig.constEnd()}; } - -QReadWriteLock& QSigner::sessionLock() const -{ - return d->lock; -} - -QSmartCard * QSigner::smartcard() const { return d->smartcard; } -TokenData QSigner::tokenauth() const { return d->auth; } -TokenData QSigner::tokensign() const { return d->sign; } - -QString -QSigner::getLastErrorStr() const -{ - return "Backend error"; -} diff --git a/client/QSigner.h b/client/QSigner.h index 1b341f3ef..6f9620742 100644 --- a/client/QSigner.h +++ b/client/QSigner.h @@ -19,44 +19,28 @@ #pragma once -#include +#include "TokenData.h" + #include -#include +#include +#include -class QReadWriteLock; class QSmartCard; class TokenData; -class QSigner final: public QThread, public digidoc::Signer +class QSigner final : public digidoc::Signer { - Q_OBJECT - + Q_DECLARE_TR_FUNCTIONS(QSigner); public: - explicit QSigner(QObject *parent = nullptr); - ~QSigner() final; + explicit QSigner(const TokenData &token); - QList cache() const; digidoc::X509Cert cert() const final; - void selectCard(const TokenData &token); - std::vector sign( const std::string &method, + std::vector sign(const std::string &method, const std::vector &digest) const final; - QReadWriteLock& sessionLock() const; - QSmartCard * smartcard() const; - TokenData tokenauth() const; - TokenData tokensign() const; - QString getLastErrorStr() const; - -Q_SIGNALS: - void cacheChanged(); - void authDataChanged( const TokenData &token ); - void signDataChanged( const TokenData &token ); - void error(const QString &title, const QString &text); private: static QCryptographicHash::Algorithm methodToNID(const std::string &method); - void run() final; - class Private; - Private *d; + TokenData m_token; }; diff --git a/client/dialogs/AddRecipients.cpp b/client/dialogs/AddRecipients.cpp index a81ecbfd5..55b0cfd9a 100644 --- a/client/dialogs/AddRecipients.cpp +++ b/client/dialogs/AddRecipients.cpp @@ -26,7 +26,7 @@ #include "FileDialog.h" #include "IKValidator.h" #include "LdapSearch.h" -#include "QSigner.h" +#include "QCryptoBackend.h" #include "Settings.h" #include "TokenData.h" #include "dialogs/WarningDialog.h" @@ -92,13 +92,13 @@ AddRecipients::AddRecipients(ItemList* itemList, QWidget *parent) connect(ui->rightPane, &ItemList::removed, ui->rightPane, &ItemList::removeItem ); connect(ui->fromCard, &QPushButton::clicked, this, [this] { - addRecipient(qApp->signer()->tokenauth().cert()); + addRecipient(qApp->cryptoManager()->tokenauth().cert()); }); auto enableRecipientFromCard = [this] { - ui->fromCard->setDisabled(qApp->signer()->tokenauth().cert().isNull()); + ui->fromCard->setDisabled(qApp->cryptoManager()->tokenauth().cert().isNull()); }; enableRecipientFromCard(); - connect(qApp->signer(), &QSigner::authDataChanged, this, std::move(enableRecipientFromCard)); + connect(qApp->cryptoManager(), &QCryptoManager::authDataChanged, this, std::move(enableRecipientFromCard)); connect(ui->fromFile, &QPushButton::clicked, this, &AddRecipients::addRecipientFromFile); connect(ui->fromHistory, &QPushButton::clicked, this, &AddRecipients::addRecipientFromHistory); diff --git a/client/dialogs/SettingsDialog.cpp b/client/dialogs/SettingsDialog.cpp index 9ad9e6a4f..306e20c6c 100644 --- a/client/dialogs/SettingsDialog.cpp +++ b/client/dialogs/SettingsDialog.cpp @@ -25,7 +25,7 @@ #include "Configuration.h" #include "Diagnostics.h" #include "FileDialog.h" -#include "QSigner.h" +#include "QCryptoBackend.h" #include "Settings.h" #include "SslCertificate.h" #include "TokenData.h" @@ -376,7 +376,7 @@ SettingsDialog::SettingsDialog(int page, QWidget *parent) #ifdef Q_OS_WIN connect(ui->btnNavFromHistory, &QPushButton::clicked, this, [this] { // remove certificates from browsing history of Edge and Google Chrome, and do it for all users. - QList cache = qApp->signer()->cache(); + QList cache = qApp->cryptoManager()->cache(); HCERTSTORE s = CertOpenStore(CERT_STORE_PROV_SYSTEM_W, X509_ASN_ENCODING, 0, CERT_SYSTEM_STORE_CURRENT_USER, L"MY"); if(!s) diff --git a/client/translations/en.ts b/client/translations/en.ts index e0ee8edf5..0079f693c 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -608,6 +608,10 @@ Failed to open document Failed to open document + + Failed to decrypt document + Failed to decrypt document + Wrong password. Wrong password. @@ -2190,10 +2194,6 @@ ID-Card QSigner - - Signing certificate is not selected. - Signing certificate is not selected. - Failed to login token Failed to login token @@ -2206,10 +2206,6 @@ ID-Card Sign certificate is not selected Signing certificate is not selected - - Failed to decrypt document - Failed to decrypt document - QSmartCard diff --git a/client/translations/et.ts b/client/translations/et.ts index b8052a14a..0a6e6af88 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -608,6 +608,10 @@ Failed to open document Dokumendi avamine ebaõnnestus + + Failed to decrypt document + Dokumendi dekrüpteerimine ebaõnnestus + Wrong password. Vale parool. @@ -2190,10 +2194,6 @@ ID-kaardiga QSigner - - Signing certificate is not selected. - Allkirjastamise sertifikaat ei ole valitud. - Failed to login token PIN-koodi valideerimine ebaõnnestus @@ -2206,10 +2206,6 @@ ID-kaardiga Sign certificate is not selected Allkirjastamise sertifikaat ei ole valitud - - Failed to decrypt document - Dokumendi dekrüpteerimine ebaõnnestus - QSmartCard diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index b10c37130..6b5d36628 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -24,7 +24,7 @@ #include "CryptoDoc.h" #include "DigiDoc.h" #include "PrintSheet.h" -#include "QSigner.h" +#include "QCryptoBackend.h" #include "Settings.h" #include "SslCertificate.h" #include "TokenData.h" @@ -244,7 +244,7 @@ void ContainerPage::encrypt(CryptoDoc *container, bool longTerm) WaitDialogHolder waitDialog(this, tr("Encrypting")); if(!container->encrypt(container->fileName(), {}, {})) return; - transition(container, qApp->signer()->tokenauth().cert()); + transition(container, qApp->cryptoManager()->tokenauth().cert()); emit action(EncryptContainerSuccess, {}, {}); return; }