| //======================================================================== |
| // |
| // pdfsig.cc |
| // |
| // This file is licensed under the GPLv2 or later |
| // |
| // Copyright 2015 André Guerreiro <aguerreiro1985@gmail.com> |
| // Copyright 2015 André Esser <bepandre@hotmail.com> |
| // Copyright 2015, 2017-2026 Albert Astals Cid <aacid@kde.org> |
| // Copyright 2016 Markus Kilås <digital@markuspage.com> |
| // Copyright 2017, 2019 Hans-Ulrich Jüttner <huj@froreich-bioscientia.de> |
| // Copyright 2017, 2019 Adrian Johnson <ajohnson@redneon.com> |
| // Copyright 2018 Chinmoy Ranjan Pradhan <chinmoyrp65@protonmail.com> |
| // Copyright 2019 Alexey Pavlov <alexpux@gmail.com> |
| // Copyright 2019. 2023, 2024 Oliver Sander <oliver.sander@tu-dresden.de> |
| // Copyright 2019 Nelson Efrain A. Cruz <neac03@gmail.com> |
| // Copyright 2021 Georgiy Sgibnev <georgiy@sgibnev.com>. Work sponsored by lab50.net. |
| // Copyright 2021 Theofilos Intzoglou <int.teo@gmail.com> |
| // Copyright 2022 Felix Jung <fxjung@posteo.de> |
| // Copyright 2022, 2024 Erich E. Hoover <erich.e.hoover@gmail.com> |
| // Copyright 2023-2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> |
| // Copyright 2025 Blair Bonnett <blair.bonnett@gmail.com> |
| // |
| //======================================================================== |
| |
| #include "config.h" |
| #include <poppler-config.h> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstddef> |
| #include <cstring> |
| #include <ctime> |
| #include <fstream> |
| #include <random> |
| #include <filesystem> |
| #include <iomanip> |
| #include <sstream> |
| #include "parseargs.h" |
| #include "goo/gbasename.h" |
| #include "Form.h" |
| #include "Page.h" |
| #include "PDFDoc.h" |
| #include "PDFDocFactory.h" |
| #include "DateInfo.h" |
| #include "GlobalParams.h" |
| #if ENABLE_NSS3 |
| # include "NSSCryptoSignBackend.h" |
| #endif |
| #if ENABLE_GPGME |
| # include "GPGMECryptoSignBackendConfiguration.h" |
| #endif |
| #include "CryptoSignBackend.h" |
| #include "SignatureInfo.h" |
| #include "Win32Console.h" |
| #include "UTF.h" |
| #if __has_include(<libgen.h>) |
| # include <libgen.h> |
| #endif |
| |
| #ifdef HAVE_GETTEXT |
| # include <libintl.h> |
| # include <clocale> |
| # define _(STRING) gettext(STRING) |
| #else |
| # define _(STRING) STRING |
| #endif |
| |
| static const char *getReadableSigState(SignatureValidationStatus sig_vs) |
| { |
| switch (sig_vs) { |
| case SIGNATURE_VALID: |
| return "Signature is Valid."; |
| |
| case SIGNATURE_INVALID: |
| return "Signature is Invalid."; |
| |
| case SIGNATURE_DIGEST_MISMATCH: |
| return "Digest Mismatch."; |
| |
| case SIGNATURE_DECODING_ERROR: |
| return "Document isn't signed or corrupted data."; |
| |
| case SIGNATURE_NOT_VERIFIED: |
| return "Signature has not yet been verified."; |
| |
| case SIGNATURE_NOT_FOUND: |
| return "Signature not found."; |
| |
| default: |
| return "Unknown Validation Failure."; |
| } |
| } |
| |
| static const char *getReadableCertState(CertificateValidationStatus cert_vs) |
| { |
| switch (cert_vs) { |
| case CERTIFICATE_TRUSTED: |
| return "Certificate is Trusted."; |
| |
| case CERTIFICATE_UNTRUSTED_ISSUER: |
| return "Certificate issuer isn't Trusted."; |
| |
| case CERTIFICATE_UNKNOWN_ISSUER: |
| return "Certificate issuer is unknown."; |
| |
| case CERTIFICATE_REVOKED: |
| return "Certificate has been Revoked."; |
| |
| case CERTIFICATE_EXPIRED: |
| return "Certificate has Expired"; |
| |
| case CERTIFICATE_NOT_VERIFIED: |
| return "Certificate has not yet been verified."; |
| |
| default: |
| return "Unknown issue with Certificate or corrupted data."; |
| } |
| } |
| |
| static std::string getReadableTime(time_t unix_time) |
| { |
| std::stringstream stringStream; |
| const std::tm tm = *std::localtime(&unix_time); |
| stringStream << std::put_time(&tm, "%b %d %Y %H:%M:%S"); |
| return stringStream.str(); |
| } |
| |
| static std::string_view trim(std::string_view input) |
| { |
| size_t first = input.find_first_not_of(" \t"); |
| if (first == std::string_view::npos) { |
| return {}; |
| } |
| size_t last = input.find_last_not_of(" \t"); |
| return input.substr(first, last - first + 1); |
| } |
| |
| static std::vector<std::string> parseAssertSignerFile(std::string_view input) |
| { |
| std::fstream file(std::string { input }); |
| std::vector<std::string> lines; |
| std::string line; |
| while (std::getline(file, line)) { |
| if (line.empty() || line[0] == '#') { |
| continue; |
| } |
| |
| std::string_view trimmedLine = trim(line); |
| |
| if (!trimmedLine.empty()) { |
| lines.emplace_back(trimmedLine); |
| } |
| } |
| return lines; |
| } |
| |
| static std::vector<std::string> parseAssertSigner(std::string_view input) |
| { |
| if (std::filesystem::exists(input)) { |
| return parseAssertSignerFile(input); |
| } |
| return std::vector<std::string> { std::string { input } }; |
| } |
| |
| static bool dumpSignature(int sig_num, FormFieldSignature *s, const char *filename) |
| { |
| const std::vector<unsigned char> &signature = s->getSignature(); |
| if (signature.empty()) { |
| printf("Cannot dump signature #%d\n", sig_num); |
| return false; |
| } |
| |
| const std::string filenameWithExtension = GooString::format("{0:s}.sig", gbasename(filename).c_str()); |
| const std::string sig_numString = std::to_string(sig_num); |
| const std::string path = filenameWithExtension + sig_numString; |
| printf("Signature #%d (%lu bytes) => %s\n", sig_num, signature.size(), path.c_str()); |
| std::ofstream outfile(path.c_str(), std::ofstream::binary); |
| outfile.write(reinterpret_cast<const char *>(signature.data()), signature.size()); |
| outfile.close(); |
| |
| return true; |
| } |
| |
| static GooString nssDir; |
| static GooString nssPassword; |
| static char ownerPassword[33] = "\001"; |
| static char userPassword[33] = "\001"; |
| static bool printVersion = false; |
| static bool printHelp = false; |
| static bool printCryptoSignBackends = false; |
| static bool dontVerifyCert = false; |
| #if ENABLE_GPGME |
| static bool allowPgp = GpgSignatureConfiguration::arePgpSignaturesAllowed(); |
| #else |
| static bool allowPgp = false; |
| #endif |
| static bool noOCSPRevocationCheck = false; |
| static bool noAppearance = false; |
| static bool dumpSignatures = false; |
| static bool etsiCAdESdetached = false; |
| static char backendString[256] = ""; |
| static char signatureName[256] = ""; |
| static char certNickname[256] = ""; |
| static char password[256] = ""; |
| static char digestName[256] = "SHA256"; |
| static GooString reason; |
| static bool listNicknames = false; |
| static bool addNewSignature = false; |
| static bool useAIACertFetch = false; |
| static GooString newSignatureFieldName; |
| static char assertSigner[256] = ""; |
| |
| static const ArgDesc argDesc[] = { |
| { .arg = "-nssdir", .kind = argGooString, .val = &nssDir, .size = 0, .usage = "path to directory of libnss3 database" }, |
| { .arg = "-nss-pwd", .kind = argGooString, .val = &nssPassword, .size = 0, .usage = "password to access the NSS database (if any)" }, |
| { .arg = "-nocert", .kind = argFlag, .val = &dontVerifyCert, .size = 0, .usage = "don't perform certificate validation" }, |
| { .arg = "-no-ocsp", .kind = argFlag, .val = &noOCSPRevocationCheck, .size = 0, .usage = "don't perform online OCSP certificate revocation check" }, |
| { .arg = "-no-appearance", .kind = argFlag, .val = &noAppearance, .size = 0, .usage = "don't add appearance information when signing existing fields" }, |
| { .arg = "-aia", .kind = argFlag, .val = &useAIACertFetch, .size = 0, .usage = "use Authority Information Access (AIA) extension for certificate fetching" }, |
| { .arg = "-assert-signer", |
| .kind = argString, |
| .val = &assertSigner, |
| .size = 256, |
| .usage = "This option checks whether the signature covering the full document been made with the specified key. The key is either specified as a fingerprint or a file listing fingerprints. The fingerprint must " |
| "be given or listed in compact format (no colons or spaces in between). If fpr_or_file specifies a file, empty lines are ignored as well as all lines starting with a hash sign. Only available for GnuPG backend." }, |
| { .arg = "-dump", .kind = argFlag, .val = &dumpSignatures, .size = 0, .usage = "dump all signatures into current directory" }, |
| { .arg = "-add-signature", .kind = argFlag, .val = &addNewSignature, .size = 0, .usage = "adds a new signature to the document" }, |
| { .arg = "-new-signature-field-name", .kind = argGooString, .val = &newSignatureFieldName, .size = 0, .usage = "field name used for the newly added signature. A random ID will be used if empty" }, |
| { .arg = "-sign", .kind = argString, .val = &signatureName, .size = 256, .usage = "sign the document in the given signature field (by name or number)" }, |
| { .arg = "-etsi", .kind = argFlag, .val = &etsiCAdESdetached, .size = 0, .usage = "create a signature of type ETSI.CAdES.detached instead of adbe.pkcs7.detached" }, |
| { .arg = "-backend", .kind = argString, .val = &backendString, .size = 256, .usage = "use given backend for signing/verification" }, |
| { .arg = "-enable-pgp", .kind = argFlag, .val = &allowPgp, .size = 0, .usage = "Enable pgp signatures in the GnuPG backend. Only available for GnuPG backend" }, |
| { .arg = "-nick", .kind = argString, .val = &certNickname, .size = 256, .usage = "use the certificate with the given nickname/fingerprint for signing" }, |
| { .arg = "-kpw", .kind = argString, .val = &password, .size = 256, .usage = "password for the signing key (might be missing if the key isn't password protected)" }, |
| { .arg = "-digest", .kind = argString, .val = &digestName, .size = 256, .usage = "name of the digest algorithm (default: SHA256)" }, |
| { .arg = "-reason", .kind = argGooString, .val = &reason, .size = 0, .usage = "reason for signing (default: no reason given)" }, |
| { .arg = "-list-nicks", .kind = argFlag, .val = &listNicknames, .size = 0, .usage = "list available nicknames in the NSS database" }, |
| { .arg = "-list-backends", .kind = argFlag, .val = &printCryptoSignBackends, .size = 0, .usage = "print cryptographic signature backends" }, |
| { .arg = "-opw", .kind = argString, .val = ownerPassword, .size = sizeof(ownerPassword), .usage = "owner password (for encrypted files)" }, |
| { .arg = "-upw", .kind = argString, .val = userPassword, .size = sizeof(userPassword), .usage = "user password (for encrypted files)" }, |
| { .arg = "-v", .kind = argFlag, .val = &printVersion, .size = 0, .usage = "print copyright and version info" }, |
| { .arg = "-h", .kind = argFlag, .val = &printHelp, .size = 0, .usage = "print usage information" }, |
| { .arg = "-help", .kind = argFlag, .val = &printHelp, .size = 0, .usage = "print usage information" }, |
| { .arg = "--help", .kind = argFlag, .val = &printHelp, .size = 0, .usage = "print usage information" }, |
| { .arg = "-?", .kind = argFlag, .val = &printHelp, .size = 0, .usage = "print usage information" }, |
| {} |
| }; |
| |
| static void print_version_usage(bool usage) |
| { |
| fprintf(stderr, "pdfsig version %s\n", PACKAGE_VERSION); |
| fprintf(stderr, "%s\n", popplerCopyright); |
| fprintf(stderr, "%s\n", xpdfCopyright); |
| if (usage) { |
| printUsage("pdfsig", "<PDF-file> [<output-file>]", argDesc); |
| } |
| } |
| |
| static void print_backends() |
| { |
| fprintf(stderr, "pdfsig backends:\n"); |
| for (const auto &backend : CryptoSign::Factory::getAvailable()) { |
| switch (backend) { |
| case CryptoSign::Backend::Type::NSS3: |
| fprintf(stderr, "NSS"); |
| break; |
| case CryptoSign::Backend::Type::GPGME: |
| fprintf(stderr, "GPG"); |
| break; |
| } |
| if (backend == CryptoSign::Factory::getActive()) { |
| fprintf(stderr, " (active)\n"); |
| } else { |
| fprintf(stderr, "\n"); |
| } |
| } |
| } |
| |
| static std::vector<std::unique_ptr<X509CertificateInfo>> getAvailableSigningCertificates(bool *error) |
| { |
| #if ENABLE_NSS3 |
| bool wrongPassword = false; |
| bool passwordNeeded = false; |
| auto passwordCallback = [&passwordNeeded, &wrongPassword](const char *) -> char * { |
| static bool firstTime = true; |
| if (!firstTime) { |
| wrongPassword = true; |
| return nullptr; |
| } |
| firstTime = false; |
| if (!nssPassword.empty()) { |
| return strdup(nssPassword.c_str()); |
| } |
| passwordNeeded = true; |
| return nullptr; |
| }; |
| NSSSignatureConfiguration::setNSSPasswordCallback(passwordCallback); |
| #endif |
| auto backend = CryptoSign::Factory::createActive(); |
| if (!backend) { |
| *error = true; |
| printf("No backends for cryptographic signatures available"); |
| return {}; |
| } |
| std::vector<std::unique_ptr<X509CertificateInfo>> vCerts = backend->getAvailableSigningCertificates(); |
| #if ENABLE_NSS3 |
| NSSSignatureConfiguration::setNSSPasswordCallback({}); |
| if (passwordNeeded) { |
| *error = true; |
| printf("Password is needed to access the NSS database.\n"); |
| printf("\tPlease provide one with -nss-pwd.\n"); |
| return {}; |
| } |
| if (wrongPassword) { |
| *error = true; |
| printf("Password was not accepted to open the NSS database.\n"); |
| printf("\tPlease provide the correct one with -nss-pwd.\n"); |
| return {}; |
| } |
| |
| #endif |
| *error = false; |
| return vCerts; |
| } |
| |
| static std::string locationToString(KeyLocation location) |
| { |
| switch (location) { |
| case KeyLocation::Unknown: |
| return {}; |
| case KeyLocation::Other: |
| return "(Other)"; |
| case KeyLocation::Computer: |
| return "(Computer)"; |
| case KeyLocation::HardwareToken: |
| return "(Hardware Token)"; |
| } |
| return {}; |
| } |
| |
| static const char *typeToString(CertificateType type) |
| { |
| switch (type) { |
| case CertificateType::PGP: |
| return "PGP"; |
| case CertificateType::X509: |
| return "S/Mime"; |
| } |
| return ""; |
| } |
| |
| static std::string TextStringToUTF8(const std::string &str) |
| { |
| const UnicodeMap *utf8Map = globalParams->getUtf8Map(); |
| |
| std::vector<Unicode> u = TextStringToUCS4(str); |
| |
| std::string convertedStr; |
| for (auto &c : u) { |
| char buf[8]; |
| const int n = utf8Map->mapUnicode(c, buf, sizeof(buf)); |
| convertedStr.append(buf, n); |
| } |
| |
| return convertedStr; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| globalParams = std::make_unique<GlobalParams>(); |
| |
| Win32Console win32Console(&argc, &argv); |
| |
| const bool ok = parseArgs(argDesc, &argc, argv); |
| |
| if (!ok) { |
| print_version_usage(true); |
| return 99; |
| } |
| |
| if (printVersion) { |
| print_version_usage(false); |
| return 0; |
| } |
| |
| if (printHelp) { |
| print_version_usage(true); |
| return 0; |
| } |
| |
| if (strlen(backendString) > 0) { |
| auto backend = CryptoSign::Factory::typeFromString(backendString); |
| if (backend) { |
| CryptoSign::Factory::setPreferredBackend(backend.value()); |
| } else { |
| fprintf(stderr, "Unsupported backend\n"); |
| return 98; |
| } |
| } |
| |
| if (printCryptoSignBackends) { |
| print_backends(); |
| return 0; |
| } |
| |
| #if ENABLE_NSS3 |
| NSSSignatureConfiguration::setNSSDir(nssDir); |
| #endif |
| #if ENABLE_GPGME |
| GpgSignatureConfiguration::setPgpSignaturesAllowed(allowPgp); |
| #else |
| if (allowPgp) { |
| printf("Pgp support not enabled in this build.\n"); |
| return 99; |
| } |
| #endif |
| |
| if (listNicknames) { |
| bool getCertsError; |
| const std::vector<std::unique_ptr<X509CertificateInfo>> vCerts = getAvailableSigningCertificates(&getCertsError); |
| if (getCertsError) { |
| return 2; |
| } |
| if (vCerts.empty()) { |
| printf("There are no certificates available.\n"); |
| } else { |
| printf("Certificate nicknames available:\n"); |
| for (const auto &cert : vCerts) { |
| const GooString &nick = cert->getNickName(); |
| const auto location = locationToString(cert->getKeyLocation()); |
| printf("%s %s %s %s\n", nick.c_str(), (cert->isQualified() ? "(*)" : " "), location.c_str(), allowPgp ? typeToString(cert->getCertificateType()) : ""); |
| } |
| } |
| |
| return 0; |
| } |
| |
| if (argc < 2) { |
| // no filename was given |
| print_version_usage(true); |
| return 99; |
| } |
| |
| std::unique_ptr<GooString> fileName = std::make_unique<GooString>(argv[1]); |
| |
| std::optional<GooString> ownerPW, userPW; |
| if (ownerPassword[0] != '\001') { |
| ownerPW = GooString(ownerPassword); |
| } |
| if (userPassword[0] != '\001') { |
| userPW = GooString(userPassword); |
| } |
| // open PDF file |
| std::unique_ptr<PDFDoc> doc(PDFDocFactory().createPDFDoc(*fileName, ownerPW, userPW)); |
| |
| if (!doc->isOk()) { |
| return 1; |
| } |
| |
| std::optional<unsigned int> signatureNumber; |
| if (strlen(signatureName) > 0) { |
| signatureNumber = atoi(signatureName); |
| if (signatureNumber == 0) { |
| signatureNumber = {}; |
| } |
| } |
| |
| if (addNewSignature && signatureNumber.has_value()) { |
| // incompatible options |
| print_version_usage(true); |
| return 99; |
| } |
| |
| if (addNewSignature) { |
| if (argc == 2) { |
| fprintf(stderr, "An output filename for the signed document must be given\n"); |
| return 2; |
| } |
| |
| if (strlen(certNickname) == 0) { |
| printf("A nickname of the signing certificate must be given\n"); |
| return 2; |
| } |
| |
| if (etsiCAdESdetached) { |
| printf("-etsi is not supported yet with -add-signature\n"); |
| printf("Please file a bug report if this is important for you\n"); |
| return 2; |
| } |
| |
| if (digestName != std::string("SHA256")) { |
| printf("Only digest SHA256 is supported at the moment with -add-signature\n"); |
| printf("Please file a bug report if this is important for you\n"); |
| return 2; |
| } |
| |
| if (doc->getPage(1) == nullptr) { |
| printf("Error getting first page of the document.\n"); |
| return 2; |
| } |
| |
| bool getCertsError; |
| // We need to call this otherwise NSS spins forever |
| getAvailableSigningCertificates(&getCertsError); |
| if (getCertsError) { |
| return 2; |
| } |
| |
| const auto rs = std::unique_ptr<GooString>(reason.toStr().empty() ? nullptr : std::make_unique<GooString>(utf8ToUtf16WithBom(reason.toStr()))); |
| |
| if (newSignatureFieldName.empty()) { |
| // Create a random field name, it could be anything but 32 hex numbers should |
| // hopefully give us something that is not already in the document |
| std::random_device rd; |
| std::mt19937 gen(rd()); |
| std::uniform_int_distribution<> distrib(1, 15); |
| for (int i = 0; i < 32; ++i) { |
| const int value = distrib(gen); |
| newSignatureFieldName.push_back(value < 10 ? 48 + value : 65 + (value - 10)); |
| } |
| } |
| |
| // We don't provide a way to customize the UI from pdfsig for now |
| const auto failure = doc->sign(std::string { argv[2] }, std::string { certNickname }, std::string { password }, newSignatureFieldName.copy(), /*page*/ 1, |
| /*rect */ { 0, 0, 0, 0 }, /*signatureText*/ {}, /*signatureTextLeft*/ {}, /*fontSize */ 0, /*leftFontSize*/ 0, |
| /*fontColor*/ {}, /*borderWidth*/ 0, /*borderColor*/ {}, /*backgroundColor*/ {}, rs.get(), /* location */ nullptr, /* image path */ "", ownerPW, userPW); |
| return !failure.has_value() ? 0 : 3; |
| } |
| |
| const std::vector<FormFieldSignature *> signatures = doc->getSignatureFields(); |
| const unsigned int sigCount = signatures.size(); |
| |
| if (!signatureNumber.has_value() && strlen(signatureName)) { |
| for (unsigned int i = 0; i < sigCount; i++) { |
| const GooString *goo = signatures.at(i)->getCreateWidget()->getField()->getFullyQualifiedName(); |
| if (!goo) { |
| continue; |
| } |
| |
| const std::string name = TextStringToUTF8(goo->toStr()); |
| if (name == signatureName) { |
| signatureNumber = i + 1; |
| break; |
| } |
| } |
| |
| if (!signatureNumber.has_value()) { |
| fprintf(stderr, "Did not find signature field with name: %s\n", signatureName); |
| return 2; |
| } |
| } |
| |
| if (signatureNumber.has_value()) { |
| // We are signing an existing signature field |
| if (argc == 2) { |
| fprintf(stderr, "An output filename for the signed document must be given\n"); |
| return 2; |
| } |
| |
| if (signatureNumber > sigCount) { |
| printf("File '%s' does not contain a signature with number %d\n", fileName->c_str(), *signatureNumber); |
| return 2; |
| } |
| |
| if (strlen(certNickname) == 0) { |
| printf("A nickname of the signing certificate must be given\n"); |
| return 2; |
| } |
| |
| if (digestName != std::string("SHA256")) { |
| printf("Only digest SHA256 is supported at the moment\n"); |
| printf("Please file a bug report if this is important for you\n"); |
| return 2; |
| } |
| |
| bool getCertsError; |
| // We need to call this otherwise NSS spins forever |
| getAvailableSigningCertificates(&getCertsError); |
| if (getCertsError) { |
| return 2; |
| } |
| |
| FormFieldSignature *ffs = signatures.at(*signatureNumber - 1); |
| auto [sig, file_size] = ffs->getCheckedSignature(); |
| if (sig) { |
| printf("Signature number %d is already signed\n", *signatureNumber); |
| return 2; |
| } |
| if (etsiCAdESdetached) { |
| ffs->setSignatureType(CryptoSign::SignatureType::ETSI_CAdES_detached); |
| } |
| const auto rs = std::unique_ptr<GooString>(reason.toStr().empty() ? nullptr : std::make_unique<GooString>(utf8ToUtf16WithBom(reason.toStr()))); |
| if (ffs->getNumWidgets() != 1) { |
| printf("Unexpected number of widgets for the signature: %d\n", ffs->getNumWidgets()); |
| return 2; |
| } |
| #ifdef HAVE_GETTEXT |
| if (!noAppearance) { |
| setlocale(LC_ALL, ""); |
| bindtextdomain("pdfsig", CMAKE_INSTALL_LOCALEDIR); |
| textdomain("pdfsig"); |
| } |
| #endif |
| auto *fws = static_cast<FormWidgetSignature *>(ffs->getWidget(0)); |
| auto backend = CryptoSign::Factory::createActive(); |
| auto sigHandler = backend->createSigningHandler(certNickname, HashAlgorithm::Sha256); |
| std::unique_ptr<X509CertificateInfo> certInfo = sigHandler->getCertificateInfo(); |
| if (!certInfo) { |
| fprintf(stderr, "signDocument: error getting signature info\n"); |
| return 2; |
| } |
| const std::string signerName = certInfo->getSubjectInfo().commonName; |
| const std::string timestamp = timeToStringWithFormat(nullptr, "%Y.%m.%d %H:%M:%S %z"); |
| const AnnotColor blackColor(0, 0, 0); |
| const std::string signatureText(GooString::format(_("Digitally signed by {0:s}"), signerName.c_str()) + "\n" + GooString::format(_("Date: {0:s}"), timestamp.c_str())); |
| const auto gSignatureText = std::make_unique<GooString>((signatureText.empty() || noAppearance) ? "" : utf8ToUtf16WithBom(signatureText)); |
| const auto gSignatureLeftText = std::make_unique<GooString>((signerName.empty() || noAppearance) ? "" : utf8ToUtf16WithBom(signerName)); |
| const auto failure = fws->signDocumentWithAppearance(argv[2], std::string { certNickname }, std::string { password }, rs.get(), nullptr, {}, {}, *gSignatureText, *gSignatureLeftText, 0, 0, std::make_unique<AnnotColor>(blackColor)); |
| return !failure.has_value() ? 0 : 3; |
| } |
| |
| if (argc > 2) { |
| // We are not signing and more than 1 filename was given |
| print_version_usage(true); |
| return 99; |
| } |
| |
| if (sigCount >= 1) { |
| if (dumpSignatures) { |
| printf("Dumping Signatures: %u\n", sigCount); |
| for (unsigned int i = 0; i < sigCount; i++) { |
| const bool dumpingOk = dumpSignature(i, signatures.at(i), fileName->c_str()); |
| if (!dumpingOk) { |
| // for now, do nothing. We have logged a message |
| // to the user before returning false in dumpSignature |
| // and it is possible to have "holes" in the signatures |
| continue; |
| } |
| } |
| return 0; |
| } |
| printf("Digital Signature Info of: %s\n", fileName->c_str()); |
| |
| } else { |
| printf("File '%s' does not contain any signatures\n", fileName->c_str()); |
| return 2; |
| } |
| std::unordered_map<int, SignatureInfo *> signatureInfos; |
| for (unsigned int i = 0; i < sigCount; i++) { |
| // Let's start the signature check first for signatures. |
| // we can always wait for completion later |
| FormFieldSignature *ffs = signatures.at(i); |
| if (ffs->getSignatureType() == CryptoSign::SignatureType::unsigned_signature_field) { |
| continue; |
| } |
| signatureInfos[i] = ffs->validateSignatureAsync(!dontVerifyCert, false, -1 /* now */, !noOCSPRevocationCheck, useAIACertFetch, {}); |
| } |
| bool totalDocumentSigned = false; |
| bool oneSignatureInvalid = false; |
| std::string totalDocumentNick; |
| |
| for (unsigned int i = 0; i < sigCount; i++) { |
| FormFieldSignature *ffs = signatures.at(i); |
| printf("Signature #%u:\n", i + 1); |
| const GooString *goo = ffs->getCreateWidget()->getField()->getFullyQualifiedName(); |
| if (goo) { |
| const std::string name = TextStringToUTF8(goo->toStr()); |
| printf(" - Signature Field Name: %s\n", name.c_str()); |
| } |
| |
| if (ffs->getSignatureType() == CryptoSign::SignatureType::unsigned_signature_field) { |
| printf(" The signature form field is not signed.\n"); |
| continue; |
| } |
| |
| const SignatureInfo *sig_info = signatureInfos[i]; |
| CertificateValidationStatus certificateStatus = ffs->validateSignatureResult(); |
| if (sig_info->getSignatureValStatus() == SIGNATURE_DECODING_ERROR) { |
| printf(" - Decoding failed\n"); |
| oneSignatureInvalid = true; |
| continue; |
| } |
| printf(" - Signer Certificate Common Name: %s\n", sig_info->getSignerName().c_str()); |
| if (CryptoSign::Factory::getActive() == CryptoSign::Backend::Type::GPGME) { |
| printf(" - Signer fingerprint: %s\n", sig_info->getCertificateInfo()->getNickName().c_str()); |
| } |
| printf(" - Signer full Distinguished Name: %s\n", sig_info->getSubjectDN().c_str()); |
| printf(" - Signing Time: %s\n", getReadableTime(sig_info->getSigningTime()).c_str()); |
| printf(" - Signing Hash Algorithm: "); |
| switch (sig_info->getHashAlgorithm()) { |
| case HashAlgorithm::Md2: |
| printf("MD2\n"); |
| break; |
| case HashAlgorithm::Md5: |
| printf("MD5\n"); |
| break; |
| case HashAlgorithm::Sha1: |
| printf("SHA1\n"); |
| break; |
| case HashAlgorithm::Sha256: |
| printf("SHA-256\n"); |
| break; |
| case HashAlgorithm::Sha384: |
| printf("SHA-384\n"); |
| break; |
| case HashAlgorithm::Sha512: |
| printf("SHA-512\n"); |
| break; |
| case HashAlgorithm::Sha224: |
| printf("SHA-224\n"); |
| break; |
| default: |
| printf("unknown\n"); |
| } |
| printf(" - Signature Type: "); |
| switch (ffs->getSignatureType()) { |
| case CryptoSign::SignatureType::adbe_pkcs7_sha1: |
| printf("adbe.pkcs7.sha1\n"); |
| break; |
| case CryptoSign::SignatureType::adbe_pkcs7_detached: |
| printf("adbe.pkcs7.detached\n"); |
| break; |
| case CryptoSign::SignatureType::ETSI_CAdES_detached: |
| printf("ETSI.CAdES.detached\n"); |
| break; |
| case CryptoSign::SignatureType::g10c_pgp_signature_detached: |
| printf("g10c.pgp.signature.detached\n"); |
| break; |
| case CryptoSign::SignatureType::unknown_signature_type: |
| case CryptoSign::SignatureType::unsigned_signature_field: /*shouldn't happen*/ |
| printf("unknown\n"); |
| } |
| const std::vector<Goffset> ranges = ffs->getSignedRangeBounds(); |
| if (ranges.size() == 4) { |
| printf(" - Signed Ranges: [%lld - %lld], [%lld - %lld]\n", ranges[0], ranges[1], ranges[2], ranges[3]); |
| auto [signature, checked_file_size] = signatures.at(i)->getCheckedSignature(); |
| if (signature && checked_file_size == ranges[3]) { |
| if (totalDocumentSigned) { |
| printf("multiple signatures is covering entire document. Impossible"); |
| return 2; |
| } |
| if (sig_info->getCertificateInfo()) { |
| totalDocumentSigned = true; |
| totalDocumentNick = sig_info->getCertificateInfo()->getNickName().toStr(); |
| } |
| printf(" - Total document signed\n"); |
| } else { |
| printf(" - Not total document signed\n"); |
| } |
| } |
| printf(" - Signature Validation: %s\n", getReadableSigState(sig_info->getSignatureValStatus())); |
| if (sig_info->getSignatureValStatus() != SIGNATURE_VALID) { |
| oneSignatureInvalid = true; |
| continue; |
| } |
| if (dontVerifyCert) { |
| continue; |
| } |
| printf(" - Certificate Validation: %s\n", getReadableCertState(certificateStatus)); |
| } |
| |
| if (!oneSignatureInvalid) { |
| if (strlen(assertSigner) > 1 && CryptoSign::Factory::getActive() == CryptoSign::Backend::Type::GPGME) { |
| if (!totalDocumentSigned) { |
| printf(" - Assert signer: Total document not signed\n"); |
| return 1; |
| } |
| std::vector<std::string> assertKeys = parseAssertSigner(assertSigner); |
| if (std::ranges::find(assertKeys, totalDocumentNick) == std::end(assertKeys)) { |
| printf(" - Assert signer: Key not in list\n"); |
| // we do't have the key in the assert list, so error out |
| return 1; |
| } |
| } |
| return 0; |
| } |
| return 1; |
| } |