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"