Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34252396
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
175 KB
Subscribers
None
View Options
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
Details
Attached
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
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment