Verification tool - output text

This commit is contained in:
Jakub Melka 2020-09-28 19:08:57 +02:00
parent 5eb7472473
commit 11e218ecc9
6 changed files with 221 additions and 19 deletions

View File

@ -389,14 +389,64 @@ void PDFSignatureVerificationResult::setTimestampDate(const QDateTime& timestamp
m_timestampDate = timestampDate; 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 PDFSignature::Type PDFSignatureVerificationResult::getType() const
@ -416,7 +466,7 @@ void PDFPublicKeySignatureHandler::initializeResult(PDFSignatureVerificationResu
result.setType(m_signatureField->getSignature().getType()); result.setType(m_signatureField->getSignature().getType());
result.setSignatureFieldReference(signatureFieldReference); result.setSignatureFieldReference(signatureFieldReference);
result.setSignatureFieldQualifiedName(signatureFieldQualifiedName); result.setSignatureFieldQualifiedName(signatureFieldQualifiedName);
result.setSignatureFilter(m_signatureField->getSignature().getFilter()); result.setSignatureHandler(m_signatureField->getSignature().getSubfilter());
} }
STACK_OF(X509)* PDFPublicKeySignatureHandler::getCertificates(PKCS7* pkcs7) STACK_OF(X509)* PDFPublicKeySignatureHandler::getCertificates(PKCS7* pkcs7)
@ -1813,16 +1863,10 @@ void PDFPublicKeySignatureHandler::addSignatureDateFromSignerInfoStack(STACK_OF(
return; return;
} }
// Jakub Melka: We will get signed attribute from signer info. If it fails, // Jakub Melka: We will get signed attribute from signer info.
// then try to get unsigned attribute.
PKCS7_SIGNER_INFO* signerInfo = sk_PKCS7_SIGNER_INFO_value(signerInfoStack, 0); PKCS7_SIGNER_INFO* signerInfo = sk_PKCS7_SIGNER_INFO_value(signerInfoStack, 0);
ASN1_TYPE* attribute = PKCS7_get_signed_attribute(signerInfo, NID_pkcs9_signingTime); ASN1_TYPE* attribute = PKCS7_get_signed_attribute(signerInfo, NID_pkcs9_signingTime);
if (!attribute)
{
attribute = PKCS7_get_attribute(signerInfo, NID_pkcs9_signingTime);
}
if (!attribute) if (!attribute)
{ {
return; return;

View File

@ -281,6 +281,13 @@ public:
} }
enum class Status
{
OK,
Warning,
Error
};
enum VerificationFlag enum VerificationFlag
{ {
None = 0x00000000, ///< Used only for initialization None = 0x00000000, ///< Used only for initialization
@ -319,7 +326,10 @@ public:
Error_Signatures_Mask = Error_Signature_Invalid | Error_Signature_SourceCertificateMissing | Error_Signature_NoSignaturesFound | Error_Signatures_Mask = Error_Signature_Invalid | Error_Signature_SourceCertificateMissing | Error_Signature_NoSignaturesFound |
Error_Signature_DigestFailure | Error_Signature_DataOther | Error_Signature_DataCoveredBySignatureMissing, 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) Q_DECLARE_FLAGS(VerificationFlags, VerificationFlag)
@ -358,6 +368,8 @@ public:
bool hasWarning() const { return m_flags & Warnings_Mask; } bool hasWarning() const { return m_flags & Warnings_Mask; }
bool hasCertificateError() const { return m_flags & Error_Certificates_Mask; } bool hasCertificateError() const { return m_flags & Error_Certificates_Mask; }
bool hasSignatureError() const { return m_flags & Error_Signatures_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); } bool hasFlag(VerificationFlag flag) const { return m_flags.testFlag(flag); }
void setFlag(VerificationFlag flag, bool value) { m_flags.setFlag(flag, value); } void setFlag(VerificationFlag flag, bool value) { m_flags.setFlag(flag, value); }
@ -383,8 +395,16 @@ public:
QDateTime getTimestampDate() const; QDateTime getTimestampDate() const;
void setTimestampDate(const QDateTime& timestampDate); void setTimestampDate(const QDateTime& timestampDate);
QByteArray getSignatureFilter() const; QByteArray getSignatureHandler() const;
void setSignatureFilter(const QByteArray& signatureFilter); 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: private:
PDFSignature::Type m_type = PDFSignature::Type::Invalid; PDFSignature::Type m_type = PDFSignature::Type::Invalid;
@ -396,7 +416,7 @@ private:
QStringList m_errors; QStringList m_errors;
QStringList m_warnings; QStringList m_warnings;
QStringList m_hashAlgorithms; QStringList m_hashAlgorithms;
QByteArray m_signatureFilter; QByteArray m_signatureHandler;
PDFCertificateInfos m_certificateInfos; PDFCertificateInfos m_certificateInfos;
}; };

