Commit 6b6d314c authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Simplify the revocation of certifications

Depending on what the user selected (key, one or more user IDs, a single
certification) all certifications that the user can revoke are
determined and, after confirmation, are revoked one after the other. If
a revocation is canceled (by canceling the pinentry dialog) or a
revocation fails then the operation is aborted.

GnuPG-bug-id: 6115
parent 23d97bc8
......@@ -34,7 +34,7 @@ set(KF5_MIN_VERSION "5.96.0")
set(KIDENTITYMANAGEMENT_VERSION "5.21.40")
set(KMAILTRANSPORT_VERSION "5.21.40")
set(KMIME_VERSION "5.21.40")
set(LIBKLEO_VERSION "5.21.45")
set(LIBKLEO_VERSION "5.21.46")
set(QT_REQUIRED_VERSION "5.15.2")
set(GPGME_REQUIRED_VERSION "1.16.0")
......
......@@ -2,7 +2,7 @@
commands/revokecertificationcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
......@@ -15,27 +15,94 @@
#include "command_p.h"
#include "exportopenpgpcertstoservercommand.h"
#include "dialogs/revokecertificationdialog.h"
#include <kleopatra_debug.h>
#include <utils/keys.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <KGuiItem>
#include <KLocalizedString>
#include <KMessageBox>
#include <KStandardGuiItem>
#include <QGpgME/Protocol>
#include <QGpgME/QuickJob>
#include <gpgme++/engineinfo.h>
#include <KLocalizedString>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
using namespace QGpgME;
namespace
{
enum class InputType {
Key,
UserIDs,
Certifications,
};
struct CertificationData {
UserID userId;
Key certificationKey;
};
static std::vector<Key> getCertificationKeys(const GpgME::UserID &userId)
{
std::vector<Key> keys;
if (userId.numSignatures() == 0) {
qCWarning(KLEOPATRA_LOG) << __func__ << "- Error: Signatures of user ID" << QString::fromUtf8(userId.id()) << "not available";
return keys;
}
std::vector<GpgME::UserID::Signature> revokableCertifications;
Kleo::copy_if(userId.signatures(), std::back_inserter(revokableCertifications), [](const auto &certification) {
return userCanRevokeCertification(certification) == CertificationCanBeRevoked;
});
Kleo::transform(revokableCertifications, std::back_inserter(keys), [](const auto &certification) {
return KeyCache::instance()->findByKeyIDOrFingerprint(certification.signerKeyID());
});
return keys;
}
static bool confirmRevocations(QWidget *parent, const std::vector<CertificationData> &certifications)
{
KMessageBox::ButtonCode answer;
if (certifications.size() == 1) {
const auto [userId, certificationKey] = certifications.front();
const auto message = xi18nc("@info",
"<para>You are about to revoke the certification of user ID<nl/>%1<nl/>made with the key<nl/>%2.</para>",
QString::fromUtf8(userId.id()),
Formatting::formatForComboBox(certificationKey));
answer = KMessageBox::questionYesNo(parent,
message,
i18nc("@title:window", "Confirm Revocation"),
KGuiItem{i18n("Revoke Certification")},
KStandardGuiItem::cancel());
} else {
QStringList l;
Kleo::transform(certifications, std::back_inserter(l), [](const auto &c) {
return i18n("User ID '%1' certified with key %2", QString::fromUtf8(c.userId.id()), Formatting::formatForComboBox(c.certificationKey));
});
const auto message = i18np("You are about to revoke the following certification:", //
"You are about to revoke the following %1 certifications:",
certifications.size());
answer = KMessageBox::questionYesNoList(parent,
message,
l,
i18nc("@title:window", "Confirm Revocation"),
KGuiItem{i18n("Revoke Certifications")},
KStandardGuiItem::cancel());
}
return answer == KMessageBox::Yes;
}
}
class RevokeCertificationCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::RevokeCertificationCommand;
......@@ -44,25 +111,22 @@ class RevokeCertificationCommand::Private : public Command::Private
return static_cast<RevokeCertificationCommand *>(q);
}
public:
explicit Private(RevokeCertificationCommand *qq, KeyListController *c);
~Private() override;
Private(InputType i, RevokeCertificationCommand *qq, KeyListController *c = nullptr);
void init();
private:
void slotDialogAccepted();
void slotDialogRejected();
void slotResult(const Error &err);
private:
void ensureDialogCreated();
std::vector<CertificationData> getCertificationsToRevoke();
void scheduleNextRevocation();
QGpgME::QuickJob *createJob();
void slotResult(const Error &err);
private:
Key certificationKey;
InputType inputType = InputType::Key;
Key certificationTarget;
std::vector<UserID> uids;
QPointer<RevokeCertificationDialog> dialog;
std::vector<CertificationData> certificationsToRevoke;
std::vector<CertificationData> completedRevocations;
QPointer<QGpgME::QuickJob> job;
};
......@@ -78,12 +142,9 @@ const RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func()
#define d d_func()
#define q q_func()
RevokeCertificationCommand::Private::Private(RevokeCertificationCommand *qq, KeyListController *c)
: Command::Private(qq, c)
{
}
RevokeCertificationCommand::Private::~Private()
RevokeCertificationCommand::Private::Private(InputType i, RevokeCertificationCommand *qq, KeyListController *c)
: Command::Private{qq, c}
, inputType{i}
{
}
......@@ -91,133 +152,151 @@ 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();
qCWarning(KLEOPATRA_LOG) << q << "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();
qCWarning(KLEOPATRA_LOG) << q << "Expected OpenPGP key, but got" << keys_.front().protocolAsString();
return;
}
certificationTarget = keys_.front();
}
void RevokeCertificationCommand::Private::slotDialogAccepted()
std::vector<CertificationData> RevokeCertificationCommand::Private::getCertificationsToRevoke()
{
const auto certificationKey = dialog->selectedCertificationKey();
const auto selectedUserIDs = dialog->selectedUserIDs();
if (certificationKey.isNull() || selectedUserIDs.empty()) {
qCDebug(KLEOPATRA_LOG) << "No certification key or no user IDs selected -> skipping revocation";
finished();
return;
}
if (inputType != InputType::Certifications) {
// ensure that the certifications of the key have been loaded
if (certificationTarget.userID(0).numSignatures() == 0) {
certificationTarget.update();
}
job = createJob();
if (!job) {
qCDebug(KLEOPATRA_LOG) << "Failed to create QuickJob";
finished();
return;
// build list of user IDs and revokable certifications
const auto userIDsToConsider = (inputType == InputType::Key) ? certificationTarget.userIDs() : uids;
for (const auto &userId : userIDsToConsider) {
Kleo::transform(getCertificationKeys(userId), std::back_inserter(certificationsToRevoke), [userId](const auto &k) {
return CertificationData{userId, k};
});
}
}
job->startRevokeSignature(certificationTarget, dialog->selectedCertificationKey(), dialog->selectedUserIDs());
}
void RevokeCertificationCommand::Private::slotDialogRejected()
{
canceled();
Kleo::erase_if(certificationsToRevoke, [](const auto &c) {
return c.certificationKey.isNull();
});
return certificationsToRevoke;
}
void RevokeCertificationCommand::Private::slotResult(const Error &err)
void RevokeCertificationCommand::Private::scheduleNextRevocation()
{
if (err.isCanceled()) {
// do nothing
} else if (err) {
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"));
if (!certificationsToRevoke.empty()) {
const auto nextCertification = certificationsToRevoke.back();
job = createJob();
if (!job) {
qCWarning(KLEOPATRA_LOG) << q << "Failed to create job";
finished();
return;
}
job->startRevokeSignature(certificationTarget, nextCertification.certificationKey, {nextCertification.userId});
} else {
information(i18n("Revocation successful."),
i18n("Revocation Succeeded"));
if (dialog && dialog->sendToServer()) {
auto const cmd = new ExportOpenPGPCertsToServerCommand(certificationTarget);
const auto message = xi18ncp("@info",
"<para>The certification has been revoked successfully.</para>"
"<para>Do you want to publish the revocation?</para>",
"<para>%1 certifications have been revoked successfully.</para>"
"<para>Do you want to publish the revocations?</para>",
completedRevocations.size());
const auto yesButton = KGuiItem{i18ncp("@action:button", "Publish Revocation", "Publish Revocations", completedRevocations.size()),
QIcon::fromTheme(QStringLiteral("view-certificate-export-server"))};
const auto answer = KMessageBox::questionYesNo(parentWidgetOrView(),
message,
i18nc("@title:window", "Confirm Publication"),
yesButton,
KStandardGuiItem::cancel(),
{},
KMessageBox::Notify | KMessageBox::Dangerous);
if (answer == KMessageBox::Yes) {
const auto cmd = new ExportOpenPGPCertsToServerCommand{certificationTarget};
cmd->start();
}
finished();
}
finished();
}
void RevokeCertificationCommand::Private::ensureDialogCreated()
void RevokeCertificationCommand::Private::slotResult(const Error &err)
{
if (dialog) {
if (err.isCanceled()) {
canceled();
return;
}
dialog = new RevokeCertificationDialog;
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
if (err) {
const auto failedRevocation = certificationsToRevoke.back();
error(xi18nc("@info",
"<para>The revocation of the certification of user ID<nl/>%1<nl/>made with key<nl/>%2<nl/>failed:</para>"
"<para><message>%3</message></para>",
Formatting::prettyNameAndEMail(failedRevocation.userId),
Formatting::formatForComboBox(failedRevocation.certificationKey),
QString::fromUtf8(err.asString())));
finished();
return;
}
connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
completedRevocations.push_back(certificationsToRevoke.back());
certificationsToRevoke.pop_back();
scheduleNextRevocation();
}
QGpgME::QuickJob *RevokeCertificationCommand::Private::createJob()
{
Q_ASSERT(!job);
Q_ASSERT(certificationTarget.protocol() == OpenPGP);
const auto backend = QGpgME::openpgp();
if (!backend) {
return nullptr;
}
QuickJob *const j = backend->quickJob();
const auto j = QGpgME::openpgp()->quickJob();
if (j) {
connect(j, &Job::progress,
q, &Command::progress);
connect(j, &QGpgME::QuickJob::result, q, [this](const GpgME::Error &error) { slotResult(error); });
connect(j, &Job::progress, q, &Command::progress);
connect(j, &QGpgME::QuickJob::result, q, [this](const GpgME::Error &error) {
slotResult(error);
});
}
return j;
}
RevokeCertificationCommand::RevokeCertificationCommand(QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
: Command{v, new Private{InputType::Key, this, c}}
{
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::Key &key)
: Command(key, new Private(this, nullptr))
: Command{key, new Private{InputType::Key, this}}
{
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID &uid)
: Command(uid.parent(), new Private(this, nullptr))
: Command{uid.parent(), new Private{InputType::UserIDs, this}}
{
std::vector<UserID>(1, uid).swap(d->uids);
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const std::vector<GpgME::UserID> &uids)
: Command{uids.empty() ? Key{} : uids.front().parent(), new Private{this, nullptr}}
: Command{uids.empty() ? Key{} : uids.front().parent(), new Private{InputType::UserIDs, this}}
{
d->uids = uids;
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID::Signature &signature)
: Command(signature.parent().parent(), new Private(this, nullptr))
: Command{signature.parent().parent(), new Private{InputType::Certifications, this}}
{
std::vector<UserID>(1, signature.parent()).swap(d->uids);
d->certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID());
if (!signature.isNull()) {
const Key certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID());
d->certificationsToRevoke = {{signature.parent(), certificationKey}};
}
d->init();
}
RevokeCertificationCommand::~RevokeCertificationCommand()
{
qCDebug(KLEOPATRA_LOG) << "~RevokeCertificationCommand()";
qCDebug(KLEOPATRA_LOG) << this << __func__;
}
// static
......@@ -234,43 +313,41 @@ void RevokeCertificationCommand::doStart()
}
if (!Kleo::all_of(d->uids, userIDBelongsToKey(d->certificationTarget))) {
qCWarning(KLEOPATRA_LOG) << "User ID <-> Key mismatch!";
qCWarning(KLEOPATRA_LOG) << this << "User ID <-> Key mismatch!";
d->finished();
return;
}
// ensure that the certifications of the key have been loaded
if (d->certificationTarget.userID(0).numSignatures() == 0) {
d->certificationTarget.update();
}
// check if there are any certifications the user can revoke
const auto userIDsToConsider = d->uids.empty() ? d->certificationTarget.userIDs() : d->uids;
std::vector<UserID> revokableUserIDs;
std::copy_if(userIDsToConsider.begin(), userIDsToConsider.end(), std::back_inserter(revokableUserIDs), &Kleo::userCanRevokeCertifications);
if (revokableUserIDs.empty()) {
const auto message = d->uids.empty() //
? i18n("You cannot revoke any certifications of this key.")
: i18np("You cannot revoke any certifications of this user ID.", "You cannot revoke any certifications of these user IDs.", d->uids.size());
KMessageBox::information(d->parentWidgetOrView(), message);
const auto certificationsToRevoke = d->getCertificationsToRevoke();
if (certificationsToRevoke.empty()) {
switch (d->inputType) {
case InputType::Key:
d->information(i18n("You cannot revoke any certifications of this key."));
break;
case InputType::UserIDs:
d->information(i18np("You cannot revoke any certifications of this user ID.", //
"You cannot revoke any certifications of these user IDs.",
d->uids.size()));
break;
case InputType::Certifications:
d->information(i18n("You cannot revoke this certification."));
break;
}
d->finished();
return;
}
d->ensureDialogCreated();
Q_ASSERT(d->dialog);
d->dialog->setCertificateToRevoke(d->certificationTarget);
d->dialog->setSelectedUserIDs(revokableUserIDs);
if (!d->certificationKey.isNull()) {
d->dialog->setSelectedCertificationKey(d->certificationKey);
if (!confirmRevocations(d->parentWidgetOrView(), certificationsToRevoke)) {
d->canceled();
return;
}
d->dialog->show();
d->scheduleNextRevocation();
}
void RevokeCertificationCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG) << "RevokeCertificationCommand::doCancel()";
qCDebug(KLEOPATRA_LOG) << this << __func__;
if (d->job) {
d->job->slotCancel();
}
......@@ -278,5 +355,3 @@ void RevokeCertificationCommand::doCancel()
#undef d
#undef q
#include "moc_revokecertificationcommand.cpp"
......@@ -10,7 +10,7 @@
#pragma once
#include <commands/command.h>
#include "command.h"
#include <gpgme++/key.h>
......@@ -23,7 +23,7 @@ class RevokeCertificationCommand : public Command
{
Q_OBJECT
public:
explicit RevokeCertificationCommand(QAbstractItemView *view, KeyListController *parent);
RevokeCertificationCommand(QAbstractItemView *view, KeyListController *parent);
explicit RevokeCertificationCommand(const GpgME::Key &key);
explicit RevokeCertificationCommand(const GpgME::UserID &uid);
explicit RevokeCertificationCommand(const std::vector<GpgME::UserID> &uids);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment