File indexing completed on 2024-06-23 05:14:02

0001 /*
0002     dialogs/certificatedetailswidget.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
0006     SPDX-FileCopyrightText: 2017 Intevation GmbH
0007     SPDX-FileCopyrightText: 2022 g10 Code GmbH
0008     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0009     SPDX-FileCopyrightText: 2022 Felix Tiede
0010 
0011     SPDX-License-Identifier: GPL-2.0-or-later
0012 */
0013 
0014 #include <config-kleopatra.h>
0015 
0016 #include "certificatedetailswidget.h"
0017 
0018 #include "exportdialog.h"
0019 #include "kleopatra_debug.h"
0020 #include "subkeyswidget.h"
0021 #include "trustchainwidget.h"
0022 #include "weboftrustdialog.h"
0023 
0024 #include "commands/certifycertificatecommand.h"
0025 #include "commands/changeexpirycommand.h"
0026 #include "commands/changepassphrasecommand.h"
0027 #ifdef MAILAKONADI_ENABLED
0028 #include "commands/exportopenpgpcerttoprovidercommand.h"
0029 #endif // MAILAKONADI_ENABLED
0030 #include "commands/adduseridcommand.h"
0031 #include "commands/detailscommand.h"
0032 #include "commands/dumpcertificatecommand.h"
0033 #include "commands/genrevokecommand.h"
0034 #include "commands/refreshcertificatecommand.h"
0035 #include "commands/revokecertificationcommand.h"
0036 #include "commands/revokeuseridcommand.h"
0037 #include "commands/setprimaryuseridcommand.h"
0038 #include "utils/accessibility.h"
0039 #include "utils/tags.h"
0040 #include "view/infofield.h"
0041 
0042 #include <Libkleo/Algorithm>
0043 #include <Libkleo/Compliance>
0044 #include <Libkleo/Dn>
0045 #include <Libkleo/Formatting>
0046 #include <Libkleo/GnuPG>
0047 #include <Libkleo/KeyCache>
0048 #include <Libkleo/KeyHelpers>
0049 #include <Libkleo/TreeWidget>
0050 
0051 #include <KLocalizedString>
0052 #include <KMessageBox>
0053 #include <KSeparator>
0054 
0055 #include <gpgme++/context.h>
0056 #include <gpgme++/key.h>
0057 #include <gpgme++/keylistresult.h>
0058 #include <gpgme++/tofuinfo.h>
0059 
0060 #include <QGpgME/Debug>
0061 #include <QGpgME/KeyListJob>
0062 #include <QGpgME/Protocol>
0063 
0064 #include <QClipboard>
0065 #include <QDateTime>
0066 #include <QGridLayout>
0067 #include <QGuiApplication>
0068 #include <QHBoxLayout>
0069 #include <QLabel>
0070 #include <QListWidget>
0071 #include <QLocale>
0072 #include <QMenu>
0073 #include <QPushButton>
0074 #include <QStringBuilder>
0075 #include <QTreeWidget>
0076 #include <QVBoxLayout>
0077 
0078 #include <map>
0079 #if __has_include(<ranges>)
0080 #include <ranges>
0081 #if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
0082 #define USE_RANGES
0083 #endif
0084 #endif
0085 #include <set>
0086 
0087 Q_DECLARE_METATYPE(GpgME::UserID)
0088 
0089 using namespace Kleo;
0090 
0091 namespace
0092 {
0093 std::vector<GpgME::UserID> selectedUserIDs(const QTreeWidget *treeWidget)
0094 {
0095     if (!treeWidget) {
0096         return {};
0097     }
0098 
0099     std::vector<GpgME::UserID> userIDs;
0100     const auto selected = treeWidget->selectedItems();
0101     std::transform(selected.begin(), selected.end(), std::back_inserter(userIDs), [](const QTreeWidgetItem *item) {
0102         return item->data(0, Qt::UserRole).value<GpgME::UserID>();
0103     });
0104     return userIDs;
0105 }
0106 }
0107 
0108 class CertificateDetailsWidget::Private
0109 {
0110 public:
0111     Private(CertificateDetailsWidget *qq);
0112 
0113     void setupCommonProperties();
0114     void updateUserIDActions();
0115     void setUpUserIDTable();
0116     void setUpSMIMEAdressList();
0117     void setupPGPProperties();
0118     void setupSMIMEProperties();
0119 
0120     void revokeUserID(const GpgME::UserID &uid);
0121     void revokeSelectedUserID();
0122     void genRevokeCert();
0123     void refreshCertificate();
0124     void certifyUserIDs();
0125     void revokeCertifications();
0126     void webOfTrustClicked();
0127     void exportClicked();
0128     void addUserID();
0129     void setPrimaryUserID(const GpgME::UserID &uid = {});
0130     void changePassphrase();
0131     void changeExpiration();
0132     void keysMayHaveChanged();
0133     void showTrustChainDialog();
0134     void showMoreDetails();
0135     void userIDTableContextMenuRequested(const QPoint &p);
0136 
0137     QString tofuTooltipString(const GpgME::UserID &uid) const;
0138     QIcon trustLevelIcon(const GpgME::UserID &uid) const;
0139     QString trustLevelText(const GpgME::UserID &uid) const;
0140 
0141     void showIssuerCertificate();
0142 
0143     void updateKey();
0144     void setUpdatedKey(const GpgME::Key &key);
0145     void keyListDone(const GpgME::KeyListResult &, const std::vector<GpgME::Key> &, const QString &, const GpgME::Error &);
0146     void copyFingerprintToClipboard();
0147 
0148 private:
0149     CertificateDetailsWidget *const q;
0150 
0151 public:
0152     GpgME::Key key;
0153     bool updateInProgress = false;
0154 
0155 private:
0156     InfoField *attributeField(const QString &attributeName)
0157     {
0158         const auto keyValuePairIt = ui.smimeAttributeFields.find(attributeName);
0159         if (keyValuePairIt != ui.smimeAttributeFields.end()) {
0160             return (*keyValuePairIt).second.get();
0161         }
0162         return nullptr;
0163     }
0164 
0165 private:
0166     struct UI {
0167         QWidget *userIDs = nullptr;
0168         QLabel *userIDTableLabel = nullptr;
0169         TreeWidget *userIDTable = nullptr;
0170         QPushButton *addUserIDBtn = nullptr;
0171         QPushButton *setPrimaryUserIDBtn = nullptr;
0172         QPushButton *certifyBtn = nullptr;
0173         QPushButton *revokeCertificationsBtn = nullptr;
0174         QPushButton *revokeUserIDBtn = nullptr;
0175         QPushButton *webOfTrustBtn = nullptr;
0176 
0177         std::map<QString, std::unique_ptr<InfoField>> smimeAttributeFields;
0178         std::unique_ptr<InfoField> smimeTrustLevelField;
0179         std::unique_ptr<InfoField> validFromField;
0180         std::unique_ptr<InfoField> expiresField;
0181         QAction *changeExpirationAction = nullptr;
0182         std::unique_ptr<InfoField> fingerprintField;
0183         QAction *copyFingerprintAction = nullptr;
0184         std::unique_ptr<InfoField> smimeIssuerField;
0185         QAction *showIssuerCertificateAction = nullptr;
0186         std::unique_ptr<InfoField> complianceField;
0187         std::unique_ptr<InfoField> trustedIntroducerField;
0188 
0189         QLabel *smimeRelatedAddresses = nullptr;
0190         QListWidget *smimeAddressList = nullptr;
0191 
0192         QPushButton *moreDetailsBtn = nullptr;
0193         QPushButton *trustChainDetailsBtn = nullptr;
0194         QPushButton *refreshBtn = nullptr;
0195         QPushButton *changePassphraseBtn = nullptr;
0196         QPushButton *exportBtn = nullptr;
0197         QPushButton *genRevokeBtn = nullptr;
0198 
0199         void setupUi(QWidget *parent)
0200         {
0201             auto mainLayout = new QVBoxLayout{parent};
0202 
0203             userIDs = new QWidget{parent};
0204             {
0205                 auto userIDsLayout = new QVBoxLayout{userIDs};
0206                 userIDsLayout->setContentsMargins({});
0207 
0208                 userIDTableLabel = new QLabel(i18n("User IDs:"), parent);
0209                 userIDsLayout->addWidget(userIDTableLabel);
0210 
0211                 userIDTable = new TreeWidget{parent};
0212                 userIDTableLabel->setBuddy(userIDTable);
0213                 userIDTable->setAccessibleName(i18n("User IDs"));
0214                 QTreeWidgetItem *__qtreewidgetitem = new QTreeWidgetItem();
0215                 __qtreewidgetitem->setText(0, QString::fromUtf8("1"));
0216                 userIDTable->setHeaderItem(__qtreewidgetitem);
0217                 userIDTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
0218                 userIDTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
0219                 userIDTable->setRootIsDecorated(false);
0220                 userIDTable->setUniformRowHeights(true);
0221                 userIDTable->setAllColumnsShowFocus(false);
0222 
0223                 userIDsLayout->addWidget(userIDTable);
0224 
0225                 {
0226                     auto buttonRow = new QHBoxLayout;
0227 
0228                     addUserIDBtn = new QPushButton(i18nc("@action:button", "Add User ID"), parent);
0229                     buttonRow->addWidget(addUserIDBtn);
0230 
0231                     setPrimaryUserIDBtn = new QPushButton{i18nc("@action:button", "Flag as Primary"), parent};
0232                     setPrimaryUserIDBtn->setToolTip(i18nc("@info:tooltip", "Flag the selected user ID as the primary user ID of this key."));
0233                     buttonRow->addWidget(setPrimaryUserIDBtn);
0234 
0235                     certifyBtn = new QPushButton(i18nc("@action:button", "Certify User IDs"), parent);
0236                     buttonRow->addWidget(certifyBtn);
0237 
0238                     webOfTrustBtn = new QPushButton(i18nc("@action:button", "Show Certifications"), parent);
0239                     buttonRow->addWidget(webOfTrustBtn);
0240 
0241                     revokeCertificationsBtn = new QPushButton(i18nc("@action:button", "Revoke Certifications"), parent);
0242                     buttonRow->addWidget(revokeCertificationsBtn);
0243 
0244                     revokeUserIDBtn = new QPushButton(i18nc("@action:button", "Revoke User ID"), parent);
0245                     buttonRow->addWidget(revokeUserIDBtn);
0246 
0247                     buttonRow->addStretch(1);
0248 
0249                     userIDsLayout->addLayout(buttonRow);
0250                 }
0251 
0252                 userIDsLayout->addWidget(new KSeparator{Qt::Horizontal, parent});
0253             }
0254 
0255             mainLayout->addWidget(userIDs);
0256 
0257             {
0258                 auto gridLayout = new QGridLayout;
0259                 gridLayout->setColumnStretch(1, 1);
0260 
0261                 int row = -1;
0262                 for (const auto &attribute : DN::attributeOrder()) {
0263                     const auto attributeLabel = DN::attributeNameToLabel(attribute);
0264                     if (attributeLabel.isEmpty()) {
0265                         continue;
0266                     }
0267                     const auto labelWithColon = i18nc("interpunctation for labels", "%1:", attributeLabel);
0268                     const auto &[it, inserted] = smimeAttributeFields.try_emplace(attribute, std::make_unique<InfoField>(labelWithColon, parent));
0269                     if (inserted) {
0270                         row++;
0271                         const auto &field = it->second;
0272                         gridLayout->addWidget(field->label(), row, 0);
0273                         gridLayout->addLayout(field->layout(), row, 1);
0274                     }
0275                 }
0276 
0277                 row++;
0278                 smimeTrustLevelField = std::make_unique<InfoField>(i18n("Trust level:"), parent);
0279                 gridLayout->addWidget(smimeTrustLevelField->label(), row, 0);
0280                 gridLayout->addLayout(smimeTrustLevelField->layout(), row, 1);
0281 
0282                 row++;
0283                 validFromField = std::make_unique<InfoField>(i18n("Valid from:"), parent);
0284                 gridLayout->addWidget(validFromField->label(), row, 0);
0285                 gridLayout->addLayout(validFromField->layout(), row, 1);
0286 
0287                 row++;
0288                 expiresField = std::make_unique<InfoField>(i18n("Valid until:"), parent);
0289                 changeExpirationAction = new QAction{parent};
0290                 changeExpirationAction->setIcon(QIcon::fromTheme(QStringLiteral("editor")));
0291                 changeExpirationAction->setToolTip(i18nc("@info:tooltip", "Change the end of the validity period"));
0292                 Kleo::setAccessibleName(changeExpirationAction, i18nc("@action:button", "Change Validity"));
0293                 expiresField->setAction(changeExpirationAction);
0294                 gridLayout->addWidget(expiresField->label(), row, 0);
0295                 gridLayout->addLayout(expiresField->layout(), row, 1);
0296 
0297                 row++;
0298                 fingerprintField = std::make_unique<InfoField>(i18n("Fingerprint:"), parent);
0299                 if (QGuiApplication::clipboard()) {
0300                     copyFingerprintAction = new QAction{parent};
0301                     copyFingerprintAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
0302                     copyFingerprintAction->setToolTip(i18nc("@info:tooltip", "Copy the fingerprint to the clipboard"));
0303                     Kleo::setAccessibleName(copyFingerprintAction, i18nc("@action:button", "Copy fingerprint"));
0304                     fingerprintField->setAction(copyFingerprintAction);
0305                 }
0306                 gridLayout->addWidget(fingerprintField->label(), row, 0);
0307                 gridLayout->addLayout(fingerprintField->layout(), row, 1);
0308 
0309                 row++;
0310                 smimeIssuerField = std::make_unique<InfoField>(i18n("Issuer:"), parent);
0311                 showIssuerCertificateAction = new QAction{parent};
0312                 showIssuerCertificateAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information")));
0313                 showIssuerCertificateAction->setToolTip(i18nc("@info:tooltip", "Show the issuer certificate"));
0314                 Kleo::setAccessibleName(showIssuerCertificateAction, i18nc("@action:button", "Show certificate"));
0315                 smimeIssuerField->setAction(showIssuerCertificateAction);
0316                 gridLayout->addWidget(smimeIssuerField->label(), row, 0);
0317                 gridLayout->addLayout(smimeIssuerField->layout(), row, 1);
0318 
0319                 row++;
0320                 complianceField = std::make_unique<InfoField>(i18n("Compliance:"), parent);
0321                 gridLayout->addWidget(complianceField->label(), row, 0);
0322                 gridLayout->addLayout(complianceField->layout(), row, 1);
0323 
0324                 row++;
0325                 trustedIntroducerField = std::make_unique<InfoField>(i18n("Trusted introducer for:"), parent);
0326                 gridLayout->addWidget(trustedIntroducerField->label(), row, 0);
0327                 trustedIntroducerField->setToolTip(i18n("See certifications for details."));
0328                 gridLayout->addLayout(trustedIntroducerField->layout(), row, 1);
0329 
0330                 mainLayout->addLayout(gridLayout);
0331             }
0332 
0333             smimeRelatedAddresses = new QLabel(i18n("Related addresses:"), parent);
0334             mainLayout->addWidget(smimeRelatedAddresses);
0335 
0336             smimeAddressList = new QListWidget{parent};
0337             smimeRelatedAddresses->setBuddy(smimeAddressList);
0338             smimeAddressList->setAccessibleName(i18n("Related addresses"));
0339             smimeAddressList->setEditTriggers(QAbstractItemView::NoEditTriggers);
0340             smimeAddressList->setSelectionMode(QAbstractItemView::SingleSelection);
0341 
0342             mainLayout->addWidget(smimeAddressList);
0343 
0344             mainLayout->addStretch();
0345 
0346             {
0347                 auto buttonRow = new QHBoxLayout;
0348 
0349                 moreDetailsBtn = new QPushButton(i18nc("@action:button", "More Details..."), parent);
0350                 buttonRow->addWidget(moreDetailsBtn);
0351 
0352                 trustChainDetailsBtn = new QPushButton(i18nc("@action:button", "Trust Chain Details"), parent);
0353                 buttonRow->addWidget(trustChainDetailsBtn);
0354 
0355                 refreshBtn = new QPushButton{i18nc("@action:button", "Update"), parent};
0356                 buttonRow->addWidget(refreshBtn);
0357 
0358                 exportBtn = new QPushButton(i18nc("@action:button", "Export"), parent);
0359                 buttonRow->addWidget(exportBtn);
0360 
0361                 changePassphraseBtn = new QPushButton(i18nc("@action:button", "Change Passphrase"), parent);
0362                 buttonRow->addWidget(changePassphraseBtn);
0363 
0364                 genRevokeBtn = new QPushButton(i18nc("@action:button", "Generate Revocation Certificate"), parent);
0365                 genRevokeBtn->setToolTip(u"<html>"
0366                                          % i18n("A revocation certificate is a file that serves as a \"kill switch\" to publicly "
0367                                                 "declare that a key shall not anymore be used.  It is not possible "
0368                                                 "to retract such a revocation certificate once it has been published.")
0369                                          % u"</html>");
0370                 buttonRow->addWidget(genRevokeBtn);
0371 
0372                 buttonRow->addStretch(1);
0373 
0374                 mainLayout->addLayout(buttonRow);
0375             }
0376         }
0377     } ui;
0378 };
0379 
0380 CertificateDetailsWidget::Private::Private(CertificateDetailsWidget *qq)
0381     : q{qq}
0382 {
0383     ui.setupUi(q);
0384 
0385     ui.userIDTable->setContextMenuPolicy(Qt::CustomContextMenu);
0386     connect(ui.userIDTable, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) {
0387         userIDTableContextMenuRequested(p);
0388     });
0389     connect(ui.userIDTable, &QTreeWidget::itemSelectionChanged, q, [this]() {
0390         updateUserIDActions();
0391     });
0392     connect(ui.addUserIDBtn, &QPushButton::clicked, q, [this]() {
0393         addUserID();
0394     });
0395     connect(ui.setPrimaryUserIDBtn, &QPushButton::clicked, q, [this]() {
0396         setPrimaryUserID();
0397     });
0398     connect(ui.revokeUserIDBtn, &QPushButton::clicked, q, [this]() {
0399         revokeSelectedUserID();
0400     });
0401     connect(ui.changePassphraseBtn, &QPushButton::clicked, q, [this]() {
0402         changePassphrase();
0403     });
0404     connect(ui.genRevokeBtn, &QPushButton::clicked, q, [this]() {
0405         genRevokeCert();
0406     });
0407     connect(ui.changeExpirationAction, &QAction::triggered, q, [this]() {
0408         changeExpiration();
0409     });
0410     connect(ui.showIssuerCertificateAction, &QAction::triggered, q, [this]() {
0411         showIssuerCertificate();
0412     });
0413     connect(ui.trustChainDetailsBtn, &QPushButton::pressed, q, [this]() {
0414         showTrustChainDialog();
0415     });
0416     connect(ui.moreDetailsBtn, &QPushButton::pressed, q, [this]() {
0417         showMoreDetails();
0418     });
0419     connect(ui.refreshBtn, &QPushButton::clicked, q, [this]() {
0420         refreshCertificate();
0421     });
0422     connect(ui.certifyBtn, &QPushButton::clicked, q, [this]() {
0423         certifyUserIDs();
0424     });
0425     connect(ui.revokeCertificationsBtn, &QPushButton::clicked, q, [this]() {
0426         revokeCertifications();
0427     });
0428     connect(ui.webOfTrustBtn, &QPushButton::clicked, q, [this]() {
0429         webOfTrustClicked();
0430     });
0431     connect(ui.exportBtn, &QPushButton::clicked, q, [this]() {
0432         exportClicked();
0433     });
0434     if (ui.copyFingerprintAction) {
0435         connect(ui.copyFingerprintAction, &QAction::triggered, q, [this]() {
0436             copyFingerprintToClipboard();
0437         });
0438     }
0439 
0440     connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() {
0441         keysMayHaveChanged();
0442     });
0443 }
0444 
0445 void CertificateDetailsWidget::Private::setupCommonProperties()
0446 {
0447     const bool isOpenPGP = key.protocol() == GpgME::OpenPGP;
0448     const bool isSMIME = key.protocol() == GpgME::CMS;
0449     const bool isOwnKey = key.hasSecret();
0450     const auto isLocalKey = !isRemoteKey(key);
0451     const auto keyCanBeCertified = Kleo::canBeCertified(key);
0452 
0453     // update visibility of UI elements
0454     ui.userIDs->setVisible(isOpenPGP);
0455     ui.addUserIDBtn->setVisible(isOwnKey);
0456     ui.setPrimaryUserIDBtn->setVisible(isOwnKey);
0457     // ui.certifyBtn->setVisible(true); // always visible (for OpenPGP keys)
0458     // ui.webOfTrustBtn->setVisible(true); // always visible (for OpenPGP keys)
0459     ui.revokeCertificationsBtn->setVisible(Kleo::Commands::RevokeCertificationCommand::isSupported());
0460     ui.revokeUserIDBtn->setVisible(isOwnKey);
0461 
0462     for (const auto &[_, field] : ui.smimeAttributeFields) {
0463         field->setVisible(isSMIME);
0464     }
0465     ui.smimeTrustLevelField->setVisible(isSMIME);
0466     // ui.validFromField->setVisible(true); // always visible
0467     // ui.expiresField->setVisible(true); // always visible
0468     if (isOpenPGP && isOwnKey) {
0469         ui.expiresField->setAction(ui.changeExpirationAction);
0470     } else {
0471         ui.expiresField->setAction(nullptr);
0472     }
0473     // ui.fingerprintField->setVisible(true); // always visible
0474     ui.smimeIssuerField->setVisible(isSMIME);
0475     ui.complianceField->setVisible(DeVSCompliance::isCompliant());
0476     ui.trustedIntroducerField->setVisible(isOpenPGP); // may be hidden again by setupPGPProperties()
0477 
0478     ui.smimeRelatedAddresses->setVisible(isSMIME);
0479     ui.smimeAddressList->setVisible(isSMIME);
0480 
0481     ui.moreDetailsBtn->setVisible(isLocalKey);
0482     ui.moreDetailsBtn->setText(isSMIME        ? i18nc("@action:button", "More Details...")
0483                                    : isOwnKey ? i18nc("@action:button", "Manage Subkeys")
0484                                               : i18nc("@action:button", "Show Subkeys"));
0485 
0486     ui.trustChainDetailsBtn->setVisible(isSMIME);
0487     ui.refreshBtn->setVisible(isLocalKey);
0488     ui.changePassphraseBtn->setVisible(isSecretKeyStoredInKeyRing(key));
0489     ui.exportBtn->setVisible(isLocalKey);
0490     ui.genRevokeBtn->setVisible(isOpenPGP && isOwnKey);
0491 
0492     // update availability of buttons
0493     const auto userCanSignUserIDs = userHasCertificationKey();
0494     ui.addUserIDBtn->setEnabled(canBeUsedForSecretKeyOperations(key));
0495     ui.setPrimaryUserIDBtn->setEnabled(false); // requires a selected user ID
0496     ui.certifyBtn->setEnabled(isLocalKey && keyCanBeCertified && userCanSignUserIDs);
0497     ui.webOfTrustBtn->setEnabled(isLocalKey);
0498     ui.revokeCertificationsBtn->setEnabled(userCanSignUserIDs && isLocalKey);
0499     ui.revokeUserIDBtn->setEnabled(false); // requires a selected user ID
0500     ui.changeExpirationAction->setEnabled(canBeUsedForSecretKeyOperations(key));
0501     ui.changePassphraseBtn->setEnabled(isSecretKeyStoredInKeyRing(key));
0502     ui.genRevokeBtn->setEnabled(canBeUsedForSecretKeyOperations(key));
0503 
0504     // update values of protocol-independent UI elements
0505     ui.validFromField->setValue(Formatting::creationDateString(key), Formatting::accessibleCreationDate(key));
0506     ui.expiresField->setValue(Formatting::expirationDateString(key, i18nc("Valid until:", "unlimited")), Formatting::accessibleExpirationDate(key));
0507     ui.fingerprintField->setValue(Formatting::prettyID(key.primaryFingerprint()), Formatting::accessibleHexID(key.primaryFingerprint()));
0508     if (DeVSCompliance::isCompliant()) {
0509         ui.complianceField->setValue(Kleo::Formatting::complianceStringForKey(key));
0510     }
0511 }
0512 
0513 void CertificateDetailsWidget::Private::updateUserIDActions()
0514 {
0515     const auto userIDs = selectedUserIDs(ui.userIDTable);
0516     const auto singleUserID = userIDs.size() == 1 ? userIDs.front() : GpgME::UserID{};
0517     const bool isPrimaryUserID = !singleUserID.isNull() && (ui.userIDTable->selectedItems().front() == ui.userIDTable->topLevelItem(0));
0518     ui.setPrimaryUserIDBtn->setEnabled(!singleUserID.isNull() //
0519                                        && !isPrimaryUserID //
0520                                        && !Kleo::isRevokedOrExpired(singleUserID) //
0521                                        && canBeUsedForSecretKeyOperations(key));
0522     ui.revokeUserIDBtn->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID));
0523 }
0524 
0525 void CertificateDetailsWidget::Private::setUpUserIDTable()
0526 {
0527     ui.userIDTable->clear();
0528 
0529     QStringList headers = {i18n("Email"), i18n("Name"), i18n("Trust Level"), i18n("Tags")};
0530     ui.userIDTable->setColumnCount(headers.count());
0531     ui.userIDTable->setColumnWidth(0, 200);
0532     ui.userIDTable->setColumnWidth(1, 200);
0533     ui.userIDTable->setHeaderLabels(headers);
0534 
0535     const auto uids = key.userIDs();
0536     for (unsigned int i = 0; i < uids.size(); ++i) {
0537         const auto &uid = uids[i];
0538         auto item = new QTreeWidgetItem;
0539         const QString toolTip = tofuTooltipString(uid);
0540         item->setData(0, Qt::UserRole, QVariant::fromValue(uid));
0541 
0542         auto pMail = Kleo::Formatting::prettyEMail(uid);
0543         auto pName = Kleo::Formatting::prettyName(uid);
0544 
0545         item->setData(0, Qt::DisplayRole, pMail);
0546         item->setData(0, Qt::ToolTipRole, toolTip);
0547         item->setData(0, Qt::AccessibleTextRole, pMail.isEmpty() ? i18nc("text for screen readers for an empty email address", "no email") : pMail);
0548         item->setData(1, Qt::DisplayRole, pName);
0549         item->setData(1, Qt::ToolTipRole, toolTip);
0550 
0551         item->setData(2, Qt::DecorationRole, trustLevelIcon(uid));
0552         item->setData(2, Qt::DisplayRole, trustLevelText(uid));
0553         item->setData(2, Qt::ToolTipRole, toolTip);
0554 
0555         GpgME::Error err;
0556         QStringList tagList;
0557         for (const auto &tag : uid.remarks(Tags::tagKeys(), err)) {
0558             if (err) {
0559                 qCWarning(KLEOPATRA_LOG) << "Getting remarks for user ID" << uid.id() << "failed:" << err;
0560             }
0561             tagList << QString::fromStdString(tag);
0562         }
0563         qCDebug(KLEOPATRA_LOG) << "tagList:" << tagList;
0564         const auto tags = tagList.join(QStringLiteral("; "));
0565         item->setData(3, Qt::DisplayRole, tags);
0566         item->setData(3, Qt::ToolTipRole, toolTip);
0567 
0568         ui.userIDTable->addTopLevelItem(item);
0569     }
0570     ui.userIDTable->restoreColumnLayout(QStringLiteral("UserIDTable"));
0571     if (!Tags::tagsEnabled()) {
0572         ui.userIDTable->hideColumn(3);
0573     }
0574 }
0575 
0576 void CertificateDetailsWidget::Private::setUpSMIMEAdressList()
0577 {
0578     ui.smimeAddressList->clear();
0579 
0580     const auto *const emailField = attributeField(QStringLiteral("EMAIL"));
0581 
0582     // add email address from primary user ID if not listed already as attribute field
0583     if (!emailField) {
0584         const auto ownerId = key.userID(0);
0585         const Kleo::DN dn(ownerId.id());
0586         const QString dnEmail = dn[QStringLiteral("EMAIL")];
0587         if (!dnEmail.isEmpty()) {
0588             ui.smimeAddressList->addItem(dnEmail);
0589         }
0590     }
0591 
0592     if (key.numUserIDs() > 1) {
0593         // iterate over the secondary user IDs
0594 #ifdef USE_RANGES
0595         for (const auto uids = key.userIDs(); const auto &uid : std::ranges::subrange(std::next(uids.begin()), uids.end())) {
0596 #else
0597         const auto uids = key.userIDs();
0598         for (auto it = std::next(uids.begin()); it != uids.end(); ++it) {
0599             const auto &uid = *it;
0600 #endif
0601             const auto name = Kleo::Formatting::prettyName(uid);
0602             const auto email = Kleo::Formatting::prettyEMail(uid);
0603             QString itemText;
0604             if (name.isEmpty() && !email.isEmpty()) {
0605                 // skip email addresses already listed in email attribute field
0606                 if (emailField && email == emailField->value()) {
0607                     continue;
0608                 }
0609                 itemText = email;
0610             } else {
0611                 // S/MIME certificates sometimes contain urls where both
0612                 // name and mail is empty. In that case we print whatever
0613                 // the uid is as name.
0614                 //
0615                 // Can be ugly like (3:uri24:http://ca.intevation.org), but
0616                 // this is better then showing an empty entry.
0617                 itemText = QString::fromUtf8(uid.id());
0618             }
0619             // avoid duplicate entries in the list
0620             if (ui.smimeAddressList->findItems(itemText, Qt::MatchExactly).empty()) {
0621                 ui.smimeAddressList->addItem(itemText);
0622             }
0623         }
0624     }
0625 
0626     if (ui.smimeAddressList->count() == 0) {
0627         ui.smimeRelatedAddresses->setVisible(false);
0628         ui.smimeAddressList->setVisible(false);
0629     }
0630 }
0631 
0632 void CertificateDetailsWidget::Private::revokeUserID(const GpgME::UserID &userId)
0633 {
0634     const QString message =
0635         xi18nc("@info", "<para>Do you really want to revoke the user ID<nl/><emphasis>%1</emphasis> ?</para>", QString::fromUtf8(userId.id()));
0636     auto confirmButton = KStandardGuiItem::ok();
0637     confirmButton.setText(i18nc("@action:button", "Revoke User ID"));
0638     confirmButton.setToolTip({});
0639     const auto choice = KMessageBox::questionTwoActions(q->window(),
0640                                                         message,
0641                                                         i18nc("@title:window", "Confirm Revocation"),
0642                                                         confirmButton,
0643                                                         KStandardGuiItem::cancel(),
0644                                                         {},
0645                                                         KMessageBox::Notify | KMessageBox::WindowModal);
0646     if (choice != KMessageBox::ButtonCode::PrimaryAction) {
0647         return;
0648     }
0649 
0650     auto cmd = new Commands::RevokeUserIDCommand(userId);
0651     cmd->setParentWidget(q);
0652     connect(cmd, &Command::finished, q, [this]() {
0653         ui.userIDTable->setEnabled(true);
0654         // the Revoke User ID button will be updated by the key update
0655         updateKey();
0656     });
0657     ui.userIDTable->setEnabled(false);
0658     ui.revokeUserIDBtn->setEnabled(false);
0659     cmd->start();
0660 }
0661 
0662 void CertificateDetailsWidget::Private::revokeSelectedUserID()
0663 {
0664     const auto userIDs = selectedUserIDs(ui.userIDTable);
0665     if (userIDs.size() != 1) {
0666         return;
0667     }
0668     revokeUserID(userIDs.front());
0669 }
0670 
0671 void CertificateDetailsWidget::Private::changeExpiration()
0672 {
0673     auto cmd = new Kleo::Commands::ChangeExpiryCommand(key);
0674     QObject::connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, q, [this]() {
0675         ui.changeExpirationAction->setEnabled(true);
0676     });
0677     ui.changeExpirationAction->setEnabled(false);
0678     cmd->start();
0679 }
0680 
0681 void CertificateDetailsWidget::Private::changePassphrase()
0682 {
0683     auto cmd = new Kleo::Commands::ChangePassphraseCommand(key);
0684     QObject::connect(cmd, &Kleo::Commands::ChangePassphraseCommand::finished, q, [this]() {
0685         ui.changePassphraseBtn->setEnabled(true);
0686     });
0687     ui.changePassphraseBtn->setEnabled(false);
0688     cmd->start();
0689 }
0690 
0691 void CertificateDetailsWidget::Private::genRevokeCert()
0692 {
0693     auto cmd = new Kleo::Commands::GenRevokeCommand(key);
0694     QObject::connect(cmd, &Kleo::Commands::GenRevokeCommand::finished, q, [this]() {
0695         ui.genRevokeBtn->setEnabled(true);
0696     });
0697     ui.genRevokeBtn->setEnabled(false);
0698     cmd->start();
0699 }
0700 
0701 void CertificateDetailsWidget::Private::refreshCertificate()
0702 {
0703     auto cmd = new Kleo::RefreshCertificateCommand{key};
0704     QObject::connect(cmd, &Kleo::RefreshCertificateCommand::finished, q, [this]() {
0705         ui.refreshBtn->setEnabled(true);
0706     });
0707     ui.refreshBtn->setEnabled(false);
0708     cmd->start();
0709 }
0710 
0711 void CertificateDetailsWidget::Private::certifyUserIDs()
0712 {
0713     const auto userIDs = selectedUserIDs(ui.userIDTable);
0714     auto cmd = userIDs.empty() ? new Kleo::Commands::CertifyCertificateCommand{key} //
0715                                : new Kleo::Commands::CertifyCertificateCommand{userIDs};
0716     QObject::connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished, q, [this]() {
0717         updateKey();
0718         ui.certifyBtn->setEnabled(true);
0719     });
0720     ui.certifyBtn->setEnabled(false);
0721     cmd->start();
0722 }
0723 
0724 void CertificateDetailsWidget::Private::revokeCertifications()
0725 {
0726     const auto userIDs = selectedUserIDs(ui.userIDTable);
0727     auto cmd = userIDs.empty() ? new Kleo::Commands::RevokeCertificationCommand{key} //
0728                                : new Kleo::Commands::RevokeCertificationCommand{userIDs};
0729     QObject::connect(cmd, &Kleo::Command::finished, q, [this]() {
0730         updateKey();
0731         ui.revokeCertificationsBtn->setEnabled(true);
0732     });
0733     ui.revokeCertificationsBtn->setEnabled(false);
0734     cmd->start();
0735 }
0736 
0737 void CertificateDetailsWidget::Private::webOfTrustClicked()
0738 {
0739     QScopedPointer<WebOfTrustDialog> dlg(new WebOfTrustDialog(q));
0740     dlg->setKey(key);
0741     dlg->exec();
0742 }
0743 
0744 void CertificateDetailsWidget::Private::exportClicked()
0745 {
0746     QScopedPointer<ExportDialog> dlg(new ExportDialog(q));
0747     dlg->setKey(key);
0748     dlg->exec();
0749 }
0750 
0751 void CertificateDetailsWidget::Private::addUserID()
0752 {
0753     auto cmd = new Kleo::Commands::AddUserIDCommand(key);
0754     QObject::connect(cmd, &Kleo::Commands::AddUserIDCommand::finished, q, [this]() {
0755         ui.addUserIDBtn->setEnabled(true);
0756         updateKey();
0757     });
0758     ui.addUserIDBtn->setEnabled(false);
0759     cmd->start();
0760 }
0761 
0762 void CertificateDetailsWidget::Private::setPrimaryUserID(const GpgME::UserID &uid)
0763 {
0764     auto userId = uid;
0765     if (userId.isNull()) {
0766         const auto userIDs = selectedUserIDs(ui.userIDTable);
0767         if (userIDs.size() != 1) {
0768             return;
0769         }
0770         userId = userIDs.front();
0771     }
0772 
0773     auto cmd = new Kleo::Commands::SetPrimaryUserIDCommand(userId);
0774     QObject::connect(cmd, &Kleo::Commands::SetPrimaryUserIDCommand::finished, q, [this]() {
0775         ui.userIDTable->setEnabled(true);
0776         // the Flag As Primary button will be updated by the key update
0777         updateKey();
0778     });
0779     ui.userIDTable->setEnabled(false);
0780     ui.setPrimaryUserIDBtn->setEnabled(false);
0781     cmd->start();
0782 }
0783 
0784 namespace
0785 {
0786 void ensureThatKeyDetailsAreLoaded(GpgME::Key &key)
0787 {
0788     if (key.userID(0).numSignatures() == 0) {
0789         key.update();
0790     }
0791 }
0792 }
0793 
0794 void CertificateDetailsWidget::Private::keysMayHaveChanged()
0795 {
0796     auto newKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint());
0797     if (!newKey.isNull()) {
0798         ensureThatKeyDetailsAreLoaded(newKey);
0799         setUpdatedKey(newKey);
0800     }
0801 }
0802 
0803 void CertificateDetailsWidget::Private::showTrustChainDialog()
0804 {
0805     QScopedPointer<TrustChainDialog> dlg(new TrustChainDialog(q));
0806     dlg->setKey(key);
0807     dlg->exec();
0808 }
0809 
0810 void CertificateDetailsWidget::Private::userIDTableContextMenuRequested(const QPoint &p)
0811 {
0812     const auto userIDs = selectedUserIDs(ui.userIDTable);
0813     const auto singleUserID = (userIDs.size() == 1) ? userIDs.front() : GpgME::UserID{};
0814     const bool isPrimaryUserID = !singleUserID.isNull() && (ui.userIDTable->selectedItems().front() == ui.userIDTable->topLevelItem(0));
0815     const bool canSignUserIDs = userHasCertificationKey();
0816     const auto isLocalKey = !isRemoteKey(key);
0817     const auto keyCanBeCertified = Kleo::canBeCertified(key);
0818 
0819     auto menu = new QMenu(q);
0820     if (key.hasSecret()) {
0821         auto action =
0822             menu->addAction(QIcon::fromTheme(QStringLiteral("favorite")), i18nc("@action:inmenu", "Flag as Primary User ID"), q, [this, singleUserID]() {
0823                 setPrimaryUserID(singleUserID);
0824             });
0825         action->setEnabled(!singleUserID.isNull() //
0826                            && !isPrimaryUserID //
0827                            && !Kleo::isRevokedOrExpired(singleUserID) //
0828                            && canBeUsedForSecretKeyOperations(key));
0829     }
0830     {
0831         const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Certify User IDs...")
0832                                                 : i18ncp("@action:inmenu", "Certify User ID...", "Certify User IDs...", userIDs.size());
0833         auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-sign")), actionText, q, [this]() {
0834             certifyUserIDs();
0835         });
0836         action->setEnabled(isLocalKey && keyCanBeCertified && canSignUserIDs);
0837     }
0838     if (Kleo::Commands::RevokeCertificationCommand::isSupported()) {
0839         const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Revoke Certifications...")
0840                                                 : i18ncp("@action:inmenu", "Revoke Certification...", "Revoke Certifications...", userIDs.size());
0841         auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), actionText, q, [this]() {
0842             revokeCertifications();
0843         });
0844         action->setEnabled(isLocalKey && canSignUserIDs);
0845     }
0846 #ifdef MAILAKONADI_ENABLED
0847     if (key.hasSecret()) {
0848         auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")),
0849                                       i18nc("@action:inmenu", "Publish at Mail Provider ..."),
0850                                       q,
0851                                       [this, singleUserID]() {
0852                                           auto cmd = new Kleo::Commands::ExportOpenPGPCertToProviderCommand(singleUserID);
0853                                           ui.userIDTable->setEnabled(false);
0854                                           connect(cmd, &Kleo::Commands::ExportOpenPGPCertToProviderCommand::finished, q, [this]() {
0855                                               ui.userIDTable->setEnabled(true);
0856                                           });
0857                                           cmd->start();
0858                                       });
0859         action->setEnabled(!singleUserID.isNull());
0860     }
0861 #endif // MAILAKONADI_ENABLED
0862     {
0863         auto action =
0864             menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), i18nc("@action:inmenu", "Revoke User ID"), q, [this, singleUserID]() {
0865                 revokeUserID(singleUserID);
0866             });
0867         action->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID));
0868     }
0869     connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater);
0870     menu->popup(ui.userIDTable->viewport()->mapToGlobal(p));
0871 }
0872 
0873 void CertificateDetailsWidget::Private::showMoreDetails()
0874 {
0875     if (key.protocol() == GpgME::CMS) {
0876         auto cmd = new Kleo::Commands::DumpCertificateCommand(key);
0877         cmd->setParentWidget(q);
0878         cmd->setUseDialog(true);
0879         cmd->start();
0880     } else {
0881         auto dlg = new SubKeysDialog{q};
0882         dlg->setAttribute(Qt::WA_DeleteOnClose);
0883         dlg->setKey(key);
0884         dlg->open();
0885     }
0886 }
0887 
0888 QString CertificateDetailsWidget::Private::tofuTooltipString(const GpgME::UserID &uid) const
0889 {
0890     const auto tofu = uid.tofuInfo();
0891     if (tofu.isNull()) {
0892         return QString();
0893     }
0894 
0895     QString html = QStringLiteral("<table border=\"0\" cell-padding=\"5\">");
0896     const auto appendRow = [&html](const QString &lbl, const QString &val) {
0897         html += QStringLiteral(
0898                     "<tr>"
0899                     "<th style=\"text-align: right; padding-right: 5px; white-space: nowrap;\">%1:</th>"
0900                     "<td style=\"white-space: nowrap;\">%2</td>"
0901                     "</tr>")
0902                     .arg(lbl, val);
0903     };
0904     const auto appendHeader = [this, &html](const QString &hdr) {
0905         html += QStringLiteral("<tr><th colspan=\"2\" style=\"background-color: %1; color: %2\">%3</th></tr>")
0906                     .arg(q->palette().highlight().color().name(), q->palette().highlightedText().color().name(), hdr);
0907     };
0908     const auto dateTime = [](long ts) {
0909         QLocale l;
0910         return ts == 0 ? i18n("never") : l.toString(QDateTime::fromSecsSinceEpoch(ts), QLocale::ShortFormat);
0911     };
0912     appendHeader(i18n("Signing"));
0913     appendRow(i18n("First message"), dateTime(tofu.signFirst()));
0914     appendRow(i18n("Last message"), dateTime(tofu.signLast()));
0915     appendRow(i18n("Message count"), QString::number(tofu.signCount()));
0916     appendHeader(i18n("Encryption"));
0917     appendRow(i18n("First message"), dateTime(tofu.encrFirst()));
0918     appendRow(i18n("Last message"), dateTime(tofu.encrLast()));
0919     appendRow(i18n("Message count"), QString::number(tofu.encrCount()));
0920 
0921     html += QStringLiteral("</table>");
0922     // Make sure the tooltip string is different for each UserID, even if the
0923     // data are the same, otherwise the tooltip is not updated and moved when
0924     // user moves mouse from one row to another.
0925     html += QStringLiteral("<!-- %1 //-->").arg(QString::fromUtf8(uid.id()));
0926     return html;
0927 }
0928 
0929 QIcon CertificateDetailsWidget::Private::trustLevelIcon(const GpgME::UserID &uid) const
0930 {
0931     if (updateInProgress) {
0932         return QIcon::fromTheme(QStringLiteral("emblem-question"));
0933     }
0934     switch (uid.validity()) {
0935     case GpgME::UserID::Unknown:
0936     case GpgME::UserID::Undefined:
0937         return QIcon::fromTheme(QStringLiteral("emblem-question"));
0938     case GpgME::UserID::Never:
0939         return QIcon::fromTheme(QStringLiteral("emblem-error"));
0940     case GpgME::UserID::Marginal:
0941         return QIcon::fromTheme(QStringLiteral("emblem-warning"));
0942     case GpgME::UserID::Full:
0943     case GpgME::UserID::Ultimate:
0944         return QIcon::fromTheme(QStringLiteral("emblem-success"));
0945     }
0946     return {};
0947 }
0948 
0949 QString CertificateDetailsWidget::Private::trustLevelText(const GpgME::UserID &uid) const
0950 {
0951     return updateInProgress ? i18n("Updating...") : Formatting::validityShort(uid);
0952 }
0953 
0954 namespace
0955 {
0956 auto isGood(const GpgME::UserID::Signature &signature)
0957 {
0958     return signature.status() == GpgME::UserID::Signature::NoError //
0959         && !signature.isInvalid() //
0960         && 0x10 <= signature.certClass() && signature.certClass() <= 0x13;
0961 }
0962 
0963 auto accumulateTrustDomains(const std::vector<GpgME::UserID::Signature> &signatures)
0964 {
0965     return std::accumulate(std::begin(signatures), std::end(signatures), std::set<QString>(), [](auto domains, const auto &signature) {
0966         if (isGood(signature) && signature.isTrustSignature()) {
0967             domains.insert(Formatting::trustSignatureDomain(signature));
0968         }
0969         return domains;
0970     });
0971 }
0972 
0973 auto accumulateTrustDomains(const std::vector<GpgME::UserID> &userIds)
0974 {
0975     return std::accumulate(std::begin(userIds), std::end(userIds), std::set<QString>(), [](auto domains, const auto &userID) {
0976         const auto newDomains = accumulateTrustDomains(userID.signatures());
0977         std::copy(std::begin(newDomains), std::end(newDomains), std::inserter(domains, std::end(domains)));
0978         return domains;
0979     });
0980 }
0981 }
0982 
0983 void CertificateDetailsWidget::Private::setupPGPProperties()
0984 {
0985     setUpUserIDTable();
0986 
0987     const auto trustDomains = accumulateTrustDomains(key.userIDs());
0988     ui.trustedIntroducerField->setVisible(!trustDomains.empty());
0989     ui.trustedIntroducerField->setValue(QStringList(std::begin(trustDomains), std::end(trustDomains)).join(u", "));
0990 
0991     ui.refreshBtn->setToolTip(i18nc("@info:tooltip", "Update the key from external sources."));
0992 }
0993 
0994 static QString formatDNToolTip(const Kleo::DN &dn)
0995 {
0996     QString html = QStringLiteral("<table border=\"0\" cell-spacing=15>");
0997 
0998     const auto appendRow = [&html, dn](const QString &lbl, const QString &attr) {
0999         const QString val = dn[attr];
1000         if (!val.isEmpty()) {
1001             html += QStringLiteral(
1002                         "<tr><th style=\"text-align: left; white-space: nowrap\">%1:</th>"
1003                         "<td style=\"white-space: nowrap\">%2</td>"
1004                         "</tr>")
1005                         .arg(lbl, val);
1006         }
1007     };
1008     appendRow(i18n("Common Name"), QStringLiteral("CN"));
1009     appendRow(i18n("Organization"), QStringLiteral("O"));
1010     appendRow(i18n("Street"), QStringLiteral("STREET"));
1011     appendRow(i18n("City"), QStringLiteral("L"));
1012     appendRow(i18n("State"), QStringLiteral("ST"));
1013     appendRow(i18n("Country"), QStringLiteral("C"));
1014     html += QStringLiteral("</table>");
1015 
1016     return html;
1017 }
1018 
1019 void CertificateDetailsWidget::Private::setupSMIMEProperties()
1020 {
1021     const auto ownerId = key.userID(0);
1022     const Kleo::DN dn(ownerId.id());
1023 
1024     for (const auto &[attributeName, field] : ui.smimeAttributeFields) {
1025         const QString attributeValue = dn[attributeName];
1026         field->setValue(attributeValue);
1027         field->setVisible(!attributeValue.isEmpty());
1028     }
1029     ui.smimeTrustLevelField->setIcon(trustLevelIcon(ownerId));
1030     ui.smimeTrustLevelField->setValue(trustLevelText(ownerId));
1031 
1032     const Kleo::DN issuerDN(key.issuerName());
1033     const QString issuerCN = issuerDN[QStringLiteral("CN")];
1034     const QString issuer = issuerCN.isEmpty() ? QString::fromUtf8(key.issuerName()) : issuerCN;
1035     ui.smimeIssuerField->setValue(issuer);
1036     ui.smimeIssuerField->setToolTip(formatDNToolTip(issuerDN));
1037     ui.showIssuerCertificateAction->setEnabled(!key.isRoot());
1038 
1039     setUpSMIMEAdressList();
1040 
1041     ui.refreshBtn->setToolTip(i18nc("@info:tooltip", "Update the CRLs and do a full validation check of the certificate."));
1042 }
1043 
1044 void CertificateDetailsWidget::Private::showIssuerCertificate()
1045 {
1046     // there is either one or no parent key
1047     const auto parentKeys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption);
1048 
1049     if (parentKeys.empty()) {
1050         KMessageBox::error(q, i18n("The issuer certificate could not be found locally."));
1051         return;
1052     }
1053     auto cmd = new Kleo::Commands::DetailsCommand(parentKeys.front());
1054     cmd->setParentWidget(q);
1055     cmd->start();
1056 }
1057 
1058 void CertificateDetailsWidget::Private::copyFingerprintToClipboard()
1059 {
1060     if (auto clipboard = QGuiApplication::clipboard()) {
1061         clipboard->setText(QString::fromLatin1(key.primaryFingerprint()));
1062     }
1063 }
1064 
1065 CertificateDetailsWidget::CertificateDetailsWidget(QWidget *parent)
1066     : QWidget{parent}
1067     , d{std::make_unique<Private>(this)}
1068 {
1069 }
1070 
1071 CertificateDetailsWidget::~CertificateDetailsWidget() = default;
1072 
1073 void CertificateDetailsWidget::Private::keyListDone(const GpgME::KeyListResult &, const std::vector<GpgME::Key> &keys, const QString &, const GpgME::Error &)
1074 {
1075     updateInProgress = false;
1076     if (keys.size() != 1) {
1077         qCWarning(KLEOPATRA_LOG) << "Invalid keylist result in update.";
1078         return;
1079     }
1080     // As we listen for keysmayhavechanged we get the update
1081     // after updating the keycache.
1082     KeyCache::mutableInstance()->insert(keys);
1083 }
1084 
1085 void CertificateDetailsWidget::Private::updateKey()
1086 {
1087     key.update();
1088     setUpdatedKey(key);
1089 }
1090 
1091 void CertificateDetailsWidget::Private::setUpdatedKey(const GpgME::Key &k)
1092 {
1093     key = k;
1094 
1095     setupCommonProperties();
1096     if (key.protocol() == GpgME::OpenPGP) {
1097         setupPGPProperties();
1098     } else {
1099         setupSMIMEProperties();
1100     }
1101 }
1102 
1103 void CertificateDetailsWidget::setKey(const GpgME::Key &key)
1104 {
1105     if (key.protocol() == GpgME::CMS) {
1106         // For everything but S/MIME this should be quick
1107         // and we don't need to show another status.
1108         d->updateInProgress = true;
1109     }
1110     d->setUpdatedKey(key);
1111 
1112     // Run a keylistjob with full details (TOFU / Validate)
1113     QGpgME::KeyListJob *job =
1114         key.protocol() == GpgME::OpenPGP ? QGpgME::openpgp()->keyListJob(false, true, true) : QGpgME::smime()->keyListJob(false, true, true);
1115 
1116     auto ctx = QGpgME::Job::context(job);
1117     ctx->addKeyListMode(GpgME::WithTofu);
1118     ctx->addKeyListMode(GpgME::SignatureNotations);
1119     if (key.hasSecret()) {
1120         ctx->addKeyListMode(GpgME::WithSecret);
1121     }
1122 
1123     // Windows QGpgME new style connect problem makes this necessary.
1124     connect(job,
1125             SIGNAL(result(GpgME::KeyListResult, std::vector<GpgME::Key>, QString, GpgME::Error)),
1126             this,
1127             SLOT(keyListDone(GpgME::KeyListResult, std::vector<GpgME::Key>, QString, GpgME::Error)));
1128 
1129     job->start(QStringList() << QLatin1StringView(key.primaryFingerprint()));
1130 }
1131 
1132 GpgME::Key CertificateDetailsWidget::key() const
1133 {
1134     return d->key;
1135 }
1136 
1137 #include "moc_certificatedetailswidget.cpp"