diff --git a/PdfForQtLib/sources/pdfsignaturehandler.cpp b/PdfForQtLib/sources/pdfsignaturehandler.cpp index 9e274bb..24f9bad 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler.cpp +++ b/PdfForQtLib/sources/pdfsignaturehandler.cpp @@ -861,8 +861,8 @@ QDateTime pdf::PDFPublicKeySignatureHandler::getDateTimeFromASN(const ASN1_TIME* tm internalTime = { }; if (ASN1_TIME_to_tm(time, &internalTime) > 0) { - time_t localTime = mktime(&internalTime); - result = QDateTime::fromSecsSinceEpoch(localTime, Qt::LocalTime); + time_t localTime = _mkgmtime(&internalTime); + result = QDateTime::fromSecsSinceEpoch(localTime, Qt::UTC); } } diff --git a/PdfForQtLib/sources/pdfsignaturehandler.h b/PdfForQtLib/sources/pdfsignaturehandler.h index 34470bc..fb3288d 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler.h +++ b/PdfForQtLib/sources/pdfsignaturehandler.h @@ -280,6 +280,8 @@ public: Error_Signatures_Mask = Error_Signature_Invalid | Error_Signature_SourceCertificateMissing | Error_Signature_NoSignaturesFound | Error_Signature_DigestFailure | Error_Signature_DataOther | Error_Signature_DataCoveredBySignatureMissing, + + Warnings_Mask = Warning_Signature_NotCoveredBytes }; Q_DECLARE_FLAGS(VerificationFlags, VerificationFlag) @@ -309,6 +311,7 @@ public: bool isCertificateValid() const { return hasFlag(Certificate_OK); } bool isSignatureValid() const { return hasFlag(Signature_OK); } bool hasError() const { return !isValid(); } + bool hasWarning() const { return m_flags & Warnings_Mask; } bool hasCertificateError() const { return m_flags & Error_Certificates_Mask; } bool hasSignatureError() const { return m_flags & Error_Signatures_Mask; } bool hasFlag(VerificationFlag flag) const { return m_flags.testFlag(flag); } @@ -318,6 +321,7 @@ public: const QString& getSignatureFieldQualifiedName() const { return m_signatureFieldQualifiedName; } const QStringList& getErrors() const { return m_errors; } const QStringList& getWarnings() const { return m_warnings; } + const PDFCertificateInfos& getCertificateInfos() const { return m_certificateInfos; } void setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName); void setSignatureFieldReference(PDFObjectReference signatureFieldReference); diff --git a/PdfForQtViewer/pdfforqtviewer.qrc b/PdfForQtViewer/pdfforqtviewer.qrc index 9c7d081..e74c5b3 100644 --- a/PdfForQtViewer/pdfforqtviewer.qrc +++ b/PdfForQtViewer/pdfforqtviewer.qrc @@ -46,5 +46,9 @@ resources/form-settings.svg resources/undo.svg resources/redo.svg + resources/result-error.svg + resources/result-information.svg + resources/result-ok.svg + resources/result-warning.svg diff --git a/PdfForQtViewer/pdfsidebarwidget.cpp b/PdfForQtViewer/pdfsidebarwidget.cpp index dfb06d0..f03e976 100644 --- a/PdfForQtViewer/pdfsidebarwidget.cpp +++ b/PdfForQtViewer/pdfsidebarwidget.cpp @@ -24,6 +24,7 @@ #include "pdfdocument.h" #include "pdfitemmodels.h" #include "pdfexception.h" +#include "pdfsignaturehandler.h" #include "pdfdrawspacecontroller.h" #include @@ -97,6 +98,7 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, PDFTextToSpee m_pageInfo[Thumbnails] = { ui->thumbnailsButton, ui->thumbnailsPage }; m_pageInfo[Attachments] = { ui->attachmentsButton, ui->attachmentsPage }; m_pageInfo[Speech] = { ui->speechButton, ui->speechPage }; + m_pageInfo[Signatures] = { ui->signaturesButton, ui->signaturesPage }; for (const auto& pageInfo : m_pageInfo) { @@ -121,7 +123,7 @@ PDFSidebarWidget::~PDFSidebarWidget() delete ui; } -void PDFSidebarWidget::setDocument(const pdf::PDFModifiedDocument& document) +void PDFSidebarWidget::setDocument(const pdf::PDFModifiedDocument& document, const std::vector& signatures) { m_document = document; m_optionalContentActivity = document.getOptionalContentActivity(); @@ -196,6 +198,7 @@ void PDFSidebarWidget::setDocument(const pdf::PDFModifiedDocument& document) // Update GUI updateGUI(preferred); updateButtons(); + updateSignatures(signatures); } bool PDFSidebarWidget::isEmpty() const @@ -233,6 +236,9 @@ bool PDFSidebarWidget::isEmpty(Page page) const case Speech: return !m_textToSpeech->isValid(); + case Signatures: + return !m_signatures.empty(); + default: Q_ASSERT(false); break; @@ -331,6 +337,201 @@ void PDFSidebarWidget::updateButtons() } } +void PDFSidebarWidget::updateSignatures(const std::vector& signatures) +{ + ui->signatureTreeWidget->setUpdatesEnabled(false); + ui->signatureTreeWidget->clear(); + + QIcon okIcon(":/resources/result-ok.svg"); + QIcon errorIcon(":/resources/result-error.svg"); + QIcon warningIcon(":/resources/result-warning.svg"); + QIcon infoIcon(":/resources/result-information.svg"); + + for (const pdf::PDFSignatureVerificationResult& signature : signatures) + { + const pdf::PDFCertificateInfos& certificateInfos = signature.getCertificateInfos(); + const pdf::PDFCertificateInfo* certificateInfo = !certificateInfos.empty() ? &certificateInfos.front() : nullptr; + + QString text = tr("Signed by - %1").arg(certificateInfo ? certificateInfo->getName(pdf::PDFCertificateInfo::CommonName) : tr("Unknown")); + QTreeWidgetItem* rootItem = new QTreeWidgetItem(QStringList(text)); + + if (signature.hasError()) + { + rootItem->setIcon(0, errorIcon); + } + else if (signature.hasWarning()) + { + rootItem->setIcon(0, warningIcon); + } + else + { + rootItem->setIcon(0, okIcon); + } + + if (signature.isCertificateValid()) + { + QTreeWidgetItem* certificateItem = new QTreeWidgetItem(rootItem, QStringList(tr("Certificate is valid."))); + certificateItem->setIcon(0, okIcon); + } + + if (signature.isSignatureValid()) + { + QTreeWidgetItem* signatureItem = new QTreeWidgetItem(rootItem, QStringList(tr("Signature is valid."))); + signatureItem->setIcon(0, okIcon); + } + + for (const QString& error : signature.getErrors()) + { + QTreeWidgetItem* item = new QTreeWidgetItem(rootItem, QStringList(error)); + item->setIcon(0, errorIcon); + } + + for (const QString& error : signature.getWarnings()) + { + QTreeWidgetItem* item = new QTreeWidgetItem(rootItem, QStringList(error)); + item->setIcon(0, warningIcon); + } + + if (certificateInfo) + { + QTreeWidgetItem* certChainRoot = new QTreeWidgetItem(rootItem, QStringList(tr("Certificate validation chain"))); + certChainRoot->setIcon(0, infoIcon); + for (const pdf::PDFCertificateInfo& currentCertificateInfo : certificateInfos) + { + QTreeWidgetItem* certRoot = new QTreeWidgetItem(certChainRoot, QStringList(currentCertificateInfo.getName(pdf::PDFCertificateInfo::CommonName))); + certRoot->setIcon(0, infoIcon); + + auto addName = [certRoot, ¤tCertificateInfo, &infoIcon](pdf::PDFCertificateInfo::NameEntry nameEntry, QString caption) + { + QString text = currentCertificateInfo.getName(nameEntry); + if (!text.isEmpty()) + { + QTreeWidgetItem* item = new QTreeWidgetItem(certRoot, QStringList(QString("%1: %2").arg(caption, text))); + item->setIcon(0, infoIcon); + } + }; + + QString publicKeyMethod; + switch (currentCertificateInfo.getPublicKey()) + { + case pdf::PDFCertificateInfo::KeyRSA: + publicKeyMethod = tr("Protected by RSA method, %1-bit key").arg(currentCertificateInfo.getKeySize()); + break; + + case pdf::PDFCertificateInfo::KeyDSA: + publicKeyMethod = tr("Protected by DSA method, %1-bit key").arg(currentCertificateInfo.getKeySize()); + break; + + case pdf::PDFCertificateInfo::KeyEC: + publicKeyMethod = tr("Protected by EC method, %1-bit key").arg(currentCertificateInfo.getKeySize()); + break; + + case pdf::PDFCertificateInfo::KeyDH: + publicKeyMethod = tr("Protected by DH method, %1-bit key").arg(currentCertificateInfo.getKeySize()); + break; + + case pdf::PDFCertificateInfo::KeyUnknown: + publicKeyMethod = tr("Unknown protection method, %1-bit key").arg(currentCertificateInfo.getKeySize()); + break; + + default: + Q_ASSERT(false); + break; + } + + addName(pdf::PDFCertificateInfo::CountryName, tr("Country")); + addName(pdf::PDFCertificateInfo::OrganizationName, tr("Organization")); + addName(pdf::PDFCertificateInfo::OrganizationalUnitName, tr("Org. unit")); + addName(pdf::PDFCertificateInfo::DistinguishedName, tr("Name")); + addName(pdf::PDFCertificateInfo::StateOrProvinceName, tr("State")); + addName(pdf::PDFCertificateInfo::SerialNumber, tr("Serial number")); + addName(pdf::PDFCertificateInfo::LocalityName, tr("Locality")); + addName(pdf::PDFCertificateInfo::Title, tr("Title")); + addName(pdf::PDFCertificateInfo::Surname, tr("Surname")); + addName(pdf::PDFCertificateInfo::GivenName, tr("Forename")); + addName(pdf::PDFCertificateInfo::Initials, tr("Initials")); + addName(pdf::PDFCertificateInfo::Pseudonym, tr("Pseudonym")); + addName(pdf::PDFCertificateInfo::GenerationalQualifier, tr("Qualifier")); + addName(pdf::PDFCertificateInfo::Email, tr("Email")); + + QTreeWidgetItem* publicKeyItem = new QTreeWidgetItem(certRoot, QStringList(publicKeyMethod)); + publicKeyItem->setIcon(0, infoIcon); + + QDateTime notValidBefore = currentCertificateInfo.getNotValidBefore().toLocalTime(); + QDateTime notValidAfter = currentCertificateInfo.getNotValidAfter().toLocalTime(); + + if (notValidBefore.isValid()) + { + QTreeWidgetItem* item = new QTreeWidgetItem(certRoot, QStringList(QString("Valid from: %2").arg(notValidBefore.toString(Qt::DefaultLocaleShortDate)))); + item->setIcon(0, infoIcon); + } + + if (notValidAfter.isValid()) + { + QTreeWidgetItem* item = new QTreeWidgetItem(certRoot, QStringList(QString("Valid to: %2").arg(notValidAfter.toString(Qt::DefaultLocaleShortDate)))); + item->setIcon(0, infoIcon); + } + + QStringList keyUsages; + pdf::PDFCertificateInfo::KeyUsageFlags keyUsageFlags = currentCertificateInfo.getKeyUsage(); + if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageDigitalSignature)) + { + keyUsages << tr("Digital signatures"); + } + if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageNonRepudiation)) + { + keyUsages << tr("Non-repudiation"); + } + if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageKeyEncipherment)) + { + keyUsages << tr("Key encipherement"); + } + if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageDataEncipherment)) + { + keyUsages << tr("Application data encipherement"); + } + if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageAgreement)) + { + keyUsages << tr("Key agreement"); + } + if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageCertSign)) + { + keyUsages << tr("Verify signatures on certificates"); + } + if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageCrlSign)) + { + keyUsages << tr("Verify signatures on revocation information"); + } + if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageEncipherOnly)) + { + keyUsages << tr("Encipher data during key agreement"); + } + if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageDecipherOnly)) + { + keyUsages << tr("Decipher data during key agreement"); + } + + if (!keyUsages.isEmpty()) + { + QTreeWidgetItem* keyUsageRoot = new QTreeWidgetItem(certRoot, QStringList(tr("Key usages"))); + keyUsageRoot->setIcon(0, infoIcon); + + for (const QString& keyUsage : keyUsages) + { + QTreeWidgetItem* keyUsageItem = new QTreeWidgetItem(keyUsageRoot, QStringList(keyUsage)); + keyUsageItem->setIcon(0, infoIcon); + } + } + } + } + + ui->signatureTreeWidget->addTopLevelItem(rootItem); + } + + ui->signatureTreeWidget->expandToDepth(1); + ui->signatureTreeWidget->setUpdatesEnabled(true); +} + void PDFSidebarWidget::onPageButtonClicked() { QObject* pushButton = sender(); diff --git a/PdfForQtViewer/pdfsidebarwidget.h b/PdfForQtViewer/pdfsidebarwidget.h index 0808d91..2374f8d 100644 --- a/PdfForQtViewer/pdfsidebarwidget.h +++ b/PdfForQtViewer/pdfsidebarwidget.h @@ -37,10 +37,11 @@ class PDFAction; class PDFDocument; class PDFDrawWidgetProxy; class PDFModifiedDocument; -class PDFOutlineTreeItemModel; class PDFThumbnailsItemModel; -class PDFAttachmentsTreeItemModel; +class PDFOutlineTreeItemModel; class PDFOptionalContentActivity; +class PDFAttachmentsTreeItemModel; +class PDFSignatureVerificationResult; class PDFOptionalContentTreeItemModel; } @@ -67,10 +68,11 @@ public: OptionalContent, Attachments, Speech, + Signatures, _END }; - void setDocument(const pdf::PDFModifiedDocument& document); + void setDocument(const pdf::PDFModifiedDocument& document, const std::vector& signatures); /// Returns true, if all items in sidebar are empty bool isEmpty() const; @@ -93,6 +95,7 @@ signals: private: void updateGUI(Page preferredPage); void updateButtons(); + void updateSignatures(const std::vector& signatures); void onPageButtonClicked(); void onOutlineItemClicked(const QModelIndex& index); @@ -116,6 +119,7 @@ private: pdf::PDFOptionalContentActivity* m_optionalContentActivity; pdf::PDFAttachmentsTreeItemModel* m_attachmentsTreeModel; std::map m_pageInfo; + std::vector m_signatures; }; } // namespace pdfviewer diff --git a/PdfForQtViewer/pdfsidebarwidget.ui b/PdfForQtViewer/pdfsidebarwidget.ui index 0eca9a9..66c3e9d 100644 --- a/PdfForQtViewer/pdfsidebarwidget.ui +++ b/PdfForQtViewer/pdfsidebarwidget.ui @@ -93,6 +93,19 @@ + + + + Signatures + + + true + + + true + + + @@ -111,7 +124,7 @@ - 5 + 6 @@ -392,6 +405,37 @@ + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + true + + + + 1 + + + + + + diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index cd0d7eb..6ea7d03 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -967,8 +967,7 @@ void PDFViewerMainWindow::openDocument(const QString& fileName) { // Verify signatures pdf::PDFForm form = pdf::PDFForm::parse(&document, document.getCatalog()->getFormObject()); - std::vector signaturesVerifications = pdf::PDFSignatureHandler::verifySignatures(form, reader.getSource()); - + result.signatures = pdf::PDFSignatureHandler::verifySignatures(form, reader.getSource()); result.document.reset(new pdf::PDFDocument(qMove(document))); } @@ -1001,7 +1000,8 @@ void PDFViewerMainWindow::onDocumentReadingFinished() // We add file to recent files only, if we have successfully read the document m_recentFileManager->addRecentFile(m_fileInfo.originalFileName); - m_pdfDocument = result.document; + m_pdfDocument = qMove(result.document); + m_signatures = qMove(result.signatures); pdf::PDFModifiedDocument document(m_pdfDocument.data(), m_optionalContentActivity); setDocument(document); @@ -1072,7 +1072,7 @@ void PDFViewerMainWindow::setDocument(pdf::PDFModifiedDocument document) m_toolManager->setDocument(document); m_textToSpeech->setDocument(document); m_pdfWidget->setDocument(document); - m_sidebarWidget->setDocument(document); + m_sidebarWidget->setDocument(document, m_signatures); m_advancedFindWidget->setDocument(document); if (m_sidebarWidget->isEmpty()) @@ -1109,6 +1109,7 @@ void PDFViewerMainWindow::setDocument(pdf::PDFModifiedDocument document) void PDFViewerMainWindow::closeDocument() { + m_signatures.clear(); setDocument(pdf::PDFModifiedDocument()); m_pdfDocument.reset(); updateActionsAvailability(); diff --git a/PdfForQtViewer/pdfviewermainwindow.h b/PdfForQtViewer/pdfviewermainwindow.h index 64d7fed..698a206 100644 --- a/PdfForQtViewer/pdfviewermainwindow.h +++ b/PdfForQtViewer/pdfviewermainwindow.h @@ -158,6 +158,7 @@ private: pdf::PDFDocumentPointer document; QString errorMessage; pdf::PDFDocumentReader::Result result = pdf::PDFDocumentReader::Result::Cancelled; + std::vector signatures; }; Ui::PDFViewerMainWindow* ui; @@ -179,6 +180,7 @@ private: QWinTaskbarButton* m_taskbarButton; QWinTaskbarProgress* m_progressTaskbarIndicator; PDFFileInfo m_fileInfo; + std::vector m_signatures; QFuture m_future; QFutureWatcher* m_futureWatcher; diff --git a/PdfForQtViewer/resources/result-error.svg b/PdfForQtViewer/resources/result-error.svg new file mode 100644 index 0000000..cad25bb --- /dev/null +++ b/PdfForQtViewer/resources/result-error.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + image/svg+xml + + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + diff --git a/PdfForQtViewer/resources/result-information.svg b/PdfForQtViewer/resources/result-information.svg new file mode 100644 index 0000000..b9e5514 --- /dev/null +++ b/PdfForQtViewer/resources/result-information.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + image/svg+xml + + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + diff --git a/PdfForQtViewer/resources/result-ok.svg b/PdfForQtViewer/resources/result-ok.svg new file mode 100644 index 0000000..6ab69f4 --- /dev/null +++ b/PdfForQtViewer/resources/result-ok.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + image/svg+xml + + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + diff --git a/PdfForQtViewer/resources/result-warning.svg b/PdfForQtViewer/resources/result-warning.svg new file mode 100644 index 0000000..61866da --- /dev/null +++ b/PdfForQtViewer/resources/result-warning.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + image/svg+xml + + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + +