Commit 692591dc authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Add the possibility to revoke certifications of OpenPGP keys

More precisely, this revokes certifications (i.e. signatures) for
user IDs of OpenPGP keys. Needs GnuPG >= 2.2.24 (soon to be released)
and gpgme >= 1.14.1 (unreleased).

GnuPG-bug-id: 5094
parent 19554ef2
Pipeline #39611 canceled with stage
......@@ -127,6 +127,8 @@ set(_kleopatra_SRCS
dialogs/ownertrustdialog.cpp
dialogs/selftestdialog.cpp
dialogs/certifycertificatedialog.cpp
dialogs/revokecertificationwidget.cpp
dialogs/revokecertificationdialog.cpp
dialogs/adduseriddialog.cpp
dialogs/addemaildialog.cpp
dialogs/exportcertificatesdialog.cpp
......@@ -222,6 +224,7 @@ set(_kleopatra_SRCS
commands/changeroottrustcommand.cpp
commands/changepassphrasecommand.cpp
commands/certifycertificatecommand.cpp
commands/revokecertificationcommand.cpp
commands/selftestcommand.cpp
commands/exportsecretkeycommand.cpp
commands/exportopenpgpcertstoservercommand.cpp
......
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "revokecertificationcommand.h"
#include "command_p.h"
#include "exportopenpgpcertstoservercommand.h"
#include "dialogs/revokecertificationdialog.h"
#include <Libkleo/Formatting>
#include <QGpgME/Protocol>
#include <QGpgME/QuickJob>
#include <gpgme++/engineinfo.h>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include "kleopatra_debug.h"
#include <gpgme++/gpgmepp_version.h>
#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<RevokeCertificationCommand *>(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<UserID> uids;
QPointer<RevokeCertificationDialog> dialog;
QPointer<QGpgME::QuickJob> job;
};
RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func() const
{
return static_cast<const Private *>(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<Key> 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("<p>An error occurred while trying to revoke the certification of<br/><br/>"
"<b>%1</b>:</p><p>\t%2</p>",
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<UserID>(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"
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __KLEOPATRA_COMMANDS_REVOKECERTIFICATIONCOMMAND_H__
#define __KLEOPATRA_COMMANDS_REVOKECERTIFICATIONCOMMAND_H__
#include <commands/command.h>
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__
......@@ -15,6 +15,7 @@
#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"
......@@ -335,7 +336,7 @@ void CertificateDetailsWidget::Private::userIDTableContextMenuRequested(const QP
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);
......@@ -347,6 +348,21 @@ void CertificateDetailsWidget::Private::userIDTableContextMenuRequested(const QP
});
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));
}
......
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "revokecertificationdialog.h"
#include "revokecertificationwidget.h"
#include <Libkleo/Formatting>
#include <KConfigGroup>
#include <KGuiItem>
#include <KLocalizedString>
#include <KSharedConfig>
#include <KStandardGuiItem>
#include <QAbstractButton>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
#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<UserID> &uids)
{
d->mainWidget->setSelectUserIDs(uids);
}
std::vector<GpgME::UserID> RevokeCertificationDialog::selectedUserIDs() const
{
return d->mainWidget->selectedUserIDs();
}
Key RevokeCertificationDialog::selectedCertificationKey() const
{
return d->mainWidget->certificationKey();
}
bool RevokeCertificationDialog::sendToServer() const
{
return d->mainWidget->publishSelected();
}
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONDIALOG_H__
#define __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONDIALOG_H__
#include <QDialog>
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<GpgME::UserID> &uids);
std::vector<GpgME::UserID> selectedUserIDs() const;
GpgME::Key selectedCertificationKey() const;
bool sendToServer() const;
private:
class Private;
const std::unique_ptr<Private> d;
};
} // namespace Kleo
#endif /* __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONDIALOG_H__ */
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "revokecertificationwidget.h"
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <Libkleo/DefaultKeyFilter>
#include <Libkleo/Formatting>
#include <Libkleo/KeySelectionCombo>
#include <QCheckBox>
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QListView>
#include <QStandardItemModel>
#include <QVBoxLayout>
#include <gpgme++/key.h>
#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) {