[poppler] 5 commits - CMakeLists.txt config.h.cmake do-the-gnupg-2.4-dance.sh .gitlab-ci.yml poppler/CryptoSignBackend.cc poppler/CryptoSignBackend.h poppler/DistinguishedNameParser.h poppler/Form.cc poppler/GPGMECryptoSignBackend.cc poppler/GPGMECryptoSignBackend.h qt5/tests qt6/tests

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Mon May 22 19:07:36 UTC 2023


 .gitlab-ci.yml                                |    4 
 CMakeLists.txt                                |   19 -
 config.h.cmake                                |    3 
 do-the-gnupg-2.4-dance.sh                     |   51 +++
 poppler/CryptoSignBackend.cc                  |   18 +
 poppler/CryptoSignBackend.h                   |    3 
 poppler/DistinguishedNameParser.h             |  321 +++++++++++++++++++++
 poppler/Form.cc                               |    3 
 poppler/GPGMECryptoSignBackend.cc             |  387 ++++++++++++++++++++++++++
 poppler/GPGMECryptoSignBackend.h              |   57 +++
 qt5/tests/CMakeLists.txt                      |    1 
 qt5/tests/check_distinguished_name_parser.cpp |  165 +++++++++++
 qt5/tests/check_signature_basics.cpp          |   52 ++-
 qt6/tests/CMakeLists.txt                      |    1 
 qt6/tests/check_distinguished_name_parser.cpp |  161 ++++++++++
 qt6/tests/check_signature_basics.cpp          |   50 ++-
 16 files changed, 1270 insertions(+), 26 deletions(-)

New commits:
commit 4efd2f9f8175cb5d448809b56f67524df6e220aa
Author: Sune Vuorela <sune at vuorela.dk>
Date:   Tue Apr 11 13:39:36 2023 +0200

    Cryptosign backend using gpgme (gpgsm) for all your signature needs

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;
     }
@@ -58,6 +64,11 @@ static std::vector<Backend::Type> createAvailableBackends()
     std::vector<Backend::Type> 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::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 at 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 at 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);
commit 9065bd7d5fb9f9554235eafe8a67d0f67ea25faf
Author: Sune Vuorela <sune at vuorela.dk>
Date:   Mon May 1 15:40:39 2023 +0200

    Set certificate info a bit earlier; that way we also get it if signature validation fails

diff --git a/poppler/Form.cc b/poppler/Form.cc
index f3ddc056..110339e2 100644
--- a/poppler/Form.cc
+++ b/poppler/Form.cc
@@ -2410,13 +2410,14 @@ SignatureInfo *FormFieldSignature::validateSignature(bool doVerifyCert, bool for
         signature_info->setSigningTime(std::chrono::system_clock::to_time_t(signature_handler->getSigningTime()));
     }
 
+    signature_info->setCertificateInfo(signature_handler->getCertificateInfo());
+
     if (sig_val_state != SIGNATURE_VALID || !doVerifyCert) {
         return signature_info;
     }
 
     const CertificateValidationStatus cert_val_state = signature_handler->validateCertificate(std::chrono::system_clock::from_time_t(validationTime), ocspRevocationCheck, enableAIA);
     signature_info->setCertificateValStatus(cert_val_state);
-    signature_info->setCertificateInfo(signature_handler->getCertificateInfo());
 
     return signature_info;
 }
commit 266a2fd0a8cce584e0ffad0bc06670ed7136fc19
Author: Sune Vuorela <sune at vuorela.dk>
Date:   Mon Mar 27 11:11:24 2023 +0200

    Add function to get specific element from the DN parse result

diff --git a/poppler/DistinguishedNameParser.h b/poppler/DistinguishedNameParser.h
index 1fef597d..dfabc97e 100644
--- a/poppler/DistinguishedNameParser.h
+++ b/poppler/DistinguishedNameParser.h
@@ -20,6 +20,7 @@
 #include <string>
 #include <utility>
 #include <optional>
+#include <algorithm>
 
 namespace DN {
 namespace detail {
@@ -306,6 +307,15 @@ static Result parseString(std::string_view string)
     return result;
 }
 
+/// returns the first value of a given key (note. there can be multiple)
+/// or nullopt if key is not available
+inline std::optional<std::string> FindFirstValue(const Result &dn, std::string_view key)
+{
+    auto first = std::find_if(dn.begin(), dn.end(), [&key](const auto &it) { return it.first == key; });
+    if (first == dn.end()) {
+        return {};
+    }
+    return first->second;
 }
-
+} // namespace DN
 #endif // DISTINGUISHEDNAMEPARSER_H
commit 395a5b7e5a8efa049a0eb3a4bbff41737aaf5fc1
Author: Sune Vuorela <sune at vuorela.dk>
Date:   Fri Mar 10 13:00:09 2023 +0100

    Parser for Distinguished Names in certificates
    
    Not all crypto libraries exposes the fields parsed, so we wil need our
    own.
    
    Derived from KDE's libkleo, but improved and has added test coverage

diff --git a/poppler/DistinguishedNameParser.h b/poppler/DistinguishedNameParser.h
new file mode 100644
index 00000000..1fef597d
--- /dev/null
+++ b/poppler/DistinguishedNameParser.h
@@ -0,0 +1,311 @@
+//========================================================================
+//
+// DistinguishedNameParser.h
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright 2002 g10 Code GmbH
+// Copyright 2004 Klarälvdalens Datakonsult AB
+// Copyright 2021 g10 Code GmbH
+// Copyright 2023 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune at vuorela.dk>
+//
+// Derived from libkleopatra (KDE key management library) dn.cpp
+//
+//========================================================================
+
+#ifndef DISTINGUISHEDNAMEPARSER_H
+#define DISTINGUISHEDNAMEPARSER_H
+
+#include <vector>
+#include <string>
+#include <utility>
+#include <optional>
+
+namespace DN {
+namespace detail {
+
+inline std::string_view removeLeadingSpaces(std::string_view view)
+{
+    auto pos = view.find_first_not_of(' ');
+    if (pos > view.size()) {
+        return {};
+    }
+    return view.substr(pos);
+}
+
+inline std::string_view removeTrailingSpaces(std::string_view view)
+{
+    auto pos = view.find_last_not_of(' ');
+    if (pos > view.size()) {
+        return {};
+    }
+    return view.substr(0, pos + 1);
+}
+
+inline unsigned char xtoi(unsigned char c)
+{
+    if (c <= '9') {
+        return c - '0';
+    }
+    if (c <= 'F') {
+        return c - 'A' + 10;
+    }
+    return c < 'a' + 10;
+}
+
+inline unsigned char xtoi(unsigned char first, unsigned char second)
+{
+    return 16 * xtoi(first) + xtoi(second);
+}
+// Parses a hex string into actual content
+inline std::optional<std::string> parseHexString(std::string_view view)
+{
+    auto size = view.size();
+    if (size == 0 || (size % 2 == 1)) {
+        return std::nullopt;
+    }
+    // It is only supposed to be called with actual hex strings
+    // but this is just to be extra sure
+    auto endHex = view.find_first_not_of("1234567890abcdefABCDEF");
+    if (endHex != std::string_view::npos) {
+        return {};
+    }
+    std::string result;
+    result.reserve(size / 2);
+    for (size_t i = 0; i < (view.size() - 1); i += 2) {
+        result.push_back(xtoi(view[i], view[i + 1]));
+    }
+    return result;
+}
+
+static const std::vector<std::pair<std::string_view, std::string_view>> oidmap = {
+    // clang-format off
+    // keep them ordered by oid:
+    {"NameDistinguisher", "0.2.262.1.10.7.20"   },
+    {"EMAIL",             "1.2.840.113549.1.9.1"},
+    {"CN",                "2.5.4.3"             },
+    {"SN",                "2.5.4.4"             },
+    {"SerialNumber",      "2.5.4.5"             },
+    {"T",                 "2.5.4.12"            },
+    {"D",                 "2.5.4.13"            },
+    {"BC",                "2.5.4.15"            },
+    {"ADDR",              "2.5.4.16"            },
+    {"PC",                "2.5.4.17"            },
+    {"GN",                "2.5.4.42"            },
+    {"Pseudo",            "2.5.4.65"            },
+    // clang-format on
+};
+
+static std::string_view attributeNameForOID(std::string_view oid)
+{
+    if (oid.substr(0, 4) == std::string_view { "OID." } || oid.substr(0, 4) == std::string_view { "oid." }) { // c++20 has starts_with. we don't have that yet.
+        oid.remove_prefix(4);
+    }
+    for (const auto &m : oidmap) {
+        if (oid == m.second) {
+            return m.first;
+        }
+    }
+    return {};
+}
+
+/* Parse a DN and return an array-ized one.  This is not a validating
+   parser and it does not support any old-stylish syntax; gpgme is
+   expected to return only rfc2253 compatible strings. */
+static std::pair<std::optional<std::string_view>, std::pair<std::string, std::string>> parse_dn_part(std::string_view stringv)
+{
+    std::pair<std::string, std::string> dnPair;
+    auto separatorPos = stringv.find_first_of('=');
+    if (separatorPos == 0 || separatorPos == std::string_view::npos) {
+        return {}; /* empty key */
+    }
+
+    std::string_view key = stringv.substr(0, separatorPos);
+    key = removeTrailingSpaces(key);
+    // map OIDs to their names:
+    if (auto name = attributeNameForOID(key); !name.empty()) {
+        key = name;
+    }
+
+    dnPair.first = std::string { key };
+    stringv = removeLeadingSpaces(stringv.substr(separatorPos + 1));
+    if (stringv.empty()) {
+        return {};
+    }
+
+    if (stringv.front() == '#') {
+        /* hexstring */
+        stringv.remove_prefix(1);
+        auto endHex = stringv.find_first_not_of("1234567890abcdefABCDEF");
+        if (!endHex || (endHex % 2 == 1)) {
+            return {}; /* empty or odd number of digits */
+        }
+        auto value = parseHexString(stringv.substr(0, endHex));
+        if (!value.has_value()) {
+            return {};
+        }
+        stringv = stringv.substr(endHex);
+        dnPair.second = value.value();
+    } else if (stringv.front() == '"') {
+        stringv.remove_prefix(1);
+        std::string value;
+        bool stop = false;
+        while (!stringv.empty() && !stop) {
+            switch (stringv.front()) {
+            case '\\': {
+                if (stringv.size() < 2) {
+                    return {};
+                }
+                if (stringv[1] == '"') {
+                    value.push_back('"');
+                    stringv.remove_prefix(2);
+                } else {
+                    // it is a bit unclear in rfc2253 if escaped hex chars should
+                    // be decoded inside quotes. Let's just forward the verbatim
+                    // for now
+                    value.push_back(stringv.front());
+                    value.push_back(stringv[1]);
+                    stringv.remove_prefix(2);
+                }
+                break;
+            }
+            case '"': {
+                stop = true;
+                stringv.remove_prefix(1);
+                break;
+            }
+            default: {
+                value.push_back(stringv.front());
+                stringv.remove_prefix(1);
+            }
+            }
+        }
+        if (!stop) {
+            // we have reached end of string, but never an actual ", so error out
+            return {};
+        }
+        dnPair.second = value;
+    } else {
+        std::string value;
+        bool stop = false;
+        bool lastAddedEscapedSpace = false;
+        while (!stringv.empty() && !stop) {
+            switch (stringv.front()) {
+            case '\\': //_escaping
+            {
+                stringv.remove_prefix(1);
+                if (stringv.empty()) {
+                    return {};
+                }
+                switch (stringv.front()) {
+                case ',':
+                case '=':
+                case '+':
+                case '<':
+                case '>':
+                case '#':
+                case ';':
+                case '\\':
+                case '"':
+                case ' ': {
+                    if (stringv.front() == ' ') {
+                        lastAddedEscapedSpace = true;
+                    } else {
+                        lastAddedEscapedSpace = false;
+                    }
+                    value.push_back(stringv.front());
+                    stringv.remove_prefix(1);
+                    break;
+                }
+                default: {
+                    if (stringv.size() < 2) {
+                        // this should be double hex-ish, but isn't.
+                        return {};
+                    }
+                    if (std::isxdigit(stringv.front()) && std::isxdigit(stringv[1])) {
+                        lastAddedEscapedSpace = false;
+                        value.push_back(xtoi(stringv.front(), stringv[1]));
+                        stringv.remove_prefix(2);
+                        break;
+                    } else {
+                        // invalid escape
+                        return {};
+                    }
+                }
+                }
+                break;
+            }
+            case '"':
+                // unescaped " in the middle; not allowed
+                return {};
+            case ',':
+            case '=':
+            case '+':
+            case '<':
+            case '>':
+            case '#':
+            case ';': {
+                stop = true;
+                break; //
+            }
+            default:
+                lastAddedEscapedSpace = false;
+                value.push_back(stringv.front());
+                stringv.remove_prefix(1);
+            }
+        }
+        if (lastAddedEscapedSpace) {
+            dnPair.second = value;
+        } else {
+            dnPair.second = std::string { removeTrailingSpaces(value) };
+        }
+    }
+    return { stringv, dnPair };
+}
+}
+
+using Result = std::vector<std::pair<std::string, std::string>>;
+
+/* Parse a DN and return an array-ized one.  This is not a validating
+   parser and it does not support any old-stylish syntax; gpgme is
+   expected to return only rfc2253 compatible strings. */
+static Result parseString(std::string_view string)
+{
+    Result result;
+    while (!string.empty()) {
+        string = detail::removeLeadingSpaces(string);
+        if (string.empty()) {
+            break;
+        }
+
+        auto [partResult, dnPair] = detail::parse_dn_part(string);
+        if (!partResult.has_value()) {
+            return {};
+        }
+
+        string = partResult.value();
+        if (dnPair.first.size() && dnPair.second.size()) {
+            result.emplace_back(std::move(dnPair));
+        }
+
+        string = detail::removeLeadingSpaces(string);
+        if (string.empty()) {
+            break;
+        }
+        switch (string.front()) {
+        case ',':
+        case ';':
+        case '+':
+            string.remove_prefix(1);
+            break;
+        default:
+            // some unexpected characters here
+            return {};
+        }
+    }
+    return result;
+}
+
+}
+
+#endif // DISTINGUISHEDNAMEPARSER_H
diff --git a/qt5/tests/CMakeLists.txt b/qt5/tests/CMakeLists.txt
index 3e3be3e0..297d9560 100644
--- a/qt5/tests/CMakeLists.txt
+++ b/qt5/tests/CMakeLists.txt
@@ -71,6 +71,7 @@ qt5_add_qtest(check_qt5_stroke_opacity check_stroke_opacity.cpp)
 qt5_add_qtest(check_qt5_utf_conversion check_utf_conversion.cpp)
 qt5_add_qtest(check_qt5_outline check_outline.cpp)
 qt5_add_qtest(check_qt5_signature_basics check_signature_basics.cpp)
+qt5_add_qtest(check_qt5_distinguished_name_parser check_distinguished_name_parser.cpp)
 if (NOT WIN32)
   qt5_add_qtest(check_qt5_pagelabelinfo check_pagelabelinfo.cpp)
   qt5_add_qtest(check_qt5_strings check_strings.cpp)
diff --git a/qt5/tests/check_distinguished_name_parser.cpp b/qt5/tests/check_distinguished_name_parser.cpp
new file mode 100644
index 00000000..48d31165
--- /dev/null
+++ b/qt5/tests/check_distinguished_name_parser.cpp
@@ -0,0 +1,165 @@
+//========================================================================
+//
+// check_distinguished_name_parser.h
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright 2023 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune at vuorela.dk>
+//========================================================================
+#include "DistinguishedNameParser.h"
+
+#include <QtTest/QtTest>
+#include <iostream>
+
+class TestDistinguishedNameParser : public QObject
+{
+    Q_OBJECT
+public:
+    explicit TestDistinguishedNameParser(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    // The big set of input/output. Several of the helper functions can be tested independently
+    void testParser();
+    void testParser_data();
+
+    void testRemoveLeadingSpaces();
+    void testRemoveLeadingSpaces_data();
+
+    void testRemoveTrailingSpaces();
+    void testRemoveTrailingSpaces_data();
+
+    void testParseHexString();
+    void testParseHexString_data();
+};
+
+Q_DECLARE_METATYPE(DN::Result);
+Q_DECLARE_METATYPE(std::string);
+Q_DECLARE_METATYPE(std::optional<std::string>);
+
+void TestDistinguishedNameParser::testParser()
+{
+    QFETCH(std::string, inputData);
+    QFETCH(DN::Result, expectedResult);
+
+    auto result = DN::parseString(inputData);
+    QCOMPARE(result, expectedResult);
+}
+
+void TestDistinguishedNameParser::testParser_data()
+{
+    QTest::addColumn<std::string>("inputData");
+    QTest::addColumn<DN::Result>("expectedResult");
+
+    QTest::newRow("empty") << std::string {} << DN::Result {};
+    QTest::newRow("CN=Simple") << std::string { "CN=Simple" } << DN::Result { { "CN", "Simple" } };
+    QTest::newRow("CN=Name with spaces") << std::string { "CN=Name with spaces" } << DN::Result { { "CN", "Name with spaces" } };
+    QTest::newRow("CN=Simple,O=Silly") << std::string { "CN=Simple,O=Silly" } << DN::Result { { "CN", "Simple" }, { "O", "Silly" } };
+    QTest::newRow("CN=Steve Kille,O=Isode Limited,C=GB") << std::string { "CN=Steve Kille,O=Isode Limited,C=GB" } << DN::Result { { "CN", "Steve Kille" }, { "O", "Isode Limited" }, { "C", "GB" } };
+    QTest::newRow("CN=some.user at example.com, O=MyCompany, L=San Diego,ST=California, C=US")
+            << std::string { "CN=some.user at example.com, O=MyCompany, L=San Diego,ST=California, C=US" } << DN::Result { { "CN", "some.user at example.com" }, { "O", "MyCompany" }, { "L", "San Diego" }, { "ST", "California" }, { "C", "US" } };
+    QTest::newRow("Multi valued") << std::string { "OU=Sales+CN=J. Smith,O=Widget Inc.,C=US" }
+                                  << DN::Result { { "OU", "Sales" }, { "CN", "J. Smith" }, { "O", "Widget Inc." }, { "C", "US" } }; // This is technically wrong, but probably good enough for now
+    QTest::newRow("Escaping comma") << std::string { "CN=L. Eagle,O=Sue\\, Grabbit and Runn,C=GB" } << DN::Result { { "CN", "L. Eagle" }, { "O", "Sue, Grabbit and Runn" }, { "C", "GB" } };
+    QTest::newRow("Escaped trailing space") << std::string { "CN=Trailing space\\ " } << DN::Result { { "CN", "Trailing space " } };
+    QTest::newRow("Escaped quote") << std::string { "CN=Quotation \\\" Mark" } << DN::Result { { "CN", "Quotation \" Mark" } };
+
+    QTest::newRow("CN=Simple with escaping") << std::string { "CN=S\\69mpl\\65\\7A" } << DN::Result { { "CN", "Simplez" } };
+    QTest::newRow("SN=Lu\\C4\\8Di\\C4\\87") << std::string { "SN=Lu\\C4\\8Di\\C4\\87" } << DN::Result { { "SN", "Lučić" } };
+    QTest::newRow("CN=\"Quoted name\"") << std::string { "CN=\"Quoted name\"" } << DN::Result { { "CN", "Quoted name" } };
+    QTest::newRow("CN=\" Leading and trailing spacees \"") << std::string { "CN=\" Leading and trailing spaces \"" } << DN::Result { { "CN", " Leading and trailing spaces " } };
+    QTest::newRow("Comma in quotes") << std::string { "CN=\"Comma, inside\"" } << DN::Result { { "CN", "Comma, inside" } };
+    QTest::newRow("forbidden chars in quotes") << std::string { "CN=\"Forbidden !@#$%&*()<>[]{},.?/\\| chars\"" } << DN::Result { { "CN", "Forbidden !@#$%&*()<>[]{},.?/\\| chars" } };
+    QTest::newRow("Quoted quotation") << std::string { "CN=\"Quotation \\\" Mark\"" } << DN::Result { { "CN", "Quotation \" Mark" } };
+    QTest::newRow("Quoted quotation") << std::string { "CN=\"Quotation \\\" Mark\\\" Multiples\"" } << DN::Result { { "CN", "Quotation \" Mark\" Multiples" } };
+
+    QTest::newRow("frompdf1") << std::string { "2.5.4.97=#5553742D49644E722E20444520313233343735323233,CN=TeleSec PKS eIDAS QES CA 5,O=Deutsche Telekom AG,C=DE" }
+                              << DN::Result { { "2.5.4.97", "USt-IdNr. DE 123475223" }, { "CN", "TeleSec PKS eIDAS QES CA 5" }, { "O", "Deutsche Telekom AG" }, { "C", "DE" } };
+    QTest::newRow("frompdf2") << std::string { "2.5.4.5=#34,CN=Koch\\, Werner,2.5.4.42=#5765726E6572,2.5.4.4=#4B6F6368,C=DE" }
+                              << DN::Result { { "SerialNumber", "4" }, { "CN", "Koch, Werner" }, { "GN", "Werner" }, { "SN", "Koch" }, { "C", "DE" } };
+    QTest::newRow("frompdf2a") << std::string { "2.5.4.5=#34,CN=Koch\\, Werner,oid.2.5.4.42=#5765726E6572,OID.2.5.4.4=#4B6F6368,C=DE" }
+                               << DN::Result { { "SerialNumber", "4" }, { "CN", "Koch, Werner" }, { "GN", "Werner" }, { "SN", "Koch" }, { "C", "DE" } };
+
+    // weird spacing
+    QTest::newRow("CN =Simple") << std::string { "CN =Simple" } << DN::Result { { "CN", "Simple" } };
+    QTest::newRow("CN= Simple") << std::string { "CN= Simple" } << DN::Result { { "CN", "Simple" } };
+    QTest::newRow("CN=Simple ") << std::string { "CN=Simple " } << DN::Result { { "CN", "Simple" } };
+    QTest::newRow("CN=Simple,") << std::string { "CN=Simple," } << DN::Result { { "CN", "Simple" } };
+    QTest::newRow("CN=Simple, O=Silly") << std::string { "CN=Simple, O=Silly" } << DN::Result { { "CN", "Simple" }, { "O", "Silly" } };
+
+    // various malformed
+    QTest::newRow("CN=Simple\\") << std::string { "CN=Simple\\" } << DN::Result {};
+    QTest::newRow("CN=") << std::string { "CN=" } << DN::Result {};
+    QTest::newRow("CN=Simple\\X") << std::string { "CN=Simple\\X" } << DN::Result {};
+    QTest::newRow("CN=Simple, O") << std::string { "CN=Simple, O" } << DN::Result {};
+    QTest::newRow("CN=Sim\"ple") << std::string { "CN=Sim\"ple, O" } << DN::Result {};
+    QTest::newRow("CN=Simple\\a") << std::string { "CN=Simple\\a" } << DN::Result {};
+    QTest::newRow("=Simple") << std::string { "=Simple" } << DN::Result {};
+    QTest::newRow("CN=\"Simple") << std::string { "CN=\"Simple" } << DN::Result {};
+    QTest::newRow("CN=\"Simple") << std::string { "CN=\"Simple\\" } << DN::Result {};
+    QTest::newRow("unquoted quotation in quotation") << std::string { "CN=\"Quotation \" Mark\"" } << DN::Result {};
+}
+
+void TestDistinguishedNameParser::testRemoveLeadingSpaces()
+{
+    QFETCH(std::string, input);
+    QFETCH(std::string, expectedOutput);
+
+    auto result = DN::detail::removeLeadingSpaces(input);
+    QCOMPARE(result, expectedOutput);
+}
+void TestDistinguishedNameParser::testRemoveLeadingSpaces_data()
+{
+    QTest::addColumn<std::string>("input");
+    QTest::addColumn<std::string>("expectedOutput");
+
+    QTest::newRow("Empty") << std::string {} << std::string {};
+    QTest::newRow("No leading spaces") << std::string { "horse" } << std::string { "horse" };
+    QTest::newRow("Some spaces") << std::string { "    horse" } << std::string { "horse" };
+    QTest::newRow("Some leading and trailing") << std::string { "    horse   " } << std::string { "horse   " };
+}
+
+void TestDistinguishedNameParser::testRemoveTrailingSpaces()
+{
+    QFETCH(std::string, input);
+    QFETCH(std::string, expectedOutput);
+
+    auto result = DN::detail::removeTrailingSpaces(input);
+    QCOMPARE(result, expectedOutput);
+}
+void TestDistinguishedNameParser::testRemoveTrailingSpaces_data()
+{
+    QTest::addColumn<std::string>("input");
+    QTest::addColumn<std::string>("expectedOutput");
+
+    QTest::newRow("Empty") << std::string {} << std::string {};
+    QTest::newRow("No leading spaces") << std::string { "horse" } << std::string { "horse" };
+    QTest::newRow("Some spaces") << std::string { "horse    " } << std::string { "horse" };
+    QTest::newRow("Some leading and trailing") << std::string { "    horse   " } << std::string { "    horse" };
+}
+
+void TestDistinguishedNameParser::testParseHexString()
+{
+    QFETCH(std::string, input);
+    QFETCH(std::optional<std::string>, expectedOutput);
+
+    auto result = DN::detail::parseHexString(input);
+    QCOMPARE(result, expectedOutput);
+}
+
+void TestDistinguishedNameParser::testParseHexString_data()
+{
+    QTest::addColumn<std::string>("input");
+    QTest::addColumn<std::optional<std::string>>("expectedOutput");
+
+    QTest::newRow("4") << std::string { "34" } << std::optional<std::string>("4");
+    QTest::newRow("Koch") << std::string { "4B6F6368" } << std::optional<std::string>("Koch");
+    QTest::newRow("USt-IdNr. DE 123475223") << std::string { "5553742D49644E722E20444520313233343735323233" } << std::optional<std::string>("USt-IdNr. DE 123475223");
+
+    // various baddies
+    QTest::newRow("empty") << std::string {} << std::optional<std::string> {};
+    QTest::newRow("FFF") << std::string { "FFF" } << std::optional<std::string> {};
+    QTest::newRow("F") << std::string { "F" } << std::optional<std::string> {};
+    QTest::newRow("XX") << std::string { "XX" } << std::optional<std::string> {};
+}
+
+QTEST_GUILESS_MAIN(TestDistinguishedNameParser);
+#include "check_distinguished_name_parser.moc"
diff --git a/qt6/tests/CMakeLists.txt b/qt6/tests/CMakeLists.txt
index 36da5b9f..3bb09d9f 100644
--- a/qt6/tests/CMakeLists.txt
+++ b/qt6/tests/CMakeLists.txt
@@ -63,6 +63,7 @@ qt6_add_qtest(check_qt6_stroke_opacity check_stroke_opacity.cpp)
 qt6_add_qtest(check_qt6_utf_conversion check_utf_conversion.cpp)
 qt6_add_qtest(check_qt6_outline check_outline.cpp)
 qt6_add_qtest(check_qt6_signature_basics check_signature_basics.cpp)
+qt6_add_qtest(check_qt6_distinguished_name_parser check_distinguished_name_parser.cpp)
 if (NOT WIN32)
   qt6_add_qtest(check_qt6_pagelabelinfo check_pagelabelinfo.cpp)
   qt6_add_qtest(check_qt6_strings check_strings.cpp)
diff --git a/qt6/tests/check_distinguished_name_parser.cpp b/qt6/tests/check_distinguished_name_parser.cpp
new file mode 100644
index 00000000..95d84e98
--- /dev/null
+++ b/qt6/tests/check_distinguished_name_parser.cpp
@@ -0,0 +1,161 @@
+//========================================================================
+//
+// check_distinguished_name_parser.h
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright 2023 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune at vuorela.dk>
+//========================================================================
+#include "DistinguishedNameParser.h"
+
+#include <QtTest/QtTest>
+#include <iostream>
+
+class TestDistinguishedNameParser : public QObject
+{
+    Q_OBJECT
+public:
+    explicit TestDistinguishedNameParser(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    // The big set of input/output. Several of the helper functions can be tested independently
+    void testParser();
+    void testParser_data();
+
+    void testRemoveLeadingSpaces();
+    void testRemoveLeadingSpaces_data();
+
+    void testRemoveTrailingSpaces();
+    void testRemoveTrailingSpaces_data();
+
+    void testParseHexString();
+    void testParseHexString_data();
+};
+
+void TestDistinguishedNameParser::testParser()
+{
+    QFETCH(std::string, inputData);
+    QFETCH(DN::Result, expectedResult);
+
+    auto result = DN::parseString(inputData);
+    QCOMPARE(result, expectedResult);
+}
+
+void TestDistinguishedNameParser::testParser_data()
+{
+    QTest::addColumn<std::string>("inputData");
+    QTest::addColumn<DN::Result>("expectedResult");
+
+    QTest::newRow("empty") << std::string {} << DN::Result {};
+    QTest::newRow("CN=Simple") << std::string { "CN=Simple" } << DN::Result { { "CN", "Simple" } };
+    QTest::newRow("CN=Name with spaces") << std::string { "CN=Name with spaces" } << DN::Result { { "CN", "Name with spaces" } };
+    QTest::newRow("CN=Simple,O=Silly") << std::string { "CN=Simple,O=Silly" } << DN::Result { { "CN", "Simple" }, { "O", "Silly" } };
+    QTest::newRow("CN=Steve Kille,O=Isode Limited,C=GB") << std::string { "CN=Steve Kille,O=Isode Limited,C=GB" } << DN::Result { { "CN", "Steve Kille" }, { "O", "Isode Limited" }, { "C", "GB" } };
+    QTest::newRow("CN=some.user at example.com, O=MyCompany, L=San Diego,ST=California, C=US")
+            << std::string { "CN=some.user at example.com, O=MyCompany, L=San Diego,ST=California, C=US" } << DN::Result { { "CN", "some.user at example.com" }, { "O", "MyCompany" }, { "L", "San Diego" }, { "ST", "California" }, { "C", "US" } };
+    QTest::newRow("Multi valued") << std::string { "OU=Sales+CN=J. Smith,O=Widget Inc.,C=US" }
+                                  << DN::Result { { "OU", "Sales" }, { "CN", "J. Smith" }, { "O", "Widget Inc." }, { "C", "US" } }; // This is technically wrong, but probably good enough for now
+    QTest::newRow("Escaping comma") << std::string { "CN=L. Eagle,O=Sue\\, Grabbit and Runn,C=GB" } << DN::Result { { "CN", "L. Eagle" }, { "O", "Sue, Grabbit and Runn" }, { "C", "GB" } };
+    QTest::newRow("Escaped trailing space") << std::string { "CN=Trailing space\\ " } << DN::Result { { "CN", "Trailing space " } };
+    QTest::newRow("Escaped quote") << std::string { "CN=Quotation \\\" Mark" } << DN::Result { { "CN", "Quotation \" Mark" } };
+
+    QTest::newRow("CN=Simple with escaping") << std::string { "CN=S\\69mpl\\65\\7A" } << DN::Result { { "CN", "Simplez" } };
+    QTest::newRow("SN=Lu\\C4\\8Di\\C4\\87") << std::string { "SN=Lu\\C4\\8Di\\C4\\87" } << DN::Result { { "SN", "Lučić" } };
+    QTest::newRow("CN=\"Quoted name\"") << std::string { "CN=\"Quoted name\"" } << DN::Result { { "CN", "Quoted name" } };
+    QTest::newRow("CN=\" Leading and trailing spacees \"") << std::string { "CN=\" Leading and trailing spaces \"" } << DN::Result { { "CN", " Leading and trailing spaces " } };
+    QTest::newRow("Comma in quotes") << std::string { "CN=\"Comma, inside\"" } << DN::Result { { "CN", "Comma, inside" } };
+    QTest::newRow("forbidden chars in quotes") << std::string { "CN=\"Forbidden !@#$%&*()<>[]{},.?/\\| chars\"" } << DN::Result { { "CN", "Forbidden !@#$%&*()<>[]{},.?/\\| chars" } };
+    QTest::newRow("Quoted quotation") << std::string { "CN=\"Quotation \\\" Mark\"" } << DN::Result { { "CN", "Quotation \" Mark" } };
+    QTest::newRow("Quoted quotation") << std::string { "CN=\"Quotation \\\" Mark\\\" Multiples\"" } << DN::Result { { "CN", "Quotation \" Mark\" Multiples" } };
+
+    QTest::newRow("frompdf1") << std::string { "2.5.4.97=#5553742D49644E722E20444520313233343735323233,CN=TeleSec PKS eIDAS QES CA 5,O=Deutsche Telekom AG,C=DE" }
+                              << DN::Result { { "2.5.4.97", "USt-IdNr. DE 123475223" }, { "CN", "TeleSec PKS eIDAS QES CA 5" }, { "O", "Deutsche Telekom AG" }, { "C", "DE" } };
+    QTest::newRow("frompdf2") << std::string { "2.5.4.5=#34,CN=Koch\\, Werner,2.5.4.42=#5765726E6572,2.5.4.4=#4B6F6368,C=DE" }
+                              << DN::Result { { "SerialNumber", "4" }, { "CN", "Koch, Werner" }, { "GN", "Werner" }, { "SN", "Koch" }, { "C", "DE" } };
+    QTest::newRow("frompdf2a") << std::string { "2.5.4.5=#34,CN=Koch\\, Werner,oid.2.5.4.42=#5765726E6572,OID.2.5.4.4=#4B6F6368,C=DE" }
+                               << DN::Result { { "SerialNumber", "4" }, { "CN", "Koch, Werner" }, { "GN", "Werner" }, { "SN", "Koch" }, { "C", "DE" } };
+
+    // weird spacing
+    QTest::newRow("CN =Simple") << std::string { "CN =Simple" } << DN::Result { { "CN", "Simple" } };
+    QTest::newRow("CN= Simple") << std::string { "CN= Simple" } << DN::Result { { "CN", "Simple" } };
+    QTest::newRow("CN=Simple ") << std::string { "CN=Simple " } << DN::Result { { "CN", "Simple" } };
+    QTest::newRow("CN=Simple,") << std::string { "CN=Simple," } << DN::Result { { "CN", "Simple" } };
+    QTest::newRow("CN=Simple, O=Silly") << std::string { "CN=Simple, O=Silly" } << DN::Result { { "CN", "Simple" }, { "O", "Silly" } };
+
+    // various malformed
+    QTest::newRow("CN=Simple\\") << std::string { "CN=Simple\\" } << DN::Result {};
+    QTest::newRow("CN=") << std::string { "CN=" } << DN::Result {};
+    QTest::newRow("CN=Simple\\X") << std::string { "CN=Simple\\X" } << DN::Result {};
+    QTest::newRow("CN=Simple, O") << std::string { "CN=Simple, O" } << DN::Result {};
+    QTest::newRow("CN=Sim\"ple") << std::string { "CN=Sim\"ple, O" } << DN::Result {};
+    QTest::newRow("CN=Simple\\a") << std::string { "CN=Simple\\a" } << DN::Result {};
+    QTest::newRow("=Simple") << std::string { "=Simple" } << DN::Result {};
+    QTest::newRow("CN=\"Simple") << std::string { "CN=\"Simple" } << DN::Result {};
+    QTest::newRow("CN=\"Simple") << std::string { "CN=\"Simple\\" } << DN::Result {};
+    QTest::newRow("unquoted quotation in quotation") << std::string { "CN=\"Quotation \" Mark\"" } << DN::Result {};
+}
+
+void TestDistinguishedNameParser::testRemoveLeadingSpaces()
+{
+    QFETCH(std::string, input);
+    QFETCH(std::string, expectedOutput);
+
+    auto result = DN::detail::removeLeadingSpaces(input);
+    QCOMPARE(result, expectedOutput);
+}
+void TestDistinguishedNameParser::testRemoveLeadingSpaces_data()
+{
+    QTest::addColumn<std::string>("input");
+    QTest::addColumn<std::string>("expectedOutput");
+
+    QTest::newRow("Empty") << std::string {} << std::string {};
+    QTest::newRow("No leading spaces") << std::string { "horse" } << std::string { "horse" };
+    QTest::newRow("Some spaces") << std::string { "    horse" } << std::string { "horse" };
+    QTest::newRow("Some leading and trailing") << std::string { "    horse   " } << std::string { "horse   " };
+}
+
+void TestDistinguishedNameParser::testRemoveTrailingSpaces()
+{
+    QFETCH(std::string, input);
+    QFETCH(std::string, expectedOutput);
+
+    auto result = DN::detail::removeTrailingSpaces(input);
+    QCOMPARE(result, expectedOutput);
+}
+void TestDistinguishedNameParser::testRemoveTrailingSpaces_data()
+{
+    QTest::addColumn<std::string>("input");
+    QTest::addColumn<std::string>("expectedOutput");
+
+    QTest::newRow("Empty") << std::string {} << std::string {};
+    QTest::newRow("No leading spaces") << std::string { "horse" } << std::string { "horse" };
+    QTest::newRow("Some spaces") << std::string { "horse    " } << std::string { "horse" };
+    QTest::newRow("Some leading and trailing") << std::string { "    horse   " } << std::string { "    horse" };
+}
+
+void TestDistinguishedNameParser::testParseHexString()
+{
+    QFETCH(std::string, input);
+    QFETCH(std::optional<std::string>, expectedOutput);
+
+    auto result = DN::detail::parseHexString(input);
+    QCOMPARE(result, expectedOutput);
+}
+
+void TestDistinguishedNameParser::testParseHexString_data()
+{
+    QTest::addColumn<std::string>("input");
+    QTest::addColumn<std::optional<std::string>>("expectedOutput");
+
+    QTest::newRow("4") << std::string { "34" } << std::optional<std::string>("4");
+    QTest::newRow("Koch") << std::string { "4B6F6368" } << std::optional<std::string>("Koch");
+    QTest::newRow("USt-IdNr. DE 123475223") << std::string { "5553742D49644E722E20444520313233343735323233" } << std::optional<std::string>("USt-IdNr. DE 123475223");
+
+    // various baddies
+    QTest::newRow("empty") << std::string {} << std::optional<std::string> {};
+    QTest::newRow("FFF") << std::string { "FFF" } << std::optional<std::string> {};
+    QTest::newRow("F") << std::string { "F" } << std::optional<std::string> {};
+    QTest::newRow("XX") << std::string { "XX" } << std::optional<std::string> {};
+}
+
+QTEST_GUILESS_MAIN(TestDistinguishedNameParser);
+#include "check_distinguished_name_parser.moc"
commit 720fcc1f1b7c2d13894ce4dfee4f68a40384d91b
Author: Sune Vuorela <sune at vuorela.dk>
Date:   Wed Mar 22 19:22:40 2023 +0100

    Let's try get in a bit of gnupg
    
    A script to build a gnupg2.4.1
    A gpgme that is hardcoded to use the above gnupg
    And enough CMake to see we can find it.

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b83b69d1..3b87969a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -38,9 +38,11 @@ clang_format:
 build:
   stage: build
   script:
+    - apt-get update
+    - bash do-the-gnupg-2.4-dance.sh $PWD/build/gnupg/
     - git clone --branch ${CI_COMMIT_REF_NAME} --depth 1 ${TEST_DATA_URL} test-data || git clone --depth 1 ${UPSTREAM_TEST_DATA_URL} test-data
     - mkdir -p build && cd build
-    - cmake -G Ninja -DTESTDATADIR=$PWD/../test-data ..
+    - cmake -G Ninja -DTESTDATADIR=$PWD/../test-data -DCMAKE_PREFIX_PATH=$PWD/gnupg ..
     - ninja -j ${FDO_CI_CONCURRENT}
     - ctest --output-on-failure
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 04696499..a7438542 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -152,6 +152,7 @@ macro_optional_find_package(JPEG)
 macro_optional_find_package(PNG)
 macro_optional_find_package(TIFF)
 macro_optional_find_package(NSS3)
+macro_optional_find_package(Gpgmepp 1.19)
 if(ENABLE_DCTDECODER STREQUAL "libjpeg")
   if(JPEG_FOUND)
     include(CheckCSourceCompiles)
@@ -351,6 +352,9 @@ include_directories(
 if (NSS3_FOUND)
   set(ENABLE_NSS3 ON)
 endif()
+if (Gpgmepp_FOUND)
+  set(ENABLE_GPGME ON)
+endif()
 if(PNG_FOUND)
   set(ENABLE_LIBPNG ON)
 endif()
@@ -362,6 +366,9 @@ set(SIGNATURE_BACKENDS "")
 if(ENABLE_NSS3)
   list(APPEND SIGNATURE_BACKENDS "NSS")
 endif()
+if(ENABLE_GPGME)
+  list(APPEND SIGNATURE_BACKENDS "GPG")
+endif()
 
 list(LENGTH SIGNATURE_BACKENDS _signing_backends_count)
 if (_signing_backends_count GREATER 0)
@@ -379,8 +386,6 @@ if (NOT DEFAULT_SIGNATURE_BACKEND)
   set(DEFAULT_SIGNATURE_BACKEND "None")
 endif()
 
-
-
 # Recent versions of poppler-data install a .pc file.
 # Use it to determine the encoding data path, if available.
 # Default to the same prefix otherwise.
@@ -860,6 +865,7 @@ show_end_message_yesno("use libtiff" ENABLE_LIBTIFF)
 show_end_message_yesno("use zlib compress" ENABLE_ZLIB)
 show_end_message_yesno("use zlib uncompress" ENABLE_ZLIB_UNCOMPRESS)
 show_end_message_yesno("use nss3" ENABLE_NSS3)
+show_end_message_yesno("use gpg" ENABLE_GPGME)
 show_end_message("  default signature backend" ${DEFAULT_SIGNATURE_BACKEND})
 show_end_message_yesno("use curl" ENABLE_LIBCURL)
 show_end_message_yesno("use libopenjpeg2" WITH_OPENJPEG)
diff --git a/do-the-gnupg-2.4-dance.sh b/do-the-gnupg-2.4-dance.sh
new file mode 100644
index 00000000..c46edf83
--- /dev/null
+++ b/do-the-gnupg-2.4-dance.sh
@@ -0,0 +1,51 @@
+#! /bin/sh
+set -eux
+
+if [ -z ${1} ] 
+then
+    echo "Destination must be provided"
+    exit 1
+fi
+
+apt-get -y install --no-install-recommends libksba-dev libgpg-error-dev libgcrypt-dev libassuan-dev  libnpth-dev libgnutls28-dev pkg-config libldap-dev wget ca-certificates bzip2 patch texinfo
+
+DESTINATION=${1}
+if [ -e "${DESTINATION}/bin/gpg" ]
+then
+   echo "Already installed"
+   exit 0
+fi
+
+if [ -e "${DESTINATION}" ]
+then
+   echo "Please use a nonexisting destination"
+   exit 1
+fi
+
+GNUPG_VERSION=2.4.1
+GPGME_VERSION=1.19.0
+
+WORKDIR=$(mktemp -d)
+
+cd ${WORKDIR}
+
+wget https://gnupg.org/ftp/gcrypt/gnupg/gnupg-${GNUPG_VERSION}.tar.bz2
+tar xf gnupg-${GNUPG_VERSION}.tar.bz2
+wget https://gnupg.org/ftp/gcrypt/gpgme/gpgme-${GPGME_VERSION}.tar.bz2
+tar xf gpgme-${GPGME_VERSION}.tar.bz2
+
+
+mkdir -p ${WORKDIR}/gnupg-${GNUPG_VERSION}/build
+cd gnupg-${GNUPG_VERSION}
+cd build
+../configure --prefix=${DESTINATION}
+make install
+
+cd ${WORKDIR}
+
+mkdir gpgme-${GPGME_VERSION}/build
+cd gpgme-${GPGME_VERSION}/build
+../configure --prefix=${DESTINATION} --enable-fixed-path=${DESTINATION}/bin --enable-languages=cpp
+PATH=${DESTINATION}/bin:$PATH make -j5 install
+
+


More information about the poppler mailing list