Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34085871
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
15 KB
Subscribers
None
View Options
diff --git a/src/commands/refreshcertificatescommand.cpp b/src/commands/refreshcertificatescommand.cpp
index 4a60358b4..e0140bf49 100644
--- a/src/commands/refreshcertificatescommand.cpp
+++ b/src/commands/refreshcertificatescommand.cpp
@@ -1,456 +1,457 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 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 "command_p.h"
#include "refreshcertificatescommand.h"
#include <settings.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyHelpers>
#include <KLocalizedString>
#include <KMessageBox>
#include <QGpgME/Protocol>
#include <QGpgME/ReceiveKeysJob>
#include <QGpgME/RefreshKeysJob>
#include <QGpgME/WKDRefreshJob>
#include <gpgme++/importresult.h>
#include <QProgressDialog>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace GpgME;
class RefreshCertificatesCommand::Private : public Command::Private
{
friend class ::RefreshCertificatesCommand;
RefreshCertificatesCommand *q_func() const
{
return static_cast<RefreshCertificatesCommand *>(q);
}
public:
explicit Private(RefreshCertificatesCommand *qq);
explicit Private(RefreshCertificatesCommand *qq, KeyListController *c);
~Private() override;
void start();
void cancel();
std::unique_ptr<QGpgME::ReceiveKeysJob> startKeyserverJob();
std::unique_ptr<QGpgME::RefreshKeysJob> startSMIMEJob();
std::unique_ptr<QGpgME::WKDRefreshJob> startWKDRefreshJob();
void onKeyserverJobResult(const ImportResult &result);
void onWKDRefreshJobResult(const ImportResult &result);
void onSMIMEJobResult(const Error &err);
void checkFinished();
private:
QPointer<QGpgME::Job> pgpJob;
QPointer<QGpgME::Job> smimeJob;
QPointer<QGpgME::Job> wkdJob;
std::vector<Key> pgpKeys;
std::vector<Key> smimeKeys;
std::vector<Key> wkdKeys;
ImportResult keyserverResult;
ImportResult wkdRefreshResult;
std::optional<Error> smimeError;
QPointer<QProgressDialog> progressDialog;
};
RefreshCertificatesCommand::Private *RefreshCertificatesCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const RefreshCertificatesCommand::Private *RefreshCertificatesCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
RefreshCertificatesCommand::Private::Private(RefreshCertificatesCommand *qq)
: Command::Private{qq}
{
}
RefreshCertificatesCommand::Private::Private(RefreshCertificatesCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
RefreshCertificatesCommand::Private::~Private() = default;
void RefreshCertificatesCommand::Private::start()
{
if (std::ranges::any_of(keys(), [](const auto &key) {
return key.protocol() == GpgME::UnknownProtocol;
})) {
qCWarning(KLEOPATRA_LOG) << "Key has unknown protocol";
finished();
return;
}
std::unique_ptr<QGpgME::Job> pgpRefreshJob;
std::unique_ptr<QGpgME::Job> smimeRefreshJob;
std::unique_ptr<QGpgME::Job> wkdRefreshJob;
auto keysByProtocol = Kleo::partitionKeysByProtocol(keys());
progressDialog = new QProgressDialog(i18ncp("@info", "Updating certificate…", "Updating %1 certificates…", keys().size()),
i18nc("@action:button", "Cancel"),
0,
0,
parentWidgetOrView());
+ progressDialog->setValue(0);
connect(progressDialog.get(), &QProgressDialog::canceled, q, &RefreshCertificatesCommand::cancel);
progressDialog->setMinimumDuration(500);
pgpKeys = keysByProtocol.openpgp;
smimeKeys = keysByProtocol.cms;
if (!smimeKeys.empty()) {
smimeRefreshJob = startSMIMEJob();
}
if (!pgpKeys.empty()) {
if (haveKeyserverConfigured()) {
pgpRefreshJob = startKeyserverJob();
} else {
keyserverResult = ImportResult{Error::fromCode(GPG_ERR_USER_1)};
}
wkdRefreshJob = startWKDRefreshJob();
}
if (!pgpRefreshJob && !smimeRefreshJob && !wkdRefreshJob) {
finished();
return;
}
pgpJob = pgpRefreshJob.release();
smimeJob = smimeRefreshJob.release();
wkdJob = wkdRefreshJob.release();
}
void RefreshCertificatesCommand::Private::cancel()
{
if (pgpJob) {
pgpJob->slotCancel();
}
if (smimeJob) {
smimeJob->slotCancel();
}
if (wkdJob) {
wkdJob->slotCancel();
}
pgpJob.clear();
smimeJob.clear();
wkdJob.clear();
delete progressDialog;
smimeError = Error::fromCode(GPG_ERR_CANCELED);
}
std::unique_ptr<QGpgME::ReceiveKeysJob> RefreshCertificatesCommand::Private::startKeyserverJob()
{
std::unique_ptr<QGpgME::ReceiveKeysJob> refreshJob{QGpgME::openpgp()->receiveKeysJob()};
Q_ASSERT(refreshJob);
connect(refreshJob.get(), &QGpgME::ReceiveKeysJob::result, q, [this](const GpgME::ImportResult &result) {
onKeyserverJobResult(result);
});
connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress);
refreshJob->start(Kleo::getFingerprints(pgpKeys));
Q_EMIT q->info(i18nc("@info:status", "Updating key..."));
return refreshJob;
}
std::unique_ptr<QGpgME::RefreshKeysJob> RefreshCertificatesCommand::Private::startSMIMEJob()
{
std::unique_ptr<QGpgME::RefreshKeysJob> refreshJob{QGpgME::smime()->refreshKeysJob()};
Q_ASSERT(refreshJob);
connect(refreshJob.get(), &QGpgME::RefreshKeysJob::result, q, [this](const GpgME::Error &err) {
onSMIMEJobResult(err);
});
connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress);
refreshJob->start(smimeKeys);
Q_EMIT q->info(i18nc("@info:status", "Updating certificate..."));
return refreshJob;
}
std::unique_ptr<QGpgME::WKDRefreshJob> RefreshCertificatesCommand::Private::startWKDRefreshJob()
{
std::unique_ptr<QGpgME::WKDRefreshJob> refreshJob{QGpgME::openpgp()->wkdRefreshJob()};
Q_ASSERT(refreshJob);
connect(refreshJob.get(), &QGpgME::WKDRefreshJob::result, q, [this](const GpgME::ImportResult &result) {
onWKDRefreshJobResult(result);
});
connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress);
Error err;
if (!Settings{}.queryWKDsForAllUserIDs()) {
// check which keys are eligible for WKD refresh, i.e. for which key a user ID has WKD as origin
for (const auto &key : pgpKeys) {
if (Kleo::any_of(key.userIDs(), [](const auto &userId) {
return !userId.isRevoked() && !userId.addrSpec().empty() && userId.origin() == Key::OriginWKD;
})) {
wkdKeys.push_back(key);
}
}
if (!wkdKeys.empty()) {
err = refreshJob->start(wkdKeys);
}
} else {
std::vector<UserID> userIds;
for (const auto &key : pgpKeys) {
bool userIdAdded = false;
Kleo::copy_if(key.userIDs(), std::back_inserter(userIds), [&userIdAdded](const auto &userId) {
const bool addUserId = !userId.isRevoked() && !userId.addrSpec().empty();
userIdAdded |= addUserId;
return addUserId;
});
if (userIdAdded) {
wkdKeys.push_back(key);
}
}
if (!userIds.empty()) {
err = refreshJob->start(userIds);
}
}
if (wkdKeys.empty()) {
wkdRefreshResult = ImportResult{Error::fromCode(GPG_ERR_USER_1)};
QMetaObject::invokeMethod(
q,
[this]() {
checkFinished();
},
Qt::QueuedConnection);
return {};
}
Q_EMIT q->info(i18nc("@info:status", "Updating key..."));
return refreshJob;
}
namespace
{
static auto informationOnChanges(const ImportResult &result)
{
QString text;
// if additional keys have been retrieved via WKD, then most of the below
// details are just a guess and may concern the additional keys instead of
// the refresh keys; this could only be clarified by a thorough comparison of
// unrefreshed and refreshed key
if (result.numUnchanged() == result.numConsidered()) {
// if numUnchanged < numConsidered, then it is not clear whether the refreshed key
// hasn't changed or whether another key retrieved via WKD hasn't changed
text = i18n("The certificate has not changed.");
} else if (result.newRevocations() > 0) {
// it is possible that a revoked key has been newly imported via WKD,
// but it is much more likely that the refreshed key was revoked
text = i18n("The certificate has been revoked.");
} else {
// it doesn't make much sense to list below details if the key has been revoked
text = i18n("The certificate has been updated.");
QStringList details;
if (result.newUserIDs() > 0) {
details.push_back(i18n("New user IDs: %1", result.newUserIDs()));
}
if (result.newSubkeys() > 0) {
details.push_back(i18n("New subkeys: %1", result.newSubkeys()));
}
if (result.newSignatures() > 0) {
details.push_back(i18n("New signatures: %1", result.newSignatures()));
}
if (!details.empty()) {
text += QLatin1StringView{"<br><br>"} + details.join(QLatin1StringView{"<br>"});
}
}
text = QLatin1StringView{"<p>"} + text + QLatin1StringView{"</p>"};
if (result.numImported() > 0) {
text += QLatin1StringView{"<p>"}
+ i18np("Additionally, one new key has been retrieved.", "Additionally, %1 new keys have been retrieved.", result.numImported())
+ QLatin1StringView{"</p>"};
}
return text;
}
}
void RefreshCertificatesCommand::Private::onKeyserverJobResult(const ImportResult &result)
{
keyserverResult = result;
if (result.error().isCanceled()) {
pgpJob.clear();
finished();
return;
}
pgpJob.clear();
checkFinished();
}
void RefreshCertificatesCommand::Private::onWKDRefreshJobResult(const ImportResult &result)
{
wkdRefreshResult = result;
if (result.error().isCanceled()) {
pgpJob.clear();
finished();
return;
}
wkdJob.clear();
checkFinished();
}
void RefreshCertificatesCommand::Private::onSMIMEJobResult(const Error &error)
{
smimeError = error;
if (error.isCanceled()) {
smimeJob.clear();
finished();
return;
}
smimeJob.clear();
checkFinished();
}
RefreshCertificatesCommand::RefreshCertificatesCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
RefreshCertificatesCommand::RefreshCertificatesCommand(const Key &key)
: Command(key, new Private(this))
{
}
RefreshCertificatesCommand::~RefreshCertificatesCommand() = default;
void RefreshCertificatesCommand::doStart()
{
d->start();
}
void RefreshCertificatesCommand::doCancel()
{
d->cancel();
}
void RefreshCertificatesCommand::Private::checkFinished()
{
if (smimeJob || pgpJob || wkdJob) {
return;
}
delete progressDialog;
if (smimeError && smimeError->code() == GPG_ERR_CANCELED) {
finished();
return;
}
const auto pgpSkipped = keyserverResult.error().code() == GPG_ERR_USER_1;
const auto pgpKeyNotFound = keyserverResult.error().code() == GPG_ERR_NO_DATA;
const auto wkdSkipped = wkdRefreshResult.error().code() == GPG_ERR_USER_1;
const auto hasSmimeError = smimeError && *smimeError;
const auto hasPgpError = !keyserverResult.isNull() && keyserverResult.error() && !pgpSkipped && !pgpKeyNotFound;
const auto hasWkdError = !wkdRefreshResult.isNull() && wkdRefreshResult.error() && !wkdSkipped;
bool success = false;
QString text;
if (!pgpKeys.empty()) {
text += QLatin1StringView{"<p><strong>"} + i18nc("@info", "Result of OpenPGP certificate update from keyserver, LDAP server, or Active Directory")
+ QLatin1StringView{"</strong></p>"};
if (hasPgpError) {
text += xi18nc("@info", "<para>Update failed:</para><para><message>%1</message></para>", Formatting::errorAsString(keyserverResult.error()));
} else if (pgpSkipped) {
text += xi18nc("@info", "<para>Update skipped because no OpenPGP keyserver is configured.</para>");
} else if (pgpKeyNotFound) {
text += xi18ncp("@info", "<para>The certificate was not found.</para>", "<para>The certificates were not found.</para>", pgpKeys.size());
} else if (pgpKeys.size() > 1) {
success = true;
text += xi18ncp("@info", "<para>The certificate was updated.</para>", "<para>The certificates were updated.</para>", pgpKeys.size());
} else if (pgpKeys.size() == 1) {
success = true;
text += informationOnChanges(keyserverResult);
}
}
if (!wkdKeys.empty() && !wkdSkipped) {
text += QLatin1StringView{"<p><strong>"} + i18nc("@info", "Result of update from Web Key Directory") + QLatin1StringView{"</strong></p>"};
if (hasWkdError) {
text += xi18nc("@info", "<para>Update failed:</para><para><message>%1</message></para>", Formatting::errorAsString(wkdRefreshResult.error()));
} else if (wkdRefreshResult.numConsidered() == 0) {
// explicitly use pgpKeys.size() also for WKD to avoid confusion caused by different plural forms for keyserver result and WKD result
text += xi18ncp("@info", "<para>The certificate was not found.</para>", "<para>The certificates were not found.</para>", pgpKeys.size());
} else if (pgpKeys.size() > 1) {
success = true;
// explicitly use pgpKeys.size() also for WKD to avoid confusion caused by different plural forms for keyserver result and WKD result
text += xi18ncp("@info", "<para>The certificate was updated.</para>", "<para>The certificates were updated.</para>", pgpKeys.size());
} else if (pgpKeys.size() == 1) {
success = true;
text += informationOnChanges(wkdRefreshResult);
}
}
if (!smimeKeys.empty()) {
text += QLatin1StringView{"<p><strong>"} + i18nc("@info", "Result of S/MIME certificate update") + QLatin1StringView{"</strong></p>"};
if (hasSmimeError) {
text += xi18nc("@info", "<para>Update failed:</para><para><message>%1</message></para>", Formatting::errorAsString(*smimeError));
} else {
success = true;
text += xi18ncp("@info", "<para>The certificate was updated.</para>", "<para>The certificates were updated.</para>", smimeKeys.size());
}
}
information(text,
success ? i18ncp("@title:window", "Certificate Updated", "Certificates Updated", keys().size()) : i18nc("@title:window", "Update Failed"));
finished();
}
#undef d
#undef q
#include "moc_refreshcertificatescommand.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Dec 3, 3:43 AM (1 d, 22 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c5/c4/614cc5070f28feb2206bfd5c2e7b
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment