diff --git a/PdfForQtLib/PdfForQtLib.pro b/PdfForQtLib/PdfForQtLib.pro index 5a318d7..0fb2e72 100644 --- a/PdfForQtLib/PdfForQtLib.pro +++ b/PdfForQtLib/PdfForQtLib.pro @@ -158,6 +158,7 @@ FORMS += \ sources/pdfrenderingerrorswidget.ui PDFFORQT_DEPENDENCIES_PATH = K:\Programming\PDF\PDF_For_Qt\PDfForQt-Dependencies +PDFFORQT_OPENSSL_PATH = K:\Programming\Qt\Tools\ # Link to freetype library LIBS += -L$$PDFFORQT_DEPENDENCIES_PATH/FreeType/ -lfreetype @@ -190,12 +191,12 @@ libjpeg.path = $$DESTDIR/install INSTALLS += libjpeg # Link OpenSSL -LIBS += -L$$PDFFORQT_DEPENDENCIES_PATH/OpenSSL/ -llibcrypto -llibssl -INCLUDEPATH += $$PDFFORQT_DEPENDENCIES_PATH/OpenSSL/include -DEPENDPATH += $$PDFFORQT_DEPENDENCIES_PATH/OpenSSL/include +LIBS += -L$$PDFFORQT_OPENSSL_PATH/OpenSSL/Win_x64/bin -L$$PDFFORQT_OPENSSL_PATH/OpenSSL/Win_x64/lib -llibcrypto -llibssl +INCLUDEPATH += $$PDFFORQT_OPENSSL_PATH/OpenSSL/Win_x64/include +DEPENDPATH += $$PDFFORQT_OPENSSL_PATH/OpenSSL/Win_x64/include # Add OpenSSL to installations -openssl_lib.files = $$PDFFORQT_DEPENDENCIES_PATH/OpenSSL/libcrypto-3.dll $$PDFFORQT_DEPENDENCIES_PATH/OpenSSL/libssl-3.dll +openssl_lib.files = $$PDFFORQT_OPENSSL_PATH/OpenSSL/Win_x64/bin/libcrypto-1_1-x64.dll $$PDFFORQT_OPENSSL_PATH/OpenSSL/Win_x64/bin/libssl-1_1-x64.dll openssl_lib.path = $$DESTDIR/install INSTALLS += openssl_lib @@ -253,7 +254,9 @@ qt_libraries.files = $$[QT_INSTALL_BINS]/Qt?Widgets$${SUFFIX}.dll \ $$[QT_INSTALL_BINS]/Qt?WinExtras$${SUFFIX}.dll \ $$[QT_INSTALL_BINS]/Qt?Svg$${SUFFIX}.dll \ $$[QT_INSTALL_BINS]/Qt?PrintSupport$${SUFFIX}.dll \ - $$[QT_INSTALL_BINS]/Qt?TextToSpeech$${SUFFIX}.dll + $$[QT_INSTALL_BINS]/Qt?TextToSpeech$${SUFFIX}.dll \ + $$[QT_INSTALL_BINS]/Qt?Network$${SUFFIX}.dll \ + $$[QT_INSTALL_BINS]/Qt?Xml$${SUFFIX}.dll qt_libraries.path = $$DESTDIR/install INSTALLS += qt_libraries diff --git a/PdfForQtLib/sources/pdfsignaturehandler.h b/PdfForQtLib/sources/pdfsignaturehandler.h index 10ea91b..e3d4d1a 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler.h +++ b/PdfForQtLib/sources/pdfsignaturehandler.h @@ -454,6 +454,9 @@ public: /// Get certificates stored in the store const CertificateEntries& getCertificates() const { return m_certificates; } + /// Set certificates + void setCertificates(CertificateEntries certificates) { m_certificates = qMove(certificates); } + private: static constexpr int persist_version = 1; diff --git a/PdfForQtViewer/PdfForQtViewer.pro b/PdfForQtViewer/PdfForQtViewer.pro index feb2397..a64064f 100644 --- a/PdfForQtViewer/PdfForQtViewer.pro +++ b/PdfForQtViewer/PdfForQtViewer.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui winextras printsupport texttospeech +QT += core gui winextras printsupport texttospeech network xml greaterThan(QT_MAJOR_VERSION, 4): QT += widgets diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index 7e611f2..d8ae0a7 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -732,6 +733,23 @@ void PDFViewerMainWindow::readActionSettings() m_recentFileManager->setRecentFilesLimit(settings.value("MaximumRecentFilesCount", PDFRecentFileManager::getDefaultRecentFiles()).toInt()); m_recentFileManager->setRecentFiles(settings.value("RecentFileList", QStringList()).toStringList()); settings.endGroup(); + + // Load trusted certificates + QString trustedCertificateStoreFileName = getTrustedCertificateStoreFileName(); + QString trustedCertificateStoreLockFileName = trustedCertificateStoreFileName + ".lock"; + + QLockFile lockFile(trustedCertificateStoreLockFileName); + if (lockFile.lock()) + { + QFile trustedCertificateStoreFile(trustedCertificateStoreFileName); + if (trustedCertificateStoreFile.open(QFile::ReadOnly)) + { + QDataStream stream(&trustedCertificateStoreFile); + m_certificateStore.deserialize(stream); + trustedCertificateStoreFile.close(); + } + lockFile.unlock(); + } } void PDFViewerMainWindow::writeSettings() @@ -760,6 +778,27 @@ void PDFViewerMainWindow::writeSettings() settings.setValue("MaximumRecentFilesCount", m_recentFileManager->getRecentFilesLimit()); settings.setValue("RecentFileList", m_recentFileManager->getRecentFiles()); settings.endGroup(); + + // Save trusted certificates + QString trustedCertificateStoreFileName = getTrustedCertificateStoreFileName(); + QString trustedCertificateStoreLockFileName = trustedCertificateStoreFileName + ".lock"; + + QFileInfo fileInfo(trustedCertificateStoreFileName); + QDir dir = fileInfo.dir(); + dir.mkpath(dir.path()); + + QLockFile lockFile(trustedCertificateStoreLockFileName); + if (lockFile.lock()) + { + QFile trustedCertificateStoreFile(trustedCertificateStoreFileName); + if (trustedCertificateStoreFile.open(QFile::WriteOnly | QFile::Truncate)) + { + QDataStream stream(&trustedCertificateStoreFile); + m_certificateStore.serialize(stream); + trustedCertificateStoreFile.close(); + } + lockFile.unlock(); + } } void PDFViewerMainWindow::updateTitle() @@ -1143,6 +1182,11 @@ QList PDFViewerMainWindow::getActions() const return findChildren(QString(), Qt::FindChildrenRecursively); } +QString PDFViewerMainWindow::getTrustedCertificateStoreFileName() const +{ + return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/TrustedCertStorage.bin"; +} + int PDFViewerMainWindow::adjustDpiX(int value) { const int physicalDpiX = this->physicalDpiX(); @@ -1269,7 +1313,7 @@ void PDFViewerMainWindow::on_actionOptions_triggered() PDFViewerSettingsDialog::OtherSettings otherSettings; otherSettings.maximumRecentFileCount = m_recentFileManager->getRecentFilesLimit(); - PDFViewerSettingsDialog dialog(m_settings->getSettings(), m_settings->getColorManagementSystemSettings(), otherSettings, getActions(), m_CMSManager, this); + PDFViewerSettingsDialog dialog(m_settings->getSettings(), m_settings->getColorManagementSystemSettings(), otherSettings, m_certificateStore, getActions(), m_CMSManager, this); if (dialog.exec() == QDialog::Accepted) { m_settings->setSettings(dialog.getSettings()); @@ -1278,6 +1322,7 @@ void PDFViewerMainWindow::on_actionOptions_triggered() m_recentFileManager->setRecentFilesLimit(dialog.getOtherSettings().maximumRecentFileCount); m_textToSpeech->setSettings(m_settings); m_formManager->setAppearanceFlags(m_settings->getSettings().m_formAppearanceFlags); + m_certificateStore = dialog.getCertificateStore(); updateMagnifierToolSettings(); updateUndoRedoSettings(); } diff --git a/PdfForQtViewer/pdfviewermainwindow.h b/PdfForQtViewer/pdfviewermainwindow.h index 6f1930f..f7216f5 100644 --- a/PdfForQtViewer/pdfviewermainwindow.h +++ b/PdfForQtViewer/pdfviewermainwindow.h @@ -148,6 +148,8 @@ private: std::vector getRenderingOptionActions() const; QList getActions() const; + QString getTrustedCertificateStoreFileName() const; + int adjustDpiX(int value); struct AsyncReadingResult diff --git a/PdfForQtViewer/pdfviewersettingsdialog.cpp b/PdfForQtViewer/pdfviewersettingsdialog.cpp index 9b5c622..b98d000 100644 --- a/PdfForQtViewer/pdfviewersettingsdialog.cpp +++ b/PdfForQtViewer/pdfviewersettingsdialog.cpp @@ -28,6 +28,9 @@ #include #include #include +#include +#include +#include namespace pdfviewer { @@ -35,15 +38,20 @@ namespace pdfviewer PDFViewerSettingsDialog::PDFViewerSettingsDialog(const PDFViewerSettings::Settings& settings, const pdf::PDFCMSSettings& cmsSettings, const OtherSettings& otherSettings, + const pdf::PDFCertificateStore& certificateStore, QList actions, - pdf::PDFCMSManager* cmsManager, QWidget *parent) : + pdf::PDFCMSManager* cmsManager, + QWidget *parent) : QDialog(parent), ui(new Ui::PDFViewerSettingsDialog), m_settings(settings), m_cmsSettings(cmsSettings), m_otherSettings(otherSettings), + m_certificateStore(certificateStore), m_actions(), - m_isLoadingData(false) + m_isLoadingData(false), + m_networkAccessManager(nullptr), + m_downloadCertificatesFromEUTLReply(nullptr) { ui->setupUi(this); @@ -142,10 +150,14 @@ PDFViewerSettingsDialog::PDFViewerSettingsDialog(const PDFViewerSettings::Settin ui->speechEnginesComboBox->addItem(engine, engine); } + connect(ui->trustedCertificateStoreTableWidget, &QTableWidget::itemSelectionChanged, this, &PDFViewerSettingsDialog::updateTrustedCertificatesTableActions); + ui->optionsPagesWidget->setCurrentRow(0); adjustSize(); loadData(); loadActionShortcutsTable(); + updateTrustedCertificatesTable(); + updateTrustedCertificatesTableActions(); } PDFViewerSettingsDialog::~PDFViewerSettingsDialog() @@ -549,6 +561,65 @@ void PDFViewerSettingsDialog::saveData() } } +void PDFViewerSettingsDialog::updateTrustedCertificatesTable() +{ + ui->trustedCertificateStoreTableWidget->setUpdatesEnabled(false); + ui->trustedCertificateStoreTableWidget->clear(); + + const pdf::PDFCertificateStore::CertificateEntries& certificates = m_certificateStore.getCertificates(); + ui->trustedCertificateStoreTableWidget->setRowCount(int(certificates.size())); + ui->trustedCertificateStoreTableWidget->setColumnCount(5); + ui->trustedCertificateStoreTableWidget->verticalHeader()->setVisible(true); + ui->trustedCertificateStoreTableWidget->setShowGrid(true); + ui->trustedCertificateStoreTableWidget->setEditTriggers(QTableWidget::NoEditTriggers); + ui->trustedCertificateStoreTableWidget->setHorizontalHeaderLabels(QStringList() << tr("Type") << tr("Certificate") << tr("Organization") << tr("Valid from") << tr("Valid to")); + + for (int i = 0; i < certificates.size(); ++i) + { + QString type; + switch (certificates[i].type) + { + case pdf::PDFCertificateStore::EntryType::User: + type = tr("User"); + break; + + case pdf::PDFCertificateStore::EntryType::EUTL: + type = tr("EUTL"); + break; + + default: + Q_ASSERT(false); + break; + } + + const pdf::PDFCertificateInfo& info = certificates[i].info; + ui->trustedCertificateStoreTableWidget->setItem(i, 0, new QTableWidgetItem(type)); + ui->trustedCertificateStoreTableWidget->setItem(i, 1, new QTableWidgetItem(info.getName(pdf::PDFCertificateInfo::CommonName))); + ui->trustedCertificateStoreTableWidget->setItem(i, 2, new QTableWidgetItem(info.getName(pdf::PDFCertificateInfo::OrganizationName))); + + QDateTime notValidBefore = info.getNotValidBefore().toLocalTime(); + QDateTime notValidAfter = info.getNotValidAfter().toLocalTime(); + + if (notValidBefore.isValid()) + { + ui->trustedCertificateStoreTableWidget->setItem(i, 3, new QTableWidgetItem(notValidBefore.toString(Qt::DefaultLocaleShortDate))); + } + + if (notValidAfter.isValid()) + { + ui->trustedCertificateStoreTableWidget->setItem(i, 4, new QTableWidgetItem(notValidAfter.toString(Qt::DefaultLocaleShortDate))); + } + } + + ui->trustedCertificateStoreTableWidget->resizeColumnsToContents(); + ui->trustedCertificateStoreTableWidget->setUpdatesEnabled(true); +} + +void PDFViewerSettingsDialog::updateTrustedCertificatesTableActions() +{ + ui->removeCertificateButton->setEnabled(!ui->trustedCertificateStoreTableWidget->selectionModel()->selectedRows().isEmpty()); +} + void PDFViewerSettingsDialog::loadActionShortcutsTable() { ui->shortcutsTableWidget->setRowCount(m_actions.size()); @@ -630,14 +701,38 @@ void PDFViewerSettingsDialog::setSpeechEngine(const QString& engine) ui->speechVoiceComboBox->setUpdatesEnabled(true); } +bool PDFViewerSettingsDialog::canCloseDialog() +{ + if (m_downloadCertificatesFromEUTLReply) + { + QMessageBox::warning(this, tr("Download"), tr("Downloading certificates from EUTL didn't finish yet. Can't close the dialog.")); + return false; + } + + return true; +} + void PDFViewerSettingsDialog::accept() { + if (!canCloseDialog()) + { + return; + } + if (saveActionShortcutsTable()) { QDialog::accept(); } } +void PDFViewerSettingsDialog::reject() +{ + if (canCloseDialog()) + { + QDialog::reject(); + } +} + void PDFViewerSettingsDialog::on_cmsProfileDirectoryButton_clicked() { QString directory = QFileDialog::getExistingDirectory(this, tr("Select color profile directory")); @@ -648,4 +743,82 @@ void PDFViewerSettingsDialog::on_cmsProfileDirectoryButton_clicked() } } +void PDFViewerSettingsDialog::on_trustedCertificateStoreDownloadEUTLButton_clicked() +{ + if (m_downloadCertificatesFromEUTLReply) + { + // Jakub Melka: We are already downloading the data + return; + } + + if (QMessageBox::question(this, tr("Download EUTL"), tr("Do you want do download EU trusted certificates list from https://ec.europa.eu/information_society/policy/esignature/trusted-list/tl-mp.xml ?")) == QMessageBox::Yes) + { + if (!m_networkAccessManager) + { + m_networkAccessManager = new QNetworkAccessManager(this); + } + + m_downloadCertificatesFromEUTLReply = m_networkAccessManager->get(QNetworkRequest(QUrl("https://ec.europa.eu/information_society/policy/esignature/trusted-list/tl-mp.xml"))); + + auto onFinished = [this]() + { + QNetworkReply::NetworkError error = m_downloadCertificatesFromEUTLReply->error(); + + if (error == QNetworkReply::NoError) + { + QByteArray data = m_downloadCertificatesFromEUTLReply->readAll(); + + QDomDocument document; + QString errorMessage; + if (document.setContent(data, &errorMessage)) + { + QDomNodeList certificateElements = document.elementsByTagName("X509Certificate"); + for (int i = 0; i < certificateElements.count(); ++i) + { + QDomElement certificateElement = certificateElements.at(i).toElement(); + QString certificateBase64Encoded = certificateElement.text(); + QByteArray certificateData = QByteArray::fromBase64(certificateBase64Encoded.toLatin1(), QByteArray::Base64Encoding); + m_certificateStore.add(pdf::PDFCertificateStore::EntryType::EUTL, certificateData); + } + updateTrustedCertificatesTable(); + } + else + { + QMessageBox::critical(this, tr("Error"), errorMessage); + } + } + else + { + QMessageBox::critical(this, tr("Error"), m_downloadCertificatesFromEUTLReply->errorString()); + } + + m_downloadCertificatesFromEUTLReply->deleteLater(); + m_downloadCertificatesFromEUTLReply = nullptr; + }; + connect(m_downloadCertificatesFromEUTLReply, &QNetworkReply::finished, this, onFinished); + } +} + +void PDFViewerSettingsDialog::on_removeCertificateButton_clicked() +{ + std::set rows; + QModelIndexList selectedIndices = ui->trustedCertificateStoreTableWidget->selectionModel()->selectedRows(); + for (const QModelIndex& index : selectedIndices) + { + rows.insert(index.row()); + } + + pdf::PDFCertificateStore::CertificateEntries newEntries; + const pdf::PDFCertificateStore::CertificateEntries& certificates = m_certificateStore.getCertificates(); + for (int i = 0; i < int(certificates.size()); ++i) + { + if (!rows.count(i)) + { + newEntries.push_back(certificates[i]); + } + } + m_certificateStore.setCertificates(qMove(newEntries)); + updateTrustedCertificatesTable(); +} + } // namespace pdfviewer diff --git a/PdfForQtViewer/pdfviewersettingsdialog.h b/PdfForQtViewer/pdfviewersettingsdialog.h index f9e6d11..f67ee43 100644 --- a/PdfForQtViewer/pdfviewersettingsdialog.h +++ b/PdfForQtViewer/pdfviewersettingsdialog.h @@ -23,6 +23,8 @@ #include class QListWidgetItem; +class QNetworkReply; +class QNetworkAccessManager; namespace Ui { @@ -47,18 +49,21 @@ public: /// \param settings Viewer settings /// \param cmsSettings Color management system settings /// \param otherSettings Other settings + /// \param certificateStore Certificate store /// \param actions Actions /// \param cmsManager CMS manager /// \param parent Parent widget explicit PDFViewerSettingsDialog(const PDFViewerSettings::Settings& settings, const pdf::PDFCMSSettings& cmsSettings, const OtherSettings& otherSettings, + const pdf::PDFCertificateStore& certificateStore, QList actions, pdf::PDFCMSManager* cmsManager, QWidget* parent); virtual ~PDFViewerSettingsDialog() override; virtual void accept() override; + virtual void reject() override; enum Page : int { @@ -78,20 +83,32 @@ public: const PDFViewerSettings::Settings& getSettings() const { return m_settings; } const pdf::PDFCMSSettings& getCMSSettings() const { return m_cmsSettings; } const OtherSettings& getOtherSettings() const { return m_otherSettings; } + const pdf::PDFCertificateStore& getCertificateStore() const { return m_certificateStore; } private slots: void on_optionsPagesWidget_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous); void on_cmsProfileDirectoryButton_clicked(); + void on_trustedCertificateStoreDownloadEUTLButton_clicked(); + + void on_removeCertificateButton_clicked(); + private: void loadData(); void saveData(); + void updateTrustedCertificatesTable(); + void updateTrustedCertificatesTableActions(); + void loadActionShortcutsTable(); bool saveActionShortcutsTable(); void setSpeechEngine(const QString& engine); + /// Returns true, if dialog can be closed. If not, then message is displayed + /// and false is returned. + bool canCloseDialog(); + Ui::PDFViewerSettingsDialog* ui; PDFViewerSettings::Settings m_settings; pdf::PDFCMSSettings m_cmsSettings; @@ -100,6 +117,9 @@ private: bool m_isLoadingData; QStringList m_textToSpeechEngines; QString m_currentSpeechEngine; + pdf::PDFCertificateStore m_certificateStore; + QNetworkAccessManager* m_networkAccessManager; + QNetworkReply* m_downloadCertificatesFromEUTLReply; }; } // namespace pdfviewer diff --git a/PdfForQtViewer/pdfviewersettingsdialog.ui b/PdfForQtViewer/pdfviewersettingsdialog.ui index fe187df..5f9cf2f 100644 --- a/PdfForQtViewer/pdfviewersettingsdialog.ui +++ b/PdfForQtViewer/pdfviewersettingsdialog.ui @@ -1271,7 +1271,11 @@ - + + + QAbstractItemView::SelectRows + +