From 4efd2f9f8175cb5d448809b56f67524df6e220aa Mon Sep 17 00:00:00 2001 From: Sune Vuorela Date: Tue, 11 Apr 2023 13:39:36 +0200 Subject: Cryptosign backend using gpgme (gpgsm) for all your signature needs --- CMakeLists.txt | 9 +- config.h.cmake | 3 + poppler/CryptoSignBackend.cc | 18 ++ poppler/CryptoSignBackend.h | 3 +- poppler/GPGMECryptoSignBackend.cc | 387 +++++++++++++++++++++++++++++++++++ poppler/GPGMECryptoSignBackend.h | 57 ++++++ qt5/tests/check_signature_basics.cpp | 52 ++++- qt6/tests/check_signature_basics.cpp | 50 ++++- 8 files changed, 557 insertions(+), 22 deletions(-) create mode 100644 poppler/GPGMECryptoSignBackend.cc create mode 100644 poppler/GPGMECryptoSignBackend.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a7438542..8b8cd554 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -377,9 +377,6 @@ if (_signing_backends_count GREATER 0) # This means that the order we append them to the list is significant list(GET SIGNATURE_BACKENDS 0 DEFAULT_SIGNATURE_BACKEND) endif() - if (NOT DEFAULT_SIGNATURE_BACKEND IN_LIST SIGNATURE_BACKENDS) - message(FATAL_ERROR "default signature backend must be one of ${SIGNATURE_BACKENDS}, was ${DEFAULT_SIGNATURE_BACKEND}") - endif() set(ENABLE_SIGNATURES ON) endif() if (NOT DEFAULT_SIGNATURE_BACKEND) @@ -559,6 +556,12 @@ if (ENABLE_NSS3) ) set(poppler_LIBS ${poppler_LIBS} PkgConfig::NSS3) endif() +if (ENABLE_GPGME) + set(poppler_SRCS ${poppler_SRCS} + poppler/GPGMECryptoSignBackend.cc + ) + set(poppler_LIBS ${poppler_LIBS} Gpgmepp) +endif() if (OpenJPEG_FOUND) set(poppler_SRCS ${poppler_SRCS} poppler/JPEG2000Stream.cc diff --git a/config.h.cmake b/config.h.cmake index 4d6ef551..10abff6e 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -27,6 +27,9 @@ /* Build against libnss3 for digital signature validation */ #cmakedefine ENABLE_NSS3 1 +/* Build against libgpgme for digital signature validation */ +#cmakedefine ENABLE_GPGME 1 + /* Signatures enabled */ #cmakedefine ENABLE_SIGNATURES 1 diff --git a/poppler/CryptoSignBackend.cc b/poppler/CryptoSignBackend.cc index 1bd28363..324143db 100644 --- a/poppler/CryptoSignBackend.cc +++ b/poppler/CryptoSignBackend.cc @@ -8,6 +8,9 @@ //======================================================================== #include "CryptoSignBackend.h" #include "config.h" +#ifdef ENABLE_GPGME +# include "GPGMECryptoSignBackend.h" +#endif #ifdef ENABLE_NSS3 # include "SignatureHandler.h" #endif @@ -31,6 +34,9 @@ std::optional Factory::typeFromString(std::string_vie if (string.empty()) { return std::nullopt; } + if ("GPG" == string) { + return Backend::Type::GPGME; + } if ("NSS" == string) { return Backend::Type::NSS3; } @@ -58,6 +64,11 @@ static std::vector createAvailableBackends() std::vector backends; #ifdef ENABLE_NSS3 backends.push_back(Backend::Type::NSS3); +#endif +#ifdef ENABLE_GPGME + if (GpgSignatureBackend::hasSufficientVersion()) { + backends.push_back(Backend::Type::GPGME); + } #endif return backends; } @@ -83,6 +94,13 @@ std::unique_ptr CryptoSign::Factory::create(Backend::Type b #else return nullptr; #endif + case Backend::Type::GPGME: { +#ifdef ENABLE_GPGME + return std::make_unique(); +#else + return nullptr; +#endif + } } return nullptr; } diff --git a/poppler/CryptoSignBackend.h b/poppler/CryptoSignBackend.h index a763e27c..9c79a24d 100644 --- a/poppler/CryptoSignBackend.h +++ b/poppler/CryptoSignBackend.h @@ -63,7 +63,8 @@ class Backend public: enum class Type { - NSS3 + NSS3, + GPGME }; virtual std::unique_ptr createVerificationHandler(std::vector &&pkcs7) = 0; virtual std::unique_ptr createSigningHandler(const std::string &certID, HashAlgorithm digestAlgTag) = 0; diff --git a/poppler/GPGMECryptoSignBackend.cc b/poppler/GPGMECryptoSignBackend.cc new file mode 100644 index 00000000..1b7f1d5c --- /dev/null +++ b/poppler/GPGMECryptoSignBackend.cc @@ -0,0 +1,387 @@ +//======================================================================== +// +// GPGMECryptoSignBackend.cc +// +// This file is licensed under the GPLv2 or later +// +// Copyright 2023 g10 Code GmbH, Author: Sune Stolborg Vuorela +//======================================================================== +#include "GPGMECryptoSignBackend.h" +#include "DistinguishedNameParser.h" +#include +#include +#include +#include +#include + +bool GpgSignatureBackend::hasSufficientVersion() +{ + // gpg 2.4.0 does not support padded signatures. + // Most gpg signatures are padded. This is fixed for 2.4.1 + // gpg 2.4.0 does not support generating signatures + // with definite lengths. This is also fixed for 2.4.1. + return GpgME::engineInfo(GpgME::GpgSMEngine).engineVersion() > "2.4.0"; +} + +/// GPGME helper methods + +// gpgme++'s string-like functions returns char pointers that can be nullptr +// Creating std::string from nullptr is, depending on c++ standards versions +// either undefined behavior or illegal, so we need a helper. + +static std::string fromCharPtr(const char *data) +{ + if (data) { + return std::string { data }; + } + return {}; +} + +static bool isSuccess(const GpgME::Error &err) +{ + if (err) { + return false; + } + if (err.isCanceled()) { + return false; + } + return true; +} + +template +static bool isValidResult(const Result &result) +{ + return isSuccess(result.error()); +} + +template +static bool hasValidResult(const std::optional &result) +{ + if (!result) { + return false; + } + return isValidResult(result.value()); +} + +static std::optional getSignature(const GpgME::VerificationResult &result, size_t signatureNumber) +{ + if (result.numSignatures() > signatureNumber) { + return result.signature(signatureNumber); + } + return std::nullopt; +} + +static X509CertificateInfo::Validity getValidityFromSubkey(const GpgME::Subkey &key) +{ + X509CertificateInfo::Validity validity; + validity.notBefore = key.creationTime(); + validity.notAfter = key.expirationTime(); + return validity; +} + +static X509CertificateInfo::EntityInfo getEntityInfoFromKey(std::string_view dnString) +{ + const auto dn = DN::parseString(dnString); + X509CertificateInfo::EntityInfo info; + info.commonName = DN::FindFirstValue(dn, "CN").value_or(std::string {}); + info.organization = DN::FindFirstValue(dn, "O").value_or(std::string {}); + info.email = DN::FindFirstValue(dn, "EMAIL").value_or(std::string {}); + info.distinguishedName = std::string { dnString }; + return info; +} + +static std::unique_ptr getCertificateInfoFromKey(const GpgME::Key &key) +{ + auto certificateInfo = std::make_unique(); + certificateInfo->setIssuerInfo(getEntityInfoFromKey(fromCharPtr(key.issuerName()))); + certificateInfo->setSerialNumber(GooString { DN::detail::parseHexString(fromCharPtr(key.issuerSerial())).value_or("") }); + auto subjectInfo = getEntityInfoFromKey(fromCharPtr(key.userID(0).id())); + if (subjectInfo.email.empty()) { + subjectInfo.email = fromCharPtr(key.userID(1).email()); + } + certificateInfo->setSubjectInfo(std::move(subjectInfo)); + certificateInfo->setValidity(getValidityFromSubkey(key.subkey(0))); + certificateInfo->setNickName(GooString(fromCharPtr(key.primaryFingerprint()))); + X509CertificateInfo::PublicKeyInfo pkInfo; + pkInfo.publicKeyStrength = key.subkey(0).length(); + switch (key.subkey(0).publicKeyAlgorithm()) { + case GpgME::Subkey::AlgoDSA: + pkInfo.publicKeyType = DSAKEY; + break; + case GpgME::Subkey::AlgoECC: + case GpgME::Subkey::AlgoECDH: + case GpgME::Subkey::AlgoECDSA: + case GpgME::Subkey::AlgoEDDSA: + pkInfo.publicKeyType = ECKEY; + break; + case GpgME::Subkey::AlgoRSA: + case GpgME::Subkey::AlgoRSA_E: + case GpgME::Subkey::AlgoRSA_S: + pkInfo.publicKeyType = RSAKEY; + break; + case GpgME::Subkey::AlgoELG: + case GpgME::Subkey::AlgoELG_E: + case GpgME::Subkey::AlgoMax: + case GpgME::Subkey::AlgoUnknown: + pkInfo.publicKeyType = OTHERKEY; + } + { + auto ctx = GpgME::Context::create(GpgME::CMS); + GpgME::Data pubkeydata; + const auto err = ctx->exportPublicKeys(key.primaryFingerprint(), pubkeydata); + if (isSuccess(err)) { + certificateInfo->setCertificateDER(GooString(pubkeydata.toString())); + } + } + + certificateInfo->setPublicKeyInfo(std::move(pkInfo)); + + int kue = 0; + // this block is kind of a hack. GPGSM collapses multiple + // into one bit, so trying to match it back can never be good + if (key.canSign()) { + kue |= KU_NON_REPUDIATION; + kue |= KU_DIGITAL_SIGNATURE; + } + if (key.canEncrypt()) { + kue |= KU_KEY_ENCIPHERMENT; + kue |= KU_DATA_ENCIPHERMENT; + } + if (key.canCertify()) { + kue |= KU_KEY_CERT_SIGN; + } + certificateInfo->setKeyUsageExtensions(kue); + + return certificateInfo; +} + +/// implementation of header file + +GpgSignatureBackend::GpgSignatureBackend() +{ + GpgME::initializeLibrary(); +} + +std::unique_ptr GpgSignatureBackend::createSigningHandler(const std::string &certID, HashAlgorithm digestAlgTag) +{ + return std::make_unique(certID, digestAlgTag); +} + +std::unique_ptr GpgSignatureBackend::createVerificationHandler(std::vector &&pkcs7) +{ + return std::make_unique(std::move(pkcs7)); +} + +std::vector> GpgSignatureBackend::getAvailableSigningCertificates() +{ + std::vector> certificates; + const auto context = GpgME::Context::create(GpgME::CMS); + auto err = context->startKeyListing(static_cast(nullptr), true /*secretOnly*/); + while (isSuccess(err)) { + const auto key = context->nextKey(err); + if (!key.isNull() && isSuccess(err)) { + if (key.isBad()) { + continue; + } + if (!key.canSign()) { + continue; + } + certificates.push_back(getCertificateInfoFromKey(key)); + } else { + break; + } + } + return certificates; +} + +GpgSignatureCreation::GpgSignatureCreation(const std::string &certId, HashAlgorithm digestAlgTag) : gpgContext { GpgME::Context::create(GpgME::CMS) } +{ + GpgME::Error error; + const auto signingKey = gpgContext->key(certId.c_str(), error, true); + if (isSuccess(error)) { + gpgContext->addSigningKey(signingKey); + key = signingKey; + } +} + +void GpgSignatureCreation::addData(unsigned char *dataBlock, int dataLen) +{ + gpgData.write(dataBlock, dataLen); +} +std::optional GpgSignatureCreation::signDetached(const std::string &password) +{ + if (!key) { + return {}; + } + gpgData.rewind(); + GpgME::Data signatureData; + const auto signingResult = gpgContext->sign(gpgData, signatureData, GpgME::SignatureMode::Detached); + if (!isValidResult(signingResult)) { + return {}; + } + + const auto signatureString = signatureData.toString(); + return GooString(std::move(signatureString)); +} + +std::unique_ptr GpgSignatureCreation::getCertificateInfo() const +{ + if (!key) { + return nullptr; + } + return getCertificateInfoFromKey(*key); +} + +GpgSignatureVerification::GpgSignatureVerification(const std::vector &p7data) : gpgContext { GpgME::Context::create(GpgME::CMS) }, signatureData(reinterpret_cast(p7data.data()), p7data.size()) +{ + gpgContext->setOffline(true); + signatureData.setEncoding(GpgME::Data::BinaryEncoding); +} + +void GpgSignatureVerification::addData(unsigned char *dataBlock, int dataLen) +{ + signedData.write(dataBlock, dataLen); +} + +std::unique_ptr GpgSignatureVerification::getCertificateInfo() const +{ + if (!hasValidResult(gpgResult)) { + return nullptr; + } + auto signature = getSignature(gpgResult.value(), 0); + if (!signature) { + return nullptr; + } + auto gpgInfo = getCertificateInfoFromKey(signature->key(true, false)); + return gpgInfo; +} + +HashAlgorithm GpgSignatureVerification::getHashAlgorithm() const +{ + if (gpgResult) { + const auto signature = getSignature(gpgResult.value(), 0); + if (!signature) { + return HashAlgorithm::Unknown; + } + switch (signature->hashAlgorithm()) { + case GPGME_MD_MD5: + return HashAlgorithm::Md5; + case GPGME_MD_SHA1: + return HashAlgorithm::Sha1; + case GPGME_MD_MD2: + return HashAlgorithm::Md2; + case GPGME_MD_SHA256: + return HashAlgorithm::Sha256; + case GPGME_MD_SHA384: + return HashAlgorithm::Sha384; + case GPGME_MD_SHA512: + return HashAlgorithm::Sha512; + case GPGME_MD_SHA224: + return HashAlgorithm::Sha224; + case GPGME_MD_NONE: + case GPGME_MD_RMD160: + case GPGME_MD_TIGER: + case GPGME_MD_HAVAL: + case GPGME_MD_MD4: + case GPGME_MD_CRC32: + case GPGME_MD_CRC32_RFC1510: + case GPGME_MD_CRC24_RFC2440: + default: + return HashAlgorithm::Unknown; + } + } + return HashAlgorithm::Unknown; +} + +std::string GpgSignatureVerification::getSignerName() const +{ + if (!hasValidResult(gpgResult)) { + return {}; + } + + const auto signature = getSignature(gpgResult.value(), 0); + if (!signature) { + return {}; + } + const auto dn = DN::parseString(fromCharPtr(signature->key(true, false).userID(0).id())); + return DN::FindFirstValue(dn, "CN").value_or(""); +} + +std::string GpgSignatureVerification::getSignerSubjectDN() const +{ + if (!hasValidResult(gpgResult)) { + return {}; + } + const auto signature = getSignature(gpgResult.value(), 0); + if (!signature) { + return {}; + } + return fromCharPtr(signature->key(true, false).userID(0).id()); +} + +std::chrono::system_clock::time_point GpgSignatureVerification::getSigningTime() const +{ + if (!hasValidResult(gpgResult)) { + return {}; + } + const auto signature = getSignature(gpgResult.value(), 0); + if (!signature) { + return {}; + } + return std::chrono::system_clock::from_time_t(signature->creationTime()); +} + +CertificateValidationStatus GpgSignatureVerification::validateCertificate(std::chrono::system_clock::time_point validation_time, bool ocspRevocationCheck, bool useAIACertFetch) +{ + if (!gpgResult) { + return CERTIFICATE_NOT_VERIFIED; + } + if (gpgResult->error()) { + return CERTIFICATE_GENERIC_ERROR; + } + const auto signature = getSignature(gpgResult.value(), 0); + if (!signature) { + return CERTIFICATE_GENERIC_ERROR; + } + const auto offline = gpgContext->offline(); + gpgContext->setOffline(!ocspRevocationCheck); + const auto key = signature->key(true, true); + gpgContext->setOffline(offline); + if (key.isExpired()) { + return CERTIFICATE_EXPIRED; + } + if (key.isRevoked()) { + return CERTIFICATE_REVOKED; + } + if (key.isBad()) { + return CERTIFICATE_NOT_VERIFIED; + } + return CERTIFICATE_TRUSTED; +} + +SignatureValidationStatus GpgSignatureVerification::validateSignature() +{ + signedData.rewind(); + const auto result = gpgContext->verifyDetachedSignature(signatureData, signedData); + gpgResult = result; + + if (!isValidResult(result)) { + return SIGNATURE_DECODING_ERROR; + } + const auto signature = getSignature(result, 0); + if (!signature) { + return SIGNATURE_DECODING_ERROR; + } + // Ensure key is actually available + signature->key(true, true); + const auto summary = signature->summary(); + + using Summary = GpgME::Signature::Summary; + if (summary & Summary::Red) { + return SIGNATURE_INVALID; + } + if (summary & Summary::Green || summary & Summary::Valid) { + return SIGNATURE_VALID; + } + return SIGNATURE_GENERIC_ERROR; +} diff --git a/poppler/GPGMECryptoSignBackend.h b/poppler/GPGMECryptoSignBackend.h new file mode 100644 index 00000000..776dfc23 --- /dev/null +++ b/poppler/GPGMECryptoSignBackend.h @@ -0,0 +1,57 @@ +//======================================================================== +// +// GPGMECryptoSignBackend.h +// +// This file is licensed under the GPLv2 or later +// +// Copyright 2023 g10 Code GmbH, Author: Sune Stolborg Vuorela +//======================================================================== +#include "CryptoSignBackend.h" + +#include +#include +#include + +class GpgSignatureBackend : public CryptoSign::Backend +{ +public: + GpgSignatureBackend(); + std::unique_ptr createVerificationHandler(std::vector &&pkcs7) final; + std::unique_ptr createSigningHandler(const std::string &certID, HashAlgorithm digestAlgTag) final; + std::vector> getAvailableSigningCertificates() final; + static bool hasSufficientVersion(); +}; + +class GpgSignatureCreation : public CryptoSign::SigningInterface +{ +public: + GpgSignatureCreation(const std::string &certId, HashAlgorithm digestAlgTag); + void addData(unsigned char *dataBlock, int dataLen) final; + std::unique_ptr getCertificateInfo() const final; + std::optional signDetached(const std::string &password) final; + +private: + std::unique_ptr gpgContext; + GpgME::Data gpgData; + std::optional key; +}; + +class GpgSignatureVerification : public CryptoSign::VerificationInterface +{ +public: + explicit GpgSignatureVerification(const std::vector &pkcs7data); + SignatureValidationStatus validateSignature() final; + void addData(unsigned char *dataBlock, int dataLen) final; + std::chrono::system_clock::time_point getSigningTime() const final; + std::string getSignerName() const final; + std::string getSignerSubjectDN() const final; + HashAlgorithm getHashAlgorithm() const final; + CertificateValidationStatus validateCertificate(std::chrono::system_clock::time_point validation_time, bool ocspRevocationCheck, bool useAIACertFetch) final; + std::unique_ptr getCertificateInfo() const final; + +private: + std::unique_ptr gpgContext; + GpgME::Data signatureData; + GpgME::Data signedData; + std::optional gpgResult; +}; diff --git a/qt5/tests/check_signature_basics.cpp b/qt5/tests/check_signature_basics.cpp index 7cdb1c37..94aad2f6 100644 --- a/qt5/tests/check_signature_basics.cpp +++ b/qt5/tests/check_signature_basics.cpp @@ -16,6 +16,7 @@ #include "PDFDoc.h" #include "GlobalParams.h" #include "SignatureInfo.h" +#include "CryptoSignBackend.h" #include "config.h" class TestSignatureBasics : public QObject @@ -27,7 +28,9 @@ public: private: std::unique_ptr doc; private Q_SLOTS: - void initTestCase(); + void init(); + void initTestCase_data(); + void initTestCase() { } void cleanupTestCase(); void testSignatureCount(); void testSignatureSizes(); @@ -35,13 +38,34 @@ private Q_SLOTS: void testSignedRanges(); }; -void TestSignatureBasics::initTestCase() +Q_DECLARE_METATYPE(CryptoSign::Backend::Type); + +void TestSignatureBasics::init() { +#ifdef ENABLE_SIGNATURES + QFETCH_GLOBAL(CryptoSign::Backend::Type, backend); + CryptoSign::Factory::setPreferredBackend(backend); + QCOMPARE(CryptoSign::Factory::getActive(), backend); +#endif + globalParams = std::make_unique(); doc = std::make_unique(std::make_unique(TESTDATADIR "/unittestcases/pdf-signature-sample-2sigs.pdf")); QVERIFY(doc); QVERIFY(doc->isOk()); } + +void TestSignatureBasics::initTestCase_data() +{ + QTest::addColumn("backend"); + +#ifdef ENABLE_NSS3 + QTest::newRow("nss") << CryptoSign::Backend::Type::NSS3; +#endif +#ifdef ENABLE_GPGME + QTest::newRow("gpg") << CryptoSign::Backend::Type::GPGME; +#endif +} + void TestSignatureBasics::cleanupTestCase() { globalParams.reset(); @@ -62,11 +86,13 @@ void TestSignatureBasics::testSignatureCount() void TestSignatureBasics::testSignatureSizes() { auto signatureFields = doc->getSignatureFields(); - // Note for later. Unpadding a signature on a command line with openssl can - // be done just by rewriting after using e.g. pdfsig -dump to extract them - // openssl pkcs7 -inform der -in pdf-signature-sample-2sigs.pdf.sig0 -outform der -out pdf-signature-sample-2sigs.pdf.sig0.unpadded - QCOMPARE(signatureFields[0]->getSignature()->getLength(), 10230); // This is technically wrong, because the signatures in this document has been padded. The correct size is 2340 - QCOMPARE(signatureFields[1]->getSignature()->getLength(), 10196); // This is technically wrong, because the signatures in this document has been padded. The correct size is 2340 + // These are not the actual signature lengths, but rather + // the length of the signature field, which is likely + // a padded field. At least the pdf specification suggest to pad + // the field. + // Poppler before 23.04 did not have a padded field, later versions do. + QCOMPARE(signatureFields[0]->getSignature()->getLength(), 10230); // Signature data size is 2340 + QCOMPARE(signatureFields[1]->getSignature()->getLength(), 10196); // Signature data size is 2340 } void TestSignatureBasics::testSignerInfo() @@ -75,9 +101,10 @@ void TestSignatureBasics::testSignerInfo() QCOMPARE(signatureFields[0]->getCreateWidget()->getField()->getFullyQualifiedName()->toStr(), std::string { "P2.AnA_Signature0_B_" }); QCOMPARE(signatureFields[0]->getSignatureType(), ETSI_CAdES_detached); auto siginfo0 = signatureFields[0]->validateSignature(false, false, -1 /* now */, false, false); -#ifdef ENABLE_NSS3 +#ifdef ENABLE_SIGNATURES QCOMPARE(siginfo0->getSignerName(), std::string { "Koch, Werner" }); QCOMPARE(siginfo0->getHashAlgorithm(), HashAlgorithm::Sha256); + QCOMPARE(siginfo0->getCertificateInfo()->getPublicKeyInfo().publicKeyStrength, 2048 / 8); #else QCOMPARE(siginfo0->getSignerName(), std::string {}); QCOMPARE(siginfo0->getHashAlgorithm(), HashAlgorithm::Unknown); @@ -87,9 +114,16 @@ void TestSignatureBasics::testSignerInfo() QCOMPARE(signatureFields[1]->getCreateWidget()->getField()->getFullyQualifiedName()->toStr(), std::string { "P2.AnA_Signature1_B_" }); QCOMPARE(signatureFields[1]->getSignatureType(), ETSI_CAdES_detached); auto siginfo1 = signatureFields[1]->validateSignature(false, false, -1 /* now */, false, false); -#ifdef ENABLE_NSS3 +#ifdef ENABLE_SIGNATURES QCOMPARE(siginfo1->getSignerName(), std::string { "Koch, Werner" }); QCOMPARE(siginfo1->getHashAlgorithm(), HashAlgorithm::Sha256); + QFETCH_GLOBAL(CryptoSign::Backend::Type, backend); + if (backend == CryptoSign::Backend::Type::GPGME) { + QCOMPARE(siginfo1->getCertificateInfo()->getPublicKeyInfo().publicKeyStrength, 2048 / 8); + } else if (backend == CryptoSign::Backend::Type::NSS3) { + // Not fully sure why it is zero here, but it seems to be. + QCOMPARE(siginfo1->getCertificateInfo()->getPublicKeyInfo().publicKeyStrength, 0); + } #else QCOMPARE(siginfo1->getSignerName(), std::string {}); QCOMPARE(siginfo1->getHashAlgorithm(), HashAlgorithm::Unknown); diff --git a/qt6/tests/check_signature_basics.cpp b/qt6/tests/check_signature_basics.cpp index 7cdb1c37..25589e93 100644 --- a/qt6/tests/check_signature_basics.cpp +++ b/qt6/tests/check_signature_basics.cpp @@ -16,6 +16,7 @@ #include "PDFDoc.h" #include "GlobalParams.h" #include "SignatureInfo.h" +#include "CryptoSignBackend.h" #include "config.h" class TestSignatureBasics : public QObject @@ -27,7 +28,9 @@ public: private: std::unique_ptr doc; private Q_SLOTS: - void initTestCase(); + void init(); + void initTestCase_data(); + void initTestCase() { } void cleanupTestCase(); void testSignatureCount(); void testSignatureSizes(); @@ -35,13 +38,32 @@ private Q_SLOTS: void testSignedRanges(); }; -void TestSignatureBasics::initTestCase() +void TestSignatureBasics::init() { +#ifdef ENABLE_SIGNATURES + QFETCH_GLOBAL(CryptoSign::Backend::Type, backend); + CryptoSign::Factory::setPreferredBackend(backend); + QCOMPARE(CryptoSign::Factory::getActive(), backend); +#endif + globalParams = std::make_unique(); doc = std::make_unique(std::make_unique(TESTDATADIR "/unittestcases/pdf-signature-sample-2sigs.pdf")); QVERIFY(doc); QVERIFY(doc->isOk()); } + +void TestSignatureBasics::initTestCase_data() +{ + QTest::addColumn("backend"); + +#ifdef ENABLE_NSS3 + QTest::newRow("nss") << CryptoSign::Backend::Type::NSS3; +#endif +#ifdef ENABLE_GPGME + QTest::newRow("gpg") << CryptoSign::Backend::Type::GPGME; +#endif +} + void TestSignatureBasics::cleanupTestCase() { globalParams.reset(); @@ -62,11 +84,13 @@ void TestSignatureBasics::testSignatureCount() void TestSignatureBasics::testSignatureSizes() { auto signatureFields = doc->getSignatureFields(); - // Note for later. Unpadding a signature on a command line with openssl can - // be done just by rewriting after using e.g. pdfsig -dump to extract them - // openssl pkcs7 -inform der -in pdf-signature-sample-2sigs.pdf.sig0 -outform der -out pdf-signature-sample-2sigs.pdf.sig0.unpadded - QCOMPARE(signatureFields[0]->getSignature()->getLength(), 10230); // This is technically wrong, because the signatures in this document has been padded. The correct size is 2340 - QCOMPARE(signatureFields[1]->getSignature()->getLength(), 10196); // This is technically wrong, because the signatures in this document has been padded. The correct size is 2340 + // These are not the actual signature lengths, but rather + // the length of the signature field, which is likely + // a padded field. At least the pdf specification suggest to pad + // the field. + // Poppler before 23.04 did not have a padded field, later versions do. + QCOMPARE(signatureFields[0]->getSignature()->getLength(), 10230); // Signature data size is 2340 + QCOMPARE(signatureFields[1]->getSignature()->getLength(), 10196); // Signature data size is 2340 } void TestSignatureBasics::testSignerInfo() @@ -75,9 +99,10 @@ void TestSignatureBasics::testSignerInfo() QCOMPARE(signatureFields[0]->getCreateWidget()->getField()->getFullyQualifiedName()->toStr(), std::string { "P2.AnA_Signature0_B_" }); QCOMPARE(signatureFields[0]->getSignatureType(), ETSI_CAdES_detached); auto siginfo0 = signatureFields[0]->validateSignature(false, false, -1 /* now */, false, false); -#ifdef ENABLE_NSS3 +#ifdef ENABLE_SIGNATURES QCOMPARE(siginfo0->getSignerName(), std::string { "Koch, Werner" }); QCOMPARE(siginfo0->getHashAlgorithm(), HashAlgorithm::Sha256); + QCOMPARE(siginfo0->getCertificateInfo()->getPublicKeyInfo().publicKeyStrength, 2048 / 8); #else QCOMPARE(siginfo0->getSignerName(), std::string {}); QCOMPARE(siginfo0->getHashAlgorithm(), HashAlgorithm::Unknown); @@ -87,9 +112,16 @@ void TestSignatureBasics::testSignerInfo() QCOMPARE(signatureFields[1]->getCreateWidget()->getField()->getFullyQualifiedName()->toStr(), std::string { "P2.AnA_Signature1_B_" }); QCOMPARE(signatureFields[1]->getSignatureType(), ETSI_CAdES_detached); auto siginfo1 = signatureFields[1]->validateSignature(false, false, -1 /* now */, false, false); -#ifdef ENABLE_NSS3 +#ifdef ENABLE_SIGNATURES QCOMPARE(siginfo1->getSignerName(), std::string { "Koch, Werner" }); QCOMPARE(siginfo1->getHashAlgorithm(), HashAlgorithm::Sha256); + QFETCH_GLOBAL(CryptoSign::Backend::Type, backend); + if (backend == CryptoSign::Backend::Type::GPGME) { + QCOMPARE(siginfo1->getCertificateInfo()->getPublicKeyInfo().publicKeyStrength, 2048 / 8); + } else if (backend == CryptoSign::Backend::Type::NSS3) { + // Not fully sure why it is zero here, but it seems to be. + QCOMPARE(siginfo1->getCertificateInfo()->getPublicKeyInfo().publicKeyStrength, 0); + } #else QCOMPARE(siginfo1->getSignerName(), std::string {}); QCOMPARE(siginfo1->getHashAlgorithm(), HashAlgorithm::Unknown); -- cgit v1.2.3