Page MenuHome GnuPG

No OneTemporary

diff --git a/src/commands/certifycertificatecommand.cpp b/src/commands/certifycertificatecommand.cpp
index 2c767215c..c2ff54ce1 100644
--- a/src/commands/certifycertificatecommand.cpp
+++ b/src/commands/certifycertificatecommand.cpp
@@ -1,347 +1,347 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/signcertificatecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2019 g10code GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "certifycertificatecommand.h"
#include "newopenpgpcertificatecommand.h"
#include "command_p.h"
#include "dialogs/certifycertificatedialog.h"
#include "exportopenpgpcertstoservercommand.h"
#include "utils/keys.h"
#include "utils/tags.h"
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <QGpgME/Protocol>
#include <QGpgME/SignKeyJob>
#include <QDate>
#include <QEventLoop>
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
#include <KLocalizedString>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
using namespace QGpgME;
class CertifyCertificateCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::CertifyCertificateCommand;
CertifyCertificateCommand *q_func() const
{
return static_cast<CertifyCertificateCommand *>(q);
}
public:
explicit Private(CertifyCertificateCommand *qq, KeyListController *c);
~Private() override;
void init();
private:
void slotDialogRejected();
void slotResult(const Error &err);
void slotCertificationPrepared();
private:
void ensureDialogCreated();
void createJob();
private:
GpgME::Key target;
std::vector<UserID> uids;
QPointer<CertifyCertificateDialog> dialog;
QPointer<QGpgME::SignKeyJob> job;
};
CertifyCertificateCommand::Private *CertifyCertificateCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const CertifyCertificateCommand::Private *CertifyCertificateCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
CertifyCertificateCommand::Private::Private(CertifyCertificateCommand *qq, KeyListController *c)
: Command::Private(qq, c)
, uids()
, dialog()
, job()
{
}
CertifyCertificateCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG);
if (dialog) {
delete dialog;
dialog = nullptr;
}
}
CertifyCertificateCommand::CertifyCertificateCommand(KeyListController *c)
: Command(new Private(this, c))
{
d->init();
}
CertifyCertificateCommand::CertifyCertificateCommand(QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
{
d->init();
}
CertifyCertificateCommand::CertifyCertificateCommand(const GpgME::Key &key)
: Command(key, new Private(this, nullptr))
{
d->init();
}
CertifyCertificateCommand::CertifyCertificateCommand(const GpgME::UserID &uid)
: Command(uid.parent(), new Private(this, nullptr))
{
std::vector<UserID>(1, uid).swap(d->uids);
d->init();
}
CertifyCertificateCommand::CertifyCertificateCommand(const std::vector<GpgME::UserID> &uids)
: Command(uids.empty() ? Key() : uids.front().parent(), new Private(this, nullptr))
{
d->uids = uids;
d->init();
}
void CertifyCertificateCommand::Private::init()
{
}
CertifyCertificateCommand::~CertifyCertificateCommand()
{
qCDebug(KLEOPATRA_LOG);
}
void CertifyCertificateCommand::doStart()
{
const std::vector<Key> keys = d->keys();
if (keys.size() != 1 || keys.front().protocol() != GpgME::OpenPGP) {
d->finished();
return;
}
// hold on to the key to certify to avoid invalidation during refreshes of the key cache
d->target = keys.front();
if (d->target.isExpired() || d->target.isRevoked()) {
const auto title = d->target.isRevoked() ? i18nc("@title:window", "Key is Revoked") : i18nc("@title:window", "Key is Expired");
const auto message = d->target.isRevoked() //
? i18nc("@info", "This key has been revoked. You cannot certify it.")
: i18nc("@info", "This key has expired. You cannot certify it.");
d->information(message, title);
d->finished();
return;
}
auto findAnyGoodKey = []() {
const std::vector<Key> secKeys = KeyCache::instance()->secretKeys();
return std::any_of(secKeys.cbegin(), secKeys.cend(), [](const Key &secKey) {
return secKey.canCertify() && secKey.protocol() == OpenPGP && !secKey.isRevoked() && !secKey.isExpired() && !secKey.isInvalid();
});
};
if (!findAnyGoodKey()) {
auto sel =
KMessageBox::questionTwoActions(d->parentWidgetOrView(),
xi18nc("@info", "To certify other certificates, you first need to create an OpenPGP certificate for yourself.")
+ QStringLiteral("<br><br>") + i18n("Do you wish to create one now?"),
- i18n("Certification Not Possible"),
+ i18nc("@title:window", "Certification Not Possible"),
KGuiItem(i18n("Create")),
KStandardGuiItem::cancel());
if (sel == KMessageBox::ButtonCode::PrimaryAction) {
QEventLoop loop;
auto cmd = new NewOpenPGPCertificateCommand;
cmd->setParentWidget(d->parentWidgetOrView());
connect(cmd, &Command::finished, &loop, &QEventLoop::quit);
QMetaObject::invokeMethod(cmd, &NewOpenPGPCertificateCommand::start, Qt::QueuedConnection);
loop.exec();
} else {
Q_EMIT(canceled());
d->finished();
return;
}
// Check again for secret keys
if (!findAnyGoodKey()) {
qCDebug(KLEOPATRA_LOG) << "Sec Keys still empty after keygen.";
Q_EMIT(canceled());
d->finished();
return;
}
}
const char *primary = keys.front().primaryFingerprint();
const bool anyMismatch = std::any_of(d->uids.cbegin(), d->uids.cend(), [primary](const UserID &uid) {
return qstricmp(uid.parent().primaryFingerprint(), primary) != 0;
});
if (anyMismatch) {
qCWarning(KLEOPATRA_LOG) << "User ID <-> Key mismatch!";
d->finished();
return;
}
d->ensureDialogCreated();
Q_ASSERT(d->dialog);
if (!(d->target.keyListMode() & GpgME::SignatureNotations)) {
d->target.update();
}
d->dialog->setCertificateToCertify(d->target, d->uids);
d->dialog->show();
}
void CertifyCertificateCommand::Private::slotDialogRejected()
{
Q_EMIT q->canceled();
finished();
}
void CertifyCertificateCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled()) {
// do nothing
} else if (err) {
error(i18n("<p>An error occurred while trying to certify<br/><br/>"
"<b>%1</b>:</p><p>\t%2</p>",
Formatting::formatForComboBox(target),
Formatting::errorAsString(err)),
i18n("Certification Error"));
} else if (dialog && dialog->exportableCertificationSelected() && dialog->sendToServer()) {
auto const cmd = new ExportOpenPGPCertsToServerCommand(target);
cmd->start();
} else {
information(i18n("Certification successful."), i18n("Certification Succeeded"));
}
if (!dialog->tags().isEmpty()) {
Tags::enableTags();
}
finished();
}
void CertifyCertificateCommand::Private::slotCertificationPrepared()
{
Q_ASSERT(dialog);
const auto selectedUserIds = dialog->selectedUserIDs();
std::vector<unsigned int> userIdIndexes;
userIdIndexes.reserve(selectedUserIds.size());
for (unsigned int i = 0, numUserIds = target.numUserIDs(); i < numUserIds; ++i) {
const auto userId = target.userID(i);
const bool userIdIsSelected = Kleo::any_of(selectedUserIds, [userId](const auto &uid) {
return Kleo::userIDsAreEqual(userId, uid);
});
if (userIdIsSelected) {
userIdIndexes.push_back(i);
}
}
createJob();
Q_ASSERT(job);
job->setExportable(dialog->exportableCertificationSelected());
job->setNonRevocable(dialog->nonRevocableCertificationSelected());
job->setUserIDsToSign(userIdIndexes);
job->setSigningKey(dialog->selectedSecretKey());
if (!dialog->tags().isEmpty()) {
// do not set an empty remark to avoid an empty signature notation (GnuPG bug T5142)
job->setRemark(dialog->tags());
}
job->setDupeOk(true);
if (dialog->trustSignatureSelected() && !dialog->trustSignatureDomain().isEmpty()) {
// always create level 1 trust signatures with complete trust
job->setTrustSignature(TrustSignatureTrust::Complete, 1, dialog->trustSignatureDomain());
}
if (!dialog->expirationDate().isNull()) {
job->setExpirationDate(dialog->expirationDate());
}
if (const Error err = job->start(target)) {
slotResult(err);
}
}
void CertifyCertificateCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG);
if (d->job) {
d->job->slotCancel();
}
}
void CertifyCertificateCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new CertifyCertificateDialog;
applyWindowID(dialog);
connect(dialog, &QDialog::rejected, q, [this]() {
slotDialogRejected();
});
connect(dialog, &QDialog::accepted, q, [this]() {
slotCertificationPrepared();
});
}
void CertifyCertificateCommand::Private::createJob()
{
Q_ASSERT(!job);
Q_ASSERT(target.protocol() == OpenPGP);
const auto backend = QGpgME::openpgp();
if (!backend) {
return;
}
SignKeyJob *const j = backend->signKeyJob();
if (!j) {
return;
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(j, &QGpgME::Job::jobProgress, q, &Command::progress);
#else
connect(j, &QGpgME::Job::progress, q, [this](const QString &, int current, int total) {
Q_EMIT q->progress(current, total);
});
#endif
connect(j, &SignKeyJob::result, q, [this](const GpgME::Error &result) {
slotResult(result);
});
job = j;
}
#undef d
#undef q
#include "moc_certifycertificatecommand.cpp"
diff --git a/src/commands/decryptverifyfilescommand.cpp b/src/commands/decryptverifyfilescommand.cpp
index 4fe457792..7318791ff 100644
--- a/src/commands/decryptverifyfilescommand.cpp
+++ b/src/commands/decryptverifyfilescommand.cpp
@@ -1,225 +1,225 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/decryptverifyfilescommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "decryptverifyfilescommand.h"
#include "viewemailfilescommand.h"
#include "command_p.h"
#include "fileoperationspreferences.h"
#include "crypto/autodecryptverifyfilescontroller.h"
#include "crypto/decryptverifyfilescontroller.h"
#include <utils/filedialog.h>
#include <Libkleo/Classify>
#include <Libkleo/Stl_Util>
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <exception>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Crypto;
class DecryptVerifyFilesCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::DecryptVerifyFilesCommand;
DecryptVerifyFilesCommand *q_func() const
{
return static_cast<DecryptVerifyFilesCommand *>(q);
}
public:
explicit Private(DecryptVerifyFilesCommand *qq, KeyListController *c, bool forceManualMode = false);
~Private() override;
QStringList selectFiles() const;
void init();
private:
void finishIfEverythingIsDone()
{
if (files.empty() && emailFiles.empty()) {
finished();
}
}
void slotControllerDone()
{
files.clear();
finishIfEverythingIsDone();
}
void slotControllerError(int, const QString &msg)
{
- KMessageBox::error(parentWidgetOrView(), msg, i18n("Decrypt/Verify Failed"));
+ KMessageBox::error(parentWidgetOrView(), msg, i18nc("@title:window", "Decrypt/Verify Failed"));
files.clear();
finishIfEverythingIsDone();
}
private:
QStringList files;
QStringList emailFiles;
std::shared_ptr<const ExecutionContext> shared_qq;
DecryptVerifyFilesController *mController;
};
DecryptVerifyFilesCommand::Private *DecryptVerifyFilesCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const DecryptVerifyFilesCommand::Private *DecryptVerifyFilesCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
DecryptVerifyFilesCommand::Private::Private(DecryptVerifyFilesCommand *qq, KeyListController *c, bool forceManualMode)
: Command::Private(qq, c)
, files()
, shared_qq(qq, [](DecryptVerifyFilesCommand *) {})
{
FileOperationsPreferences prefs;
if (!forceManualMode && prefs.autoDecryptVerify()) {
mController = new AutoDecryptVerifyFilesController();
} else {
mController = new DecryptVerifyFilesController();
}
}
DecryptVerifyFilesCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG);
delete mController;
}
DecryptVerifyFilesCommand::DecryptVerifyFilesCommand(KeyListController *c)
: Command(new Private(this, c))
{
d->init();
}
DecryptVerifyFilesCommand::DecryptVerifyFilesCommand(QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
{
d->init();
}
DecryptVerifyFilesCommand::DecryptVerifyFilesCommand(const QStringList &files, KeyListController *c, bool forceManualMode)
: Command(new Private(this, c, forceManualMode))
{
d->init();
d->files = files;
}
DecryptVerifyFilesCommand::DecryptVerifyFilesCommand(const QStringList &files, QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
{
d->init();
d->files = files;
}
void DecryptVerifyFilesCommand::Private::init()
{
mController->setExecutionContext(shared_qq);
connect(mController, &Controller::done, q, [this]() {
slotControllerDone();
});
connect(mController, &Controller::error, q, [this](int err, const QString &details) {
slotControllerError(err, details);
});
}
DecryptVerifyFilesCommand::~DecryptVerifyFilesCommand()
{
qCDebug(KLEOPATRA_LOG);
}
void DecryptVerifyFilesCommand::setFiles(const QStringList &files)
{
d->files = files;
}
void DecryptVerifyFilesCommand::setOperation(DecryptVerifyOperation op)
{
try {
d->mController->setOperation(op);
} catch (...) {
}
}
DecryptVerifyOperation DecryptVerifyFilesCommand::operation() const
{
return d->mController->operation();
}
void DecryptVerifyFilesCommand::doStart()
{
try {
if (d->files.empty()) {
d->files = d->selectFiles();
}
if (d->files.empty()) {
d->finished();
return;
}
for (const auto &file : std::as_const(d->files)) {
const unsigned int classification = classify(file);
if (classification & Class::MimeFile) {
d->emailFiles << file;
d->files.removeAll(file);
}
}
if (!d->emailFiles.empty()) {
const auto viewEmailCommand = new ViewEmailFilesCommand(d->emailFiles, nullptr);
connect(viewEmailCommand, &ViewEmailFilesCommand::finished, this, [this] {
d->emailFiles.clear();
d->finishIfEverythingIsDone();
});
viewEmailCommand->start();
}
if (d->files.empty()) {
return;
}
d->mController->setFiles(d->files);
d->mController->start();
} catch (const std::exception &e) {
d->information(i18n("An error occurred: %1", QString::fromLocal8Bit(e.what())), i18n("Decrypt/Verify Files Error"));
d->finished();
}
}
void DecryptVerifyFilesCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG);
d->mController->cancel();
}
QStringList DecryptVerifyFilesCommand::Private::selectFiles() const
{
return FileDialog::getOpenFileNames(parentWidgetOrView(), i18n("Select One or More Files to Decrypt and/or Verify"), QStringLiteral("enc"));
}
#undef d
#undef q
#include "moc_decryptverifyfilescommand.cpp"
diff --git a/src/commands/dumpcertificatecommand.cpp b/src/commands/dumpcertificatecommand.cpp
index 311f77167..ba3c92a33 100644
--- a/src/commands/dumpcertificatecommand.cpp
+++ b/src/commands/dumpcertificatecommand.cpp
@@ -1,360 +1,360 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/dumpcertificatecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "dumpcertificatecommand.h"
#include "command_p.h"
#include <Libkleo/GnuPG>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <KMessageBox>
#include <KProcess>
#include <KStandardGuiItem>
#include <QPushButton>
#include <QByteArray>
#include <QFontDatabase>
#include <QHBoxLayout>
#include <QPointer>
#include <QString>
#include <QTextEdit>
#include <QTimer>
#include <QVBoxLayout>
static const int PROCESS_TERMINATE_TIMEOUT = 5000; // milliseconds
namespace
{
class DumpCertificateDialog : public QDialog
{
Q_OBJECT
public:
explicit DumpCertificateDialog(QWidget *parent = nullptr)
: QDialog(parent)
, ui(this)
{
resize(600, 500);
}
Q_SIGNALS:
void updateRequested();
public Q_SLOTS:
void append(const QString &line)
{
ui.logTextWidget.append(line);
ui.logTextWidget.ensureCursorVisible();
}
void clear()
{
ui.logTextWidget.clear();
}
private:
struct Ui {
QTextEdit logTextWidget;
QPushButton updateButton, closeButton;
QVBoxLayout vlay;
QHBoxLayout hlay;
explicit Ui(DumpCertificateDialog *q)
: logTextWidget(q)
, updateButton(i18nc("@action:button Update the log text widget", "&Update"), q)
, closeButton(q)
, vlay(q)
, hlay()
{
KGuiItem::assign(&closeButton, KStandardGuiItem::close());
KDAB_SET_OBJECT_NAME(logTextWidget);
KDAB_SET_OBJECT_NAME(updateButton);
KDAB_SET_OBJECT_NAME(closeButton);
KDAB_SET_OBJECT_NAME(vlay);
KDAB_SET_OBJECT_NAME(hlay);
logTextWidget.setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
logTextWidget.setReadOnly(true);
logTextWidget.setWordWrapMode(QTextOption::NoWrap);
vlay.addWidget(&logTextWidget, 1);
vlay.addLayout(&hlay);
hlay.addWidget(&updateButton);
hlay.addStretch(1);
hlay.addWidget(&closeButton);
connect(&updateButton, &QAbstractButton::clicked, q, &DumpCertificateDialog::updateRequested);
connect(&closeButton, &QAbstractButton::clicked, q, &QWidget::close);
}
} ui;
};
}
using namespace Kleo;
using namespace Kleo::Commands;
static QByteArray chomped(QByteArray ba)
{
while (ba.endsWith('\n') || ba.endsWith('\r')) {
ba.chop(1);
}
return ba;
}
class DumpCertificateCommand::Private : Command::Private
{
friend class ::Kleo::Commands::DumpCertificateCommand;
DumpCertificateCommand *q_func() const
{
return static_cast<DumpCertificateCommand *>(q);
}
public:
explicit Private(DumpCertificateCommand *qq, KeyListController *c);
~Private() override;
QString errorString() const
{
return QString::fromLocal8Bit(errorBuffer);
}
private:
void init();
void refreshView();
private:
void slotProcessFinished(int, QProcess::ExitStatus);
void slotProcessReadyReadStandardOutput()
{
while (process.canReadLine()) {
const QString line = Kleo::stringFromGpgOutput(chomped(process.readLine()));
if (dialog) {
dialog->append(line);
}
outputBuffer.push_back(line);
}
}
void slotProcessReadyReadStandardError()
{
errorBuffer += process.readAllStandardError();
}
void slotUpdateRequested()
{
if (process.state() == QProcess::NotRunning) {
refreshView();
}
}
void slotDialogDestroyed()
{
dialog = nullptr;
if (process.state() != QProcess::NotRunning) {
q->cancel();
} else {
finished();
}
}
private:
QPointer<DumpCertificateDialog> dialog;
KProcess process;
QByteArray errorBuffer;
QStringList outputBuffer;
bool useDialog;
bool canceled;
};
DumpCertificateCommand::Private *DumpCertificateCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const DumpCertificateCommand::Private *DumpCertificateCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
DumpCertificateCommand::Private::Private(DumpCertificateCommand *qq, KeyListController *c)
: Command::Private(qq, c)
, process()
, errorBuffer()
, outputBuffer()
, useDialog(true)
, canceled(false)
{
process.setOutputChannelMode(KProcess::SeparateChannels);
process.setReadChannel(KProcess::StandardOutput);
}
DumpCertificateCommand::Private::~Private()
{
if (dialog && !dialog->isVisible()) {
delete dialog;
}
}
DumpCertificateCommand::DumpCertificateCommand(KeyListController *c)
: Command(new Private(this, c))
{
d->init();
}
DumpCertificateCommand::DumpCertificateCommand(QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
{
d->init();
}
DumpCertificateCommand::DumpCertificateCommand(const GpgME::Key &k)
: Command(k, new Private(this, nullptr))
{
d->init();
}
void DumpCertificateCommand::Private::init()
{
#if QT_DEPRECATED_SINCE(5, 13)
connect(&process,
qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
#else
connect(&process,
&QProcess::finished,
#endif
q,
[this](int exitCode, QProcess::ExitStatus status) {
slotProcessFinished(exitCode, status);
});
connect(&process, &QProcess::readyReadStandardError, q, [this]() {
slotProcessReadyReadStandardError();
});
connect(&process, &QProcess::readyReadStandardOutput, q, [this] {
slotProcessReadyReadStandardOutput();
});
if (!key().isNull()) {
process << gpgSmPath() << QStringLiteral("--dump-cert") << QLatin1String(key().primaryFingerprint());
}
}
DumpCertificateCommand::~DumpCertificateCommand()
{
}
void DumpCertificateCommand::setUseDialog(bool use)
{
d->useDialog = use;
}
bool DumpCertificateCommand::useDialog() const
{
return d->useDialog;
}
QStringList DumpCertificateCommand::output() const
{
return d->outputBuffer;
}
void DumpCertificateCommand::doStart()
{
const std::vector<GpgME::Key> keys = d->keys();
if (keys.size() != 1 || keys.front().protocol() != GpgME::CMS) {
d->finished();
return;
}
if (d->useDialog) {
d->dialog = new DumpCertificateDialog;
d->applyWindowID(d->dialog);
d->dialog->setAttribute(Qt::WA_DeleteOnClose);
d->dialog->setWindowTitle(i18nc("@title:window", "Certificate Dump"));
connect(d->dialog, &DumpCertificateDialog::updateRequested, this, [this]() {
d->slotUpdateRequested();
});
connect(d->dialog, &QObject::destroyed, this, [this]() {
d->slotDialogDestroyed();
});
}
d->refreshView();
}
void DumpCertificateCommand::Private::refreshView()
{
if (dialog) {
dialog->clear();
}
errorBuffer.clear();
outputBuffer.clear();
process.start();
if (process.waitForStarted()) {
if (dialog) {
dialog->show();
}
} else {
KMessageBox::error(dialog ? static_cast<QWidget *>(dialog) : parentWidgetOrView(),
i18n("Unable to start process gpgsm. "
"Please check your installation."),
i18n("Dump Certificate Error"));
finished();
}
}
void DumpCertificateCommand::doCancel()
{
d->canceled = true;
if (d->process.state() != QProcess::NotRunning) {
d->process.terminate();
QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, &d->process, &QProcess::kill);
}
if (d->dialog) {
d->dialog->close();
}
d->dialog = nullptr;
}
void DumpCertificateCommand::Private::slotProcessFinished(int code, QProcess::ExitStatus status)
{
if (!canceled) {
if (status == QProcess::CrashExit)
KMessageBox::error(dialog,
i18n("The GpgSM process that tried to dump the certificate "
"ended prematurely because of an unexpected error. "
"Please check the output of gpgsm --dump-cert %1 for details.",
QLatin1String(key().primaryFingerprint())),
- i18n("Dump Certificate Error"));
+ i18nc("@title:window", "Dump Certificate Error"));
else if (code)
KMessageBox::error(dialog,
i18n("An error occurred while trying to dump the certificate. "
"The output from GpgSM was:\n%1",
errorString()),
- i18n("Dump Certificate Error"));
+ i18nc("@title:window", "Dump Certificate Error"));
}
if (!useDialog) {
slotDialogDestroyed();
}
}
#undef d
#undef q
#include "dumpcertificatecommand.moc"
#include "moc_dumpcertificatecommand.cpp"
diff --git a/src/commands/dumpcrlcachecommand.cpp b/src/commands/dumpcrlcachecommand.cpp
index 512d10ef4..a17a01973 100644
--- a/src/commands/dumpcrlcachecommand.cpp
+++ b/src/commands/dumpcrlcachecommand.cpp
@@ -1,365 +1,365 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/dumpcrlcachecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "dumpcrlcachecommand.h"
#include "command_p.h"
#include <Libkleo/GnuPG>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMessageBox>
#include <KProcess>
#include <KStandardGuiItem>
#include <QPushButton>
#include <KSharedConfig>
#include <QByteArray>
#include <QFontDatabase>
#include <QHBoxLayout>
#include <QString>
#include <QTextEdit>
#include <QTimer>
#include <QVBoxLayout>
static const int PROCESS_TERMINATE_TIMEOUT = 5000; // milliseconds
namespace
{
class DumpCrlCacheDialog : public QDialog
{
Q_OBJECT
public:
explicit DumpCrlCacheDialog(QWidget *parent = nullptr)
: QDialog(parent)
, ui(this)
, mWithRevocations(false)
{
readConfig();
}
~DumpCrlCacheDialog() override
{
writeConfig();
}
Q_SIGNALS:
void updateRequested();
public Q_SLOTS:
void append(const QString &line)
{
ui.logTextWidget.append(line);
ui.logTextWidget.ensureCursorVisible();
}
void clear()
{
ui.logTextWidget.clear();
}
public:
void setWithRevocations(bool value)
{
mWithRevocations = value;
}
Q_REQUIRED_RESULT bool withRevocations()
{
return mWithRevocations;
}
private:
void readConfig()
{
KConfigGroup dialog(KSharedConfig::openStateConfig(), "DumpCrlCacheDialog");
const QSize size = dialog.readEntry("Size", QSize(600, 400));
if (size.isValid()) {
resize(size);
}
}
void writeConfig()
{
KConfigGroup dialog(KSharedConfig::openStateConfig(), "DumpCrlCacheDialog");
dialog.writeEntry("Size", size());
dialog.sync();
}
struct Ui {
QTextEdit logTextWidget;
QPushButton updateButton, closeButton, revocationsButton;
QVBoxLayout vlay;
QHBoxLayout hlay;
explicit Ui(DumpCrlCacheDialog *q)
: logTextWidget(q)
, updateButton(i18nc("@action:button Update the log text widget", "&Update"), q)
, closeButton(q)
, vlay(q)
, hlay()
{
KGuiItem::assign(&closeButton, KStandardGuiItem::close());
KDAB_SET_OBJECT_NAME(logTextWidget);
KDAB_SET_OBJECT_NAME(updateButton);
KDAB_SET_OBJECT_NAME(closeButton);
KDAB_SET_OBJECT_NAME(vlay);
KDAB_SET_OBJECT_NAME(hlay);
logTextWidget.setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
logTextWidget.setReadOnly(true);
vlay.addWidget(&logTextWidget, 1);
vlay.addLayout(&hlay);
revocationsButton.setText(i18n("Show Entries"));
hlay.addWidget(&updateButton);
hlay.addWidget(&revocationsButton);
hlay.addStretch(1);
hlay.addWidget(&closeButton);
connect(&updateButton, &QAbstractButton::clicked, q, &DumpCrlCacheDialog::updateRequested);
connect(&closeButton, &QAbstractButton::clicked, q, &QWidget::close);
connect(&revocationsButton, &QAbstractButton::clicked, q, [q, this]() {
q->mWithRevocations = true;
revocationsButton.setEnabled(false);
q->updateRequested();
});
}
} ui;
bool mWithRevocations;
};
}
using namespace Kleo;
using namespace Kleo::Commands;
static QByteArray chomped(QByteArray ba)
{
while (ba.endsWith('\n') || ba.endsWith('\r')) {
ba.chop(1);
}
return ba;
}
class DumpCrlCacheCommand::Private : Command::Private
{
friend class ::Kleo::Commands::DumpCrlCacheCommand;
DumpCrlCacheCommand *q_func() const
{
return static_cast<DumpCrlCacheCommand *>(q);
}
public:
explicit Private(DumpCrlCacheCommand *qq, KeyListController *c);
~Private() override;
QString errorString() const
{
return QString::fromLocal8Bit(errorBuffer);
}
private:
void init();
void refreshView();
private:
void slotProcessFinished(int, QProcess::ExitStatus);
void slotProcessReadyReadStandardOutput()
{
static int count;
while (process.canReadLine()) {
if (!dialog) {
break;
}
const QByteArray line = chomped(process.readLine());
if (!line.size()) {
continue;
}
if (!dialog->withRevocations() && line.contains("reasons")) {
count++;
continue;
} else if (count) {
dialog->append(QLatin1Char(' ') + i18nc("Count of revocations in a CRL", "Entries:") + QStringLiteral("\t\t%1\n").arg(count));
count = 0;
}
dialog->append(stringFromGpgOutput(line));
}
}
void slotProcessReadyReadStandardError()
{
errorBuffer += process.readAllStandardError();
}
void slotUpdateRequested()
{
if (process.state() == QProcess::NotRunning) {
refreshView();
}
}
void slotDialogDestroyed()
{
dialog = nullptr;
if (process.state() != QProcess::NotRunning) {
q->cancel();
} else {
finished();
}
}
private:
DumpCrlCacheDialog *dialog;
KProcess process;
QByteArray errorBuffer;
bool canceled;
};
DumpCrlCacheCommand::Private *DumpCrlCacheCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const DumpCrlCacheCommand::Private *DumpCrlCacheCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
DumpCrlCacheCommand::Private::Private(DumpCrlCacheCommand *qq, KeyListController *c)
: Command::Private(qq, c)
, dialog(nullptr)
, process()
, errorBuffer()
, canceled(false)
{
process.setOutputChannelMode(KProcess::SeparateChannels);
process.setReadChannel(KProcess::StandardOutput);
process << gpgSmPath() << QStringLiteral("--call-dirmngr") << QStringLiteral("listcrls");
}
DumpCrlCacheCommand::Private::~Private()
{
if (dialog && !dialog->isVisible()) {
delete dialog;
}
}
DumpCrlCacheCommand::DumpCrlCacheCommand(KeyListController *c)
: Command(new Private(this, c))
{
d->init();
}
DumpCrlCacheCommand::DumpCrlCacheCommand(QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
{
d->init();
}
void DumpCrlCacheCommand::Private::init()
{
#if QT_DEPRECATED_SINCE(5, 13)
connect(&process,
qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
#else
connect(&process,
&QProcess::finished,
#endif
q,
[this](int exitCode, QProcess::ExitStatus status) {
slotProcessFinished(exitCode, status);
});
connect(&process, &QProcess::readyReadStandardError, q, [this]() {
slotProcessReadyReadStandardError();
});
connect(&process, &QProcess::readyReadStandardOutput, q, [this] {
slotProcessReadyReadStandardOutput();
});
}
DumpCrlCacheCommand::~DumpCrlCacheCommand()
{
}
void DumpCrlCacheCommand::doStart()
{
d->dialog = new DumpCrlCacheDialog;
d->dialog->setAttribute(Qt::WA_DeleteOnClose);
d->dialog->setWindowTitle(i18nc("@title:window", "CRL Cache Dump"));
connect(d->dialog, &DumpCrlCacheDialog::updateRequested, this, [this]() {
d->slotUpdateRequested();
});
connect(d->dialog, &QObject::destroyed, this, [this]() {
d->slotDialogDestroyed();
});
d->refreshView();
}
void DumpCrlCacheCommand::Private::refreshView()
{
dialog->clear();
process.start();
if (process.waitForStarted()) {
dialog->show();
} else {
KMessageBox::error(dialog ? static_cast<QWidget *>(dialog) : parentWidgetOrView(),
i18n("Unable to start process gpgsm. "
"Please check your installation."),
i18n("Dump CRL Cache Error"));
finished();
}
}
void DumpCrlCacheCommand::doCancel()
{
d->canceled = true;
if (d->process.state() != QProcess::NotRunning) {
d->process.terminate();
QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, &d->process, &QProcess::kill);
}
if (d->dialog) {
d->dialog->close();
}
d->dialog = nullptr;
}
void DumpCrlCacheCommand::Private::slotProcessFinished(int code, QProcess::ExitStatus status)
{
if (!canceled) {
if (status == QProcess::CrashExit)
KMessageBox::error(dialog,
i18n("The GpgSM process that tried to dump the CRL cache "
"ended prematurely because of an unexpected error. "
"Please check the output of gpgsm --call-dirmngr listcrls for details."),
- i18n("Dump CRL Cache Error"));
+ i18nc("@title:window", "Dump CRL Cache Error"));
else if (code)
KMessageBox::error(dialog,
i18n("An error occurred while trying to dump the CRL cache. "
"The output from GpgSM was:\n%1",
errorString()),
- i18n("Dump CRL Cache Error"));
+ i18nc("@title:window", "Dump CRL Cache Error"));
}
}
#undef d
#undef q
#include "dumpcrlcachecommand.moc"
#include "moc_dumpcrlcachecommand.cpp"
diff --git a/src/commands/exportcertificatecommand.cpp b/src/commands/exportcertificatecommand.cpp
index 48332908b..e89dd89fd 100644
--- a/src/commands/exportcertificatecommand.cpp
+++ b/src/commands/exportcertificatecommand.cpp
@@ -1,366 +1,368 @@
/* -*- mode: c++; c-basic-offset:4 -*-
exportcertificatecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "exportcertificatecommand.h"
#include "fileoperationspreferences.h"
#include "command_p.h"
#include <utils/applicationstate.h>
#include <utils/filedialog.h>
#include <Libkleo/Classify>
#include <Libkleo/Formatting>
#include <QGpgME/ExportJob>
#include <QGpgME/Protocol>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QSaveFile>
#include <QFileInfo>
#include <QMap>
#include <QPointer>
#include <algorithm>
#include <vector>
using namespace Kleo;
using namespace GpgME;
using namespace QGpgME;
class ExportCertificateCommand::Private : public Command::Private
{
friend class ::ExportCertificateCommand;
ExportCertificateCommand *q_func() const
{
return static_cast<ExportCertificateCommand *>(q);
}
public:
explicit Private(ExportCertificateCommand *qq, KeyListController *c);
~Private() override;
void startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys);
void cancelJobs();
void exportResult(const GpgME::Error &, const QByteArray &);
void showError(const GpgME::Error &error);
bool requestFileNames(GpgME::Protocol prot);
void finishedIfLastJob();
private:
QMap<GpgME::Protocol, QString> fileNames;
uint jobsPending = 0;
QMap<QObject *, QString> outFileForSender;
QPointer<ExportJob> cmsJob;
QPointer<ExportJob> pgpJob;
};
ExportCertificateCommand::Private *ExportCertificateCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ExportCertificateCommand::Private *ExportCertificateCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ExportCertificateCommand::Private::Private(ExportCertificateCommand *qq, KeyListController *c)
: Command::Private(qq, c)
{
}
ExportCertificateCommand::Private::~Private()
{
}
ExportCertificateCommand::ExportCertificateCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
ExportCertificateCommand::ExportCertificateCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
ExportCertificateCommand::ExportCertificateCommand(const Key &key)
: Command(key, new Private(this, nullptr))
{
}
ExportCertificateCommand::~ExportCertificateCommand()
{
}
void ExportCertificateCommand::setOpenPGPFileName(const QString &fileName)
{
if (!d->jobsPending) {
d->fileNames[OpenPGP] = fileName;
}
}
QString ExportCertificateCommand::openPGPFileName() const
{
return d->fileNames[OpenPGP];
}
void ExportCertificateCommand::setX509FileName(const QString &fileName)
{
if (!d->jobsPending) {
d->fileNames[CMS] = fileName;
}
}
QString ExportCertificateCommand::x509FileName() const
{
return d->fileNames[CMS];
}
void ExportCertificateCommand::doStart()
{
std::vector<Key> keys = d->keys();
if (keys.empty()) {
return;
}
const auto firstCms = std::partition(keys.begin(), keys.end(), [](const GpgME::Key &key) {
return key.protocol() != GpgME::CMS;
});
std::vector<Key> openpgp, cms;
std::copy(keys.begin(), firstCms, std::back_inserter(openpgp));
std::copy(firstCms, keys.end(), std::back_inserter(cms));
Q_ASSERT(!openpgp.empty() || !cms.empty());
const bool haveBoth = !cms.empty() && !openpgp.empty();
const GpgME::Protocol prot = haveBoth ? UnknownProtocol : (!cms.empty() ? CMS : OpenPGP);
if (!d->requestFileNames(prot)) {
Q_EMIT canceled();
d->finished();
} else {
if (!openpgp.empty()) {
d->startExportJob(GpgME::OpenPGP, openpgp);
}
if (!cms.empty()) {
d->startExportJob(GpgME::CMS, cms);
}
}
}
bool ExportCertificateCommand::Private::requestFileNames(GpgME::Protocol protocol)
{
if (protocol == UnknownProtocol) {
if (!fileNames[GpgME::OpenPGP].isEmpty() && !fileNames[GpgME::CMS].isEmpty()) {
return true;
}
/* Unknown protocol ask for first PGP Export file name */
if (fileNames[GpgME::OpenPGP].isEmpty() && !requestFileNames(GpgME::OpenPGP)) {
return false;
}
/* And then for CMS */
return requestFileNames(GpgME::CMS);
}
if (!fileNames[protocol].isEmpty()) {
return true;
}
const auto lastDir = ApplicationState::lastUsedExportDirectory();
QString proposedFileName = lastDir + QLatin1Char('/');
if (keys().size() == 1) {
const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt();
const auto key = keys().front();
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
const auto asciiArmoredCertificateClass = (protocol == OpenPGP ? Class::OpenPGP : Class::CMS) | Class::Ascii | Class::Certificate;
/* Not translated so it's better to use in tutorials etc. */
proposedFileName += QStringLiteral("%1_%2_public.%3")
.arg(name)
.arg(Formatting::prettyKeyID(key.shortKeyID()))
.arg(outputFileExtension(asciiArmoredCertificateClass, usePGPFileExt));
}
if (protocol == GpgME::CMS) {
if (!fileNames[GpgME::OpenPGP].isEmpty()) {
/* If the user has already selected a PGP file name then use that as basis
* for a proposal for the S/MIME file. */
proposedFileName = fileNames[GpgME::OpenPGP];
const int idx = proposedFileName.size() - 4;
if (proposedFileName.endsWith(QLatin1String(".asc"))) {
proposedFileName.replace(idx, 4, QLatin1String(".pem"));
}
if (proposedFileName.endsWith(QLatin1String(".gpg")) || proposedFileName.endsWith(QLatin1String(".pgp"))) {
proposedFileName.replace(idx, 4, QLatin1String(".der"));
}
}
}
if (proposedFileName.isEmpty()) {
proposedFileName = lastDir;
proposedFileName += i18nc("A generic filename for exported certificates", "certificates");
proposedFileName += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem");
}
auto fname = FileDialog::getSaveFileNameEx(parentWidgetOrView(),
i18nc("1 is protocol", "Export %1 Certificates", Formatting::displayName(protocol)),
QStringLiteral("imp"),
proposedFileName,
protocol == GpgME::OpenPGP ? i18n("OpenPGP Certificates") + QLatin1String(" (*.asc *.gpg *.pgp)")
: i18n("S/MIME Certificates") + QLatin1String(" (*.pem *.der)"));
if (!fname.isEmpty() && protocol == GpgME::CMS && fileNames[GpgME::OpenPGP] == fname) {
- KMessageBox::error(parentWidgetOrView(), i18n("You have to select different filenames for different protocols."), i18n("Export Error"));
+ KMessageBox::error(parentWidgetOrView(),
+ i18n("You have to select different filenames for different protocols."),
+ i18nc("@title:window", "Export Error"));
return false;
}
const QFileInfo fi(fname);
if (fi.suffix().isEmpty()) {
fname += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem");
}
fileNames[protocol] = fname;
ApplicationState::setLastUsedExportDirectory(fi.absolutePath());
return !fname.isEmpty();
}
void ExportCertificateCommand::Private::startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys)
{
Q_ASSERT(protocol != GpgME::UnknownProtocol);
const QGpgME::Protocol *const backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
const QString fileName = fileNames[protocol];
const bool binary = protocol == GpgME::OpenPGP
? fileName.endsWith(QLatin1String(".gpg"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String(".pgp"), Qt::CaseInsensitive)
: fileName.endsWith(QLatin1String(".der"), Qt::CaseInsensitive);
std::unique_ptr<ExportJob> job(backend->publicKeyExportJob(!binary));
Q_ASSERT(job.get());
connect(job.get(), &QGpgME::ExportJob::result, q, [this](const GpgME::Error &result, const QByteArray &keyData) {
exportResult(result, keyData);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress);
#else
connect(job.get(), &QGpgME::Job::progress, q, [this](const QString &, int current, int total) {
Q_EMIT q->progress(current, total);
});
#endif
QStringList fingerprints;
fingerprints.reserve(keys.size());
for (const Key &i : keys) {
fingerprints << QLatin1String(i.primaryFingerprint());
}
const GpgME::Error err = job->start(fingerprints);
if (err) {
showError(err);
finished();
return;
}
Q_EMIT q->info(i18n("Exporting certificates..."));
++jobsPending;
const QPointer<ExportJob> exportJob(job.release());
outFileForSender[exportJob.data()] = fileName;
(protocol == CMS ? cmsJob : pgpJob) = exportJob;
}
void ExportCertificateCommand::Private::showError(const GpgME::Error &err)
{
Q_ASSERT(err);
const QString msg = i18n(
"<qt><p>An error occurred while trying to export "
"the certificate:</p>"
"<p><b>%1</b></p></qt>",
Formatting::errorAsString(err));
error(msg, i18n("Certificate Export Failed"));
}
void ExportCertificateCommand::doCancel()
{
d->cancelJobs();
}
void ExportCertificateCommand::Private::finishedIfLastJob()
{
if (jobsPending <= 0) {
finished();
}
}
static bool write_complete(QIODevice &iod, const QByteArray &data)
{
qint64 total = 0;
qint64 toWrite = data.size();
while (total < toWrite) {
const qint64 written = iod.write(data.data() + total, toWrite);
if (written < 0) {
return false;
}
total += written;
toWrite -= written;
}
return true;
}
void ExportCertificateCommand::Private::exportResult(const GpgME::Error &err, const QByteArray &data)
{
Q_ASSERT(jobsPending > 0);
--jobsPending;
Q_ASSERT(outFileForSender.contains(q->sender()));
const QString outFile = outFileForSender[q->sender()];
if (err) {
showError(err);
finishedIfLastJob();
return;
}
QSaveFile savefile(outFile);
// TODO: use KIO
const QString writeErrorMsg = i18n("Could not write to file %1.", outFile);
const QString errorCaption = i18n("Certificate Export Failed");
if (!savefile.open(QIODevice::WriteOnly)) {
error(writeErrorMsg, errorCaption);
finishedIfLastJob();
return;
}
if (!write_complete(savefile, data) || !savefile.commit()) {
error(writeErrorMsg, errorCaption);
}
finishedIfLastJob();
}
void ExportCertificateCommand::Private::cancelJobs()
{
if (cmsJob) {
cmsJob->slotCancel();
}
if (pgpJob) {
pgpJob->slotCancel();
}
}
#undef d
#undef q
#include "moc_exportcertificatecommand.cpp"
diff --git a/src/commands/genrevokecommand.cpp b/src/commands/genrevokecommand.cpp
index 5371b2b84..64e1fc99c 100644
--- a/src/commands/genrevokecommand.cpp
+++ b/src/commands/genrevokecommand.cpp
@@ -1,203 +1,203 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/genrevokecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "genrevokecommand.h"
#include <utils/applicationstate.h>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <KMessageBox>
#include <QFile>
#include <QFileDialog>
#include <QProcess>
#include <QTextStream>
#include "command_p.h"
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
GenRevokeCommand::GenRevokeCommand(QAbstractItemView *v, KeyListController *c)
: GnuPGProcessCommand(v, c)
{
}
GenRevokeCommand::GenRevokeCommand(KeyListController *c)
: GnuPGProcessCommand(c)
{
}
GenRevokeCommand::GenRevokeCommand(const Key &key)
: GnuPGProcessCommand(key)
{
}
// Fixup the revocation certificate similar to GnuPG
void GenRevokeCommand::postSuccessHook(QWidget *parentWidget)
{
QFile f(mOutputFileName);
if (!f.open(QIODevice::ReadOnly)) {
// Should never happen because in this case we would not have had a success.
KMessageBox::error(parentWidget, QStringLiteral("Failed to access the created output file."), errorCaption());
return;
}
const QString revCert = QString::fromLocal8Bit(f.readAll());
f.close();
if (!f.open(QIODevice::WriteOnly)) {
KMessageBox::error(parentWidget, QStringLiteral("Failed to write to the created output file."), errorCaption());
return;
}
QTextStream s(&f);
s << i18n("This is a revocation certificate for the OpenPGP key:") << "\n\n" //
<< " " << Formatting::prettyNameAndEMail(d->key()) << "\n" //
<< "Fingerprint: " << d->key().primaryFingerprint() << "\n\n"
<< i18n(
"A revocation certificate is a kind of \"kill switch\" to publicly\n"
"declare that a key shall not anymore be used. It is not possible\n"
"to retract such a revocation certificate once it has been published.")
<< "\n\n"
<< i18n(
"Use it to revoke this key in case of a compromise or loss of\n"
"the secret key.")
<< "\n\n"
<< i18n(
"To avoid an accidental use of this file, a colon has been inserted\n"
"before the 5 dashes below. Remove this colon with a text editor\n"
"before importing and publishing this revocation certificate.")
<< "\n\n:" << revCert;
s.flush();
qCDebug(KLEOPATRA_LOG) << "revocation certificate stored as:" << mOutputFileName;
f.close();
KMessageBox::information(d->parentWidgetOrView(),
i18nc("@info",
"Certificate successfully created.<br><br>"
"Note:<br>To prevent accidental import of the revocation<br>"
"it is required to manually edit the certificate<br>"
"before it can be imported."),
- i18n("Revocation certificate created"));
+ i18nc("@title:window", "Revocation certificate created"));
}
/* Well not much to do with GnuPGProcessCommand anymore I guess.. */
void GenRevokeCommand::doStart()
{
auto proposedFileName = ApplicationState::lastUsedExportDirectory() + u'/' + QString::fromLatin1(d->key().primaryFingerprint()) + QLatin1String{".rev"};
while (mOutputFileName.isEmpty()) {
mOutputFileName = QFileDialog::getSaveFileName(d->parentWidgetOrView(),
i18n("Generate revocation certificate"),
proposedFileName,
QStringLiteral("%1 (*.rev)").arg(i18n("Revocation Certificates ")),
{},
QFileDialog::DontConfirmOverwrite);
if (mOutputFileName.isEmpty()) {
d->finished();
return;
}
if (!mOutputFileName.endsWith(QLatin1String(".rev"))) {
mOutputFileName += QLatin1String(".rev");
}
const QFileInfo fi{mOutputFileName};
if (fi.exists()) {
auto sel = KMessageBox::questionTwoActions(d->parentWidgetOrView(),
xi18n("The file <filename>%1</filename> already exists. Do you wish to overwrite it?", fi.fileName()),
i18nc("@title:window", "Overwrite File?"),
KStandardGuiItem::overwrite(),
KStandardGuiItem::cancel(),
{},
KMessageBox::Notify | KMessageBox::Dangerous);
if (sel == KMessageBox::ButtonCode::SecondaryAction) {
proposedFileName = mOutputFileName;
mOutputFileName.clear();
}
}
}
ApplicationState::setLastUsedExportDirectory(mOutputFileName);
auto proc = process();
// We do custom io
disconnect(m_procReadyReadStdErrConnection);
proc->setReadChannel(QProcess::StandardOutput);
GnuPGProcessCommand::doStart();
connect(proc, &QProcess::readyReadStandardOutput, this, [proc]() {
while (proc->canReadLine()) {
const QString line = QString::fromUtf8(proc->readLine()).trimmed();
// Command-fd is a stable interface, while this is all kind of hacky we
// are on a deadline :-/
if (line == QLatin1String("[GNUPG:] GET_BOOL gen_revoke.okay")) {
proc->write("y\n");
} else if (line == QLatin1String("[GNUPG:] GET_LINE ask_revocation_reason.code")) {
proc->write("0\n");
} else if (line == QLatin1String("[GNUPG:] GET_LINE ask_revocation_reason.text")) {
proc->write("\n");
} else if (line == QLatin1String("[GNUPG:] GET_BOOL openfile.overwrite.okay")) {
// We asked before
proc->write("y\n");
} else if (line == QLatin1String("[GNUPG:] GET_BOOL ask_revocation_reason.okay")) {
proc->write("y\n");
}
}
});
}
QStringList GenRevokeCommand::arguments() const
{
const Key key = d->key();
return {
gpgPath(),
QStringLiteral("--command-fd"),
QStringLiteral("0"),
QStringLiteral("--status-fd"),
QStringLiteral("1"),
QStringLiteral("-o"),
mOutputFileName,
QStringLiteral("--gen-revoke"),
QLatin1String(key.primaryFingerprint()),
};
}
QString GenRevokeCommand::errorCaption() const
{
return i18nc("@title:window", "Error creating revocation certificate");
}
QString GenRevokeCommand::crashExitMessage(const QStringList &) const
{
// We show a success message so a failure is either the user aborted
// or a bug.
qCDebug(KLEOPATRA_LOG) << "Crash exit of GenRevokeCommand";
return QString();
}
QString GenRevokeCommand::errorExitMessage(const QStringList &) const
{
// We show a success message so a failure is either the user aborted
// or a bug.
qCDebug(KLEOPATRA_LOG) << "Error exit of GenRevokeCommand";
return QString();
}
#include "moc_genrevokecommand.cpp"
diff --git a/src/conf/kleopageconfigdialog.cpp b/src/conf/kleopageconfigdialog.cpp
index 281b88597..bc9886ee9 100644
--- a/src/conf/kleopageconfigdialog.cpp
+++ b/src/conf/kleopageconfigdialog.cpp
@@ -1,239 +1,239 @@
/*
kleopageconfigdialog.cpp
This file is part of Kleopatra
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-only
It is derived from KCMultidialog which is:
SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
SPDX-FileCopyrightText: 2003 Daniel Molkentin <molkentin@kde.org>
SPDX-FileCopyrightText: 2003, 2006 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "kleopageconfigdialog.h"
#include <QDesktopServices>
#include <QDialogButtonBox>
#include <QProcess>
#include <QPushButton>
#include <QStandardPaths>
#include <QUrl>
#include <KCModule>
#include <KLocalizedString>
#include <KMessageBox>
#include <KStandardGuiItem>
#include "kleopatra_debug.h"
KleoPageConfigDialog::KleoPageConfigDialog(QWidget *parent)
: KPageDialog(parent)
{
setModal(false);
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
buttonBox->setStandardButtons(QDialogButtonBox::Help //
| QDialogButtonBox::RestoreDefaults //
| QDialogButtonBox::Cancel //
| QDialogButtonBox::Apply //
| QDialogButtonBox::Ok //
| QDialogButtonBox::Reset);
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::apply());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Reset), KStandardGuiItem::reset());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Help), KStandardGuiItem::help());
buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false);
buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &KleoPageConfigDialog::slotApplyClicked);
connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, &KleoPageConfigDialog::slotOkClicked);
connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, &KleoPageConfigDialog::slotDefaultClicked);
connect(buttonBox->button(QDialogButtonBox::Help), &QAbstractButton::clicked, this, &KleoPageConfigDialog::slotHelpClicked);
connect(buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, this, &KleoPageConfigDialog::slotUser1Clicked);
setButtonBox(buttonBox);
connect(this, &KPageDialog::currentPageChanged, this, &KleoPageConfigDialog::slotCurrentPageChanged);
}
void KleoPageConfigDialog::slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem *previous)
{
if (!previous) {
return;
}
blockSignals(true);
setCurrentPage(previous);
KCModule *previousModule = qobject_cast<KCModule *>(previous->widget());
bool canceled = false;
if (previousModule && mChangedModules.contains(previousModule)) {
const int queryUser = KMessageBox::warningTwoActionsCancel(this,
i18n("The settings of the current module have changed.\n"
"Do you want to apply the changes or discard them?"),
- i18n("Apply Settings"),
+ i18nc("@title:window", "Apply Settings"),
KStandardGuiItem::apply(),
KStandardGuiItem::discard(),
KStandardGuiItem::cancel());
if (queryUser == KMessageBox::ButtonCode::PrimaryAction) {
previousModule->save();
} else if (queryUser == KMessageBox::ButtonCode::SecondaryAction) {
previousModule->load();
}
canceled = queryUser == KMessageBox::Cancel;
}
if (!canceled) {
mChangedModules.removeAll(previousModule);
setCurrentPage(current);
}
blockSignals(false);
clientChanged();
}
void KleoPageConfigDialog::apply()
{
QPushButton *applyButton = buttonBox()->button(QDialogButtonBox::Apply);
applyButton->setFocus();
for (KCModule *module : mChangedModules) {
module->save();
}
mChangedModules.clear();
Q_EMIT configCommitted();
clientChanged();
}
void KleoPageConfigDialog::slotDefaultClicked()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
KCModule *module = qobject_cast<KCModule *>(item->widget());
if (!module) {
return;
}
module->defaults();
clientChanged();
}
void KleoPageConfigDialog::slotUser1Clicked()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
KCModule *module = qobject_cast<KCModule *>(item->widget());
if (!module) {
return;
}
module->load();
mChangedModules.removeAll(module);
clientChanged();
}
void KleoPageConfigDialog::slotApplyClicked()
{
apply();
}
void KleoPageConfigDialog::slotOkClicked()
{
apply();
accept();
}
void KleoPageConfigDialog::slotHelpClicked()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
const QString docPath = mHelpUrls.value(item->name());
QUrl docUrl;
#ifdef Q_OS_WIN
docUrl = QUrl(QLatin1String("https://docs.kde.org/index.php?branch=stable5&language=") + QLocale().name() + QLatin1String("&application=kleopatra"));
#else
docUrl = QUrl(QStringLiteral("help:/")).resolved(QUrl(docPath)); // same code as in KHelpClient::invokeHelp
#endif
if (docUrl.scheme() == QLatin1String("help") || docUrl.scheme() == QLatin1String("man") || docUrl.scheme() == QLatin1String("info")) {
// Warning: Don't assume that the program needs to be in PATH. On Windows, it will also be found next to the calling process.
QProcess::startDetached(QStringLiteral("khelpcenter"), QStringList() << docUrl.toString());
} else {
QDesktopServices::openUrl(docUrl);
}
}
void KleoPageConfigDialog::addModule(const QString &name, const QString &docPath, const QString &icon, KCModule *module)
{
mModules << module;
KPageWidgetItem *item = addPage(module->widget(), name);
item->setIcon(QIcon::fromTheme(icon));
connect(module, &KCModule::needsSaveChanged, this, [this, module]() {
moduleChanged(module->needsSave());
});
mHelpUrls.insert(name, docPath);
}
void KleoPageConfigDialog::moduleChanged(bool state)
{
KCModule *module = qobject_cast<KCModule *>(sender());
qCDebug(KLEOPATRA_LOG) << "Module changed: " << state << " mod " << module;
if (mChangedModules.contains(module)) {
if (!state) {
mChangedModules.removeAll(module);
} else {
return;
}
}
if (state) {
mChangedModules << module;
}
clientChanged();
}
void KleoPageConfigDialog::clientChanged()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
KCModule *module = qobject_cast<KCModule *>(item->widget());
if (!module) {
return;
}
qCDebug(KLEOPATRA_LOG) << "Client changed: "
<< " mod " << module;
bool change = mChangedModules.contains(module);
QPushButton *resetButton = buttonBox()->button(QDialogButtonBox::Reset);
if (resetButton) {
resetButton->setEnabled(change);
}
QPushButton *applyButton = buttonBox()->button(QDialogButtonBox::Apply);
if (applyButton) {
applyButton->setEnabled(change);
}
}
#include "moc_kleopageconfigdialog.cpp"
diff --git a/src/crypto/autodecryptverifyfilescontroller.cpp b/src/crypto/autodecryptverifyfilescontroller.cpp
index 07456bb71..6f2fde9e9 100644
--- a/src/crypto/autodecryptverifyfilescontroller.cpp
+++ b/src/crypto/autodecryptverifyfilescontroller.cpp
@@ -1,621 +1,621 @@
/* -*- mode: c++; c-basic-offset:4 -*-
autodecryptverifyfilescontroller.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "autodecryptverifyfilescontroller.h"
#include "fileoperationspreferences.h"
#include <crypto/decryptverifytask.h>
#include <crypto/gui/decryptverifyfilesdialog.h>
#include <crypto/gui/decryptverifyoperationwidget.h>
#include <crypto/taskcollection.h>
#include "commands/decryptverifyfilescommand.h"
#include <Libkleo/GnuPG>
#include <utils/archivedefinition.h>
#include <utils/input.h>
#include <utils/kleo_assert.h>
#include <utils/output.h>
#include <utils/path-helper.h>
#include <Libkleo/Classify>
#ifndef Q_OS_WIN
#include <KIO/CopyJob>
#include <KIO/Global>
#endif
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <KMessageBox>
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
#include <QGpgME/DecryptVerifyArchiveJob>
#endif
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QTemporaryDir>
#include <QTimer>
#include <gpgme++/decryptionresult.h>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace Kleo::Crypto::Gui;
class AutoDecryptVerifyFilesController::Private
{
AutoDecryptVerifyFilesController *const q;
public:
explicit Private(AutoDecryptVerifyFilesController *qq);
void schedule();
QString getEmbeddedFileName(const QString &fileName) const;
void exec();
std::vector<std::shared_ptr<Task>> buildTasks(const QStringList &, QStringList &);
struct CryptoFile {
QString baseName;
QString fileName;
GpgME::Protocol protocol = GpgME::UnknownProtocol;
int classification = 0;
std::shared_ptr<Output> output;
};
QList<CryptoFile> classifyAndSortFiles(const QStringList &files);
void reportError(int err, const QString &details)
{
q->setLastError(err, details);
q->emitDoneOrError();
}
void cancelAllTasks();
QStringList m_passedFiles, m_filesAfterPreparation;
std::vector<std::shared_ptr<const DecryptVerifyResult>> m_results;
std::vector<std::shared_ptr<Task>> m_runnableTasks, m_completedTasks;
std::shared_ptr<Task> m_runningTask;
bool m_errorDetected = false;
DecryptVerifyOperation m_operation = DecryptVerify;
QPointer<DecryptVerifyFilesDialog> m_dialog;
std::unique_ptr<QTemporaryDir> m_workDir;
};
AutoDecryptVerifyFilesController::Private::Private(AutoDecryptVerifyFilesController *qq)
: q(qq)
{
qRegisterMetaType<VerificationResult>();
}
void AutoDecryptVerifyFilesController::Private::schedule()
{
if (!m_runningTask && !m_runnableTasks.empty()) {
const std::shared_ptr<Task> t = m_runnableTasks.back();
m_runnableTasks.pop_back();
t->start();
m_runningTask = t;
}
if (!m_runningTask) {
kleo_assert(m_runnableTasks.empty());
for (const std::shared_ptr<const DecryptVerifyResult> &i : std::as_const(m_results)) {
Q_EMIT q->verificationResult(i->verificationResult());
}
}
}
QString AutoDecryptVerifyFilesController::Private::getEmbeddedFileName(const QString &fileName) const
{
auto it = std::find_if(m_results.cbegin(), m_results.cend(), [fileName](const auto &r) {
return r->fileName() == fileName;
});
if (it != m_results.cend()) {
const auto embeddedFilePath = QString::fromUtf8((*it)->decryptionResult().fileName());
if (embeddedFilePath.contains(QLatin1Char{'\\'})) {
// ignore embedded file names containing '\'
return {};
}
// strip the path from the embedded file name
return QFileInfo{embeddedFilePath}.fileName();
} else {
return {};
}
}
void AutoDecryptVerifyFilesController::Private::exec()
{
Q_ASSERT(!m_dialog);
QStringList undetected;
std::vector<std::shared_ptr<Task>> tasks = buildTasks(m_passedFiles, undetected);
if (!undetected.isEmpty()) {
// Since GpgME 1.7.0 Classification is supposed to be reliable
// so we really can't do anything with this data.
reportError(makeGnuPGError(GPG_ERR_GENERAL),
xi18n("Failed to find encrypted or signed data in one or more files.<nl/>"
"You can manually select what to do with the files now.<nl/>"
"If they contain signed or encrypted data please report a bug (see Help->Report Bug)."));
auto cmd = new Commands::DecryptVerifyFilesCommand(undetected, nullptr, true);
cmd->start();
}
if (tasks.empty()) {
q->emitDoneOrError();
return;
}
Q_ASSERT(m_runnableTasks.empty());
m_runnableTasks.swap(tasks);
std::shared_ptr<TaskCollection> coll(new TaskCollection);
for (const std::shared_ptr<Task> &i : std::as_const(m_runnableTasks)) {
q->connectTask(i);
}
coll->setTasks(m_runnableTasks);
DecryptVerifyFilesDialog dialog{coll};
m_dialog = &dialog;
m_dialog->setOutputLocation(heuristicBaseDirectory(m_passedFiles));
QTimer::singleShot(0, q, SLOT(schedule()));
const auto result = m_dialog->exec();
if (result == QDialog::Rejected) {
q->cancel();
} else if (result == QDialog::Accepted && m_workDir) {
// Without workdir there is nothing to move.
const QDir workdir(m_workDir->path());
const QDir outDir(m_dialog->outputLocation());
bool overWriteAll = false;
qCDebug(KLEOPATRA_LOG) << workdir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
for (const QFileInfo &fi : workdir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot)) {
const auto inpath = fi.absoluteFilePath();
if (fi.isDir()) {
// A directory. Assume that the input was an archive
// and avoid directory merges by trying to find a non
// existing directory.
auto candidate = fi.fileName();
if (candidate.startsWith(QLatin1Char('-'))) {
// Bug in GpgTar Extracts stdout passed archives to a dir named -
candidate = QFileInfo(m_passedFiles.first()).baseName();
}
QString suffix;
QFileInfo ofi;
int i = 0;
do {
ofi = QFileInfo(outDir.absoluteFilePath(candidate + suffix));
if (!ofi.exists()) {
break;
}
suffix = QStringLiteral("_%1").arg(++i);
} while (i < 1000);
const auto destPath = ofi.absoluteFilePath();
#ifndef Q_OS_WIN
auto job = KIO::moveAs(QUrl::fromLocalFile(inpath), QUrl::fromLocalFile(destPath));
qCDebug(KLEOPATRA_LOG) << "Moving" << job->srcUrls().front().toLocalFile() << "to" << job->destUrl().toLocalFile();
if (!job->exec()) {
if (job->error() == KIO::ERR_USER_CANCELED) {
break;
}
reportError(makeGnuPGError(GPG_ERR_GENERAL),
xi18nc("@info",
"<para>Failed to move <filename>%1</filename> to <filename>%2</filename>.</para>"
"<para><message>%3</message></para>",
inpath,
destPath,
job->errorString()));
}
#else
// On Windows, KIO::move does not work for folders when crossing partition boundaries
if (!moveDir(inpath, destPath)) {
reportError(makeGnuPGError(GPG_ERR_GENERAL),
xi18nc("@info", "<para>Failed to move <filename>%1</filename> to <filename>%2</filename>.</para>", inpath, destPath));
}
#endif
continue;
}
const auto embeddedFileName = getEmbeddedFileName(inpath);
QString outFileName = fi.fileName();
if (!embeddedFileName.isEmpty() && embeddedFileName != fi.fileName()) {
// we switch "Yes" and "No" because Yes is default, but saving with embedded file name could be dangerous
const auto answer = KMessageBox::questionTwoActionsCancel(
m_dialog,
xi18n("Shall the file be saved with the original file name <filename>%1</filename>?", embeddedFileName),
- i18n("Use Original File Name?"),
+ i18nc("@title:window", "Use Original File Name?"),
KGuiItem(xi18n("No, Save As <filename>%1</filename>", fi.fileName())),
KGuiItem(xi18n("Yes, Save As <filename>%1</filename>", embeddedFileName)));
if (answer == KMessageBox::Cancel) {
qCDebug(KLEOPATRA_LOG) << "Saving canceled for:" << inpath;
continue;
} else if (answer == KMessageBox::ButtonCode::SecondaryAction) {
outFileName = embeddedFileName;
}
}
const auto outpath = outDir.absoluteFilePath(outFileName);
qCDebug(KLEOPATRA_LOG) << "Moving " << inpath << " to " << outpath;
const QFileInfo ofi(outpath);
if (ofi.exists()) {
int sel = KMessageBox::Cancel;
if (!overWriteAll) {
sel = KMessageBox::questionTwoActionsCancel(m_dialog,
i18n("The file <b>%1</b> already exists.\n"
"Overwrite?",
outpath),
i18n("Overwrite Existing File?"),
KStandardGuiItem::overwrite(),
KGuiItem(i18n("Overwrite All")),
KStandardGuiItem::cancel());
}
if (sel == KMessageBox::Cancel) {
qCDebug(KLEOPATRA_LOG) << "Overwriting canceled for: " << outpath;
continue;
}
if (sel == KMessageBox::ButtonCode::SecondaryAction) { // Overwrite All
overWriteAll = true;
}
if (!QFile::remove(outpath)) {
reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to delete <filename>%1</filename>.", outpath));
continue;
}
}
if (!QFile::rename(inpath, outpath)) {
reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to move <filename>%1</filename> to <filename>%2</filename>.", inpath, outpath));
}
}
}
q->emitDoneOrError();
}
QList<AutoDecryptVerifyFilesController::Private::CryptoFile> AutoDecryptVerifyFilesController::Private::classifyAndSortFiles(const QStringList &files)
{
const auto isSignature = [](int classification) -> bool {
return mayBeDetachedSignature(classification) //
|| mayBeOpaqueSignature(classification) //
|| (classification & Class::TypeMask) == Class::ClearsignedMessage;
};
QList<CryptoFile> out;
for (const auto &file : files) {
CryptoFile cFile;
cFile.fileName = file;
cFile.baseName = stripSuffix(file);
cFile.classification = classify(file);
cFile.protocol = findProtocol(cFile.classification);
auto it = std::find_if(out.begin(), out.end(), [&cFile](const CryptoFile &other) {
return other.protocol == cFile.protocol && other.baseName == cFile.baseName;
});
if (it != out.end()) {
// If we found a file with the same basename, make sure that encrypted
// file is before the signature file, so that we first decrypt and then
// verify
if (isSignature(cFile.classification) && isCipherText(it->classification)) {
out.insert(it + 1, cFile);
} else if (isCipherText(cFile.classification) && isSignature(it->classification)) {
out.insert(it, cFile);
} else {
// both are signatures or both are encrypted files, in which
// case order does not matter
out.insert(it, cFile);
}
} else {
out.push_back(cFile);
}
}
return out;
}
static bool archiveJobsCanBeUsed([[maybe_unused]] GpgME::Protocol protocol)
{
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
return (protocol == GpgME::OpenPGP) && QGpgME::DecryptVerifyArchiveJob::isSupported();
#else
return false;
#endif
}
std::vector<std::shared_ptr<Task>> AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected)
{
// sort files so that we make sure we first decrypt and then verify
QList<CryptoFile> cryptoFiles = classifyAndSortFiles(fileNames);
std::vector<std::shared_ptr<Task>> tasks;
for (auto it = cryptoFiles.begin(), end = cryptoFiles.end(); it != end; ++it) {
auto &cFile = (*it);
QFileInfo fi(cFile.fileName);
qCDebug(KLEOPATRA_LOG) << "classified" << cFile.fileName << "as" << printableClassification(cFile.classification);
if (!fi.isReadable()) {
reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), xi18n("Cannot open <filename>%1</filename> for reading.", cFile.fileName));
continue;
}
if (mayBeAnyCertStoreType(cFile.classification)) {
// Trying to verify a certificate. Possible because extensions are often similar
// for PGP Keys.
reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT),
xi18n("The file <filename>%1</filename> contains certificates and can't be decrypted or verified.", cFile.fileName));
qCDebug(KLEOPATRA_LOG) << "reported error";
continue;
}
// We can't reliably detect CMS detached signatures, so we will try to do
// our best to use the current file as a detached signature and fallback to
// opaque signature otherwise.
if (cFile.protocol == GpgME::CMS && mayBeDetachedSignature(cFile.classification)) {
// First, see if previous task was a decryption task for the same file
// and "pipe" it's output into our input
std::shared_ptr<Input> input;
bool prepend = false;
if (it != cryptoFiles.begin()) {
const auto prev = it - 1;
if (prev->protocol == cFile.protocol && prev->baseName == cFile.baseName) {
input = Input::createFromOutput(prev->output);
prepend = true;
}
}
if (!input) {
if (QFile::exists(cFile.baseName)) {
input = Input::createFromFile(cFile.baseName);
}
}
if (input) {
qCDebug(KLEOPATRA_LOG) << "Detached CMS verify: " << cFile.fileName;
std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
t->setInput(Input::createFromFile(cFile.fileName));
t->setSignedData(input);
t->setProtocol(cFile.protocol);
if (prepend) {
// Put the verify task BEFORE the decrypt task in the tasks queue,
// because the tasks are executed in reverse order!
tasks.insert(tasks.end() - 1, t);
} else {
tasks.push_back(t);
}
continue;
} else {
// No signed data, maybe not a detached signature
}
}
if (isDetachedSignature(cFile.classification)) {
// Detached signature, try to find data or ask the user.
QString signedDataFileName = cFile.baseName;
if (!QFile::exists(signedDataFileName)) {
signedDataFileName = QFileDialog::getOpenFileName(nullptr,
xi18n("Select the file to verify with the signature <filename>%1</filename>", fi.fileName()),
fi.path());
}
if (signedDataFileName.isEmpty()) {
qCDebug(KLEOPATRA_LOG) << "No signed data selected. Verify aborted.";
} else {
qCDebug(KLEOPATRA_LOG) << "Detached verify: " << cFile.fileName << " Data: " << signedDataFileName;
std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
t->setInput(Input::createFromFile(cFile.fileName));
t->setSignedData(Input::createFromFile(signedDataFileName));
t->setProtocol(cFile.protocol);
tasks.push_back(t);
}
continue;
}
if (!mayBeAnyMessageType(cFile.classification)) {
// Not a Message? Maybe there is a signature for this file?
const auto signatures = findSignatures(cFile.fileName);
bool foundSig = false;
if (!signatures.empty()) {
for (const QString &sig : signatures) {
const auto classification = classify(sig);
qCDebug(KLEOPATRA_LOG) << "Guessing: " << sig << " is a signature for: " << cFile.fileName << "Classification: " << classification;
const auto proto = findProtocol(classification);
if (proto == GpgME::UnknownProtocol) {
qCDebug(KLEOPATRA_LOG) << "Could not determine protocol. Skipping guess.";
continue;
}
foundSig = true;
std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
t->setInput(Input::createFromFile(sig));
t->setSignedData(Input::createFromFile(cFile.fileName));
t->setProtocol(proto);
tasks.push_back(t);
}
}
if (!foundSig) {
undetected << cFile.fileName;
qCDebug(KLEOPATRA_LOG) << "Failed detection for: " << cFile.fileName << " adding to undetected.";
}
} else {
const FileOperationsPreferences fileOpSettings;
// Any Message type so we have input and output.
const auto input = Input::createFromFile(cFile.fileName);
std::shared_ptr<ArchiveDefinition> ad;
if (fileOpSettings.autoExtractArchives()) {
const auto archiveDefinitions = ArchiveDefinition::getArchiveDefinitions();
ad = q->pick_archive_definition(cFile.protocol, archiveDefinitions, cFile.fileName);
}
if (fileOpSettings.dontUseTmpDir()) {
if (!m_workDir) {
m_workDir = std::make_unique<QTemporaryDir>(heuristicBaseDirectory(fileNames) + QStringLiteral("/kleopatra-XXXXXX"));
}
if (!m_workDir->isValid()) {
qCDebug(KLEOPATRA_LOG) << heuristicBaseDirectory(fileNames) << "not a valid temporary directory.";
m_workDir.reset();
}
}
if (!m_workDir) {
m_workDir = std::make_unique<QTemporaryDir>();
}
qCDebug(KLEOPATRA_LOG) << "Using:" << m_workDir->path() << "as temporary directory.";
const auto wd = QDir(m_workDir->path());
std::shared_ptr<Output> output;
if (ad) {
if ((ad->id() == QLatin1String{"tar"}) && archiveJobsCanBeUsed(cFile.protocol)) {
// we don't need an output
} else {
output = ad->createOutputFromUnpackCommand(cFile.protocol, ad->stripExtension(cFile.protocol, cFile.baseName), wd);
}
} else {
output = Output::createFromFile(wd.absoluteFilePath(outputFileName(fi.fileName())), false);
}
// If this might be opaque CMS signature, then try that. We already handled
// detached CMS signature above
const auto isCMSOpaqueSignature = cFile.protocol == GpgME::CMS && mayBeOpaqueSignature(cFile.classification);
if (isOpaqueSignature(cFile.classification) || isCMSOpaqueSignature) {
qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask";
std::shared_ptr<VerifyOpaqueTask> t(new VerifyOpaqueTask);
t->setInput(input);
if (output) {
t->setOutput(output);
}
t->setProtocol(cFile.protocol);
if (ad) {
t->setExtractArchive(true);
t->setInputFile(cFile.fileName);
if (output) {
t->setOutputDirectory(m_workDir->path());
} else {
// make gpgtar extract to a subfolder of the work directory based on the input file name
const auto baseFileName = QFileInfo{ad->stripExtension(cFile.protocol, cFile.baseName)}.fileName();
t->setOutputDirectory(QDir{m_workDir->path()}.filePath(baseFileName));
}
}
tasks.push_back(t);
} else {
// Any message. That is not an opaque signature needs to be
// decrypted. Verify we always do because we can't know if
// an encrypted message is also signed.
qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask";
std::shared_ptr<DecryptVerifyTask> t(new DecryptVerifyTask);
t->setInput(input);
if (output) {
t->setOutput(output);
}
t->setProtocol(cFile.protocol);
if (ad) {
t->setExtractArchive(true);
t->setInputFile(cFile.fileName);
if (output) {
t->setOutputDirectory(m_workDir->path());
} else {
// make gpgtar extract to a subfolder of the work directory based on the input file name
const auto baseFileName = QFileInfo{ad->stripExtension(cFile.protocol, cFile.baseName)}.fileName();
t->setOutputDirectory(QDir{m_workDir->path()}.filePath(baseFileName));
}
}
cFile.output = output;
tasks.push_back(t);
}
}
}
return tasks;
}
void AutoDecryptVerifyFilesController::setFiles(const QStringList &files)
{
d->m_passedFiles = files;
}
AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(QObject *parent)
: DecryptVerifyFilesController(parent)
, d(new Private(this))
{
}
AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *parent)
: DecryptVerifyFilesController(ctx, parent)
, d(new Private(this))
{
}
AutoDecryptVerifyFilesController::~AutoDecryptVerifyFilesController()
{
qCDebug(KLEOPATRA_LOG);
}
void AutoDecryptVerifyFilesController::start()
{
d->exec();
}
void AutoDecryptVerifyFilesController::setOperation(DecryptVerifyOperation op)
{
d->m_operation = op;
}
DecryptVerifyOperation AutoDecryptVerifyFilesController::operation() const
{
return d->m_operation;
}
void AutoDecryptVerifyFilesController::Private::cancelAllTasks()
{
// we just kill all runnable tasks - this will not result in
// signal emissions.
m_runnableTasks.clear();
// a cancel() will result in a call to
if (m_runningTask) {
m_runningTask->cancel();
}
}
void AutoDecryptVerifyFilesController::cancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
try {
d->m_errorDetected = true;
if (d->m_dialog) {
d->m_dialog->close();
}
d->cancelAllTasks();
} catch (const std::exception &e) {
qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what();
}
}
void AutoDecryptVerifyFilesController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result)
{
Q_ASSERT(task);
Q_UNUSED(task)
// We could just delete the tasks here, but we can't use
// Qt::QueuedConnection here (we need sender()) and other slots
// might not yet have executed. Therefore, we push completed tasks
// into a burial container
d->m_completedTasks.push_back(d->m_runningTask);
d->m_runningTask.reset();
if (const std::shared_ptr<const DecryptVerifyResult> &dvr = std::dynamic_pointer_cast<const DecryptVerifyResult>(result)) {
d->m_results.push_back(dvr);
}
QTimer::singleShot(0, this, SLOT(schedule()));
}
#include "moc_autodecryptverifyfilescontroller.cpp"
diff --git a/src/crypto/gui/signencryptwidget.cpp b/src/crypto/gui/signencryptwidget.cpp
index 579b6041e..c1c765d8a 100644
--- a/src/crypto/gui/signencryptwidget.cpp
+++ b/src/crypto/gui/signencryptwidget.cpp
@@ -1,849 +1,852 @@
/* crypto/gui/signencryptwidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "signencryptwidget.h"
#include "kleopatra_debug.h"
#include "certificatelineedit.h"
#include "fileoperationspreferences.h"
#include "kleopatraapplication.h"
#include "settings.h"
#include "unknownrecipientwidget.h"
#include "dialogs/certificateselectiondialog.h"
#include "utils/gui-helper.h"
#include <QCheckBox>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QScrollArea>
#include <QScrollBar>
#include <QVBoxLayout>
#include <Libkleo/Compliance>
#include <Libkleo/DefaultKeyFilter>
#include <Libkleo/ExpiryChecker>
#include <Libkleo/ExpiryCheckerConfig>
#include <Libkleo/ExpiryCheckerSettings>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/KeySelectionCombo>
#include <Libkleo/GnuPG>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMessageBox>
#include <KMessageWidget>
#include <KSharedConfig>
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;
namespace
{
class SignCertificateFilter : public DefaultKeyFilter
{
public:
SignCertificateFilter(GpgME::Protocol proto)
: DefaultKeyFilter()
{
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setHasSecret(DefaultKeyFilter::Set);
setCanSign(DefaultKeyFilter::Set);
setValidIfSMIME(DefaultKeyFilter::Set);
if (proto == GpgME::OpenPGP) {
setIsOpenPGP(DefaultKeyFilter::Set);
} else if (proto == GpgME::CMS) {
setIsOpenPGP(DefaultKeyFilter::NotSet);
}
}
};
class EncryptCertificateFilter : public DefaultKeyFilter
{
public:
EncryptCertificateFilter(GpgME::Protocol proto)
: DefaultKeyFilter()
{
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setCanEncrypt(DefaultKeyFilter::Set);
setValidIfSMIME(DefaultKeyFilter::Set);
if (proto == GpgME::OpenPGP) {
setIsOpenPGP(DefaultKeyFilter::Set);
} else if (proto == GpgME::CMS) {
setIsOpenPGP(DefaultKeyFilter::NotSet);
}
}
};
class EncryptSelfCertificateFilter : public EncryptCertificateFilter
{
public:
EncryptSelfCertificateFilter(GpgME::Protocol proto)
: EncryptCertificateFilter(proto)
{
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setCanEncrypt(DefaultKeyFilter::Set);
setHasSecret(DefaultKeyFilter::Set);
setValidIfSMIME(DefaultKeyFilter::Set);
}
};
}
class SignEncryptWidget::Private
{
SignEncryptWidget *const q;
public:
struct RecipientWidgets {
CertificateLineEdit *edit;
KMessageWidget *expiryMessage;
};
explicit Private(SignEncryptWidget *qq, bool sigEncExclusive)
: q{qq}
, mModel{AbstractKeyListModel::createFlatKeyListModel(qq)}
, mIsExclusive{sigEncExclusive}
{
}
CertificateLineEdit *addRecipientWidget();
/* Inserts a new recipient widget after widget @p after or at the end
* if @p after is null. */
CertificateLineEdit *insertRecipientWidget(CertificateLineEdit *after);
void recpRemovalRequested(const RecipientWidgets &recipient);
void onProtocolChanged();
void updateCheckBoxes();
ExpiryChecker *expiryChecker();
void updateExpiryMessages(KMessageWidget *w, const GpgME::Key &key, ExpiryChecker::CheckFlags flags);
void updateAllExpiryMessages();
public:
KeySelectionCombo *mSigSelect = nullptr;
KMessageWidget *mSignKeyExpiryMessage = nullptr;
KeySelectionCombo *mSelfSelect = nullptr;
KMessageWidget *mEncryptToSelfKeyExpiryMessage = nullptr;
std::vector<RecipientWidgets> mRecpWidgets;
QList<UnknownRecipientWidget *> mUnknownWidgets;
QList<GpgME::Key> mAddedKeys;
QList<KeyGroup> mAddedGroups;
QVBoxLayout *mRecpLayout = nullptr;
Operations mOp;
AbstractKeyListModel *mModel = nullptr;
QCheckBox *mSymmetric = nullptr;
QCheckBox *mSigChk = nullptr;
QCheckBox *mEncOtherChk = nullptr;
QCheckBox *mEncSelfChk = nullptr;
GpgME::Protocol mCurrentProto = GpgME::UnknownProtocol;
const bool mIsExclusive;
std::unique_ptr<ExpiryChecker> mExpiryChecker;
};
SignEncryptWidget::SignEncryptWidget(QWidget *parent, bool sigEncExclusive)
: QWidget{parent}
, d{new Private{this, sigEncExclusive}}
{
auto lay = new QVBoxLayout(this);
lay->setContentsMargins(0, 0, 0, 0);
d->mModel->useKeyCache(true, KeyList::IncludeGroups);
const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty();
const bool havePublicKeys = !KeyCache::instance()->keys().empty();
const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly();
/* The signature selection */
{
auto sigGrp = new QGroupBox{i18nc("@title:group", "Prove authenticity (sign)"), this};
d->mSigChk = new QCheckBox{i18n("Sign as:"), this};
d->mSigChk->setEnabled(haveSecretKeys);
d->mSigChk->setChecked(haveSecretKeys);
d->mSigSelect = new KeySelectionCombo{this};
d->mSigSelect->setEnabled(d->mSigChk->isChecked());
d->mSignKeyExpiryMessage = new KMessageWidget{this};
d->mSignKeyExpiryMessage->setVisible(false);
auto groupLayout = new QGridLayout{sigGrp};
groupLayout->setColumnStretch(1, 1);
groupLayout->addWidget(d->mSigChk, 0, 0);
groupLayout->addWidget(d->mSigSelect, 0, 1);
groupLayout->addWidget(d->mSignKeyExpiryMessage, 1, 1);
lay->addWidget(sigGrp);
connect(d->mSigChk, &QCheckBox::toggled, this, [this](bool checked) {
d->mSigSelect->setEnabled(checked);
updateOp();
d->updateExpiryMessages(d->mSignKeyExpiryMessage, signKey(), ExpiryChecker::OwnSigningKey);
});
connect(d->mSigSelect, &KeySelectionCombo::currentKeyChanged, this, [this]() {
updateOp();
d->updateExpiryMessages(d->mSignKeyExpiryMessage, signKey(), ExpiryChecker::OwnSigningKey);
});
}
// Recipient selection
{
auto encBox = new QGroupBox{i18nc("@title:group", "Encrypt"), this};
auto encBoxLay = new QVBoxLayout{encBox};
auto recipientGrid = new QGridLayout;
int row = 0;
// Own key
d->mEncSelfChk = new QCheckBox{i18n("Encrypt for me:"), this};
d->mEncSelfChk->setEnabled(haveSecretKeys && !symmetricOnly);
d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly);
d->mSelfSelect = new KeySelectionCombo{this};
d->mSelfSelect->setEnabled(d->mEncSelfChk->isChecked());
d->mEncryptToSelfKeyExpiryMessage = new KMessageWidget{this};
d->mEncryptToSelfKeyExpiryMessage->setVisible(false);
recipientGrid->addWidget(d->mEncSelfChk, row, 0);
recipientGrid->addWidget(d->mSelfSelect, row, 1);
row++;
recipientGrid->addWidget(d->mEncryptToSelfKeyExpiryMessage, row, 1);
// Checkbox for other keys
row++;
d->mEncOtherChk = new QCheckBox{i18n("Encrypt for others:"), this};
d->mEncOtherChk->setEnabled(havePublicKeys && !symmetricOnly);
d->mEncOtherChk->setChecked(havePublicKeys && !symmetricOnly);
recipientGrid->addWidget(d->mEncOtherChk, row, 0, Qt::AlignTop);
connect(d->mEncOtherChk, &QCheckBox::toggled, this, [this](bool checked) {
for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
recipient.edit->setEnabled(checked);
d->updateExpiryMessages(recipient.expiryMessage, checked ? recipient.edit->key() : Key{}, ExpiryChecker::EncryptionKey);
}
updateOp();
});
d->mRecpLayout = new QVBoxLayout;
recipientGrid->addLayout(d->mRecpLayout, row, 1);
recipientGrid->setRowStretch(row + 1, 1);
// Scroll area for other keys
auto recipientWidget = new QWidget;
auto recipientScroll = new QScrollArea;
recipientWidget->setLayout(recipientGrid);
recipientScroll->setWidget(recipientWidget);
recipientScroll->setWidgetResizable(true);
recipientScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
recipientScroll->setFrameStyle(QFrame::NoFrame);
recipientScroll->setFocusPolicy(Qt::NoFocus);
recipientGrid->setContentsMargins(0, 0, 0, 0);
encBoxLay->addWidget(recipientScroll, 1);
auto bar = recipientScroll->verticalScrollBar();
connect(bar, &QScrollBar::rangeChanged, this, [bar](int, int max) {
bar->setValue(max);
});
d->addRecipientWidget();
// Checkbox for password
d->mSymmetric = new QCheckBox(i18n("Encrypt with password. Anyone you share the password with can read the data."));
d->mSymmetric->setToolTip(i18nc("Tooltip information for symmetric encryption",
"Additionally to the keys of the recipients you can encrypt your data with a password. "
"Anyone who has the password can read the data without any secret key. "
"Using a password is <b>less secure</b> then public key cryptography. Even if you pick a very strong password."));
d->mSymmetric->setChecked(symmetricOnly || !havePublicKeys);
encBoxLay->addWidget(d->mSymmetric);
// Connect it
connect(d->mEncSelfChk, &QCheckBox::toggled, this, [this](bool checked) {
d->mSelfSelect->setEnabled(checked);
updateOp();
d->updateExpiryMessages(d->mEncryptToSelfKeyExpiryMessage, selfKey(), ExpiryChecker::OwnEncryptionKey);
});
connect(d->mSelfSelect, &KeySelectionCombo::currentKeyChanged, this, [this]() {
updateOp();
d->updateExpiryMessages(d->mEncryptToSelfKeyExpiryMessage, selfKey(), ExpiryChecker::OwnEncryptionKey);
});
connect(d->mSymmetric, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp);
if (d->mIsExclusive) {
connect(d->mEncOtherChk, &QCheckBox::toggled, this, [this](bool value) {
if (d->mCurrentProto != GpgME::CMS) {
return;
}
if (value) {
d->mSigChk->setChecked(false);
}
});
connect(d->mEncSelfChk, &QCheckBox::toggled, this, [this](bool value) {
if (d->mCurrentProto != GpgME::CMS) {
return;
}
if (value) {
d->mSigChk->setChecked(false);
}
});
connect(d->mSigChk, &QCheckBox::toggled, this, [this](bool value) {
if (d->mCurrentProto != GpgME::CMS) {
return;
}
if (value) {
d->mEncSelfChk->setChecked(false);
d->mEncOtherChk->setChecked(false);
}
});
}
// Ensure that the d->mSigChk is aligned together with the encryption check boxes.
d->mSigChk->setMinimumWidth(qMax(d->mEncOtherChk->width(), d->mEncSelfChk->width()));
lay->addWidget(encBox);
}
connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this]() {
d->updateCheckBoxes();
d->updateAllExpiryMessages();
});
connect(KleopatraApplication::instance(), &KleopatraApplication::configurationChanged, this, [this]() {
d->updateCheckBoxes();
d->mExpiryChecker.reset();
d->updateAllExpiryMessages();
});
loadKeys();
d->onProtocolChanged();
updateOp();
}
SignEncryptWidget::~SignEncryptWidget() = default;
void SignEncryptWidget::setSignAsText(const QString &text)
{
d->mSigChk->setText(text);
}
void SignEncryptWidget::setEncryptForMeText(const QString &text)
{
d->mEncSelfChk->setText(text);
}
void SignEncryptWidget::setEncryptForOthersText(const QString &text)
{
d->mEncOtherChk->setText(text);
}
void SignEncryptWidget::setEncryptWithPasswordText(const QString &text)
{
d->mSymmetric->setText(text);
}
CertificateLineEdit *SignEncryptWidget::Private::addRecipientWidget()
{
return insertRecipientWidget(nullptr);
}
CertificateLineEdit *SignEncryptWidget::Private::insertRecipientWidget(CertificateLineEdit *after)
{
Q_ASSERT(!after || mRecpLayout->indexOf(after) != -1);
const auto index = after ? mRecpLayout->indexOf(after) + 2 : mRecpLayout->count();
const RecipientWidgets recipient{new CertificateLineEdit{mModel, new EncryptCertificateFilter{mCurrentProto}, q}, new KMessageWidget{q}};
recipient.edit->setAccessibleNameOfLineEdit(i18nc("text for screen readers", "recipient key"));
recipient.edit->setEnabled(mEncOtherChk->isChecked());
recipient.expiryMessage->setVisible(false);
if (static_cast<unsigned>(index / 2) < mRecpWidgets.size()) {
mRecpWidgets.insert(mRecpWidgets.begin() + index / 2, recipient);
} else {
mRecpWidgets.push_back(recipient);
}
if (mRecpLayout->count() > 0) {
auto prevWidget = after ? after : mRecpLayout->itemAt(mRecpLayout->count() - 1)->widget();
Kleo::forceSetTabOrder(prevWidget, recipient.edit);
Kleo::forceSetTabOrder(recipient.edit, recipient.expiryMessage);
}
mRecpLayout->insertWidget(index, recipient.edit);
mRecpLayout->insertWidget(index + 1, recipient.expiryMessage);
connect(recipient.edit, &CertificateLineEdit::keyChanged, q, &SignEncryptWidget::recipientsChanged);
connect(recipient.edit, &CertificateLineEdit::editingStarted, q, &SignEncryptWidget::recipientsChanged);
connect(recipient.edit, &CertificateLineEdit::cleared, q, &SignEncryptWidget::recipientsChanged);
connect(recipient.edit, &CertificateLineEdit::certificateSelectionRequested, q, [this, recipient]() {
q->certificateSelectionRequested(recipient.edit);
});
return recipient.edit;
}
void SignEncryptWidget::addRecipient(const Key &key)
{
CertificateLineEdit *certSel = d->addRecipientWidget();
if (!key.isNull()) {
certSel->setKey(key);
d->mAddedKeys << key;
}
}
void SignEncryptWidget::addRecipient(const KeyGroup &group)
{
CertificateLineEdit *certSel = d->addRecipientWidget();
if (!group.isNull()) {
certSel->setGroup(group);
d->mAddedGroups << group;
}
}
void SignEncryptWidget::certificateSelectionRequested(CertificateLineEdit *certificateLineEdit)
{
CertificateSelectionDialog dlg{this};
dlg.setOptions(CertificateSelectionDialog::Options( //
CertificateSelectionDialog::MultiSelection | //
CertificateSelectionDialog::EncryptOnly | //
CertificateSelectionDialog::optionsFromProtocol(d->mCurrentProto) | //
CertificateSelectionDialog::IncludeGroups));
if (!certificateLineEdit->key().isNull()) {
const auto key = certificateLineEdit->key();
const auto name = QString::fromUtf8(key.userID(0).name());
const auto email = QString::fromUtf8(key.userID(0).email());
dlg.setStringFilter(!name.isEmpty() ? name : email);
} else if (!certificateLineEdit->group().isNull()) {
dlg.setStringFilter(certificateLineEdit->group().name());
} else {
dlg.setStringFilter(certificateLineEdit->text());
}
if (dlg.exec()) {
const std::vector<Key> keys = dlg.selectedCertificates();
const std::vector<KeyGroup> groups = dlg.selectedGroups();
if (keys.size() == 0 && groups.size() == 0) {
return;
}
CertificateLineEdit *certWidget = nullptr;
for (const Key &key : keys) {
if (!certWidget) {
certWidget = certificateLineEdit;
} else {
certWidget = d->insertRecipientWidget(certWidget);
}
certWidget->setKey(key);
}
for (const KeyGroup &group : groups) {
if (!certWidget) {
certWidget = certificateLineEdit;
} else {
certWidget = d->insertRecipientWidget(certWidget);
}
certWidget->setGroup(group);
}
}
recipientsChanged();
}
void SignEncryptWidget::clearAddedRecipients()
{
for (auto w : std::as_const(d->mUnknownWidgets)) {
d->mRecpLayout->removeWidget(w);
delete w;
}
for (auto &key : std::as_const(d->mAddedKeys)) {
removeRecipient(key);
}
for (auto &group : std::as_const(d->mAddedGroups)) {
removeRecipient(group);
}
}
void SignEncryptWidget::addUnknownRecipient(const char *keyID)
{
auto unknownWidget = new UnknownRecipientWidget(keyID);
d->mUnknownWidgets << unknownWidget;
if (d->mRecpLayout->count() > 0) {
auto lastWidget = d->mRecpLayout->itemAt(d->mRecpLayout->count() - 1)->widget();
setTabOrder(lastWidget, unknownWidget);
}
d->mRecpLayout->addWidget(unknownWidget);
connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this]() {
// Check if any unknown recipient can now be found.
for (auto w : d->mUnknownWidgets) {
auto key = KeyCache::instance()->findByKeyIDOrFingerprint(w->keyID().toLatin1().constData());
if (key.isNull()) {
std::vector<std::string> subids;
subids.push_back(std::string(w->keyID().toLatin1().constData()));
for (const auto &subkey : KeyCache::instance()->findSubkeysByKeyID(subids)) {
key = subkey.parent();
}
}
if (key.isNull()) {
continue;
}
// Key is now available replace by line edit.
qCDebug(KLEOPATRA_LOG) << "Removing widget for keyid: " << w->keyID();
d->mRecpLayout->removeWidget(w);
d->mUnknownWidgets.removeAll(w);
delete w;
addRecipient(key);
}
});
}
void SignEncryptWidget::recipientsChanged()
{
const bool hasEmptyRecpWidget = std::any_of(std::cbegin(d->mRecpWidgets), std::cend(d->mRecpWidgets), [](auto w) {
return w.edit->isEmpty();
});
if (!hasEmptyRecpWidget) {
d->addRecipientWidget();
}
updateOp();
for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
if (!recipient.edit->isEditingInProgress() || recipient.edit->isEmpty()) {
d->updateExpiryMessages(recipient.expiryMessage, d->mEncOtherChk->isChecked() ? recipient.edit->key() : Key{}, ExpiryChecker::EncryptionKey);
}
}
}
Key SignEncryptWidget::signKey() const
{
if (d->mSigSelect->isEnabled()) {
return d->mSigSelect->currentKey();
}
return Key();
}
Key SignEncryptWidget::selfKey() const
{
if (d->mSelfSelect->isEnabled()) {
return d->mSelfSelect->currentKey();
}
return Key();
}
std::vector<Key> SignEncryptWidget::recipients() const
{
std::vector<Key> ret;
for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
const auto *const w = recipient.edit;
if (!w->isEnabled()) {
// If one is disabled, all are disabled.
break;
}
const Key k = w->key();
const KeyGroup g = w->group();
if (!k.isNull()) {
ret.push_back(k);
} else if (!g.isNull()) {
const auto keys = g.keys();
std::copy(keys.begin(), keys.end(), std::back_inserter(ret));
}
}
const Key k = selfKey();
if (!k.isNull()) {
ret.push_back(k);
}
return ret;
}
bool SignEncryptWidget::isDeVsAndValid() const
{
if (!signKey().isNull() && !DeVSCompliance::keyIsCompliant(signKey())) {
return false;
}
if (!selfKey().isNull() && !DeVSCompliance::keyIsCompliant(selfKey())) {
return false;
}
for (const auto &key : recipients()) {
if (!DeVSCompliance::keyIsCompliant(key)) {
return false;
}
}
return true;
}
static QString expiryMessage(const ExpiryChecker::Result &result)
{
if (result.expiration.certificate.isNull()) {
return {};
}
switch (result.expiration.status) {
case ExpiryChecker::Expired:
return i18nc("@info", "This certificate is expired.");
case ExpiryChecker::ExpiresSoon: {
if (result.expiration.duration.count() == 0) {
return i18nc("@info", "This certificate expires today.");
} else {
return i18ncp("@info", "This certificate expires tomorrow.", "This certificate expires in %1 days.", result.expiration.duration.count());
}
}
case ExpiryChecker::NoSuitableSubkey:
if (result.checkFlags & ExpiryChecker::EncryptionKey) {
return i18nc("@info", "This certificate cannot be used for encryption.");
} else {
return i18nc("@info", "This certificate cannot be used for signing.");
}
case ExpiryChecker::InvalidKey:
case ExpiryChecker::InvalidCheckFlags:
break; // wrong usage of ExpiryChecker; can be ignored
case ExpiryChecker::NotNearExpiry:;
}
return {};
}
void SignEncryptWidget::updateOp()
{
const Key sigKey = signKey();
const std::vector<Key> recp = recipients();
Operations op = NoOperation;
if (!sigKey.isNull()) {
op |= Sign;
}
if (!recp.empty() || encryptSymmetric()) {
op |= Encrypt;
}
d->mOp = op;
Q_EMIT operationChanged(d->mOp);
Q_EMIT keysChanged();
}
SignEncryptWidget::Operations SignEncryptWidget::currentOp() const
{
return d->mOp;
}
namespace
{
bool recipientWidgetHasFocus(QWidget *w)
{
// check if w (or its focus proxy) or a child widget of w has focus
return w->hasFocus() || w->isAncestorOf(qApp->focusWidget());
}
}
void SignEncryptWidget::Private::recpRemovalRequested(const RecipientWidgets &recipient)
{
if (!recipient.edit) {
return;
}
const int emptyEdits = std::count_if(std::cbegin(mRecpWidgets), std::cend(mRecpWidgets), [](const auto &r) {
return r.edit->isEmpty();
});
if (emptyEdits > 1) {
if (recipientWidgetHasFocus(recipient.edit) || recipientWidgetHasFocus(recipient.expiryMessage)) {
const int index = mRecpLayout->indexOf(recipient.edit);
const auto focusWidget = (index < mRecpLayout->count() - 2) ? //
mRecpLayout->itemAt(index + 2)->widget()
: mRecpLayout->itemAt(mRecpLayout->count() - 3)->widget();
focusWidget->setFocus();
}
mRecpLayout->removeWidget(recipient.expiryMessage);
mRecpLayout->removeWidget(recipient.edit);
const auto it = std::find_if(std::begin(mRecpWidgets), std::end(mRecpWidgets), [recipient](const auto &r) {
return r.edit == recipient.edit;
});
mRecpWidgets.erase(it);
recipient.expiryMessage->deleteLater();
recipient.edit->deleteLater();
}
}
void SignEncryptWidget::removeRecipient(const GpgME::Key &key)
{
for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
const auto editKey = recipient.edit->key();
if (key.isNull() && editKey.isNull()) {
d->recpRemovalRequested(recipient);
return;
}
if (editKey.primaryFingerprint() && key.primaryFingerprint() && !strcmp(editKey.primaryFingerprint(), key.primaryFingerprint())) {
d->recpRemovalRequested(recipient);
return;
}
}
}
void SignEncryptWidget::removeRecipient(const KeyGroup &group)
{
for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
const auto editGroup = recipient.edit->group();
if (group.isNull() && editGroup.isNull()) {
d->recpRemovalRequested(recipient);
return;
}
if (editGroup.name() == group.name()) {
d->recpRemovalRequested(recipient);
return;
}
}
}
bool SignEncryptWidget::encryptSymmetric() const
{
return d->mSymmetric->isChecked();
}
void SignEncryptWidget::loadKeys()
{
KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys");
auto cache = KeyCache::instance();
d->mSigSelect->setDefaultKey(keys.readEntry("SigningKey", QString()));
d->mSelfSelect->setDefaultKey(keys.readEntry("EncryptKey", QString()));
}
void SignEncryptWidget::saveOwnKeys() const
{
KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys");
auto sigKey = d->mSigSelect->currentKey();
auto encKey = d->mSelfSelect->currentKey();
if (!sigKey.isNull()) {
keys.writeEntry("SigningKey", sigKey.primaryFingerprint());
}
if (!encKey.isNull()) {
keys.writeEntry("EncryptKey", encKey.primaryFingerprint());
}
}
void SignEncryptWidget::setSigningChecked(bool value)
{
d->mSigChk->setChecked(value && !KeyCache::instance()->secretKeys().empty());
}
void SignEncryptWidget::setEncryptionChecked(bool checked)
{
if (checked) {
const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty();
const bool havePublicKeys = !KeyCache::instance()->keys().empty();
const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly();
d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly);
d->mEncOtherChk->setChecked(havePublicKeys && !symmetricOnly);
d->mSymmetric->setChecked(symmetricOnly || !havePublicKeys);
} else {
d->mEncSelfChk->setChecked(false);
d->mEncOtherChk->setChecked(false);
d->mSymmetric->setChecked(false);
}
}
void SignEncryptWidget::setProtocol(GpgME::Protocol proto)
{
if (d->mCurrentProto == proto) {
return;
}
d->mCurrentProto = proto;
d->onProtocolChanged();
}
void Kleo::SignEncryptWidget::Private::onProtocolChanged()
{
mSigSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new SignCertificateFilter(mCurrentProto)));
mSelfSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new EncryptSelfCertificateFilter(mCurrentProto)));
const auto encFilter = std::shared_ptr<KeyFilter>(new EncryptCertificateFilter(mCurrentProto));
for (const auto &recipient : std::as_const(mRecpWidgets)) {
recipient.edit->setKeyFilter(encFilter);
}
if (mIsExclusive) {
mSymmetric->setDisabled(mCurrentProto == GpgME::CMS);
if (mSymmetric->isChecked() && mCurrentProto == GpgME::CMS) {
mSymmetric->setChecked(false);
}
if (mSigChk->isChecked() && mCurrentProto == GpgME::CMS && (mEncSelfChk->isChecked() || mEncOtherChk->isChecked())) {
mSigChk->setChecked(false);
}
}
}
bool SignEncryptWidget::isComplete() const
{
return currentOp() != NoOperation //
&& std::all_of(std::cbegin(d->mRecpWidgets), std::cend(d->mRecpWidgets), [](const auto &r) {
return !r.edit->isEnabled() || r.edit->hasAcceptableInput();
});
}
bool SignEncryptWidget::validate()
{
CertificateLineEdit *firstUnresolvedRecipient = nullptr;
QStringList unresolvedRecipients;
for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
if (recipient.edit->isEnabled() && !recipient.edit->hasAcceptableInput()) {
if (!firstUnresolvedRecipient) {
firstUnresolvedRecipient = recipient.edit;
}
unresolvedRecipients.push_back(recipient.edit->text().toHtmlEscaped());
}
}
if (!unresolvedRecipients.isEmpty()) {
- KMessageBox::errorList(this, i18n("Could not find a key for the following recipients:"), unresolvedRecipients, i18n("Failed to find some keys"));
+ KMessageBox::errorList(this,
+ i18n("Could not find a key for the following recipients:"),
+ unresolvedRecipients,
+ i18nc("@title:window", "Failed to find some keys"));
}
if (firstUnresolvedRecipient) {
firstUnresolvedRecipient->setFocus();
}
return unresolvedRecipients.isEmpty();
}
void SignEncryptWidget::Private::updateCheckBoxes()
{
const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty();
const bool havePublicKeys = !KeyCache::instance()->keys().empty();
const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly();
mSigChk->setEnabled(haveSecretKeys);
mEncSelfChk->setEnabled(haveSecretKeys && !symmetricOnly);
mEncOtherChk->setEnabled(havePublicKeys && !symmetricOnly);
if (symmetricOnly) {
mEncSelfChk->setChecked(false);
mEncOtherChk->setChecked(false);
mSymmetric->setChecked(true);
}
}
ExpiryChecker *Kleo::SignEncryptWidget::Private::expiryChecker()
{
if (!mExpiryChecker) {
mExpiryChecker.reset(new ExpiryChecker{ExpiryCheckerConfig{}.settings()});
}
return mExpiryChecker.get();
}
void SignEncryptWidget::Private::updateExpiryMessages(KMessageWidget *messageWidget, const GpgME::Key &key, ExpiryChecker::CheckFlags flags)
{
messageWidget->setCloseButtonVisible(false);
if (!Settings{}.showExpiryNotifications() || key.isNull()) {
messageWidget->setVisible(false);
} else {
const auto result = expiryChecker()->checkKey(key, flags);
const auto message = expiryMessage(result);
messageWidget->setText(message);
messageWidget->setVisible(!message.isEmpty());
}
}
void SignEncryptWidget::Private::updateAllExpiryMessages()
{
updateExpiryMessages(mSignKeyExpiryMessage, q->signKey(), ExpiryChecker::OwnSigningKey);
updateExpiryMessages(mEncryptToSelfKeyExpiryMessage, q->selfKey(), ExpiryChecker::OwnEncryptionKey);
for (const auto &recipient : std::as_const(mRecpWidgets)) {
if (recipient.edit->isEnabled()) {
updateExpiryMessages(recipient.expiryMessage, recipient.edit->key(), ExpiryChecker::EncryptionKey);
}
}
}
#include "moc_signencryptwidget.cpp"
diff --git a/src/crypto/gui/verifychecksumsdialog.cpp b/src/crypto/gui/verifychecksumsdialog.cpp
index 581b5e013..f9c85c78e 100644
--- a/src/crypto/gui/verifychecksumsdialog.cpp
+++ b/src/crypto/gui/verifychecksumsdialog.cpp
@@ -1,392 +1,392 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/gui/verifychecksumsdialog.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "verifychecksumsdialog.h"
#ifndef QT_NO_DIRMODEL
#include <Libkleo/SystemInfo>
#include <KLocalizedString>
#include <KMessageBox>
#include "kleopatra_debug.h"
#include <QDialogButtonBox>
#include <QFileSystemModel>
#include <QHBoxLayout>
#include <QHash>
#include <QHeaderView>
#include <QLabel>
#include <QProgressBar>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QStringList>
#include <QTreeView>
#include <QVBoxLayout>
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace Kleo::Crypto::Gui;
namespace
{
static Qt::GlobalColor statusColor[] = {
Qt::color0, // Unknown - nothing
Qt::green, // OK
Qt::red, // Failed
Qt::darkRed, // Error
};
static_assert((sizeof(statusColor) / sizeof(*statusColor)) == VerifyChecksumsDialog::NumStatii, "");
class ColorizedFileSystemModel : public QFileSystemModel
{
Q_OBJECT
public:
explicit ColorizedFileSystemModel(QObject *parent = nullptr)
: QFileSystemModel(parent)
, statusMap()
{
}
QVariant data(const QModelIndex &mi, int role = Qt::DisplayRole) const override
{
if (mi.isValid() && role == Qt::BackgroundRole && !SystemInfo::isHighContrastModeActive()) {
const QHash<QString, VerifyChecksumsDialog::Status>::const_iterator it = statusMap.find(filePath(mi));
if (it != statusMap.end())
if (const Qt::GlobalColor c = statusColor[*it]) {
return QColor(c);
}
}
return QFileSystemModel::data(mi, role);
}
public Q_SLOTS:
void setStatus(const QString &file, VerifyChecksumsDialog::Status status)
{
if (status >= VerifyChecksumsDialog::NumStatii || file.isEmpty()) {
return;
}
// canonicalize filename:
const QModelIndex mi = index(file);
const QString canonical = filePath(mi);
if (canonical.isEmpty()) {
qCDebug(KLEOPATRA_LOG) << "can't locate file " << file;
return;
}
const QHash<QString, VerifyChecksumsDialog::Status>::iterator it = statusMap.find(canonical);
if (it != statusMap.end())
if (*it == status) {
return; // nothing to do
} else {
*it = status;
}
else {
statusMap[canonical] = status;
}
emitDataChangedFor(mi);
}
void clearStatusInformation()
{
using std::swap;
QHash<QString, VerifyChecksumsDialog::Status> oldStatusMap;
swap(statusMap, oldStatusMap);
for (QHash<QString, VerifyChecksumsDialog::Status>::const_iterator it = oldStatusMap.constBegin(), end = oldStatusMap.constEnd(); it != end; ++it) {
emitDataChangedFor(it.key());
}
}
private:
void emitDataChangedFor(const QString &file)
{
emitDataChangedFor(index(file));
}
void emitDataChangedFor(const QModelIndex &mi)
{
const QModelIndex p = parent(mi);
Q_EMIT dataChanged(index(mi.row(), 0, p), index(mi.row(), columnCount(p) - 1, p));
}
private:
QHash<QString, VerifyChecksumsDialog::Status> statusMap;
};
static int find_layout_item(const QBoxLayout &blay)
{
for (int i = 0, end = blay.count(); i < end; ++i)
if (QLayoutItem *item = blay.itemAt(i))
if (item->layout()) {
return i;
}
return 0;
}
struct BaseWidget {
QSortFilterProxyModel proxy;
QLabel label;
QTreeView view;
BaseWidget(QFileSystemModel *model, QWidget *parent, QVBoxLayout *vlay)
: proxy()
, label(parent)
, view(parent)
{
KDAB_SET_OBJECT_NAME(proxy);
KDAB_SET_OBJECT_NAME(label);
KDAB_SET_OBJECT_NAME(view);
const int row = find_layout_item(*vlay);
vlay->insertWidget(row, &label);
vlay->insertWidget(row + 1, &view, 1);
proxy.setSourceModel(model);
view.setModel(&proxy);
QRect r;
for (int i = 0; i < proxy.columnCount(); ++i) {
view.resizeColumnToContents(i);
}
// define some minimum sizes
view.header()->resizeSection(0, qMax(view.header()->sectionSize(0), 220));
view.header()->resizeSection(1, qMax(view.header()->sectionSize(1), 75));
view.header()->resizeSection(2, qMax(view.header()->sectionSize(2), 75));
view.header()->resizeSection(3, qMax(view.header()->sectionSize(3), 140));
for (int i = 0; i < proxy.rowCount(); ++i) {
r = r.united(view.visualRect(proxy.index(proxy.columnCount() - 1, i)));
}
view.setMinimumSize(
QSize(qBound(r.width() + 4 * view.frameWidth(), 220 + 75 + 75 + 140 + 4 * view.frameWidth(), 1024), // 100 is the default defaultSectionSize
qBound(r.height(), 220, 512)));
}
void setBase(const QString &base)
{
label.setText(base);
if (auto fsm = qobject_cast<QFileSystemModel *>(proxy.sourceModel())) {
view.setRootIndex(proxy.mapFromSource(fsm->index(base)));
} else {
qCWarning(KLEOPATRA_LOG) << "expect a QFileSystemModel-derived class as proxy.sourceModel(), got ";
if (!proxy.sourceModel()) {
qCWarning(KLEOPATRA_LOG) << "a null pointer";
} else {
qCWarning(KLEOPATRA_LOG) << proxy.sourceModel()->metaObject()->className();
}
}
}
};
} // anon namespace
class VerifyChecksumsDialog::Private
{
friend class ::Kleo::Crypto::Gui::VerifyChecksumsDialog;
VerifyChecksumsDialog *const q;
public:
explicit Private(VerifyChecksumsDialog *qq)
: q(qq)
, bases()
, errors()
, model()
, ui(q)
{
qRegisterMetaType<Status>("Kleo::Crypto::Gui::VerifyChecksumsDialog::Status");
}
private:
void slotErrorButtonClicked()
{
- KMessageBox::errorList(q, i18n("The following errors and warnings were recorded:"), errors, i18n("Checksum Verification Errors"));
+ KMessageBox::errorList(q, i18n("The following errors and warnings were recorded:"), errors, i18nc("@title:window", "Checksum Verification Errors"));
}
private:
void updateErrors()
{
const bool active = ui.isProgressBarActive();
ui.progressLabel.setVisible(active);
ui.progressBar.setVisible(active);
ui.errorLabel.setVisible(!active);
ui.errorButton.setVisible(!active && !errors.empty());
if (errors.empty()) {
ui.errorLabel.setText(i18n("No errors occurred"));
} else {
ui.errorLabel.setText(i18np("One error occurred", "%1 errors occurred", errors.size()));
}
}
private:
QStringList bases;
QStringList errors;
ColorizedFileSystemModel model;
struct UI {
std::vector<BaseWidget *> baseWidgets;
QLabel progressLabel;
QProgressBar progressBar;
QLabel errorLabel;
QPushButton errorButton;
QDialogButtonBox buttonBox;
QVBoxLayout vlay;
QHBoxLayout hlay[2];
explicit UI(VerifyChecksumsDialog *q)
: baseWidgets()
, progressLabel(i18n("Progress:"), q)
, progressBar(q)
, errorLabel(i18n("No errors occurred"), q)
, errorButton(i18nc("Show Errors", "Show"), q)
, buttonBox(QDialogButtonBox::Close, Qt::Horizontal, q)
, vlay(q)
{
KDAB_SET_OBJECT_NAME(progressLabel);
KDAB_SET_OBJECT_NAME(progressBar);
KDAB_SET_OBJECT_NAME(errorLabel);
KDAB_SET_OBJECT_NAME(errorButton);
KDAB_SET_OBJECT_NAME(buttonBox);
KDAB_SET_OBJECT_NAME(vlay);
KDAB_SET_OBJECT_NAME(hlay[0]);
KDAB_SET_OBJECT_NAME(hlay[1]);
errorButton.setAutoDefault(false);
hlay[0].addWidget(&progressLabel);
hlay[0].addWidget(&progressBar, 1);
hlay[1].addWidget(&errorLabel, 1);
hlay[1].addWidget(&errorButton);
vlay.addLayout(&hlay[0]);
vlay.addLayout(&hlay[1]);
vlay.addWidget(&buttonBox);
errorLabel.hide();
errorButton.hide();
QPushButton *close = closeButton();
connect(close, &QPushButton::clicked, q, &VerifyChecksumsDialog::canceled);
connect(close, &QPushButton::clicked, q, &VerifyChecksumsDialog::accept);
connect(&errorButton, SIGNAL(clicked()), q, SLOT(slotErrorButtonClicked()));
}
~UI()
{
qDeleteAll(baseWidgets);
}
QPushButton *closeButton() const
{
return buttonBox.button(QDialogButtonBox::Close);
}
void setBases(const QStringList &bases, QFileSystemModel *model)
{
// create new BaseWidgets:
for (unsigned int i = baseWidgets.size(), end = bases.size(); i < end; ++i) {
baseWidgets.push_back(new BaseWidget(model, vlay.parentWidget(), &vlay));
}
// shed surplus BaseWidgets:
for (unsigned int i = bases.size(), end = baseWidgets.size(); i < end; ++i) {
delete baseWidgets.back();
baseWidgets.pop_back();
}
Q_ASSERT(static_cast<unsigned>(bases.size()) == baseWidgets.size());
// update bases:
for (unsigned int i = 0; i < baseWidgets.size(); ++i) {
baseWidgets[i]->setBase(bases[i]);
}
}
void setProgress(int cur, int tot)
{
progressBar.setMaximum(tot);
progressBar.setValue(cur);
}
bool isProgressBarActive() const
{
const int tot = progressBar.maximum();
const int cur = progressBar.value();
return !tot || cur != tot;
}
} ui;
};
VerifyChecksumsDialog::VerifyChecksumsDialog(QWidget *parent)
: QDialog(parent)
, d(new Private(this))
{
}
VerifyChecksumsDialog::~VerifyChecksumsDialog()
{
}
// slot
void VerifyChecksumsDialog::setBaseDirectories(const QStringList &bases)
{
if (d->bases == bases) {
return;
}
d->bases = bases;
d->ui.setBases(bases, &d->model);
}
// slot
void VerifyChecksumsDialog::setErrors(const QStringList &errors)
{
if (d->errors == errors) {
return;
}
d->errors = errors;
d->updateErrors();
}
// slot
void VerifyChecksumsDialog::setProgress(int cur, int tot)
{
d->ui.setProgress(cur, tot);
d->updateErrors();
}
// slot
void VerifyChecksumsDialog::setStatus(const QString &file, Status status)
{
d->model.setStatus(file, status);
}
// slot
void VerifyChecksumsDialog::clearStatusInformation()
{
d->errors.clear();
d->updateErrors();
d->model.clearStatusInformation();
}
#include "moc_verifychecksumsdialog.cpp"
#include "verifychecksumsdialog.moc"
#endif // QT_NO_DIRMODEL
diff --git a/src/dialogs/certifywidget.cpp b/src/dialogs/certifywidget.cpp
index 9d0190d8a..e9ca33193 100644
--- a/src/dialogs/certifywidget.cpp
+++ b/src/dialogs/certifywidget.cpp
@@ -1,762 +1,762 @@
/* dialogs/certifywidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2019, 2021 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 "certifywidget.h"
#include "view/infofield.h"
#include <utils/accessibility.h>
#include <utils/expiration.h>
#include <utils/keys.h>
#include <settings.h>
#include "kleopatra_debug.h"
#include <KConfigGroup>
#include <KDateComboBox>
#include <KLocalizedString>
#include <KMessageBox>
#include <KMessageWidget>
#include <KSeparator>
#include <KSharedConfig>
#include <Libkleo/Algorithm>
#include <Libkleo/DefaultKeyFilter>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KeySelectionCombo>
#include <Libkleo/NavigatableTreeWidget>
#include <Libkleo/Predicates>
#include <QGpgME/ChangeOwnerTrustJob>
#include <QGpgME/Protocol>
#include <QAction>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QLineEdit>
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QScrollArea>
#include <QToolButton>
#include <QVBoxLayout>
#include <gpgme++/key.h>
Q_DECLARE_METATYPE(GpgME::UserID)
using namespace Kleo;
using namespace GpgME;
static QDebug operator<<(QDebug s, const GpgME::UserID &userID)
{
return s << Formatting::prettyUserID(userID);
}
namespace
{
// Maybe move this in its own file
// based on code from StackOverflow
class AnimatedExpander : public QWidget
{
Q_OBJECT
public:
explicit AnimatedExpander(const QString &title, const QString &accessibleTitle = {}, QWidget *parent = nullptr);
void setContentLayout(QLayout *contentLayout);
private:
QGridLayout mainLayout;
QToolButton toggleButton;
QFrame headerLine;
QParallelAnimationGroup toggleAnimation;
QWidget contentArea;
int animationDuration{300};
};
AnimatedExpander::AnimatedExpander(const QString &title, const QString &accessibleTitle, QWidget *parent)
: QWidget{parent}
{
#ifdef Q_OS_WIN
// draw dotted focus frame if button has focus; otherwise, draw invisible frame using background color
toggleButton.setStyleSheet(
QStringLiteral("QToolButton { border: 1px solid palette(window); }"
"QToolButton:focus { border: 1px dotted palette(window-text); }"));
#else
// this works with Breeze style because Breeze draws the focus frame when drawing CE_ToolButtonLabel
// while the Windows styles (and Qt's common base style) draw the focus frame before drawing CE_ToolButtonLabel
toggleButton.setStyleSheet(QStringLiteral("QToolButton { border: none; }"));
#endif
toggleButton.setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toggleButton.setArrowType(Qt::ArrowType::RightArrow);
toggleButton.setText(title);
if (!accessibleTitle.isEmpty()) {
toggleButton.setAccessibleName(accessibleTitle);
}
toggleButton.setCheckable(true);
toggleButton.setChecked(false);
headerLine.setFrameShape(QFrame::HLine);
headerLine.setFrameShadow(QFrame::Sunken);
headerLine.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
contentArea.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
// start out collapsed
contentArea.setMaximumHeight(0);
contentArea.setMinimumHeight(0);
contentArea.setVisible(false);
// let the entire widget grow and shrink with its content
toggleAnimation.addAnimation(new QPropertyAnimation(this, "minimumHeight"));
toggleAnimation.addAnimation(new QPropertyAnimation(this, "maximumHeight"));
toggleAnimation.addAnimation(new QPropertyAnimation(&contentArea, "maximumHeight"));
mainLayout.setVerticalSpacing(0);
mainLayout.setContentsMargins(0, 0, 0, 0);
int row = 0;
mainLayout.addWidget(&toggleButton, row, 0, 1, 1, Qt::AlignLeft);
mainLayout.addWidget(&headerLine, row++, 2, 1, 1);
mainLayout.addWidget(&contentArea, row, 0, 1, 3);
setLayout(&mainLayout);
QObject::connect(&toggleButton, &QToolButton::clicked, [this](const bool checked) {
if (checked) {
// make the content visible when expanding starts
contentArea.setVisible(true);
}
toggleButton.setArrowType(checked ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow);
toggleAnimation.setDirection(checked ? QAbstractAnimation::Forward : QAbstractAnimation::Backward);
toggleAnimation.start();
});
connect(&toggleAnimation, &QAbstractAnimation::finished, [this]() {
// hide the content area when it is fully collapsed
if (!toggleButton.isChecked()) {
contentArea.setVisible(false);
}
});
}
void AnimatedExpander::setContentLayout(QLayout *contentLayout)
{
delete contentArea.layout();
contentArea.setLayout(contentLayout);
const auto collapsedHeight = sizeHint().height() - contentArea.maximumHeight();
auto contentHeight = contentLayout->sizeHint().height();
for (int i = 0; i < toggleAnimation.animationCount() - 1; ++i) {
auto expanderAnimation = static_cast<QPropertyAnimation *>(toggleAnimation.animationAt(i));
expanderAnimation->setDuration(animationDuration);
expanderAnimation->setStartValue(collapsedHeight);
expanderAnimation->setEndValue(collapsedHeight + contentHeight);
}
auto contentAnimation = static_cast<QPropertyAnimation *>(toggleAnimation.animationAt(toggleAnimation.animationCount() - 1));
contentAnimation->setDuration(animationDuration);
contentAnimation->setStartValue(0);
contentAnimation->setEndValue(contentHeight);
}
class SecKeyFilter : public DefaultKeyFilter
{
public:
SecKeyFilter()
: DefaultKeyFilter()
{
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setHasSecret(DefaultKeyFilter::Set);
setCanCertify(DefaultKeyFilter::Set);
setIsOpenPGP(DefaultKeyFilter::Set);
}
bool matches(const GpgME::Key &key, Kleo::KeyFilter::MatchContexts contexts) const override
{
if (!(availableMatchContexts() & contexts)) {
return false;
}
if (_detail::ByFingerprint<std::equal_to>()(key, mExcludedKey)) {
return false;
}
return DefaultKeyFilter::matches(key, contexts);
}
void setExcludedKey(const GpgME::Key &key)
{
mExcludedKey = key;
}
private:
GpgME::Key mExcludedKey;
};
auto checkBoxSize(const QCheckBox *checkBox)
{
QStyleOptionButton opt;
return checkBox->style()->sizeFromContents(QStyle::CT_CheckBox, &opt, QSize(), checkBox);
}
class TreeWidget : public NavigatableTreeWidget
{
Q_OBJECT
public:
using NavigatableTreeWidget::NavigatableTreeWidget;
protected:
void focusInEvent(QFocusEvent *event) override
{
NavigatableTreeWidget::focusInEvent(event);
// queue the invokation, so that it happens after the widget itself got focus
QMetaObject::invokeMethod(this, &TreeWidget::forceAccessibleFocusEventForCurrentItem, Qt::QueuedConnection);
}
private:
void forceAccessibleFocusEventForCurrentItem()
{
// force Qt to send a focus event for the current item to accessibility
// tools; otherwise, the user has no idea which item is selected when the
// list gets keyboard input focus
const auto current = currentIndex();
setCurrentIndex({});
setCurrentIndex(current);
}
};
}
class CertifyWidget::Private
{
enum Role { UserIdRole = Qt::UserRole };
public:
Private(CertifyWidget *qq)
: q{qq}
{
auto mainLay = new QVBoxLayout{q};
{
auto label = new QLabel{i18n("Verify the fingerprint, mark the user IDs you want to certify, "
"and select the key you want to certify the user IDs with.<br>"
"<i>Note: Only the fingerprint clearly identifies the key and its owner.</i>"),
q};
label->setWordWrap(true);
labelHelper.addLabel(label);
mainLay->addWidget(label);
}
mainLay->addWidget(new KSeparator{Qt::Horizontal, q});
{
auto grid = new QGridLayout;
grid->setColumnStretch(1, 1);
int row = -1;
row++;
mFprField = std::make_unique<InfoField>(i18n("Fingerprint:"), q);
grid->addWidget(mFprField->label(), row, 0);
grid->addLayout(mFprField->layout(), row, 1);
row++;
auto label = new QLabel{i18n("Certify with:"), q};
mSecKeySelect = new KeySelectionCombo{/* secretOnly= */ true, q};
mSecKeySelect->setKeyFilter(std::make_shared<SecKeyFilter>());
label->setBuddy(mSecKeySelect);
grid->addWidget(label, row, 0);
grid->addWidget(mSecKeySelect);
mainLay->addLayout(grid);
}
mMissingOwnerTrustInfo = new KMessageWidget{q};
mSetOwnerTrustAction = new QAction{q};
mSetOwnerTrustAction->setText(i18nc("@action:button", "Set Owner Trust"));
mSetOwnerTrustAction->setToolTip(i18nc("@info:tooltip",
"Click to set the trust level of the selected certification key to ultimate trust. "
"This is what you usually want to do for your own keys."));
connect(mSetOwnerTrustAction, &QAction::triggered, q, [this]() {
setOwnerTrust();
});
mMissingOwnerTrustInfo->addAction(mSetOwnerTrustAction);
mMissingOwnerTrustInfo->setVisible(false);
mainLay->addWidget(mMissingOwnerTrustInfo);
mainLay->addWidget(new KSeparator{Qt::Horizontal, q});
userIdListView = new TreeWidget{q};
userIdListView->setAccessibleName(i18n("User IDs"));
userIdListView->setEditTriggers(QAbstractItemView::NoEditTriggers);
userIdListView->setSelectionMode(QAbstractItemView::SingleSelection);
userIdListView->setRootIsDecorated(false);
userIdListView->setUniformRowHeights(true);
userIdListView->setAllColumnsShowFocus(false);
userIdListView->setHeaderHidden(true);
userIdListView->setHeaderLabels({i18nc("@title:column", "User ID")});
mainLay->addWidget(userIdListView, 1);
// Setup the advanced area
auto expander = new AnimatedExpander{i18n("Advanced"), i18n("Show advanced options"), q};
mainLay->addWidget(expander);
auto advLay = new QVBoxLayout;
mExportCB = new QCheckBox{q};
mExportCB->setText(i18n("Certify for everyone to see (exportable)"));
advLay->addWidget(mExportCB);
{
auto layout = new QHBoxLayout;
mPublishCB = new QCheckBox{q};
mPublishCB->setText(i18n("Publish on keyserver afterwards"));
mPublishCB->setEnabled(mExportCB->isChecked());
layout->addSpacing(checkBoxSize(mExportCB).width());
layout->addWidget(mPublishCB);
advLay->addLayout(layout);
}
{
auto tagsLay = new QHBoxLayout;
auto label = new QLabel{i18n("Tags:"), q};
mTagsLE = new QLineEdit{q};
label->setBuddy(mTagsLE);
const auto tooltip = i18n("You can use this to add additional info to a certification.") + QStringLiteral("<br/><br/>")
+ i18n("Tags created by anyone with full certification trust "
"are shown in the keylist and can be searched.");
label->setToolTip(tooltip);
mTagsLE->setToolTip(tooltip);
tagsLay->addWidget(label);
tagsLay->addWidget(mTagsLE, 1);
advLay->addLayout(tagsLay);
}
{
auto layout = new QHBoxLayout;
mExpirationCheckBox = new QCheckBox{q};
mExpirationCheckBox->setText(i18n("Expiration:"));
mExpirationDateEdit = new KDateComboBox{q};
Kleo::setUpExpirationDateComboBox(mExpirationDateEdit, {QDate::currentDate().addDays(1), QDate{}});
mExpirationDateEdit->setDate(Kleo::defaultExpirationDate(ExpirationOnUnlimitedValidity::InternalDefaultExpiration));
mExpirationDateEdit->setEnabled(mExpirationCheckBox->isChecked());
const auto tooltip = i18n("You can use this to set an expiration date for a certification.") + QStringLiteral("<br/><br/>")
+ i18n("By setting an expiration date, you can limit the validity of "
"your certification to a certain amount of time. Once the expiration "
"date has passed, your certification is no longer valid.");
mExpirationCheckBox->setToolTip(tooltip);
mExpirationDateEdit->setToolTip(tooltip);
layout->addWidget(mExpirationCheckBox);
layout->addWidget(mExpirationDateEdit, 1);
advLay->addLayout(layout);
}
{
mTrustSignatureCB = new QCheckBox{q};
mTrustSignatureCB->setText(i18n("Certify as trusted introducer"));
const auto tooltip = i18n("You can use this to certify a trusted introducer for a domain.") + QStringLiteral("<br/><br/>")
+ i18n("All certificates with email addresses belonging to the domain "
"that have been certified by the trusted introducer are treated "
"as certified, i.e. a trusted introducer acts as a kind of "
"intermediate CA for a domain.");
mTrustSignatureCB->setToolTip(tooltip);
advLay->addWidget(mTrustSignatureCB);
}
{
auto layout = new QHBoxLayout;
auto label = new QLabel{i18n("Domain:"), q};
mTrustSignatureDomainLE = new QLineEdit{q};
mTrustSignatureDomainLE->setEnabled(mTrustSignatureCB->isChecked());
label->setBuddy(mTrustSignatureDomainLE);
layout->addSpacing(checkBoxSize(mTrustSignatureCB).width());
layout->addWidget(label);
layout->addWidget(mTrustSignatureDomainLE);
advLay->addLayout(layout);
}
expander->setContentLayout(advLay);
connect(userIdListView, &QTreeWidget::itemChanged, q, [this](auto item, auto) {
onItemChanged(item);
});
connect(mExportCB, &QCheckBox::toggled, [this](bool on) {
mPublishCB->setEnabled(on);
});
connect(mSecKeySelect, &KeySelectionCombo::currentKeyChanged, [this](const GpgME::Key &) {
updateTags();
checkOwnerTrust();
Q_EMIT q->changed();
});
connect(mExpirationCheckBox, &QCheckBox::toggled, q, [this](bool checked) {
mExpirationDateEdit->setEnabled(checked);
Q_EMIT q->changed();
});
connect(mExpirationDateEdit, &KDateComboBox::dateChanged, q, &CertifyWidget::changed);
connect(mTrustSignatureCB, &QCheckBox::toggled, q, [this](bool on) {
mTrustSignatureDomainLE->setEnabled(on);
Q_EMIT q->changed();
});
connect(mTrustSignatureDomainLE, &QLineEdit::textChanged, q, &CertifyWidget::changed);
loadConfig();
}
~Private() = default;
void loadConfig()
{
const Settings settings;
mExpirationCheckBox->setChecked(settings.certificationValidityInDays() > 0);
if (settings.certificationValidityInDays() > 0) {
const QDate expirationDate = QDate::currentDate().addDays(settings.certificationValidityInDays());
mExpirationDateEdit->setDate(expirationDate > mExpirationDateEdit->maximumDate() //
? mExpirationDateEdit->maximumDate() //
: expirationDate);
}
const KConfigGroup conf(KSharedConfig::openConfig(), "CertifySettings");
mSecKeySelect->setDefaultKey(conf.readEntry("LastKey", QString()));
mExportCB->setChecked(conf.readEntry("ExportCheckState", false));
mPublishCB->setChecked(conf.readEntry("PublishCheckState", false));
}
void setUpUserIdList(const std::vector<GpgME::UserID> &uids)
{
userIdListView->clear();
for (const auto &uid : uids) {
if (uid.isInvalid() || Kleo::isRevokedOrExpired(uid)) {
// Skip user IDs that cannot really be certified.
continue;
}
auto item = new QTreeWidgetItem;
item->setData(0, UserIdRole, QVariant::fromValue(uid));
item->setData(0, Qt::DisplayRole, Kleo::Formatting::prettyUserID(uid));
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
item->setCheckState(0, Qt::Checked);
userIdListView->addTopLevelItem(item);
}
}
void updateTags()
{
struct ItemAndRemark {
QTreeWidgetItem *item;
QString remark;
};
if (mTagsLE->isModified()) {
return;
}
GpgME::Key remarkKey = mSecKeySelect->currentKey();
if (!remarkKey.isNull()) {
std::vector<ItemAndRemark> itemsAndRemarks;
// first choose the remark we want to prefill the Tags field with
QString remark;
for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) {
const auto item = userIdListView->topLevelItem(i);
const auto uid = getUserId(item);
GpgME::Error err;
const char *c_remark = uid.remark(remarkKey, err);
const QString itemRemark = (!err && c_remark) ? QString::fromUtf8(c_remark) : QString{};
if (!itemRemark.isEmpty() && (itemRemark != remark)) {
if (!remark.isEmpty()) {
qCDebug(KLEOPATRA_LOG) << "Different remarks on user IDs. Taking last.";
}
remark = itemRemark;
}
itemsAndRemarks.push_back({item, itemRemark});
}
// then select the user IDs with the chosen remark; this prevents overwriting existing
// different remarks on the other user IDs (as long as the user doesn't select any of
// the unselected user IDs with a different remark)
if (!remark.isEmpty()) {
for (const auto &[item, itemRemark] : itemsAndRemarks) {
item->setCheckState(0, itemRemark == remark ? Qt::Checked : Qt::Unchecked);
}
}
mTagsLE->setText(remark);
}
}
void updateTrustSignatureDomain()
{
if (mTrustSignatureDomainLE->text().isEmpty() && mTarget.numUserIDs() == 1) {
// try to guess the domain to use for the trust signature
const auto address = mTarget.userID(0).addrSpec();
const auto atPos = address.find('@');
if (atPos != std::string::npos) {
const auto domain = address.substr(atPos + 1);
mTrustSignatureDomainLE->setText(QString::fromUtf8(domain.c_str(), domain.size()));
}
}
}
void setTarget(const GpgME::Key &key, const std::vector<GpgME::UserID> &uids)
{
mFprField->setValue(QStringLiteral("<b>") + Formatting::prettyID(key.primaryFingerprint()) + QStringLiteral("</b>"),
Formatting::accessibleHexID(key.primaryFingerprint()));
mTarget = key;
setUpUserIdList(uids.empty() ? mTarget.userIDs() : uids);
auto keyFilter = std::make_shared<SecKeyFilter>();
keyFilter->setExcludedKey(mTarget);
mSecKeySelect->setKeyFilter(keyFilter);
updateTags();
updateTrustSignatureDomain();
}
GpgME::Key secKey() const
{
return mSecKeySelect->currentKey();
}
GpgME::UserID getUserId(const QTreeWidgetItem *item) const
{
return item ? item->data(0, UserIdRole).value<UserID>() : UserID{};
}
void selectUserIDs(const std::vector<GpgME::UserID> &uids)
{
for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) {
const auto uidItem = userIdListView->topLevelItem(i);
const auto itemUserId = getUserId(uidItem);
const bool userIdIsInList = Kleo::any_of(uids, [itemUserId](const auto &uid) {
return Kleo::userIDsAreEqual(itemUserId, uid);
});
uidItem->setCheckState(0, userIdIsInList ? Qt::Checked : Qt::Unchecked);
}
}
std::vector<GpgME::UserID> selectedUserIDs() const
{
std::vector<GpgME::UserID> userIds;
userIds.reserve(userIdListView->topLevelItemCount());
for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) {
const auto *const uidItem = userIdListView->topLevelItem(i);
if (uidItem->checkState(0) == Qt::Checked) {
userIds.push_back(getUserId(uidItem));
}
}
qCDebug(KLEOPATRA_LOG) << "Checked user IDs:" << userIds;
return userIds;
}
bool exportableSelected() const
{
return mExportCB->isChecked();
}
bool publishSelected() const
{
return mPublishCB->isChecked();
}
QString tags() const
{
return mTagsLE->text().trimmed();
}
GpgME::Key target() const
{
return mTarget;
}
bool isValid() const
{
static const QRegularExpression domainNameRegExp{QStringLiteral(R"(^\s*((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}\s*$)"),
QRegularExpression::CaseInsensitiveOption};
// do not accept null keys
if (mTarget.isNull() || mSecKeySelect->currentKey().isNull()) {
return false;
}
// do not accept empty list of user IDs
if (selectedUserIDs().empty()) {
return false;
}
// do not accept if the key to certify is selected as certification key;
// this shouldn't happen because the key to certify is excluded from the choice, but better safe than sorry
if (_detail::ByFingerprint<std::equal_to>()(mTarget, mSecKeySelect->currentKey())) {
return false;
}
if (mExpirationCheckBox->isChecked() && !mExpirationDateEdit->isValid()) {
return false;
}
if (mTrustSignatureCB->isChecked() && !domainNameRegExp.match(mTrustSignatureDomainLE->text()).hasMatch()) {
return false;
}
return true;
}
void checkOwnerTrust()
{
const auto secretKey = secKey();
if (secretKey.ownerTrust() != GpgME::Key::Ultimate) {
mMissingOwnerTrustInfo->setMessageType(KMessageWidget::Information);
mMissingOwnerTrustInfo->setIcon(QIcon::fromTheme(QStringLiteral("question")));
mMissingOwnerTrustInfo->setText(i18n("Is this your own key?"));
mSetOwnerTrustAction->setEnabled(true);
mMissingOwnerTrustInfo->animatedShow();
} else {
mMissingOwnerTrustInfo->animatedHide();
}
}
void setOwnerTrust()
{
mSetOwnerTrustAction->setEnabled(false);
QGpgME::ChangeOwnerTrustJob *const j = QGpgME::openpgp()->changeOwnerTrustJob();
connect(j, &QGpgME::ChangeOwnerTrustJob::result, q, [this](const GpgME::Error &err) {
if (err) {
KMessageBox::error(q,
i18n("<p>Changing the certification trust of the key <b>%1</b> failed:</p><p>%2</p>",
Formatting::formatForComboBox(secKey()),
Formatting::errorAsString(err)),
- i18n("Certification Trust Change Failed"));
+ i18nc("@title:window", "Certification Trust Change Failed"));
}
if (err || err.isCanceled()) {
mSetOwnerTrustAction->setEnabled(true);
} else {
mMissingOwnerTrustInfo->setMessageType(KMessageWidget::Positive);
mMissingOwnerTrustInfo->setIcon(QIcon::fromTheme(QStringLiteral("checkmark")));
mMissingOwnerTrustInfo->setText(i18n("Owner trust set successfully."));
}
});
j->start(secKey(), GpgME::Key::Ultimate);
}
void onItemChanged(QTreeWidgetItem *item)
{
Q_EMIT q->changed();
#ifndef QT_NO_ACCESSIBILITY
if (item) {
// assume that the checked state changed
QAccessible::State st;
st.checked = true;
QAccessibleStateChangeEvent e(userIdListView, st);
e.setChild(userIdListView->indexOfTopLevelItem(item));
QAccessible::updateAccessibility(&e);
}
#endif
}
public:
CertifyWidget *const q;
std::unique_ptr<InfoField> mFprField;
KeySelectionCombo *mSecKeySelect = nullptr;
KMessageWidget *mMissingOwnerTrustInfo = nullptr;
NavigatableTreeWidget *userIdListView = nullptr;
QCheckBox *mExportCB = nullptr;
QCheckBox *mPublishCB = nullptr;
QLineEdit *mTagsLE = nullptr;
QCheckBox *mTrustSignatureCB = nullptr;
QLineEdit *mTrustSignatureDomainLE = nullptr;
QCheckBox *mExpirationCheckBox = nullptr;
KDateComboBox *mExpirationDateEdit = nullptr;
QAction *mSetOwnerTrustAction = nullptr;
LabelHelper labelHelper;
GpgME::Key mTarget;
};
CertifyWidget::CertifyWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
}
Kleo::CertifyWidget::~CertifyWidget() = default;
void CertifyWidget::setTarget(const GpgME::Key &key, const std::vector<GpgME::UserID> &uids)
{
d->setTarget(key, uids);
}
GpgME::Key CertifyWidget::target() const
{
return d->target();
}
void CertifyWidget::selectUserIDs(const std::vector<GpgME::UserID> &uids)
{
d->selectUserIDs(uids);
}
std::vector<GpgME::UserID> CertifyWidget::selectedUserIDs() const
{
return d->selectedUserIDs();
}
GpgME::Key CertifyWidget::secKey() const
{
return d->secKey();
}
bool CertifyWidget::exportableSelected() const
{
return d->exportableSelected();
}
QString CertifyWidget::tags() const
{
return d->tags();
}
bool CertifyWidget::publishSelected() const
{
return d->publishSelected();
}
bool CertifyWidget::trustSignatureSelected() const
{
return d->mTrustSignatureCB->isChecked();
}
QString CertifyWidget::trustSignatureDomain() const
{
return d->mTrustSignatureDomainLE->text().trimmed();
}
QDate CertifyWidget::expirationDate() const
{
return d->mExpirationCheckBox->isChecked() ? d->mExpirationDateEdit->date() : QDate{};
}
bool CertifyWidget::isValid() const
{
return d->isValid();
}
// For UserID model
#include "certifywidget.moc"
#include "moc_certifywidget.cpp"
diff --git a/src/dialogs/deletecertificatesdialog.cpp b/src/dialogs/deletecertificatesdialog.cpp
index 26abee7a2..be4b57766 100644
--- a/src/dialogs/deletecertificatesdialog.cpp
+++ b/src/dialogs/deletecertificatesdialog.cpp
@@ -1,233 +1,233 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/deletecertificatesdialog.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "deletecertificatesdialog.h"
#include <utils/accessibility.h>
#include <view/keytreeview.h>
#include <Libkleo/KeyListModel>
#include <Libkleo/Stl_Util>
#include "kleopatra_debug.h"
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSharedConfig>
#include <KStandardGuiItem>
#include <QCursor>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
#include <QTreeView>
#include <QVBoxLayout>
#include <QWhatsThis>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;
class DeleteCertificatesDialog::Private
{
friend class ::Kleo::Dialogs::DeleteCertificatesDialog;
DeleteCertificatesDialog *const q;
public:
explicit Private(DeleteCertificatesDialog *qq)
: q(qq)
, ui(q)
{
}
void slotWhatsThisRequested()
{
qCDebug(KLEOPATRA_LOG);
if (QWidget *const widget = qobject_cast<QWidget *>(q->sender()))
if (!widget->whatsThis().isEmpty()) {
showToolTip(QCursor::pos(), widget->whatsThis(), widget);
}
}
void readConfig()
{
KConfigGroup dialog(KSharedConfig::openStateConfig(), "DeleteCertificatesDialog");
ui.selectedKTV.restoreLayout(dialog);
ui.unselectedKTV.restoreLayout(dialog);
const QSize size = dialog.readEntry("Size", QSize(600, 400));
if (size.isValid()) {
q->resize(size);
}
}
void writeConfig()
{
KConfigGroup dialog(KSharedConfig::openStateConfig(), "DeleteCertificatesDialog");
ui.selectedKTV.saveLayout(dialog);
dialog.writeEntry("Size", q->size());
dialog.sync();
}
private:
struct UI {
QLabel selectedLB;
KeyTreeView selectedKTV;
QLabel unselectedLB;
KeyTreeView unselectedKTV;
QDialogButtonBox buttonBox;
QVBoxLayout vlay;
explicit UI(DeleteCertificatesDialog *qq)
: selectedLB(i18n("These are the certificates you have selected for deletion:"), qq)
, selectedKTV(qq)
, unselectedLB(i18n("These certificates will be deleted even though you did <b>not</b> "
"explicitly select them (<a href=\"whatsthis://\">Why?</a>):"),
qq)
, unselectedKTV(qq)
, buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
, vlay(qq)
{
KDAB_SET_OBJECT_NAME(selectedLB);
KDAB_SET_OBJECT_NAME(selectedKTV);
KDAB_SET_OBJECT_NAME(unselectedLB);
KDAB_SET_OBJECT_NAME(unselectedKTV);
KDAB_SET_OBJECT_NAME(buttonBox);
KDAB_SET_OBJECT_NAME(vlay);
vlay.addWidget(&selectedLB);
vlay.addWidget(&selectedKTV, 1);
vlay.addWidget(&unselectedLB);
vlay.addWidget(&unselectedKTV, 1);
vlay.addWidget(&buttonBox);
const QString unselectedWhatsThis = xi18nc("@info:whatsthis",
"<title>Why do you want to delete more certificates than I selected?</title>"
"<para>When you delete CA certificates (both root CAs and intermediate CAs), "
"the certificates issued by them will also be deleted.</para>"
"<para>This can be nicely seen in <application>Kleopatra</application>'s "
"hierarchical view mode: In this mode, if you delete a certificate that has "
"children, those children will also be deleted. Think of CA certificates as "
"folders containing other certificates: When you delete the folder, you "
"delete its contents, too.</para>");
unselectedLB.setContextMenuPolicy(Qt::NoContextMenu);
unselectedLB.setWhatsThis(unselectedWhatsThis);
unselectedKTV.setWhatsThis(unselectedWhatsThis);
buttonBox.button(QDialogButtonBox::Ok)->setText(i18nc("@action:button", "Delete"));
connect(&unselectedLB, SIGNAL(linkActivated(QString)), qq, SLOT(slotWhatsThisRequested()));
selectedKTV.setFlatModel(AbstractKeyListModel::createFlatKeyListModel(&selectedKTV));
unselectedKTV.setFlatModel(AbstractKeyListModel::createFlatKeyListModel(&unselectedKTV));
selectedKTV.setHierarchicalView(false);
selectedKTV.view()->setSelectionMode(QAbstractItemView::NoSelection);
unselectedKTV.setHierarchicalView(false);
unselectedKTV.view()->setSelectionMode(QAbstractItemView::NoSelection);
connect(&buttonBox, SIGNAL(accepted()), qq, SLOT(accept()));
connect(&buttonBox, &QDialogButtonBox::rejected, qq, &QDialog::reject);
}
} ui;
};
DeleteCertificatesDialog::DeleteCertificatesDialog(QWidget *p)
: QDialog(p)
, d(new Private(this))
{
d->readConfig();
}
DeleteCertificatesDialog::~DeleteCertificatesDialog()
{
d->writeConfig();
}
void DeleteCertificatesDialog::setSelectedKeys(const std::vector<Key> &keys)
{
d->ui.selectedKTV.setKeys(keys);
}
void DeleteCertificatesDialog::setUnselectedKeys(const std::vector<Key> &keys)
{
d->ui.unselectedLB.setVisible(!keys.empty());
d->ui.unselectedKTV.setVisible(!keys.empty());
d->ui.unselectedKTV.setKeys(keys);
}
std::vector<Key> DeleteCertificatesDialog::keys() const
{
const std::vector<Key> sel = d->ui.selectedKTV.keys();
const std::vector<Key> uns = d->ui.unselectedKTV.keys();
std::vector<Key> result;
result.reserve(sel.size() + uns.size());
result.insert(result.end(), sel.begin(), sel.end());
result.insert(result.end(), uns.begin(), uns.end());
return result;
}
void DeleteCertificatesDialog::accept()
{
const std::vector<Key> sel = d->ui.selectedKTV.keys();
const std::vector<Key> uns = d->ui.unselectedKTV.keys();
const uint secret =
std::count_if(sel.cbegin(), sel.cend(), std::mem_fn(&Key::hasSecret)) + std::count_if(uns.cbegin(), uns.cend(), std::mem_fn(&Key::hasSecret));
const uint total = sel.size() + uns.size();
int ret = KMessageBox::Continue;
if (secret)
ret = KMessageBox::warningContinueCancel(this,
secret == total ? i18np("The certificate to be deleted is your own. "
"It contains private key material, "
"which is needed to decrypt past communication "
"encrypted to the certificate, and should therefore "
"not be deleted.",
"All of the certificates to be deleted "
"are your own. "
"They contain private key material, "
"which is needed to decrypt past communication "
"encrypted to the certificate, and should therefore "
"not be deleted.",
secret)
: i18np("One of the certificates to be deleted "
"is your own. "
"It contains private key material, "
"which is needed to decrypt past communication "
"encrypted to the certificate, and should therefore "
"not be deleted.",
"Some of the certificates to be deleted "
"are your own. "
"They contain private key material, "
"which is needed to decrypt past communication "
"encrypted to the certificate, and should therefore "
"not be deleted.",
secret),
- i18n("Secret Key Deletion"),
+ i18nc("@title:window", "Secret Key Deletion"),
KStandardGuiItem::guiItem(KStandardGuiItem::Delete),
KStandardGuiItem::cancel(),
QString(),
KMessageBox::Notify | KMessageBox::Dangerous);
if (ret == KMessageBox::Continue) {
QDialog::accept();
} else {
QDialog::reject();
}
}
#include "moc_deletecertificatesdialog.cpp"
diff --git a/src/view/nullpinwidget.cpp b/src/view/nullpinwidget.cpp
index 33d1358f4..8ec869030 100644
--- a/src/view/nullpinwidget.cpp
+++ b/src/view/nullpinwidget.cpp
@@ -1,104 +1,104 @@
/* view/nullpinwidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "nullpinwidget.h"
#include "kleopatra_debug.h"
#include "smartcard/netkeycard.h"
#include "commands/changepincommand.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <KLocalizedString>
#include <KMessageBox>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
NullPinWidget::NullPinWidget(QWidget *parent)
: QWidget(parent)
{
const auto nullTitle = i18nc(
"NullPIN is a word that is used all over in the netkey "
"documentation and should be understandable by Netkey cardholders",
"The NullPIN is still active on this card.");
const auto nullDescription = i18n("You need to set a PIN before you can use the certificates.");
const auto descriptionLbl = new QLabel(QStringLiteral("<b>%1</b><br/>%2").arg(nullTitle, nullDescription));
auto vLay = new QVBoxLayout(this);
vLay->addWidget(descriptionLbl, 0, Qt::AlignCenter);
mNKSBtn = new QPushButton(i18nc("NKS is an identifier for a type of keys on a NetKey card", "Set NKS PIN"));
mSigGBtn = new QPushButton(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Set SigG PIN"));
connect(mNKSBtn, &QPushButton::clicked, this, [this]() {
doChangePin(NetKeyCard::nksPinKeyRef());
});
connect(mSigGBtn, &QPushButton::clicked, this, [this]() {
doChangePin(NetKeyCard::sigGPinKeyRef());
});
auto hLayBtn = new QHBoxLayout;
hLayBtn->addStretch(1);
hLayBtn->addWidget(mNKSBtn);
hLayBtn->addWidget(mSigGBtn);
hLayBtn->addStretch(1);
vLay->addLayout(hLayBtn);
}
void NullPinWidget::setSerialNumber(const std::string &serialNumber)
{
mSerialNumber = serialNumber;
}
void NullPinWidget::doChangePin(const std::string &keyRef)
{
parentWidget()->setEnabled(false);
auto ret = KMessageBox::warningContinueCancel(this,
i18n("Setting a PIN is required but <b>can't be reverted</b>.")
+ QStringLiteral("<p>%1</p><p>%2</p>")
.arg(i18n("If you proceed you will be asked to enter a new PIN "
"and later to repeat that PIN."))
.arg(i18n("It will <b>not be possible</b> to recover the "
"card if the PIN has been entered wrongly more than 2 times.")),
- i18n("Set initial PIN"),
+ i18nc("@title:window", "Set initial PIN"),
KStandardGuiItem::cont(),
KStandardGuiItem::cancel());
if (ret != KMessageBox::Continue) {
parentWidget()->setEnabled(true);
return;
}
auto cmd = new ChangePinCommand(mSerialNumber, NetKeyCard::AppName, this);
connect(cmd, &ChangePinCommand::finished, this, [this]() {
this->parentWidget()->setEnabled(true);
});
cmd->setKeyRef(keyRef);
cmd->setMode(ChangePinCommand::NullPinMode);
cmd->start();
}
void NullPinWidget::setSigGVisible(bool val)
{
mSigGBtn->setVisible(val);
}
void NullPinWidget::setNKSVisible(bool val)
{
mNKSBtn->setVisible(val);
}
#include "moc_nullpinwidget.cpp"

File Metadata

Mime Type
text/x-diff
Expires
Wed, Dec 24, 10:49 PM (1 d, 16 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
6b/77/85f75d7ac952286c1d017ef51320

Event Timeline