diff --git a/PdfForQtLib/sources/pdfsignaturehandler.cpp b/PdfForQtLib/sources/pdfsignaturehandler.cpp index a148d70..f48a61e 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler.cpp +++ b/PdfForQtLib/sources/pdfsignaturehandler.cpp @@ -353,6 +353,14 @@ void PDFSignatureVerificationResult::setSignatureFieldReference(PDFObjectReferen m_signatureFieldReference = signatureFieldReference; } +void PDFSignatureVerificationResult::addHashAlgorithm(const QString& algorithm) +{ + if (!m_hashAlgorithms.contains(algorithm)) + { + m_hashAlgorithms << algorithm; + } +} + void PDFSignatureVerificationResult::validate() { if (isCertificateValid() && isSignatureValid()) @@ -660,6 +668,13 @@ void PDFPublicKeySignatureHandler::verifySignature(PDFSignatureVerificationResul break; } + if (signerInfoValue->digest_alg && signerInfoValue->digest_alg->algorithm) + { + std::array buffer = { }; + OBJ_obj2txt(buffer.data(), int(buffer.size() - 1), signerInfoValue->digest_alg->algorithm, 0); + result.addHashAlgorithm(QString::fromLatin1(buffer.data())); + } + const int verification = PKCS7_signatureVerify(dataBio, pkcs7, signerInfoValue, signer); if (verification <= 0) { @@ -1339,7 +1354,7 @@ void PDFSignatureHandler_adbe_pkcs7_rsa_sha1::verifyRSASignature(PDFSignatureVer const unsigned int encryptedSignLength = signKey.length(); if (ASN1_OCTET_STRING* encryptedString = d2i_ASN1_OCTET_STRING(nullptr, &encryptedSign, encryptedSignLength)) { - int algorithmNID = 0; + int algorithmNID = NID_undef; QByteArray digestBuffer; if (!getMessageDigest(outputBuffer, encryptedString, rsa, algorithmNID, digestBuffer)) { @@ -1353,6 +1368,10 @@ void PDFSignatureHandler_adbe_pkcs7_rsa_sha1::verifyRSASignature(PDFSignatureVer const unsigned char* digest = convertByteArrayToUcharPtr(digestBuffer); const unsigned int digestLength = digestBuffer.length(); + std::array buffer = { }; + OBJ_obj2txt(buffer.data(), int(buffer.size() - 1), OBJ_nid2obj(algorithmNID), 0); + result.addHashAlgorithm(QString::fromLatin1(buffer.data())); + const int verifyValue = RSA_verify(algorithmNID, digest, digestLength, encryptedString->data, encryptedString->length, rsa); ASN1_OCTET_STRING_free(encryptedString); @@ -1760,6 +1779,53 @@ bool PDFCertificateStore::contains(const PDFCertificateInfo& info) return std::find_if(m_certificates.cbegin(), m_certificates.cend(), [&info](const auto& entry) { return entry.info == info; }) != m_certificates.cend(); } +QString PDFCertificateStore::getDefaultCertificateStoreFileName() const +{ + return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/TrustedCertStorage.bin"; +} + +void PDFCertificateStore::loadDefaultUserCertificates() +{ + QString trustedCertificateStoreFileName = getDefaultCertificateStoreFileName(); + QString trustedCertificateStoreLockFileName = trustedCertificateStoreFileName + ".lock"; + + QLockFile lockFile(trustedCertificateStoreLockFileName); + if (lockFile.lock()) + { + QFile trustedCertificateStoreFile(trustedCertificateStoreFileName); + if (trustedCertificateStoreFile.open(QFile::ReadOnly)) + { + QDataStream stream(&trustedCertificateStoreFile); + deserialize(stream); + trustedCertificateStoreFile.close(); + } + lockFile.unlock(); + } +} + +void PDFCertificateStore::saveDefaultUserCertificates() +{ + QString trustedCertificateStoreFileName = getDefaultCertificateStoreFileName(); + 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); + serialize(stream); + trustedCertificateStoreFile.close(); + } + lockFile.unlock(); + } +} + } // namespace pdf #ifdef Q_OS_WIN diff --git a/PdfForQtLib/sources/pdfsignaturehandler.h b/PdfForQtLib/sources/pdfsignaturehandler.h index ccba515..5a72fba 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler.h +++ b/PdfForQtLib/sources/pdfsignaturehandler.h @@ -365,12 +365,14 @@ public: const QString& getSignatureFieldQualifiedName() const { return m_signatureFieldQualifiedName; } const QStringList& getErrors() const { return m_errors; } const QStringList& getWarnings() const { return m_warnings; } + const QStringList& getHashAlgorithms() const { return m_hashAlgorithms; } const PDFCertificateInfos& getCertificateInfos() const { return m_certificateInfos; } void setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName); void setSignatureFieldReference(PDFObjectReference signatureFieldReference); void addCertificateInfo(PDFCertificateInfo info) { m_certificateInfos.emplace_back(qMove(info)); } + void addHashAlgorithm(const QString& algorithm); /// Adds OK flag, if both certificate and signature are valid void validate(); @@ -382,6 +384,7 @@ private: QString m_signatureFieldQualifiedName; QStringList m_errors; QStringList m_warnings; + QStringList m_hashAlgorithms; PDFCertificateInfos m_certificateInfos; }; @@ -480,6 +483,15 @@ public: /// Set certificates void setCertificates(CertificateEntries certificates) { m_certificates = qMove(certificates); } + /// Returns default certificate store file name + QString getDefaultCertificateStoreFileName() const; + + /// Load from default user certificate storage + void loadDefaultUserCertificates(); + + /// Save to default user certificate storage + void saveDefaultUserCertificates(); + private: static constexpr int persist_version = 1; diff --git a/PdfForQtViewer/pdfsidebarwidget.cpp b/PdfForQtViewer/pdfsidebarwidget.cpp index 4661dcc..b7da064 100644 --- a/PdfForQtViewer/pdfsidebarwidget.cpp +++ b/PdfForQtViewer/pdfsidebarwidget.cpp @@ -430,6 +430,13 @@ void PDFSidebarWidget::updateSignatures(const std::vectorsetIcon(0, warningIcon); } + QString hashAlgorithms = signature.getHashAlgorithms().join(", "); + if (!hashAlgorithms.isEmpty()) + { + QTreeWidgetItem* item = new QTreeWidgetItem(rootItem, QStringList(tr("Hash algorithms: %1").arg(hashAlgorithms))); + item->setIcon(0, infoIcon); + } + if (certificateInfo) { QTreeWidgetItem* certChainRoot = new QTreeWidgetItem(rootItem, QStringList(tr("Certificate validation chain"))); diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index 2ecc88a..a80ebd6 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -735,21 +735,7 @@ void PDFViewerMainWindow::readActionSettings() 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(); - } + m_certificateStore.loadDefaultUserCertificates(); } void PDFViewerMainWindow::writeSettings() @@ -780,25 +766,7 @@ void PDFViewerMainWindow::writeSettings() 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(); - } + m_certificateStore.saveDefaultUserCertificates(); } void PDFViewerMainWindow::updateTitle() @@ -1200,11 +1168,6 @@ 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(); diff --git a/PdfForQtViewer/pdfviewermainwindow.h b/PdfForQtViewer/pdfviewermainwindow.h index f7216f5..6f1930f 100644 --- a/PdfForQtViewer/pdfviewermainwindow.h +++ b/PdfForQtViewer/pdfviewermainwindow.h @@ -148,8 +148,6 @@ private: std::vector getRenderingOptionActions() const; QList getActions() const; - QString getTrustedCertificateStoreFileName() const; - int adjustDpiX(int value); struct AsyncReadingResult diff --git a/PdfTool/PdfTool.pro b/PdfTool/PdfTool.pro index 43b3c0a..f34991e 100644 --- a/PdfTool/PdfTool.pro +++ b/PdfTool/PdfTool.pro @@ -15,8 +15,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with PDFForQt. If not, see . -QT -= gui - CONFIG += c++11 console CONFIG -= app_bundle @@ -43,7 +41,8 @@ LIBS += -lPDFForQtLib SOURCES += \ main.cpp \ pdfoutputformatter.cpp \ - pdftoolabstractapplication.cpp + pdftoolabstractapplication.cpp \ + pdftoolverifysignatures.cpp # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin @@ -56,4 +55,5 @@ INSTALLS += application HEADERS += \ pdfoutputformatter.h \ - pdftoolabstractapplication.h + pdftoolabstractapplication.h \ + pdftoolverifysignatures.h diff --git a/PdfTool/pdfoutputformatter.cpp b/PdfTool/pdfoutputformatter.cpp index 11630b5..b9793e5 100644 --- a/PdfTool/pdfoutputformatter.cpp +++ b/PdfTool/pdfoutputformatter.cpp @@ -630,4 +630,13 @@ void PDFConsole::writeText(QString text) #endif } +void PDFConsole::writeError(QString text) +{ +#ifdef Q_OS_WIN + WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), text.utf16(), text.size(), nullptr, nullptr); +#else + QTextStream(stdout) << text; +#endif +} + } // pdftool diff --git a/PdfTool/pdfoutputformatter.h b/PdfTool/pdfoutputformatter.h index 408d39d..30d3f4f 100644 --- a/PdfTool/pdfoutputformatter.h +++ b/PdfTool/pdfoutputformatter.h @@ -95,6 +95,9 @@ public: /// Writes text to the console static void writeText(QString text); + /// Writes error to the console + static void writeError(QString text); + private: explicit PDFConsole() = delete; }; diff --git a/PdfTool/pdftoolabstractapplication.cpp b/PdfTool/pdftoolabstractapplication.cpp index 6a90553..b1001a7 100644 --- a/PdfTool/pdftoolabstractapplication.cpp +++ b/PdfTool/pdftoolabstractapplication.cpp @@ -130,12 +130,30 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser* { parser->addOption(QCommandLineOption("console-format", "Console output text format (valid values: text|xml|html).", "console-format", "text")); } + + if (optionFlags.testFlag(OpenDocument)) + { + parser->addOption(QCommandLineOption("pswd", "Password for encrypted document.", "password")); + parser->addPositionalArgument("document", "Processed document."); + parser->addOption(QCommandLineOption("no-permissive-reading", "Do not attempt to fix damaged documents.")); + } + + if (optionFlags.testFlag(SignatureVerification)) + { + parser->addOption(QCommandLineOption("ver-no-user-cert", "Disable user certificate store.")); + parser->addOption(QCommandLineOption("ver-no-sys-cert", "Disable system certificate store.")); + parser->addOption(QCommandLineOption("ver-no-cert-check", "Disable certificate validation.")); + parser->addOption(QCommandLineOption("ver-cert-details", "Print certificate details (including chain, if found).")); + parser->addOption(QCommandLineOption("ver-ignore-exp-date", "Ignore certificate expiration date.")); + } } PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser) const { PDFToolOptions options; + QStringList positionalArguments = parser->positionalArguments(); + Options optionFlags = getOptionsFlags(); if (optionFlags.testFlag(ConsoleFormat)) { @@ -154,10 +172,31 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser } else { + if (!consoleFormat.isEmpty()) + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Unknown console format '%1'. Defaulting to text console format.").arg(consoleFormat)); + } + options.outputStyle = PDFOutputFormatter::Style::Text; } } + if (optionFlags.testFlag(OpenDocument)) + { + options.document = positionalArguments.isEmpty() ? QString() : positionalArguments.front(); + options.password = parser->value("password"); + options.permissiveReading = !parser->isSet("no-permissive-reading"); + } + + if (optionFlags.testFlag(SignatureVerification)) + { + options.verificationUseUserCertificates = !parser->isSet("ver-no-user-cert"); + options.verificationUseSystemCertificates = !parser->isSet("ver-no-sys-cert"); + options.verificationOmitCertificateCheck = parser->isSet("ver-no-cert-check"); + options.verificationPrintCertificateDetails = parser->isSet("ver-cert-details"); + options.verificationIgnoreExpirationDate = parser->isSet("ver-ignore-exp-date"); + } + return options; } diff --git a/PdfTool/pdftoolabstractapplication.h b/PdfTool/pdftoolabstractapplication.h index 180b4b2..68b5f96 100644 --- a/PdfTool/pdftoolabstractapplication.h +++ b/PdfTool/pdftoolabstractapplication.h @@ -38,9 +38,20 @@ struct PDFToolTranslationContext struct PDFToolOptions { + // For option 'ConsoleFormat' + PDFOutputFormatter::Style outputStyle = PDFOutputFormatter::Style::Text; + + // For option 'OpenDocument' QString document; QString password; - PDFOutputFormatter::Style outputStyle = PDFOutputFormatter::Style::Text; + bool permissiveReading = true; + + // For option 'SignatureVerification' + bool verificationUseUserCertificates = true; + bool verificationUseSystemCertificates = true; + bool verificationOmitCertificateCheck = false; + bool verificationPrintCertificateDetails = false; + bool verificationIgnoreExpirationDate = false; }; /// Base class for all applications @@ -52,7 +63,9 @@ public: enum ExitCodes { - ExitSuccess = EXIT_SUCCESS + ExitSuccess = EXIT_SUCCESS, + ErrorNoDocumentSpecified, + ErrorDocumentReading }; enum StandardString @@ -64,7 +77,9 @@ public: enum Option { - ConsoleFormat = 0x0001, ///< Set format of console output (text, xml or html) + ConsoleFormat = 0x0001, ///< Set format of console output (text, xml or html) + OpenDocument = 0x0002, ///< Flags for document reading + SignatureVerification = 0x0004, ///< Flags for signature verification }; Q_DECLARE_FLAGS(Options, Option) diff --git a/PdfTool/pdftoolverifysignatures.cpp b/PdfTool/pdftoolverifysignatures.cpp new file mode 100644 index 0000000..ab2b3f2 --- /dev/null +++ b/PdfTool/pdftoolverifysignatures.cpp @@ -0,0 +1,118 @@ +// Copyright (C) 2020 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + +#include "pdftoolverifysignatures.h" + +#include "pdfdocumentreader.h" +#include "pdfsignaturehandler.h" +#include "pdfform.h" + +namespace pdftool +{ + +static PDFToolVerifySignaturesApplication s_verifySignaturesApplication; + +QString PDFToolVerifySignaturesApplication::getStandardString(StandardString standardString) const +{ + switch (standardString) + { + case Command: + return "verify-signatures"; + + case Name: + return PDFToolTranslationContext::tr("Signature verification"); + + case Description: + return PDFToolTranslationContext::tr("Verify signatures and timestamps in pdf document."); + + default: + Q_ASSERT(false); + break; + } + + return QString(); +} + +int PDFToolVerifySignaturesApplication::execute(const PDFToolOptions& options) +{ + // No document specified? + if (options.document.isEmpty()) + { + PDFConsole::writeError(PDFToolTranslationContext::tr("No document specified.")); + return ErrorNoDocumentSpecified; + } + + bool isFirstPasswordAttempt = true; + auto passwordCallback = [&options, &isFirstPasswordAttempt](bool* ok) -> QString + { + *ok = isFirstPasswordAttempt; + isFirstPasswordAttempt = false; + return options.password; + }; + pdf::PDFDocumentReader reader(nullptr, passwordCallback, options.permissiveReading); + pdf::PDFDocument document = reader.readFromFile(options.document); + + switch (reader.getReadingResult()) + { + case pdf::PDFDocumentReader::Result::OK: + break; + + case pdf::PDFDocumentReader::Result::Cancelled: + return ExitSuccess; + + case pdf::PDFDocumentReader::Result::Failed: + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Error occured during document reading. %1").arg(reader.getErrorMessage())); + return ErrorDocumentReading; + } + + default: + Q_ASSERT(false); + return ErrorDocumentReading; + } + + for (const QString& warning : reader.getWarnings()) + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Warning: %1").arg(warning)); + } + + // Verify signatures + pdf::PDFCertificateStore certificateStore; + if (options.verificationUseUserCertificates) + { + certificateStore.loadDefaultUserCertificates(); + } + + pdf::PDFSignatureHandler::Parameters parameters; + parameters.store = &certificateStore; + parameters.dss = &document.getCatalog()->getDocumentSecurityStore(); + parameters.enableVerification = true; + parameters.ignoreExpirationDate = options.verificationIgnoreExpirationDate; + parameters.useSystemCertificateStore = options.verificationUseSystemCertificates; + + pdf::PDFForm form = pdf::PDFForm::parse(&document, document.getCatalog()->getFormObject()); + std::vector signatures = pdf::PDFSignatureHandler::verifySignatures(form, reader.getSource(), parameters); + + return ExitSuccess; +} + +PDFToolAbstractApplication::Options PDFToolVerifySignaturesApplication::getOptionsFlags() const +{ + return PDFToolAbstractApplication::ConsoleFormat | PDFToolAbstractApplication::OpenDocument | PDFToolAbstractApplication::SignatureVerification; +} + +} // namespace pdftool diff --git a/PdfTool/pdftoolverifysignatures.h b/PdfTool/pdftoolverifysignatures.h new file mode 100644 index 0000000..5430abc --- /dev/null +++ b/PdfTool/pdftoolverifysignatures.h @@ -0,0 +1,36 @@ +// Copyright (C) 2020 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + +#ifndef PDFTOOLVERIFYSIGNATURES_H +#define PDFTOOLVERIFYSIGNATURES_H + +#include "pdftoolabstractapplication.h" + +namespace pdftool +{ + +class PDFToolVerifySignaturesApplication : public PDFToolAbstractApplication +{ +public: + virtual QString getStandardString(StandardString standardString) const override; + virtual int execute(const PDFToolOptions& options) override; + virtual Options getOptionsFlags() const override; +}; + +} // namespace pdftool + +#endif // PDFTOOLVERIFYSIGNATURES_H