diff --git a/src/kleo/expirychecker.cpp b/src/kleo/expirychecker.cpp
index fb08e2a9..4047a19f 100644
--- a/src/kleo/expirychecker.cpp
+++ b/src/kleo/expirychecker.cpp
@@ -1,545 +1,545 @@
 /*
     This file is part of libkleopatra, the KDE keymanagement library
     SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
     SPDX-FileCopyrightText: 2021 Sandro Knauß <sknauss@kde.org>
     SPDX-FileCopyrightText: 2023 g10 Code GmbH
     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
 
     Based on kpgp.h
     Copyright (C) 2001,2002 the KPGP authors
     See file libkdenetwork/AUTHORS.kpgp for details
 
     SPDX-License-Identifier: LGPL-2.0-or-later
 */
 
 #include "expirychecker.h"
 
 #include "debug.h"
-#include "dn.h"
 #include "expirycheckersettings.h"
 
 #include <libkleo/algorithm.h>
+#include <libkleo/formatting.h>
 #include <libkleo/keycache.h>
 #include <libkleo_debug.h>
 
 #include <KLocalizedString>
 
 #include <QGpgME/KeyListJob>
 #include <QGpgME/Protocol>
 
 #include <QTimeZone>
 
 #include <gpgme++/keylistresult.h>
 
 #include <set>
 
 #include <cmath>
 #include <ctime>
 
 using namespace Kleo;
 
 class Kleo::ExpiryCheckerPrivate
 {
     Kleo::ExpiryChecker *q;
 
 public:
     ExpiryCheckerPrivate(ExpiryChecker *qq, const ExpiryCheckerSettings &settings_)
         : q{qq}
         , settings{settings_}
     {
     }
 
     ExpiryChecker::Expiration calculateExpiration(const GpgME::Subkey &subkey) const;
     ExpiryChecker::Expiration checkForExpiration(const GpgME::Key &key, Kleo::chrono::days threshold, ExpiryChecker::CheckFlags flags) const;
 
     ExpiryChecker::Result checkKeyNearExpiry(const GpgME::Key &key, ExpiryChecker::CheckFlags flags);
 
     ExpiryCheckerSettings settings;
     std::set<QByteArray> alreadyWarnedFingerprints;
     std::shared_ptr<TimeProvider> timeProvider;
 };
 
 ExpiryChecker::ExpiryChecker(const ExpiryCheckerSettings &settings, QObject *parent)
     : QObject{parent}
     , d{new ExpiryCheckerPrivate{this, settings}}
 {
 }
 
 ExpiryChecker::~ExpiryChecker() = default;
 
 ExpiryCheckerSettings ExpiryChecker::settings() const
 {
     return d->settings;
 }
 
 QString formatOpenPGPMessage(ExpiryChecker::Expiration expiration, ExpiryChecker::CheckFlags flags)
 {
     const GpgME::Key key = expiration.certificate;
     const bool isOwnKey = flags & ExpiryChecker::OwnKey;
     const bool isSigningKey = flags & ExpiryChecker::SigningKey;
     const auto keyInfo = ki18nc("<b>User ID of key</b> (Key ID key ID of key in hex notation)", "<b>%1</b> (Key ID 0x%2)")
                              .subs(QString::fromUtf8(key.userID(0).id()))
                              .subs(QString::fromLatin1(key.keyID()));
     if (expiration.status == ExpiryChecker::Expired) {
         qCDebug(LIBKLEO_LOG) << "Key" << key << "expired" << expiration.duration.count() << "days ago";
         if (expiration.duration.count() == 0) {
             KLocalizedString msg;
             if (isSigningKey) {
                 msg = ki18n("<p>Your OpenPGP signing key</p><p align=center>%1</p><p>expired less than a day ago.</p>");
             } else if (isOwnKey) {
                 msg = ki18n("<p>Your OpenPGP encryption key</p><p align=center>%1</p><p>expired less than a day ago.</p>");
             } else {
                 msg = ki18n("<p>The OpenPGP key for</p><p align=center>%1</p><p>expired less than a day ago.</p>");
             }
             return msg.subs(keyInfo).toString();
         }
         KLocalizedString msg;
         if (isSigningKey) {
             msg = ki18np("<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expired yesterday.</p>",
                          "<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expired %1 days ago.</p>");
         } else if (isOwnKey) {
             msg = ki18np("<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expired yesterday.</p>",
                          "<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expired %1 days ago.</p>");
         } else {
             msg = ki18np("<p>The OpenPGP key for</p><p align=center>%2</p><p>expired yesterday.</p>",
                          "<p>The OpenPGP key for</p><p align=center>%2</p><p>expired %1 days ago.</p>");
         }
         return msg.subs(expiration.duration.count()).subs(keyInfo).toString();
     }
     qCDebug(LIBKLEO_LOG) << "Key" << key << "expires in" << expiration.duration.count() << "days";
     if (expiration.duration.count() == 0) {
         KLocalizedString msg;
         if (isSigningKey) {
             msg = ki18n("<p>Your OpenPGP signing key</p><p align=center>%1</p><p>expires today.</p>");
         } else if (isOwnKey) {
             msg = ki18n("<p>Your OpenPGP encryption key</p><p align=center>%1</p><p>expires today.</p>");
         } else {
             msg = ki18n("<p>The OpenPGP key for</p><p align=center>%1</p><p>expires today.</p>");
         }
         return msg.subs(keyInfo).toString();
     }
     KLocalizedString msg;
     if (isSigningKey) {
         msg = ki18np("<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expires tomorrow.</p>",
                      "<p>Your OpenPGP signing key</p><p align=center>%2</p><p>expires in %1 days.</p>");
     } else if (isOwnKey) {
         msg = ki18np("<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expires tomorrow.</p>",
                      "<p>Your OpenPGP encryption key</p><p align=center>%2</p><p>expires in %1 days.</p>");
     } else {
         msg = ki18np("<p>The OpenPGP key for</p><p align=center>%2</p><p>expires tomorrow.</p>",
                      "<p>The OpenPGP key for</p><p align=center>%2</p><p>expires in %1 days.</p>");
     }
     return msg.subs(expiration.duration.count()).subs(keyInfo).toString();
 }
 
 QString formatSMIMEMessage(const GpgME::Key &orig_key, ExpiryChecker::Expiration expiration, ExpiryChecker::CheckFlags flags, bool ca)
 {
     const GpgME::Key key = expiration.certificate;
     const bool isOwnKey = flags & ExpiryChecker::OwnKey;
     const bool isSigningKey = flags & ExpiryChecker::SigningKey;
     const auto userCert = orig_key.isNull() ? key : orig_key;
     const auto userCertInfo = ki18nc("<b>User ID of certificate</b> (serial number serial no. of certificate)", "<b>%1</b> (serial number %2)")
-                                  .subs(Kleo::DN(userCert.userID(0).id()).prettyDN())
+                                  .subs(Formatting::prettyDN(userCert.userID(0).id()))
                                   .subs(QString::fromLatin1(userCert.issuerSerial()));
     if (expiration.status == ExpiryChecker::Expired) {
         qCDebug(LIBKLEO_LOG) << "Certificate" << key << "expired" << expiration.duration.count() << "days ago";
         if (ca) {
             if (key.isRoot()) {
                 if (expiration.duration.count() == 0) {
                     KLocalizedString msg;
                     if (isSigningKey) {
                         msg = ki18n(
                             "<p>The root certificate</p><p align=center><b>%2</b></p>"
                             "<p>for your S/MIME signing certificate</p><p align=center>%1</p>"
                             "<p>expired less than a day ago.</p>");
                     } else if (isOwnKey) {
                         msg = ki18n(
                             "<p>The root certificate</p><p align=center><b>%2</b></p>"
                             "<p>for your S/MIME encryption certificate</p><p align=center>%1</p>"
                             "<p>expired less than a day ago.</p>");
                     } else {
                         msg = ki18n(
                             "<p>The root certificate</p><p align=center><b>%2</b></p>"
                             "<p>for S/MIME certificate</p><p align=center>%1</p>"
                             "<p>expired less than a day ago.</p>");
                     }
-                    return msg.subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
+                    return msg.subs(userCertInfo).subs(Formatting::prettyDN(key.userID(0).id())).toString();
                 }
                 KLocalizedString msg;
                 if (isSigningKey) {
                     msg = ki18np(
                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
                         "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
                         "<p>expired yesterday.</p>",
                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
                         "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
                         "<p>expired %1 days ago.</p>");
                 } else if (isOwnKey) {
                     msg = ki18np(
                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
                         "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
                         "<p>expired yesterday.</p>",
                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
                         "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
                         "<p>expired %1 days ago.</p>");
                 } else {
                     msg = ki18np(
                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
                         "<p>for S/MIME certificate</p><p align=center>%2</p>"
                         "<p>expired yesterday.</p>",
                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
                         "<p>for S/MIME certificate</p><p align=center>%2</p>"
                         "<p>expired %1 days ago.</p>");
                 }
-                return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
+                return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Formatting::prettyDN(key.userID(0).id())).toString();
             } else {
                 if (expiration.duration.count() == 0) {
                     KLocalizedString msg;
                     if (isSigningKey) {
                         msg = ki18n(
                             "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
                             "<p>for your S/MIME signing certificate</p><p align=center>%1</p>"
                             "<p>expired less than a day ago.</p>");
                     } else if (isOwnKey) {
                         msg = ki18n(
                             "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
                             "<p>for your S/MIME encryption certificate</p><p align=center>%1</p>"
                             "<p>expired less than a day ago.</p>");
                     } else {
                         msg = ki18n(
                             "<p>The intermediate CA certificate</p><p align=center><b>%2</b></p>"
                             "<p>for S/MIME certificate</p><p align=center>%1</p>"
                             "<p>expired less than a day ago.</p>");
                     }
-                    return msg.subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
+                    return msg.subs(userCertInfo).subs(Formatting::prettyDN(key.userID(0).id())).toString();
                 }
                 KLocalizedString msg;
                 if (isSigningKey) {
                     msg = ki18np(
                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                         "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
                         "<p>expired yesterday.</p>",
                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                         "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
                         "<p>expired %1 days ago.</p>");
                 } else if (isOwnKey) {
                     msg = ki18np(
                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                         "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
                         "<p>expired yesterday.</p>",
                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                         "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
                         "<p>expired %1 days ago.</p>");
                 } else {
                     msg = ki18np(
                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                         "<p>for S/MIME certificate</p><p align=center>%2</p>"
                         "<p>expired yesterday.</p>",
                         "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                         "<p>for S/MIME certificate</p><p align=center>%2</p>"
                         "<p>expired %1 days ago.</p>");
                 }
-                return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
+                return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Formatting::prettyDN(key.userID(0).id())).toString();
             }
         } else {
             if (expiration.duration.count() == 0) {
                 KLocalizedString msg;
                 if (isSigningKey) {
                     msg = ki18n("<p>Your S/MIME signing certificate</p><p align=center>%1</p><p>expired less than a day ago.</p>");
                 } else if (isOwnKey) {
                     msg = ki18n("<p>Your S/MIME encryption certificate</p><p align=center>%1</p><p>expired less than a day ago.</p>");
                 } else {
                     msg = ki18n("<p>The S/MIME certificate for</p><p align=center>%1</p><p>expired less than a day ago.</p>");
                 }
                 return msg.subs(userCertInfo).toString();
             }
             KLocalizedString msg;
             if (isSigningKey) {
                 msg = ki18np("<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expired yesterday.</p>",
                              "<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expired %1 days ago.</p>");
             } else if (isOwnKey) {
                 msg = ki18np("<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expired yesterday.</p>",
                              "<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expired %1 days ago.</p>");
             } else {
                 msg = ki18np("<p>The S/MIME certificate for</p><p align=center>%2</p><p>expired yesterday.</p>",
                              "<p>The S/MIME certificate for</p><p align=center>%2</p><p>expired %1 days ago.</p>");
             }
             return msg.subs(expiration.duration.count()).subs(userCertInfo).toString();
         }
     }
     qCDebug(LIBKLEO_LOG) << "Certificate" << key << "expires in" << expiration.duration.count() << "days";
     if (ca) {
         if (key.isRoot()) {
             if (expiration.duration.count() == 0) {
                 KLocalizedString msg;
                 if (isSigningKey) {
                     msg = ki18n(
                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
                         "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
                         "<p>expires today.</p>");
                 } else if (isOwnKey) {
                     msg = ki18n(
                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
                         "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
                         "<p>expires today.</p>");
                 } else {
                     msg = ki18n(
                         "<p>The root certificate</p><p align=center><b>%3</b></p>"
                         "<p>for S/MIME certificate</p><p align=center>%2</p>"
                         "<p>expires today.</p>");
                 }
-                return msg.subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
+                return msg.subs(userCertInfo).subs(Formatting::prettyDN(key.userID(0).id())).toString();
             }
             KLocalizedString msg;
             if (isSigningKey) {
                 msg = ki18np(
                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
                     "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
                     "<p>expires tomorrow.</p>",
                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
                     "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
                     "<p>expires in %1 days.</p>");
             } else if (isOwnKey) {
                 msg = ki18np(
                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
                     "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
                     "<p>expires tomorrow.</p>",
                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
                     "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
                     "<p>expires in %1 days.</p>");
             } else {
                 msg = ki18np(
                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
                     "<p>for S/MIME certificate</p><p align=center>%2</p>"
                     "<p>expires tomorrow.</p>",
                     "<p>The root certificate</p><p align=center><b>%3</b></p>"
                     "<p>for S/MIME certificate</p><p align=center>%2</p>"
                     "<p>expires in %1 days.</p>");
             }
-            return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
+            return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Formatting::prettyDN(key.userID(0).id())).toString();
         }
         if (expiration.duration.count() == 0) {
             KLocalizedString msg;
             if (isSigningKey) {
                 msg = ki18n(
                     "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                     "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
                     "<p>expires today.</p>");
             } else if (isOwnKey) {
                 msg = ki18n(
                     "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                     "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
                     "<p>expires today.</p>");
             } else {
                 msg = ki18n(
                     "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                     "<p>for S/MIME certificate</p><p align=center>%2</p>"
                     "<p>expires today.</p>");
             }
         }
         KLocalizedString msg;
         if (isSigningKey) {
             msg = ki18np(
                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
                 "<p>expires tomorrow.</p>",
                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                 "<p>for your S/MIME signing certificate</p><p align=center>%2</p>"
                 "<p>expires in %1 days.</p>");
         } else if (isOwnKey) {
             msg = ki18np(
                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
                 "<p>expires tomorrow.</p>",
                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                 "<p>for your S/MIME encryption certificate</p><p align=center>%2</p>"
                 "<p>expires in %1 days.</p>");
         } else {
             msg = ki18np(
                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                 "<p>for S/MIME certificate</p><p align=center>%2</p>"
                 "<p>expires tomorrow.</p>",
                 "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
                 "<p>for S/MIME certificate</p><p align=center>%2</p>"
                 "<p>expires in %1 days.</p>");
         }
-        return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Kleo::DN(key.userID(0).id()).prettyDN()).toString();
+        return msg.subs(expiration.duration.count()).subs(userCertInfo).subs(Formatting::prettyDN(key.userID(0).id())).toString();
     }
     if (expiration.duration.count() == 0) {
         KLocalizedString msg;
         if (isSigningKey) {
             msg = ki18n("<p>Your S/MIME signing certificate</p><p align=center>%2</p><p>expires today.</p>");
         } else if (isOwnKey) {
             msg = ki18n("<p>Your S/MIME encryption certificate</p><p align=center>%2</p><p>expires today.</p>");
         } else {
             msg = ki18n("<p>The S/MIME certificate for</p><p align=center>%2</p><p>expires today.</p>");
         }
         return msg.subs(userCertInfo).toString();
     }
     KLocalizedString msg;
     if (isSigningKey) {
         msg = ki18np(
             "<p>Your S/MIME signing certificate</p><p align=center>%2</p>"
             "<p>expires tomorrow.</p>",
             "<p>Your S/MIME signing certificate</p><p align=center>%2</p>"
             "<p>expires in %1 days.</p>");
     } else if (isOwnKey) {
         msg = ki18np(
             "<p>Your S/MIME encryption certificate</p><p align=center>%2</p>"
             "<p>expires tomorrow.</p>",
             "<p>Your S/MIME encryption certificate</p><p align=center>%2</p>"
             "<p>expires in %1 days.</p>");
     } else {
         msg = ki18np(
             "<p>The S/MIME certificate for</p><p align=center>%2</p>"
             "<p>expires tomorrow.</p>",
             "<p>The S/MIME certificate for</p><p align=center>%2</p>"
             "<p>expires in %1 days.</p>");
     }
     return msg.subs(expiration.duration.count()).subs(userCertInfo).toString();
 }
 
 static GpgME::Subkey findBestSubkey(const GpgME::Key &key, ExpiryChecker::CheckFlags usageFlags)
 {
     // find the subkey with the latest expiration date for the given usage flags
     if (!(usageFlags & ExpiryChecker::UsageMask)) {
         // return primary key if no specific usage is specified (as for chain certificates)
         return key.subkey(0);
     }
     GpgME::Subkey result;
     for (unsigned int i = 0; i < key.numSubkeys(); ++i) {
         const auto subkey = key.subkey(i);
         if (subkey.isRevoked() || subkey.isInvalid() || subkey.isDisabled()) {
             // unusable subkey
             continue;
         }
         if (((usageFlags & ExpiryChecker::EncryptionKey) && !subkey.canEncrypt()) //
             || ((usageFlags & ExpiryChecker::SigningKey) && !subkey.canSign()) //
             || ((usageFlags & ExpiryChecker::CertificationKey) && !subkey.canCertify())) {
             // unsuitable subkey for requested usage
             continue;
         }
         if (subkey.neverExpires()) {
             // stop looking for the best subkey if we found a suitable subkey that doesn't expire;
             // return the primary key because a non-expiring subkey inherits the primary key's expiration
             return key.subkey(0);
         }
         if (quint32(subkey.expirationTime()) > quint32(result.expirationTime())) {
             result = subkey;
         }
     }
     return result;
 }
 
 ExpiryChecker::Expiration ExpiryCheckerPrivate::calculateExpiration(const GpgME::Subkey &subkey) const
 {
     if (subkey.neverExpires()) {
         return {subkey.parent(), ExpiryChecker::NotNearExpiry, Kleo::chrono::days::zero()};
     }
     const qint64 currentTime = timeProvider ? timeProvider->currentTime() : QDateTime::currentSecsSinceEpoch();
     const auto currentDate = timeProvider ? timeProvider->currentDate() : QDate::currentDate();
     const auto timeZone = timeProvider ? timeProvider->timeZone() : QTimeZone{QTimeZone::LocalTime};
     // interpret the expiration time as unsigned 32-bit value if it's negative; gpg also uses uint32 internally
     const qint64 expirationTime = qint64(subkey.expirationTime() < 0 ? quint32(subkey.expirationTime()) : subkey.expirationTime());
     const auto expirationDate = QDateTime::fromSecsSinceEpoch(expirationTime, timeZone).date();
     if (expirationTime <= currentTime) {
         return {subkey.parent(), ExpiryChecker::Expired, Kleo::chrono::days{expirationDate.daysTo(currentDate)}};
     } else {
         return {subkey.parent(), ExpiryChecker::ExpiresSoon, Kleo::chrono::days{currentDate.daysTo(expirationDate)}};
     }
 }
 
 ExpiryChecker::Expiration ExpiryCheckerPrivate::checkForExpiration(const GpgME::Key &key, //
                                                                    Kleo::chrono::days threshold,
                                                                    ExpiryChecker::CheckFlags usageFlags) const
 {
     const auto subkey = findBestSubkey(key, usageFlags);
     if (subkey.isNull()) {
         return {key, ExpiryChecker::NoSuitableSubkey, {}};
     }
     ExpiryChecker::Expiration expiration = calculateExpiration(subkey);
     if ((expiration.status == ExpiryChecker::ExpiresSoon) && (expiration.duration > threshold)) {
         // key expires, but not too soon
         expiration.status = ExpiryChecker::NotNearExpiry;
     }
     return expiration;
 }
 
 ExpiryChecker::Result ExpiryCheckerPrivate::checkKeyNearExpiry(const GpgME::Key &orig_key, ExpiryChecker::CheckFlags flags)
 {
     static const int maximumCertificateChainLength = 100;
     const bool isOwnKey = flags & ExpiryChecker::OwnKey;
 
     ExpiryChecker::Result result;
     result.checkFlags = flags;
     result.expiration.certificate = orig_key;
 
     // use vector instead of set because certificate chains are usually very short
     std::vector<std::string> checkedCertificates;
     auto key = orig_key;
     for (int chainCount = 0; chainCount < maximumCertificateChainLength; ++chainCount) {
         checkedCertificates.push_back(key.primaryFingerprint());
 
         const GpgME::Subkey subkey = key.subkey(0);
 
         const bool newMessage = !alreadyWarnedFingerprints.count(subkey.fingerprint());
 
         const auto threshold = chainCount > 0 //
             ? (key.isRoot() ? settings.rootCertThreshold() : settings.chainCertThreshold()) //
             : (isOwnKey ? settings.ownKeyThreshold() : settings.otherKeyThreshold());
         const auto usageFlags = (chainCount == 0) ? (flags & ExpiryChecker::UsageMask) : ExpiryChecker::CheckFlags{};
         const auto expiration = checkForExpiration(key, threshold, usageFlags);
         if (chainCount == 0) {
             result.expiration = expiration;
         } else if (expiration.status != ExpiryChecker::NotNearExpiry) {
             result.chainExpiration.push_back(expiration);
         }
         if (expiration.status == ExpiryChecker::Expired) {
             const QString msg = key.protocol() == GpgME::OpenPGP //
                 ? formatOpenPGPMessage(expiration, flags)
                 : formatSMIMEMessage(orig_key, expiration, flags, chainCount > 0);
             alreadyWarnedFingerprints.insert(subkey.fingerprint());
             Q_EMIT q->expiryMessage(key, msg, isOwnKey ? ExpiryChecker::OwnKeyExpired : ExpiryChecker::OtherKeyExpired, newMessage);
         } else if (expiration.status == ExpiryChecker::ExpiresSoon) {
             const QString msg = key.protocol() == GpgME::OpenPGP //
                 ? formatOpenPGPMessage(expiration, flags)
                 : formatSMIMEMessage(orig_key, expiration, flags, chainCount > 0);
             alreadyWarnedFingerprints.insert(subkey.fingerprint());
             Q_EMIT q->expiryMessage(key, msg, isOwnKey ? ExpiryChecker::OwnKeyNearExpiry : ExpiryChecker::OtherKeyNearExpiry, newMessage);
         } else if (expiration.status == ExpiryChecker::NoSuitableSubkey) {
             break;
         }
         if (!(flags & ExpiryChecker::CheckChain) || key.isRoot() || (key.protocol() != GpgME::CMS)) {
             break;
         }
         const auto keys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption);
         if (keys.empty()) {
             break;
         }
         key = keys.front();
         if (Kleo::contains(checkedCertificates, key.primaryFingerprint())) {
             break; // this certificate was already checked (looks like a circle in the chain)
         }
     }
     return result;
 }
 
 ExpiryChecker::Result ExpiryChecker::checkKey(const GpgME::Key &key, CheckFlags flags) const
 {
     if (key.isNull()) {
         qWarning(LIBKLEO_LOG) << __func__ << "called with null key";
         return {flags, {key, InvalidKey, {}}, {}};
     }
     if (!(flags & UsageMask)) {
         qWarning(LIBKLEO_LOG) << __func__ << "called with invalid flags:" << flags;
         return {flags, {key, InvalidCheckFlags, {}}, {}};
     }
     return d->checkKeyNearExpiry(key, flags);
 }
 
 void ExpiryChecker::setTimeProviderForTest(const std::shared_ptr<TimeProvider> &timeProvider)
 {
     d->timeProvider = timeProvider;
 }
 
 #include "moc_expirychecker.cpp"
diff --git a/src/ui/keyrequester.cpp b/src/ui/keyrequester.cpp
index b7fa5e91..164fac67 100644
--- a/src/ui/keyrequester.cpp
+++ b/src/ui/keyrequester.cpp
@@ -1,516 +1,515 @@
 /*  -*- c++ -*-
     keyrequester.cpp
 
     This file is part of libkleopatra, the KDE keymanagement library
     SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
 
     Based on kpgpui.cpp
     SPDX-FileCopyrightText: 2001, 2002 the KPGP authors
     See file libkdenetwork/AUTHORS.kpgp for details
 
     This file is part of KPGP, the KDE PGP/GnuPG support library.
 
     SPDX-License-Identifier: GPL-2.0-or-later
  */
 
 #include <config-libkleo.h>
 
 #include "keyrequester.h"
 
 #include "keyselectiondialog.h"
 
 #include <libkleo/algorithm.h>
 #include <libkleo/compliance.h>
-#include <libkleo/dn.h>
 #include <libkleo/formatting.h>
 #include <libkleo/keyhelpers.h>
 
 #include <KLocalizedString>
 #include <KMessageBox>
 
 #include <QGpgME/KeyListJob>
 
 #include <QApplication>
 #include <QDialog>
 #include <QHBoxLayout>
 #include <QPushButton>
 #include <QString>
 
 #include <gpgme++/key.h>
 #include <gpgme++/keylistresult.h>
 
 using namespace QGpgME;
 using namespace Kleo;
 
 Kleo::KeyRequester::KeyRequester(unsigned int allowedKeys, bool multipleKeys, QWidget *parent)
     : QWidget(parent)
     , mOpenPGPBackend(nullptr)
     , mSMIMEBackend(nullptr)
     , mMulti(multipleKeys)
     , mKeyUsage(allowedKeys)
     , mJobs(0)
     , d(nullptr)
 {
     init();
 }
 
 Kleo::KeyRequester::KeyRequester(QWidget *parent)
     : QWidget(parent)
     , mOpenPGPBackend(nullptr)
     , mSMIMEBackend(nullptr)
     , mMulti(false)
     , mKeyUsage(0)
     , mJobs(0)
     , d(nullptr)
 {
     init();
 }
 
 void Kleo::KeyRequester::init()
 {
     auto hlay = new QHBoxLayout(this);
     hlay->setContentsMargins(0, 0, 0, 0);
 
     if (DeVSCompliance::isCompliant()) {
         mComplianceIcon = new QLabel{this};
         mComplianceIcon->setPixmap(Formatting::questionIcon().pixmap(22));
     }
 
     // the label where the key id is to be displayed:
     mLabel = new QLabel(this);
     mLabel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
 
     // the button to unset any key:
     mEraseButton = new QPushButton(this);
     mEraseButton->setAutoDefault(false);
     mEraseButton->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
     mEraseButton->setIcon(
         QIcon::fromTheme(QApplication::isRightToLeft() ? QStringLiteral("edit-clear-locationbar-ltr") : QStringLiteral("edit-clear-locationbar-rtl")));
     mEraseButton->setToolTip(i18nc("@info:tooltip", "Clear"));
 
     // the button to call the KeySelectionDialog:
     mDialogButton = new QPushButton(i18nc("@action:button", "Change..."), this);
     mDialogButton->setAutoDefault(false);
 
     if (mComplianceIcon) {
         hlay->addWidget(mComplianceIcon);
     }
     hlay->addWidget(mLabel, 1);
     hlay->addWidget(mEraseButton);
     hlay->addWidget(mDialogButton);
 
     connect(mEraseButton, &QPushButton::clicked, this, &SigningKeyRequester::slotEraseButtonClicked);
     connect(mDialogButton, &QPushButton::clicked, this, &SigningKeyRequester::slotDialogButtonClicked);
 
     setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
 
     setAllowedKeys(mKeyUsage);
 }
 
 Kleo::KeyRequester::~KeyRequester()
 {
 }
 
 const std::vector<GpgME::Key> &Kleo::KeyRequester::keys() const
 {
     return mKeys;
 }
 
 const GpgME::Key &Kleo::KeyRequester::key() const
 {
     static const GpgME::Key null = GpgME::Key::null;
     if (mKeys.empty()) {
         return null;
     } else {
         return mKeys.front();
     }
 }
 
 void Kleo::KeyRequester::setKeys(const std::vector<GpgME::Key> &keys)
 {
     mKeys.clear();
     for (auto it = keys.begin(); it != keys.end(); ++it) {
         if (!it->isNull()) {
             mKeys.push_back(*it);
         }
     }
     updateKeys();
 }
 
 void Kleo::KeyRequester::setKey(const GpgME::Key &key)
 {
     mKeys.clear();
     if (!key.isNull()) {
         mKeys.push_back(key);
     }
     updateKeys();
 }
 
 QString Kleo::KeyRequester::fingerprint() const
 {
     if (mKeys.empty()) {
         return QString();
     } else {
         return QLatin1StringView(mKeys.front().primaryFingerprint());
     }
 }
 
 QStringList Kleo::KeyRequester::fingerprints() const
 {
     QStringList result;
     for (auto it = mKeys.begin(); it != mKeys.end(); ++it) {
         if (!it->isNull()) {
             if (const char *fpr = it->primaryFingerprint()) {
                 result.push_back(QLatin1StringView(fpr));
             }
         }
     }
     return result;
 }
 
 void Kleo::KeyRequester::setFingerprint(const QString &fingerprint)
 {
     startKeyListJob(QStringList(fingerprint));
 }
 
 void Kleo::KeyRequester::setFingerprints(const QStringList &fingerprints)
 {
     startKeyListJob(fingerprints);
 }
 
 void Kleo::KeyRequester::updateKeys()
 {
     if (mKeys.empty()) {
         if (mComplianceIcon) {
             mComplianceIcon->setPixmap(Formatting::unavailableIcon().pixmap(22));
             mComplianceIcon->setToolTip(QString{});
         }
         mLabel->clear();
         return;
     }
     if (mKeys.size() > 1) {
         setMultipleKeysEnabled(true);
     }
 
     QStringList labelTexts;
     QString toolTipText;
     for (std::vector<GpgME::Key>::const_iterator it = mKeys.begin(); it != mKeys.end(); ++it) {
         if (it->isNull()) {
             continue;
         }
         const QString fpr = QLatin1StringView(it->primaryFingerprint());
         const QString keyID = QString::fromLatin1(it->keyID());
         labelTexts.push_back(keyID);
         toolTipText += keyID + QLatin1StringView(": ");
         if (const char *uid = it->userID(0).id()) {
             if (it->protocol() == GpgME::OpenPGP) {
                 toolTipText += QString::fromUtf8(uid);
             } else {
-                toolTipText += Kleo::DN(uid).prettyDN();
+                toolTipText += Formatting::prettyDN(uid);
             }
         } else {
             toolTipText += xi18n("<placeholder>unknown</placeholder>");
         }
         toolTipText += QLatin1Char('\n');
     }
     if (mComplianceIcon) {
         if (Kleo::all_of(mKeys, &Kleo::DeVSCompliance::keyIsCompliant)) {
             mComplianceIcon->setPixmap(Formatting::successIcon().pixmap(22));
             mComplianceIcon->setToolTip(DeVSCompliance::name(true));
         } else {
             mComplianceIcon->setPixmap(Formatting::warningIcon().pixmap(22));
             mComplianceIcon->setToolTip(DeVSCompliance::name(false));
         }
     }
     mLabel->setText(labelTexts.join(QLatin1StringView(", ")));
     mLabel->setToolTip(toolTipText);
 }
 
 #ifndef __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
 #define __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
 static void showKeyListError(QWidget *parent, const GpgME::Error &err)
 {
     Q_ASSERT(err);
     const QString msg = i18n(
         "<qt><p>An error occurred while fetching "
         "the keys from the backend:</p>"
         "<p><b>%1</b></p></qt>",
         Formatting::errorAsString(err));
 
     KMessageBox::error(parent, msg, i18nc("@title:window", "Key Listing Failed"));
 }
 #endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
 
 void Kleo::KeyRequester::startKeyListJob(const QStringList &fingerprints)
 {
     if (!mSMIMEBackend && !mOpenPGPBackend) {
         return;
     }
 
     mTmpKeys.clear();
     mJobs = 0;
 
     unsigned int count = 0;
     for (QStringList::const_iterator it = fingerprints.begin(); it != fingerprints.end(); ++it) {
         if (!(*it).trimmed().isEmpty()) {
             ++count;
         }
     }
 
     if (!count) {
         // don't fall into the trap that an empty pattern means
         // "return all keys" :)
         setKey(GpgME::Key::null);
         return;
     }
 
     if (mOpenPGPBackend) {
         KeyListJob *job = mOpenPGPBackend->keyListJob(false); // local, no sigs
         if (!job) {
             KMessageBox::error(this,
                                i18n("The OpenPGP backend does not support listing keys. "
                                     "Check your installation."),
                                i18nc("@title:window", "Key Listing Failed"));
         } else {
             connect(job, &KeyListJob::result, this, &SigningKeyRequester::slotKeyListResult);
             connect(job, &KeyListJob::nextKey, this, &SigningKeyRequester::slotNextKey);
 
             const GpgME::Error err =
                 job->start(fingerprints, mKeyUsage & Kleo::KeySelectionDialog::SecretKeys && !(mKeyUsage & Kleo::KeySelectionDialog::PublicKeys));
 
             if (err) {
                 showKeyListError(this, err);
             } else {
                 ++mJobs;
             }
         }
     }
 
     if (mSMIMEBackend) {
         KeyListJob *job = mSMIMEBackend->keyListJob(false); // local, no sigs
         if (!job) {
             KMessageBox::error(this,
                                i18n("The S/MIME backend does not support listing keys. "
                                     "Check your installation."),
                                i18nc("@title:window", "Key Listing Failed"));
         } else {
             connect(job, &KeyListJob::result, this, &SigningKeyRequester::slotKeyListResult);
             connect(job, &KeyListJob::nextKey, this, &SigningKeyRequester::slotNextKey);
 
             const GpgME::Error err =
                 job->start(fingerprints, mKeyUsage & Kleo::KeySelectionDialog::SecretKeys && !(mKeyUsage & Kleo::KeySelectionDialog::PublicKeys));
 
             if (err) {
                 showKeyListError(this, err);
             } else {
                 ++mJobs;
             }
         }
     }
 
     if (mJobs > 0) {
         mEraseButton->setEnabled(false);
         mDialogButton->setEnabled(false);
     }
 }
 
 void Kleo::KeyRequester::slotNextKey(const GpgME::Key &key)
 {
     if (!key.isNull()) {
         mTmpKeys.push_back(key);
     }
 }
 
 void Kleo::KeyRequester::slotKeyListResult(const GpgME::KeyListResult &res)
 {
     if (res.error()) {
         showKeyListError(this, res.error());
     }
 
     if (--mJobs <= 0) {
         mEraseButton->setEnabled(true);
         mDialogButton->setEnabled(true);
 
         setKeys(mTmpKeys);
         mTmpKeys.clear();
     }
 }
 
 void Kleo::KeyRequester::slotDialogButtonClicked()
 {
     KeySelectionDialog *dlg = mKeys.empty() ? new KeySelectionDialog(mDialogCaption, mDialogMessage, mInitialQuery, mKeyUsage, mMulti, false, this)
                                             : new KeySelectionDialog(mDialogCaption, mDialogCaption, mKeys, mKeyUsage, mMulti, false, this);
 
     if (dlg->exec() == QDialog::Accepted) {
         if (mMulti) {
             setKeys(dlg->selectedKeys());
         } else {
             setKey(dlg->selectedKey());
         }
         Q_EMIT changed();
     }
 
     delete dlg;
 }
 
 void Kleo::KeyRequester::slotEraseButtonClicked()
 {
     if (!mKeys.empty()) {
         Q_EMIT changed();
     }
     mKeys.clear();
     updateKeys();
 }
 
 void Kleo::KeyRequester::setDialogCaption(const QString &caption)
 {
     mDialogCaption = caption;
 }
 
 void Kleo::KeyRequester::setDialogMessage(const QString &msg)
 {
     mDialogMessage = msg;
 }
 
 bool Kleo::KeyRequester::isMultipleKeysEnabled() const
 {
     return mMulti;
 }
 
 void Kleo::KeyRequester::setMultipleKeysEnabled(bool multi)
 {
     if (multi == mMulti) {
         return;
     }
 
     if (!multi && !mKeys.empty()) {
         mKeys.erase(mKeys.begin() + 1, mKeys.end());
     }
 
     mMulti = multi;
     updateKeys();
 }
 
 unsigned int Kleo::KeyRequester::allowedKeys() const
 {
     return mKeyUsage;
 }
 
 void Kleo::KeyRequester::setAllowedKeys(unsigned int keyUsage)
 {
     mKeyUsage = keyUsage;
     mOpenPGPBackend = nullptr;
     mSMIMEBackend = nullptr;
 
     if (mKeyUsage & KeySelectionDialog::OpenPGPKeys) {
         mOpenPGPBackend = openpgp();
     }
     if (mKeyUsage & KeySelectionDialog::SMIMEKeys) {
         mSMIMEBackend = smime();
     }
 
     if (mOpenPGPBackend && !mSMIMEBackend) {
         mDialogCaption = i18n("OpenPGP Key Selection");
         mDialogMessage = i18n("Please select an OpenPGP key to use.");
     } else if (!mOpenPGPBackend && mSMIMEBackend) {
         mDialogCaption = i18n("S/MIME Key Selection");
         mDialogMessage = i18n("Please select an S/MIME key to use.");
     } else {
         mDialogCaption = i18n("Key Selection");
         mDialogMessage = i18n("Please select an (OpenPGP or S/MIME) key to use.");
     }
 }
 
 QPushButton *Kleo::KeyRequester::dialogButton()
 {
     return mDialogButton;
 }
 
 QPushButton *Kleo::KeyRequester::eraseButton()
 {
     return mEraseButton;
 }
 
 static inline unsigned int foo(bool openpgp, bool smime, bool trusted, bool valid)
 {
     unsigned int result = 0;
     if (openpgp) {
         result |= Kleo::KeySelectionDialog::OpenPGPKeys;
     }
     if (smime) {
         result |= Kleo::KeySelectionDialog::SMIMEKeys;
     }
     if (trusted) {
         result |= Kleo::KeySelectionDialog::TrustedKeys;
     }
     if (valid) {
         result |= Kleo::KeySelectionDialog::ValidKeys;
     }
     return result;
 }
 
 static inline unsigned int encryptionKeyUsage(bool openpgp, bool smime, bool trusted, bool valid)
 {
     return foo(openpgp, smime, trusted, valid) | Kleo::KeySelectionDialog::EncryptionKeys | Kleo::KeySelectionDialog::PublicKeys;
 }
 
 static inline unsigned int signingKeyUsage(bool openpgp, bool smime, bool trusted, bool valid)
 {
     return foo(openpgp, smime, trusted, valid) | Kleo::KeySelectionDialog::SigningKeys | Kleo::KeySelectionDialog::SecretKeys;
 }
 
 Kleo::EncryptionKeyRequester::EncryptionKeyRequester(bool multi, unsigned int proto, QWidget *parent, bool onlyTrusted, bool onlyValid)
     : KeyRequester(encryptionKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid), multi, parent)
     , d(nullptr)
 {
 }
 
 Kleo::EncryptionKeyRequester::EncryptionKeyRequester(QWidget *parent)
     : KeyRequester(0, false, parent)
     , d(nullptr)
 {
 }
 
 Kleo::EncryptionKeyRequester::~EncryptionKeyRequester()
 {
 }
 
 void Kleo::EncryptionKeyRequester::setAllowedKeys(unsigned int proto, bool onlyTrusted, bool onlyValid)
 {
     KeyRequester::setAllowedKeys(encryptionKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid));
 }
 
 Kleo::SigningKeyRequester::SigningKeyRequester(bool multi, unsigned int proto, QWidget *parent, bool onlyTrusted, bool onlyValid)
     : KeyRequester(signingKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid), multi, parent)
     , d(nullptr)
 {
 }
 
 Kleo::SigningKeyRequester::SigningKeyRequester(QWidget *parent)
     : KeyRequester(0, false, parent)
     , d(nullptr)
 {
 }
 
 Kleo::SigningKeyRequester::~SigningKeyRequester()
 {
 }
 
 void Kleo::SigningKeyRequester::setAllowedKeys(unsigned int proto, bool onlyTrusted, bool onlyValid)
 {
     KeyRequester::setAllowedKeys(signingKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid));
 }
 
 void Kleo::KeyRequester::virtual_hook(int, void *)
 {
 }
 void Kleo::EncryptionKeyRequester::virtual_hook(int id, void *data)
 {
     KeyRequester::virtual_hook(id, data);
 }
 void Kleo::SigningKeyRequester::virtual_hook(int id, void *data)
 {
     KeyRequester::virtual_hook(id, data);
 }
 
 #include "moc_keyrequester.cpp"
diff --git a/src/ui/keyselectiondialog.cpp b/src/ui/keyselectiondialog.cpp
index ee257409..fbed3e67 100644
--- a/src/ui/keyselectiondialog.cpp
+++ b/src/ui/keyselectiondialog.cpp
@@ -1,1015 +1,1014 @@
 /*  -*- c++ -*-
     keyselectiondialog.cpp
 
     This file is part of libkleopatra, the KDE keymanagement library
     SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
 
     Based on kpgpui.cpp
     SPDX-FileCopyrightText: 2001, 2002 the KPGP authors
     See file libkdenetwork/AUTHORS.kpgp for details
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
 #include <config-libkleo.h>
 
 #include "keyselectiondialog.h"
 
 #include "keylistview.h"
 #include "progressdialog.h"
 
 #include <libkleo/compat.h>
 #include <libkleo/compliance.h>
-#include <libkleo/dn.h>
 #include <libkleo/formatting.h>
 
 #include <kleo_ui_debug.h>
 
 #include <KConfig>
 #include <KConfigGroup>
 #include <KLocalizedString>
 #include <KMessageBox>
 #include <KSharedConfig>
 
 #include <QGpgME/KeyListJob>
 
 #include <QApplication>
 #include <QCheckBox>
 #include <QDialogButtonBox>
 #include <QFrame>
 #include <QHBoxLayout>
 #include <QLabel>
 #include <QLineEdit>
 #include <QMenu>
 #include <QProcess>
 #include <QPushButton>
 #include <QRegularExpression>
 #include <QScrollBar>
 #include <QTimer>
 #include <QVBoxLayout>
 
 #include <gpgme++/key.h>
 #include <gpgme++/keylistresult.h>
 
 #include <algorithm>
 #include <iterator>
 #include <string.h>
 
 using namespace Kleo;
 
 static bool checkKeyUsage(const GpgME::Key &key, unsigned int keyUsage, QString *statusString = nullptr)
 {
     auto setStatusString = [statusString](const QString &status) {
         if (statusString) {
             *statusString = status;
         }
     };
 
     if (keyUsage & KeySelectionDialog::ValidKeys) {
         if (key.isInvalid()) {
             if (key.keyListMode() & GpgME::Validate) {
                 qCDebug(KLEO_UI_LOG) << "key is invalid";
                 setStatusString(i18n("The key is not valid."));
                 return false;
             } else {
                 qCDebug(KLEO_UI_LOG) << "key is invalid - ignoring";
             }
         }
         if (key.isExpired()) {
             qCDebug(KLEO_UI_LOG) << "key is expired";
             setStatusString(i18n("The key is expired."));
             return false;
         } else if (key.isRevoked()) {
             qCDebug(KLEO_UI_LOG) << "key is revoked";
             setStatusString(i18n("The key is revoked."));
             return false;
         } else if (key.isDisabled()) {
             qCDebug(KLEO_UI_LOG) << "key is disabled";
             setStatusString(i18n("The key is disabled."));
             return false;
         }
     }
 
     if (keyUsage & KeySelectionDialog::EncryptionKeys && !Kleo::keyHasEncrypt(key)) {
         qCDebug(KLEO_UI_LOG) << "key can't encrypt";
         setStatusString(i18n("The key is not designated for encryption."));
         return false;
     }
     if (keyUsage & KeySelectionDialog::SigningKeys && !Kleo::keyHasSign(key)) {
         qCDebug(KLEO_UI_LOG) << "key can't sign";
         setStatusString(i18n("The key is not designated for signing."));
         return false;
     }
     if (keyUsage & KeySelectionDialog::CertificationKeys && !Kleo::keyHasCertify(key)) {
         qCDebug(KLEO_UI_LOG) << "key can't certify";
         setStatusString(i18n("The key is not designated for certifying."));
         return false;
     }
     if (keyUsage & KeySelectionDialog::AuthenticationKeys && !Kleo::keyHasAuthenticate(key)) {
         qCDebug(KLEO_UI_LOG) << "key can't authenticate";
         setStatusString(i18n("The key is not designated for authentication."));
         return false;
     }
 
     if (keyUsage & KeySelectionDialog::SecretKeys && !(keyUsage & KeySelectionDialog::PublicKeys) && !key.hasSecret()) {
         qCDebug(KLEO_UI_LOG) << "key isn't secret";
         setStatusString(i18n("The key is not secret."));
         return false;
     }
 
     if (keyUsage & KeySelectionDialog::TrustedKeys && key.protocol() == GpgME::OpenPGP &&
         // only check this for secret keys for now.
         // Seems validity isn't checked for secret keylistings...
         !key.hasSecret()) {
         std::vector<GpgME::UserID> uids = key.userIDs();
         for (std::vector<GpgME::UserID>::const_iterator it = uids.begin(); it != uids.end(); ++it) {
             if (!it->isRevoked() && it->validity() >= GpgME::UserID::Marginal) {
                 setStatusString(i18n("The key can be used."));
                 return true;
             }
         }
         qCDebug(KLEO_UI_LOG) << "key has no UIDs with validity >= Marginal";
         setStatusString(i18n("The key is not trusted enough."));
         return false;
     }
     // X.509 keys are always trusted, else they won't be the keybox.
     // PENDING(marc) check that this ^ is correct
 
     setStatusString(i18n("The key can be used."));
     return true;
 }
 
 static bool checkKeyUsage(const std::vector<GpgME::Key> &keys, unsigned int keyUsage)
 {
     for (auto it = keys.begin(); it != keys.end(); ++it) {
         if (!checkKeyUsage(*it, keyUsage)) {
             return false;
         }
     }
     return true;
 }
 
 namespace
 {
 
 class ColumnStrategy : public KeyListView::ColumnStrategy
 {
 public:
     ColumnStrategy(unsigned int keyUsage);
 
     QString title(int col) const override;
     int width(int col, const QFontMetrics &fm) const override;
 
     QString text(const GpgME::Key &key, int col) const override;
     QString accessibleText(const GpgME::Key &key, int column) const override;
     QString toolTip(const GpgME::Key &key, int col) const override;
     QIcon icon(const GpgME::Key &key, int col) const override;
 
 private:
     const QIcon mKeyGoodPix, mKeyBadPix, mKeyUnknownPix, mKeyValidPix;
     const unsigned int mKeyUsage;
 };
 
 ColumnStrategy::ColumnStrategy(unsigned int keyUsage)
     : KeyListView::ColumnStrategy()
     , mKeyGoodPix(QStringLiteral(":/libkleopatra/key_ok"))
     , mKeyBadPix(QStringLiteral(":/libkleopatra/key_bad"))
     , mKeyUnknownPix(QStringLiteral(":/libkleopatra/key_unknown"))
     , mKeyValidPix(QStringLiteral(":/libkleopatra/key"))
     , mKeyUsage(keyUsage)
 {
     if (keyUsage == 0) {
         qCWarning(KLEO_UI_LOG) << "KeySelectionDialog: keyUsage == 0. You want to use AllKeys instead.";
     }
 }
 
 QString ColumnStrategy::title(int col) const
 {
     switch (col) {
     case 0:
         return i18n("Key ID");
     case 1:
         return i18n("User ID");
     default:
         return QString();
     }
 }
 
 int ColumnStrategy::width(int col, const QFontMetrics &fm) const
 {
     if (col == 0) {
         static const char hexchars[] = "0123456789ABCDEF";
         int maxWidth = 0;
         for (unsigned int i = 0; i < 16; ++i) {
             maxWidth = qMax(fm.boundingRect(QLatin1Char(hexchars[i])).width(), maxWidth);
         }
         return 8 * maxWidth + 2 * 16 /* KIconLoader::SizeSmall */;
     }
     return KeyListView::ColumnStrategy::width(col, fm);
 }
 
 QString ColumnStrategy::text(const GpgME::Key &key, int col) const
 {
     switch (col) {
     case 0: {
         if (key.keyID()) {
             return Formatting::prettyID(key.keyID());
         } else {
             return xi18n("<placeholder>unknown</placeholder>");
         }
     }
     case 1: {
         const char *uid = key.userID(0).id();
         if (key.protocol() == GpgME::OpenPGP) {
             return uid && *uid ? QString::fromUtf8(uid) : QString();
         } else { // CMS
-            return DN(uid).prettyDN();
+            return Formatting::prettyDN(uid);
         }
     }
     default:
         return QString();
     }
 }
 
 QString ColumnStrategy::accessibleText(const GpgME::Key &key, int col) const
 {
     switch (col) {
     case 0: {
         if (key.keyID()) {
             return Formatting::accessibleHexID(key.keyID());
         }
         [[fallthrough]];
     }
     default:
         return {};
     }
 }
 
 QString ColumnStrategy::toolTip(const GpgME::Key &key, int) const
 {
     const char *uid = key.userID(0).id();
     const char *fpr = key.primaryFingerprint();
     const char *issuer = key.issuerName();
     const GpgME::Subkey subkey = key.subkey(0);
     const QString expiry = Formatting::expirationDateString(subkey);
     const QString creation = Formatting::creationDateString(subkey);
     QString keyStatusString;
     if (!checkKeyUsage(key, mKeyUsage, &keyStatusString)) {
         // Show the status in bold if there is a problem
         keyStatusString = QLatin1StringView("<b>") % keyStatusString % QLatin1StringView("</b>");
     }
 
     QString html = QStringLiteral("<qt><p style=\"style='white-space:pre'\">");
     if (key.protocol() == GpgME::OpenPGP) {
         html += i18n("OpenPGP key for <b>%1</b>", uid ? QString::fromUtf8(uid) : i18n("unknown"));
     } else {
-        html += i18n("S/MIME key for <b>%1</b>", uid ? DN(uid).prettyDN() : i18n("unknown"));
+        html += i18n("S/MIME key for <b>%1</b>", uid ? Formatting::prettyDN(uid) : i18n("unknown"));
     }
     html += QStringLiteral("</p><table>");
 
     const auto addRow = [&html](const QString &name, const QString &value) {
         html += QStringLiteral("<tr><td align=\"right\"><b>%1: </b></td><td>%2</td></tr>").arg(name, value);
     };
     addRow(i18n("Valid from"), creation);
     addRow(i18n("Valid until"), expiry);
     addRow(i18nc("Key fingerprint", "Fingerprint"), fpr ? QString::fromLatin1(fpr) : i18n("unknown"));
     if (key.protocol() != GpgME::OpenPGP) {
-        addRow(i18nc("Key issuer", "Issuer"), issuer ? DN(issuer).prettyDN() : i18n("unknown"));
+        addRow(i18nc("Key issuer", "Issuer"), issuer ? Formatting::prettyDN(issuer) : i18n("unknown"));
     }
     addRow(i18nc("Key status", "Status"), keyStatusString);
     if (DeVSCompliance::isActive()) {
         addRow(i18nc("Compliance of key", "Compliance"), DeVSCompliance::name(key.isDeVs()));
     }
     html += QStringLiteral("</table></qt>");
 
     return html;
 }
 
 QIcon ColumnStrategy::icon(const GpgME::Key &key, int col) const
 {
     if (col != 0) {
         return QIcon();
     }
     // this key did not undergo a validating keylisting yet:
     if (!(key.keyListMode() & GpgME::Validate)) {
         return mKeyUnknownPix;
     }
 
     if (!checkKeyUsage(key, mKeyUsage)) {
         return mKeyBadPix;
     }
 
     if (key.protocol() == GpgME::CMS) {
         return mKeyGoodPix;
     }
 
     switch (key.userID(0).validity()) {
     default:
     case GpgME::UserID::Unknown:
     case GpgME::UserID::Undefined:
         return mKeyUnknownPix;
     case GpgME::UserID::Never:
         return mKeyValidPix;
     case GpgME::UserID::Marginal:
     case GpgME::UserID::Full:
     case GpgME::UserID::Ultimate: {
         if (DeVSCompliance::isActive() && !key.isDeVs()) {
             return mKeyValidPix;
         }
         return mKeyGoodPix;
     }
     }
 }
 
 }
 
 static const int sCheckSelectionDelay = 250;
 
 KeySelectionDialog::KeySelectionDialog(QWidget *parent, Options options)
     : QDialog(parent)
     , mOpenPGPBackend(QGpgME::openpgp())
     , mSMIMEBackend(QGpgME::smime())
     , mKeyUsage(AllKeys)
 {
     qCDebug(KLEO_UI_LOG) << "mTruncated:" << mTruncated << "mSavedOffsetY:" << mSavedOffsetY;
     setUpUI(options, QString());
 }
 
 KeySelectionDialog::KeySelectionDialog(const QString &title,
                                        const QString &text,
                                        const std::vector<GpgME::Key> &selectedKeys,
                                        unsigned int keyUsage,
                                        bool extendedSelection,
                                        bool rememberChoice,
                                        QWidget *parent,
                                        bool modal)
     : QDialog(parent)
     , mSelectedKeys(selectedKeys)
     , mKeyUsage(keyUsage)
 {
     setWindowTitle(title);
     setModal(modal);
     init(rememberChoice, extendedSelection, text, QString());
 }
 
 KeySelectionDialog::KeySelectionDialog(const QString &title,
                                        const QString &text,
                                        const QString &initialQuery,
                                        const std::vector<GpgME::Key> &selectedKeys,
                                        unsigned int keyUsage,
                                        bool extendedSelection,
                                        bool rememberChoice,
                                        QWidget *parent,
                                        bool modal)
     : QDialog(parent)
     , mSelectedKeys(selectedKeys)
     , mKeyUsage(keyUsage)
     , mSearchText(initialQuery)
     , mInitialQuery(initialQuery)
 {
     setWindowTitle(title);
     setModal(modal);
     init(rememberChoice, extendedSelection, text, initialQuery);
 }
 
 KeySelectionDialog::KeySelectionDialog(const QString &title,
                                        const QString &text,
                                        const QString &initialQuery,
                                        unsigned int keyUsage,
                                        bool extendedSelection,
                                        bool rememberChoice,
                                        QWidget *parent,
                                        bool modal)
     : QDialog(parent)
     , mKeyUsage(keyUsage)
     , mSearchText(initialQuery)
     , mInitialQuery(initialQuery)
 {
     setWindowTitle(title);
     setModal(modal);
     init(rememberChoice, extendedSelection, text, initialQuery);
 }
 
 void KeySelectionDialog::setUpUI(Options options, const QString &initialQuery)
 {
     auto mainLayout = new QVBoxLayout(this);
     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
     mOkButton = buttonBox->button(QDialogButtonBox::Ok);
     mOkButton->setDefault(true);
     mOkButton->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Return));
 
     mCheckSelectionTimer = new QTimer(this);
     mStartSearchTimer = new QTimer(this);
 
     QFrame *page = new QFrame(this);
     mainLayout->addWidget(page);
     mainLayout->addWidget(buttonBox);
 
     mTopLayout = new QVBoxLayout(page);
     mTopLayout->setContentsMargins(0, 0, 0, 0);
 
     mTextLabel = new QLabel(page);
     mTextLabel->setWordWrap(true);
 
     // Setting the size policy is necessary as a workaround for https://issues.kolab.org/issue4429
     // and http://bugreports.qt.nokia.com/browse/QTBUG-8740
     mTextLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
     connect(mTextLabel, &QLabel::linkActivated, this, &KeySelectionDialog::slotStartCertificateManager);
     mTopLayout->addWidget(mTextLabel);
     mTextLabel->hide();
 
     QPushButton *const searchExternalPB = new QPushButton(i18nc("@action:button", "Search for &External Certificates"), page);
     mTopLayout->addWidget(searchExternalPB, 0, Qt::AlignLeft);
     connect(searchExternalPB, &QAbstractButton::clicked, this, &KeySelectionDialog::slotStartSearchForExternalCertificates);
     if (initialQuery.isEmpty()) {
         searchExternalPB->hide();
     }
 
     auto hlay = new QHBoxLayout();
     mTopLayout->addLayout(hlay);
 
     auto le = new QLineEdit(page);
     le->setClearButtonEnabled(true);
     le->setText(initialQuery);
 
     QLabel *lbSearchFor = new QLabel(i18nc("@label:textbox", "&Search for:"), page);
     lbSearchFor->setBuddy(le);
 
     hlay->addWidget(lbSearchFor);
     hlay->addWidget(le, 1);
     le->setFocus();
 
     connect(le, &QLineEdit::textChanged, this, [this](const QString &s) {
         slotSearch(s);
     });
     connect(mStartSearchTimer, &QTimer::timeout, this, &KeySelectionDialog::slotFilter);
 
     mKeyListView = new KeyListView(new ColumnStrategy(mKeyUsage), nullptr, page);
     mKeyListView->setObjectName(QLatin1StringView("mKeyListView"));
     mKeyListView->header()->stretchLastSection();
     mKeyListView->setRootIsDecorated(true);
     mKeyListView->setSortingEnabled(true);
     mKeyListView->header()->setSortIndicatorShown(true);
     mKeyListView->header()->setSortIndicator(1, Qt::AscendingOrder); // sort by User ID
     if (options & ExtendedSelection) {
         mKeyListView->setSelectionMode(QAbstractItemView::ExtendedSelection);
     }
     mTopLayout->addWidget(mKeyListView, 10);
 
     if (options & RememberChoice) {
         mRememberCB = new QCheckBox(i18nc("@option:check", "&Remember choice"), page);
         mTopLayout->addWidget(mRememberCB);
         mRememberCB->setWhatsThis(
             i18n("<qt><p>If you check this box your choice will "
                  "be stored and you will not be asked again."
                  "</p></qt>"));
     }
 
     connect(mCheckSelectionTimer, &QTimer::timeout, this, [this]() {
         slotCheckSelection();
     });
     connectSignals();
 
     connect(mKeyListView, &KeyListView::doubleClicked, this, &KeySelectionDialog::slotTryOk);
     connect(mKeyListView, &KeyListView::contextMenu, this, &KeySelectionDialog::slotRMB);
 
     if (options & RereadKeys) {
         QPushButton *button = new QPushButton(i18nc("@action:button", "&Reread Keys"));
         buttonBox->addButton(button, QDialogButtonBox::ActionRole);
         connect(button, &QPushButton::clicked, this, &KeySelectionDialog::slotRereadKeys);
     }
     if (options & ExternalCertificateManager) {
         QPushButton *button = new QPushButton(i18nc("@action:button", "&Start Certificate Manager"));
         buttonBox->addButton(button, QDialogButtonBox::ActionRole);
         connect(button, &QPushButton::clicked, this, [this]() {
             slotStartCertificateManager();
         });
     }
     connect(mOkButton, &QPushButton::clicked, this, &KeySelectionDialog::slotOk);
     connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &KeySelectionDialog::slotCancel);
 
     mTopLayout->activate();
 
     if (qApp) {
         QSize dialogSize(sizeHint());
         KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), QStringLiteral("Key Selection Dialog"));
         dialogSize = dialogConfig.readEntry("Dialog size", dialogSize);
         const QByteArray headerState = dialogConfig.readEntry("header", QByteArray());
         if (!headerState.isEmpty()) {
             mKeyListView->header()->restoreState(headerState);
         }
         resize(dialogSize);
     }
 }
 
 void KeySelectionDialog::init(bool rememberChoice, bool extendedSelection, const QString &text, const QString &initialQuery)
 {
     Options options = {RereadKeys, ExternalCertificateManager};
     options.setFlag(ExtendedSelection, extendedSelection);
     options.setFlag(RememberChoice, rememberChoice);
 
     setUpUI(options, initialQuery);
     setText(text);
 
     if (mKeyUsage & OpenPGPKeys) {
         mOpenPGPBackend = QGpgME::openpgp();
     }
     if (mKeyUsage & SMIMEKeys) {
         mSMIMEBackend = QGpgME::smime();
     }
 
     slotRereadKeys();
 }
 
 KeySelectionDialog::~KeySelectionDialog()
 {
     disconnectSignals();
     KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), QStringLiteral("Key Selection Dialog"));
     dialogConfig.writeEntry("Dialog size", size());
     dialogConfig.writeEntry("header", mKeyListView->header()->saveState());
     dialogConfig.sync();
 }
 
 void KeySelectionDialog::setText(const QString &text)
 {
     mTextLabel->setText(text);
     mTextLabel->setVisible(!text.isEmpty());
 }
 
 void KeySelectionDialog::setKeys(const std::vector<GpgME::Key> &keys)
 {
     for (const GpgME::Key &key : keys) {
         mKeyListView->slotAddKey(key);
     }
 }
 
 void KeySelectionDialog::connectSignals()
 {
     if (mKeyListView->isMultiSelection()) {
         connect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged);
     } else {
         connect(mKeyListView,
                 qOverload<KeyListViewItem *>(&KeyListView::selectionChanged),
                 this,
                 qOverload<KeyListViewItem *>(&KeySelectionDialog::slotCheckSelection));
     }
 }
 
 void KeySelectionDialog::disconnectSignals()
 {
     if (mKeyListView->isMultiSelection()) {
         disconnect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged);
     } else {
         disconnect(mKeyListView,
                    qOverload<KeyListViewItem *>(&KeyListView::selectionChanged),
                    this,
                    qOverload<KeyListViewItem *>(&KeySelectionDialog::slotCheckSelection));
     }
 }
 
 const GpgME::Key &KeySelectionDialog::selectedKey() const
 {
     static const GpgME::Key null = GpgME::Key::null;
     if (mKeyListView->isMultiSelection() || !mKeyListView->selectedItem()) {
         return null;
     }
     return mKeyListView->selectedItem()->key();
 }
 
 QString KeySelectionDialog::fingerprint() const
 {
     return QLatin1StringView(selectedKey().primaryFingerprint());
 }
 
 QStringList KeySelectionDialog::fingerprints() const
 {
     QStringList result;
     for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
         if (const char *fpr = it->primaryFingerprint()) {
             result.push_back(QLatin1StringView(fpr));
         }
     }
     return result;
 }
 
 QStringList KeySelectionDialog::pgpKeyFingerprints() const
 {
     QStringList result;
     for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
         if (it->protocol() == GpgME::OpenPGP) {
             if (const char *fpr = it->primaryFingerprint()) {
                 result.push_back(QLatin1StringView(fpr));
             }
         }
     }
     return result;
 }
 
 QStringList KeySelectionDialog::smimeFingerprints() const
 {
     QStringList result;
     for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
         if (it->protocol() == GpgME::CMS) {
             if (const char *fpr = it->primaryFingerprint()) {
                 result.push_back(QLatin1StringView(fpr));
             }
         }
     }
     return result;
 }
 
 void KeySelectionDialog::slotRereadKeys()
 {
     mKeyListView->clear();
     mListJobCount = 0;
     mTruncated = 0;
     mSavedOffsetY = mKeyListView->verticalScrollBar()->value();
 
     disconnectSignals();
     mKeyListView->setEnabled(false);
 
     // FIXME: save current selection
     if (mOpenPGPBackend) {
         startKeyListJobForBackend(mOpenPGPBackend, std::vector<GpgME::Key>(), false /*non-validating*/);
     }
     if (mSMIMEBackend) {
         startKeyListJobForBackend(mSMIMEBackend, std::vector<GpgME::Key>(), false /*non-validating*/);
     }
 
     if (mListJobCount == 0) {
         mKeyListView->setEnabled(true);
         KMessageBox::information(this,
                                  i18n("No backends found for listing keys. "
                                       "Check your installation."),
                                  i18nc("@title:window", "Key Listing Failed"));
         connectSignals();
     }
 }
 
 void KeySelectionDialog::slotStartCertificateManager(const QString &query)
 {
     QStringList args;
 
     if (!query.isEmpty()) {
         args << QStringLiteral("--search") << query;
     }
     const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
     if (exec.isEmpty()) {
         qCWarning(KLEO_UI_LOG) << "Could not find kleopatra executable in PATH";
         KMessageBox::error(this,
                            i18n("Could not start certificate manager; "
                                 "please check your installation."),
                            i18nc("@title:window", "Certificate Manager Error"));
     } else {
         QProcess::startDetached(QStringLiteral("kleopatra"), args);
         qCDebug(KLEO_UI_LOG) << "\nslotStartCertManager(): certificate manager started.";
     }
 }
 
 #ifndef __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
 #define __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
 static void showKeyListError(QWidget *parent, const GpgME::Error &err)
 {
     Q_ASSERT(err);
     const QString msg = i18n(
         "<qt><p>An error occurred while fetching "
         "the keys from the backend:</p>"
         "<p><b>%1</b></p></qt>",
         Formatting::errorAsString(err));
 
     KMessageBox::error(parent, msg, i18nc("@title:window", "Key Listing Failed"));
 }
 #endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
 
 namespace
 {
 struct ExtractFingerprint {
     QString operator()(const GpgME::Key &key)
     {
         return QLatin1StringView(key.primaryFingerprint());
     }
 };
 }
 
 void KeySelectionDialog::startKeyListJobForBackend(const QGpgME::Protocol *backend, const std::vector<GpgME::Key> &keys, bool validate)
 {
     Q_ASSERT(backend);
     QGpgME::KeyListJob *job = backend->keyListJob(false, false, validate); // local, w/o sigs, validation as given
     if (!job) {
         return;
     }
 
     connect(job, &QGpgME::KeyListJob::result, this, &KeySelectionDialog::slotKeyListResult);
     if (validate) {
         connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotRefreshKey);
     } else {
         connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotAddKey);
     }
 
     QStringList fprs;
     std::transform(keys.begin(), keys.end(), std::back_inserter(fprs), ExtractFingerprint());
     const GpgME::Error err = job->start(fprs, mKeyUsage & SecretKeys && !(mKeyUsage & PublicKeys));
 
     if (err) {
         return showKeyListError(this, err);
     }
 
 #ifndef LIBKLEO_NO_PROGRESSDIALOG
     // FIXME: create a MultiProgressDialog:
     (void)new ProgressDialog(job, validate ? i18n("Checking selected keys...") : i18n("Fetching keys..."), this);
 #endif
     ++mListJobCount;
 }
 
 static void selectKeys(KeyListView *klv, const std::vector<GpgME::Key> &selectedKeys)
 {
     klv->clearSelection();
     if (selectedKeys.empty()) {
         return;
     }
     for (auto it = selectedKeys.begin(); it != selectedKeys.end(); ++it) {
         if (KeyListViewItem *item = klv->itemByFingerprint(it->primaryFingerprint())) {
             item->setSelected(true);
         }
     }
 }
 
 void KeySelectionDialog::slotKeyListResult(const GpgME::KeyListResult &res)
 {
     if (res.error()) {
         showKeyListError(this, res.error());
     } else if (res.isTruncated()) {
         ++mTruncated;
     }
 
     if (--mListJobCount > 0) {
         return; // not yet finished...
     }
 
     if (mTruncated > 0) {
         KMessageBox::information(this,
                                  i18np("<qt>One backend returned truncated output.<p>"
                                        "Not all available keys are shown</p></qt>",
                                        "<qt>%1 backends returned truncated output.<p>"
                                        "Not all available keys are shown</p></qt>",
                                        mTruncated),
                                  i18n("Key List Result"));
     }
 
     mKeyListView->flushKeys();
 
     mKeyListView->setEnabled(true);
     mListJobCount = mTruncated = 0;
     mKeysToCheck.clear();
 
     selectKeys(mKeyListView, mSelectedKeys);
 
     slotFilter();
 
     connectSignals();
 
     slotSelectionChanged();
 
     // restore the saved position of the contents
     mKeyListView->verticalScrollBar()->setValue(mSavedOffsetY);
     mSavedOffsetY = 0;
 }
 
 void KeySelectionDialog::slotSelectionChanged()
 {
     qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotSelectionChanged()";
 
     // (re)start the check selection timer. Checking the selection is delayed
     // because else drag-selection doesn't work very good (checking key trust
     // is slow).
     mCheckSelectionTimer->start(sCheckSelectionDelay);
 }
 
 namespace
 {
 struct AlreadyChecked {
     bool operator()(const GpgME::Key &key) const
     {
         return key.keyListMode() & GpgME::Validate;
     }
 };
 }
 
 void KeySelectionDialog::slotCheckSelection(KeyListViewItem *item)
 {
     qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotCheckSelection()";
 
     mCheckSelectionTimer->stop();
 
     mSelectedKeys.clear();
 
     if (!mKeyListView->isMultiSelection()) {
         if (item) {
             mSelectedKeys.push_back(item->key());
         }
     }
 
     for (KeyListViewItem *it = mKeyListView->firstChild(); it; it = it->nextSibling()) {
         if (it->isSelected()) {
             mSelectedKeys.push_back(it->key());
         }
     }
 
     mKeysToCheck.clear();
     std::remove_copy_if(mSelectedKeys.begin(), mSelectedKeys.end(), std::back_inserter(mKeysToCheck), AlreadyChecked());
     if (mKeysToCheck.empty()) {
         mOkButton->setEnabled(!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage));
         return;
     }
 
     // performed all fast checks - now for validating key listing:
     startValidatingKeyListing();
 }
 
 void KeySelectionDialog::startValidatingKeyListing()
 {
     if (mKeysToCheck.empty()) {
         return;
     }
 
     mListJobCount = 0;
     mTruncated = 0;
     mSavedOffsetY = mKeyListView->verticalScrollBar()->value();
 
     disconnectSignals();
     mKeyListView->setEnabled(false);
 
     std::vector<GpgME::Key> smime;
     std::vector<GpgME::Key> openpgp;
     for (std::vector<GpgME::Key>::const_iterator it = mKeysToCheck.begin(); it != mKeysToCheck.end(); ++it) {
         if (it->protocol() == GpgME::OpenPGP) {
             openpgp.push_back(*it);
         } else {
             smime.push_back(*it);
         }
     }
 
     if (!openpgp.empty()) {
         Q_ASSERT(mOpenPGPBackend);
         startKeyListJobForBackend(mOpenPGPBackend, openpgp, true /*validate*/);
     }
     if (!smime.empty()) {
         Q_ASSERT(mSMIMEBackend);
         startKeyListJobForBackend(mSMIMEBackend, smime, true /*validate*/);
     }
 
     Q_ASSERT(mListJobCount > 0);
 }
 
 bool KeySelectionDialog::rememberSelection() const
 {
     return mRememberCB && mRememberCB->isChecked();
 }
 
 void KeySelectionDialog::slotRMB(KeyListViewItem *item, const QPoint &p)
 {
     if (!item) {
         return;
     }
 
     mCurrentContextMenuItem = item;
 
     QMenu menu;
     menu.addAction(i18n("Recheck Key"), this, &KeySelectionDialog::slotRecheckKey);
     menu.exec(p);
 }
 
 void KeySelectionDialog::slotRecheckKey()
 {
     if (!mCurrentContextMenuItem || mCurrentContextMenuItem->key().isNull()) {
         return;
     }
 
     mKeysToCheck.clear();
     mKeysToCheck.push_back(mCurrentContextMenuItem->key());
 }
 
 void KeySelectionDialog::slotTryOk()
 {
     if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) {
         slotOk();
     }
 }
 
 void KeySelectionDialog::slotOk()
 {
     if (mCheckSelectionTimer->isActive()) {
         slotCheckSelection();
     }
 #if 0 // Laurent I don't understand why we returns here.
     // button could be disabled again after checking the selected key1
     if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) {
         return;
     }
 #endif
     mStartSearchTimer->stop();
     accept();
 }
 
 void KeySelectionDialog::slotCancel()
 {
     mCheckSelectionTimer->stop();
     mStartSearchTimer->stop();
     reject();
 }
 
 void KeySelectionDialog::slotSearch(const QString &text)
 {
     mSearchText = text.trimmed().toUpper();
     slotSearch();
 }
 
 void KeySelectionDialog::slotSearch()
 {
     mStartSearchTimer->setSingleShot(true);
     mStartSearchTimer->start(sCheckSelectionDelay);
 }
 
 void KeySelectionDialog::slotFilter()
 {
     if (mSearchText.isEmpty()) {
         showAllItems();
         return;
     }
 
     // OK, so we need to filter:
     QRegularExpression keyIdRegExp(QRegularExpression::anchoredPattern(QLatin1StringView("(?:0x)?[A-F0-9]{1,16}")), QRegularExpression::CaseInsensitiveOption);
     if (keyIdRegExp.match(mSearchText).hasMatch()) {
         if (mSearchText.startsWith(QLatin1StringView("0X"))) {
             // search for keyID only:
             filterByKeyID(mSearchText.mid(2));
         } else {
             // search for UID and keyID:
             filterByKeyIDOrUID(mSearchText);
         }
     } else {
         // search in UID:
         filterByUID(mSearchText);
     }
 }
 
 void KeySelectionDialog::filterByKeyID(const QString &keyID)
 {
     Q_ASSERT(keyID.length() <= 16);
     Q_ASSERT(!keyID.isEmpty()); // regexp in slotFilter should prevent these
     if (keyID.isEmpty()) {
         showAllItems();
     } else {
         for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
             item->setHidden(!item->text(0).remove(u' ').toUpper().startsWith(keyID));
         }
     }
 }
 
 static bool anyUIDMatches(const KeyListViewItem *item, const QRegularExpression &rx)
 {
     if (!item) {
         return false;
     }
 
     const std::vector<GpgME::UserID> uids = item->key().userIDs();
     for (auto it = uids.begin(); it != uids.end(); ++it) {
         if (it->id() && rx.match(QString::fromUtf8(it->id())).hasMatch()) {
             return true;
         }
     }
     return false;
 }
 
 void KeySelectionDialog::filterByKeyIDOrUID(const QString &str)
 {
     Q_ASSERT(!str.isEmpty());
 
     // match beginnings of words:
     QRegularExpression rx(QLatin1StringView("\\b") + QRegularExpression::escape(str), QRegularExpression::CaseInsensitiveOption);
 
     for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
         item->setHidden(!item->text(0).remove(u' ').toUpper().startsWith(str) && !anyUIDMatches(item, rx));
     }
 }
 
 void KeySelectionDialog::filterByUID(const QString &str)
 {
     Q_ASSERT(!str.isEmpty());
 
     // match beginnings of words:
     QRegularExpression rx(QLatin1StringView("\\b") + QRegularExpression::escape(str), QRegularExpression::CaseInsensitiveOption);
 
     for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
         item->setHidden(!anyUIDMatches(item, rx));
     }
 }
 
 void KeySelectionDialog::showAllItems()
 {
     for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
         item->setHidden(false);
     }
 }
 
 #include "moc_keyselectiondialog.cpp"
diff --git a/src/utils/formatting.cpp b/src/utils/formatting.cpp
index 977e117a..9c60945b 100644
--- a/src/utils/formatting.cpp
+++ b/src/utils/formatting.cpp
@@ -1,1750 +1,1760 @@
 /* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
     utils/formatting.cpp
 
     This file is part of Kleopatra, the KDE keymanager
     SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
     SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
 #include <config-libkleo.h>
 
 #include "formatting.h"
 
 #include "algorithm.h"
 #include "compat.h"
 #include "compliance.h"
 #include "cryptoconfig.h"
 #include "gnupg.h"
 #include "keyhelpers.h"
 
 #include <libkleo/dn.h>
 #include <libkleo/keycache.h>
 #include <libkleo/keygroup.h>
 
 #include <libkleo_debug.h>
 
 #include <KEmailAddress>
 #include <KLocalizedString>
 
 #include <QGpgME/CryptoConfig>
+#include <QGpgME/DN>
 #include <QGpgME/Protocol>
 
 #include <QDateTime>
 #include <QIcon>
 #include <QLocale>
 #include <QRegularExpression>
 #include <QString>
 
 #include <gpgme++/importresult.h>
 #include <gpgme++/key.h>
 
 #include <gpg-error.h>
 
 using namespace GpgME;
 using namespace Kleo;
 
 using namespace Qt::Literals::StringLiterals;
 
 namespace
 {
 QIcon iconForValidityAndCompliance(UserID::Validity validity, bool isCompliant)
 {
     switch (validity) {
     case UserID::Ultimate:
     case UserID::Full:
     case UserID::Marginal:
         return isCompliant ? Formatting::successIcon() : Formatting::infoIcon();
     case UserID::Never:
         return Formatting::errorIcon();
     case UserID::Undefined:
     case UserID::Unknown:
     default:
         return Formatting::infoIcon();
     }
 }
 QIcon iconForValidity(const UserID &userId)
 {
     const bool keyIsCompliant = !DeVSCompliance::isActive() || //
         (DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(userId.parent()));
     return iconForValidityAndCompliance(userId.validity(), keyIsCompliant);
 }
 }
 
 QIcon Formatting::IconProvider::icon(const GpgME::Key &key) const
 {
     return icon(key.userID(0));
 }
 
 QIcon Formatting::IconProvider::icon(const GpgME::UserID &userID) const
 {
     if (usage.canEncrypt() && !Kleo::canBeUsedForEncryption(userID.parent())) {
         return Formatting::errorIcon();
     }
     if (usage.canSign() && !Kleo::canBeUsedForSigning(userID.parent())) {
         return Formatting::errorIcon();
     }
     if (userID.parent().isBad() || userID.isBad()) {
         return Formatting::errorIcon();
     }
     if (Kleo::isRevokedOrExpired(userID)) {
         return Formatting::errorIcon();
     }
     return iconForValidity(userID);
 }
 
 QIcon Formatting::IconProvider::icon(const KeyGroup &group) const
 {
     if (usage.canEncrypt() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForEncryption)) {
         return Formatting::errorIcon();
     }
     if (usage.canSign() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForSigning)) {
         return Formatting::errorIcon();
     }
     return validityIcon(group);
 }
 
 QIcon Formatting::successIcon()
 {
     return QIcon::fromTheme(QStringLiteral("emblem-success"));
 }
 
 QIcon Formatting::infoIcon()
 {
     return QIcon::fromTheme(QStringLiteral("emblem-information"));
 }
 
 QIcon Formatting::questionIcon()
 {
     return QIcon::fromTheme(QStringLiteral("emblem-question"));
 }
 
 QIcon Formatting::unavailableIcon()
 {
     return QIcon::fromTheme(QStringLiteral("emblem-unavailable"));
 }
 
 QIcon Formatting::warningIcon()
 {
     return QIcon::fromTheme(QStringLiteral("emblem-warning"));
 }
 
 QIcon Formatting::errorIcon()
 {
     return QIcon::fromTheme(QStringLiteral("emblem-error"));
 }
 
 //
 // Name
 //
 
 QString Formatting::prettyName(int proto, const char *id, const char *name_, const char *comment_)
 {
     if (proto == GpgME::OpenPGP) {
         const QString name = QString::fromUtf8(name_);
         if (name.isEmpty()) {
             return QString();
         }
         const QString comment = QString::fromUtf8(comment_);
         if (comment.isEmpty()) {
             return name;
         }
         return QStringLiteral("%1 (%2)").arg(name, comment);
     }
 
     if (proto == GpgME::CMS) {
-        const DN subject(id);
+        const QGpgME::DN subject(id);
         const QString cn = subject[QStringLiteral("CN")].trimmed();
         if (cn.isEmpty()) {
+            subject.setAttributeOrder(DNAttributes::order());
             return subject.prettyDN();
         }
         return cn;
     }
 
     return QString();
 }
 
 QString Formatting::prettyNameAndEMail(int proto, const char *id, const char *name_, const char *email_, const char *comment_)
 {
     return prettyNameAndEMail(proto, QString::fromUtf8(id), QString::fromUtf8(name_), prettyEMail(email_, id), QString::fromUtf8(comment_));
 }
 
 QString Formatting::prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment)
 {
     if (proto == GpgME::OpenPGP) {
         if (name.isEmpty()) {
             if (email.isEmpty()) {
                 return QString();
             } else if (comment.isEmpty()) {
                 return QStringLiteral("<%1>").arg(email);
             } else {
                 return QStringLiteral("(%2) <%1>").arg(email, comment);
             }
         }
         if (email.isEmpty()) {
             if (comment.isEmpty()) {
                 return name;
             } else {
                 return QStringLiteral("%1 (%2)").arg(name, comment);
             }
         }
         if (comment.isEmpty()) {
             return QStringLiteral("%1 <%2>").arg(name, email);
         } else {
             return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment);
         }
     }
 
     if (proto == GpgME::CMS) {
-        const DN subject(id);
+        const QGpgME::DN subject(id);
         const QString cn = subject[QStringLiteral("CN")].trimmed();
         if (cn.isEmpty()) {
+            subject.setAttributeOrder(DN::attributeOrder());
             return subject.prettyDN();
         }
         return cn;
     }
     return QString();
 }
 
 QString Formatting::prettyUserID(const UserID &uid)
 {
     if (uid.parent().protocol() == GpgME::OpenPGP) {
         return prettyNameAndEMail(uid);
     }
     const QByteArray id = QByteArray(uid.id()).trimmed();
     if (id.startsWith('<')) {
         return prettyEMail(uid.email(), uid.id());
     }
     if (id.startsWith('(')) {
         // ### parse uri/dns:
         return QString::fromUtf8(uid.id());
     } else {
-        return DN(uid.id()).prettyDN();
+        return prettyDN(uid.id());
     }
 }
 
 QString Formatting::prettyKeyID(const char *id)
 {
     if (!id) {
         return QString();
     }
     return QLatin1StringView("0x") + QString::fromLatin1(id).toUpper();
 }
 
 QString Formatting::prettyNameAndEMail(const UserID &uid)
 {
     return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment());
 }
 
 QString Formatting::prettyNameAndEMail(const Key &key)
 {
     return prettyNameAndEMail(key.userID(0));
 }
 
 QString Formatting::prettyName(const Key &key)
 {
     return prettyName(key.userID(0));
 }
 
 QString Formatting::prettyName(const UserID &uid)
 {
     return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment());
 }
 
 QString Formatting::prettyName(const UserID::Signature &sig)
 {
     return prettyName(GpgME::OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment());
 }
 
 //
 // EMail
 //
 
 QString Formatting::prettyEMail(const Key &key)
 {
     for (unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) {
         const QString email = prettyEMail(key.userID(i));
         if (!email.isEmpty()) {
             return email;
         }
     }
     return QString();
 }
 
 QString Formatting::prettyEMail(const UserID &uid)
 {
     return prettyEMail(uid.email(), uid.id());
 }
 
 QString Formatting::prettyEMail(const UserID::Signature &sig)
 {
     return prettyEMail(sig.signerEmail(), sig.signerUserID());
 }
 
 QString Formatting::prettyEMail(const char *email_, const char *id)
 {
     QString email;
     QString name;
     QString comment;
     if (email_ && KEmailAddress::splitAddress(QString::fromUtf8(email_), name, email, comment) == KEmailAddress::AddressOk) {
         return email;
     } else {
         return DN(id)[QStringLiteral("EMAIL")].trimmed();
     }
 }
 
+QString Formatting::prettyDN(const char *utf8DN)
+{
+    QGpgME::DN dn{utf8DN};
+    dn.setAttributeOrder(Kleo::DN::attributeOrder());
+    return dn.prettyDN();
+}
+
 //
 // Tooltip
 //
 
 namespace
 {
 
 static QString protect_whitespace(QString s)
 {
     static const QLatin1Char SP(' ');
     static const QLatin1Char NBSP('\xA0');
     return s.replace(SP, NBSP);
 }
 
 template<typename T_arg>
 QString format_row(const QString &field, const T_arg &arg)
 {
     return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg);
 }
 QString format_row(const QString &field, const QString &arg)
 {
     return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg.toHtmlEscaped());
 }
 QString format_row(const QString &field, const char *arg)
 {
     return format_row(field, QString::fromUtf8(arg));
 }
 
 QString format_keytype(const Key &key)
 {
     const Subkey subkey = key.subkey(0);
     if (key.hasSecret()) {
         return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString()));
     } else {
         return i18n("%1-bit %2", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString()));
     }
 }
 
 QString format_subkeytype(const Subkey &subkey)
 {
     const auto algo = subkey.publicKeyAlgorithm();
 
     if (algo == Subkey::AlgoECC || algo == Subkey::AlgoECDSA || algo == Subkey::AlgoECDH || algo == Subkey::AlgoEDDSA) {
         return QString::fromStdString(subkey.algoName());
     }
     return i18n("%1-bit %2", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString()));
 }
 
 QString format_keyusage(const Key &key)
 {
     QStringList capabilities;
     if (Kleo::keyHasSign(key)) {
         if (key.isQualified()) {
             capabilities.push_back(i18n("Signing (Qualified)"));
         } else {
             capabilities.push_back(i18n("Signing"));
         }
     }
     if (Kleo::keyHasEncrypt(key)) {
         capabilities.push_back(i18n("Encryption"));
     }
     if (Kleo::keyHasCertify(key)) {
         capabilities.push_back(i18n("Certifying User IDs"));
     }
     if (Kleo::keyHasAuthenticate(key)) {
         capabilities.push_back(i18n("SSH Authentication"));
     }
     return capabilities.join(QLatin1StringView(", "));
 }
 
 QString format_subkeyusage(const Subkey &subkey)
 {
     QStringList capabilities;
     if (subkey.canSign()) {
         if (subkey.isQualified()) {
             capabilities.push_back(i18n("Signing (Qualified)"));
         } else {
             capabilities.push_back(i18n("Signing"));
         }
     }
     if (subkey.canEncrypt()) {
         capabilities.push_back(i18n("Encryption"));
     }
     if (subkey.canCertify()) {
         capabilities.push_back(i18n("Certifying User IDs"));
     }
     if (subkey.canAuthenticate()) {
         capabilities.push_back(i18n("SSH Authentication"));
     }
     return capabilities.join(QLatin1StringView(", "));
 }
 
 static QString time_t2string(time_t t)
 {
     const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t));
     return QLocale().toString(dt, QLocale::ShortFormat);
 }
 
 static QString make_red(const QString &txt)
 {
     return QLatin1StringView("<font color=\"red\">") + txt.toHtmlEscaped() + QLatin1StringView("</font>");
 }
 
 }
 
 static QString toolTipInternal(const GpgME::Key &key, const GpgME::UserID &userID, int flags)
 {
     if (flags == 0 || (key.protocol() != GpgME::CMS && key.protocol() != GpgME::OpenPGP)) {
         return QString();
     }
 
     const Subkey subkey = key.subkey(0);
 
     QString result;
     if (flags & Formatting::Validity) {
         if (key.protocol() == GpgME::OpenPGP || (key.keyListMode() & Validate)) {
             if (key.isDisabled()) {
                 result = i18n("Disabled");
             } else if (userID.isRevoked() || key.isRevoked()) {
                 result = make_red(i18n("Revoked"));
             } else if (key.isExpired()) {
                 result = make_red(i18n("Expired"));
             } else if (key.keyListMode() & GpgME::Validate) {
                 if (!userID.isNull()) {
                     if (userID.validity() >= UserID::Validity::Full) {
                         result = i18n("User ID is certified.");
                         const auto compliance = Formatting::complianceStringForUserID(userID);
                         if (!compliance.isEmpty()) {
                             result += QStringLiteral("<br>") + compliance;
                         }
                     } else {
                         result = i18n("User ID is not certified.");
                     }
                 } else {
                     unsigned int fullyTrusted = 0;
                     for (const auto &uid : key.userIDs()) {
                         if (uid.validity() >= UserID::Validity::Full) {
                             fullyTrusted++;
                         }
                     }
                     if (fullyTrusted == key.numUserIDs()) {
                         result = i18n("All User IDs are certified.");
                         const auto compliance = Formatting::complianceStringForKey(key);
                         if (!compliance.isEmpty()) {
                             result += QStringLiteral("<br>") + compliance;
                         }
                     } else {
                         result = i18np("One User ID is not certified.", "%1 User IDs are not certified.", key.numUserIDs() - fullyTrusted);
                     }
                 }
             } else {
                 result = i18n("The validity cannot be checked at the moment.");
             }
         } else {
             result = i18n("The validity cannot be checked at the moment.");
         }
     }
     if (flags == Formatting::Validity) {
         return result;
     }
 
     result += QLatin1StringView("<table border=\"0\">");
     if (key.protocol() == GpgME::CMS) {
         if (flags & Formatting::SerialNumber) {
             result += format_row(i18n("Serial number"), key.issuerSerial());
         }
         if (flags & Formatting::Issuer) {
             result += format_row(i18n("Issuer"), key.issuerName());
         }
     }
     if (flags & Formatting::UserIDs) {
         if (userID.isNull()) {
             const std::vector<UserID> uids = key.userIDs();
             if (!uids.empty()) {
                 result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User ID"), Formatting::prettyUserID(uids.front()));
             }
             if (uids.size() > 1) {
                 for (auto it = uids.begin() + 1, end = uids.end(); it != end; ++it) {
                     if (!it->isRevoked() && !it->isInvalid()) {
                         result += format_row(i18n("a.k.a."), Formatting::prettyUserID(*it));
                     }
                 }
             }
         } else {
             result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User ID"), Formatting::prettyUserID(userID));
         }
     }
     if (flags & Formatting::ExpiryDates) {
         result += format_row(i18n("Valid from"), time_t2string(subkey.creationTime()));
 
         if (!subkey.neverExpires()) {
             result += format_row(i18n("Valid until"), time_t2string(subkey.expirationTime()));
         }
     }
 
     if (flags & Formatting::CertificateType) {
         result += format_row(i18n("Type"), format_keytype(key));
     }
     if (flags & Formatting::CertificateUsage) {
         result += format_row(i18n("Usage"), format_keyusage(key));
     }
     if (flags & Formatting::KeyID) {
         result += format_row(i18n("Key ID"), QString::fromLatin1(key.keyID()));
     }
     if (flags & Formatting::Fingerprint) {
         result += format_row(i18n("Fingerprint"), key.primaryFingerprint());
     }
     if (flags & Formatting::OwnerTrust) {
         if (key.protocol() == GpgME::OpenPGP) {
             result += format_row(i18n("Certification trust"), Formatting::ownerTrustShort(key));
         } else if (key.isRoot()) {
             result += format_row(i18n("Trusted issuer?"), (userID.isNull() ? key.userID(0) : userID).validity() == UserID::Ultimate ? i18n("Yes") : i18n("No"));
         }
     }
     if (flags & Formatting::StorageLocation) {
         if (const char *card = subkey.cardSerialNumber()) {
             result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
         } else {
             result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
         }
     }
     if (flags & Formatting::Subkeys) {
         for (const auto &sub : key.subkeys()) {
             result += QLatin1StringView("<hr/>");
             result += format_row(i18n("Subkey"), sub.fingerprint());
             if (sub.isRevoked()) {
                 result += format_row(i18n("Status"), i18n("Revoked"));
             } else if (sub.isExpired()) {
                 result += format_row(i18n("Status"), i18n("Expired"));
             }
             if (flags & Formatting::ExpiryDates) {
                 result += format_row(i18n("Valid from"), time_t2string(sub.creationTime()));
 
                 if (!sub.neverExpires()) {
                     result += format_row(i18n("Valid until"), time_t2string(sub.expirationTime()));
                 }
             }
 
             if (flags & Formatting::CertificateType) {
                 result += format_row(i18n("Type"), format_subkeytype(sub));
             }
             if (flags & Formatting::CertificateUsage) {
                 result += format_row(i18n("Usage"), format_subkeyusage(sub));
             }
             if (flags & Formatting::StorageLocation) {
                 if (const char *card = sub.cardSerialNumber()) {
                     result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
                 } else {
                     result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
                 }
             }
         }
     }
     result += QLatin1StringView("</table>");
 
     return result;
 }
 
 QString Formatting::toolTip(const Key &key, int flags)
 {
     return toolTipInternal(key, UserID(), flags);
 }
 
 namespace
 {
 template<typename Container>
 QString getValidityStatement(const Container &keys)
 {
     const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) {
         return key.protocol() == GpgME::OpenPGP;
     });
     const bool allKeysAreValidated = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) {
         return key.keyListMode() & Validate;
     });
     if (allKeysAreOpenPGP || allKeysAreValidated) {
         const bool someKeysAreBad = std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::isBad));
         if (someKeysAreBad) {
             return i18n("Some keys are revoked, expired, disabled, or invalid.");
         } else {
             const bool allKeysAreFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity);
             if (allKeysAreFullyValid) {
                 return i18n("All keys are certified.");
             } else {
                 return i18n("Some keys are not certified.");
             }
         }
     }
     return i18n("The validity of the keys cannot be checked at the moment.");
 }
 }
 
 QString Formatting::toolTip(const KeyGroup &group, int flags)
 {
     static const unsigned int maxNumKeysForTooltip = 20;
 
     if (group.isNull()) {
         return QString();
     }
 
     const KeyGroup::Keys &keys = group.keys();
     if (keys.size() == 0) {
         return i18nc("@info:tooltip", "This group does not contain any keys.");
     }
 
     if (Kleo::any_of(keys, [](const auto &key) {
             return !key.hasEncrypt();
         })) {
         return i18nc("@info:tooltip", "Some of the certificates in this group cannot be used for encryption. Using this group can lead to unexpected results.");
     }
 
     const QString validity = (flags & Validity) ? getValidityStatement(keys) : QString();
     if (flags == Validity) {
         return validity;
     }
 
     // list either up to maxNumKeysForTooltip keys or (maxNumKeysForTooltip-1) keys followed by "and n more keys"
     const unsigned int numKeysForTooltip = keys.size() > maxNumKeysForTooltip ? maxNumKeysForTooltip - 1 : keys.size();
 
     QStringList result;
     result.reserve(3 + 2 + numKeysForTooltip + 2);
     if (!validity.isEmpty()) {
         result.push_back(QStringLiteral("<p>"));
         result.push_back(validity.toHtmlEscaped());
         result.push_back(QStringLiteral("</p>"));
     }
 
     result.push_back(QStringLiteral("<p>"));
     result.push_back(i18n("Keys:"));
     {
         auto it = keys.cbegin();
         for (unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) {
             result.push_back(QLatin1StringView("<br>") + Formatting::summaryLine(*it).toHtmlEscaped());
         }
     }
     if (keys.size() > numKeysForTooltip) {
         result.push_back(QLatin1StringView("<br>")
                          + i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip));
     }
     result.push_back(QStringLiteral("</p>"));
 
     return result.join(QLatin1Char('\n'));
 }
 
 QString Formatting::toolTip(const UserID &userID, int flags)
 {
     return toolTipInternal(userID.parent(), userID, flags);
 }
 
 //
 // Creation and Expiration
 //
 
 namespace
 {
 static QDate time_t2date(time_t t)
 {
     if (!t) {
         return {};
     }
     const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t));
     return dt.date();
 }
 static QString accessible_date_format()
 {
     return i18nc(
         "date format suitable for screen readers; "
         "d: day as a number without a leading zero, "
         "MMMM: localized month name, "
         "yyyy: year as a four digit number",
         "MMMM d, yyyy");
 }
 
 template<typename T>
 QString expiration_date_string(const T &tee, const QString &noExpiration)
 {
     return tee.neverExpires() ? noExpiration : Formatting::dateString(time_t2date(tee.expirationTime()));
 }
 template<typename T>
 QDate creation_date(const T &tee)
 {
     return time_t2date(tee.creationTime());
 }
 template<typename T>
 QDate expiration_date(const T &tee)
 {
     return time_t2date(tee.expirationTime());
 }
 }
 
 QString Formatting::dateString(time_t t)
 {
     return dateString(time_t2date(t));
 }
 
 QString Formatting::dateString(const QDate &date)
 {
     return QLocale().toString(date, QLocale::ShortFormat);
 }
 
 QString Formatting::accessibleDate(time_t t)
 {
     return accessibleDate(time_t2date(t));
 }
 
 QString Formatting::accessibleDate(const QDate &date)
 {
     return QLocale().toString(date, accessible_date_format());
 }
 
 QString Formatting::expirationDateString(const Key &key, const QString &noExpiration)
 {
     // if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD),
     // then we assume that the date is valid; if the date is zero for a remote key, then
     // we don't know if it's unknown or unlimited
     return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) //
         ? i18nc("@info the expiration date of the key is unknown", "unknown")
         : expiration_date_string(key.subkey(0), noExpiration);
 }
 
 QString Formatting::expirationDateString(const Subkey &subkey, const QString &noExpiration)
 {
     return expiration_date_string(subkey, noExpiration);
 }
 
 QString Formatting::expirationDateString(const UserID::Signature &sig, const QString &noExpiration)
 {
     return expiration_date_string(sig, noExpiration);
 }
 
 QDate Formatting::expirationDate(const Key &key)
 {
     return expiration_date(key.subkey(0));
 }
 
 QDate Formatting::expirationDate(const Subkey &subkey)
 {
     return expiration_date(subkey);
 }
 
 QDate Formatting::expirationDate(const UserID::Signature &sig)
 {
     return expiration_date(sig);
 }
 
 QString Formatting::accessibleExpirationDate(const Key &key, const QString &noExpiration)
 {
     // if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD),
     // then we assume that the date is valid; if the date is zero for a remote key, then
     // we don't know if it's unknown or unlimited
     return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) //
         ? i18nc("@info the expiration date of the key is unknown", "unknown")
         : accessibleExpirationDate(key.subkey(0), noExpiration);
 }
 
 QString Formatting::accessibleExpirationDate(const Subkey &subkey, const QString &noExpiration)
 {
     if (subkey.neverExpires()) {
         return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration;
     } else {
         return accessibleDate(expirationDate(subkey));
     }
 }
 
 QString Formatting::accessibleExpirationDate(const UserID::Signature &sig, const QString &noExpiration)
 {
     if (sig.neverExpires()) {
         return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration;
     } else {
         return accessibleDate(expirationDate(sig));
     }
 }
 
 QString Formatting::creationDateString(const Key &key)
 {
     return dateString(creation_date(key.subkey(0)));
 }
 
 QString Formatting::creationDateString(const Subkey &subkey)
 {
     return dateString(creation_date(subkey));
 }
 
 QString Formatting::creationDateString(const UserID::Signature &sig)
 {
     return dateString(creation_date(sig));
 }
 
 QDate Formatting::creationDate(const Key &key)
 {
     return creation_date(key.subkey(0));
 }
 
 QDate Formatting::creationDate(const Subkey &subkey)
 {
     return creation_date(subkey);
 }
 
 QDate Formatting::creationDate(const UserID::Signature &sig)
 {
     return creation_date(sig);
 }
 
 QString Formatting::accessibleCreationDate(const Key &key)
 {
     return accessibleDate(creationDate(key));
 }
 
 QString Formatting::accessibleCreationDate(const Subkey &subkey)
 {
     return accessibleDate(creationDate(subkey));
 }
 
 //
 // Types
 //
 
 QString Formatting::displayName(GpgME::Protocol p)
 {
     if (p == GpgME::CMS) {
         return i18nc("X.509/CMS encryption standard", "S/MIME");
     }
     if (p == GpgME::OpenPGP) {
         return i18n("OpenPGP");
     }
     return i18nc("Unknown encryption protocol", "Unknown");
 }
 
 QString Formatting::type(const Key &key)
 {
     return displayName(key.protocol());
 }
 
 QString Formatting::type(const Subkey &subkey)
 {
     return QString::fromUtf8(subkey.publicKeyAlgorithmAsString());
 }
 
 QString Formatting::type(const KeyGroup &group)
 {
     Q_UNUSED(group)
     return i18nc("a group of keys/certificates", "Group");
 }
 
 //
 // Status / Validity
 //
 
 QString Formatting::ownerTrustShort(const Key &key)
 {
     return ownerTrustShort(key.ownerTrust());
 }
 
 QString Formatting::ownerTrustShort(Key::OwnerTrust trust)
 {
     switch (trust) {
     case Key::Unknown:
         return i18nc("unknown trust level", "unknown");
     case Key::Never:
         return i18n("untrusted");
     case Key::Marginal:
         return i18nc("marginal trust", "marginal");
     case Key::Full:
         return i18nc("full trust", "full");
     case Key::Ultimate:
         return i18nc("ultimate trust", "ultimate");
     case Key::Undefined:
         return i18nc("undefined trust", "undefined");
     default:
         Q_ASSERT(!"unexpected owner trust value");
         break;
     }
     return QString();
 }
 
 QString Formatting::validityShort(const Subkey &subkey)
 {
     if (subkey.isDisabled()) {
         return i18n("disabled");
     }
     if (subkey.isRevoked()) {
         return i18n("revoked");
     }
     if (subkey.isExpired()) {
         return i18n("expired");
     }
     if (subkey.isInvalid()) {
         return i18n("invalid");
     }
     return i18nc("as in 'this subkey is ok'", "OK");
 }
 
 QString Formatting::validityShort(const UserID &uid)
 {
     if (uid.isRevoked()) {
         return i18n("revoked");
     }
     if (uid.isInvalid()) {
         return i18n("invalid");
     }
     switch (uid.validity()) {
     case UserID::Unknown:
         return i18nc("unknown trust level", "unknown");
     case UserID::Undefined:
         return i18nc("undefined trust", "undefined");
     case UserID::Never:
         return i18n("untrusted");
     case UserID::Marginal:
         return i18nc("marginal trust", "marginal");
     case UserID::Full:
         return i18nc("full trust", "full");
     case UserID::Ultimate:
         return i18nc("ultimate trust", "ultimate");
     }
     return QString();
 }
 
 QString Formatting::validityShort(const UserID::Signature &sig)
 {
     switch (sig.status()) {
     case UserID::Signature::NoError:
         if (!sig.isInvalid()) {
             /* See RFC 4880 Section 5.2.1 */
             switch (sig.certClass()) {
             case 0x10: /* Generic */
             case 0x11: /* Persona */
             case 0x12: /* Casual */
             case 0x13: /* Positive */
                 return i18n("valid");
             case 0x30:
                 return i18n("revoked");
             default:
                 return i18n("class %1", sig.certClass());
             }
         }
         [[fallthrough]];
         // fall through:
     case UserID::Signature::GeneralError:
         return i18n("invalid");
     case UserID::Signature::SigExpired:
         return i18n("expired");
     case UserID::Signature::KeyExpired:
         return i18n("certificate expired");
     case UserID::Signature::BadSignature:
         return i18nc("fake/invalid signature", "bad");
     case UserID::Signature::NoPublicKey: {
         /* GnuPG returns the same error for no public key as for expired
          * or revoked certificates. */
         const auto key = KeyCache::instance()->findByKeyIDOrFingerprint(sig.signerKeyID());
         if (key.isNull()) {
             return i18n("no public key");
         } else if (key.isDisabled()) {
             return i18n("key disabled");
         } else if (key.isRevoked()) {
             return i18n("key revoked");
         } else if (key.isExpired()) {
             return i18n("key expired");
         }
         /* can't happen */
         return QStringLiteral("unknown");
     }
     }
     return QString();
 }
 
 QIcon Formatting::validityIcon(const UserID::Signature &sig)
 {
     switch (sig.status()) {
     case UserID::Signature::NoError:
         if (!sig.isInvalid()) {
             /* See RFC 4880 Section 5.2.1 */
             switch (sig.certClass()) {
             case 0x10: /* Generic */
             case 0x11: /* Persona */
             case 0x12: /* Casual */
             case 0x13: /* Positive */
                 return Formatting::successIcon();
             case 0x30:
                 return Formatting::errorIcon();
             default:
                 return QIcon();
             }
         }
         [[fallthrough]];
         // fall through:
     case UserID::Signature::BadSignature:
     case UserID::Signature::GeneralError:
         return Formatting::errorIcon();
     case UserID::Signature::SigExpired:
     case UserID::Signature::KeyExpired:
         return Formatting::infoIcon();
     case UserID::Signature::NoPublicKey:
         return Formatting::questionIcon();
     }
     return QIcon();
 }
 
 QString Formatting::formatKeyLink(const Key &key)
 {
     if (key.isNull()) {
         return QString();
     }
     return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(QLatin1StringView(key.primaryFingerprint()), Formatting::prettyName(key));
 }
 
 QString Formatting::formatForComboBox(const GpgME::Key &key)
 {
     const QString name = prettyName(key);
     QString mail = prettyEMail(key);
     if (!mail.isEmpty()) {
         mail = QLatin1Char('<') + mail + QLatin1Char('>');
     }
     return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1StringView(key.keyID())).simplified();
 }
 
 QString Formatting::nameAndEmailForSummaryLine(const UserID &id)
 {
     Q_ASSERT(!id.isNull());
 
     const QString email = Formatting::prettyEMail(id);
     const QString name = Formatting::prettyName(id);
 
     if (name.isEmpty()) {
         return email;
     } else if (email.isEmpty()) {
         return name;
     } else {
         return QStringLiteral("%1 <%2>").arg(name, email);
     }
 }
 
 QString Formatting::nameAndEmailForSummaryLine(const Key &key)
 {
     Q_ASSERT(!key.isNull());
 
     const QString email = Formatting::prettyEMail(key);
     const QString name = Formatting::prettyName(key);
 
     if (name.isEmpty()) {
         return email;
     } else if (email.isEmpty()) {
         return name;
     } else {
         return QStringLiteral("%1 <%2>").arg(name, email);
     }
 }
 
 const char *Formatting::summaryToString(const Signature::Summary summary)
 {
     if (summary & Signature::Red) {
         return "RED";
     }
     if (summary & Signature::Green) {
         return "GREEN";
     }
     return "YELLOW";
 }
 
 QString Formatting::signatureToString(const Signature &sig, const Key &key)
 {
     if (sig.isNull()) {
         return QString();
     }
 
     const bool red = (sig.summary() & Signature::Red);
     const bool valid = (sig.summary() & Signature::Valid);
 
     if (red) {
         if (key.isNull()) {
             if (const char *fpr = sig.fingerprint()) {
                 return i18n("Bad signature by unknown certificate %1: %2", QString::fromLatin1(fpr), Formatting::errorAsString(sig.status()));
             } else {
                 return i18n("Bad signature by an unknown certificate: %1", Formatting::errorAsString(sig.status()));
             }
         } else {
             return i18n("Bad signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status()));
         }
 
     } else if (valid) {
         if (key.isNull()) {
             if (const char *fpr = sig.fingerprint()) {
                 return i18n("Good signature by unknown certificate %1.", QString::fromLatin1(fpr));
             } else {
                 return i18n("Good signature by an unknown certificate.");
             }
         } else {
             return i18n("Good signature by %1.", nameAndEmailForSummaryLine(key));
         }
 
     } else if (key.isNull()) {
         if (const char *fpr = sig.fingerprint()) {
             return i18n("Invalid signature by unknown certificate %1: %2", QString::fromLatin1(fpr), Formatting::errorAsString(sig.status()));
         } else {
             return i18n("Invalid signature by an unknown certificate: %1", Formatting::errorAsString(sig.status()));
         }
     } else {
         return i18n("Invalid signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status()));
     }
 }
 
 //
 // ImportResult
 //
 
 QString Formatting::importMetaData(const Import &import, const QStringList &ids)
 {
     const QString result = importMetaData(import);
     if (result.isEmpty()) {
         return QString();
     } else {
         return result + QLatin1Char('\n') + i18n("This certificate was imported from the following sources:") + QLatin1Char('\n') + ids.join(QLatin1Char('\n'));
     }
 }
 
 QString Formatting::importMetaData(const Import &import)
 {
     if (import.isNull()) {
         return QString();
     }
 
     if (import.error().isCanceled()) {
         return i18n("The import of this certificate was canceled.");
     }
     if (import.error()) {
         return i18n("An error occurred importing this certificate: %1", Formatting::errorAsString(import.error()));
     }
 
     const unsigned int status = import.status();
     if (status & Import::NewKey) {
         return (status & Import::ContainedSecretKey) ? i18n("This certificate was new to your keystore. The secret key is available.")
                                                      : i18n("This certificate is new to your keystore.");
     }
 
     QStringList results;
     if (status & Import::NewUserIDs) {
         results.push_back(i18n("New user-ids were added to this certificate by the import."));
     }
     if (status & Import::NewSignatures) {
         results.push_back(i18n("New signatures were added to this certificate by the import."));
     }
     if (status & Import::NewSubkeys) {
         results.push_back(i18n("New subkeys were added to this certificate by the import."));
     }
 
     return results.empty() ? i18n("The import contained no new data for this certificate. It is unchanged.") : results.join(QLatin1Char('\n'));
 }
 
 //
 // Overview in CertificateDetailsDialog
 //
 
 QString Formatting::formatOverview(const Key &key)
 {
     return toolTip(key, AllOptions);
 }
 
 QString Formatting::usageString(const Subkey &sub)
 {
     QStringList usageStrings;
     if (sub.canCertify()) {
         usageStrings << i18n("Certify");
     }
     if (sub.canSign()) {
         usageStrings << i18n("Sign");
     }
     if (sub.canEncrypt()) {
         usageStrings << i18n("Encrypt");
     }
     if (sub.canAuthenticate()) {
         usageStrings << i18n("Authenticate");
     }
     if (sub.canRenc()) {
         usageStrings << i18nc("Means 'Additional Decryption Subkey'; Don't try translating that, though.", "ADSK");
     }
     return usageStrings.join(QLatin1StringView(", "));
 }
 
 QString Formatting::summaryLine(const UserID &id)
 {
     return i18nc("name <email> (validity, protocol, creation date)",
                  "%1 (%2, %3, created: %4)",
                  nameAndEmailForSummaryLine(id),
                  Formatting::complianceStringShort(id),
                  displayName(id.parent().protocol()),
                  Formatting::creationDateString(id.parent()));
 }
 
 QString Formatting::summaryLine(const Key &key)
 {
     return nameAndEmailForSummaryLine(key) + QLatin1Char(' ')
         + i18nc("(validity, protocol, creation date)",
                 "(%1, %2, created: %3)",
                 Formatting::complianceStringShort(key),
                 displayName(key.protocol()),
                 Formatting::creationDateString(key));
 }
 
 QString Formatting::summaryLine(const KeyGroup &group)
 {
     switch (group.source()) {
     case KeyGroup::ApplicationConfig:
     case KeyGroup::GnuPGConfig:
         return i18ncp("name of group of keys (n key(s), validity)",
                       "%2 (1 key, %3)",
                       "%2 (%1 keys, %3)",
                       group.keys().size(),
                       group.name(),
                       Formatting::complianceStringShort(group));
     case KeyGroup::Tags:
         return i18ncp("name of group of keys (n key(s), validity, tag)",
                       "%2 (1 key, %3, tag)",
                       "%2 (%1 keys, %3, tag)",
                       group.keys().size(),
                       group.name(),
                       Formatting::complianceStringShort(group));
     default:
         return i18ncp("name of group of keys (n key(s), validity, group ...)",
                       "%2 (1 key, %3, unknown origin)",
                       "%2 (%1 keys, %3, unknown origin)",
                       group.keys().size(),
                       group.name(),
                       Formatting::complianceStringShort(group));
     }
 }
 
 // Icon for certificate selection indication
 QIcon Formatting::iconForUid(const UserID &uid)
 {
     if (Kleo::isRevokedOrExpired(uid)) {
         return Formatting::errorIcon();
     }
     return iconForValidity(uid);
 }
 
 QString Formatting::validity(const UserID &uid)
 {
     switch (uid.validity()) {
     case UserID::Ultimate:
         return i18n("The certificate is marked as your own.");
     case UserID::Full:
         return i18n("The certificate belongs to this recipient.");
     case UserID::Marginal:
         return i18n("The trust model indicates marginally that the certificate belongs to this recipient.");
     case UserID::Never:
         return i18n("This certificate should not be used.");
     case UserID::Undefined:
     case UserID::Unknown:
     default:
         return i18n("There is no indication that this certificate belongs to this recipient.");
     }
 }
 
 QString Formatting::validity(const KeyGroup &group)
 {
     if (group.isNull()) {
         return QString();
     }
 
     const KeyGroup::Keys &keys = group.keys();
     if (keys.size() == 0) {
         return i18n("This group does not contain any keys.");
     }
 
     return getValidityStatement(keys);
 }
 
 namespace
 {
 template<typename Container>
 UserID::Validity minimalValidity(const Container &keys)
 {
     const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [](int validity, const Key &key) {
         return std::min<int>(validity, minimalValidityOfNotRevokedUserIDs(key));
     });
     return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
 }
 
 template<typename Container>
 bool allKeysAreCompliant(const Container &keys)
 {
     if (!DeVSCompliance::isActive()) {
         return true;
     }
     if (!DeVSCompliance::isCompliant()) {
         return false;
     }
     return Kleo::all_of(keys, DeVSCompliance::keyIsCompliant);
 }
 }
 
 QIcon Formatting::validityIcon(const KeyGroup &group)
 {
     if (Kleo::any_of(group.keys(), std::mem_fn(&Key::isBad))) {
         return Formatting::errorIcon();
     }
     return iconForValidityAndCompliance(minimalValidity(group.keys()), allKeysAreCompliant(group.keys()));
 }
 
 QString Formatting::complianceMode()
 {
     const auto complianceValue = getCryptoConfigStringValue("gpg", "compliance");
     return complianceValue == QLatin1StringView("gnupg") ? QString() : complianceValue;
 }
 
 QString Formatting::complianceStringForKey(const GpgME::Key &key)
 {
     // There will likely be more in the future for other institutions
     // for now we only have DE-VS
     if (DeVSCompliance::isCompliant()) {
         return isRemoteKey(key) //
             ? i18nc("@info the compliance of the key with certain requirements is unknown", "unknown")
             : DeVSCompliance::name(DeVSCompliance::keyIsCompliant(key));
     }
     return QString();
 }
 
 QString Formatting::complianceStringForUserID(const GpgME::UserID &userID)
 {
     // There will likely be more in the future for other institutions
     // for now we only have DE-VS
     if (DeVSCompliance::isCompliant()) {
         return isRemoteKey(userID.parent()) //
             ? i18nc("@info the compliance of the key with certain requirements is unknown", "unknown")
             : DeVSCompliance::name(DeVSCompliance::userIDIsCompliant(userID));
     }
     return QString();
 }
 
 QString Formatting::complianceStringShort(const GpgME::UserID &id)
 {
     if (DeVSCompliance::isCompliant() && DeVSCompliance::userIDIsCompliant(id)) {
         return QStringLiteral("★ ") + DeVSCompliance::name(true);
     }
     const bool keyValidityChecked = (id.parent().keyListMode() & GpgME::Validate);
     if (keyValidityChecked && id.validity() >= UserID::Full) {
         return i18nc("As in 'this user ID is valid.'", "certified");
     }
     if (id.parent().isDisabled()) {
         return i18n("disabled");
     }
     if (id.parent().isRevoked() || id.isRevoked()) {
         return i18n("revoked");
     }
     if (id.parent().isExpired() || isExpired(id)) {
         return i18n("expired");
     }
     if (id.parent().isInvalid() || id.isInvalid()) {
         return i18n("invalid");
     }
     if (keyValidityChecked) {
         return i18nc("As in 'this user ID is not certified'", "not certified");
     }
 
     return i18nc("The validity of this user ID has not been/could not be checked", "not checked");
 }
 
 QString Formatting::complianceStringShort(const GpgME::Key &key)
 {
     if (DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(key)) {
         return QStringLiteral("★ ") + DeVSCompliance::name(true);
     }
     const bool keyValidityChecked = (key.keyListMode() & GpgME::Validate);
     if (key.isDisabled()) {
         return i18n("disabled");
     }
     if (key.isRevoked()) {
         return i18n("revoked");
     }
     if (key.isExpired()) {
         return i18n("expired");
     }
     if (key.isInvalid()) {
         return i18n("invalid");
     }
     if (keyValidityChecked && Kleo::allUserIDsHaveFullValidity(key)) {
         return i18nc("As in all user IDs are valid.", "certified");
     }
     if (keyValidityChecked) {
         return i18nc("As in not all user IDs are valid.", "not certified");
     }
 
     return i18nc("The validity of the user IDs has not been/could not be checked", "not checked");
 }
 
 QString Formatting::complianceStringShort(const KeyGroup &group)
 {
     const KeyGroup::Keys &keys = group.keys();
 
     const bool allKeysFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity);
     if (allKeysFullyValid) {
         return i18nc("As in all keys are valid.", "all certified");
     }
 
     return i18nc("As in not all keys are valid.", "not all certified");
 }
 
 QString Formatting::prettyID(const char *id)
 {
     if (!id) {
         return QString();
     }
     QString ret = QString::fromLatin1(id).toUpper();
     if (ret.size() == 64) {
         // looks like a V5 fingerprint; format the first 25 bytes as 10 groups of 5 hex characters
         ret.truncate(50);
         return ret.replace(QRegularExpression(QStringLiteral("(.....)")), QStringLiteral("\\1 ")).trimmed();
     }
     ret = ret.replace(QRegularExpression(QStringLiteral("(....)")), QStringLiteral("\\1 ")).trimmed();
     // For the standard 10 group V4 fingerprint let us use a double space in the
     // middle to increase readability
     if (ret.size() == 49) {
         ret.insert(24, QLatin1Char(' '));
     }
     return ret;
 }
 
 QString Formatting::accessibleHexID(const char *id)
 {
     static const QRegularExpression groupOfFourRegExp{QStringLiteral("(?:(.)(.)(.)(.))")};
     static const QRegularExpression groupOfFiveRegExp{QStringLiteral("(?:(.)(.)(.)(.)(.))")};
 
     QString ret;
     ret = QString::fromLatin1(id);
     if (ret.size() == 64) {
         ret.truncate(50);
         return ret.replace(groupOfFiveRegExp, QStringLiteral("\\1 \\2 \\3 \\4 \\5, ")).chopped(2);
     }
     if (!ret.isEmpty() && (ret.size() % 4 == 0)) {
         ret = ret.replace(groupOfFourRegExp, QStringLiteral("\\1 \\2 \\3 \\4, ")).chopped(2);
     }
     return ret;
 }
 
 QString Formatting::origin(int o)
 {
     switch (o) {
     case Key::OriginKS:
         return i18n("Keyserver");
     case Key::OriginDane:
         return QStringLiteral("DANE");
     case Key::OriginWKD:
         return QStringLiteral("WKD");
     case Key::OriginURL:
         return QStringLiteral("URL");
     case Key::OriginFile:
         return i18n("File import");
     case Key::OriginSelf:
         return i18n("Generated");
     case Key::OriginOther:
     case Key::OriginUnknown:
     default:
         return {};
     }
 }
 
 namespace
 {
 QString formatTrustScope(const char *trustScope)
 {
     static const QRegularExpression escapedNonAlphaNum{QStringLiteral(R"(\\([^0-9A-Za-z]))")};
 
     const auto scopeRegExp = QString::fromUtf8(trustScope);
     if (scopeRegExp.startsWith(u"<[^>]+[@.]") && scopeRegExp.endsWith(u">$")) {
         // looks like a trust scope regular expression created by gpg
         auto domain = scopeRegExp.mid(10, scopeRegExp.size() - 10 - 2);
         domain.replace(escapedNonAlphaNum, QStringLiteral(R"(\1)"));
         return domain;
     }
     return scopeRegExp;
 }
 }
 
 QString Formatting::trustSignatureDomain(const GpgME::UserID::Signature &sig)
 {
     return formatTrustScope(sig.trustScope());
 }
 
 QString Formatting::trustSignature(const GpgME::UserID::Signature &sig)
 {
     switch (sig.trustValue()) {
     case TrustSignatureTrust::Partial:
         return i18nc("Certifies this key as partially trusted introducer for 'domain name'.",
                      "Certifies this key as partially trusted introducer for '%1'.",
                      trustSignatureDomain(sig));
     case TrustSignatureTrust::Complete:
         return i18nc("Certifies this key as fully trusted introducer for 'domain name'.",
                      "Certifies this key as fully trusted introducer for '%1'.",
                      trustSignatureDomain(sig));
     default:
         return {};
     }
 }
 
 QString Formatting::errorAsString(const GpgME::Error &error)
 {
 #ifdef Q_OS_WIN
     // On Windows, we set GpgME resp. libgpg-error to return (translated) error messages as UTF-8
 #if GPGMEPP_ERROR_HAS_ASSTDSTRING
     const std::string s = error.asStdString();
     qCDebug(LIBKLEO_LOG) << __func__ << "gettext_use_utf8(-1) returns" << gettext_use_utf8(-1);
     qCDebug(LIBKLEO_LOG) << __func__ << "error:" << s;
     qCDebug(LIBKLEO_LOG) << __func__ << "error (percent-encoded):" << QByteArray::fromStdString(s).toPercentEncoding();
     return QString::fromStdString(s);
 #else
     const char *s = error.asString();
     qCDebug(LIBKLEO_LOG) << __func__ << "gettext_use_utf8(-1) returns" << gettext_use_utf8(-1);
     qCDebug(LIBKLEO_LOG) << __func__ << "error:" << s;
     qCDebug(LIBKLEO_LOG) << __func__ << "error (percent-encoded):" << QByteArray{s}.toPercentEncoding();
     return QString::fromUtf8(s);
 #endif
 #else
 #if GPGMEPP_ERROR_HAS_ASSTDSTRING
     const std::string s = error.asStdString();
     return QString::fromLocal8Bit(QByteArrayView{s.data(), qsizetype(s.size())});
 #else
     return QString::fromLocal8Bit(error.asString());
 #endif
 #endif
 }
 
 QString Formatting::prettyAlgorithmName(const std::string &algorithm)
 {
     static const std::map<std::string, QString> displayNames = {
         {"brainpoolP256r1", i18nc("@info", "ECC (Brainpool P-256)")},
         {"brainpoolP384r1", i18nc("@info", "ECC (Brainpool P-384)")},
         {"brainpoolP512r1", i18nc("@info", "ECC (Brainpool P-512)")},
         {"curve25519", i18nc("@info", "ECC (Curve25519)")},
         {"curve448", i18nc("@info", "ECC (Curve448)")},
         {"ed25519", i18nc("@info", "ECC (Ed25519)")},
         {"ed448", i18nc("@info", "ECC (Ed448)")},
         {"cv25519", i18nc("@info", "ECC (Cv25519)")},
         {"cv448", i18nc("@info", "ECC (Cv448)")},
         {"nistp256", i18nc("@info", "ECC (NIST P-256)")},
         {"nistp384", i18nc("@info", "ECC (NIST P-384)")},
         {"nistp521", i18nc("@info", "ECC (NIST P-521)")},
         {"rsa1024", i18nc("@info", "RSA 1024")},
         {"rsa2048", i18nc("@info", "RSA 2048")},
         {"rsa3072", i18nc("@info", "RSA 3072")},
         {"rsa4096", i18nc("@info", "RSA 4096")},
         {"dsa1024", i18nc("@info", "DSA 1024")},
         {"dsa2048", i18nc("@info", "DSA 2048")},
         {"elg1024", i18nc("@info", "Elgamal 1024")},
         {"elg2048", i18nc("@info", "Elgamal 2048")},
         {"elg3072", i18nc("@info", "Elgamal 3072")},
         {"elg4096", i18nc("@info", "Elgamal 4096")},
     };
     const auto it = displayNames.find(algorithm);
     return (it != displayNames.end()) ? it->second : QString::fromStdString(algorithm);
 }
 
 static QString formatValidSignatureWithTrustLevel(const GpgME::UserID &id)
 {
     if (id.isNull()) {
         return QString();
     }
     switch (id.validity()) {
     case GpgME::UserID::Marginal:
         return i18n("The signature is valid but the trust in the certificate's validity is only marginal.");
     case GpgME::UserID::Full:
         return i18n("The signature is valid and the certificate's validity is fully trusted.");
     case GpgME::UserID::Ultimate:
         return i18n("The signature is valid and the certificate's validity is ultimately trusted.");
     case GpgME::UserID::Never:
         return i18n("The signature is valid but the certificate's validity is <em>not trusted</em>.");
     case GpgME::UserID::Unknown:
         return i18n("The signature is valid but the certificate's validity is unknown.");
     case GpgME::UserID::Undefined:
     default:
         return i18n("The signature is valid but the certificate's validity is undefined.");
     }
 }
 
 static QString renderKeyLink(const QString &fpr, const QString &text)
 {
     return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(fpr, text.toHtmlEscaped());
 }
 
 static QString renderKey(const GpgME::Key &key)
 {
     if (key.isNull()) {
         return i18n("Unknown certificate");
     }
 
     return renderKeyLink(QLatin1StringView(key.primaryFingerprint()),
                          i18nc("User ID (Key ID)", "%1 (%2)", Formatting::prettyNameAndEMail(key), Formatting::prettyID(key.subkey(0).keyID())));
 }
 
 static QString formatSigningInformation(const GpgME::Signature &sig, const GpgME::Key &key)
 {
     if (sig.isNull()) {
         return QString();
     }
     QString text;
     const QDateTime dt = sig.creationTime() != 0 ? QDateTime::fromSecsSinceEpoch(quint32(sig.creationTime())) : QDateTime();
 
     if (key.isNull()) {
         const auto id =
             QStringLiteral("<br/><a href='certificate:%1'>%2</a>").arg(QString::fromLatin1(sig.fingerprint()), Formatting::prettyID(sig.fingerprint()));
         if (dt.isValid()) {
             return i18nc("1 is a date",
                          "Signature created on %1 using an unknown certificate with fingerprint %2",
                          QLocale().toString(dt, QLocale::ShortFormat),
                          id);
         }
         return i18n("Signature created using an unknown certificate with fingerprint %1", id);
     }
 
     if (dt.isValid()) {
         text += i18nc("1 is a date", "Signature created on %1 with certificate: %2", QLocale().toString(dt, QLocale::ShortFormat), renderKey(key));
     } else {
         text += i18n("Signature created with certificate: %1", renderKey(key));
     }
 
     if (Kleo::DeVSCompliance::isCompliant() && ((sig.summary() & GpgME::Signature::Valid) || (sig.summary() & GpgME::Signature::Green))) {
         text += (QStringLiteral("<br/>")
                  + (sig.isDeVs() ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
                                          "The signature is %1",
                                          Kleo::DeVSCompliance::name(true))
                                  : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
                                          "The signature <b>is not</b> %1.",
                                          Kleo::DeVSCompliance::name(true))));
     }
 
     return text;
 }
 
 static QString signatureSummaryToString(GpgME::Signature::Summary summary)
 {
     if (summary & GpgME::Signature::None) {
         return i18n("Error: Signature not verified");
     } else if ((summary & GpgME::Signature::Valid) || (summary & GpgME::Signature::Green)) {
         return i18n("Good signature");
     } else if (summary & GpgME::Signature::KeyRevoked) {
         return i18n("Signing certificate was revoked");
     } else if (summary & GpgME::Signature::KeyExpired) {
         return i18n("Signing certificate is expired");
     } else if (summary & GpgME::Signature::KeyMissing) {
         return i18n("Certificate is not available");
     } else if (summary & GpgME::Signature::SigExpired) {
         return i18n("Signature expired");
     } else if (summary & GpgME::Signature::CrlMissing) {
         return i18n("CRL missing");
     } else if (summary & GpgME::Signature::CrlTooOld) {
         return i18n("CRL too old");
     } else if (summary & GpgME::Signature::BadPolicy) {
         return i18n("Bad policy");
     } else if (summary & GpgME::Signature::SysError) {
         return i18n("System error"); // ### retrieve system error details?
     } else if (summary & GpgME::Signature::Red) {
         return i18n("Bad signature");
     }
     return QString();
 }
 
 static QLatin1StringView stripAngleBrackets(const QLatin1StringView &str)
 {
     if (str.isEmpty()) {
         return str;
     }
     if (str[0] == '<' && str[str.size() - 1] == '>') {
         return str.mid(1, str.size() - 2);
     }
     return str;
 }
 
 QString Formatting::email(const GpgME::UserID &uid)
 {
     if (uid.parent().protocol() == GpgME::OpenPGP) {
         const QLatin1StringView email(uid.email());
         if (!email.isEmpty()) {
             return stripAngleBrackets(email).toString();
         }
         return {};
     }
 
     Q_ASSERT(uid.parent().protocol() == GpgME::CMS);
 
     const QLatin1StringView id(uid.id());
     if (!id.isEmpty()) {
         if (id[0] == '<') {
             return stripAngleBrackets(id).toString();
         }
         return Kleo::DN(id)[QStringLiteral("EMAIL")].trimmed();
     }
     return {};
 }
 
 static GpgME::UserID findUserIDByMailbox(const GpgME::Key &key, const QString &email)
 {
     const auto userIDs{key.userIDs()};
     for (const GpgME::UserID &id : userIDs) {
         if (Formatting::email(id).compare(email, Qt::CaseInsensitive)) {
             return id;
         }
     }
     return {};
 }
 
 QString Kleo::Formatting::prettySignature(const GpgME::Signature &sig, const QString &sender)
 {
     if (sig.isNull()) {
         return QString();
     }
 
     const GpgME::Key key = Kleo::KeyCache::instance()->findSigner(sig);
 
     const QString text = formatSigningInformation(sig, key) + QLatin1StringView("<br/>");
 
     // Green
     if (sig.summary() & GpgME::Signature::Valid) {
         GpgME::UserID id = findUserIDByMailbox(key, sender);
         if (id.isNull()) {
             for (int i = 0, count = key.userIDs().size(); i < count; i++) {
                 id = key.userID(i);
                 if (!id.isNull()) {
                     break;
                 }
             }
         }
 
         return text + formatValidSignatureWithTrustLevel(!id.isNull() ? id : key.userID(0));
     }
 
     // Red
     if ((sig.summary() & GpgME::Signature::Red)) {
         const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary()));
         if (sig.summary() & GpgME::Signature::SysError) {
             return ret + QStringLiteral(" (%1)").arg(Kleo::Formatting::errorAsString(sig.status()));
         }
         return ret;
     }
 
     // Key missing
     if ((sig.summary() & GpgME::Signature::KeyMissing)) {
         return text + i18n("You can search the certificate on a keyserver or import it from a file.");
     }
 
     // Yellow
     if ((sig.validity() & GpgME::Signature::Validity::Undefined) //
         || (sig.validity() & GpgME::Signature::Validity::Unknown) //
         || (sig.summary() == GpgME::Signature::Summary::None)) {
         return text
             + (key.protocol() == GpgME::OpenPGP
                    ? i18n("The used key is not certified by you or any trusted person.")
                    : i18n("The used certificate is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown."));
     }
 
     // Catch all fall through
     const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary()));
     if (sig.summary() & GpgME::Signature::SysError) {
         return ret + QStringLiteral(" (%1)").arg(Kleo::Formatting::errorAsString(sig.status()));
     }
     return ret;
 }
diff --git a/src/utils/formatting.h b/src/utils/formatting.h
index 0c027ec9..645c3371 100644
--- a/src/utils/formatting.h
+++ b/src/utils/formatting.h
@@ -1,240 +1,242 @@
 /* -*- mode: c++; c-basic-offset:4 -*-
     utils/formatting.h
 
     This file is part of Kleopatra, the KDE keymanager
     SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
     SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
 #pragma once
 
 #include "keyusage.h"
 
 #include "kleo_export.h"
 
 #include <QStringList>
 
 #include <gpgme++/key.h>
 
 class QString;
 class QDate;
 class QIcon;
 
 namespace GpgME
 {
 class Error;
 class Import;
 }
 
 namespace Kleo
 {
 class KeyGroup;
 
 namespace Formatting
 {
 
 class KLEO_EXPORT IconProvider
 {
 public:
     inline explicit IconProvider(KeyUsage::Flags requiredUsages)
         : usage{requiredUsages}
     {
     }
 
     QIcon icon(const GpgME::Key &key) const;
     QIcon icon(const KeyGroup &group) const;
     QIcon icon(const GpgME::UserID &userID) const;
 
 private:
     KeyUsage usage;
 };
 
 KLEO_EXPORT QIcon successIcon();
 KLEO_EXPORT QIcon infoIcon();
 KLEO_EXPORT QIcon questionIcon();
 KLEO_EXPORT QIcon unavailableIcon();
 KLEO_EXPORT QIcon warningIcon();
 KLEO_EXPORT QIcon errorIcon();
 
 KLEO_EXPORT QString prettyNameAndEMail(int proto, const char *id, const char *name, const char *email, const char *comment = nullptr);
 KLEO_EXPORT QString prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment = {});
 KLEO_EXPORT QString prettyNameAndEMail(const GpgME::Key &key);
 KLEO_EXPORT QString prettyNameAndEMail(const GpgME::UserID &key);
 
 KLEO_EXPORT QString prettyUserID(const GpgME::UserID &uid);
 KLEO_EXPORT QString prettyKeyID(const char *id);
 
 KLEO_EXPORT QString prettyName(int proto, const char *id, const char *name, const char *comment = nullptr);
 KLEO_EXPORT QString prettyName(const GpgME::Key &key);
 KLEO_EXPORT QString prettyName(const GpgME::UserID &uid);
 KLEO_EXPORT QString prettyName(const GpgME::UserID::Signature &sig);
 
 KLEO_EXPORT QString prettyEMail(const char *email, const char *id);
 KLEO_EXPORT QString prettyEMail(const GpgME::Key &key);
 KLEO_EXPORT QString prettyEMail(const GpgME::UserID &uid);
 KLEO_EXPORT QString prettyEMail(const GpgME::UserID::Signature &sig);
 
+KLEO_EXPORT QString prettyDN(const char *utf8DN);
+
 /* Formats a fingerprint or keyid into groups of four */
 KLEO_EXPORT QString prettyID(const char *id);
 KLEO_EXPORT QString accessibleHexID(const char *id);
 
 /**
  * Formats a signature from a VerificationResult.
  *
  * @param signature The signature to display.
  * @param sender The sender of the signature, if multiple UserIds are found, this will be the displayed one otherwise the first non empty UserID will be
  * displayed.
  *
  * @note The resulting string will contains links to the key in the following format "key:<fingerprint>".
  */
 KLEO_EXPORT QString prettySignature(const GpgME::Signature &signature, const QString &sender);
 
 // clang-format off
 enum ToolTipOption {
     KeyID            = 0x001,
     Validity         = 0x002,
     StorageLocation  = 0x004,
     SerialNumber     = 0x008,
     Issuer           = 0x010,
     Subject          = 0x020,
     ExpiryDates      = 0x040,
     CertificateType  = 0x080,
     CertificateUsage = 0x100,
     Fingerprint      = 0x200,
     UserIDs          = 0x400,
     OwnerTrust       = 0x800,
     Subkeys          = 0x1000,
 
     AllOptions       = 0xffff
 };
 // clang-format on
 
 KLEO_EXPORT QString toolTip(const GpgME::Key &key, int opts);
 KLEO_EXPORT QString toolTip(const Kleo::KeyGroup &group, int opts);
 KLEO_EXPORT QString toolTip(const GpgME::UserID &userID, int opts);
 
 /// Returns expiration date of @p key as string, or @p noExpiration if the key doesn't expire.
 KLEO_EXPORT QString expirationDateString(const GpgME::Key &key, const QString &noExpiration = {});
 /// Returns expiration date of @p subkey as string, or @p noExpiration if the subkey doesn't expire.
 KLEO_EXPORT QString expirationDateString(const GpgME::Subkey &subkey, const QString &noExpiration = {});
 /// Returns expiration date of @p sig as string, or @p noExpiration if the signature doesn't expire.
 KLEO_EXPORT QString expirationDateString(const GpgME::UserID::Signature &sig, const QString &noExpiration = {});
 KLEO_EXPORT QDate expirationDate(const GpgME::Key &key);
 KLEO_EXPORT QDate expirationDate(const GpgME::Subkey &subkey);
 KLEO_EXPORT QDate expirationDate(const GpgME::UserID::Signature &sig);
 /**
  * Returns expiration date of @p key as string suitable for screen readers.
  * If the key doesn't expire, then it returns @p noExpiration if @p noExpiration is not empty. Otherwise,
  * returns the localization of "unlimited".
  */
 KLEO_EXPORT QString accessibleExpirationDate(const GpgME::Key &key, const QString &noExpiration = {});
 /**
  * Returns expiration date of @p subkey as string suitable for screen readers.
  * If the subkey doesn't expire, then it returns @p noExpiration if @p noExpiration is not empty. Otherwise,
  * returns the localization of "unlimited".
  */
 KLEO_EXPORT QString accessibleExpirationDate(const GpgME::Subkey &subkey, const QString &noExpiration = {});
 /**
  * Returns expiration date of @p sig as string suitable for screen readers.
  * If the signature doesn't expire, then it returns @p noExpiration if @p noExpiration is not empty. Otherwise,
  * returns the localization of "unlimited".
  */
 KLEO_EXPORT QString accessibleExpirationDate(const GpgME::UserID::Signature &sig, const QString &noExpiration = {});
 
 KLEO_EXPORT QString creationDateString(const GpgME::Key &key);
 KLEO_EXPORT QString creationDateString(const GpgME::Subkey &subkey);
 KLEO_EXPORT QString creationDateString(const GpgME::UserID::Signature &sig);
 KLEO_EXPORT QDate creationDate(const GpgME::Key &key);
 KLEO_EXPORT QDate creationDate(const GpgME::Subkey &subkey);
 KLEO_EXPORT QDate creationDate(const GpgME::UserID::Signature &sig);
 KLEO_EXPORT QString accessibleCreationDate(const GpgME::Key &key);
 KLEO_EXPORT QString accessibleCreationDate(const GpgME::Subkey &subkey);
 
 /* Convert a GPGME style time or a QDate to a localized string */
 KLEO_EXPORT QString dateString(time_t t);
 KLEO_EXPORT QString dateString(const QDate &date);
 KLEO_EXPORT QString accessibleDate(time_t t);
 KLEO_EXPORT QString accessibleDate(const QDate &date);
 
 KLEO_EXPORT QString displayName(GpgME::Protocol prot);
 KLEO_EXPORT QString type(const GpgME::Key &key);
 KLEO_EXPORT QString type(const GpgME::Subkey &subkey);
 KLEO_EXPORT QString type(const Kleo::KeyGroup &group);
 
 KLEO_EXPORT QString ownerTrustShort(const GpgME::Key &key);
 KLEO_EXPORT QString ownerTrustShort(GpgME::Key::OwnerTrust trust);
 
 KLEO_EXPORT QString validityShort(const GpgME::Subkey &subkey);
 KLEO_EXPORT QString validityShort(const GpgME::UserID &uid);
 KLEO_EXPORT QString validityShort(const GpgME::UserID::Signature &sig);
 KLEO_EXPORT QIcon validityIcon(const GpgME::UserID::Signature &sig);
 /* A sentence about the validity of the UserID */
 KLEO_EXPORT QString validity(const GpgME::UserID &uid);
 KLEO_EXPORT QString validity(const Kleo::KeyGroup &group);
 KLEO_EXPORT QIcon validityIcon(const Kleo::KeyGroup &group);
 
 KLEO_EXPORT QString formatForComboBox(const GpgME::Key &key);
 
 KLEO_EXPORT QString formatKeyLink(const GpgME::Key &key);
 
 KLEO_EXPORT QString signatureToString(const GpgME::Signature &sig, const GpgME::Key &key);
 
 KLEO_EXPORT const char *summaryToString(const GpgME::Signature::Summary summary);
 
 KLEO_EXPORT QString importMetaData(const GpgME::Import &import);
 KLEO_EXPORT QString importMetaData(const GpgME::Import &import, const QStringList &sources);
 
 KLEO_EXPORT QString formatOverview(const GpgME::Key &key);
 KLEO_EXPORT QString usageString(const GpgME::Subkey &subkey);
 KLEO_EXPORT QString summaryLine(const GpgME::UserID &id);
 KLEO_EXPORT QString summaryLine(const GpgME::Key &key);
 KLEO_EXPORT QString summaryLine(const KeyGroup &group);
 KLEO_EXPORT QString nameAndEmailForSummaryLine(const GpgME::Key &key);
 KLEO_EXPORT QString nameAndEmailForSummaryLine(const GpgME::UserID &id);
 
 KLEO_EXPORT QIcon iconForUid(const GpgME::UserID &uid);
 
 /* The compliance mode of the gnupg system. Empty if compliance
  * mode is not set.
  * Use Kleo::gnupgComplianceMode() instead.
  */
 KLEO_DEPRECATED_EXPORT QString complianceMode();
 
 /* A sentence if the key confirms to the current compliance mode */
 KLEO_EXPORT QString complianceStringForKey(const GpgME::Key &key);
 KLEO_EXPORT QString complianceStringForUserID(const GpgME::UserID &userID);
 
 /* A single word for use in keylists to describe the validity of the
  * given key, including any conformance statements relevant to the
  * current conformance mode.  */
 KLEO_EXPORT QString complianceStringShort(const GpgME::Key &key);
 KLEO_EXPORT QString complianceStringShort(const GpgME::UserID &id);
 KLEO_EXPORT QString complianceStringShort(const Kleo::KeyGroup &group);
 
 /* The origin of the key mapped to a localized string */
 KLEO_EXPORT QString origin(int o);
 
 /* Human-readable trust signature scope (for trust signature regexp created by GnuPG) */
 KLEO_EXPORT QString trustSignatureDomain(const GpgME::UserID::Signature &sig);
 /* Summary of trust signature properties */
 KLEO_EXPORT QString trustSignature(const GpgME::UserID::Signature &sig);
 
 /**
  * Returns the value of Error::asString() for the error \p error as Unicode string.
  */
 KLEO_EXPORT QString errorAsString(const GpgME::Error &error);
 
 /**
  * Returns a name suitable for being displayed for the GPG algorithm name @p algorithm.
  */
 KLEO_EXPORT QString prettyAlgorithmName(const std::string &algorithm);
 
 /**
  * Returns the email associated to a UserID.
  */
 KLEO_EXPORT QString email(const GpgME::UserID &uid);
 }
 }