View File

@ -74,9 +74,13 @@ public:
inline void beginTableHeaderRow(QString name) { beginElement(Element::TableHeaderRow, name); } inline void beginTableHeaderRow(QString name) { beginElement(Element::TableHeaderRow, name); }
inline void endTableHeaderRow() { endElement(); } inline void endTableHeaderRow() { endElement(); }
inline void beginTableRow(QString name) { beginElement(Element::TableRow, name); } 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 endTableRow() { endElement(); }
inline void writeTableHeaderColumn(QString name, QString description, Qt::Alignment alignment = Qt::AlignCenter) { beginElement(Element::TableHeaderColumn, name, description, alignment); 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 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 /// Ends current line
void endl(); void endl();

View File

@ -143,8 +143,9 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser*
parser->addOption(QCommandLineOption("ver-no-user-cert", "Disable user certificate store.")); 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-sys-cert", "Disable system certificate store."));
parser->addOption(QCommandLineOption("ver-no-cert-check", "Disable certificate validation.")); 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-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)) if (optionFlags.testFlag(OpenDocument))
{ {
options.document = positionalArguments.isEmpty() ? QString() : positionalArguments.front(); 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"); 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.verificationUseUserCertificates = !parser->isSet("ver-no-user-cert");
options.verificationUseSystemCertificates = !parser->isSet("ver-no-sys-cert"); options.verificationUseSystemCertificates = !parser->isSet("ver-no-sys-cert");
options.verificationOmitCertificateCheck = parser->isSet("ver-no-cert-check"); 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"); 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; return options;

View File

@ -22,6 +22,7 @@
#include <QtGlobal> #include <QtGlobal>
#include <QString> #include <QString>
#include <QDateTime>
#include <QCoreApplication> #include <QCoreApplication>
#include <vector> #include <vector>
@ -52,6 +53,7 @@ struct PDFToolOptions
bool verificationOmitCertificateCheck = false; bool verificationOmitCertificateCheck = false;
bool verificationPrintCertificateDetails = false; bool verificationPrintCertificateDetails = false;
bool verificationIgnoreExpirationDate = false; bool verificationIgnoreExpirationDate = false;
Qt::DateFormat verificationDateFormat = Qt::DefaultLocaleShortDate;
}; };
/// Base class for all applications /// Base class for all applications

View File

@ -107,6 +107,115 @@ int PDFToolVerifySignaturesApplication::execute(const PDFToolOptions& options)
pdf::PDFForm form = pdf::PDFForm::parse(&document, document.getCatalog()->getFormObject()); pdf::PDFForm form = pdf::PDFForm::parse(&document, document.getCatalog()->getFormObject());
std::vector<pdf::PDFSignatureVerificationResult> signatures = pdf::PDFSignatureHandler::verifySignatures(form, reader.getSource(), parameters); std::vector<pdf::PDFSignatureVerificationResult> 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; return ExitSuccess;
} }