diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fbd9a4dd5..64d4aa7c7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,361 +1,364 @@ add_subdirectory(icons) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) if (NOT DISABLE_KWATCHGNUPG) add_subdirectory(kwatchgnupg) endif() add_subdirectory(libkleopatraclient) add_subdirectory(conf) add_subdirectory(kconf_update) if(WIN32) set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_win.cpp) set(_kleopatra_extra_SRCS utils/gnupg-registry.c selftest/registrycheck.cpp utils/windowsprocessdevice.cpp ) else() set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_unix.cpp) set(_kleopatra_extra_SRCS) endif() set(_kleopatra_uiserver_SRCS uiserver/sessiondata.cpp uiserver/uiserver.cpp ${_kleopatra_extra_uiserver_SRCS} uiserver/assuanserverconnection.cpp uiserver/echocommand.cpp uiserver/decryptverifycommandemailbase.cpp uiserver/decryptverifycommandfilesbase.cpp uiserver/signcommand.cpp uiserver/signencryptfilescommand.cpp uiserver/prepencryptcommand.cpp uiserver/prepsigncommand.cpp uiserver/encryptcommand.cpp uiserver/selectcertificatecommand.cpp uiserver/importfilescommand.cpp uiserver/createchecksumscommand.cpp uiserver/verifychecksumscommand.cpp selftest/uiservercheck.cpp ) if(ASSUAN2_FOUND) include_directories(${ASSUAN2_INCLUDES}) set(_kleopatra_uiserver_extra_libs ${ASSUAN2_LIBRARIES}) else() include_directories(${ASSUAN_INCLUDES}) if(WIN32) set(_kleopatra_uiserver_extra_libs ${ASSUAN_VANILLA_LIBRARIES}) else() set(_kleopatra_uiserver_extra_libs ${ASSUAN_PTHREAD_LIBRARIES}) endif() endif() if(HAVE_GPG_ERR_SOURCE_KLEO) add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO) else() add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1) endif() ki18n_wrap_ui(_kleopatra_uiserver_SRCS crypto/gui/signingcertificateselectionwidget.ui) if("${Gpgmepp_VERSION}" VERSION_GREATER_EQUAL "1.14.1") set(_kleopatra_deviceinfowatcher_files smartcard/deviceinfowatcher.cpp ) else() set(_kleopatra_deviceinfowatcher_files) endif() set(_kleopatra_SRCS utils/gui-helper.cpp utils/filedialog.cpp utils/kdpipeiodevice.cpp utils/headerview.cpp utils/scrollarea.cpp utils/dragqueen.cpp utils/multivalidator.cpp utils/systemtrayicon.cpp utils/hex.cpp utils/path-helper.cpp utils/input.cpp utils/output.cpp utils/validation.cpp utils/wsastarter.cpp utils/iodevicelogger.cpp utils/log.cpp utils/action_data.cpp utils/types.cpp utils/archivedefinition.cpp utils/auditlog.cpp utils/clipboardmenu.cpp utils/kuniqueservice.cpp utils/remarks.cpp utils/writecertassuantransaction.cpp selftest/selftest.cpp selftest/enginecheck.cpp selftest/gpgconfcheck.cpp selftest/gpgagentcheck.cpp selftest/libkleopatrarccheck.cpp ${_kleopatra_extra_SRCS} view/keylistcontroller.cpp view/keytreeview.cpp view/searchbar.cpp view/smartcardwidget.cpp view/padwidget.cpp view/pgpcardwidget.cpp view/pivcardwidget.cpp view/netkeywidget.cpp view/nullpinwidget.cpp view/tabwidget.cpp view/keycacheoverlay.cpp view/waitwidget.cpp view/welcomewidget.cpp dialogs/certificateselectiondialog.cpp dialogs/certifywidget.cpp dialogs/expirydialog.cpp dialogs/lookupcertificatesdialog.cpp dialogs/ownertrustdialog.cpp dialogs/selftestdialog.cpp dialogs/certifycertificatedialog.cpp + dialogs/revokecertificationwidget.cpp + dialogs/revokecertificationdialog.cpp dialogs/adduseriddialog.cpp dialogs/addemaildialog.cpp dialogs/exportcertificatesdialog.cpp dialogs/deletecertificatesdialog.cpp dialogs/setinitialpindialog.cpp dialogs/certificatedetailswidget.cpp dialogs/trustchainwidget.cpp dialogs/weboftrustwidget.cpp dialogs/weboftrustdialog.cpp dialogs/exportdialog.cpp dialogs/subkeyswidget.cpp dialogs/gencardkeydialog.cpp dialogs/updatenotification.cpp dialogs/pivcardapplicationadministrationkeyinputdialog.cpp crypto/controller.cpp crypto/certificateresolver.cpp crypto/sender.cpp crypto/recipient.cpp crypto/task.cpp crypto/taskcollection.cpp crypto/decryptverifytask.cpp crypto/decryptverifyemailcontroller.cpp crypto/decryptverifyfilescontroller.cpp crypto/autodecryptverifyfilescontroller.cpp crypto/encryptemailtask.cpp crypto/encryptemailcontroller.cpp crypto/newsignencryptemailcontroller.cpp crypto/signencrypttask.cpp crypto/signencryptfilescontroller.cpp crypto/signemailtask.cpp crypto/signemailcontroller.cpp crypto/createchecksumscontroller.cpp crypto/verifychecksumscontroller.cpp crypto/gui/wizard.cpp crypto/gui/wizardpage.cpp crypto/gui/certificateselectionline.cpp crypto/gui/certificatelineedit.cpp crypto/gui/signingcertificateselectionwidget.cpp crypto/gui/signingcertificateselectiondialog.cpp crypto/gui/resultitemwidget.cpp crypto/gui/resultlistwidget.cpp crypto/gui/resultpage.cpp crypto/gui/newresultpage.cpp crypto/gui/signencryptfileswizard.cpp crypto/gui/signencryptemailconflictdialog.cpp crypto/gui/decryptverifyoperationwidget.cpp crypto/gui/decryptverifyfileswizard.cpp crypto/gui/decryptverifyfilesdialog.cpp crypto/gui/objectspage.cpp crypto/gui/resolverecipientspage.cpp crypto/gui/signerresolvepage.cpp crypto/gui/encryptemailwizard.cpp crypto/gui/signemailwizard.cpp crypto/gui/signencryptwidget.cpp crypto/gui/signencryptwizard.cpp crypto/gui/unknownrecipientwidget.cpp crypto/gui/verifychecksumsdialog.cpp commands/command.cpp commands/gnupgprocesscommand.cpp commands/detailscommand.cpp commands/exportcertificatecommand.cpp commands/importcertificatescommand.cpp commands/importcertificatefromfilecommand.cpp commands/importcertificatefromclipboardcommand.cpp commands/importcertificatefromdatacommand.cpp commands/lookupcertificatescommand.cpp commands/reloadkeyscommand.cpp commands/refreshx509certscommand.cpp commands/refreshopenpgpcertscommand.cpp commands/deletecertificatescommand.cpp commands/decryptverifyfilescommand.cpp commands/signencryptfilescommand.cpp commands/signencryptfoldercommand.cpp commands/encryptclipboardcommand.cpp commands/signclipboardcommand.cpp commands/decryptverifyclipboardcommand.cpp commands/clearcrlcachecommand.cpp commands/dumpcrlcachecommand.cpp commands/dumpcertificatecommand.cpp commands/importcrlcommand.cpp commands/changeexpirycommand.cpp commands/changeownertrustcommand.cpp commands/changeroottrustcommand.cpp commands/changepassphrasecommand.cpp commands/certifycertificatecommand.cpp + commands/revokecertificationcommand.cpp commands/selftestcommand.cpp commands/exportsecretkeycommand.cpp commands/exportopenpgpcertstoservercommand.cpp commands/adduseridcommand.cpp commands/newcertificatecommand.cpp commands/setinitialpincommand.cpp commands/learncardkeyscommand.cpp commands/checksumcreatefilescommand.cpp commands/checksumverifyfilescommand.cpp commands/exportpaperkeycommand.cpp commands/importpaperkeycommand.cpp commands/genrevokecommand.cpp commands/keytocardcommand.cpp commands/cardcommand.cpp commands/pivgeneratecardkeycommand.cpp commands/changepincommand.cpp commands/authenticatepivcardapplicationcommand.cpp commands/setpivcardapplicationadministrationkeycommand.cpp commands/certificatetopivcardcommand.cpp commands/importcertificatefrompivcardcommand.cpp ${_kleopatra_uiserver_files} conf/configuredialog.cpp newcertificatewizard/listwidget.cpp newcertificatewizard/newcertificatewizard.cpp smartcard/readerstatus.cpp smartcard/card.cpp smartcard/openpgpcard.cpp smartcard/netkeycard.cpp smartcard/pivcard.cpp smartcard/keypairinfo.cpp smartcard/utils.cpp ${_kleopatra_deviceinfowatcher_files} aboutdata.cpp systrayicon.cpp kleopatraapplication.cpp mainwindow.cpp main.cpp ) if(WIN32) configure_file (versioninfo.rc.in versioninfo.rc) set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc ${_kleopatra_SRCS}) endif() if(HAVE_KCMUTILS) set (_kleopatra_extra_libs KF5::KCMUtils) else() set (_kleopatra_SRCS conf/kleopageconfigdialog.cpp ${_kleopatra_SRCS}) endif() ecm_qt_declare_logging_category(_kleopatra_SRCS HEADER kleopatra_debug.h IDENTIFIER KLEOPATRA_LOG CATEGORY_NAME org.kde.pim.kleopatra DESCRIPTION "kleopatra (kleopatra)" OLD_CATEGORY_NAMES log_kleopatra EXPORT KLEOPATRA ) if(KLEO_MODEL_TEST) add_definitions(-DKLEO_MODEL_TEST) set(_kleopatra_SRCS ${_kleopatra_SRCS} models/modeltest.cpp) endif() ki18n_wrap_ui(_kleopatra_SRCS dialogs/expirydialog.ui dialogs/lookupcertificatesdialog.ui dialogs/ownertrustdialog.ui dialogs/selectchecklevelwidget.ui dialogs/selftestdialog.ui dialogs/adduseriddialog.ui dialogs/setinitialpindialog.ui dialogs/certificatedetailswidget.ui dialogs/trustchainwidget.ui dialogs/subkeyswidget.ui newcertificatewizard/listwidget.ui newcertificatewizard/chooseprotocolpage.ui newcertificatewizard/enterdetailspage.ui newcertificatewizard/overviewpage.ui newcertificatewizard/keycreationpage.ui newcertificatewizard/resultpage.ui newcertificatewizard/advancedsettingsdialog.ui ) kconfig_add_kcfg_files(_kleopatra_SRCS kcfg/tooltippreferences.kcfgc kcfg/emailoperationspreferences.kcfgc kcfg/fileoperationspreferences.kcfgc kcfg/smimevalidationpreferences.kcfgc ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kleopatra.png") ecm_add_app_icon(_kleopatra_SRCS ICONS ${ICONS_SRCS}) qt5_add_resources(_kleopatra_SRCS kleopatra.qrc) add_executable(kleopatra_bin ${_kleopatra_SRCS} ${_kleopatra_uiserver_SRCS}) set_target_properties(kleopatra_bin PROPERTIES OUTPUT_NAME kleopatra) target_link_libraries(kleopatra_bin Gpgmepp QGpgme ${_kleopatra_extra_libs} KF5::Libkleo KF5::Mime KF5::I18n KF5::XmlGui KF5::IconThemes KF5::WindowSystem KF5::CoreAddons KF5::ItemModels KF5::Crash Qt5::Network Qt5::PrintSupport # Printing secret keys ${_kleopatra_uiserver_extra_libs} ${_kleopatra_dbusaddons_libs} kleopatraclientcore ) install(TARGETS kleopatra_bin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install( PROGRAMS data/org.kde.kleopatra.desktop data/kleopatra_import.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install(FILES data/org.kde.kleopatra.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install( FILES data/kleopatra_signencryptfiles.desktop data/kleopatra_signencryptfolders.desktop data/kleopatra_decryptverifyfiles.desktop data/kleopatra_decryptverifyfolders.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) diff --git a/src/commands/revokecertificationcommand.cpp b/src/commands/revokecertificationcommand.cpp new file mode 100644 index 000000000..d2eaf8e20 --- /dev/null +++ b/src/commands/revokecertificationcommand.cpp @@ -0,0 +1,239 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + commands/revokecertificationcommand.cpp + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2020 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include "revokecertificationcommand.h" + +#include "command_p.h" + +#include "exportopenpgpcertstoservercommand.h" +#include "dialogs/revokecertificationdialog.h" + +#include + +#include +#include + +#include +#include + +#include + +#include "kleopatra_debug.h" + +#include +#if GPGMEPP_VERSION >= 0x10E01 // 1.14.1 +# define GPGME_HAS_REVSIG +#endif + +using namespace Kleo; +using namespace Kleo::Commands; +using namespace GpgME; +using namespace QGpgME; + +class RevokeCertificationCommand::Private : public Command::Private +{ + friend class ::Kleo::Commands::RevokeCertificationCommand; + RevokeCertificationCommand *q_func() const + { + return static_cast(q); + } +public: + explicit Private(RevokeCertificationCommand *qq, KeyListController *c); + ~Private(); + + void init(); + +private: + void slotDialogAccepted(); + void slotDialogRejected(); + void slotResult(const Error &err); + +private: + void ensureDialogCreated(); + void createJob(); + +private: + Key certificationTarget; + std::vector uids; + QPointer dialog; + QPointer job; +}; + +RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func() +{ + return static_cast(d.get()); +} +const RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func() const +{ + return static_cast(d.get()); +} + +#define d d_func() +#define q q_func() + +RevokeCertificationCommand::Private::Private(RevokeCertificationCommand *qq, KeyListController *c) + : Command::Private(qq, c) +{ +} + +RevokeCertificationCommand::Private::~Private() +{ +} + +void RevokeCertificationCommand::Private::init() +{ + const std::vector keys_ = keys(); + if (keys_.size() != 1) { + qCWarning(KLEOPATRA_LOG) << "RevokeCertificationCommand::Private::init: Expected exactly one key, but got" << keys_.size(); + return; + } + if (keys_.front().protocol() != GpgME::OpenPGP) { + qCWarning(KLEOPATRA_LOG) << "RevokeCertificationCommand::Private::init: Expected OpenPGP key, but got" << keys_.front().protocolAsString(); + return; + } + certificationTarget = keys_.front(); +} + +void RevokeCertificationCommand::Private::slotDialogAccepted() +{ + createJob(); + +#ifdef GPGME_HAS_REVSIG + job->startRevokeSignature(certificationTarget, dialog->selectedCertificationKey(), dialog->selectedUserIDs()); +#endif +} + +void RevokeCertificationCommand::Private::slotDialogRejected() +{ + canceled(); +} + +void RevokeCertificationCommand::Private::slotResult(const Error &err) +{ + if (!err && !err.isCanceled() && dialog && dialog->sendToServer()) { + ExportOpenPGPCertsToServerCommand *const cmd = new ExportOpenPGPCertsToServerCommand(certificationTarget); + cmd->start(); + } else if (!err) { + information(i18n("Revocation successful."), + i18n("Revocation Succeeded")); + } else { + error(i18n("

An error occurred while trying to revoke the certification of

" + "%1:

\t%2

", + Formatting::formatForComboBox(certificationTarget), + QString::fromUtf8(err.asString())), + i18n("Revocation Error")); + } + + finished(); +} + +void RevokeCertificationCommand::Private::ensureDialogCreated() +{ + if (dialog) { + return; + } + + dialog = new RevokeCertificationDialog; + applyWindowID(dialog); + dialog->setAttribute(Qt::WA_DeleteOnClose); + + connect(dialog, SIGNAL(accepted()), q, SLOT(slotDialogAccepted())); + connect(dialog, SIGNAL(rejected()), q, SLOT(slotDialogRejected())); +} + +void RevokeCertificationCommand::Private::createJob() +{ + Q_ASSERT(!job); + + Q_ASSERT(certificationTarget.protocol() == OpenPGP); + const auto backend = QGpgME::openpgp(); + if (!backend) { + return; + } + + QuickJob *const j = backend->quickJob(); + if (!j) { + return; + } + + connect(j, &Job::progress, + q, &Command::progress); + connect(j, SIGNAL(result(GpgME::Error)), + q, SLOT(slotResult(GpgME::Error))); + + job = j; +} + +RevokeCertificationCommand::RevokeCertificationCommand(QAbstractItemView *v, KeyListController *c) + : Command(v, new Private(this, c)) +{ + d->init(); +} + +RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID &uid) + : Command(uid.parent(), new Private(this, nullptr)) +{ + std::vector(1, uid).swap(d->uids); + d->init(); +} + +RevokeCertificationCommand::~RevokeCertificationCommand() +{ + qCDebug(KLEOPATRA_LOG) << "~RevokeCertificationCommand()"; +} + +// static +bool RevokeCertificationCommand::isSupported() +{ +#ifdef GPGME_HAS_REVSIG + return engineInfo(GpgEngine).engineVersion() >= "2.2.24"; +#else + return false; +#endif +} + +void RevokeCertificationCommand::doStart() +{ + if (d->certificationTarget.isNull()) { + d->finished(); + return; + } + + for (const UserID &uid : qAsConst(d->uids)) + if (qstricmp(uid.parent().primaryFingerprint(), d->certificationTarget.primaryFingerprint()) != 0) { + qCWarning(KLEOPATRA_LOG) << "User-ID <-> Key mismatch!"; + d->finished(); + return; + } + + d->ensureDialogCreated(); + Q_ASSERT(d->dialog); + + d->dialog->setCertificateToRevoke(d->certificationTarget); + if (!d->uids.empty()) { + d->dialog->setSelectedUserIDs(d->uids); + } + d->dialog->show(); +} + +void RevokeCertificationCommand::doCancel() +{ + qCDebug(KLEOPATRA_LOG) << "RevokeCertificationCommand::doCancel()"; + if (d->job) { + d->job->slotCancel(); + } +} + +#undef d +#undef q + +#include "moc_revokecertificationcommand.cpp" diff --git a/src/commands/revokecertificationcommand.h b/src/commands/revokecertificationcommand.h new file mode 100644 index 000000000..8832cc9f1 --- /dev/null +++ b/src/commands/revokecertificationcommand.h @@ -0,0 +1,57 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + commands/revokecertificationcommand.h + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2020 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef __KLEOPATRA_COMMANDS_REVOKECERTIFICATIONCOMMAND_H__ +#define __KLEOPATRA_COMMANDS_REVOKECERTIFICATIONCOMMAND_H__ + +#include + +namespace GpgME +{ +class UserID; +} + +namespace Kleo +{ +namespace Commands +{ + +class RevokeCertificationCommand : public Command +{ + Q_OBJECT +public: + explicit RevokeCertificationCommand(QAbstractItemView *view, KeyListController *parent); + explicit RevokeCertificationCommand(const GpgME::UserID &uid); + ~RevokeCertificationCommand() override; + + /* reimp */ static Restrictions restrictions() + { + return OnlyOneKey | MustBeOpenPGP; + } + + static bool isSupported(); + +private: + void doStart() override; + void doCancel() override; + +private: + class Private; + inline Private *d_func(); + inline const Private *d_func() const; + Q_PRIVATE_SLOT(d_func(), void slotResult(GpgME::Error)) + Q_PRIVATE_SLOT(d_func(), void slotDialogAccepted()) + Q_PRIVATE_SLOT(d_func(), void slotDialogRejected()) +}; + +} // namespace Commands +} // namespace Kleo + +#endif // __KLEOPATRA_COMMANDS_REVOKECERTIFICATIONCOMMAND_H__ diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp index b4e8a46a8..6764a7680 100644 --- a/src/dialogs/certificatedetailswidget.cpp +++ b/src/dialogs/certificatedetailswidget.cpp @@ -1,645 +1,661 @@ /* SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "certificatedetailswidget.h" #include "ui_certificatedetailswidget.h" #include "kleopatra_debug.h" #include "exportdialog.h" #include "trustchainwidget.h" #include "subkeyswidget.h" #include "weboftrustdialog.h" #include "commands/changepassphrasecommand.h" #include "commands/changeexpirycommand.h" #include "commands/certifycertificatecommand.h" +#include "commands/revokecertificationcommand.h" #include "commands/adduseridcommand.h" #include "commands/genrevokecommand.h" #include "commands/detailscommand.h" #include "commands/dumpcertificatecommand.h" #include "utils/remarks.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 # define GPGME_HAS_REMARKS #endif #define HIDE_ROW(row) \ ui.row->setVisible(false); \ ui.row##Lbl->setVisible(false); Q_DECLARE_METATYPE(GpgME::UserID) using namespace Kleo; class CertificateDetailsWidget::Private { public: Private(CertificateDetailsWidget *parent) : updateInProgress (false), q(parent) {} void setupCommonProperties(); void setupPGPProperties(); void setupSMIMEProperties(); void revokeUID(const GpgME::UserID &uid); void genRevokeCert(); void certifyClicked(); void webOfTrustClicked(); void exportClicked(); void addUserID(); void changePassphrase(); void changeExpiration(); void keysMayHaveChanged(); void showTrustChainDialog(); void showMoreDetails(); void publishCertificate(); void userIDTableContextMenuRequested(const QPoint &p); QString tofuTooltipString(const GpgME::UserID &uid) const; void smimeLinkActivated(const QString &link); void setUpdatedKey(const GpgME::Key &key); void keyListDone(const GpgME::KeyListResult &, const std::vector &, const QString &, const GpgME::Error &); Ui::CertificateDetailsWidget ui; GpgME::Key key; bool updateInProgress; private: CertificateDetailsWidget *const q; }; void CertificateDetailsWidget::Private::setupCommonProperties() { // TODO: Enable once implemented HIDE_ROW(publishing) const bool hasSecret = key.hasSecret(); const bool isOpenPGP = key.protocol() == GpgME::OpenPGP; // TODO: Enable once implemented const bool canRevokeUID = false; // isOpenPGP && hasSecret ui.changePassphraseBtn->setVisible(hasSecret); ui.genRevokeBtn->setVisible(isOpenPGP && hasSecret); ui.certifyBtn->setVisible(isOpenPGP && !hasSecret); ui.changeExpirationBtn->setVisible(isOpenPGP && hasSecret); ui.addUserIDBtn->setVisible(hasSecret && isOpenPGP); ui.webOfTrustBtn->setVisible(isOpenPGP); ui.hboxLayout_1->addStretch(1); ui.validFrom->setText(Kleo::Formatting::creationDateString(key)); const QString expiry = Kleo::Formatting::expirationDateString(key); ui.expires->setText(expiry.isEmpty() ? i18nc("Expires", "never") : expiry); ui.type->setText(Kleo::Formatting::type(key)); ui.fingerprint->setText(Formatting::prettyID(key.primaryFingerprint())); if (Kleo::Formatting::complianceMode().isEmpty()) { HIDE_ROW(compliance) } else { ui.complianceLbl->setText(Kleo::Formatting::complianceStringForKey(key)); } ui.userIDTable->clear(); QStringList headers = { i18n("Email"), i18n("Name"), i18n("Trust Level"), i18n("Tags") }; if (canRevokeUID) { headers << QString(); } ui.userIDTable->setColumnCount(headers.count()); ui.userIDTable->setColumnWidth(0, 200); ui.userIDTable->setColumnWidth(1, 200); ui.userIDTable->setHeaderLabels(headers); const auto uids = key.userIDs(); for (unsigned int i = 0; i < uids.size(); ++i) { const auto &uid = uids[i]; auto item = new QTreeWidgetItem; const QString toolTip = tofuTooltipString(uid); item->setData(0, Qt::UserRole, QVariant::fromValue(uid)); auto pMail = Kleo::Formatting::prettyEMail(uid); auto pName = Kleo::Formatting::prettyName(uid); if (!isOpenPGP && pMail.isEmpty() && !pName.isEmpty()) { // S/MIME UserIDs are sometimes split, with one userID // containing the name another the Mail, we merge these // UID's into a single item. if (i + 1 < uids.size()) { pMail = Kleo::Formatting::prettyEMail(uids[i + 1]); // skip next uid ++i; } } if (!isOpenPGP && pMail.isEmpty() && pName.isEmpty()) { // S/MIME certificates sometimes contain urls where both // name and mail is empty. In that case we print whatever // the uid is as name. // // Can be ugly like (3:uri24:http://ca.intevation.org), but // this is better then showing an empty entry. pName = QString::fromLatin1(uid.id()); } item->setData(0, Qt::DisplayRole, pMail); item->setData(0, Qt::ToolTipRole, toolTip); item->setData(1, Qt::DisplayRole, pName); item->setData(1, Qt::ToolTipRole, toolTip); QIcon trustIcon; if (updateInProgress) { trustIcon = QIcon::fromTheme(QStringLiteral("emblem-question")); item->setData(2, Qt::DisplayRole, i18n("Updating...")); } else { switch (uid.validity()) { case GpgME::UserID::Unknown: case GpgME::UserID::Undefined: trustIcon = QIcon::fromTheme(QStringLiteral("emblem-question")); break; case GpgME::UserID::Never: trustIcon = QIcon::fromTheme(QStringLiteral("emblem-error")); break; case GpgME::UserID::Marginal: trustIcon = QIcon::fromTheme(QStringLiteral("emblem-warning")); break; case GpgME::UserID::Full: case GpgME::UserID::Ultimate: trustIcon = QIcon::fromTheme(QStringLiteral("emblem-success")); break; } item->setData(2, Qt::DisplayRole, Kleo::Formatting::validityShort(uid)); } item->setData(2, Qt::DecorationRole, trustIcon); item->setData(2, Qt::ToolTipRole, toolTip); GpgME::Error err; QStringList remarkList; #ifdef GPGME_HAS_REMARKS for (const auto &rem: uid.remarks(Remarks::remarkKeys(), err)) { remarkList << QString::fromStdString(rem); } #endif const auto remark = remarkList.join(QStringLiteral("; ")); item->setData(3, Qt::DisplayRole, remark); item->setData(3, Qt::ToolTipRole, toolTip); ui.userIDTable->addTopLevelItem(item); if (canRevokeUID) { auto button = new QPushButton; button->setIcon(QIcon::fromTheme(QStringLiteral("entry-delete"))); button->setToolTip(i18n("Revoke this User ID")); button->setMaximumWidth(32); QObject::connect(button, &QPushButton::clicked, q, [this, uid]() { revokeUID(uid); }); ui.userIDTable->setItemWidget(item, 4, button); } } if (!Remarks::remarksEnabled()) { ui.userIDTable->hideColumn(3); } } void CertificateDetailsWidget::Private::revokeUID(const GpgME::UserID &uid) { Q_UNUSED(uid); qCWarning(KLEOPATRA_LOG) << "Revoking UserID is not implemented. How did you even get here?!?!"; } void CertificateDetailsWidget::Private::changeExpiration() { auto cmd = new Kleo::Commands::ChangeExpiryCommand(key); QObject::connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, q, [this]() { ui.changeExpirationBtn->setEnabled(true); }); ui.changeExpirationBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::changePassphrase() { auto cmd = new Kleo::Commands::ChangePassphraseCommand(key); QObject::connect(cmd, &Kleo::Commands::ChangePassphraseCommand::finished, q, [this]() { ui.changePassphraseBtn->setEnabled(true); }); ui.changePassphraseBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::genRevokeCert() { auto cmd = new Kleo::Commands::GenRevokeCommand(key); QObject::connect(cmd, &Kleo::Commands::GenRevokeCommand::finished, q, [this]() { ui.genRevokeBtn->setEnabled(true); }); ui.genRevokeBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::certifyClicked() { auto cmd = new Kleo::Commands::CertifyCertificateCommand(key); QObject::connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished, q, [this]() { ui.certifyBtn->setEnabled(true); }); ui.certifyBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::webOfTrustClicked() { QScopedPointer dlg(new WebOfTrustDialog(q)); dlg->setKey(key); dlg->exec(); } void CertificateDetailsWidget::Private::exportClicked() { QScopedPointer dlg(new ExportDialog(q)); dlg->setKey(key); dlg->exec(); } void CertificateDetailsWidget::Private::addUserID() { auto cmd = new Kleo::Commands::AddUserIDCommand(key); QObject::connect(cmd, &Kleo::Commands::AddUserIDCommand::finished, q, [this]() { ui.addUserIDBtn->setEnabled(true); key.update(); q->setKey(key); }); ui.addUserIDBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::keysMayHaveChanged() { auto newKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint()); if (!newKey.isNull()) { setUpdatedKey(newKey); } } void CertificateDetailsWidget::Private::showTrustChainDialog() { QScopedPointer dlg(new TrustChainDialog(q)); dlg->setKey(key); dlg->exec(); } void CertificateDetailsWidget::Private::publishCertificate() { qCWarning(KLEOPATRA_LOG) << "publishCertificateis not implemented."; //TODO } void CertificateDetailsWidget::Private::userIDTableContextMenuRequested(const QPoint &p) { auto item = ui.userIDTable->itemAt(p); if (!item) { return; } const auto userID = item->data(0, Qt::UserRole).value(); QMenu *menu = new QMenu(q); menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-sign")), - i18n("Certify ..."), + i18n("Certify..."), q, [this, userID]() { auto cmd = new Kleo::Commands::CertifyCertificateCommand(userID); ui.userIDTable->setEnabled(false); connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished, q, [this]() { ui.userIDTable->setEnabled(true); // Trigger an update when done q->setKey(key); }); cmd->start(); }); + if (Kleo::Commands::RevokeCertificationCommand::isSupported()) { + menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), + i18n("Revoke Certification..."), + q, [this, userID]() { + auto cmd = new Kleo::Commands::RevokeCertificationCommand(userID); + ui.userIDTable->setEnabled(false); + connect(cmd, &Kleo::Commands::RevokeCertificationCommand::finished, + q, [this]() { + ui.userIDTable->setEnabled(true); + // Trigger an update when done + q->setKey(key); + }); + cmd->start(); + }); + } connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); menu->popup(ui.userIDTable->viewport()->mapToGlobal(p)); } void CertificateDetailsWidget::Private::showMoreDetails() { ui.moreDetailsBtn->setEnabled(false); if (key.protocol() == GpgME::CMS) { auto cmd = new Kleo::Commands::DumpCertificateCommand(key); connect(cmd, &Kleo::Commands::DumpCertificateCommand::finished, q, [this]() { ui.moreDetailsBtn->setEnabled(true); }); cmd->setUseDialog(true); cmd->start(); } else { QScopedPointer dlg(new SubKeysDialog(q)); dlg->setKey(key); dlg->exec(); ui.moreDetailsBtn->setEnabled(true); } } QString CertificateDetailsWidget::Private::tofuTooltipString(const GpgME::UserID &uid) const { const auto tofu = uid.tofuInfo(); if (tofu.isNull()) { return QString(); } QString html = QStringLiteral(""); const auto appendRow = [&html](const QString &lbl, const QString &val) { html += QStringLiteral("" "" "" "") .arg(lbl, val); }; const auto appendHeader = [this, &html](const QString &hdr) { html += QStringLiteral("") .arg(q->palette().highlight().color().name(), q->palette().highlightedText().color().name(), hdr); }; const auto dateTime = [](long ts) { QLocale l; return ts == 0 ? i18n("never") : l.toString(QDateTime::fromSecsSinceEpoch(ts), QLocale::ShortFormat); }; appendHeader(i18n("Signing")); appendRow(i18n("First message"), dateTime(tofu.signFirst())); appendRow(i18n("Last message"), dateTime(tofu.signLast())); appendRow(i18n("Message count"), QString::number(tofu.signCount())); appendHeader(i18n("Encryption")); appendRow(i18n("First message"), dateTime(tofu.encrFirst())); appendRow(i18n("Last message"), dateTime(tofu.encrLast())); appendRow(i18n("Message count"), QString::number(tofu.encrCount())); html += QStringLiteral("
%1:%2
%3
"); // Make sure the tooltip string is different for each UserID, even if the // data are the same, otherwise the tooltip is not updated and moved when // user moves mouse from one row to another. html += QStringLiteral("").arg(QString::fromUtf8(uid.id())); return html; } void CertificateDetailsWidget::Private::setupPGPProperties() { HIDE_ROW(smimeOwner) HIDE_ROW(smimeIssuer) ui.smimeRelatedAddresses->setVisible(false); ui.trustChainDetailsBtn->setVisible(false); ui.userIDTable->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.userIDTable, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) { userIDTableContextMenuRequested(p); }); } static QString formatDNToolTip(const Kleo::DN &dn) { QString html = QStringLiteral(""); const auto appendRow = [&html, dn](const QString &lbl, const QString &attr) { const QString val = dn[attr]; if (!val.isEmpty()) { html += QStringLiteral( "" "" "").arg(lbl, val); } }; appendRow(i18n("Common Name"), QStringLiteral("CN")); appendRow(i18n("Organization"), QStringLiteral("O")); appendRow(i18n("Street"), QStringLiteral("STREET")); appendRow(i18n("City"), QStringLiteral("L")); appendRow(i18n("State"), QStringLiteral("ST")); appendRow(i18n("Country"), QStringLiteral("C")); html += QStringLiteral("
%1:%2
"); return html; } void CertificateDetailsWidget::Private::setupSMIMEProperties() { HIDE_ROW(publishing) const auto ownerId = key.userID(0); const Kleo::DN dn(ownerId.id()); const QString cn = dn[QStringLiteral("CN")]; const QString o = dn[QStringLiteral("O")]; const QString dnEmail = dn[QStringLiteral("EMAIL")]; const QString name = cn.isEmpty() ? dnEmail : cn; QString owner; if (name.isEmpty()) { owner = dn.dn(); } else if (o.isEmpty()) { owner = name; } else { owner = i18nc(" of ", "%1 of %2", name, o); } ui.smimeOwner->setText(owner); ui.smimeOwner->setTextInteractionFlags(Qt::TextBrowserInteraction); const Kleo::DN issuerDN(key.issuerName()); const QString issuerCN = issuerDN[QStringLiteral("CN")]; const QString issuer = issuerCN.isEmpty() ? QString::fromUtf8(key.issuerName()) : issuerCN; ui.smimeIssuer->setText(QStringLiteral("%1").arg(issuer)); ui.smimeIssuer->setToolTip(formatDNToolTip(issuerDN)); ui.smimeOwner->setToolTip(formatDNToolTip(dn)); } void CertificateDetailsWidget::Private::smimeLinkActivated(const QString &link) { if (link == QLatin1String("#issuerDetails")) { const auto parentKey = KeyCache::instance()->findIssuers(key, KeyCache::NoOption); if (!parentKey.size()) { return; } auto cmd = new Kleo::Commands::DetailsCommand(parentKey[0], nullptr); cmd->setParentWidget(q); cmd->start(); return; } qCWarning(KLEOPATRA_LOG) << "Unknown link activated:" << link; } CertificateDetailsWidget::CertificateDetailsWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { d->ui.setupUi(this); connect(d->ui.addUserIDBtn, &QPushButton::clicked, this, [this]() { d->addUserID(); }); connect(d->ui.changePassphraseBtn, &QPushButton::clicked, this, [this]() { d->changePassphrase(); }); connect(d->ui.genRevokeBtn, &QPushButton::clicked, this, [this]() { d->genRevokeCert(); }); connect(d->ui.changeExpirationBtn, &QPushButton::clicked, this, [this]() { d->changeExpiration(); }); connect(d->ui.smimeOwner, &QLabel::linkActivated, this, [this](const QString &link) { d->smimeLinkActivated(link); }); connect(d->ui.smimeIssuer, &QLabel::linkActivated, this, [this](const QString &link) { d->smimeLinkActivated(link); }); connect(d->ui.trustChainDetailsBtn, &QPushButton::pressed, this, [this]() { d->showTrustChainDialog(); }); connect(d->ui.moreDetailsBtn, &QPushButton::pressed, this, [this]() { d->showMoreDetails(); }); connect(d->ui.publishing, &QPushButton::pressed, this, [this]() { d->publishCertificate(); }); connect(d->ui.certifyBtn, &QPushButton::clicked, this, [this]() { d->certifyClicked(); }); connect(d->ui.webOfTrustBtn, &QPushButton::clicked, this, [this]() { d->webOfTrustClicked(); }); connect(d->ui.exportBtn, &QPushButton::clicked, this, [this]() { d->exportClicked(); }); connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this]() { d->keysMayHaveChanged(); }); } CertificateDetailsWidget::~CertificateDetailsWidget() { } void CertificateDetailsWidget::Private::keyListDone(const GpgME::KeyListResult &, const std::vector &keys, const QString &, const GpgME::Error &) { updateInProgress = false; if (keys.size() != 1) { qCWarning(KLEOPATRA_LOG) << "Invalid keylist result in update."; return; } // As we listen for keysmayhavechanged we get the update // after updating the keycache. KeyCache::mutableInstance()->insert(keys); } void CertificateDetailsWidget::Private::setUpdatedKey(const GpgME::Key &k) { key = k; setupCommonProperties(); if (key.protocol() == GpgME::OpenPGP) { setupPGPProperties(); } else { setupSMIMEProperties(); } } void CertificateDetailsWidget::setKey(const GpgME::Key &key) { if (key.protocol() == GpgME::CMS) { // For everything but S/MIME this should be quick // and we don't need to show another status. d->updateInProgress = true; } d->setUpdatedKey(key); // Run a keylistjob with full details (TOFU / Validate) QGpgME::KeyListJob *job = key.protocol() == GpgME::OpenPGP ? QGpgME::openpgp()->keyListJob(false, true, true) : QGpgME::smime()->keyListJob(false, true, true); auto ctx = QGpgME::Job::context(job); ctx->addKeyListMode(GpgME::WithTofu); ctx->addKeyListMode(GpgME::Signatures); ctx->addKeyListMode(GpgME::SignatureNotations); // Windows QGpgME new style connect problem makes this necessary. connect(job, SIGNAL(result(GpgME::KeyListResult,std::vector,QString,GpgME::Error)), this, SLOT(keyListDone(GpgME::KeyListResult,std::vector,QString,GpgME::Error))); job->start(QStringList() << QLatin1String(key.primaryFingerprint()), key.hasSecret()); } GpgME::Key CertificateDetailsWidget::key() const { return d->key; } CertificateDetailsDialog::CertificateDetailsDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18nc("@title:window", "Certificate Details")); auto l = new QVBoxLayout(this); l->addWidget(new CertificateDetailsWidget(this)); auto bbox = new QDialogButtonBox(this); auto btn = bbox->addButton(QDialogButtonBox::Close); connect(btn, &QPushButton::pressed, this, &QDialog::accept); l->addWidget(bbox); readConfig(); } CertificateDetailsDialog::~CertificateDetailsDialog() { writeConfig(); } void CertificateDetailsDialog::readConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "CertificateDetailsDialog"); const QSize size = dialog.readEntry("Size", QSize(730, 280)); if (size.isValid()) { resize(size); } } void CertificateDetailsDialog::writeConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "CertificateDetailsDialog"); dialog.writeEntry("Size", size()); dialog.sync(); } void CertificateDetailsDialog::setKey(const GpgME::Key &key) { auto w = findChild(); Q_ASSERT(w); w->setKey(key); } GpgME::Key CertificateDetailsDialog::key() const { auto w = findChild(); Q_ASSERT(w); return w->key(); } #include "moc_certificatedetailswidget.cpp" diff --git a/src/dialogs/revokecertificationdialog.cpp b/src/dialogs/revokecertificationdialog.cpp new file mode 100644 index 000000000..24fbff735 --- /dev/null +++ b/src/dialogs/revokecertificationdialog.cpp @@ -0,0 +1,138 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + dialogs/revokecertificationdialog.cpp + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB + SPDX-FileCopyrightText: 2020 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include "revokecertificationdialog.h" + +#include "revokecertificationwidget.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "kleopatra_debug.h" + +using namespace GpgME; +using namespace Kleo; + +class RevokeCertificationDialog::Private +{ + friend class ::Kleo::RevokeCertificationDialog; + RevokeCertificationDialog *const q; +public: + explicit Private(RevokeCertificationDialog *qq); + ~Private(); + +private: + void saveGeometry(); + void restoreGeometry(const QSize &defaultSize); + +private: + RevokeCertificationWidget *mainWidget = nullptr; +}; + + +RevokeCertificationDialog::Private::Private(RevokeCertificationDialog *qq) + : q(qq) +{ +} + +RevokeCertificationDialog::Private::~Private() +{ +} + +void RevokeCertificationDialog::Private::saveGeometry() +{ + KConfigGroup cfgGroup(KSharedConfig::openConfig(), "RevokeCertificationDialog"); + cfgGroup.writeEntry("geometry", q->saveGeometry()); + cfgGroup.sync(); +} + +void RevokeCertificationDialog::Private::restoreGeometry(const QSize &defaultSize) +{ + KConfigGroup cfgGroup(KSharedConfig::openConfig(), "RevokeCertificationDialog"); + const QByteArray geometry = cfgGroup.readEntry("geometry", QByteArray()); + if (!geometry.isEmpty()) { + q->restoreGeometry(geometry); + } else { + q->resize(defaultSize); + } +} + +RevokeCertificationDialog::RevokeCertificationDialog(QWidget *p, Qt::WindowFlags f) + : QDialog(p, f) + , d(new Private(this)) +{ + setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); + + auto mainLay = new QVBoxLayout(this); + d->mainWidget = new RevokeCertificationWidget(this); + mainLay->addWidget(d->mainWidget); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(); + mainLay->addWidget(buttonBox); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel | + QDialogButtonBox::Ok); + KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok()); + KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); + buttonBox->button(QDialogButtonBox::Ok)->setText(i18n("Revoke Certification")); + connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, + this, [this] () { + d->mainWidget->saveConfig(); + accept(); + }); + connect(buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, + this, [this] () { close(); }); + + d->restoreGeometry(QSize(640, 480)); +} + +RevokeCertificationDialog::~RevokeCertificationDialog() +{ + d->saveGeometry(); +} + +void RevokeCertificationDialog::setCertificateToRevoke(const Key &key) +{ + setWindowTitle(i18nc("@title:window arg is name, email of certificate holder", + "Revoke Certification: %1", Formatting::prettyName(key))); + d->mainWidget->setTarget(key); +} + +void RevokeCertificationDialog::setSelectedUserIDs(const std::vector &uids) +{ + d->mainWidget->setSelectUserIDs(uids); +} + +std::vector RevokeCertificationDialog::selectedUserIDs() const +{ + return d->mainWidget->selectedUserIDs(); +} + +Key RevokeCertificationDialog::selectedCertificationKey() const +{ + return d->mainWidget->certificationKey(); +} + +bool RevokeCertificationDialog::sendToServer() const +{ + return d->mainWidget->publishSelected(); +} diff --git a/src/dialogs/revokecertificationdialog.h b/src/dialogs/revokecertificationdialog.h new file mode 100644 index 000000000..1fdf1203f --- /dev/null +++ b/src/dialogs/revokecertificationdialog.h @@ -0,0 +1,49 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + dialogs/revokecertificationdialog.h + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB + SPDX-FileCopyrightText: 2020 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONDIALOG_H__ +#define __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONDIALOG_H__ + +#include + +namespace GpgME +{ +class Key; +class UserID; +} + +namespace Kleo +{ + +class RevokeCertificationDialog : public QDialog +{ + Q_OBJECT +public: + explicit RevokeCertificationDialog(QWidget *parent = nullptr, Qt::WindowFlags f = {}); + ~RevokeCertificationDialog() override; + + void setCertificateToRevoke(const GpgME::Key &key); + + void setSelectedUserIDs(const std::vector &uids); + std::vector selectedUserIDs() const; + + GpgME::Key selectedCertificationKey() const; + + bool sendToServer() const; + +private: + class Private; + const std::unique_ptr d; +}; + +} // namespace Kleo + +#endif /* __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONDIALOG_H__ */ diff --git a/src/dialogs/revokecertificationwidget.cpp b/src/dialogs/revokecertificationwidget.cpp new file mode 100644 index 000000000..782ee31c7 --- /dev/null +++ b/src/dialogs/revokecertificationwidget.cpp @@ -0,0 +1,261 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + dialogs/revokecertificationwidget.cpp + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2020 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "revokecertificationwidget.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "kleopatra_debug.h" + +using namespace Kleo; + +namespace { + +class CertificationKeyFilter: public DefaultKeyFilter +{ +public: + CertificationKeyFilter(const GpgME::Key &certificationTarget); + + bool matches(const GpgME::Key &key, Kleo::KeyFilter::MatchContexts contexts) const override; + +private: + GpgME::Key mCertificationTarget; // the key to certify or to revoke the certification of +}; + +CertificationKeyFilter::CertificationKeyFilter(const GpgME::Key &certificationTarget) + : DefaultKeyFilter() + , mCertificationTarget(certificationTarget) +{ + setIsOpenPGP(DefaultKeyFilter::Set); + setHasSecret(DefaultKeyFilter::Set); + setCanCertify(DefaultKeyFilter::Set); + setRevoked(DefaultKeyFilter::NotSet); + setExpired(DefaultKeyFilter::NotSet); + setInvalid(DefaultKeyFilter::NotSet); + setDisabled(DefaultKeyFilter::NotSet); +} + +bool CertificationKeyFilter::matches(const GpgME::Key &key, Kleo::KeyFilter::MatchContexts contexts) const +{ + if (!(availableMatchContexts() & contexts)) { + return false; + } + // exclude certification target from list of certification keys + if (qstrcmp(key.primaryFingerprint(), mCertificationTarget.primaryFingerprint()) == 0) { + return false; + } + return DefaultKeyFilter::matches(key, contexts); +} + +static bool uidsAreEqual(const GpgME::UserID &lhs, const GpgME::UserID &rhs) +{ + // use uidhash if available + if (lhs.uidhash() && rhs.uidhash()) { + return strcmp(lhs.uidhash(), rhs.uidhash()) == 0; + } + // compare actual user ID string and primary key; this is not unique, but it's all we can do if uidhash is missing + return qstrcmp(lhs.id(), rhs.id()) == 0 + && qstrcmp(lhs.parent().primaryFingerprint(), rhs.parent().primaryFingerprint()) == 0; +} + +class UserIDModel : public QStandardItemModel +{ + Q_OBJECT +public: + explicit UserIDModel(QObject *parent = nullptr) : QStandardItemModel(parent) + { + } + + void setKey(const GpgME::Key &key) + { + mKey = key; + clear(); + const std::vector uids = key.userIDs(); + for (const auto &uid : uids) { + QStandardItem *const item = new QStandardItem; + item->setText(Formatting::prettyUserID(uid)); + item->setCheckable(true); + item->setEditable(false); + item->setCheckState(Qt::Checked); + appendRow(item); + } + } + + void setCheckedUserIDs(const std::vector &checkedUids) + { + const auto keyUids = mKey.userIDs(); + Q_ASSERT(rowCount() == static_cast(keyUids.size())); + + for (int i = 0; i < rowCount(); ++i) { + const auto &keyUid = keyUids[i]; + const bool uidIsChecked = std::find_if(checkedUids.cbegin(), checkedUids.cend(), + [keyUid](const GpgME::UserID &checkedUid) { return uidsAreEqual(keyUid, checkedUid); }) != checkedUids.cend(); + item(i)->setCheckState(uidIsChecked ? Qt::Checked : Qt::Unchecked); + } + } + + std::vector checkedUserIDs() const + { + const auto keyUids = mKey.userIDs(); + Q_ASSERT(rowCount() == static_cast(keyUids.size())); + + std::vector checkedUids; + for (int i = 0; i < rowCount(); ++i) { + if (item(i)->checkState() == Qt::Checked) { + checkedUids.push_back(keyUids[i]); + } + } + return checkedUids; + } + +private: + GpgME::Key mKey; +}; + +} // unnamed namespace + +class RevokeCertificationWidget::Private +{ + friend class ::Kleo::RevokeCertificationWidget; + RevokeCertificationWidget *const q; + + QLabel *mFprLabel; + KeySelectionCombo *mCertificationKeySelect; + QCheckBox *mPublishCB; + + UserIDModel mUserIDModel; + GpgME::Key mTarget; + +public: + Private(RevokeCertificationWidget *qq) + : q(qq) + , mFprLabel(new QLabel) + , mCertificationKeySelect(new KeySelectionCombo(/* secretOnly = */ true)) + , mPublishCB(new QCheckBox) + { + QVBoxLayout *mainLayout = new QVBoxLayout(q); + mainLayout->addWidget(mFprLabel); + + auto certKeyLayout = new QHBoxLayout; + { + auto label = new QLabel(i18n("Certification key:")); + label->setToolTip(i18n("The key whose certifications shall be revoke")); + certKeyLayout->addWidget(label); + } + certKeyLayout->addWidget(mCertificationKeySelect, 1); + mainLayout->addLayout(certKeyLayout); + + auto splitLine = new QFrame; + splitLine->setFrameShape(QFrame::HLine); + splitLine->setFrameShadow(QFrame::Sunken); + splitLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + mainLayout->addWidget(splitLine); + + auto listView = new QListView; + listView->setModel(&mUserIDModel); + mainLayout->addWidget(listView, 1); + + mPublishCB = new QCheckBox(i18n("Publish revocations on keyserver")); + mainLayout->addWidget(mPublishCB); + + loadConfig(); + } + + ~Private() + { + } + + void saveConfig() + { + KConfigGroup conf(KSharedConfig::openConfig(), "RevokeCertificationSettings"); + const auto certificationKey = mCertificationKeySelect->currentKey(); + if (!certificationKey.isNull()) { + conf.writeEntry("LastKey", certificationKey.primaryFingerprint()); + } + conf.writeEntry("PublishCheckState", mPublishCB->isChecked()); + } + + void loadConfig() + { + const KConfigGroup conf(KSharedConfig::openConfig(), "RevokeCertificationSettings"); + mCertificationKeySelect->setDefaultKey(conf.readEntry("LastKey", QString())); + mPublishCB->setChecked(conf.readEntry("PublishCheckState", false)); + } +}; + +RevokeCertificationWidget::RevokeCertificationWidget(QWidget *parent) + : QWidget(parent) + , d(new Private(this)) +{ +} + +RevokeCertificationWidget::~RevokeCertificationWidget() +{ +} + +void RevokeCertificationWidget::setTarget(const GpgME::Key &key) +{ + d->mTarget = key; + d->mFprLabel->setText(i18n("Fingerprint: %1", + Formatting::prettyID(d->mTarget.primaryFingerprint())) + QStringLiteral("
") + + i18n("Only the fingerprint clearly identifies the key and its owner.")); + d->mCertificationKeySelect->setKeyFilter(std::shared_ptr(new CertificationKeyFilter(d->mTarget))); + d->mUserIDModel.setKey(d->mTarget); +} + +GpgME::Key RevokeCertificationWidget::target() const +{ + return d->mTarget; +} + +void RevokeCertificationWidget::setSelectUserIDs(const std::vector &uids) +{ + d->mUserIDModel.setCheckedUserIDs(uids); +} + +std::vector RevokeCertificationWidget::selectedUserIDs() const +{ + return d->mUserIDModel.checkedUserIDs(); +} + +GpgME::Key RevokeCertificationWidget::certificationKey() const +{ + return d->mCertificationKeySelect->currentKey(); +} + +bool RevokeCertificationWidget::publishSelected() const +{ + return d->mPublishCB->isChecked(); +} + +void Kleo::RevokeCertificationWidget::saveConfig() const +{ + d->saveConfig(); +} + +// for UserIDModel +#include "revokecertificationwidget.moc" diff --git a/src/dialogs/revokecertificationwidget.h b/src/dialogs/revokecertificationwidget.h new file mode 100644 index 000000000..0866bfc17 --- /dev/null +++ b/src/dialogs/revokecertificationwidget.h @@ -0,0 +1,61 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + dialogs/revokecertificationwidget.h + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2020 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONWIDGET_H__ +#define __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONWIDGET_H__ + +#include + +#include + +namespace GpgME +{ +class Key; +class UserID; +} + +namespace Kleo +{ +/** Widget for revoking OpenPGP certifications. */ +class RevokeCertificationWidget : public QWidget +{ + Q_OBJECT +public: + explicit RevokeCertificationWidget(QWidget *parent = nullptr); + ~RevokeCertificationWidget() override; + + /* Set the key to revoke certifications of */ + void setTarget(const GpgME::Key &key); + + /* Get the key to revoke certifications of */ + GpgME::Key target() const; + + /* Select specific user ids. Default: all */ + void setSelectUserIDs(const std::vector &uids); + + /* The user ids whose certifications shall be revoked */ + std::vector selectedUserIDs() const; + + /* The selected certification key */ + GpgME::Key certificationKey() const; + + /* Whether the revocations shall be published */ + bool publishSelected() const; + + void saveConfig() const; + +private: + class Private; + const std::unique_ptr d; +}; + +} // namespace Kleo + +#endif // __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONWIDGET_H__ diff --git a/src/kleopatra.rc b/src/kleopatra.rc index 24d9509ca..0ed0cf577 100644 --- a/src/kleopatra.rc +++ b/src/kleopatra.rc @@ -1,128 +1,131 @@ &File &View &Certificates + + &Tools &Settings &Window &Help Main Toolbar &Certificates + diff --git a/src/view/keylistcontroller.cpp b/src/view/keylistcontroller.cpp index 12c5f2c99..30af228f1 100644 --- a/src/view/keylistcontroller.cpp +++ b/src/view/keylistcontroller.cpp @@ -1,801 +1,810 @@ /* -*- mode: c++; c-basic-offset:4 -*- controllers/keylistcontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keylistcontroller.h" #include "tabwidget.h" #include #include #include "tooltippreferences.h" #include "kleopatra_debug.h" #include "commands/exportcertificatecommand.h" #include "commands/exportopenpgpcertstoservercommand.h" #include "commands/exportsecretkeycommand.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/changepassphrasecommand.h" #include "commands/lookupcertificatescommand.h" #include "commands/reloadkeyscommand.h" #include "commands/refreshx509certscommand.h" #include "commands/refreshopenpgpcertscommand.h" #include "commands/detailscommand.h" #include "commands/deletecertificatescommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/signencryptfilescommand.h" #include "commands/signencryptfoldercommand.h" #include "commands/clearcrlcachecommand.h" #include "commands/dumpcrlcachecommand.h" #include "commands/dumpcertificatecommand.h" #include "commands/importcrlcommand.h" #include "commands/changeexpirycommand.h" #include "commands/changeownertrustcommand.h" #include "commands/changeroottrustcommand.h" #include "commands/certifycertificatecommand.h" +#include "commands/revokecertificationcommand.h" #include "commands/adduseridcommand.h" #include "commands/newcertificatecommand.h" #include "commands/checksumverifyfilescommand.h" #include "commands/checksumcreatefilescommand.h" #include "commands/exportpaperkeycommand.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; using namespace GpgME; class KeyListController::Private { friend class ::Kleo::KeyListController; KeyListController *const q; public: explicit Private(KeyListController *qq); ~Private(); void connectView(QAbstractItemView *view); void connectCommand(Command *cmd); void connectTabWidget(); void disconnectTabWidget(); void addCommand(Command *cmd) { connectCommand(cmd); commands.insert(std::lower_bound(commands.begin(), commands.end(), cmd), cmd); } void addView(QAbstractItemView *view) { connectView(view); views.insert(std::lower_bound(views.begin(), views.end(), view), view); } void removeView(QAbstractItemView *view) { view->disconnect(q); view->selectionModel()->disconnect(q); views.erase(std::remove(views.begin(), views.end(), view), views.end()); } public: void slotDestroyed(QObject *o) { qCDebug(KLEOPATRA_LOG) << (void *)o; views.erase(std::remove(views.begin(), views.end(), o), views.end()); commands.erase(std::remove(commands.begin(), commands.end(), o), commands.end()); } void slotDoubleClicked(const QModelIndex &idx); void slotActivated(const QModelIndex &idx); void slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_); void slotContextMenu(const QPoint &pos); void slotCommandFinished(); void slotAddKey(const Key &key); void slotAboutToRemoveKey(const Key &key); void slotProgress(const QString &what, int current, int total) { Q_EMIT q->progress(current, total); if (!what.isEmpty()) { Q_EMIT q->message(what); } } void slotActionTriggered(); void slotCurrentViewChanged(QAbstractItemView *view) { if (view && !std::binary_search(views.cbegin(), views.cend(), view)) { qCDebug(KLEOPATRA_LOG) << "you need to register view" << view << "before trying to set it as the current view!"; addView(view); } currentView = view; q->enableDisableActions(view ? view->selectionModel() : nullptr); } private: int toolTipOptions() const; private: static Command::Restrictions calculateRestrictionsMask(const QItemSelectionModel *sm); private: struct action_item { QPointer action; Command::Restrictions restrictions; Command *(*createCommand)(QAbstractItemView *, KeyListController *); }; std::vector actions; std::vector views; std::vector commands; QPointer parentWidget; QPointer tabWidget; QPointer currentView; QPointer flatModel, hierarchicalModel; }; KeyListController::Private::Private(KeyListController *qq) : q(qq), actions(), views(), commands(), parentWidget(), tabWidget(), flatModel(), hierarchicalModel() { connect(KeyCache::mutableInstance().get(), SIGNAL(added(GpgME::Key)), q, SLOT(slotAddKey(GpgME::Key))); connect(KeyCache::mutableInstance().get(), SIGNAL(aboutToRemove(GpgME::Key)), q, SLOT(slotAboutToRemoveKey(GpgME::Key))); } KeyListController::Private::~Private() {} KeyListController::KeyListController(QObject *p) : QObject(p), d(new Private(this)) { } KeyListController::~KeyListController() {} void KeyListController::Private::slotAddKey(const Key &key) { // ### make model act on keycache directly... if (flatModel) { flatModel->addKey(key); } if (hierarchicalModel) { hierarchicalModel->addKey(key); } } void KeyListController::Private::slotAboutToRemoveKey(const Key &key) { // ### make model act on keycache directly... if (flatModel) { flatModel->removeKey(key); } if (hierarchicalModel) { hierarchicalModel->removeKey(key); } } void KeyListController::addView(QAbstractItemView *view) { if (!view || std::binary_search(d->views.cbegin(), d->views.cend(), view)) { return; } d->addView(view); } void KeyListController::removeView(QAbstractItemView *view) { if (!view || !std::binary_search(d->views.cbegin(), d->views.cend(), view)) { return; } d->removeView(view); } void KeyListController::setCurrentView(QAbstractItemView *view) { d->slotCurrentViewChanged(view); } std::vector KeyListController::views() const { return d->views; } void KeyListController::setFlatModel(AbstractKeyListModel *model) { if (model == d->flatModel) { return; } d->flatModel = model; if (model) { model->clear(); if (KeyCache::instance()->initialized()) { model->addKeys(KeyCache::instance()->keys()); } model->setToolTipOptions(d->toolTipOptions()); } } void KeyListController::setHierarchicalModel(AbstractKeyListModel *model) { if (model == d->hierarchicalModel) { return; } d->hierarchicalModel = model; if (model) { model->clear(); if (KeyCache::instance()->initialized()) { model->addKeys(KeyCache::instance()->keys()); } model->setToolTipOptions(d->toolTipOptions()); } } void KeyListController::setTabWidget(TabWidget *tabWidget) { if (tabWidget == d->tabWidget) { return; } d->disconnectTabWidget(); d->tabWidget = tabWidget; d->connectTabWidget(); d->slotCurrentViewChanged(tabWidget ? tabWidget->currentView() : nullptr); } void KeyListController::setParentWidget(QWidget *parent) { d->parentWidget = parent; } QWidget *KeyListController::parentWidget() const { return d->parentWidget; } static const struct { const char *signal; const char *slot; } tabs2controller[] = { { SIGNAL(viewAdded(QAbstractItemView*)), SLOT(addView(QAbstractItemView*)) }, { SIGNAL(viewAboutToBeRemoved(QAbstractItemView*)), SLOT(removeView(QAbstractItemView*)) }, { SIGNAL(currentViewChanged(QAbstractItemView*)), SLOT(slotCurrentViewChanged(QAbstractItemView*)) }, }; static const unsigned int numTabs2Controller = sizeof tabs2controller / sizeof * tabs2controller; void KeyListController::Private::connectTabWidget() { if (!tabWidget) { return; } const auto views = tabWidget->views(); std::for_each(views.cbegin(), views.cend(), [this](QAbstractItemView *view) { addView(view); }); for (unsigned int i = 0; i < numTabs2Controller; ++i) { connect(tabWidget, tabs2controller[i].signal, q, tabs2controller[i].slot); } } void KeyListController::Private::disconnectTabWidget() { if (!tabWidget) { return; } for (unsigned int i = 0; i < numTabs2Controller; ++i) { disconnect(tabWidget, tabs2controller[i].signal, q, tabs2controller[i].slot); } const auto views = tabWidget->views(); std::for_each(views.cbegin(), views.cend(), [this](QAbstractItemView *view) { removeView(view); }); } AbstractKeyListModel *KeyListController::flatModel() const { return d->flatModel; } AbstractKeyListModel *KeyListController::hierarchicalModel() const { return d->hierarchicalModel; } QAbstractItemView *KeyListController::currentView() const { return d->currentView; } TabWidget *KeyListController::tabWidget() const { return d->tabWidget; } void KeyListController::createActions(KActionCollection *coll) { const action_data action_data[] = { // File menu { "file_new_certificate", i18n("New Key Pair..."), QString(), "view-certificate-add", nullptr, nullptr, QStringLiteral("Ctrl+N"), false, true }, { "file_export_certificates", i18n("Export..."), i18n("Export the selected certificate (public key) to a file"), "view-certificate-export", nullptr, nullptr, QStringLiteral("Ctrl+E"), false, true }, { "file_export_certificates_to_server", i18n("Publish on Server..."), i18n("Publish the selected certificate (public key) on a public keyserver"), "view-certificate-export-server", nullptr, nullptr, QStringLiteral("Ctrl+Shift+E"), false, true }, { "file_export_secret_keys", i18n("Backup Secret Keys..."), QString(), "view-certificate-export-secret", nullptr, nullptr, QString(), false, true }, { "file_export_paper_key", i18n("Print Secret Key..."), QString(), "document-print", nullptr, nullptr, QString(), false, true }, { "file_lookup_certificates", i18n("Lookup on Server..."), i18n("Search for certificates online using a public keyserver"), "edit-find", nullptr, nullptr, QStringLiteral("Shift+Ctrl+I"), false, true }, { "file_import_certificates", i18n("Import..."), i18n("Import a certificate from a file"), "view-certificate-import", nullptr, nullptr, QStringLiteral("Ctrl+I"), false, true }, { "file_decrypt_verify_files", i18n("Decrypt/Verify..."), i18n("Decrypt and/or verify files"), "document-edit-decrypt-verify", nullptr, nullptr, QString(), false, true }, { "file_sign_encrypt_files", i18n("Sign/Encrypt..."), i18n("Encrypt and/or sign files"), "document-edit-sign-encrypt", nullptr, nullptr, QString(), false, true }, { "file_sign_encrypt_folder", i18n("Sign/Encrypt Folder..."), i18n("Encrypt and/or sign folders"), nullptr/*"folder-edit-sign-encrypt"*/, nullptr, nullptr, QString(), false, true }, { "file_checksum_create_files", i18n("Create Checksum Files..."), QString(), nullptr/*"document-checksum-create"*/, nullptr, nullptr, QString(), false, true }, { "file_checksum_verify_files", i18n("Verify Checksum Files..."), QString(), nullptr/*"document-checksum-verify"*/, nullptr, nullptr, QString(), false, true }, // View menu { "view_redisplay", i18n("Redisplay"), QString(), "view-refresh", nullptr, nullptr, QStringLiteral("F5"), false, true }, { "view_stop_operations", i18n("Stop Operation"), QString(), "process-stop", this, SLOT(cancelCommands()), QStringLiteral("Escape"), false, false }, { "view_certificate_details", i18n("Details"), QString(), "dialog-information", nullptr, nullptr, QString(), false, true }, // Certificate menu { "certificates_delete", i18n("Delete"), i18n("Delete selected certificates"), "edit-delete", nullptr, nullptr, QStringLiteral("Delete"), false, true }, { "certificates_certify_certificate", i18n("Certify..."), i18n("Certify the validity of the selected certificate"), "view-certificate-sign", nullptr, nullptr, QString(), false, true }, + { + "certificates_revoke_certification", i18n("Revoke Certification..."), i18n("Revoke the certification of the selected certificate"), + "view-certificate-revoke", nullptr, nullptr, QString(), false, true + }, { "certificates_change_expiry", i18n("Change Expiry Date..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_change_owner_trust", i18n("Change Certification Trust..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_trust_root", i18n("Trust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_distrust_root", i18n("Distrust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_change_passphrase", i18n("Change Passphrase..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_add_userid", i18n("Add User-ID..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_dump_certificate", i18n("Technical Details"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, // Tools menu { "tools_refresh_x509_certificates", i18n("Refresh S/MIME Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), false, true }, { "tools_refresh_openpgp_certificates", i18n("Refresh OpenPGP Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), false, true }, { "crl_clear_crl_cache", i18n("Clear CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "crl_dump_crl_cache", i18n("Dump CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "crl_import_crl", i18n("Import CRL From File..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, // Window menu // (come from TabWidget) // Help menu // (come from MainWindow) }; make_actions_from_data(action_data, coll); if (QAction *action = coll->action(QStringLiteral("view_stop_operations"))) { connect(this, &KeyListController::commandsExecuting, action, &QAction::setEnabled); } // ### somehow make this better... registerActionForCommand(coll->action(QStringLiteral("file_new_certificate"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_lookup_certificates"))); registerActionForCommand(coll->action(QStringLiteral("file_import_certificates"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_export_certificates"))); registerActionForCommand(coll->action(QStringLiteral("file_export_secret_keys"))); registerActionForCommand(coll->action(QStringLiteral("file_export_paper_key"))); registerActionForCommand(coll->action(QStringLiteral("file_export_certificates_to_server"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_decrypt_verify_files"))); registerActionForCommand(coll->action(QStringLiteral("file_sign_encrypt_files"))); registerActionForCommand(coll->action(QStringLiteral("file_sign_encrypt_folder"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_checksum_create_files"))); registerActionForCommand(coll->action(QStringLiteral("file_checksum_verify_files"))); registerActionForCommand(coll->action(QStringLiteral("view_redisplay"))); //coll->action( "view_stop_operations" ) <-- already dealt with in make_actions_from_data() registerActionForCommand(coll->action(QStringLiteral("view_certificate_details"))); registerActionForCommand(coll->action(QStringLiteral("certificates_change_owner_trust"))); registerActionForCommand(coll->action(QStringLiteral("certificates_trust_root"))); registerActionForCommand(coll->action(QStringLiteral("certificates_distrust_root"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_certify_certificate"))); + if (RevokeCertificationCommand::isSupported()) { + registerActionForCommand(coll->action(QStringLiteral("certificates_revoke_certification"))); + } + //--- registerActionForCommand(coll->action(QStringLiteral("certificates_change_expiry"))); registerActionForCommand(coll->action(QStringLiteral("certificates_change_passphrase"))); registerActionForCommand(coll->action(QStringLiteral("certificates_add_userid"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_delete"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_dump_certificate"))); registerActionForCommand(coll->action(QStringLiteral("tools_refresh_x509_certificates"))); registerActionForCommand(coll->action(QStringLiteral("tools_refresh_openpgp_certificates"))); //--- registerActionForCommand(coll->action(QStringLiteral("crl_import_crl"))); //--- registerActionForCommand(coll->action(QStringLiteral("crl_clear_crl_cache"))); registerActionForCommand(coll->action(QStringLiteral("crl_dump_crl_cache"))); enableDisableActions(nullptr); } void KeyListController::registerAction(QAction *action, Command::Restrictions restrictions, Command * (*create)(QAbstractItemView *, KeyListController *)) { if (!action) { return; } Q_ASSERT(!action->isCheckable()); // can be added later, for now, disallow const Private::action_item ai = { action, restrictions, create }; connect(action, SIGNAL(triggered()), this, SLOT(slotActionTriggered())); d->actions.push_back(ai); } void KeyListController::registerCommand(Command *cmd) { if (!cmd || std::binary_search(d->commands.cbegin(), d->commands.cend(), cmd)) { return; } d->addCommand(cmd); qCDebug(KLEOPATRA_LOG) << (void *)cmd; if (d->commands.size() == 1) { Q_EMIT commandsExecuting(true); } } bool KeyListController::hasRunningCommands() const { return !d->commands.empty(); } bool KeyListController::shutdownWarningRequired() const { return std::any_of(d->commands.cbegin(), d->commands.cend(), std::mem_fn(&Command::warnWhenRunningAtShutdown)); } // slot void KeyListController::cancelCommands() { std::for_each(d->commands.begin(), d->commands.end(), std::mem_fn(&Command::cancel)); } void KeyListController::Private::connectView(QAbstractItemView *view) { connect(view, SIGNAL(destroyed(QObject*)), q, SLOT(slotDestroyed(QObject*))); connect(view, SIGNAL(doubleClicked(QModelIndex)), q, SLOT(slotDoubleClicked(QModelIndex))); connect(view, SIGNAL(activated(QModelIndex)), q, SLOT(slotActivated(QModelIndex))); connect(view->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(view, SIGNAL(customContextMenuRequested(QPoint)), q, SLOT(slotContextMenu(QPoint))); } void KeyListController::Private::connectCommand(Command *cmd) { if (!cmd) { return; } connect(cmd, SIGNAL(destroyed(QObject*)), q, SLOT(slotDestroyed(QObject*))); connect(cmd, SIGNAL(finished()), q, SLOT(slotCommandFinished())); //connect( cmd, SIGNAL(canceled()), q, SLOT(slotCommandCanceled()) ); connect(cmd, &Command::info, q, &KeyListController::message); connect(cmd, SIGNAL(progress(QString,int,int)), q, SLOT(slotProgress(QString,int,int))); } void KeyListController::Private::slotDoubleClicked(const QModelIndex &idx) { QAbstractItemView *const view = qobject_cast(q->sender()); if (!view || !std::binary_search(views.cbegin(), views.cend(), view)) { return; } DetailsCommand *const c = new DetailsCommand(view, q); if (parentWidget) { c->setParentWidget(parentWidget); } c->setIndex(idx); c->start(); } void KeyListController::Private::slotActivated(const QModelIndex &idx) { Q_UNUSED(idx); QAbstractItemView *const view = qobject_cast(q->sender()); if (!view || !std::binary_search(views.cbegin(), views.cend(), view)) { return; } } void KeyListController::Private::slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_) { Q_UNUSED(old); Q_UNUSED(new_); const QItemSelectionModel *const sm = qobject_cast(q->sender()); if (!sm) { return; } q->enableDisableActions(sm); } void KeyListController::Private::slotContextMenu(const QPoint &p) { QAbstractItemView *const view = qobject_cast(q->sender()); if (view && std::binary_search(views.cbegin(), views.cend(), view)) { Q_EMIT q->contextMenuRequested(view, view->viewport()->mapToGlobal(p)); } else { qCDebug(KLEOPATRA_LOG) << "sender is not a QAbstractItemView*!"; } } void KeyListController::Private::slotCommandFinished() { Command *const cmd = qobject_cast(q->sender()); if (!cmd || !std::binary_search(commands.cbegin(), commands.cend(), cmd)) { return; } qCDebug(KLEOPATRA_LOG) << (void *)cmd; if (commands.size() == 1) { Q_EMIT q->commandsExecuting(false); } } void KeyListController::enableDisableActions(const QItemSelectionModel *sm) const { const Command::Restrictions restrictionsMask = d->calculateRestrictionsMask(sm); Q_FOREACH (const Private::action_item &ai, d->actions) if (ai.action) { ai.action->setEnabled(ai.restrictions == (ai.restrictions & restrictionsMask)); } } static bool all_secret_are_not_owner_trust_ultimate(const std::vector &keys) { for (const Key &key : keys) if (key.hasSecret() && key.ownerTrust() == Key::Ultimate) { return false; } return true; } Command::Restrictions find_root_restrictions(const std::vector &keys) { bool trusted = false, untrusted = false; for (const Key &key : keys) if (key.isRoot()) if (key.userID(0).validity() == UserID::Ultimate) { trusted = true; } else { untrusted = true; } else { return Command::NoRestriction; } if (trusted) if (untrusted) { return Command::NoRestriction; } else { return Command::MustBeTrustedRoot; } else if (untrusted) { return Command::MustBeUntrustedRoot; } else { return Command::NoRestriction; } } Command::Restrictions KeyListController::Private::calculateRestrictionsMask(const QItemSelectionModel *sm) { if (!sm) { return Command::NoRestriction; } const KeyListModelInterface *const m = dynamic_cast(sm->model()); if (!m) { return Command::NoRestriction; } const std::vector keys = m->keys(sm->selectedRows()); if (keys.empty()) { return Command::NoRestriction; } Command::Restrictions result = Command::NeedSelection; if (keys.size() == 1) { result |= Command::OnlyOneKey; } if (std::all_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::hasSecret))) { result |= Command::NeedSecretKey; } else if (!std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::hasSecret))) { result |= Command::MustNotBeSecretKey; } if (std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == OpenPGP; })) { result |= Command::MustBeOpenPGP; } else if (std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == CMS; })) { result |= Command::MustBeCMS; } if (all_secret_are_not_owner_trust_ultimate(keys)) { result |= Command::MayOnlyBeSecretKeyIfOwnerTrustIsNotYetUltimate; } result |= find_root_restrictions(keys); if (const ReaderStatus *rs = ReaderStatus::instance()) { if (!rs->firstCardWithNullPin().empty()) { result |= Command::AnyCardHasNullPin; } if (rs->anyCardCanLearnKeys()) { result |= Command::AnyCardCanLearnKeys; } } return result; } void KeyListController::Private::slotActionTriggered() { if (const QObject *const s = q->sender()) { const auto it = std::find_if(actions.cbegin(), actions.cend(), [this](const action_item &item) { return item.action == q->sender(); }); if (it != actions.end()) if (Command *const c = it->createCommand(this->currentView, q)) { if (parentWidget) { c->setParentWidget(parentWidget); } c->start(); } else qCDebug(KLEOPATRA_LOG) << "createCommand() == NULL for action(?) \"" << qPrintable(s->objectName()) << "\""; else { qCDebug(KLEOPATRA_LOG) << "I don't know anything about action(?) \"%s\"", qPrintable(s->objectName()); } } else { qCDebug(KLEOPATRA_LOG) << "not called through a signal/slot connection (sender() == NULL)"; } } int KeyListController::Private::toolTipOptions() const { using namespace Kleo::Formatting; static const int validityFlags = Validity | Issuer | ExpiryDates | CertificateUsage; static const int ownerFlags = Subject | UserIDs | OwnerTrust; static const int detailsFlags = StorageLocation | CertificateType | SerialNumber | Fingerprint; const TooltipPreferences prefs; int flags = KeyID; flags |= prefs.showValidity() ? validityFlags : 0; flags |= prefs.showOwnerInformation() ? ownerFlags : 0; flags |= prefs.showCertificateDetails() ? detailsFlags : 0; return flags; } void KeyListController::updateConfig() { const int opts = d->toolTipOptions(); if (d->flatModel) { d->flatModel->setToolTipOptions(opts); } if (d->hierarchicalModel) { d->hierarchicalModel->setToolTipOptions(opts); } } #include "moc_keylistcontroller.cpp"