summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSune Vuorela <sune@vuorela.dk>2023-04-11 13:39:36 +0200
committerSune Vuorela <sune@vuorela.dk>2023-05-21 15:07:41 +0200
commit4efd2f9f8175cb5d448809b56f67524df6e220aa (patch)
tree63b35dd0f8378c5c009a5ab8368dd5c1bf14adfa
parent9065bd7d5fb9f9554235eafe8a67d0f67ea25faf (diff)
Cryptosign backend using gpgme (gpgsm) for all your signature needs
-rw-r--r--CMakeLists.txt9
-rw-r--r--config.h.cmake3
-rw-r--r--poppler/CryptoSignBackend.cc18
-rw-r--r--poppler/CryptoSignBackend.h3
-rw-r--r--poppler/GPGMECryptoSignBackend.cc387
-rw-r--r--poppler/GPGMECryptoSignBackend.h57
-rw-r--r--qt5/tests/check_signature_basics.cpp52
-rw-r--r--qt6/tests/check_signature_basics.cpp50
8 files changed, 557 insertions, 22 deletions
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<CryptoSign::Backend::Type> 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;
}
@@ -59,6 +65,11 @@ static std::vector<Backend::Type> createAvailableBackends()
#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;
}
std::vector<Backend::Type> Factory::getAvailable()
@@ -83,6 +94,13 @@ std::unique_ptr<CryptoSign::Backend> CryptoSign::Factory::create(Backend::Type b
#else
return nullptr;
#endif
+ case Backend::Type::GPGME: {
+#ifdef ENABLE_GPGME
+ return std::make_unique<GpgSignatureBackend>();
+#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<VerificationInterface> createVerificationHandler(std::vector<unsigned char> &&pkcs7) = 0;
virtual std::unique_ptr<SigningInterface> 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 <sune@vuorela.dk>
+//========================================================================
+#include "GPGMECryptoSignBackend.h"
+#include "DistinguishedNameParser.h"
+#include <gpgme.h>
+#include <gpgme++/key.h>
+#include <gpgme++/gpgmepp_version.h>
+#include <gpgme++/signingresult.h>
+#include <gpgme++/engineinfo.h>
+
+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<typename Result>
+static bool isValidResult(const Result &result)
+{
+ return isSuccess(result.error());
+}
+
+template<typename Result>
+static bool hasValidResult(const std::optional<Result> &result)
+{
+ if (!result) {
+ return false;
+ }
+ return isValidResult(result.value());
+}
+
+static std::optional<GpgME::Signature> 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<X509CertificateInfo> getCertificateInfoFromKey(const GpgME::Key &key)
+{
+ auto certificateInfo = std::make_unique<X509CertificateInfo>();
+ 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<CryptoSign::SigningInterface> GpgSignatureBackend::createSigningHandler(const std::string &certID, HashAlgorithm digestAlgTag)
+{
+ return std::make_unique<GpgSignatureCreation>(certID, digestAlgTag);
+}
+
+std::unique_ptr<CryptoSign::VerificationInterface> GpgSignatureBackend::createVerificationHandler(std::vector<unsigned char> &&pkcs7)
+{
+ return std::make_unique<GpgSignatureVerification>(std::move(pkcs7));
+}
+
+std::vector<std::unique_ptr<X509CertificateInfo>> GpgSignatureBackend::getAvailableSigningCertificates()
+{
+ std::vector<std::unique_ptr<X509CertificateInfo>> certificates;
+ const auto context = GpgME::Context::create(GpgME::CMS);
+ auto err = context->startKeyListing(static_cast<const char *>(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<GooString> 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<X509CertificateInfo> GpgSignatureCreation::getCertificateInfo() const
+{
+ if (!key) {
+ return nullptr;
+ }
+ return getCertificateInfoFromKey(*key);
+}
+
+GpgSignatureVerification::GpgSignatureVerification(const std::vector<unsigned char> &p7data) : gpgContext { GpgME::Context::create(GpgME::CMS) }, signatureData(reinterpret_cast<const char *>(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<X509CertificateInfo> 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 <sune@vuorela.dk>
+//========================================================================
+#include "CryptoSignBackend.h"
+
+#include <gpgme++/data.h>
+#include <gpgme++/context.h>
+#include <optional>
+
+class GpgSignatureBackend : public CryptoSign::Backend
+{
+public:
+ GpgSignatureBackend();
+ std::unique_ptr<CryptoSign::VerificationInterface> createVerificationHandler(std::vector<unsigned char> &&pkcs7) final;
+ std::unique_ptr<CryptoSign::SigningInterface> createSigningHandler(const std::string &certID, HashAlgorithm digestAlgTag) final;
+ std::vector<std::unique_ptr<X509CertificateInfo>> 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<X509CertificateInfo> getCertificateInfo() const final;
+ std::optional<GooString> signDetached(const std::string &password) final;
+
+private:
+ std::unique_ptr<GpgME::Context> gpgContext;
+ GpgME::Data gpgData;
+ std::optional<GpgME::Key> key;
+};
+
+class GpgSignatureVerification : public CryptoSign::VerificationInterface
+{
+public:
+ explicit GpgSignatureVerification(const std::vector<unsigned char> &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<X509CertificateInfo> getCertificateInfo() const final;
+
+private:
+ std::unique_ptr<GpgME::Context> gpgContext;
+ GpgME::Data signatureData;
+ GpgME::Data signedData;
+ std::optional<GpgME::VerificationResult> 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<PDFDoc> 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<GlobalParams>();
doc = std::make_unique<PDFDoc>(std::make_unique<GooString>(TESTDATADIR "/unittestcases/pdf-signature-sample-2sigs.pdf"));
QVERIFY(doc);
QVERIFY(doc->isOk());
}
+
+void TestSignatureBasics::initTestCase_data()
+{
+ QTest::addColumn<CryptoSign::Backend::Type>("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<PDFDoc> 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<GlobalParams>();
doc = std::make_unique<PDFDoc>(std::make_unique<GooString>(TESTDATADIR "/unittestcases/pdf-signature-sample-2sigs.pdf"));
QVERIFY(doc);
QVERIFY(doc->isOk());
}
+
+void TestSignatureBasics::initTestCase_data()
+{
+ QTest::addColumn<CryptoSign::Backend::Type>("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);