PDF4QT/PdfTool/pdftoolverifysignatures.cpp

399 lines
21 KiB
C++

// Copyright (C) 2020-2021 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT 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
// with the written consent of the copyright owner, any later version.
//
// PDF4QT 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 PDF4QT. If not, see <https://www.gnu.org/licenses/>.
#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."), options.outputCodec);
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, false);
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()), options.outputCodec);
return ErrorDocumentReading;
}
default:
Q_ASSERT(false);
return ErrorDocumentReading;
}
for (const QString& warning : reader.getWarnings())
{
PDFConsole::writeError(PDFToolTranslationContext::tr("Warning: %1").arg(warning), options.outputCodec);
}
// 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<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() ? convertDateTimeToString(signature.getSignatureDate().toLocalTime(), options.outputDateFormat) : QString());
formatter.writeTableColumn("timestamp-date", signature.getTimestampDate().isValid() ? convertDateTimeToString(signature.getTimestampDate().toLocalTime(), options.outputDateFormat) : 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 ii = 1;
for (const pdf::PDFSignatureVerificationResult& signature : signatures)
{
formatter.endl();
formatter.beginHeader("signature", PDFToolTranslationContext::tr("%1 #%2").arg(getTypeName(signature)).arg(ii), ii);
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() ? convertDateTimeToString(signature.getSignatureDate().toLocalTime(), options.outputDateFormat) : QString()));
formatter.writeText("timestamp-date", PDFToolTranslationContext::tr("Timestamp date: %1").arg(signature.getTimestampDate().isValid() ? convertDateTimeToString(signature.getTimestampDate().toLocalTime(), options.outputDateFormat) : 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")));
// Signature range
const pdf::PDFClosedIntervalSet& bytesCoveredBySignature = signature.getBytesCoveredBySignature();
formatter.writeText("byte-range", PDFToolTranslationContext::tr("Byte range covered by signature: %1").arg(bytesCoveredBySignature.toText(false)));
if (signature.hasError())
{
formatter.endl();
formatter.beginHeader("errors", PDFToolTranslationContext::tr("Errors:"));
for (const QString& error : signature.getErrors())
{
formatter.writeText("error", error);
}
formatter.endHeader();
}
if (signature.hasWarning())
{
formatter.endl();
formatter.beginHeader("warnings", PDFToolTranslationContext::tr("Warnings:"));
for (const QString& warning : signature.getWarnings())
{
formatter.writeText("warning", warning);
}
formatter.endHeader();
}
formatter.endl();
if (!options.verificationOmitCertificateCheck)
{
int certificateIndex = 1;
for (const pdf::PDFCertificateInfo& currentInfo : certificateInfos)
{
formatter.beginTable("certificate", PDFToolTranslationContext::tr("Certificate No. #%1").arg(certificateIndex++));
formatter.beginTableHeaderRow("header");
formatter.writeTableHeaderColumn("description", PDFToolTranslationContext::tr("Description"));
formatter.writeTableHeaderColumn("value", PDFToolTranslationContext::tr("Value"));
formatter.endTableHeaderRow();
auto addName = [&formatter, &currentInfo](pdf::PDFCertificateInfo::NameEntry nameEntry, QString caption)
{
QString text = currentInfo.getName(nameEntry);
if (!text.isEmpty())
{
formatter.beginTableRow("info", int(nameEntry));
formatter.writeTableColumn("description", caption);
formatter.writeTableColumn("value", text);
formatter.endTableRow();
}
};
addName(pdf::PDFCertificateInfo::CountryName, PDFToolTranslationContext::tr("Country"));
addName(pdf::PDFCertificateInfo::OrganizationName, PDFToolTranslationContext::tr("Organization"));
addName(pdf::PDFCertificateInfo::OrganizationalUnitName, PDFToolTranslationContext::tr("Org. unit"));
addName(pdf::PDFCertificateInfo::DistinguishedName, PDFToolTranslationContext::tr("Name"));
addName(pdf::PDFCertificateInfo::StateOrProvinceName, PDFToolTranslationContext::tr("State"));
addName(pdf::PDFCertificateInfo::SerialNumber, PDFToolTranslationContext::tr("Serial number"));
addName(pdf::PDFCertificateInfo::LocalityName, PDFToolTranslationContext::tr("Locality"));
addName(pdf::PDFCertificateInfo::Title, PDFToolTranslationContext::tr("Title"));
addName(pdf::PDFCertificateInfo::Surname, PDFToolTranslationContext::tr("Surname"));
addName(pdf::PDFCertificateInfo::GivenName, PDFToolTranslationContext::tr("Forename"));
addName(pdf::PDFCertificateInfo::Initials, PDFToolTranslationContext::tr("Initials"));
addName(pdf::PDFCertificateInfo::Pseudonym, PDFToolTranslationContext::tr("Pseudonym"));
addName(pdf::PDFCertificateInfo::GenerationalQualifier, PDFToolTranslationContext::tr("Qualifier"));
addName(pdf::PDFCertificateInfo::Email, PDFToolTranslationContext::tr("Email"));
QString publicKeyMethod;
switch (currentInfo.getPublicKey())
{
case pdf::PDFCertificateInfo::KeyRSA:
publicKeyMethod = PDFToolTranslationContext::tr("RSA method, %1-bit key").arg(currentInfo.getKeySize());
break;
case pdf::PDFCertificateInfo::KeyDSA:
publicKeyMethod = PDFToolTranslationContext::tr("DSA method, %1-bit key").arg(currentInfo.getKeySize());
break;
case pdf::PDFCertificateInfo::KeyEC:
publicKeyMethod = PDFToolTranslationContext::tr("EC method, %1-bit key").arg(currentInfo.getKeySize());
break;
case pdf::PDFCertificateInfo::KeyDH:
publicKeyMethod = PDFToolTranslationContext::tr("DH method, %1-bit key").arg(currentInfo.getKeySize());
break;
case pdf::PDFCertificateInfo::KeyUnknown:
publicKeyMethod = PDFToolTranslationContext::tr("Unknown method, %1-bit key").arg(currentInfo.getKeySize());
break;
default:
Q_ASSERT(false);
break;
}
formatter.beginTableRow("protection-method");
formatter.writeTableColumn("description", PDFToolTranslationContext::tr("Encryption method"));
formatter.writeTableColumn("value", publicKeyMethod);
formatter.endTableRow();
QDateTime notValidBefore = currentInfo.getNotValidBefore().toLocalTime();
QDateTime notValidAfter = currentInfo.getNotValidAfter().toLocalTime();
if (notValidBefore.isValid())
{
formatter.beginTableRow("valid-from");
formatter.writeTableColumn("description", PDFToolTranslationContext::tr("Valid from"));
formatter.writeTableColumn("value", convertDateTimeToString(notValidBefore, options.outputDateFormat));
formatter.endTableRow();
}
if (notValidAfter.isValid())
{
formatter.beginTableRow("valid-to");
formatter.writeTableColumn("description", PDFToolTranslationContext::tr("Valid to"));
formatter.writeTableColumn("value", convertDateTimeToString(notValidAfter, options.outputDateFormat));
formatter.endTableRow();
}
pdf::PDFCertificateInfo::KeyUsageFlags keyUsageFlags = currentInfo.getKeyUsage();
QStringList keyUsages;
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageDigitalSignature))
{
keyUsages << PDFToolTranslationContext::tr("Digital signatures");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageNonRepudiation))
{
keyUsages << PDFToolTranslationContext::tr("Non-repudiation");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageKeyEncipherment))
{
keyUsages << PDFToolTranslationContext::tr("Key encipherement");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageDataEncipherment))
{
keyUsages << PDFToolTranslationContext::tr("Application data encipherement");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageAgreement))
{
keyUsages << PDFToolTranslationContext::tr("Key agreement");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageCertSign))
{
keyUsages << PDFToolTranslationContext::tr("Verify signatures on certificates");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageCrlSign))
{
keyUsages << PDFToolTranslationContext::tr("Verify signatures on revocation information");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageEncipherOnly))
{
keyUsages << PDFToolTranslationContext::tr("Encipher data during key agreement");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageDecipherOnly))
{
keyUsages << PDFToolTranslationContext::tr("Decipher data during key agreement");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageExtended_TIMESTAMP))
{
keyUsages << PDFToolTranslationContext::tr("Trusted timestamping");
}
formatter.beginTableRow("key-usages");
formatter.writeTableColumn("description", PDFToolTranslationContext::tr("Key usage"));
formatter.writeTableColumn("value", keyUsages.join(", "));
formatter.endTableRow();
formatter.endTable();
formatter.endl();
}
}
formatter.endHeader();
++ii;
}
formatter.endHeader();
}
}
else
{
formatter.writeText("no-signatures", PDFToolTranslationContext::tr("No digital signatures or timestamps found in the document."));
}
formatter.endDocument();
PDFConsole::writeText(formatter.getString(), options.outputCodec);
return ExitSuccess;
}
PDFToolAbstractApplication::Options PDFToolVerifySignaturesApplication::getOptionsFlags() const
{
return PDFToolAbstractApplication::ConsoleFormat | PDFToolAbstractApplication::OpenDocument | PDFToolAbstractApplication::SignatureVerification | PDFToolAbstractApplication::DateFormat;
}
} // namespace pdftool