From 11e218ecc9284ccb2368fceb0914253e8c481a11 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Mon, 28 Sep 2020 19:08:57 +0200 Subject: [PATCH] Verification tool - output text --- PdfForQtLib/sources/pdfsignaturehandler.cpp | 68 +++++++++--- PdfForQtLib/sources/pdfsignaturehandler.h | 28 ++++- PdfTool/pdfoutputformatter.h | 4 + PdfTool/pdftoolabstractapplication.cpp | 29 +++++- PdfTool/pdftoolabstractapplication.h | 2 + PdfTool/pdftoolverifysignatures.cpp | 109 ++++++++++++++++++++ 6 files changed, 221 insertions(+), 19 deletions(-) diff --git a/PdfForQtLib/sources/pdfsignaturehandler.cpp b/PdfForQtLib/sources/pdfsignaturehandler.cpp index deeb038..6a6de86 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler.cpp +++ b/PdfForQtLib/sources/pdfsignaturehandler.cpp @@ -389,14 +389,64 @@ void PDFSignatureVerificationResult::setTimestampDate(const QDateTime& timestamp m_timestampDate = timestampDate; } -QByteArray PDFSignatureVerificationResult::getSignatureFilter() const +QByteArray PDFSignatureVerificationResult::getSignatureHandler() const { - return m_signatureFilter; + return m_signatureHandler; } -void PDFSignatureVerificationResult::setSignatureFilter(const QByteArray& signatureFilter) +void PDFSignatureVerificationResult::setSignatureHandler(const QByteArray& signatureFilter) { - m_signatureFilter = signatureFilter; + m_signatureHandler = signatureFilter; +} + +PDFSignatureVerificationResult::Status PDFSignatureVerificationResult::getCertificateStatus() const +{ + if (hasCertificateError()) + { + return Status::Error; + } + + if (hasCertificateWarning()) + { + return Status::Warning; + } + + return Status::OK; +} + +PDFSignatureVerificationResult::Status PDFSignatureVerificationResult::getSignatureStatus() const +{ + if (hasSignatureError()) + { + return Status::Error; + } + + if (hasSignatureWarning()) + { + return Status::Warning; + } + + return Status::OK; +} + +QString PDFSignatureVerificationResult::getStatusText(Status status) +{ + switch (status) + { + case Status::OK: + return PDFTranslationContext::tr("OK"); + + case Status::Warning: + return PDFTranslationContext::tr("Warning"); + + case Status::Error: + return PDFTranslationContext::tr("Error"); + + default: + break; + } + + return QString(); } PDFSignature::Type PDFSignatureVerificationResult::getType() const @@ -416,7 +466,7 @@ void PDFPublicKeySignatureHandler::initializeResult(PDFSignatureVerificationResu result.setType(m_signatureField->getSignature().getType()); result.setSignatureFieldReference(signatureFieldReference); result.setSignatureFieldQualifiedName(signatureFieldQualifiedName); - result.setSignatureFilter(m_signatureField->getSignature().getFilter()); + result.setSignatureHandler(m_signatureField->getSignature().getSubfilter()); } STACK_OF(X509)* PDFPublicKeySignatureHandler::getCertificates(PKCS7* pkcs7) @@ -1813,16 +1863,10 @@ void PDFPublicKeySignatureHandler::addSignatureDateFromSignerInfoStack(STACK_OF( return; } - // Jakub Melka: We will get signed attribute from signer info. If it fails, - // then try to get unsigned attribute. + // Jakub Melka: We will get signed attribute from signer info. PKCS7_SIGNER_INFO* signerInfo = sk_PKCS7_SIGNER_INFO_value(signerInfoStack, 0); ASN1_TYPE* attribute = PKCS7_get_signed_attribute(signerInfo, NID_pkcs9_signingTime); - if (!attribute) - { - attribute = PKCS7_get_attribute(signerInfo, NID_pkcs9_signingTime); - } - if (!attribute) { return; diff --git a/PdfForQtLib/sources/pdfsignaturehandler.h b/PdfForQtLib/sources/pdfsignaturehandler.h index 590f608..57fd842 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler.h +++ b/PdfForQtLib/sources/pdfsignaturehandler.h @@ -281,6 +281,13 @@ public: } + enum class Status + { + OK, + Warning, + Error + }; + enum VerificationFlag { None = 0x00000000, ///< Used only for initialization @@ -319,7 +326,10 @@ 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 | Warning_Certificate_CRLValidityTimeExpired | Warning_Certificate_QualifiedStatement + Warning_Certificates_Mask = Warning_Certificate_CRLValidityTimeExpired | Warning_Certificate_QualifiedStatement, + Warning_Signatures_Mask = Warning_Signature_NotCoveredBytes, + + Warnings_Mask = Warning_Certificates_Mask | Warning_Signatures_Mask }; Q_DECLARE_FLAGS(VerificationFlags, VerificationFlag) @@ -358,6 +368,8 @@ public: 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 hasCertificateWarning() const { return m_flags & Warning_Certificates_Mask; } + bool hasSignatureWarning() const { return m_flags & Warning_Signatures_Mask; } bool hasFlag(VerificationFlag flag) const { return m_flags.testFlag(flag); } void setFlag(VerificationFlag flag, bool value) { m_flags.setFlag(flag, value); } @@ -383,8 +395,16 @@ public: QDateTime getTimestampDate() const; void setTimestampDate(const QDateTime& timestampDate); - QByteArray getSignatureFilter() const; - void setSignatureFilter(const QByteArray& signatureFilter); + QByteArray getSignatureHandler() const; + void setSignatureHandler(const QByteArray& signatureFilter); + + Status getCertificateStatus() const; + Status getSignatureStatus() const; + + QString getCertificateStatusText() const { return getStatusText(getCertificateStatus()); } + QString getSignatureStatusText() const { return getStatusText(getSignatureStatus()); } + + static QString getStatusText(Status status); private: PDFSignature::Type m_type = PDFSignature::Type::Invalid; @@ -396,7 +416,7 @@ private: QStringList m_errors; QStringList m_warnings; QStringList m_hashAlgorithms; - QByteArray m_signatureFilter; + QByteArray m_signatureHandler; PDFCertificateInfos m_certificateInfos; }; diff --git a/PdfTool/pdfoutputformatter.h b/PdfTool/pdfoutputformatter.h index 30d3f4f..36829a4 100644 --- a/PdfTool/pdfoutputformatter.h +++ b/PdfTool/pdfoutputformatter.h @@ -74,9 +74,13 @@ public: inline void beginTableHeaderRow(QString name) { beginElement(Element::TableHeaderRow, name); } inline void endTableHeaderRow() { endElement(); } inline void beginTableRow(QString name) { beginElement(Element::TableRow, name); } + inline void beginTableRow(QString name, int reference) { beginElement(Element::TableRow, name, QString(), Qt::AlignLeft, reference); } inline void endTableRow() { endElement(); } inline void writeTableHeaderColumn(QString name, QString description, Qt::Alignment alignment = Qt::AlignCenter) { beginElement(Element::TableHeaderColumn, name, description, alignment); endElement(); } inline void writeTableColumn(QString name, QString description, Qt::Alignment alignment = Qt::AlignLeft) { beginElement(Element::TableColumn, name, description, alignment); endElement(); } + inline void writeText(QString name, QString description) { beginElement(Element::Text, name, description); endElement(); } + inline void beginHeader(QString name, QString description, int reference = 0) { beginElement(Element::Header, name, description, Qt::AlignLeft, reference); } + inline void endHeader() { endElement(); } /// Ends current line void endl(); diff --git a/PdfTool/pdftoolabstractapplication.cpp b/PdfTool/pdftoolabstractapplication.cpp index b1001a7..bfbeead 100644 --- a/PdfTool/pdftoolabstractapplication.cpp +++ b/PdfTool/pdftoolabstractapplication.cpp @@ -143,8 +143,9 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser* 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-details", "Print details (including certificate chain, if found).")); parser->addOption(QCommandLineOption("ver-ignore-exp-date", "Ignore certificate expiration date.")); + parser->addOption(QCommandLineOption("ver-date-format", "Console output date/time format (valid values: short|long|iso|rfc2822).", "ver-date-format", "short")); } } @@ -184,7 +185,7 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser if (optionFlags.testFlag(OpenDocument)) { options.document = positionalArguments.isEmpty() ? QString() : positionalArguments.front(); - options.password = parser->value("password"); + options.password = parser->isSet("pswd") ? parser->value("password") : QString(); options.permissiveReading = !parser->isSet("no-permissive-reading"); } @@ -193,8 +194,30 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser 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.verificationPrintCertificateDetails = parser->isSet("ver-details"); options.verificationIgnoreExpirationDate = parser->isSet("ver-ignore-exp-date"); + + QString dateFormat = parser->value("ver-date-format"); + if (dateFormat == "short") + { + options.verificationDateFormat = Qt::DefaultLocaleShortDate; + } + else if (dateFormat == "long") + { + options.verificationDateFormat = Qt::DefaultLocaleLongDate; + } + else if (dateFormat == "iso") + { + options.verificationDateFormat = Qt::ISODate; + } + else if (dateFormat == "rfc2822") + { + options.verificationDateFormat = Qt::RFC2822Date; + } + else if (!dateFormat.isEmpty()) + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Unknown console date/time format '%1'. Defaulting to short date/time format.").arg(dateFormat)); + } } return options; diff --git a/PdfTool/pdftoolabstractapplication.h b/PdfTool/pdftoolabstractapplication.h index 68b5f96..5aa2d98 100644 --- a/PdfTool/pdftoolabstractapplication.h +++ b/PdfTool/pdftoolabstractapplication.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -52,6 +53,7 @@ struct PDFToolOptions bool verificationOmitCertificateCheck = false; bool verificationPrintCertificateDetails = false; bool verificationIgnoreExpirationDate = false; + Qt::DateFormat verificationDateFormat = Qt::DefaultLocaleShortDate; }; /// Base class for all applications diff --git a/PdfTool/pdftoolverifysignatures.cpp b/PdfTool/pdftoolverifysignatures.cpp index ab2b3f2..7736444 100644 --- a/PdfTool/pdftoolverifysignatures.cpp +++ b/PdfTool/pdftoolverifysignatures.cpp @@ -107,6 +107,115 @@ int PDFToolVerifySignaturesApplication::execute(const PDFToolOptions& options) pdf::PDFForm form = pdf::PDFForm::parse(&document, document.getCatalog()->getFormObject()); std::vector signatures = pdf::PDFSignatureHandler::verifySignatures(form, reader.getSource(), parameters); + PDFOutputFormatter formatter(options.outputStyle); + formatter.beginDocument("signatures", PDFToolTranslationContext::tr("Digital signatures/timestamps verification of %1").arg(options.document)); + formatter.endl(); + + auto getTypeName = [](const pdf::PDFSignatureVerificationResult& signature) + { + switch (signature.getType()) + { + case pdf::PDFSignature::Type::Invalid: + return PDFToolTranslationContext::tr("Invalid"); + + case pdf::PDFSignature::Type::Sig: + return PDFToolTranslationContext::tr("Signature"); + + case pdf::PDFSignature::Type::DocTimeStamp: + return PDFToolTranslationContext::tr("Timestamp"); + break; + + default: + Q_ASSERT(false); + break; + } + + return QString(); + }; + + if (!signatures.empty()) + { + formatter.beginTable("overview", PDFToolTranslationContext::tr("Overview")); + + formatter.beginTableHeaderRow("header"); + formatter.writeTableHeaderColumn("no", PDFToolTranslationContext::tr("No."), Qt::AlignLeft); + formatter.writeTableHeaderColumn("type", PDFToolTranslationContext::tr("Type"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("common-name", PDFToolTranslationContext::tr("Signed by"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("cert-status", PDFToolTranslationContext::tr("Certificate"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("signature-status", PDFToolTranslationContext::tr("Signature"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("signing-date", PDFToolTranslationContext::tr("Signing date"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("timestamp-date", PDFToolTranslationContext::tr("Timestamp date"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("hash-algorithm", PDFToolTranslationContext::tr("Hash alg."), Qt::AlignLeft); + formatter.writeTableHeaderColumn("handler", PDFToolTranslationContext::tr("Handler"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("whole-signed", PDFToolTranslationContext::tr("Signed whole"), Qt::AlignLeft); + formatter.endTableHeaderRow(); + + int i = 1; + for (const pdf::PDFSignatureVerificationResult& signature : signatures) + { + const pdf::PDFCertificateInfos& certificateInfos = signature.getCertificateInfos(); + const pdf::PDFCertificateInfo* certificateInfo = !certificateInfos.empty() ? &certificateInfos.front() : nullptr; + + formatter.beginTableRow("signature", i); + + formatter.writeTableColumn("no", QString::number(i), Qt::AlignRight); + formatter.writeTableColumn("type", getTypeName(signature)); + + QString commonName = certificateInfo ? certificateInfo->getName(pdf::PDFCertificateInfo::CommonName) : PDFToolTranslationContext::tr("Unknown"); + formatter.writeTableColumn("common-name", commonName); + formatter.writeTableColumn("cert-status", options.verificationOmitCertificateCheck ? PDFToolTranslationContext::tr("Skipped") : signature.getCertificateStatusText()); + formatter.writeTableColumn("signature-status", signature.getSignatureStatusText()); + formatter.writeTableColumn("signing-date", signature.getSignatureDate().isValid() ? signature.getSignatureDate().toLocalTime().toString(options.verificationDateFormat) : QString()); + formatter.writeTableColumn("timestamp-date", signature.getTimestampDate().isValid() ? signature.getTimestampDate().toLocalTime().toString(options.verificationDateFormat) : QString()); + formatter.writeTableColumn("hash-algorithm", signature.getHashAlgorithms().join(", ").toUpper()); + formatter.writeTableColumn("handler", QString::fromLatin1(signature.getSignatureHandler())); + formatter.writeTableColumn("whole-signed", signature.hasFlag(pdf::PDFSignatureVerificationResult::Warning_Signature_NotCoveredBytes) ? PDFToolTranslationContext::tr("No") : PDFToolTranslationContext::tr("Yes")); + + formatter.endTableRow(); + ++i; + } + + formatter.endTable(); + + + if (options.verificationPrintCertificateDetails) + { + formatter.endl(); + formatter.beginHeader("details", PDFToolTranslationContext::tr("Details")); + + int i = 1; + for (const pdf::PDFSignatureVerificationResult& signature : signatures) + { + formatter.endl(); + formatter.beginHeader("signature", PDFToolTranslationContext::tr("%1 #%2").arg(getTypeName(signature)).arg(i), i); + + const pdf::PDFCertificateInfos& certificateInfos = signature.getCertificateInfos(); + const pdf::PDFCertificateInfo* certificateInfo = !certificateInfos.empty() ? &certificateInfos.front() : nullptr; + QString commonName = certificateInfo ? certificateInfo->getName(pdf::PDFCertificateInfo::CommonName) : PDFToolTranslationContext::tr("Unknown"); + formatter.writeText("common-name", PDFToolTranslationContext::tr("Signed by: %1").arg(commonName)); + formatter.writeText("certificate-status", PDFToolTranslationContext::tr("Certificate status: %1").arg(options.verificationOmitCertificateCheck ? PDFToolTranslationContext::tr("Skipped") : signature.getCertificateStatusText())); + formatter.writeText("signature-status", PDFToolTranslationContext::tr("Signature status: %1").arg(signature.getSignatureStatusText())); + formatter.writeText("signing-date", PDFToolTranslationContext::tr("Signing date: %1").arg(signature.getSignatureDate().isValid() ? signature.getSignatureDate().toLocalTime().toString(options.verificationDateFormat) : QString())); + formatter.writeText("timestamp-date", PDFToolTranslationContext::tr("Timestamp date: %1").arg(signature.getTimestampDate().isValid() ? signature.getTimestampDate().toLocalTime().toString(options.verificationDateFormat) : QString())); + formatter.writeText("hash-algorithm", PDFToolTranslationContext::tr("Hash algorithm: %1").arg(signature.getHashAlgorithms().join(", ").toUpper())); + formatter.writeText("handler", PDFToolTranslationContext::tr("Handler: %1").arg(QString::fromLatin1(signature.getSignatureHandler()))); + formatter.writeText("whole-signed", PDFToolTranslationContext::tr("Is whole document signed: %1").arg(signature.hasFlag(pdf::PDFSignatureVerificationResult::Warning_Signature_NotCoveredBytes) ? PDFToolTranslationContext::tr("No") : PDFToolTranslationContext::tr("Yes"))); + + formatter.endHeader(); + ++i; + } + + formatter.endHeader(); + } + } + else + { + formatter.writeText("no-signatures", PDFToolTranslationContext::tr("No digital signatures or timestamps found in the document.")); + } + + formatter.endDocument(); + + PDFConsole::writeText(formatter.getString()); return ExitSuccess; }