Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F22067727
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
11 KB
Subscribers
None
View Options
diff --git a/src/commands/certifygroupcommand.cpp b/src/commands/certifygroupcommand.cpp
index 48757392a..1fab287a1 100644
--- a/src/commands/certifygroupcommand.cpp
+++ b/src/commands/certifygroupcommand.cpp
@@ -1,351 +1,359 @@
/*
commands/certifygroupcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "certifygroupcommand.h"
#include "command_p.h"
#include <commands/exportopenpgpcertstoservercommand.h>
#include <dialogs/certifycertificatedialog.h>
#include <utils/tags.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/KeyGroup>
#include <Libkleo/KeyHelpers>
#include <QGpgME/Protocol>
#include <QGpgME/SignKeyJob>
#include <QDate>
#include <QProgressDialog>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
namespace
{
struct CertificationResultData {
std::vector<UserID> userIds;
GpgME::Error error;
};
}
class CertifyGroupCommand::Private : public Command::Private
{
friend class ::Kleo::CertifyGroupCommand;
CertifyGroupCommand *q_func() const
{
return static_cast<CertifyGroupCommand *>(q);
}
public:
explicit Private(CertifyGroupCommand *qq);
~Private() override;
void start();
private:
void showDialog();
void certifyCertificates();
void setUpProgressDialog(int numberOfKeysToCertify);
void startNextCertification();
void createJob();
void slotResult(const Error &err);
void wrapUp();
private:
KeyGroup group;
std::vector<Key> certificates;
QPointer<CertifyCertificateDialog> dialog;
QPointer<QProgressDialog> progressDialog;
std::vector<UserID> userIdsToCertify;
struct {
Key certificationKey;
QDate expirationDate;
QString tags;
bool exportable = false;
bool sendToServer = false;
} certificationOptions;
struct {
std::vector<UserID> userIds;
} jobData;
QPointer<QGpgME::SignKeyJob> job;
std::vector<CertificationResultData> results;
};
CertifyGroupCommand::Private *CertifyGroupCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const CertifyGroupCommand::Private *CertifyGroupCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
CertifyGroupCommand::Private::Private(CertifyGroupCommand *qq)
: Command::Private(qq)
{
}
CertifyGroupCommand::Private::~Private() = default;
void CertifyGroupCommand::Private::start()
{
if (!group.isNull()) {
const auto &groupKeys = group.keys();
certificates = std::vector<GpgME::Key>(groupKeys.begin(), groupKeys.end());
}
if (certificates.empty()) {
finished();
return;
}
if (!allKeysHaveProtocol(certificates, GpgME::OpenPGP)) {
const auto title = i18nc("@title:window", "Group Cannot Be Certified");
const auto message = i18nc("@info", "This group contains S/MIME certificates which cannot be certified.");
information(message, title);
finished();
return;
}
showDialog();
}
void CertifyGroupCommand::Private::showDialog()
{
dialog = new CertifyCertificateDialog;
dialog->setAttribute(Qt::WA_DeleteOnClose);
applyWindowID(dialog);
connect(dialog, &QDialog::accepted, q, [this]() {
certifyCertificates();
});
connect(dialog, &QDialog::rejected, q, [this]() {
canceled();
});
if (!group.isNull()) {
dialog->setGroupName(group.name());
}
dialog->setCertificatesToCertify(certificates);
dialog->show();
}
void CertifyGroupCommand::Private::certifyCertificates()
{
userIdsToCertify = dialog->selectedUserIDs();
if (userIdsToCertify.empty()) {
canceled();
return;
}
certificationOptions.certificationKey = dialog->selectedSecretKey();
certificationOptions.expirationDate = dialog->expirationDate();
certificationOptions.tags = dialog->tags();
certificationOptions.exportable = dialog->exportableCertificationSelected();
certificationOptions.sendToServer = dialog->sendToServer();
setUpProgressDialog(userIdsToCertify.size());
startNextCertification();
}
void CertifyGroupCommand::Private::setUpProgressDialog(int numberOfKeysToCertify)
{
if (progressDialog) {
return;
}
progressDialog = new QProgressDialog{parentWidgetOrView()};
progressDialog->setAttribute(Qt::WA_DeleteOnClose);
progressDialog->setModal(true);
progressDialog->setWindowTitle(i18nc("@title:window", "Certify Certificates"));
progressDialog->setLabelText(i18nc("@info:progress", "Certifying certificates ..."));
progressDialog->setMinimumDuration(1000);
progressDialog->setMaximum(numberOfKeysToCertify);
progressDialog->setValue(0);
connect(progressDialog, &QProgressDialog::canceled, q, &Command::cancel);
connect(q, &Command::finished, progressDialog, [this]() {
progressDialog->accept();
});
}
void CertifyGroupCommand::Private::startNextCertification()
{
Q_ASSERT(!userIdsToCertify.empty());
const auto nextKey = userIdsToCertify.front().parent();
// for now we only deal with primary user IDs
jobData.userIds = {userIdsToCertify.front()};
userIdsToCertify.erase(userIdsToCertify.begin());
const std::vector<unsigned int> userIdIndexes = {0};
createJob();
job->setUserIDsToSign(userIdIndexes);
if (const Error err = job->start(nextKey)) {
QMetaObject::invokeMethod(
q,
[this, err]() {
slotResult(err);
},
Qt::QueuedConnection);
}
}
void CertifyGroupCommand::Private::createJob()
{
Q_ASSERT(!job);
std::unique_ptr<QGpgME::SignKeyJob> newJob{QGpgME::openpgp()->signKeyJob()};
newJob->setDupeOk(true);
newJob->setSigningKey(certificationOptions.certificationKey);
newJob->setExportable(certificationOptions.exportable);
if (!certificationOptions.tags.isEmpty()) {
// do not set an empty remark to avoid an empty signature notation (GnuPG bug T5142)
newJob->setRemark(certificationOptions.tags);
}
if (!certificationOptions.expirationDate.isNull()) {
newJob->setExpirationDate(certificationOptions.expirationDate);
}
connect(newJob.get(), &QGpgME::SignKeyJob::result, q, [this](const GpgME::Error &result) {
slotResult(result);
});
job = newJob.release();
}
void CertifyGroupCommand::Private::slotResult(const Error &err)
{
results.push_back({
jobData.userIds,
err,
});
progressDialog->setValue(results.size());
if (err.isCanceled()) {
finished();
- return;
- }
-
- if (!userIdsToCertify.empty()) {
+ } else if (err && (results.size() == 1) //
+ && ((err.sourceID() == GPG_ERR_SOURCE_PINENTRY) //
+ || (err.code() == GPG_ERR_BAD_PASSPHRASE))) {
+ // abort the certification process on certain errors during the first
+ // certification, e.g. bad passphrase or pinentry timeout, where it's
+ // better to restart the whole process instead of continuing with the
+ // next certificate
+ error(xi18nc("@info",
+ "<para>The certification of the certificates failed.</para>"
+ "<para>Error: <message>%1</message></para>",
+ Formatting::errorAsString(results.front().error)));
+ finished();
+ } else if (!userIdsToCertify.empty()) {
job.clear();
jobData.userIds.clear();
startNextCertification();
- return;
+ } else {
+ wrapUp();
}
-
- wrapUp();
}
static QString resultSummary(const std::vector<CertificationResultData> &results)
{
Q_ASSERT(!results.empty());
const int totalCount = results.size();
const int successCount = Kleo::count_if(results, [](const auto &result) {
return !result.error;
});
if (successCount == totalCount) {
return i18nc("@info", "All certificates were certified successfully.");
}
if (successCount == 0) {
// we assume that all attempted certifications failed for the same reason
return xi18nc("@info",
"<para>The certification of all certificates failed.</para>"
"<para>Error: <message>%1</message></para>",
Formatting::errorAsString(results.front().error));
}
return i18ncp("@info", //
"1 of %2 certificates was certified successfully.",
"%1 of %2 certificates were certified successfully.",
successCount,
totalCount);
}
void CertifyGroupCommand::Private::wrapUp()
{
Q_ASSERT(userIdsToCertify.empty());
Q_ASSERT(!results.empty());
const int successCount = Kleo::count_if(results, [](const auto &result) {
return !result.error;
});
const bool sendToServer = (successCount > 0) && certificationOptions.exportable && certificationOptions.sendToServer;
QString message = QLatin1String{"<p>"} + resultSummary(results) + QLatin1String{"</p>"};
if (sendToServer) {
message += i18nc("@info", "<p>Next the certified certificates will be uploaded to the configured certificate directory.</p>");
}
const auto failedUserIdsInfo = std::accumulate(results.cbegin(), results.cend(), QStringList{}, [](auto failedUserIds, const auto &result) {
if (result.error) {
failedUserIds.push_back(i18nc("A user ID (an error description)",
"%1 (%2)",
Formatting::formatForComboBox(result.userIds.front().parent()),
Formatting::errorAsString(result.error)));
}
return failedUserIds;
});
if (successCount > 0) {
if (failedUserIdsInfo.size() > 0) {
message += i18nc("@info", "<p>Certifying the following certificates failed:</p>");
}
informationList(message, failedUserIdsInfo, i18nc("@title:window", "Certification Completed"));
} else {
error(message);
}
if (sendToServer) {
const auto certificatesToSendToServer = std::accumulate(results.cbegin(), results.cend(), std::vector<Key>{}, [](auto keys, const auto &result) {
if (!result.error) {
keys.push_back(result.userIds.front().parent());
}
return keys;
});
const auto cmd = new ExportOpenPGPCertsToServerCommand(certificatesToSendToServer);
cmd->start();
}
if (!certificationOptions.tags.isEmpty()) {
Tags::enableTags();
}
finished();
}
CertifyGroupCommand::CertifyGroupCommand(const KeyGroup &group)
: Command{new Private{this}}
{
d->group = group;
}
CertifyGroupCommand::~CertifyGroupCommand() = default;
void CertifyGroupCommand::doStart()
{
d->start();
}
void CertifyGroupCommand::doCancel()
{
if (d->dialog) {
d->dialog->close();
}
if (d->job) {
d->job->slotCancel();
}
}
#undef d
#undef q
#include "moc_certifygroupcommand.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Apr 22, 3:56 AM (8 h, 42 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
8b/f6/d537a87b57b2abc7f11cedc8dcd0
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment