diff --git a/src/conf/appearanceconfigwidget.cpp b/src/conf/appearanceconfigwidget.cpp
index 5d92109f1..0d3f0a5af 100644
--- a/src/conf/appearanceconfigwidget.cpp
+++ b/src/conf/appearanceconfigwidget.cpp
@@ -1,908 +1,908 @@
 /*
     This file is part of kleopatra, the KDE key manager
     SPDX-FileCopyrightText: 2002, 2004, 2008 Klarälvdalens Datakonsult AB
     SPDX-FileCopyrightText: 2002, 2003 Marc Mutz <mutz@kde.org>
 
     SPDX-License-Identifier: GPL-2.0-or-later
  */
 
 #include <config-kleopatra.h>
 
 #include "appearanceconfigwidget.h"
 
 #include <settings.h>
 
 #include <Libkleo/DNAttributeOrderConfigWidget>
-#include <Libkleo/Dn>
+#include <Libkleo/DnAttributes>
 #include <Libkleo/ExpiryCheckerConfig>
 #include <Libkleo/KeyFilterManager>
 #include <Libkleo/SystemInfo>
 
 #include <KConfig>
 #include <KConfigGroup>
 #include <KIconDialog>
 #include <KLocalization>
 #include <KLocalizedString>
 #include <KMessageWidget>
 #include <KSeparator>
 
 #include <QApplication>
 #include <QCheckBox>
 #include <QColor>
 #include <QColorDialog>
 #include <QFont>
 #include <QFontDialog>
 #include <QGridLayout>
 #include <QIcon>
 #include <QLabel>
 #include <QListWidget>
 #include <QRegularExpression>
 #include <QSpinBox>
 #include <QString>
 #include <QVBoxLayout>
 
 #include <algorithm>
 
 using namespace Kleo;
 using namespace Kleo::Config;
 
 enum {
     HasNameRole = Qt::UserRole + 0x1234, /*!< Records that the user has assigned a name (to avoid comparing with i18n-strings) */
     HasFontRole, /*!< Records that the user has chosen  completely different font (as opposed to italic/bold/strikeout) */
     IconNameRole, /*!< Records the name of the icon (since QIcon won't give it out again, once set) */
     MayChangeNameRole,
     MayChangeForegroundRole,
     MayChangeBackgroundRole,
     MayChangeFontRole,
     MayChangeItalicRole,
     MayChangeBoldRole,
     MayChangeStrikeOutRole,
     MayChangeIconRole,
     StoredForegroundRole, /*!< Stores the actual configured foreground color */
     StoredBackgroundRole, /*!< Stores the actual configured background color */
     ConfigGroupRole,
 
     EndDummy,
 };
 
 static QFont tryToFindFontFor(const QListWidgetItem *item)
 {
     if (item)
         if (const QListWidget *const lw = item->listWidget()) {
             return lw->font();
         }
     return QApplication::font("QListWidget");
 }
 
 static bool is(const QListWidgetItem *item, bool (QFont::*func)() const)
 {
     if (!item) {
         return false;
     }
     const QVariant v = item->data(Qt::FontRole);
     if (!v.isValid() || v.userType() != QMetaType::QFont) {
         return false;
     }
     return (v.value<QFont>().*func)();
 }
 
 static bool is_italic(const QListWidgetItem *item)
 {
     return is(item, &QFont::italic);
 }
 static bool is_bold(const QListWidgetItem *item)
 {
     return is(item, &QFont::bold);
 }
 static bool is_strikeout(const QListWidgetItem *item)
 {
     return is(item, &QFont::strikeOut);
 }
 
 static void set(QListWidgetItem *item, bool on, void (QFont::*func)(bool))
 {
     if (!item) {
         return;
     }
     const QVariant v = item->data(Qt::FontRole);
     QFont font = v.isValid() && v.userType() == QMetaType::QFont ? v.value<QFont>() : tryToFindFontFor(item);
     (font.*func)(on);
     item->setData(Qt::FontRole, font);
 }
 
 static void set_italic(QListWidgetItem *item, bool on)
 {
     set(item, on, &QFont::setItalic);
 }
 static void set_bold(QListWidgetItem *item, bool on)
 {
     set(item, on, &QFont::setBold);
 }
 static void set_strikeout(QListWidgetItem *item, bool on)
 {
     set(item, on, &QFont::setStrikeOut);
 }
 
 static void apply_config(const KConfigGroup &group, QListWidgetItem *item)
 {
     if (!item) {
         return;
     }
 
     const QString name = group.readEntry("Name");
     item->setText(name.isEmpty() ? i18nc("Key filter without user-assigned name", "<unnamed>") : name);
     item->setData(HasNameRole, !name.isEmpty());
     item->setData(MayChangeNameRole, !group.isEntryImmutable("Name"));
     item->setData(ConfigGroupRole, QVariant::fromValue(group));
 
     const QColor fg = group.readEntry("foreground-color", QColor());
     item->setData(StoredForegroundRole, fg.isValid() ? QBrush(fg) : QVariant());
     if (!SystemInfo::isHighContrastModeActive()) {
         item->setData(Qt::ForegroundRole, fg.isValid() ? QBrush(fg) : QVariant());
     }
     item->setData(MayChangeForegroundRole, !group.isEntryImmutable("foreground-color"));
 
     const QColor bg = group.readEntry("background-color", QColor());
     item->setData(StoredBackgroundRole, bg.isValid() ? QBrush(bg) : QVariant());
     if (!SystemInfo::isHighContrastModeActive()) {
         item->setData(Qt::BackgroundRole, bg.isValid() ? QBrush(bg) : QVariant());
     }
     item->setData(MayChangeBackgroundRole, !group.isEntryImmutable("background-color"));
 
     const QFont defaultFont = tryToFindFontFor(item);
     if (group.hasKey("font")) {
         const QFont font = group.readEntry("font", defaultFont);
         item->setData(Qt::FontRole, font != defaultFont ? font : QVariant());
         item->setData(HasFontRole, font != defaultFont);
     } else {
         QFont font = defaultFont;
         font.setStrikeOut(group.readEntry("font-strikeout", false));
         font.setItalic(group.readEntry("font-italic", false));
         font.setBold(group.readEntry("font-bold", false));
         item->setData(Qt::FontRole, font);
         item->setData(HasFontRole, false);
     }
     item->setData(MayChangeFontRole, !group.isEntryImmutable("font"));
     item->setData(MayChangeItalicRole, !group.isEntryImmutable("font-italic"));
     item->setData(MayChangeBoldRole, !group.isEntryImmutable("font-bold"));
     item->setData(MayChangeStrikeOutRole, !group.isEntryImmutable("font-strikeout"));
 
     const QString iconName = group.readEntry("icon");
     item->setData(Qt::DecorationRole, iconName.isEmpty() ? QVariant() : QIcon::fromTheme(iconName));
     item->setData(IconNameRole, iconName.isEmpty() ? QVariant() : iconName);
     item->setData(MayChangeIconRole, !group.isEntryImmutable("icon"));
 }
 
 static void resetString(QListWidgetItem *item, int role, int allowRole, const char *key)
 {
     if (item && item->data(allowRole).toBool()) {
         auto config = item->data(ConfigGroupRole).value<KConfigGroup>();
         config.revertToDefault(key);
         item->setData(role, config.readEntry(key, QString()));
     }
 }
 
 static void resetColor(QListWidgetItem *item, int role, int allowRole, const char *key)
 {
     if (item && item->data(allowRole).toBool()) {
         auto config = item->data(ConfigGroupRole).value<KConfigGroup>();
         config.revertToDefault(key);
 
         const auto value = config.readEntry(key, QColor{});
         item->setData(role, value.isValid() ? value : QVariant());
     }
 }
 
 static void erase_if_allowed(QListWidgetItem *item, const int allowRole[], size_t numAllowRoles)
 {
     if (!item) {
         return;
     }
     for (unsigned int i = 0; i < numAllowRoles; ++i)
         if (!item->data(allowRole[i]).toBool()) {
             return;
         }
     auto config = item->data(ConfigGroupRole).value<KConfigGroup>();
     config.revertToDefault("font");
     config.revertToDefault("font-bold");
     config.revertToDefault("font-strikeout");
     config.revertToDefault("font-italic");
     auto value = config.readEntry<QFont>("font", tryToFindFontFor(item));
     value.setBold(config.readEntry("font-bold", false));
     value.setStrikeOut(config.readEntry("font-strikeout", false));
     value.setItalic(config.readEntry("font-italic", false));
     item->setData(Qt::FontRole, value);
 }
 
 static void set_default_appearance(QListWidgetItem *item)
 {
     if (!item) {
         return;
     }
     if (!SystemInfo::isHighContrastModeActive()) {
         resetColor(item, Qt::ForegroundRole, MayChangeForegroundRole, "foreground-color");
         resetColor(item, Qt::BackgroundRole, MayChangeBackgroundRole, "background-color");
     }
     resetColor(item, StoredForegroundRole, MayChangeForegroundRole, "foreground-color");
     resetColor(item, StoredBackgroundRole, MayChangeBackgroundRole, "background-color");
     resetString(item, IconNameRole, MayChangeIconRole, "icon");
     item->setData(Qt::DecorationRole, QIcon::fromTheme(item->data(ConfigGroupRole).value<KConfigGroup>().readEntry("icon", QString())));
     static const int fontAllowRoles[] = {
         MayChangeFontRole,
         MayChangeItalicRole,
         MayChangeBoldRole,
         MayChangeStrikeOutRole,
     };
     erase_if_allowed(item, fontAllowRoles, sizeof(fontAllowRoles) / sizeof(int));
 }
 
 static void writeOrDelete(KConfigGroup &group, const char *key, const QVariant &value)
 {
     if (value.isValid()) {
         group.writeEntry(key, value);
     } else {
         group.revertToDefault(key);
     }
 }
 
 static QVariant brush2color(const QVariant &v)
 {
     if (v.isValid()) {
         if (v.userType() == QMetaType::QColor) {
             return v;
         } else if (v.userType() == QMetaType::QBrush) {
             return v.value<QBrush>().color();
         }
     }
     return QVariant();
 }
 
 static void save_to_config(const QListWidgetItem *item, KConfigGroup &group)
 {
     if (!item) {
         return;
     }
     writeOrDelete(group, "Name", item->data(HasNameRole).toBool() ? item->text() : QVariant());
     writeOrDelete(group, "foreground-color", brush2color(item->data(StoredForegroundRole)));
     writeOrDelete(group, "background-color", brush2color(item->data(StoredBackgroundRole)));
     writeOrDelete(group, "icon", item->data(IconNameRole));
 
     group.deleteEntry("font");
     group.deleteEntry("font-strikeout");
     group.deleteEntry("font-italic");
     group.deleteEntry("font-bold");
 
     if (item->data(HasFontRole).toBool()) {
         writeOrDelete(group, "font", item->data(Qt::FontRole));
         return;
     }
 
     if (is_strikeout(item)) {
         group.writeEntry("font-strikeout", true);
     }
     if (is_italic(item)) {
         group.writeEntry("font-italic", true);
     }
     if (is_bold(item)) {
         group.writeEntry("font-bold", true);
     }
 }
 
 static void kiosk_enable(QWidget *w, const QListWidgetItem *item, int allowRole)
 {
     if (!w) {
         return;
     }
     if (item && !item->data(allowRole).toBool()) {
         w->setEnabled(false);
         w->setToolTip(i18nc("@info:tooltip", "This parameter has been locked down by the system administrator."));
     } else {
         w->setEnabled(item);
         w->setToolTip(QString());
     }
 }
 
 class Ui_AppearanceConfigWidget
 {
 public:
     QTabWidget *tabWidget;
     KMessageWidget *highContrastMsg;
     QListWidget *categoriesLV;
     QPushButton *iconButton;
     QPushButton *foregroundButton;
     QPushButton *backgroundButton;
     QPushButton *fontButton;
     QCheckBox *italicCB;
     QCheckBox *boldCB;
     QCheckBox *strikeoutCB;
     QPushButton *defaultLookPB;
     QCheckBox *tooltipValidityCheckBox;
     QCheckBox *tooltipOwnerCheckBox;
     QCheckBox *tooltipDetailsCheckBox;
     QCheckBox *showExpirationCheckBox;
     QSpinBox *ownCertificateThresholdSpinBox;
     QSpinBox *otherCertificateThresholdSpinBox;
 
     void setupUi(QWidget *parent)
     {
         if (parent->objectName().isEmpty())
             parent->setObjectName(QString::fromUtf8("AppearanceConfigWidget"));
         auto mainLayout = new QVBoxLayout{parent};
         mainLayout->setContentsMargins({});
         tabWidget = new QTabWidget(parent);
         tabWidget->setDocumentMode(true);
         tabWidget->setObjectName(QString::fromUtf8("tabWidget"));
 
         {
             auto tab = new QWidget{parent};
             auto tabLayout = new QVBoxLayout{tab};
 
             tabLayout->addWidget(new KSeparator{tab});
 
             auto label = new QLabel{tab};
             label->setText(i18nc("@info", "Show the following information in certificate list tooltips:"));
             tabLayout->addWidget(label);
 
             tooltipValidityCheckBox = new QCheckBox{i18nc("@option:check", "Show validity"), tab};
             tabLayout->addWidget(tooltipValidityCheckBox);
 
             tooltipOwnerCheckBox = new QCheckBox{i18nc("@option:check", "Show owner information"), tab};
             tabLayout->addWidget(tooltipOwnerCheckBox);
 
             tooltipDetailsCheckBox = new QCheckBox{i18nc("@option:check", "Show technical details"), tab};
             tabLayout->addWidget(tooltipDetailsCheckBox);
 
             tabLayout->addWidget(new KSeparator{tab});
 
             showExpirationCheckBox = new QCheckBox{i18nc("@option:check", "Show upcoming certificate expiration"), tab};
             tabLayout->addWidget(showExpirationCheckBox);
 
             {
                 auto gridLayout = new QGridLayout;
                 const ExpiryCheckerConfig expiryConfig;
                 {
                     auto label = new QLabel{i18nc("@label:spinbox", "Threshold for own certificates:"), tab};
                     ownCertificateThresholdSpinBox = new QSpinBox{tab};
                     label->setBuddy(ownCertificateThresholdSpinBox);
                     const auto configItem = expiryConfig.ownKeyThresholdInDaysItem();
                     ownCertificateThresholdSpinBox->setMinimum(configItem->minValue().toInt());
                     ownCertificateThresholdSpinBox->setMaximum(configItem->maxValue().toInt());
                     ownCertificateThresholdSpinBox->setSpecialValueText(i18nc("@item never show expiry notification", "never"));
                     KLocalization::setupSpinBoxFormatString(ownCertificateThresholdSpinBox, ki18ncp("@item:valuesuffix", "%v day", "%v days"));
                     ownCertificateThresholdSpinBox->setToolTip(
                         i18nc("@info:tooltip", "Select the number of days you want to be warned in advance, if your own certificate is about to expire soon."));
                     gridLayout->addWidget(label, 0, 0);
                     gridLayout->addWidget(ownCertificateThresholdSpinBox, 0, 1);
                 }
                 {
                     auto label = new QLabel{i18nc("@label:spinbox", "Threshold for other certificates:"), tab};
                     otherCertificateThresholdSpinBox = new QSpinBox{tab};
                     label->setBuddy(otherCertificateThresholdSpinBox);
                     const auto configItem = expiryConfig.otherKeyThresholdInDaysItem();
                     otherCertificateThresholdSpinBox->setMinimum(configItem->minValue().toInt());
                     otherCertificateThresholdSpinBox->setMaximum(configItem->maxValue().toInt());
                     otherCertificateThresholdSpinBox->setSpecialValueText(i18nc("@item never show expiry notification", "never"));
                     KLocalization::setupSpinBoxFormatString(otherCertificateThresholdSpinBox, ki18ncp("@item:valuesuffix", "%v day", "%v days"));
                     otherCertificateThresholdSpinBox->setToolTip(
                         i18nc("@info:tooltip",
                               "Select the number of days you want to be warned in advance, if another person's certificate is about to expire soon."));
                     gridLayout->addWidget(label, 1, 0);
                     gridLayout->addWidget(otherCertificateThresholdSpinBox, 1, 1);
                 }
                 gridLayout->setColumnStretch(2, 1);
                 tabLayout->addLayout(gridLayout);
             }
 
             tabLayout->addStretch(1);
 
             tabWidget->addTab(tab, i18nc("@title:tab", "General"));
         }
 
         auto tab_2 = new QWidget();
         tab_2->setObjectName(QString::fromUtf8("tab_2"));
         auto gridLayout = new QGridLayout(tab_2);
         gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
         highContrastMsg = new KMessageWidget(tab_2);
         highContrastMsg->setObjectName(QString::fromUtf8("highContrastMsg"));
 
         gridLayout->addWidget(highContrastMsg, 0, 0, 1, 2);
 
         categoriesLV = new QListWidget(tab_2);
         categoriesLV->setObjectName(QString::fromUtf8("categoriesLV"));
 
         gridLayout->addWidget(categoriesLV, 1, 0, 1, 1);
 
         auto vboxLayout = new QVBoxLayout();
         vboxLayout->setObjectName(QString::fromUtf8("vboxLayout"));
         iconButton = new QPushButton(tab_2);
         iconButton->setText(i18nc("@action:button", "Set Icon..."));
         iconButton->setObjectName(QString::fromUtf8("iconButton"));
         iconButton->setEnabled(false);
 
         vboxLayout->addWidget(iconButton);
 
         foregroundButton = new QPushButton(tab_2);
         foregroundButton->setText(i18nc("@action:button", "Set Text Color..."));
         foregroundButton->setObjectName(QString::fromUtf8("foregroundButton"));
         foregroundButton->setEnabled(false);
 
         vboxLayout->addWidget(foregroundButton);
 
         backgroundButton = new QPushButton(tab_2);
         backgroundButton->setText(i18nc("@action:button", "Set Background Color..."));
         backgroundButton->setObjectName(QString::fromUtf8("backgroundButton"));
         backgroundButton->setEnabled(false);
 
         vboxLayout->addWidget(backgroundButton);
 
         fontButton = new QPushButton(tab_2);
         fontButton->setText(i18nc("@action:button", "Set Font..."));
         fontButton->setObjectName(QString::fromUtf8("fontButton"));
         fontButton->setEnabled(false);
 
         vboxLayout->addWidget(fontButton);
 
         italicCB = new QCheckBox(tab_2);
         italicCB->setText(i18nc("@option:check", "Italic"));
         italicCB->setObjectName(QString::fromUtf8("italicCB"));
         italicCB->setEnabled(false);
 
         vboxLayout->addWidget(italicCB);
 
         boldCB = new QCheckBox(tab_2);
         boldCB->setText(i18nc("@option:check", "Bold"));
         boldCB->setObjectName(QString::fromUtf8("boldCB"));
         boldCB->setEnabled(false);
 
         vboxLayout->addWidget(boldCB);
 
         strikeoutCB = new QCheckBox(tab_2);
         strikeoutCB->setText(i18nc("@option:check", "Strikeout"));
         strikeoutCB->setObjectName(QString::fromUtf8("strikeoutCB"));
         strikeoutCB->setEnabled(false);
 
         vboxLayout->addWidget(strikeoutCB);
 
         vboxLayout->addStretch(1);
 
         defaultLookPB = new QPushButton(tab_2);
         defaultLookPB->setText(i18nc("@action:button", "Default Appearance"));
         defaultLookPB->setObjectName(QString::fromUtf8("defaultLookPB"));
         defaultLookPB->setEnabled(false);
 
         vboxLayout->addWidget(defaultLookPB);
 
         gridLayout->addLayout(vboxLayout, 1, 1, 1, 1);
 
         tabWidget->addTab(tab_2, i18nc("@title:tab", "Certificate Categories"));
 
         mainLayout->addWidget(tabWidget);
     }
 };
 
 class AppearanceConfigWidget::Private : public Ui_AppearanceConfigWidget
 {
     friend class ::Kleo::Config::AppearanceConfigWidget;
     AppearanceConfigWidget *const q;
 
 public:
     explicit Private(AppearanceConfigWidget *qq)
         : Ui_AppearanceConfigWidget()
         , q(qq)
     {
         setupUi(q);
 
         if (QLayout *const l = q->layout()) {
             l->setContentsMargins(0, 0, 0, 0);
         }
 
         highContrastMsg->setVisible(SystemInfo::isHighContrastModeActive());
         highContrastMsg->setMessageType(KMessageWidget::Warning);
         highContrastMsg->setIcon(q->style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, q));
         highContrastMsg->setText(i18n("The preview of colors is disabled because high-contrast mode is active."));
         highContrastMsg->setCloseButtonVisible(false);
 
         if (Kleo::Settings{}.cmsEnabled()) {
             auto w = new QWidget;
             dnOrderWidget = new DNAttributeOrderConfigWidget{w};
             dnOrderWidget->setObjectName(QLatin1StringView("dnOrderWidget"));
             (new QVBoxLayout(w))->addWidget(dnOrderWidget);
 
             tabWidget->addTab(w, i18n("DN-Attribute Order"));
 
             connect(dnOrderWidget, &DNAttributeOrderConfigWidget::changed, q, &AppearanceConfigWidget::changed);
         }
 
         connect(iconButton, SIGNAL(clicked()), q, SLOT(slotIconClicked()));
 #ifndef QT_NO_COLORDIALOG
         connect(foregroundButton, SIGNAL(clicked()), q, SLOT(slotForegroundClicked()));
         connect(backgroundButton, SIGNAL(clicked()), q, SLOT(slotBackgroundClicked()));
 #else
         foregroundButton->hide();
         backgroundButton->hide();
 #endif
 #ifndef QT_NO_FONTDIALOG
         connect(fontButton, SIGNAL(clicked()), q, SLOT(slotFontClicked()));
 #else
         fontButton->hide();
 #endif
         auto emitChanged = [this]() {
             Q_EMIT q->changed();
         };
         connect(categoriesLV, SIGNAL(itemSelectionChanged()), q, SLOT(slotSelectionChanged()));
         connect(defaultLookPB, SIGNAL(clicked()), q, SLOT(slotDefaultClicked()));
         connect(italicCB, SIGNAL(toggled(bool)), q, SLOT(slotItalicToggled(bool)));
         connect(boldCB, SIGNAL(toggled(bool)), q, SLOT(slotBoldToggled(bool)));
         connect(strikeoutCB, SIGNAL(toggled(bool)), q, SLOT(slotStrikeOutToggled(bool)));
         connect(tooltipValidityCheckBox, SIGNAL(toggled(bool)), q, SLOT(slotTooltipValidityChanged(bool)));
         connect(tooltipOwnerCheckBox, SIGNAL(toggled(bool)), q, SLOT(slotTooltipOwnerChanged(bool)));
         connect(tooltipDetailsCheckBox, SIGNAL(toggled(bool)), q, SLOT(slotTooltipDetailsChanged(bool)));
         connect(showExpirationCheckBox, &QCheckBox::toggled, q, emitChanged);
         connect(ownCertificateThresholdSpinBox, &QSpinBox::valueChanged, q, emitChanged);
         connect(otherCertificateThresholdSpinBox, &QSpinBox::valueChanged, q, emitChanged);
     }
 
 private:
     void enableDisableActions(QListWidgetItem *item);
     QListWidgetItem *selectedItem() const;
 
 private:
     void slotIconClicked();
 #ifndef QT_NO_COLORDIALOG
     void slotForegroundClicked();
     void slotBackgroundClicked();
 #endif
 #ifndef QT_NO_FONTDIALOG
     void slotFontClicked();
 #endif
     void slotSelectionChanged();
     void slotDefaultClicked();
     void slotItalicToggled(bool);
     void slotBoldToggled(bool);
     void slotStrikeOutToggled(bool);
     void slotTooltipValidityChanged(bool);
     void slotTooltipOwnerChanged(bool);
     void slotTooltipDetailsChanged(bool);
 
 private:
     Kleo::DNAttributeOrderConfigWidget *dnOrderWidget = nullptr;
 };
 
 AppearanceConfigWidget::AppearanceConfigWidget(QWidget *p, Qt::WindowFlags f)
     : QWidget(p, f)
     , d(new Private(this))
 {
     //    load();
 }
 
 AppearanceConfigWidget::~AppearanceConfigWidget()
 {
 }
 
 void AppearanceConfigWidget::Private::slotSelectionChanged()
 {
     enableDisableActions(selectedItem());
 }
 
 QListWidgetItem *AppearanceConfigWidget::Private::selectedItem() const
 {
     const QList<QListWidgetItem *> items = categoriesLV->selectedItems();
     return items.empty() ? nullptr : items.front();
 }
 
 void AppearanceConfigWidget::Private::enableDisableActions(QListWidgetItem *item)
 {
     kiosk_enable(iconButton, item, MayChangeIconRole);
 #ifndef QT_NO_COLORDIALOG
     kiosk_enable(foregroundButton, item, MayChangeForegroundRole);
     kiosk_enable(backgroundButton, item, MayChangeBackgroundRole);
 #endif
 #ifndef QT_NO_FONTDIALOG
     kiosk_enable(fontButton, item, MayChangeFontRole);
 #endif
     kiosk_enable(italicCB, item, MayChangeItalicRole);
     kiosk_enable(boldCB, item, MayChangeBoldRole);
     kiosk_enable(strikeoutCB, item, MayChangeStrikeOutRole);
 
     defaultLookPB->setEnabled(item);
 
     italicCB->setChecked(is_italic(item));
     boldCB->setChecked(is_bold(item));
     strikeoutCB->setChecked(is_strikeout(item));
 }
 
 void AppearanceConfigWidget::Private::slotDefaultClicked()
 {
     QListWidgetItem *const item = selectedItem();
     if (!item) {
         return;
     }
 
     set_default_appearance(item);
     enableDisableActions(item);
 
     Q_EMIT q->changed();
 }
 
 void AppearanceConfigWidget::defaults()
 {
     // use temporary KConfigSkeleton instances for (re)setting the values to the defaults;
     // the setters respect the immutability of the individual settings, so that we don't have
     // to check this ourselves
 
     Settings settings;
     settings.setShowExpiryNotifications(settings.findItem(QStringLiteral("ShowExpiryNotifications"))->getDefault().toBool());
     d->showExpirationCheckBox->setChecked(settings.showExpiryNotifications());
 
     {
         ExpiryCheckerConfig expiryConfig;
         expiryConfig.setOwnKeyThresholdInDays(expiryConfig.ownKeyThresholdInDaysItem()->getDefault().toInt());
         d->ownCertificateThresholdSpinBox->setValue(expiryConfig.ownKeyThresholdInDays());
         expiryConfig.setOtherKeyThresholdInDays(expiryConfig.otherKeyThresholdInDaysItem()->getDefault().toInt());
         d->otherCertificateThresholdSpinBox->setValue(expiryConfig.otherKeyThresholdInDays());
     }
 
     // This simply means "default look for every category"
     for (int i = 0, end = d->categoriesLV->count(); i != end; ++i) {
         set_default_appearance(d->categoriesLV->item(i));
     }
 
     settings.setShowValidity(settings.findItem(QStringLiteral("ShowValidity"))->getDefault().toBool());
     d->tooltipValidityCheckBox->setChecked(settings.showValidity());
     settings.setShowOwnerInformation(settings.findItem(QStringLiteral("ShowOwnerInformation"))->getDefault().toBool());
     d->tooltipOwnerCheckBox->setChecked(settings.showOwnerInformation());
     settings.setShowCertificateDetails(settings.findItem(QStringLiteral("ShowCertificateDetails"))->getDefault().toBool());
     d->tooltipDetailsCheckBox->setChecked(settings.showCertificateDetails());
 
     if (d->dnOrderWidget) {
         if (!settings.isImmutable(QStringLiteral("AttributeOrder"))) {
-            d->dnOrderWidget->setAttributeOrder(DN::defaultAttributeOrder());
+            d->dnOrderWidget->setAttributeOrder(DNAttributes::defaultOrder());
         }
     }
 
     Q_EMIT changed();
 }
 
 bool greaterThanBySpecificity(const QString &lhs, const QString &rhs)
 {
     const auto lFilter = KeyFilterManager::instance()->keyFilterByID(lhs);
     const auto rFilter = KeyFilterManager::instance()->keyFilterByID(rhs);
     if (!lFilter) {
         return false;
     }
     if (!rFilter) {
         return true;
     }
     return lFilter->specificity() > rFilter->specificity();
 }
 
 void AppearanceConfigWidget::load()
 {
     const Settings settings;
     d->showExpirationCheckBox->setChecked(settings.showExpiryNotifications());
     d->showExpirationCheckBox->setEnabled(!settings.isImmutable(QStringLiteral("ShowExpiryNotifications")));
 
     {
         const ExpiryCheckerConfig expiryConfig;
         d->ownCertificateThresholdSpinBox->setValue(expiryConfig.ownKeyThresholdInDays());
         d->ownCertificateThresholdSpinBox->setEnabled(!expiryConfig.ownKeyThresholdInDaysItem()->isImmutable());
         d->otherCertificateThresholdSpinBox->setValue(expiryConfig.otherKeyThresholdInDays());
         d->otherCertificateThresholdSpinBox->setEnabled(!expiryConfig.otherKeyThresholdInDaysItem()->isImmutable());
     }
 
     if (d->dnOrderWidget) {
-        d->dnOrderWidget->setAttributeOrder(DN::attributeOrder());
+        d->dnOrderWidget->setAttributeOrder(DNAttributes::order());
         d->dnOrderWidget->setEnabled(!settings.isImmutable(QStringLiteral("AttributeOrder")));
     }
 
     d->categoriesLV->clear();
     KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"));
     if (!config) {
         return;
     }
     QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Key Filter #\\d+$")));
     std::ranges::stable_sort(groups, greaterThanBySpecificity);
     for (const QString &group : groups) {
         const KConfigGroup configGroup{config, group};
         const bool isCmsSpecificKeyFilter = !configGroup.readEntry("is-openpgp-key", true);
         auto item = new QListWidgetItem{d->categoriesLV};
         // hide CMS-specific filters if CMS is disabled; we hide those filters
         // instead of skipping them, so that they are not removed on save
         item->setHidden(isCmsSpecificKeyFilter && !Kleo::Settings{}.cmsEnabled());
         apply_config(configGroup, item);
     }
 
     d->tooltipValidityCheckBox->setChecked(settings.showValidity());
     d->tooltipValidityCheckBox->setEnabled(!settings.isImmutable(QStringLiteral("ShowValidity")));
     d->tooltipOwnerCheckBox->setChecked(settings.showOwnerInformation());
     d->tooltipOwnerCheckBox->setEnabled(!settings.isImmutable(QStringLiteral("ShowOwnerInformation")));
     d->tooltipDetailsCheckBox->setChecked(settings.showCertificateDetails());
     d->tooltipDetailsCheckBox->setEnabled(!settings.isImmutable(QStringLiteral("ShowCertificateDetails")));
 }
 
 void AppearanceConfigWidget::save()
 {
     Settings settings;
     settings.setShowExpiryNotifications(d->showExpirationCheckBox->isChecked());
     if (d->dnOrderWidget) {
         settings.setAttributeOrder(d->dnOrderWidget->attributeOrder());
-        DN::setAttributeOrder(settings.attributeOrder());
+        DNAttributes::setOrder(settings.attributeOrder());
     }
 
     {
         ExpiryCheckerConfig expiryConfig;
         expiryConfig.setOwnKeyThresholdInDays(d->ownCertificateThresholdSpinBox->value());
         expiryConfig.setOtherKeyThresholdInDays(d->otherCertificateThresholdSpinBox->value());
         expiryConfig.save();
     }
 
     settings.setShowValidity(d->tooltipValidityCheckBox->isChecked());
     settings.setShowOwnerInformation(d->tooltipOwnerCheckBox->isChecked());
     settings.setShowCertificateDetails(d->tooltipDetailsCheckBox->isChecked());
     settings.save();
 
     KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"));
     if (!config) {
         return;
     }
     // We know (assume) that the groups in the config object haven't changed,
     // so we just iterate over them and over the listviewitems, and map one-to-one.
     QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Key Filter #\\d+$")));
     std::ranges::stable_sort(groups, greaterThanBySpecificity);
 #if 0
     if (groups.isEmpty()) {
         // If we created the default categories ourselves just now, then we need to make up their list
         Q3ListViewItemIterator lvit(categoriesLV);
         for (; lvit.current(); ++lvit) {
             groups << lvit.current()->text(0);
         }
     }
 #endif
     for (int i = 0, end = std::min<int>(groups.size(), d->categoriesLV->count()); i != end; ++i) {
         const QListWidgetItem *const item = d->categoriesLV->item(i);
         Q_ASSERT(item);
         KConfigGroup group(config, groups[i]);
         save_to_config(item, group);
     }
 
     config->sync();
     KeyFilterManager::instance()->reload();
 }
 
 void AppearanceConfigWidget::Private::slotIconClicked()
 {
     QListWidgetItem *const item = selectedItem();
     if (!item) {
         return;
     }
 
     const QString iconName = KIconDialog::getIcon(/* repeating default arguments begin */
                                                   KIconLoader::Desktop,
                                                   KIconLoader::Application,
                                                   false,
                                                   0,
                                                   false,
                                                   /* repeating default arguments end */
                                                   q);
     if (iconName.isEmpty()) {
         return;
     }
 
     item->setIcon(QIcon::fromTheme(iconName));
     item->setData(IconNameRole, iconName);
     Q_EMIT q->changed();
 }
 
 #ifndef QT_NO_COLORDIALOG
 void AppearanceConfigWidget::Private::slotForegroundClicked()
 {
     QListWidgetItem *const item = selectedItem();
     if (!item) {
         return;
     }
 
     const QVariant v = brush2color(item->data(StoredForegroundRole));
 
     const QColor initial = v.isValid() ? v.value<QColor>() : categoriesLV->palette().color(QPalette::Normal, QPalette::Text);
     const QColor c = QColorDialog::getColor(initial, q);
 
     if (c.isValid()) {
         item->setData(StoredForegroundRole, QBrush(c));
         if (!SystemInfo::isHighContrastModeActive()) {
             item->setData(Qt::ForegroundRole, QBrush(c));
         }
         Q_EMIT q->changed();
     }
 }
 
 void AppearanceConfigWidget::Private::slotBackgroundClicked()
 {
     QListWidgetItem *const item = selectedItem();
     if (!item) {
         return;
     }
 
     const QVariant v = brush2color(item->data(StoredBackgroundRole));
 
     const QColor initial = v.isValid() ? v.value<QColor>() : categoriesLV->palette().color(QPalette::Normal, QPalette::Base);
     const QColor c = QColorDialog::getColor(initial, q);
 
     if (c.isValid()) {
         item->setData(StoredBackgroundRole, QBrush(c));
         if (!SystemInfo::isHighContrastModeActive()) {
             item->setData(Qt::BackgroundRole, QBrush(c));
         }
         Q_EMIT q->changed();
     }
 }
 #endif // QT_NO_COLORDIALOG
 
 #ifndef QT_NO_FONTDIALOG
 void AppearanceConfigWidget::Private::slotFontClicked()
 {
     QListWidgetItem *const item = selectedItem();
     if (!item) {
         return;
     }
 
     const QVariant v = item->data(Qt::FontRole);
 
     bool ok = false;
     const QFont defaultFont = tryToFindFontFor(item);
     const QFont initial = v.isValid() && v.userType() == QMetaType::QFont ? v.value<QFont>() : defaultFont;
     QFont f = QFontDialog::getFont(&ok, initial, q);
     if (!ok) {
         return;
     }
 
     // disallow circumventing KIOSK:
     if (!item->data(MayChangeItalicRole).toBool()) {
         f.setItalic(initial.italic());
     }
     if (!item->data(MayChangeBoldRole).toBool()) {
         f.setBold(initial.bold());
     }
     if (!item->data(MayChangeStrikeOutRole).toBool()) {
         f.setStrikeOut(initial.strikeOut());
     }
 
     item->setData(Qt::FontRole, f != defaultFont ? f : QVariant());
     item->setData(HasFontRole, true);
     Q_EMIT q->changed();
 }
 #endif // QT_NO_FONTDIALOG
 
 void AppearanceConfigWidget::Private::slotItalicToggled(bool on)
 {
     set_italic(selectedItem(), on);
     Q_EMIT q->changed();
 }
 
 void AppearanceConfigWidget::Private::slotBoldToggled(bool on)
 {
     set_bold(selectedItem(), on);
     Q_EMIT q->changed();
 }
 
 void AppearanceConfigWidget::Private::slotStrikeOutToggled(bool on)
 {
     set_strikeout(selectedItem(), on);
     Q_EMIT q->changed();
 }
 
 void AppearanceConfigWidget::Private::slotTooltipValidityChanged(bool)
 {
     Q_EMIT q->changed();
 }
 
 void AppearanceConfigWidget::Private::slotTooltipOwnerChanged(bool)
 {
     Q_EMIT q->changed();
 }
 
 void AppearanceConfigWidget::Private::slotTooltipDetailsChanged(bool)
 {
     Q_EMIT q->changed();
 }
 
 #include "moc_appearanceconfigwidget.cpp"
diff --git a/src/dialogs/certificatedetailsinputwidget.cpp b/src/dialogs/certificatedetailsinputwidget.cpp
index b7ea5e1de..c6be9e771 100644
--- a/src/dialogs/certificatedetailsinputwidget.cpp
+++ b/src/dialogs/certificatedetailsinputwidget.cpp
@@ -1,363 +1,363 @@
 /*
     This file is part of Kleopatra, the KDE keymanager
     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
 
     SPDX-FileCopyrightText: 2020 g10 Code GmbH
     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
 #include "certificatedetailsinputwidget.h"
 
 #include <utils/userinfo.h>
 
-#include <Libkleo/Dn>
+#include <Libkleo/DnAttributes>
 #include <Libkleo/OidMap>
 #include <Libkleo/Stl_Util>
 #include <Libkleo/Validation>
 
 #include <KConfigGroup>
 #include <KLocalizedString>
 #include <KSharedConfig>
 
 #include <QGpgME/DN>
 
 #include <QLabel>
 #include <QLineEdit>
 #include <QVBoxLayout>
 #include <QValidator>
 
 #include "kleopatra_debug.h"
 
 using namespace Kleo;
 using namespace Kleo::Dialogs;
 
 namespace
 {
 struct Line {
     QString attr;
     QString label;
     QString regex;
     QLineEdit *edit;
     std::shared_ptr<QValidator> validator;
     bool required;
 };
 
 QString attributeFromKey(QString key)
 {
     return key.remove(QLatin1Char('!'));
 }
 
 QString attributeLabel(const QString &attr)
 {
     if (attr.isEmpty()) {
         return QString();
     }
-    const QString label = DN::attributeNameToLabel(attr);
+    const QString label = DNAttributes::nameToLabel(attr);
     if (!label.isEmpty()) {
         return i18nc("Format string for the labels in the \"Your Personal Data\" page", "%1 (%2)", label, attr);
     } else {
         return attr;
     }
 }
 
 QLineEdit *addRow(QGridLayout *l, const QString &label, const QString &preset, const std::shared_ptr<QValidator> &validator, bool readonly, bool required)
 {
     Q_ASSERT(l);
 
     auto lb = new QLabel(l->parentWidget());
     lb->setText(i18nc("interpunctation for labels", "%1:", label));
 
     auto le = new QLineEdit(l->parentWidget());
     le->setText(preset);
     if (validator) {
         le->setValidator(validator.get());
     }
     le->setReadOnly(readonly && le->hasAcceptableInput());
 
     auto reqLB = new QLabel(l->parentWidget());
     reqLB->setText(required ? i18n("(required)") : i18n("(optional)"));
 
     const int row = l->rowCount();
     l->addWidget(lb, row, 0);
     l->addWidget(le, row, 1);
     l->addWidget(reqLB, row, 2);
     return le;
 }
 
 bool hasIntermediateInput(const QLineEdit *le)
 {
     QString text = le->text();
     int pos = le->cursorPosition();
     const QValidator *const v = le->validator();
     return v && v->validate(text, pos) == QValidator::Intermediate;
 }
 
 QString requirementsAreMet(const QList<Line> &lines)
 {
     for (const Line &line : lines) {
         const QLineEdit *le = line.edit;
         if (!le) {
             continue;
         }
         qCDebug(KLEOPATRA_LOG) << "requirementsAreMet(): checking \"" << line.attr << "\" against \"" << le->text() << "\":";
         if (le->text().trimmed().isEmpty()) {
             if (line.required) {
                 if (line.regex.isEmpty()) {
                     return xi18nc("@info", "<interface>%1</interface> is required, but empty.", line.label);
                 } else {
                     return xi18nc("@info",
                                   "<interface>%1</interface> is required, but empty.<nl/>"
                                   "Local Admin rule: <icode>%2</icode>",
                                   line.label,
                                   line.regex);
                 }
             }
         } else if (hasIntermediateInput(le)) {
             if (line.regex.isEmpty()) {
                 return xi18nc("@info", "<interface>%1</interface> is incomplete.", line.label);
             } else {
                 return xi18nc("@info",
                               "<interface>%1</interface> is incomplete.<nl/>"
                               "Local Admin rule: <icode>%2</icode>",
                               line.label,
                               line.regex);
             }
         } else if (!le->hasAcceptableInput()) {
             if (line.regex.isEmpty()) {
                 return xi18nc("@info", "<interface>%1</interface> is invalid.", line.label);
             } else {
                 return xi18nc("@info",
                               "<interface>%1</interface> is invalid.<nl/>"
                               "Local Admin rule: <icode>%2</icode>",
                               line.label,
                               line.regex);
             }
         }
     }
     return QString();
 }
 }
 
 class CertificateDetailsInputWidget::Private
 {
     friend class ::Kleo::Dialogs::CertificateDetailsInputWidget;
     CertificateDetailsInputWidget *const q;
 
     struct {
         QGridLayout *gridLayout;
         QList<Line> lines;
         QLineEdit *dn;
         QLabel *error;
     } ui;
 
 public:
     Private(CertificateDetailsInputWidget *qq)
         : q(qq)
     {
         auto mainLayout = new QVBoxLayout(q);
 
         ui.gridLayout = new QGridLayout();
         mainLayout->addLayout(ui.gridLayout);
 
         createForm();
 
         mainLayout->addStretch(1);
 
         ui.dn = new QLineEdit();
         ui.dn->setFrame(false);
         ui.dn->setAlignment(Qt::AlignCenter);
         ui.dn->setReadOnly(true);
         mainLayout->addWidget(ui.dn);
 
         ui.error = new QLabel();
         {
             QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
             sizePolicy.setHorizontalStretch(0);
             sizePolicy.setVerticalStretch(0);
             sizePolicy.setHeightForWidth(ui.error->sizePolicy().hasHeightForWidth());
             ui.error->setSizePolicy(sizePolicy);
         }
         {
             QPalette palette;
             QBrush brush(QColor(255, 0, 0, 255));
             brush.setStyle(Qt::SolidPattern);
             palette.setBrush(QPalette::Active, QPalette::WindowText, brush);
             palette.setBrush(QPalette::Inactive, QPalette::WindowText, brush);
             QBrush brush1(QColor(114, 114, 114, 255));
             brush1.setStyle(Qt::SolidPattern);
             palette.setBrush(QPalette::Disabled, QPalette::WindowText, brush1);
             ui.error->setPalette(palette);
         }
         ui.error->setTextFormat(Qt::RichText);
         // set error label to have a fixed height of two lines:
         ui.error->setText(QStringLiteral("2<br>1"));
         ui.error->setFixedHeight(ui.error->minimumSizeHint().height());
         ui.error->clear();
         mainLayout->addWidget(ui.error);
 
         // select the preset text in the first line edit
         if (!ui.lines.empty()) {
             ui.lines.first().edit->selectAll();
         }
 
         // explicitly update DN and check requirements after setup is complete
         updateDN();
         checkRequirements();
     }
 
     ~Private()
     {
         // remember current attribute values as presets for next certificate
         KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard"));
         for (const Line &line : ui.lines) {
             const QString attr = attributeFromKey(line.attr);
             const QString value = line.edit->text().trimmed();
             config.writeEntry(attr, value);
         }
         config.sync();
     }
 
     void createForm()
     {
         static const QStringList defaultAttributeOrder = {
             QStringLiteral("CN!"),
             QStringLiteral("EMAIL!"),
             QStringLiteral("L"),
             QStringLiteral("OU"),
             QStringLiteral("O"),
             QStringLiteral("C"),
         };
         const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard"));
 
         const QStringList attrOrder = config.readEntry("DNAttributeOrder", defaultAttributeOrder);
 
         for (const QString &rawKey : attrOrder) {
             const QString key = rawKey.trimmed().toUpper();
             const QString attr = attributeFromKey(key);
             if (attr.isEmpty()) {
                 continue;
             }
             const QString defaultPreset = //
                 (attr == QLatin1StringView("CN"))      ? userFullName()
                 : (attr == QLatin1StringView("EMAIL")) ? userEmailAddress()
                                                        : QString();
             const QString preset = config.readEntry(attr, defaultPreset);
             const bool required = key.endsWith(QLatin1Char('!'));
             const bool readonly = config.isEntryImmutable(attr);
             const QString label = config.readEntry(attr + QLatin1StringView("_label"), attributeLabel(attr));
             const QString regex = config.readEntry(attr + QLatin1StringView("_regex"));
 
             std::shared_ptr<QValidator> validator;
             if (attr == QLatin1StringView("EMAIL")) {
                 validator = regex.isEmpty() ? Validation::email() : Validation::email(regex);
             } else if (!regex.isEmpty()) {
                 validator = std::make_shared<QRegularExpressionValidator>(QRegularExpression{regex});
             }
 
             QLineEdit *le = addRow(ui.gridLayout, label, preset, validator, readonly, required);
 
             const Line line = {attr, label, regex, le, validator, required};
             ui.lines.push_back(line);
 
             if (attr != QLatin1StringView("EMAIL")) {
                 connect(le, &QLineEdit::textChanged, le, [this]() {
                     updateDN();
                 });
             }
             connect(le, &QLineEdit::textChanged, le, [this]() {
                 checkRequirements();
             });
         }
     }
 
     void updateDN()
     {
         ui.dn->setText(cmsDN());
     }
 
     QString cmsDN() const
     {
         QGpgME::DN dn;
         for (const Line &line : ui.lines) {
             const QString text = line.edit->text().trimmed();
             if (text.isEmpty()) {
                 continue;
             }
             QString attr = attributeFromKey(line.attr);
             if (attr == QLatin1StringView("EMAIL")) {
                 continue;
             }
             if (const char *const oid = oidForAttributeName(attr)) {
                 attr = QString::fromUtf8(oid);
             }
             dn.append(QGpgME::DN::Attribute(attr, text));
         }
         return dn.dn();
     }
 
     void checkRequirements()
     {
         const QString error = requirementsAreMet(ui.lines);
         ui.error->setText(error);
         Q_EMIT q->validityChanged(error.isEmpty());
     }
 
     QLineEdit *attributeWidget(const QString &attribute)
     {
         for (const Line &line : ui.lines) {
             if (attributeFromKey(line.attr) == attribute) {
                 return line.edit;
             }
         }
         qCWarning(KLEOPATRA_LOG) << "CertificateDetailsInputWidget: No widget for attribute" << attribute;
         return nullptr;
     }
 
     void setAttributeValue(const QString &attribute, const QString &value)
     {
         QLineEdit *w = attributeWidget(attribute);
         if (w) {
             w->setText(value);
         }
     }
 
     QString attributeValue(const QString &attribute)
     {
         const QLineEdit *w = attributeWidget(attribute);
         return w ? w->text().trimmed() : QString();
     }
 };
 
 CertificateDetailsInputWidget::CertificateDetailsInputWidget(QWidget *parent)
     : QWidget(parent)
     , d(new Private(this))
 {
 }
 
 CertificateDetailsInputWidget::~CertificateDetailsInputWidget()
 {
 }
 
 void CertificateDetailsInputWidget::setName(const QString &name)
 {
     d->setAttributeValue(QStringLiteral("CN"), name);
 }
 
 void CertificateDetailsInputWidget::setEmail(const QString &email)
 {
     d->setAttributeValue(QStringLiteral("EMAIL"), email);
 }
 
 QString CertificateDetailsInputWidget::email() const
 {
     return d->attributeValue(QStringLiteral("EMAIL"));
 }
 
 QString CertificateDetailsInputWidget::dn() const
 {
     return d->ui.dn->text();
 }
 
 #include "moc_certificatedetailsinputwidget.cpp"
diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp
index 4fcabde59..90ccd19c2 100644
--- a/src/dialogs/certificatedetailswidget.cpp
+++ b/src/dialogs/certificatedetailswidget.cpp
@@ -1,724 +1,724 @@
 /*
     This file is part of Kleopatra, the KDE keymanager
     SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
     SPDX-FileCopyrightText: 2017 Intevation GmbH
     SPDX-FileCopyrightText: 2022 g10 Code GmbH
     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
     SPDX-FileCopyrightText: 2022 Felix Tiede
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
 #include <config-kleopatra.h>
 
 #include "certificatedetailswidget.h"
 
 #include "certificatedumpwidget.h"
 #include "dialogs/weboftrustwidget.h"
 #include "kleopatra_debug.h"
 #include "revokerswidget.h"
 #include "subkeyswidget.h"
 #include "trustchainwidget.h"
 #include "useridswidget.h"
 
 #include "commands/changeexpirycommand.h"
 #include "commands/detailscommand.h"
 #include "utils/accessibility.h"
 #include "view/infofield.h"
 
 #include <Libkleo/Algorithm>
 #include <Libkleo/Compliance>
-#include <Libkleo/Dn>
+#include <Libkleo/DnAttributes>
 #include <Libkleo/Formatting>
 #include <Libkleo/GnuPG>
 #include <Libkleo/KeyCache>
 #include <Libkleo/KeyHelpers>
 #include <Libkleo/TreeWidget>
 
 #include <KLocalizedString>
 #include <KMessageBox>
 #include <KSeparator>
 
 #include <gpgme++/context.h>
 #include <gpgme++/key.h>
 #include <gpgme++/keylistresult.h>
 
 #include <gpgme.h>
 
 #include <QGpgME/DN>
 #include <QGpgME/Debug>
 #include <QGpgME/KeyListJob>
 #include <QGpgME/Protocol>
 
 #include <QClipboard>
 #include <QDateTime>
 #include <QGridLayout>
 #include <QGuiApplication>
 #include <QHBoxLayout>
 #include <QLabel>
 #include <QListWidget>
 #include <QLocale>
 #include <QMenu>
 #include <QPushButton>
 #include <QStackedWidget>
 #include <QStringBuilder>
 #include <QTreeWidget>
 #include <QVBoxLayout>
 
 #include <map>
 #if __has_include(<ranges>)
 #include <ranges>
 #if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
 #define USE_RANGES
 #endif
 #endif
 #include <set>
 
 Q_DECLARE_METATYPE(GpgME::UserID)
 
 using namespace Kleo;
 
 class CertificateDetailsWidget::Private
 {
 public:
     Private(CertificateDetailsWidget *qq);
 
     void setupCommonProperties();
     void setUpSMIMEAdressList();
     void setupPGPProperties();
     void setupSMIMEProperties();
 
     void refreshCertificate();
     void changeExpiration();
     void keysMayHaveChanged();
 
     QIcon trustLevelIcon(const GpgME::UserID &uid) const;
     QString trustLevelText(const GpgME::UserID &uid) const;
 
     void showIssuerCertificate();
 
     void updateKey();
     void setUpdatedKey(const GpgME::Key &key);
     void keyListDone(const GpgME::KeyListResult &, const std::vector<GpgME::Key> &, const QString &, const GpgME::Error &);
     void copyFingerprintToClipboard();
     void setUpdateInProgress(bool updateInProgress);
     void setTabVisible(QWidget *tab, bool visible);
 
 private:
     CertificateDetailsWidget *const q;
 
 public:
     GpgME::Key key;
     bool updateInProgress = false;
 
 private:
     InfoField *attributeField(const QString &attributeName)
     {
         const auto keyValuePairIt = ui.smimeAttributeFields.find(attributeName);
         if (keyValuePairIt != ui.smimeAttributeFields.end()) {
             return (*keyValuePairIt).second.get();
         }
         return nullptr;
     }
 
 private:
     struct UI {
         UserIdsWidget *userIDs = nullptr;
 
         std::map<QString, std::unique_ptr<InfoField>> smimeAttributeFields;
         std::unique_ptr<InfoField> smimeTrustLevelField;
         std::unique_ptr<InfoField> validFromField;
         std::unique_ptr<InfoField> expiresField;
         QAction *changeExpirationAction = nullptr;
         std::unique_ptr<InfoField> fingerprintField;
         QAction *copyFingerprintAction = nullptr;
         std::unique_ptr<InfoField> smimeIssuerField;
         QAction *showIssuerCertificateAction = nullptr;
         std::unique_ptr<InfoField> complianceField;
         std::unique_ptr<InfoField> trustedIntroducerField;
         std::unique_ptr<InfoField> primaryUserIdField;
         std::unique_ptr<InfoField> privateKeyInfoField;
         std::unique_ptr<InfoField> statusField;
         std::unique_ptr<InfoField> usageField;
 
         QListWidget *smimeAddressList = nullptr;
 
         QTabWidget *tabWidget = nullptr;
         SubKeysWidget *subKeysWidget = nullptr;
         WebOfTrustWidget *webOfTrustWidget = nullptr;
         TrustChainWidget *trustChainWidget = nullptr;
         CertificateDumpWidget *certificateDumpWidget = nullptr;
         RevokersWidget *revokersWidget = nullptr;
 
         void setupUi(QWidget *parent)
         {
             auto mainLayout = new QVBoxLayout{parent};
 
             {
                 auto gridLayout = new QGridLayout;
                 gridLayout->setColumnStretch(1, 1);
 
                 int row = -1;
 
                 row++;
                 primaryUserIdField = std::make_unique<InfoField>(i18n("User ID:"), parent);
                 gridLayout->addWidget(primaryUserIdField->label(), row, 0);
                 gridLayout->addLayout(primaryUserIdField->layout(), row, 1);
 
-                for (const auto &attribute : DN::attributeOrder()) {
-                    const auto attributeLabel = DN::attributeNameToLabel(attribute);
+                for (const auto &attribute : DNAttributes::order()) {
+                    const auto attributeLabel = DNAttributes::nameToLabel(attribute);
                     if (attributeLabel.isEmpty()) {
                         continue;
                     }
                     const auto labelWithColon = i18nc("interpunctation for labels", "%1:", attributeLabel);
                     const auto &[it, inserted] = smimeAttributeFields.try_emplace(attribute, std::make_unique<InfoField>(labelWithColon, parent));
                     if (inserted) {
                         row++;
                         const auto &field = it->second;
                         gridLayout->addWidget(field->label(), row, 0);
                         gridLayout->addLayout(field->layout(), row, 1);
                     }
                 }
 
                 row++;
                 smimeTrustLevelField = std::make_unique<InfoField>(i18n("Trust level:"), parent);
                 gridLayout->addWidget(smimeTrustLevelField->label(), row, 0);
                 gridLayout->addLayout(smimeTrustLevelField->layout(), row, 1);
 
                 row++;
                 validFromField = std::make_unique<InfoField>(i18n("Valid from:"), parent);
                 gridLayout->addWidget(validFromField->label(), row, 0);
                 gridLayout->addLayout(validFromField->layout(), row, 1);
 
                 row++;
                 expiresField = std::make_unique<InfoField>(i18n("Valid until:"), parent);
                 changeExpirationAction = new QAction{parent};
                 changeExpirationAction->setIcon(QIcon::fromTheme(QStringLiteral("editor")));
                 changeExpirationAction->setToolTip(i18nc("@info:tooltip", "Change the end of the validity period"));
                 Kleo::setAccessibleName(changeExpirationAction, i18nc("@action:button", "Change Validity"));
                 expiresField->setAction(changeExpirationAction);
                 gridLayout->addWidget(expiresField->label(), row, 0);
                 gridLayout->addLayout(expiresField->layout(), row, 1);
 
                 row++;
                 statusField = std::make_unique<InfoField>(i18n("Status:"), parent);
                 gridLayout->addWidget(statusField->label(), row, 0);
                 gridLayout->addLayout(statusField->layout(), row, 1);
 
                 row++;
                 fingerprintField = std::make_unique<InfoField>(i18n("Fingerprint:"), parent);
                 if (QGuiApplication::clipboard()) {
                     copyFingerprintAction = new QAction{parent};
                     copyFingerprintAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
                     copyFingerprintAction->setToolTip(i18nc("@info:tooltip", "Copy the fingerprint to the clipboard"));
                     Kleo::setAccessibleName(copyFingerprintAction, i18nc("@action:button", "Copy fingerprint"));
                     fingerprintField->setAction(copyFingerprintAction);
                 }
                 gridLayout->addWidget(fingerprintField->label(), row, 0);
                 gridLayout->addLayout(fingerprintField->layout(), row, 1);
 
                 row++;
                 smimeIssuerField = std::make_unique<InfoField>(i18n("Issuer:"), parent);
                 showIssuerCertificateAction = new QAction{parent};
                 showIssuerCertificateAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information")));
                 showIssuerCertificateAction->setToolTip(i18nc("@info:tooltip", "Show the issuer certificate"));
                 Kleo::setAccessibleName(showIssuerCertificateAction, i18nc("@action:button", "Show certificate"));
                 smimeIssuerField->setAction(showIssuerCertificateAction);
                 gridLayout->addWidget(smimeIssuerField->label(), row, 0);
                 gridLayout->addLayout(smimeIssuerField->layout(), row, 1);
 
                 row++;
                 complianceField = std::make_unique<InfoField>(i18n("Compliance:"), parent);
                 gridLayout->addWidget(complianceField->label(), row, 0);
                 gridLayout->addLayout(complianceField->layout(), row, 1);
 
                 row++;
                 usageField = std::make_unique<InfoField>(i18n("Usage:"), parent);
                 gridLayout->addWidget(usageField->label(), row, 0);
                 gridLayout->addLayout(usageField->layout(), row, 1);
 
                 row++;
                 trustedIntroducerField = std::make_unique<InfoField>(i18n("Trusted introducer for:"), parent);
                 gridLayout->addWidget(trustedIntroducerField->label(), row, 0);
                 trustedIntroducerField->setToolTip(i18nc("@info:tooltip", "See certifications for details."));
                 gridLayout->addLayout(trustedIntroducerField->layout(), row, 1);
 
                 row++;
                 privateKeyInfoField = std::make_unique<InfoField>(i18n("Private Key:"), parent);
                 gridLayout->addWidget(privateKeyInfoField->label(), row, 0);
                 gridLayout->addLayout(privateKeyInfoField->layout(), row, 1);
 
                 mainLayout->addLayout(gridLayout);
             }
 
             tabWidget = new QTabWidget(parent);
             tabWidget->setDocumentMode(true); // we don't want a frame around the page widgets
             tabWidget->tabBar()->setDrawBase(false); // only draw the tabs
 
             mainLayout->addWidget(tabWidget);
 
             userIDs = new UserIdsWidget(parent);
 
             tabWidget->addTab(userIDs, i18nc("@title:tab", "User IDs"));
             smimeAddressList = new QListWidget{parent};
             // Breeze draws no frame for scroll areas that are the only widget in a layout...unless we force it
             smimeAddressList->setProperty("_breeze_force_frame", true);
             smimeAddressList->setAccessibleName(i18n("Related addresses"));
             smimeAddressList->setEditTriggers(QAbstractItemView::NoEditTriggers);
             smimeAddressList->setSelectionMode(QAbstractItemView::SingleSelection);
             tabWidget->addTab(smimeAddressList, i18nc("@title:tab", "Related Addresses"));
 
             subKeysWidget = new SubKeysWidget(parent);
             tabWidget->addTab(subKeysWidget, i18nc("@title:tab", "Subkeys"));
 
             webOfTrustWidget = new WebOfTrustWidget(parent);
             tabWidget->addTab(webOfTrustWidget, i18nc("@title:tab", "Certifications"));
 
             trustChainWidget = new TrustChainWidget(parent);
             tabWidget->addTab(trustChainWidget, i18nc("@title:tab", "Trust Chain Details"));
 
             certificateDumpWidget = new CertificateDumpWidget(parent);
             tabWidget->addTab(certificateDumpWidget, i18nc("@title:tab", "Certificate Dump"));
 
 #if GPGME_VERSION_NUMBER >= 0x011800 // 1.24.0
             revokersWidget = new RevokersWidget(parent);
             const auto index = tabWidget->addTab(revokersWidget, i18nc("@title:tab", "Revokers"));
             tabWidget->setTabVisible(index, false);
 #endif
         }
     } ui;
 };
 
 CertificateDetailsWidget::Private::Private(CertificateDetailsWidget *qq)
     : q{qq}
 {
     ui.setupUi(q);
 
     connect(ui.changeExpirationAction, &QAction::triggered, q, [this]() {
         changeExpiration();
     });
     connect(ui.showIssuerCertificateAction, &QAction::triggered, q, [this]() {
         showIssuerCertificate();
     });
     if (ui.copyFingerprintAction) {
         connect(ui.copyFingerprintAction, &QAction::triggered, q, [this]() {
             copyFingerprintToClipboard();
         });
     }
 
     connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() {
         keysMayHaveChanged();
     });
     connect(ui.userIDs, &UserIdsWidget::updateKey, q, [this]() {
         updateKey();
     });
 }
 
 void CertificateDetailsWidget::Private::setupCommonProperties()
 {
     const bool isOpenPGP = key.protocol() == GpgME::OpenPGP;
     const bool isSMIME = key.protocol() == GpgME::CMS;
     const bool isOwnKey = key.hasSecret();
 
     for (const auto &[_, field] : ui.smimeAttributeFields) {
         field->setVisible(isSMIME);
     }
     ui.smimeTrustLevelField->setVisible(isSMIME);
     // ui.validFromField->setVisible(true); // always visible
     // ui.expiresField->setVisible(true); // always visible
     if (isOpenPGP && isOwnKey) {
         ui.expiresField->setAction(ui.changeExpirationAction);
     } else {
         ui.expiresField->setAction(nullptr);
     }
     // ui.fingerprintField->setVisible(true); // always visible
     ui.smimeIssuerField->setVisible(isSMIME);
     ui.complianceField->setVisible(DeVSCompliance::isCompliant());
     ui.trustedIntroducerField->setVisible(isOpenPGP); // may be hidden again by setupPGPProperties()
 
     // update availability of buttons
     ui.changeExpirationAction->setEnabled(canBeUsedForSecretKeyOperations(key));
 
     // update values of protocol-independent UI elements
     ui.validFromField->setValue(Formatting::creationDateString(key), Formatting::accessibleCreationDate(key));
     ui.expiresField->setValue(Formatting::expirationDateString(key, i18nc("Valid until:", "unlimited")), Formatting::accessibleExpirationDate(key));
     ui.fingerprintField->setValue(Formatting::prettyID(key.primaryFingerprint()), Formatting::accessibleHexID(key.primaryFingerprint()));
     ui.statusField->setValue(Formatting::complianceStringShort(key));
 
     QString storage;
     const auto &subkey = key.subkey(0);
     if (!key.hasSecret()) {
         storage = i18nc("not applicable", "n/a");
     } else if (key.subkey(0).isCardKey()) {
         if (const char *serialNo = subkey.cardSerialNumber()) {
             storage = i18nc("As in 'this secret key is stored on smart card <serial number>'", "smart card %1", QString::fromUtf8(serialNo));
         } else {
             storage = i18nc("As in 'this secret key is stored on a smart card'", "smart card");
         }
     } else if (!subkey.isSecret()) {
         storage = i18nc("key is 'offline key', i.e. secret key is not stored on this computer", "offline");
     } else if (KeyCache::instance()->cardsForSubkey(subkey).size() > 0) {
         storage = i18ncp("As in 'this key is stored on this computer and on smart card(s)'",
                          "On this computer and on a smart card",
                          "On this computer and on %1 smart cards",
                          KeyCache::instance()->cardsForSubkey(subkey).size());
     } else {
         storage = i18nc("As in 'this secret key is stored on this computer'", "on this computer");
     }
     ui.privateKeyInfoField->setValue(storage);
     if (DeVSCompliance::isCompliant()) {
         ui.complianceField->setValue(Kleo::Formatting::complianceStringForKey(key));
     }
 
     ui.usageField->setVisible(key.protocol() == GpgME::CMS);
 
     QStringList usage;
 
     if (subkey.canEncrypt()) {
         usage += i18n("encryption");
     }
     if (subkey.canSign()) {
         usage += i18n("signing");
     }
     if (subkey.canCertify()) {
         usage += i18n("certification");
     }
     if (subkey.canAuthenticate()) {
         usage += i18n("authentication");
     }
     if (subkey.isQualified()) {
         usage += i18nc("as in: can be used for ...", "qualified signatures");
     }
 
     ui.usageField->setValue(usage.join(i18nc("Separator between words in a list", ", ")));
 }
 
 void CertificateDetailsWidget::Private::setUpSMIMEAdressList()
 {
     ui.smimeAddressList->clear();
 
     const auto *const emailField = attributeField(QStringLiteral("EMAIL"));
 
     // add email address from primary user ID if not listed already as attribute field
     if (!emailField) {
         const auto ownerId = key.userID(0);
         const QGpgME::DN dn(ownerId.id());
         const QString dnEmail = dn[QStringLiteral("EMAIL")];
         if (!dnEmail.isEmpty()) {
             ui.smimeAddressList->addItem(dnEmail);
         }
     }
 
     if (key.numUserIDs() > 1) {
         // iterate over the secondary user IDs
 #ifdef USE_RANGES
         for (const auto uids = key.userIDs(); const auto &uid : std::ranges::subrange(std::next(uids.begin()), uids.end())) {
 #else
         const auto uids = key.userIDs();
         for (auto it = std::next(uids.begin()); it != uids.end(); ++it) {
             const auto &uid = *it;
 #endif
             const auto name = Kleo::Formatting::prettyName(uid);
             const auto email = Kleo::Formatting::prettyEMail(uid);
             QString itemText;
             if (name.isEmpty() && !email.isEmpty()) {
                 // skip email addresses already listed in email attribute field
                 if (emailField && email == emailField->value()) {
                     continue;
                 }
                 itemText = email;
             } else {
                 // S/MIME certificates sometimes contain urls where both
                 // name and mail is empty. In that case we print whatever
                 // the uid is as name.
                 //
                 // Can be ugly like (3:uri24:http://ca.intevation.org), but
                 // this is better then showing an empty entry.
                 itemText = QString::fromUtf8(uid.id());
             }
             // avoid duplicate entries in the list
             if (ui.smimeAddressList->findItems(itemText, Qt::MatchExactly).empty()) {
                 ui.smimeAddressList->addItem(itemText);
             }
         }
     }
 
     if (ui.smimeAddressList->count() == 0) {
         ui.tabWidget->setTabVisible(1, false);
     }
 }
 
 void CertificateDetailsWidget::Private::changeExpiration()
 {
     auto cmd = new Kleo::Commands::ChangeExpiryCommand(key);
     QObject::connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, q, [this]() {
         ui.changeExpirationAction->setEnabled(true);
     });
     ui.changeExpirationAction->setEnabled(false);
     cmd->start();
 }
 
 namespace
 {
 void ensureThatKeyDetailsAreLoaded(GpgME::Key &key)
 {
     if (key.userID(0).numSignatures() == 0) {
         key.update();
     }
 }
 }
 
 void CertificateDetailsWidget::Private::keysMayHaveChanged()
 {
     auto newKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint());
     if (!newKey.isNull()) {
         ensureThatKeyDetailsAreLoaded(newKey);
         setUpdatedKey(newKey);
     }
 }
 
 QIcon CertificateDetailsWidget::Private::trustLevelIcon(const GpgME::UserID &uid) const
 {
     if (updateInProgress) {
         return QIcon::fromTheme(QStringLiteral("emblem-question"));
     }
     switch (uid.validity()) {
     case GpgME::UserID::Unknown:
     case GpgME::UserID::Undefined:
         return QIcon::fromTheme(QStringLiteral("emblem-question"));
     case GpgME::UserID::Never:
         return QIcon::fromTheme(QStringLiteral("emblem-error"));
     case GpgME::UserID::Marginal:
         return QIcon::fromTheme(QStringLiteral("emblem-warning"));
     case GpgME::UserID::Full:
     case GpgME::UserID::Ultimate:
         return QIcon::fromTheme(QStringLiteral("emblem-success"));
     }
     return {};
 }
 
 QString CertificateDetailsWidget::Private::trustLevelText(const GpgME::UserID &uid) const
 {
     return updateInProgress ? i18n("Updating...") : Formatting::validityShort(uid);
 }
 
 namespace
 {
 auto isGood(const GpgME::UserID::Signature &signature)
 {
     return signature.status() == GpgME::UserID::Signature::NoError //
         && !signature.isInvalid() //
         && 0x10 <= signature.certClass() && signature.certClass() <= 0x13;
 }
 
 auto accumulateTrustDomains(const std::vector<GpgME::UserID::Signature> &signatures)
 {
     return std::accumulate(std::begin(signatures), std::end(signatures), std::set<QString>(), [](auto domains, const auto &signature) {
         if (isGood(signature) && signature.isTrustSignature()) {
             domains.insert(Formatting::trustSignatureDomain(signature));
         }
         return domains;
     });
 }
 
 auto accumulateTrustDomains(const std::vector<GpgME::UserID> &userIds)
 {
     return std::accumulate(std::begin(userIds), std::end(userIds), std::set<QString>(), [](auto domains, const auto &userID) {
         const auto newDomains = accumulateTrustDomains(userID.signatures());
         std::copy(std::begin(newDomains), std::end(newDomains), std::inserter(domains, std::end(domains)));
         return domains;
     });
 }
 }
 
 void CertificateDetailsWidget::Private::setTabVisible(QWidget *tab, bool visible)
 {
     ui.tabWidget->setTabVisible(ui.tabWidget->indexOf(tab), visible);
 }
 
 void CertificateDetailsWidget::Private::setupPGPProperties()
 {
     setTabVisible(ui.userIDs, true);
     setTabVisible(ui.smimeAddressList, false);
     setTabVisible(ui.subKeysWidget, true);
     setTabVisible(ui.webOfTrustWidget, true);
     setTabVisible(ui.trustChainWidget, false);
     setTabVisible(ui.certificateDumpWidget, false);
 
     ui.userIDs->setKey(key);
     ui.subKeysWidget->setKey(key);
     ui.webOfTrustWidget->setKey(key);
 #if GPGME_VERSION_NUMBER >= 0x011800 // 1.24.0
     ui.revokersWidget->setKey(key);
     setTabVisible(ui.revokersWidget, key.numRevocationKeys() > 0);
 #endif
 
     const auto trustDomains = accumulateTrustDomains(key.userIDs());
     ui.trustedIntroducerField->setVisible(!trustDomains.empty());
     ui.trustedIntroducerField->setValue(QStringList(std::begin(trustDomains), std::end(trustDomains)).join(u", "));
 
     ui.primaryUserIdField->setValue(Formatting::prettyUserID(key.userID(0)));
     ui.primaryUserIdField->setVisible(true);
 }
 
 static QString formatDNToolTip(const QGpgME::DN &dn)
 {
     QString html = QStringLiteral("<table border=\"0\" cell-spacing=15>");
 
     const auto appendRow = [&html, dn](const QString &lbl, const QString &attr) {
         const QString val = dn[attr];
         if (!val.isEmpty()) {
             html += QStringLiteral(
                         "<tr><th style=\"text-align: left; white-space: nowrap\">%1:</th>"
                         "<td style=\"white-space: nowrap\">%2</td>"
                         "</tr>")
                         .arg(lbl, val);
         }
     };
     appendRow(i18n("Common Name"), QStringLiteral("CN"));
     appendRow(i18n("Organization"), QStringLiteral("O"));
     appendRow(i18n("Street"), QStringLiteral("STREET"));
     appendRow(i18n("City"), QStringLiteral("L"));
     appendRow(i18n("State"), QStringLiteral("ST"));
     appendRow(i18n("Country"), QStringLiteral("C"));
     html += QStringLiteral("</table>");
 
     return html;
 }
 
 void CertificateDetailsWidget::Private::setupSMIMEProperties()
 {
     setTabVisible(ui.userIDs, false);
     setTabVisible(ui.smimeAddressList, true);
     setTabVisible(ui.subKeysWidget, false);
     setTabVisible(ui.webOfTrustWidget, false);
     setTabVisible(ui.trustChainWidget, true);
     setTabVisible(ui.certificateDumpWidget, true);
 
     ui.trustChainWidget->setKey(key);
     const auto ownerId = key.userID(0);
     const QGpgME::DN dn(ownerId.id());
 
     for (const auto &[attributeName, field] : ui.smimeAttributeFields) {
         const QString attributeValue = dn[attributeName];
         field->setValue(attributeValue);
         field->setVisible(!attributeValue.isEmpty());
     }
     ui.smimeTrustLevelField->setIcon(trustLevelIcon(ownerId));
     ui.smimeTrustLevelField->setValue(trustLevelText(ownerId));
 
     const QGpgME::DN issuerDN(key.issuerName());
     const QString issuerCN = issuerDN[QStringLiteral("CN")];
     const QString issuer = issuerCN.isEmpty() ? QString::fromUtf8(key.issuerName()) : issuerCN;
     ui.smimeIssuerField->setValue(issuer);
     ui.smimeIssuerField->setToolTip(formatDNToolTip(issuerDN));
     ui.showIssuerCertificateAction->setEnabled(!key.isRoot());
 
     ui.primaryUserIdField->setVisible(false);
 
     ui.certificateDumpWidget->setKey(key);
 
     setUpSMIMEAdressList();
 }
 
 void CertificateDetailsWidget::Private::showIssuerCertificate()
 {
     // there is either one or no parent key
     const auto parentKeys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption);
 
     if (parentKeys.empty()) {
         KMessageBox::error(q, i18n("The issuer certificate could not be found locally."));
         return;
     }
     auto cmd = new Kleo::Commands::DetailsCommand(parentKeys.front());
     cmd->setParentWidget(q);
     cmd->start();
 }
 
 void CertificateDetailsWidget::Private::copyFingerprintToClipboard()
 {
     if (auto clipboard = QGuiApplication::clipboard()) {
         clipboard->setText(QString::fromLatin1(key.primaryFingerprint()));
     }
 }
 
 CertificateDetailsWidget::CertificateDetailsWidget(QWidget *parent)
     : QWidget{parent}
     , d{std::make_unique<Private>(this)}
 {
 }
 
 CertificateDetailsWidget::~CertificateDetailsWidget() = default;
 
 void CertificateDetailsWidget::Private::keyListDone(const GpgME::KeyListResult &, const std::vector<GpgME::Key> &keys, const QString &, const GpgME::Error &)
 {
     setUpdateInProgress(false);
     if (keys.size() != 1) {
         qCWarning(KLEOPATRA_LOG) << "Invalid keylist result in update.";
         return;
     }
     // As we listen for keysmayhavechanged we get the update
     // after updating the keycache.
     KeyCache::mutableInstance()->insert(keys);
 }
 
 void CertificateDetailsWidget::Private::updateKey()
 {
     key.update();
     setUpdatedKey(key);
 }
 
 void CertificateDetailsWidget::Private::setUpdatedKey(const GpgME::Key &k)
 {
     key = k;
 
     setupCommonProperties();
     if (key.protocol() == GpgME::OpenPGP) {
         setupPGPProperties();
     } else {
         setupSMIMEProperties();
     }
 }
 
 void CertificateDetailsWidget::setKey(const GpgME::Key &key)
 {
     if (key.protocol() == GpgME::CMS) {
         // For everything but S/MIME this should be quick
         // and we don't need to show another status.
         d->setUpdateInProgress(true);
     }
     d->setUpdatedKey(key);
 
     // Run a keylistjob with full details (TOFU / Validate)
     QGpgME::KeyListJob *job =
         key.protocol() == GpgME::OpenPGP ? QGpgME::openpgp()->keyListJob(false, true, true) : QGpgME::smime()->keyListJob(false, true, true);
 
     auto ctx = QGpgME::Job::context(job);
     ctx->addKeyListMode(GpgME::WithTofu);
     ctx->addKeyListMode(GpgME::SignatureNotations);
     ctx->addKeyListMode(GpgME::WithKeygrip);
     if (key.hasSecret()) {
         ctx->addKeyListMode(GpgME::WithSecret);
     }
 
     // Windows QGpgME new style connect problem makes this necessary.
     connect(job,
             SIGNAL(result(GpgME::KeyListResult, std::vector<GpgME::Key>, QString, GpgME::Error)),
             this,
             SLOT(keyListDone(GpgME::KeyListResult, std::vector<GpgME::Key>, QString, GpgME::Error)));
 
     job->start(QStringList() << QLatin1StringView(key.primaryFingerprint()));
 }
 
 GpgME::Key CertificateDetailsWidget::key() const
 {
     return d->key;
 }
 
 void CertificateDetailsWidget::Private::setUpdateInProgress(bool updateInProgress)
 {
     this->updateInProgress = updateInProgress;
     ui.userIDs->setUpdateInProgress(updateInProgress);
 }
 
 #include "moc_certificatedetailswidget.cpp"
diff --git a/src/kleopatraapplication.cpp b/src/kleopatraapplication.cpp
index 85067b270..589274c71 100644
--- a/src/kleopatraapplication.cpp
+++ b/src/kleopatraapplication.cpp
@@ -1,923 +1,923 @@
 /*
     This file is part of Kleopatra, the KDE keymanager
     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
 
     SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
     SPDX-FileContributor: Intevation GmbH
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
 #include <config-kleopatra.h>
 
 #include "kleopatraapplication.h"
 
 #include "kleopatra_options.h"
 #include "mainwindow.h"
 #include "settings.h"
 #include "systrayicon.h"
 
 #include <conf/configuredialog.h>
 #include <conf/groupsconfigdialog.h>
 #include <dialogs/smartcardwindow.h>
 #include <smartcard/readerstatus.h>
 
 #include <Libkleo/GnuPG>
 #include <utils/kdpipeiodevice.h>
 #include <utils/log.h>
 #include <utils/userinfo.h>
 
 #include <gpgme++/key.h>
 
 #include <Libkleo/Classify>
-#include <Libkleo/Dn>
+#include <Libkleo/DnAttributes>
 #include <Libkleo/FileSystemWatcher>
 #include <Libkleo/KeyCache>
 #include <Libkleo/KeyFilterManager>
 #include <Libkleo/KeyGroupConfig>
 #include <Libkleo/SystemInfo>
 
 #include <uiserver/uiserver.h>
 
 #include "commands/checksumcreatefilescommand.h"
 #include "commands/checksumverifyfilescommand.h"
 #include "commands/decryptverifyfilescommand.h"
 #include "commands/detailscommand.h"
 #include "commands/importcertificatefromfilecommand.h"
 #include "commands/lookupcertificatescommand.h"
 #include "commands/newcertificatesigningrequestcommand.h"
 #include "commands/newopenpgpcertificatecommand.h"
 #include "commands/signencryptfilescommand.h"
 
 #include "dialogs/updatenotification.h"
 
 #ifdef Q_OS_WIN
 #include <utils/winapi-helpers.h>
 #endif
 
 #include "kleopatra_debug.h"
 #include <KLocalizedString>
 #include <KMessageBox>
 #include <KWindowSystem>
 
 #if __has_include(<KWaylandExtras>)
 #include <KWaylandExtras>
 #define HAVE_WAYLAND
 #endif
 
 #include <QDesktopServices>
 #include <QDir>
 #include <QFile>
 #include <QFocusFrame>
 #include <QProxyStyle>
 #if QT_CONFIG(graphicseffect)
 #include <QGraphicsEffect>
 #endif
 #include <QPointer>
 #include <QSettings>
 #include <QStyleOption>
 #include <QStylePainter>
 
 #include <KSharedConfig>
 
 #ifdef Q_OS_WIN
 #include <windows.h>
 #endif
 
 using namespace Kleo;
 using namespace Kleo::Commands;
 
 using namespace Qt::Literals::StringLiterals;
 
 static void add_resources()
 {
     const QStringList iconDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, u"libkleopatra/pics"_s, QStandardPaths::LocateDirectory);
     // qCDebug(KLEOPATRA_LOG) << "Adding icon search paths:" << iconDirs;
     QIcon::setFallbackSearchPaths(QIcon::fallbackSearchPaths() << iconDirs);
 }
 
 static QList<QByteArray> default_logging_options()
 {
     QList<QByteArray> result;
     result.push_back("io");
     return result;
 }
 
 namespace
 {
 class FocusFrame : public QFocusFrame
 {
     Q_OBJECT
 public:
     using QFocusFrame::QFocusFrame;
 
 protected:
     void paintEvent(QPaintEvent *event) override;
 };
 
 static QRect effectiveWidgetRect(const QWidget *w)
 {
     // based on QWidgetPrivate::effectiveRectFor
 #if QT_CONFIG(graphicseffect)
     if (auto graphicsEffect = w->graphicsEffect(); graphicsEffect && graphicsEffect->isEnabled())
         return graphicsEffect->boundingRectFor(w->rect()).toAlignedRect();
 #endif // QT_CONFIG(graphicseffect)
     return w->rect();
 }
 
 static QRect clipRect(const QWidget *w)
 {
     // based on QWidgetPrivate::clipRect
     if (!w->isVisible()) {
         return QRect();
     }
     QRect r = effectiveWidgetRect(w);
     int ox = 0;
     int oy = 0;
     while (w && w->isVisible() && !w->isWindow() && w->parentWidget()) {
         ox -= w->x();
         oy -= w->y();
         w = w->parentWidget();
         r &= QRect(ox, oy, w->width(), w->height());
     }
     return r;
 }
 
 void FocusFrame::paintEvent(QPaintEvent *)
 {
     if (!widget()) {
         return;
     }
 
     QStylePainter p(this);
     QStyleOptionFocusRect option;
     initStyleOption(&option);
     const int vmargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &option);
     const int hmargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option);
     const QRect rect = clipRect(widget()).adjusted(0, 0, hmargin * 2, vmargin * 2);
     p.setClipRect(rect);
     p.drawPrimitive(QStyle::PE_FrameFocusRect, option);
 }
 }
 
 class KleopatraApplication::Private
 {
     friend class ::KleopatraApplication;
     KleopatraApplication *const q;
 
 public:
     explicit Private(KleopatraApplication *qq)
         : q(qq)
         , ignoreNewInstance(true)
         , firstNewInstance(true)
 #ifndef QT_NO_SYSTEMTRAYICON
         , sysTray(nullptr)
 #endif
     {
     }
     ~Private()
     {
 #ifndef QT_NO_SYSTEMTRAYICON
         delete sysTray;
 #endif
     }
     void setUpSysTrayIcon()
     {
 #ifndef QT_NO_SYSTEMTRAYICON
         Q_ASSERT(readerStatus);
         sysTray = new SysTrayIcon();
         sysTray->setFirstCardWithNullPin(readerStatus->firstCardWithNullPin());
         connect(readerStatus.get(), &SmartCard::ReaderStatus::firstCardWithNullPinChanged, sysTray, &SysTrayIcon::setFirstCardWithNullPin);
 #endif
     }
 
 private:
     void connectConfigureDialog()
     {
         if (configureDialog) {
             if (q->mainWindow()) {
                 connect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted()));
             }
             connect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged);
         }
     }
     void disconnectConfigureDialog()
     {
         if (configureDialog) {
             if (q->mainWindow()) {
                 disconnect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted()));
             }
             disconnect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged);
         }
     }
 
 public:
     bool ignoreNewInstance;
     bool firstNewInstance;
     QPointer<FocusFrame> focusFrame;
     QPointer<ConfigureDialog> configureDialog;
     QPointer<GroupsConfigDialog> groupsConfigDialog;
     QPointer<MainWindow> mainWindow;
     QPointer<SmartCardWindow> smartCardWindow;
     std::unique_ptr<SmartCard::ReaderStatus> readerStatus;
 #ifndef QT_NO_SYSTEMTRAYICON
     SysTrayIcon *sysTray;
 #endif
     std::shared_ptr<KeyGroupConfig> groupConfig;
     std::shared_ptr<KeyCache> keyCache;
     std::shared_ptr<Log> log;
     std::shared_ptr<FileSystemWatcher> watcher;
     std::shared_ptr<QSettings> distroSettings;
 
 public:
     void setupKeyCache()
     {
         keyCache = KeyCache::mutableInstance();
         keyCache->setRefreshInterval(Settings{}.refreshInterval());
         watcher.reset(new FileSystemWatcher);
 
         watcher->whitelistFiles(gnupgFileWhitelist());
         watcher->addPaths(gnupgFolderWhitelist());
         watcher->setDelay(1000);
         keyCache->addFileSystemWatcher(watcher);
         keyCache->setGroupConfig(groupConfig);
         keyCache->setGroupsEnabled(Settings().groupsEnabled());
         // always enable remarks (aka tags); in particular, this triggers a
         // relisting of the keys with signatures and signature notations
         // after the initial (fast) key listing
         keyCache->enableRemarks(true);
     }
 
     void setUpFilterManager()
     {
         if (!Settings{}.cmsEnabled()) {
             KeyFilterManager::instance()->alwaysFilterByProtocol(GpgME::OpenPGP);
         }
     }
 
     void setupLogging()
     {
         log = Log::mutableInstance();
 
         const QByteArray envOptions = qgetenv("KLEOPATRA_LOGOPTIONS");
         const bool logAll = envOptions.trimmed() == "all";
         const QList<QByteArray> options = envOptions.isEmpty() ? default_logging_options() : envOptions.split(',');
 
         const QByteArray dirNative = qgetenv("KLEOPATRA_LOGDIR");
         if (dirNative.isEmpty()) {
             return;
         }
         const QString dir = QFile::decodeName(dirNative);
         const QString logFileName = QDir(dir).absoluteFilePath(QStringLiteral("kleopatra.log.%1").arg(QCoreApplication::applicationPid()));
         std::unique_ptr<QFile> logFile(new QFile(logFileName));
         if (!logFile->open(QIODevice::WriteOnly | QIODevice::Append)) {
             qCDebug(KLEOPATRA_LOG) << "Could not open file for logging: " << logFileName << "\nLogging disabled";
             return;
         }
 
         log->setOutputDirectory(dir);
         if (logAll || options.contains("io")) {
             log->setIOLoggingEnabled(true);
         }
         qInstallMessageHandler(Log::messageHandler);
 
         if (logAll || options.contains("pipeio")) {
             KDPipeIODevice::setDebugLevel(KDPipeIODevice::Debug);
         }
         UiServer::setLogStream(log->logFile());
     }
 
     void updateFocusFrame(QWidget *focusWidget)
     {
         if (focusWidget && focusWidget->inherits("QLabel") && focusWidget->window()->testAttribute(Qt::WA_KeyboardFocusChange)) {
             if (!focusFrame) {
                 focusFrame = new FocusFrame{focusWidget};
             }
             focusFrame->setWidget(focusWidget);
         } else if (focusFrame) {
             focusFrame->setWidget(nullptr);
         }
     }
     MainWindow *getOrCreateMainWindow()
     {
         auto mw = q->mainWindow();
         if (!mw) {
             mw = new MainWindow;
             mw->setAttribute(Qt::WA_DeleteOnClose);
             q->setMainWindow(mw);
         }
         return mw;
     }
     void exportFocusWindow()
     {
 #ifdef HAVE_WAYLAND
         KWaylandExtras::self()->exportWindow(QGuiApplication::focusWindow());
 #endif
     }
 };
 
 class KleopatraProxyStyle : public QProxyStyle
 {
 public:
     int styleHint(StyleHint hint, const QStyleOption *option = nullptr, const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const override
     {
         // disable parent<->child navigation in tree views with left/right arrow keys
         // because this interferes with column by column navigation that is required
         // for accessibility
         if (hint == QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren)
             return 0;
         return QProxyStyle::styleHint(hint, option, widget, returnData);
     }
 };
 
 KleopatraApplication::KleopatraApplication(int &argc, char *argv[])
     : QApplication(argc, argv)
     , d(new Private(this))
 {
     setStyle(new KleopatraProxyStyle);
 #ifdef Q_OS_WIN
     if (SystemInfo::isHighContrastModeActive()) {
         // use colors specified by Windows if high-contrast mode is active
         QPalette highContrastPalette = palette();
 
         const QColor linkColor = win_getSysColor(COLOR_HOTLIGHT);
         highContrastPalette.setColor(QPalette::All, QPalette::Link, linkColor);
         highContrastPalette.setColor(QPalette::All, QPalette::LinkVisited, linkColor);
 
         const QColor disabledColor = win_getSysColor(COLOR_GRAYTEXT);
         highContrastPalette.setColor(QPalette::Disabled, QPalette::WindowText, disabledColor);
         highContrastPalette.setColor(QPalette::Disabled, QPalette::Text, disabledColor);
         highContrastPalette.setColor(QPalette::Disabled, QPalette::ButtonText, disabledColor);
         highContrastPalette.setColor(QPalette::All, QPalette::PlaceholderText, disabledColor);
 
         setPalette(highContrastPalette);
     }
 #endif
     connect(this, &QApplication::focusChanged, this, [this](QWidget *, QWidget *now) {
         d->updateFocusFrame(now);
     });
 }
 
 void KleopatraApplication::init()
 {
     const QString groupConfigPath = Kleo::gnupgHomeDirectory() + QStringLiteral("/kleopatra/kleopatragroupsrc");
     d->groupConfig = std::make_shared<KeyGroupConfig>(groupConfigPath);
 
     const auto blockedUrlSchemes = Settings{}.blockedUrlSchemes();
     for (const auto &scheme : blockedUrlSchemes) {
         QDesktopServices::setUrlHandler(scheme, this, "blockUrl");
     }
     add_resources();
-    DN::setAttributeOrder(Settings{}.attributeOrder());
+    DNAttributes::setOrder(Settings{}.attributeOrder());
     /* Start the gpg-agent early, this is done explicitly
      * because on an empty keyring our keylistings wont start
      * the agent. In that case any assuan-connect calls to
      * the agent will fail. The requested start via the
      * connection is additionally done in case the gpg-agent
      * is killed while Kleopatra is running. */
     startGpgAgent();
     d->readerStatus.reset(new SmartCard::ReaderStatus);
     connect(d->readerStatus.get(), &SmartCard::ReaderStatus::startOfGpgAgentRequested, this, &KleopatraApplication::startGpgAgent);
     d->setupKeyCache();
     d->setUpSysTrayIcon();
     d->setUpFilterManager();
     d->setupLogging();
 
 #ifndef QT_NO_SYSTEMTRAYICON
     if (d->sysTray) {
         d->sysTray->show();
     }
 #endif
 
 #ifdef HAVE_WAYLAND
     connect(KWaylandExtras::self(), &KWaylandExtras::windowExported, this, [](const auto, const auto &token) {
         qputenv("PINENTRY_GEOM_HINT", QUrl::toPercentEncoding(token));
     });
     connect(qApp, &QGuiApplication::focusWindowChanged, this, [this](auto w) {
         if (!w) {
             return;
         }
         d->exportFocusWindow();
     });
 
     QMetaObject::invokeMethod(
         this,
         [this]() {
             d->exportFocusWindow();
         },
         Qt::QueuedConnection);
 #endif
 
     if (!Kleo::userIsElevated()) {
         // For users running Kleo with elevated permissions on Windows we
         // always quit the application when the last window is closed.
         setQuitOnLastWindowClosed(false);
     }
 
     // Sync config when we are about to quit
     connect(this, &QApplication::aboutToQuit, this, []() {
         KSharedConfig::openConfig()->sync();
     });
 }
 
 KleopatraApplication::~KleopatraApplication()
 {
     delete d->groupsConfigDialog;
     delete d->smartCardWindow;
     delete d->mainWindow;
 }
 
 namespace
 {
 using Func = void (KleopatraApplication::*)(const QStringList &, GpgME::Protocol);
 }
 
 void KleopatraApplication::slotActivateRequested(const QStringList &arguments, const QString &workingDirectory)
 {
     QCommandLineParser parser;
 
     kleopatra_options(&parser);
     QString err;
     if (!arguments.isEmpty() && !parser.parse(arguments)) {
         err = parser.errorText();
     } else if (arguments.isEmpty()) {
         // KDBusServices omits the application name if no other
         // arguments are provided. In that case the parser prints
         // a warning.
         parser.parse(QStringList() << QCoreApplication::applicationFilePath());
     }
 
     if (err.isEmpty()) {
         err = newInstance(parser, workingDirectory);
     }
 
     if (!err.isEmpty()) {
         KMessageBox::error(nullptr, err.toHtmlEscaped(), i18nc("@title:window", "Failed to execute command"));
         Q_EMIT setExitValue(1);
         return;
     }
     Q_EMIT setExitValue(0);
 }
 
 QString KleopatraApplication::newInstance(const QCommandLineParser &parser, const QString &workingDirectory)
 {
     if (d->ignoreNewInstance) {
         qCDebug(KLEOPATRA_LOG) << "New instance ignored because of ignoreNewInstance";
         return QString();
     }
 
     QStringList files;
     const QDir cwd = QDir(workingDirectory);
     bool queryMode = parser.isSet(QStringLiteral("query")) || parser.isSet(QStringLiteral("search"));
 
     // Query and Search treat positional arguments differently, see below.
     if (!queryMode) {
         const auto positionalArguments = parser.positionalArguments();
         for (const QString &file : positionalArguments) {
             // We do not check that file exists here. Better handle
             // these errors in the UI.
             if (QFileInfo(file).isAbsolute()) {
                 files << QDir::fromNativeSeparators(file);
             } else {
                 files << cwd.absoluteFilePath(file);
             }
         }
     }
 
     GpgME::Protocol protocol = GpgME::UnknownProtocol;
 
     if (parser.isSet(QStringLiteral("openpgp"))) {
         qCDebug(KLEOPATRA_LOG) << "found OpenPGP";
         protocol = GpgME::OpenPGP;
     }
 
     if (parser.isSet(QStringLiteral("cms"))) {
         qCDebug(KLEOPATRA_LOG) << "found CMS";
         if (protocol == GpgME::OpenPGP) {
             return i18n("Ambiguous protocol: --openpgp and --cms");
         }
         protocol = GpgME::CMS;
     }
 
     // Check for Parent Window id
     WId parentId = 0;
     if (parser.isSet(QStringLiteral("parent-windowid"))) {
 #ifdef Q_OS_WIN
         // WId is not a portable type as it is a pointer type on Windows.
         // casting it from an integer is ok though as the values are guaranteed to
         // be compatible in the documentation.
         parentId = static_cast<WId>((parser.value(QStringLiteral("parent-windowid")).toUInt()));
 #else
         parentId = parser.value(QStringLiteral("parent-windowid")).toUInt();
 #endif
     }
 
     // Handle openpgp4fpr URI scheme
     QString needle;
     if (queryMode) {
         needle = parser.positionalArguments().join(QLatin1Char(' '));
     }
     if (needle.startsWith(QLatin1StringView("openpgp4fpr:"))) {
         needle.remove(0, 12);
     }
 
     // Check for --search command.
     if (parser.isSet(QStringLiteral("search"))) {
         // This is an extra command instead of a combination with the
         // similar query to avoid changing the older query commands behavior
         // and query's "show details if a certificate exist or search on a
         // keyserver" logic is hard to explain and use consistently.
         if (needle.isEmpty()) {
             return i18n("No search string specified for --search");
         }
         auto const cmd = new LookupCertificatesCommand(needle, nullptr);
         cmd->setParentWId(parentId);
         cmd->start();
         return QString();
     }
 
     // Check for --query command
     if (parser.isSet(QStringLiteral("query"))) {
         if (needle.isEmpty()) {
             return i18n("No fingerprint argument specified for --query");
         }
         auto cmd = Command::commandForQuery(needle);
         cmd->setParentWId(parentId);
         cmd->start();
         return QString();
     }
 
     // Check for --gen-key command
     if (parser.isSet(QStringLiteral("gen-key"))) {
         if (protocol == GpgME::CMS) {
             const Kleo::Settings settings{};
             if (settings.cmsEnabled() && settings.cmsCertificateCreationAllowed()) {
                 auto cmd = new NewCertificateSigningRequestCommand;
                 cmd->setParentWId(parentId);
                 cmd->start();
             } else {
                 return i18n("You are not allowed to create S/MIME certificate signing requests.");
             }
         } else {
             auto cmd = new NewOpenPGPCertificateCommand;
             cmd->setParentWId(parentId);
             cmd->start();
         }
         return QString();
     }
 
     // Check for --config command
     if (parser.isSet(QStringLiteral("config"))) {
         openConfigDialogWithForeignParent(parentId);
         return QString();
     }
 
     struct FuncInfo {
         QString optionName;
         Func func;
     };
 
     // While most of these options can be handled by the content autodetection
     // below it might be useful to override the autodetection if the input is in
     // doubt and you e.g. only want to import .asc files or fail and not decrypt them
     // if they are actually encrypted data.
     static const std::vector<FuncInfo> funcMap{
         {QStringLiteral("import-certificate"), &KleopatraApplication::importCertificatesFromFile},
         {QStringLiteral("encrypt"), &KleopatraApplication::encryptFiles},
         {QStringLiteral("sign"), &KleopatraApplication::signFiles},
         {QStringLiteral("encrypt-sign"), &KleopatraApplication::signEncryptFiles},
         {QStringLiteral("sign-encrypt"), &KleopatraApplication::signEncryptFiles},
         {QStringLiteral("decrypt"), &KleopatraApplication::decryptFiles},
         {QStringLiteral("verify"), &KleopatraApplication::verifyFiles},
         {QStringLiteral("decrypt-verify"), &KleopatraApplication::decryptVerifyFiles},
         {QStringLiteral("checksum"), &KleopatraApplication::checksumFiles},
     };
 
     QString found;
     Func foundFunc = nullptr;
     for (const auto &[opt, fn] : funcMap) {
         if (parser.isSet(opt) && found.isEmpty()) {
             found = opt;
             foundFunc = fn;
         } else if (parser.isSet(opt)) {
             return i18n(R"(Ambiguous commands "%1" and "%2")", found, opt);
         }
     }
 
     QStringList errors;
     if (!found.isEmpty()) {
         if (files.empty()) {
             return i18n("No files specified for \"%1\" command", found);
         }
         qCDebug(KLEOPATRA_LOG) << "found" << found;
         (this->*foundFunc)(files, protocol);
     } else {
         if (files.empty()) {
             if (!(d->firstNewInstance && isSessionRestored())) {
                 qCDebug(KLEOPATRA_LOG) << "openOrRaiseMainWindow";
                 openOrRaiseMainWindow();
             }
         } else {
             for (const QString &fileName : std::as_const(files)) {
                 QFileInfo fi(fileName);
                 if (!fi.isReadable()) {
                     errors << i18n("Cannot read \"%1\"", fileName);
                 }
             }
             handleFiles(files, parentId);
         }
     }
     d->firstNewInstance = false;
 
 #ifdef Q_OS_WIN
     // On Windows we might be started from the
     // explorer in any working directory. E.g.
     // a double click on a file. To avoid preventing
     // the folder from deletion we set the
     // working directory to the users homedir.
     QDir::setCurrent(QDir::homePath());
 #endif
 
     return errors.join(QLatin1Char('\n'));
 }
 
 void KleopatraApplication::handleFiles(const QStringList &files, WId parentId)
 {
     auto mw = d->getOrCreateMainWindow();
     const QList<Command *> allCmds = Command::commandsForFiles(files, mw->keyListController());
     for (Command *cmd : allCmds) {
         if (parentId) {
             cmd->setParentWId(parentId);
         } else {
             cmd->setParentWidget(mw);
         }
         if (dynamic_cast<ImportCertificateFromFileCommand *>(cmd)) {
             openOrRaiseMainWindow();
         }
         cmd->start();
     }
 }
 
 const MainWindow *KleopatraApplication::mainWindow() const
 {
     return d->mainWindow;
 }
 
 MainWindow *KleopatraApplication::mainWindow()
 {
     return d->mainWindow;
 }
 
 void KleopatraApplication::setMainWindow(MainWindow *mainWindow)
 {
     if (mainWindow == d->mainWindow) {
         return;
     }
 
     d->disconnectConfigureDialog();
 
     d->mainWindow = mainWindow;
 #ifndef QT_NO_SYSTEMTRAYICON
     if (d->sysTray) {
         d->sysTray->setMainWindow(mainWindow);
     }
 #endif
 
     d->connectConfigureDialog();
 }
 
 static void open_or_raise(QWidget *w)
 {
 #ifdef Q_OS_WIN
     if (w->isMinimized()) {
         qCDebug(KLEOPATRA_LOG) << __func__ << "unminimizing and raising window";
         w->raise();
     } else if (w->isVisible()) {
         qCDebug(KLEOPATRA_LOG) << __func__ << "raising window";
         w->raise();
 #else
     if (w->isVisible()) {
         qCDebug(KLEOPATRA_LOG) << __func__ << "activating window";
         KWindowSystem::updateStartupId(w->windowHandle());
         KWindowSystem::activateWindow(w->windowHandle());
 #endif
     } else {
         qCDebug(KLEOPATRA_LOG) << __func__ << "showing window";
         w->show();
     }
 }
 
 void KleopatraApplication::toggleMainWindowVisibility()
 {
     if (mainWindow()) {
         mainWindow()->setVisible(!mainWindow()->isVisible());
     } else {
         openOrRaiseMainWindow();
     }
 }
 
 void KleopatraApplication::restoreMainWindow()
 {
     qCDebug(KLEOPATRA_LOG) << "restoring main window";
 
     // Sanity checks
     if (!isSessionRestored()) {
         qCDebug(KLEOPATRA_LOG) << "Not in session restore";
         return;
     }
 
     if (mainWindow()) {
         qCDebug(KLEOPATRA_LOG) << "Already have main window";
         return;
     }
 
     auto mw = d->getOrCreateMainWindow();
     if (KMainWindow::canBeRestored(1)) {
         // restore to hidden state, Mainwindow::readProperties() will
         // restore saved visibility.
         mw->restore(1, false);
     }
 }
 
 void KleopatraApplication::openOrRaiseMainWindow()
 {
     auto mw = d->getOrCreateMainWindow();
     open_or_raise(mw);
     UpdateNotification::checkUpdate(mw);
 }
 
 void KleopatraApplication::openOrRaiseSmartCardWindow()
 {
     if (!d->smartCardWindow) {
         d->smartCardWindow = new SmartCardWindow;
         d->smartCardWindow->setAttribute(Qt::WA_DeleteOnClose);
     }
     open_or_raise(d->smartCardWindow);
 }
 
 void KleopatraApplication::openConfigDialogWithForeignParent(WId parentWId)
 {
     if (!d->configureDialog) {
         d->configureDialog = new ConfigureDialog;
         d->configureDialog->setAttribute(Qt::WA_DeleteOnClose);
         d->connectConfigureDialog();
     }
 
     // This is similar to what the commands do.
     if (parentWId) {
         if (QWidget *pw = QWidget::find(parentWId)) {
             d->configureDialog->setParent(pw, d->configureDialog->windowFlags());
         } else {
             d->configureDialog->setAttribute(Qt::WA_NativeWindow, true);
             KWindowSystem::setMainWindow(d->configureDialog->windowHandle(), parentWId);
         }
     }
 
     open_or_raise(d->configureDialog);
 
     // If we have a parent we want to raise over it.
     if (parentWId) {
         d->configureDialog->raise();
     }
 }
 
 void KleopatraApplication::openOrRaiseConfigDialog()
 {
     openConfigDialogWithForeignParent(0);
 }
 
 void KleopatraApplication::openOrRaiseGroupsConfigDialog(QWidget *parent)
 {
     if (!d->groupsConfigDialog) {
         d->groupsConfigDialog = new GroupsConfigDialog{parent};
         d->groupsConfigDialog->setAttribute(Qt::WA_DeleteOnClose);
     } else {
         // reparent the dialog to ensure it's shown on top of the (modal) parent
         d->groupsConfigDialog->setParent(parent, Qt::Dialog);
     }
     open_or_raise(d->groupsConfigDialog);
 }
 
 #ifndef QT_NO_SYSTEMTRAYICON
 void KleopatraApplication::startMonitoringSmartCard()
 {
     Q_ASSERT(d->readerStatus);
     d->readerStatus->startMonitoring();
 }
 #endif // QT_NO_SYSTEMTRAYICON
 
 void KleopatraApplication::importCertificatesFromFile(const QStringList &files, GpgME::Protocol /*proto*/)
 {
     openOrRaiseMainWindow();
     if (!files.empty()) {
         mainWindow()->importCertificatesFromFile(files);
     }
 }
 
 void KleopatraApplication::encryptFiles(const QStringList &files, GpgME::Protocol proto)
 {
     auto const cmd = new SignEncryptFilesCommand(files, nullptr);
     cmd->setEncryptionPolicy(Force);
     cmd->setSigningPolicy(Allow);
     if (proto != GpgME::UnknownProtocol) {
         cmd->setProtocol(proto);
     }
     cmd->start();
 }
 
 void KleopatraApplication::signFiles(const QStringList &files, GpgME::Protocol proto)
 {
     auto const cmd = new SignEncryptFilesCommand(files, nullptr);
     cmd->setSigningPolicy(Force);
     cmd->setEncryptionPolicy(Deny);
     if (proto != GpgME::UnknownProtocol) {
         cmd->setProtocol(proto);
     }
     cmd->start();
 }
 
 void KleopatraApplication::signEncryptFiles(const QStringList &files, GpgME::Protocol proto)
 {
     auto const cmd = new SignEncryptFilesCommand(files, nullptr);
     if (proto != GpgME::UnknownProtocol) {
         cmd->setProtocol(proto);
     }
     cmd->start();
 }
 
 void KleopatraApplication::decryptFiles(const QStringList &files, GpgME::Protocol /*proto*/)
 {
     auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
     cmd->setOperation(Decrypt);
     cmd->start();
 }
 
 void KleopatraApplication::verifyFiles(const QStringList &files, GpgME::Protocol /*proto*/)
 {
     auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
     cmd->setOperation(Verify);
     cmd->start();
 }
 
 void KleopatraApplication::decryptVerifyFiles(const QStringList &files, GpgME::Protocol /*proto*/)
 {
     auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
     cmd->start();
 }
 
 void KleopatraApplication::checksumFiles(const QStringList &files, GpgME::Protocol /*proto*/)
 {
     QStringList verifyFiles, createFiles;
 
     for (const QString &file : files) {
         if (isChecksumFile(file)) {
             verifyFiles << file;
         } else {
             createFiles << file;
         }
     }
 
     if (!verifyFiles.isEmpty()) {
         auto const cmd = new ChecksumVerifyFilesCommand(verifyFiles, nullptr);
         cmd->start();
     }
     if (!createFiles.isEmpty()) {
         auto const cmd = new ChecksumCreateFilesCommand(createFiles, nullptr);
         cmd->start();
     }
 }
 
 void KleopatraApplication::setIgnoreNewInstance(bool ignore)
 {
     d->ignoreNewInstance = ignore;
 }
 
 bool KleopatraApplication::ignoreNewInstance() const
 {
     return d->ignoreNewInstance;
 }
 
 void KleopatraApplication::blockUrl(const QUrl &url)
 {
     qCDebug(KLEOPATRA_LOG) << "Blocking URL" << url;
     KMessageBox::error(mainWindow(), i18n("Opening an external link is administratively prohibited."), i18nc("@title:window", "Prohibited"));
 }
 
 void KleopatraApplication::startGpgAgent()
 {
     Kleo::launchGpgAgent();
 }
 
 void KleopatraApplication::setDistributionSettings(const std::shared_ptr<QSettings> &settings)
 {
     d->distroSettings = settings;
 }
 
 std::shared_ptr<QSettings> KleopatraApplication::distributionSettings() const
 {
     return d->distroSettings;
 }
 
 #include "kleopatraapplication.moc"
 
 #include "moc_kleopatraapplication.cpp"
diff --git a/src/newcertificatewizard/enterdetailspage.cpp b/src/newcertificatewizard/enterdetailspage.cpp
index 023386b5d..8e3ef2c55 100644
--- a/src/newcertificatewizard/enterdetailspage.cpp
+++ b/src/newcertificatewizard/enterdetailspage.cpp
@@ -1,531 +1,531 @@
 /*
     This file is part of Kleopatra, the KDE keymanager
     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
     SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik
     SPDX-FileContributor: Intevation GmbH
     SPDX-FileCopyrightText: 2022 g10 Code GmbH
     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
 #include <config-kleopatra.h>
 
 #include "enterdetailspage_p.h"
 
 #include "advancedsettingsdialog_p.h"
 
 #include "utils/userinfo.h"
 
 #include <settings.h>
 
 #include <Libkleo/Compat>
-#include <Libkleo/Dn>
+#include <Libkleo/DnAttributes>
 #include <Libkleo/Formatting>
 #include <Libkleo/OidMap>
 #include <Libkleo/Stl_Util>
 #include <Libkleo/Validation>
 
 #include <KAdjustingScrollArea>
 #include <KLocalizedString>
 
 #include <QGpgME/CryptoConfig>
 #include <QGpgME/DN>
 #include <QGpgME/Protocol>
 
 #include <QCheckBox>
 #include <QHBoxLayout>
 #include <QLabel>
 #include <QLineEdit>
 #include <QMetaProperty>
 #include <QPushButton>
 #include <QSpacerItem>
 #include <QVBoxLayout>
 #include <QValidator>
 
 #include "kleopatra_debug.h"
 
 using namespace Kleo;
 using namespace Kleo::NewCertificateUi;
 using namespace GpgME;
 
 static void set_tab_order(const QList<QWidget *> &wl)
 {
     kdtools::for_each_adjacent_pair(wl.begin(), wl.end(), [](QWidget *w1, QWidget *w2) {
         QWidget::setTabOrder(w1, w2);
     });
 }
 
 static QString pgpLabel(const QString &attr)
 {
     if (attr == QLatin1StringView("NAME")) {
         return i18n("Name");
     }
     if (attr == QLatin1StringView("EMAIL")) {
         return i18n("EMail");
     }
     return QString();
 }
 
 static QString attributeLabel(const QString &attr, bool pgp)
 {
     if (attr.isEmpty()) {
         return QString();
     }
-    const QString label = pgp ? pgpLabel(attr) : Kleo::DN::attributeNameToLabel(attr);
+    const QString label = pgp ? pgpLabel(attr) : Kleo::DNAttributes::nameToLabel(attr);
     if (!label.isEmpty())
         if (pgp) {
             return label;
         } else
             return i18nc("Format string for the labels in the \"Your Personal Data\" page", "%1 (%2)", label, attr);
     else {
         return attr;
     }
 }
 
 static QString attributeFromKey(QString key)
 {
     return key.remove(QLatin1Char('!'));
 }
 
 struct EnterDetailsPage::UI {
     QGridLayout *gridLayout = nullptr;
     QLabel *nameLB = nullptr;
     QLineEdit *nameLE = nullptr;
     QLabel *nameRequiredLB = nullptr;
     QLabel *emailLB = nullptr;
     QLineEdit *emailLE = nullptr;
     QLabel *emailRequiredLB = nullptr;
     QCheckBox *withPassCB = nullptr;
     QLineEdit *resultLE = nullptr;
     QLabel *errorLB = nullptr;
     QPushButton *advancedPB = nullptr;
 
     UI(QWizardPage *parent)
     {
         parent->setTitle(i18nc("@title", "Enter Details"));
 
         auto mainLayout = new QVBoxLayout{parent};
         const auto margins = mainLayout->contentsMargins();
         mainLayout->setContentsMargins(margins.left(), 0, margins.right(), 0);
 
         auto scrollArea = new KAdjustingScrollArea{parent};
         scrollArea->setFocusPolicy(Qt::NoFocus);
         scrollArea->setFrameStyle(QFrame::NoFrame);
         scrollArea->setBackgroundRole(parent->backgroundRole());
         scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
         scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents);
         auto widget = new QWidget;
         scrollArea->setWidget(widget);
         auto scrollAreaLayout = new QVBoxLayout(widget);
         scrollAreaLayout->setContentsMargins(0, margins.top(), 0, margins.bottom());
 
         gridLayout = new QGridLayout;
         int row = 0;
 
         nameLB = new QLabel{i18n("Real name:"), parent};
         nameLE = new QLineEdit{parent};
         nameRequiredLB = new QLabel{i18n("(required)"), parent};
         gridLayout->addWidget(nameLB, row, 0, 1, 1);
         gridLayout->addWidget(nameLE, row, 1, 1, 1);
         gridLayout->addWidget(nameRequiredLB, row, 2, 1, 1);
 
         row++;
         emailLB = new QLabel{i18n("EMail address:"), parent};
         emailLE = new QLineEdit{parent};
         emailRequiredLB = new QLabel{i18n("(required)"), parent};
 
         gridLayout->addWidget(emailLB, row, 0, 1, 1);
         gridLayout->addWidget(emailLE, row, 1, 1, 1);
         gridLayout->addWidget(emailRequiredLB, row, 2, 1, 1);
 
         row++;
         withPassCB = new QCheckBox{i18n("Protect the generated key with a passphrase."), parent};
         withPassCB->setToolTip(
             i18nc("@info:tooltip", "Encrypts the secret key with an unrecoverable passphrase. You will be asked for the passphrase during key generation."));
         gridLayout->addWidget(withPassCB, row, 1, 1, 2);
 
         scrollAreaLayout->addLayout(gridLayout);
 
         auto verticalSpacer = new QSpacerItem{20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding};
 
         scrollAreaLayout->addItem(verticalSpacer);
 
         resultLE = new QLineEdit{parent};
         resultLE->setFrame(false);
         resultLE->setAlignment(Qt::AlignCenter);
         resultLE->setReadOnly(true);
 
         scrollAreaLayout->addWidget(resultLE);
 
         auto horizontalLayout = new QHBoxLayout;
         errorLB = new QLabel{parent};
         QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
         sizePolicy.setHorizontalStretch(0);
         sizePolicy.setVerticalStretch(0);
         sizePolicy.setHeightForWidth(errorLB->sizePolicy().hasHeightForWidth());
         errorLB->setSizePolicy(sizePolicy);
         QPalette palette;
         QBrush brush(QColor(255, 0, 0, 255));
         brush.setStyle(Qt::SolidPattern);
         palette.setBrush(QPalette::Active, QPalette::WindowText, brush);
         palette.setBrush(QPalette::Inactive, QPalette::WindowText, brush);
         QBrush brush1(QColor(114, 114, 114, 255));
         brush1.setStyle(Qt::SolidPattern);
         palette.setBrush(QPalette::Disabled, QPalette::WindowText, brush1);
         errorLB->setPalette(palette);
         errorLB->setTextFormat(Qt::RichText);
 
         horizontalLayout->addWidget(errorLB);
 
         advancedPB = new QPushButton{i18n("Advanced Settings..."), parent};
         advancedPB->setAutoDefault(false);
 
         horizontalLayout->addWidget(advancedPB);
 
         scrollAreaLayout->addLayout(horizontalLayout);
 
         mainLayout->addWidget(scrollArea);
     }
 };
 
 EnterDetailsPage::EnterDetailsPage(QWidget *p)
     : WizardPage{p}
     , ui{new UI{this}}
     , dialog{new AdvancedSettingsDialog{this}}
 {
     setObjectName(QLatin1StringView("Kleo__NewCertificateUi__EnterDetailsPage"));
 
     Settings settings;
     if (settings.hideAdvanced()) {
         setSubTitle(i18n("Please enter your personal details below."));
     } else {
         setSubTitle(i18n("Please enter your personal details below. If you want more control over the parameters, click on the Advanced Settings button."));
     }
     ui->advancedPB->setVisible(!settings.hideAdvanced());
     ui->resultLE->setFocusPolicy(Qt::NoFocus);
 
     // set errorLB to have a fixed height of two lines:
     ui->errorLB->setText(QStringLiteral("2<br>1"));
     ui->errorLB->setFixedHeight(ui->errorLB->minimumSizeHint().height());
     ui->errorLB->clear();
 
     connect(ui->advancedPB, &QPushButton::clicked, this, &EnterDetailsPage::slotAdvancedSettingsClicked);
     connect(ui->resultLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged);
     // The email doesn't necessarily show up in ui->resultLE:
     connect(ui->emailLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged);
     registerDialogPropertiesAsFields();
     registerField(QStringLiteral("dn"), ui->resultLE);
     registerField(QStringLiteral("name"), ui->nameLE);
     registerField(QStringLiteral("email"), ui->emailLE);
     registerField(QStringLiteral("protectedKey"), ui->withPassCB);
     setCommitPage(true);
     setButtonText(QWizard::CommitButton, i18nc("@action", "Create"));
 
     const auto conf = QGpgME::cryptoConfig();
     if (!conf) {
         qCWarning(KLEOPATRA_LOG) << "Failed to obtain cryptoConfig.";
         return;
     }
     const auto entry = getCryptoConfigEntry(conf, "gpg-agent", "enforce-passphrase-constraints");
     if (entry && entry->boolValue()) {
         qCDebug(KLEOPATRA_LOG) << "Disabling passphrace cb because of agent config.";
         ui->withPassCB->setEnabled(false);
         ui->withPassCB->setChecked(true);
     } else {
         const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard"));
         ui->withPassCB->setChecked(config.readEntry("WithPassphrase", false));
         ui->withPassCB->setEnabled(!config.isEntryImmutable("WithPassphrase"));
     }
 }
 
 EnterDetailsPage::~EnterDetailsPage() = default;
 
 void EnterDetailsPage::initializePage()
 {
     updateForm();
     ui->withPassCB->setVisible(false);
 }
 
 void EnterDetailsPage::cleanupPage()
 {
     saveValues();
 }
 
 void EnterDetailsPage::registerDialogPropertiesAsFields()
 {
     const QMetaObject *const mo = dialog->metaObject();
     for (unsigned int i = mo->propertyOffset(), end = i + mo->propertyCount(); i != end; ++i) {
         const QMetaProperty mp = mo->property(i);
         if (mp.isValid()) {
             registerField(QLatin1StringView(mp.name()), dialog, mp.name(), SIGNAL(accepted()));
         }
     }
 }
 
 void EnterDetailsPage::saveValues()
 {
     for (const Line &line : std::as_const(lineList)) {
         savedValues[attributeFromKey(line.attr)] = line.edit->text().trimmed();
     }
 }
 
 void EnterDetailsPage::clearForm()
 {
     qDeleteAll(dynamicWidgets);
     dynamicWidgets.clear();
     lineList.clear();
 
     ui->nameLE->hide();
     ui->nameLE->clear();
     ui->nameLB->hide();
     ui->nameRequiredLB->hide();
 
     ui->emailLE->hide();
     ui->emailLE->clear();
     ui->emailLB->hide();
     ui->emailRequiredLB->hide();
 }
 
 static int row_index_of(QWidget *w, QGridLayout *l)
 {
     const int idx = l->indexOf(w);
     int r, c, rs, cs;
     l->getItemPosition(idx, &r, &c, &rs, &cs);
     return r;
 }
 
 static QLineEdit *
 adjust_row(QGridLayout *l, int row, const QString &label, const QString &preset, const std::shared_ptr<QValidator> &validator, bool readonly, bool required)
 {
     Q_ASSERT(l);
     Q_ASSERT(row >= 0);
     Q_ASSERT(row < l->rowCount());
 
     auto lb = qobject_cast<QLabel *>(l->itemAtPosition(row, 0)->widget());
     Q_ASSERT(lb);
     auto le = qobject_cast<QLineEdit *>(l->itemAtPosition(row, 1)->widget());
     Q_ASSERT(le);
     lb->setBuddy(le); // For better accessibility
     auto reqLB = qobject_cast<QLabel *>(l->itemAtPosition(row, 2)->widget());
     Q_ASSERT(reqLB);
 
     lb->setText(i18nc("interpunctation for labels", "%1:", label));
     le->setText(preset);
     reqLB->setText(required ? i18n("(required)") : i18n("(optional)"));
     if (validator) {
         le->setValidator(validator.get());
     }
 
     le->setReadOnly(readonly && le->hasAcceptableInput());
 
     lb->show();
     le->show();
     reqLB->show();
 
     return le;
 }
 
 static int add_row(QGridLayout *l, QList<QWidget *> *wl)
 {
     Q_ASSERT(l);
     Q_ASSERT(wl);
     const int row = l->rowCount();
     QWidget *w1, *w2, *w3;
     l->addWidget(w1 = new QLabel(l->parentWidget()), row, 0);
     l->addWidget(w2 = new QLineEdit(l->parentWidget()), row, 1);
     l->addWidget(w3 = new QLabel(l->parentWidget()), row, 2);
     wl->push_back(w1);
     wl->push_back(w2);
     wl->push_back(w3);
     return row;
 }
 
 void EnterDetailsPage::updateForm()
 {
     clearForm();
 
     const auto settings = Kleo::Settings{};
     const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard"));
 
     QStringList attrOrder = config.readEntry("DNAttributeOrder", QStringList());
     if (attrOrder.empty()) {
         attrOrder << QStringLiteral("CN!") << QStringLiteral("L") << QStringLiteral("OU") << QStringLiteral("O") << QStringLiteral("C")
                   << QStringLiteral("EMAIL!");
     }
 
     QList<QWidget *> widgets;
     widgets.push_back(ui->nameLE);
     widgets.push_back(ui->emailLE);
 
     QMap<int, Line> lines;
 
     for (const QString &rawKey : std::as_const(attrOrder)) {
         const QString key = rawKey.trimmed().toUpper();
         const QString attr = attributeFromKey(key);
         if (attr.isEmpty()) {
             continue;
         }
         const QString preset = savedValues.value(attr, config.readEntry(attr, QString()));
         const bool required = key.endsWith(QLatin1Char('!'));
         const bool readonly = config.isEntryImmutable(attr);
         const QString label = config.readEntry(attr + QLatin1StringView("_label"), attributeLabel(attr, false));
         const QString regex = config.readEntry(attr + QLatin1StringView("_regex"));
         const QString placeholder = config.readEntry(attr + QLatin1StringView{"_placeholder"});
 
         int row;
         bool known = true;
         std::shared_ptr<QValidator> validator;
         if (attr == QLatin1StringView("EMAIL")) {
             row = row_index_of(ui->emailLE, ui->gridLayout);
             validator = regex.isEmpty() ? Validation::email() : Validation::email(regex);
         } else if (attr == QLatin1StringView("NAME") || attr == QLatin1StringView("CN")) {
             if (attr == QLatin1StringView("NAME")) {
                 continue;
             }
             row = row_index_of(ui->nameLE, ui->gridLayout);
         } else {
             known = false;
             row = add_row(ui->gridLayout, &dynamicWidgets);
         }
         if (!validator && !regex.isEmpty()) {
             validator = std::make_shared<QRegularExpressionValidator>(QRegularExpression{regex});
         }
 
         QLineEdit *le = adjust_row(ui->gridLayout, row, label, preset, validator, readonly, required);
         le->setPlaceholderText(placeholder);
 
         const Line line = {key, label, regex, le, validator};
         lines[row] = line;
 
         if (!known) {
             widgets.push_back(le);
         }
 
         // don't connect twice:
         disconnect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel);
         connect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel);
     }
 
     // create lineList in visual order, so requirementsAreMet()
     // complains from top to bottom:
     lineList.reserve(lines.count());
     std::copy(lines.cbegin(), lines.cend(), std::back_inserter(lineList));
 
     widgets.push_back(ui->withPassCB);
     widgets.push_back(ui->advancedPB);
 
     if (ui->nameLE->text().isEmpty() && settings.prefillCN()) {
         ui->nameLE->setText(userFullName());
     }
     if (ui->emailLE->text().isEmpty() && settings.prefillEmail()) {
         ui->emailLE->setText(userEmailAddress());
     }
 
     slotUpdateResultLabel();
 
     set_tab_order(widgets);
 }
 
 QString EnterDetailsPage::cmsDN() const
 {
     QGpgME::DN dn;
     for (QList<Line>::const_iterator it = lineList.begin(), end = lineList.end(); it != end; ++it) {
         const QString text = it->edit->text().trimmed();
         if (text.isEmpty()) {
             continue;
         }
         QString attr = attributeFromKey(it->attr);
         if (attr == QLatin1StringView("EMAIL")) {
             continue;
         }
         if (const char *const oid = oidForAttributeName(attr)) {
             attr = QString::fromUtf8(oid);
         }
         dn.append(QGpgME::DN::Attribute(attr, text));
     }
     return dn.dn();
 }
 
 QString EnterDetailsPage::pgpUserID() const
 {
     return Formatting::prettyNameAndEMail(OpenPGP, QString(), ui->nameLE->text().trimmed(), ui->emailLE->text().trimmed(), QString());
 }
 
 static bool has_intermediate_input(const QLineEdit *le)
 {
     QString text = le->text();
     int pos = le->cursorPosition();
     const QValidator *const v = le->validator();
     return v && v->validate(text, pos) == QValidator::Intermediate;
 }
 
 static bool requirementsAreMet(const QList<EnterDetailsPage::Line> &list, QString &error)
 {
     bool allEmpty = true;
     for (const auto &line : list) {
         const QLineEdit *le = line.edit;
         if (!le) {
             continue;
         }
         const QString key = line.attr;
         qCDebug(KLEOPATRA_LOG) << "requirementsAreMet(): checking" << key << "against" << le->text() << ":";
         if (le->text().trimmed().isEmpty()) {
             if (key.endsWith(QLatin1Char('!'))) {
                 if (line.regex.isEmpty()) {
                     error = xi18nc("@info", "<interface>%1</interface> is required, but empty.", line.label);
                 } else
                     error = xi18nc("@info",
                                    "<interface>%1</interface> is required, but empty.<nl/>"
                                    "Local Admin rule: <icode>%2</icode>",
                                    line.label,
                                    line.regex);
                 return false;
             }
         } else if (has_intermediate_input(le)) {
             if (line.regex.isEmpty()) {
                 error = xi18nc("@info", "<interface>%1</interface> is incomplete.", line.label);
             } else
                 error = xi18nc("@info",
                                "<interface>%1</interface> is incomplete.<nl/>"
                                "Local Admin rule: <icode>%2</icode>",
                                line.label,
                                line.regex);
             return false;
         } else if (!le->hasAcceptableInput()) {
             if (line.regex.isEmpty()) {
                 error = xi18nc("@info", "<interface>%1</interface> is invalid.", line.label);
             } else
                 error = xi18nc("@info",
                                "<interface>%1</interface> is invalid.<nl/>"
                                "Local Admin rule: <icode>%2</icode>",
                                line.label,
                                line.regex);
             return false;
         } else {
             allEmpty = false;
         }
     }
     // Ensure that at least one value is acceptable
     return !allEmpty;
 }
 
 bool EnterDetailsPage::isComplete() const
 {
     QString error;
     const bool ok = requirementsAreMet(lineList, error);
     ui->errorLB->setText(error);
     return ok;
 }
 
 void EnterDetailsPage::slotAdvancedSettingsClicked()
 {
     dialog->exec();
 }
 
 void EnterDetailsPage::slotUpdateResultLabel()
 {
     ui->resultLE->setText(cmsDN());
 }
 
 #include "moc_enterdetailspage_p.cpp"