diff --git a/src/commands/certificatetopivcardcommand.cpp b/src/commands/certificatetopivcardcommand.cpp
index 1bed64025..02c183dd4 100644
--- a/src/commands/certificatetopivcardcommand.cpp
+++ b/src/commands/certificatetopivcardcommand.cpp
@@ -1,278 +1,265 @@
/* commands/certificatetopivcardcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "certificatetopivcardcommand.h"
#include "cardcommand_p.h"
#include "commands/authenticatepivcardapplicationcommand.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include "utils/writecertassuantransaction.h"
#include
#include
#include
#include
#include
#include
#include
#if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36
# define GPG_ERROR_HAS_NO_AUTH
#endif
#include "kleopatra_debug.h"
-#include
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
class CertificateToPIVCardCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::CertificateToPIVCardCommand;
CertificateToPIVCardCommand *q_func() const
{
return static_cast(q);
}
public:
explicit Private(CertificateToPIVCardCommand *qq, const std::string &slot, const std::string &serialno);
~Private() override;
private:
void start();
void startCertificateToPIVCard();
void authenticate();
void authenticationFinished();
void authenticationCanceled();
private:
std::string cardSlot;
Key certificate;
bool hasBeenCanceled = false;
};
CertificateToPIVCardCommand::Private *CertificateToPIVCardCommand::d_func()
{
return static_cast(d.get());
}
const CertificateToPIVCardCommand::Private *CertificateToPIVCardCommand::d_func() const
{
return static_cast(d.get());
}
#define q q_func()
#define d d_func()
CertificateToPIVCardCommand::Private::Private(CertificateToPIVCardCommand *qq, const std::string &slot, const std::string &serialno)
: CardCommand::Private(qq, serialno, nullptr)
, cardSlot(slot)
{
}
CertificateToPIVCardCommand::Private::~Private()
{
}
namespace {
static Key getCertificateToWriteToPIVCard(const std::string &cardSlot, const std::shared_ptr &card)
{
if (!cardSlot.empty()) {
const std::string cardKeygrip = card->keyInfo(cardSlot).grip;
const auto certificate = KeyCache::instance()->findSubkeyByKeyGrip(cardKeygrip).parent();
if (certificate.isNull() || certificate.protocol() != GpgME::CMS) {
return Key();
}
if ((cardSlot == PIVCard::pivAuthenticationKeyRef() && certificate.canSign()) ||
(cardSlot == PIVCard::cardAuthenticationKeyRef() && certificate.canSign()) ||
(cardSlot == PIVCard::digitalSignatureKeyRef() && certificate.canSign()) ||
(cardSlot == PIVCard::keyManagementKeyRef() && certificate.canEncrypt())) {
return certificate;
}
}
return Key();
}
}
void CertificateToPIVCardCommand::Private::start()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::Private::start()";
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard(serialNumber());
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
certificate = getCertificateToWriteToPIVCard(cardSlot, pivCard);
if (certificate.isNull()) {
error(i18n("Sorry! No suitable certificate to write to this card slot was found."));
finished();
return;
}
const QString certificateInfo = i18nc("X.509 certificate DN (validity, created: date)", "%1 (%2, created: %3)",
DN(certificate.userID(0).id()).prettyDN(),
Formatting::complianceStringShort(certificate),
Formatting::creationDateString(certificate));
const QString message = i18nc(
"@info %1 name of card slot, %2 serial number of card",
"Please confirm that you want to write the following certificate to the %1 slot of card %2:
"
"%3 ",
PIVCard::keyDisplayName(cardSlot), QString::fromStdString(serialNumber()), certificateInfo);
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
auto confirmButton = KStandardGuiItem::ok();
-#else
- auto confirmButton = KStandardGuiItem::yes();
-#endif
confirmButton.setText(i18nc("@action:button", "Write certificate"));
confirmButton.setToolTip(QString());
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
const auto choice = KMessageBox::questionTwoActions(
-#else
- const auto choice = KMessageBox::questionYesNo(
-#endif
parentWidgetOrView(),
message,
i18nc("@title:window", "Write certificate to card"),
confirmButton,
KStandardGuiItem::cancel(),
QString(),
KMessageBox::Notify | KMessageBox::WindowModal);
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (choice != KMessageBox::ButtonCode::PrimaryAction) {
-#else
- if (choice != KMessageBox::Yes) {
-#endif
finished();
return;
}
startCertificateToPIVCard();
}
void CertificateToPIVCardCommand::Private::startCertificateToPIVCard()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::Private::startCertificateToPIVCard()";
auto ctx = Context::createForProtocol(GpgME::CMS);
QGpgME::QByteArrayDataProvider dp;
Data data(&dp);
const Error err = ctx->exportPublicKeys(certificate.primaryFingerprint(), data);
if (err) {
error(i18nc("@info", "Exporting the certificate failed: %1", QString::fromUtf8(err.asString())));
finished();
return;
}
const QByteArray certificateData = dp.data();
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard(serialNumber());
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
const QByteArray command = QByteArrayLiteral("SCD WRITECERT ") + QByteArray::fromStdString(cardSlot);
auto transaction = std::unique_ptr(new WriteCertAssuanTransaction(certificateData));
ReaderStatus::mutableInstance()->startTransaction(
pivCard,
command,
q_func(),
[this](const GpgME::Error &err) {
q->certificateToPIVCardDone(err);
},
std::move(transaction));
}
void CertificateToPIVCardCommand::Private::authenticate()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticate()";
auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView());
connect(cmd, &AuthenticatePIVCardApplicationCommand::finished,
q, [this]() { authenticationFinished(); });
connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled,
q, [this]() { authenticationCanceled(); });
cmd->start();
}
void CertificateToPIVCardCommand::Private::authenticationFinished()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticationFinished()";
if (!hasBeenCanceled) {
startCertificateToPIVCard();
}
}
void CertificateToPIVCardCommand::Private::authenticationCanceled()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticationCanceled()";
hasBeenCanceled = true;
canceled();
}
CertificateToPIVCardCommand::CertificateToPIVCardCommand(const std::string& cardSlot, const std::string &serialno)
: CardCommand(new Private(this, cardSlot, serialno))
{
}
CertificateToPIVCardCommand::~CertificateToPIVCardCommand()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::~CertificateToPIVCardCommand()";
}
void CertificateToPIVCardCommand::certificateToPIVCardDone(const Error &err)
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::certificateToPIVCardDone():"
<< err.asString() << "(" << err.code() << ")";
if (err) {
#ifdef GPG_ERROR_HAS_NO_AUTH
// gpgme 1.13 reports "BAD PIN" instead of "NO AUTH"
if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) {
d->authenticate();
return;
}
#endif
d->error(i18nc("@info", "Writing the certificate to the card failed: %1", QString::fromUtf8(err.asString())));
} else if (!err.isCanceled()) {
d->success(i18nc("@info", "Writing the certificate to the card succeeded."));
ReaderStatus::mutableInstance()->updateStatus();
}
d->finished();
}
void CertificateToPIVCardCommand::doStart()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::doStart()";
d->start();
}
void CertificateToPIVCardCommand::doCancel()
{
}
#undef q_func
#undef d_func
diff --git a/src/commands/certifycertificatecommand.cpp b/src/commands/certifycertificatecommand.cpp
index 5c5a0821b..212a3288a 100644
--- a/src/commands/certifycertificatecommand.cpp
+++ b/src/commands/certifycertificatecommand.cpp
@@ -1,349 +1,340 @@
/* -*- 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
#include "certifycertificatecommand.h"
#include "newopenpgpcertificatecommand.h"
#include "command_p.h"
#include "exportopenpgpcertstoservercommand.h"
#include "dialogs/certifycertificatedialog.h"
#include "utils/keys.h"
#include "utils/tags.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "kleopatra_debug.h"
#include
-#include
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(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 uids;
QPointer dialog;
QPointer job;
};
CertifyCertificateCommand::Private *CertifyCertificateCommand::d_func()
{
return static_cast(d.get());
}
const CertifyCertificateCommand::Private *CertifyCertificateCommand::d_func() const
{
return static_cast(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(1, uid).swap(d->uids);
d->init();
}
CertifyCertificateCommand::CertifyCertificateCommand(const std::vector &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 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 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()) {
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
auto sel =
KMessageBox::questionTwoActions(d->parentWidgetOrView(),
-#else
- auto sel = KMessageBox::questionYesNo(d->parentWidgetOrView(),
-#endif
xi18nc("@info", "To certify other certificates, you first need to create an OpenPGP certificate for yourself.")
+ QStringLiteral(" ") + i18n("Do you wish to create one now?"),
i18n("Certification Not Possible"),
KGuiItem(i18n("Create")),
KStandardGuiItem::cancel());
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (sel == KMessageBox::ButtonCode::PrimaryAction) {
-#else
- if (sel == KMessageBox::Yes) {
-#endif
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);
if (d->uids.size()) {
d->dialog->setSelectedUserIDs(d->uids);
}
d->dialog->show();
}
void CertifyCertificateCommand::Private::slotDialogRejected()
{
Q_EMIT q->canceled();
finished();
}
void CertifyCertificateCommand::Private::slotResult(const Error &err)
{
if (!err && !err.isCanceled() && dialog && dialog->exportableCertificationSelected() && dialog->sendToServer()) {
auto const cmd = new ExportOpenPGPCertsToServerCommand(target);
cmd->start();
} else if (!err) {
information(i18n("Certification successful."),
i18n("Certification Succeeded"));
} else {
error(i18n("An error occurred while trying to certify "
"%1 :
\t%2
",
Formatting::formatForComboBox(target),
QString::fromUtf8(err.asString())),
i18n("Certification Error"));
}
if (!dialog->tags().isEmpty()) {
Tags::enableTags();
}
finished();
}
void CertifyCertificateCommand::Private::slotCertificationPrepared()
{
Q_ASSERT(dialog);
const auto selectedUserIds = dialog->selectedUserIDs();
std::vector 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());
job->setCheckLevel(dialog->selectedCheckLevel());
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;
}
connect(j, &Job::progress,
q, &Command::progress);
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/genrevokecommand.cpp b/src/commands/genrevokecommand.cpp
index 205223d07..5c2ab9ec4 100644
--- a/src/commands/genrevokecommand.cpp
+++ b/src/commands/genrevokecommand.cpp
@@ -1,208 +1,199 @@
/* -*- 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
#include "genrevokecommand.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "command_p.h"
#include "kleopatra_debug.h"
-#include
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, errorCaption(),
QStringLiteral("Failed to access the created output file."));
return;
}
const QString revCert = QString::fromLocal8Bit(f.readAll());
f.close();
if (!f.open(QIODevice::WriteOnly)) {
KMessageBox::error(parentWidget, errorCaption(),
QStringLiteral("Failed to write to the created output file."));
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. "
"Note: To prevent accidental import of the revocation "
"it is required to manually edit the certificate "
"before it can be imported."),
i18n("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()) {
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
auto sel = KMessageBox::questionTwoActions(d->parentWidgetOrView(),
-#else
- auto sel = KMessageBox::questionYesNo(d->parentWidgetOrView(),
-#endif
xi18n("The file %1 already exists. Do you wish to overwrite it?", fi.fileName()),
i18nc("@title:window", "Overwrite File?"),
KStandardGuiItem::overwrite(),
KStandardGuiItem::cancel(),
{},
KMessageBox::Notify | KMessageBox::Dangerous);
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (sel == KMessageBox::ButtonCode::SecondaryAction) {
-#else
- if (sel == KMessageBox::No) {
-#endif
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();
QStringList result;
result << gpgPath() << QStringLiteral("--command-fd") << QStringLiteral("0") << QStringLiteral("--status-fd") << QStringLiteral("1")
<< QStringLiteral("-o") << mOutputFileName
<< QStringLiteral("--gen-revoke")
<< QLatin1String(key.primaryFingerprint());
return result;
}
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();
}
diff --git a/src/commands/importcertificatescommand.cpp b/src/commands/importcertificatescommand.cpp
index 8f96edb61..c684a857b 100644
--- a/src/commands/importcertificatescommand.cpp
+++ b/src/commands/importcertificatescommand.cpp
@@ -1,1071 +1,1048 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/importcertificatescommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "importcertificatescommand.h"
#include "importcertificatescommand_p.h"
#include "certifycertificatecommand.h"
#include
#include
#include "kleopatra_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
#include
#endif
#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
#include
#include
#include
#include // for Qt::escape
#include
#include
#include
#include
using namespace GpgME;
using namespace Kleo;
using namespace QGpgME;
bool operator==(const ImportJobData &lhs, const ImportJobData &rhs)
{
return lhs.job == rhs.job;
}
namespace
{
make_comparator_str(ByImportFingerprint, .fingerprint());
class ImportResultProxyModel : public AbstractKeyListSortFilterProxyModel
{
Q_OBJECT
public:
ImportResultProxyModel(const std::vector &results, QObject *parent = nullptr)
: AbstractKeyListSortFilterProxyModel(parent)
{
updateFindCache(results);
}
~ImportResultProxyModel() override {}
ImportResultProxyModel *clone() const override
{
// compiler-generated copy ctor is fine!
return new ImportResultProxyModel(*this);
}
void setImportResults(const std::vector &results)
{
updateFindCache(results);
invalidateFilter();
}
protected:
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid() || role != Qt::ToolTipRole) {
return AbstractKeyListSortFilterProxyModel::data(index, role);
}
const QString fpr = index.data(KeyList::FingerprintRole).toString();
// find information:
const std::vector::const_iterator it
= Kleo::binary_find(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
fpr.toLatin1().constData(),
ByImportFingerprint());
if (it == m_importsByFingerprint.end()) {
return AbstractKeyListSortFilterProxyModel::data(index, role);
} else {
QStringList rv;
const auto ids = m_idsByFingerprint[it->fingerprint()];
rv.reserve(ids.size());
std::copy(ids.cbegin(), ids.cend(), std::back_inserter(rv));
return Formatting::importMetaData(*it, rv);
}
}
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
//
// 0. Keep parents of matching children:
//
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
Q_ASSERT(index.isValid());
for (int i = 0, end = sourceModel()->rowCount(index); i != end; ++i)
if (filterAcceptsRow(i, index)) {
return true;
}
//
// 1. Check that this is an imported key:
//
const QString fpr = index.data(KeyList::FingerprintRole).toString();
return std::binary_search(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
fpr.toLatin1().constData(),
ByImportFingerprint());
}
private:
void updateFindCache(const std::vector &results)
{
m_importsByFingerprint.clear();
m_idsByFingerprint.clear();
m_results = results;
for (const auto &r : results) {
const std::vector imports = r.result.imports();
m_importsByFingerprint.insert(m_importsByFingerprint.end(), imports.begin(), imports.end());
for (std::vector::const_iterator it = imports.begin(), end = imports.end(); it != end; ++it) {
m_idsByFingerprint[it->fingerprint()].insert(r.id);
}
}
std::sort(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
ByImportFingerprint());
}
private:
mutable std::vector m_importsByFingerprint;
mutable std::map< const char *, std::set, ByImportFingerprint > m_idsByFingerprint;
std::vector m_results;
};
bool importFailed(const ImportResultData &r)
{
// ignore GPG_ERR_EOF error to handle the "failed" import of files
// without X.509 certificates by gpgsm gracefully
return r.result.error() && r.result.error().code() != GPG_ERR_EOF;
}
bool importWasCanceled(const ImportResultData &r)
{
return r.result.error().isCanceled();
}
}
ImportCertificatesCommand::Private::Private(ImportCertificatesCommand *qq, KeyListController *c)
: Command::Private(qq, c)
, progressWindowTitle{i18nc("@title:window", "Importing Certificates")}
, progressLabelText{i18n("Importing certificates... (this can take a while)")}
{
}
ImportCertificatesCommand::Private::~Private()
{
if (progressDialog) {
delete progressDialog;
}
}
#define d d_func()
#define q q_func()
ImportCertificatesCommand::ImportCertificatesCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
ImportCertificatesCommand::ImportCertificatesCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
ImportCertificatesCommand::~ImportCertificatesCommand() = default;
static QString format_ids(const std::vector &ids)
{
QStringList escapedIds;
for (const QString &id : ids) {
if (!id.isEmpty()) {
escapedIds << id.toHtmlEscaped();
}
}
return escapedIds.join(QLatin1String(" "));
}
static QString make_tooltip(const std::vector &results)
{
if (results.empty()) {
return {};
}
std::vector ids;
ids.reserve(results.size());
std::transform(std::begin(results), std::end(results),
std::back_inserter(ids),
[](const auto &r) { return r.id; });
std::sort(std::begin(ids), std::end(ids));
ids.erase(std::unique(std::begin(ids), std::end(ids)), std::end(ids));
if (ids.size() == 1)
if (ids.front().isEmpty()) {
return {};
} else
return i18nc("@info:tooltip",
"Imported Certificates from %1",
ids.front().toHtmlEscaped());
else
return i18nc("@info:tooltip",
"Imported certificates from these sources: %1",
format_ids(ids));
}
void ImportCertificatesCommand::Private::setImportResultProxyModel(const std::vector &results)
{
if (std::none_of(std::begin(results), std::end(results),
[](const auto &r) { return r.result.numConsidered() > 0; })) {
return;
}
q->addTemporaryView(i18nc("@title:tab", "Imported Certificates"),
new ImportResultProxyModel(results),
make_tooltip(results));
if (QTreeView *const tv = qobject_cast(parentWidgetOrView())) {
tv->expandAll();
}
}
int sum(const std::vector &res, int (ImportResult::*fun)() const)
{
return kdtools::accumulate_transform(res.begin(), res.end(), std::mem_fn(fun), 0);
}
static QString make_report(const std::vector &results,
const std::vector &groups)
{
const KLocalizedString normalLine = ki18n("%1 %2 ");
const KLocalizedString boldLine = ki18n("%1 %2 ");
const KLocalizedString headerLine = ki18n("%1 ");
std::vector res;
res.reserve(results.size());
std::transform(std::begin(results), std::end(results),
std::back_inserter(res),
[](const auto &r) { return r.result; });
const auto numProcessedCertificates = sum(res, &ImportResult::numConsidered);
QStringList lines;
if (numProcessedCertificates > 0 || groups.size() == 0) {
lines.push_back(headerLine.subs(i18n("Certificates")).toString());
lines.push_back(normalLine.subs(i18n("Total number processed:"))
.subs(numProcessedCertificates).toString());
lines.push_back(normalLine.subs(i18n("Imported:"))
.subs(sum(res, &ImportResult::numImported)).toString());
if (const int n = sum(res, &ImportResult::newSignatures))
lines.push_back(normalLine.subs(i18n("New signatures:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::newUserIDs))
lines.push_back(normalLine.subs(i18n("New user IDs:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numKeysWithoutUserID))
lines.push_back(normalLine.subs(i18n("Certificates without user IDs:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::newSubkeys))
lines.push_back(normalLine.subs(i18n("New subkeys:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::newRevocations))
lines.push_back(boldLine.subs(i18n("Newly revoked:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::notImported))
lines.push_back(boldLine.subs(i18n("Not imported:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numUnchanged))
lines.push_back(normalLine.subs(i18n("Unchanged:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysConsidered))
lines.push_back(normalLine.subs(i18n("Secret keys processed:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysImported))
lines.push_back(normalLine.subs(i18n("Secret keys imported:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysConsidered) - sum(res, &ImportResult::numSecretKeysImported) - sum(res, &ImportResult::numSecretKeysUnchanged))
if (n > 0)
lines.push_back(boldLine.subs(i18n("Secret keys not imported:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysUnchanged))
lines.push_back(normalLine.subs(i18n("Secret keys unchanged:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numV3KeysSkipped))
lines.push_back(normalLine.subs(i18n("Deprecated PGP-2 keys skipped:"))
.subs(n).toString());
}
if (!lines.empty()) {
lines.push_back(headerLine.subs(QLatin1String{" "}).toString());
}
if (groups.size() > 0) {
const auto newGroups = std::count_if(std::begin(groups), std::end(groups),
[](const auto &g) {
return g.status == ImportedGroup::Status::New;
});
const auto updatedGroups = groups.size() - newGroups;
lines.push_back(headerLine.subs(i18n("Certificate Groups")).toString());
lines.push_back(normalLine.subs(i18n("Total number processed:"))
.subs(groups.size()).toString());
lines.push_back(normalLine.subs(i18n("New groups:"))
.subs(newGroups).toString());
lines.push_back(normalLine.subs(i18n("Updated groups:"))
.subs(updatedGroups).toString());
}
return lines.join(QLatin1String{});
}
static bool isImportFromSingleSource(const std::vector &res)
{
return (res.size() == 1) || (res.size() == 2 && res[0].id == res[1].id);
}
static QString make_message_report(const std::vector &res,
const std::vector &groups)
{
QString report{QLatin1String{""}};
if (res.empty()) {
report += i18n("No imports (should not happen, please report a bug).");
} else {
const QString title = isImportFromSingleSource(res) && !res.front().id.isEmpty() ?
i18n("Detailed results of importing %1:", res.front().id) :
i18n("Detailed results of import:");
report += QLatin1String{""} + title + QLatin1String{"
"};
report += QLatin1String{"
"};
report += make_report(res, groups);
report += QLatin1String{"
"};
}
report += QLatin1String{""};
return report;
}
// Returns false on error, true if please certify was shown.
bool ImportCertificatesCommand::Private::showPleaseCertify(const GpgME::Import &imp)
{
const char *fpr = imp.fingerprint();
if (!fpr) {
// WTF
qCWarning(KLEOPATRA_LOG) << "Import without fingerprint";
return false;
}
// Exactly one public key imported. Let's see if it is openpgp. We are async here so
// we can just fetch it.
auto ctx = GpgME::Context::createForProtocol(GpgME::OpenPGP);
if (!ctx) {
// WTF
qCWarning(KLEOPATRA_LOG) << "Failed to create OpenPGP proto";
return false;
}
GpgME::Error err;
auto key = ctx->key(fpr, err, false);
delete ctx;
if (key.isNull() || err) {
// No such key most likely not OpenPGP
return false;
}
if (!Kleo::canBeCertified(key)) {
// key is expired or revoked
return false;
}
for (const auto &uid: key.userIDs()) {
if (uid.validity() >= GpgME::UserID::Marginal) {
// Already marginal so don't bug the user
return false;
}
}
const QStringList suggestions = QStringList() << i18n("A phone call to the person.")
<< i18n("Using a business card.")
<< i18n("Confirming it on a trusted website.");
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
auto sel = KMessageBox::questionTwoActions(parentWidgetOrView(),
-#else
- auto sel = KMessageBox::questionYesNo(parentWidgetOrView(),
-#endif
i18n("In order to mark the certificate as valid (green) it needs to be certified.") + QStringLiteral(" ")
+ i18n("Certifying means that you check the Fingerprint.") + QStringLiteral(" ")
+ i18n("Some suggestions to do this are:")
+ QStringLiteral("%1").arg(suggestions.join(QStringLiteral(" ") + i18n("Do you wish to start this process now?"),
i18nc("@title", "You have imported a new certificate (public key)"),
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
KGuiItem(i18n("Import")),
KStandardGuiItem::cancel(),
-#else
- KStandardGuiItem::yes(),
- KStandardGuiItem::no(),
-#endif
QStringLiteral("CertifyQuestion"));
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (sel == KMessageBox::ButtonCode::PrimaryAction) {
-#else
- if (sel == KMessageBox::Yes) {
-#endif
QEventLoop loop;
auto cmd = new Commands::CertifyCertificateCommand(key);
cmd->setParentWidget(parentWidgetOrView());
connect(cmd, &Command::finished, &loop, &QEventLoop::quit);
QMetaObject::invokeMethod(cmd, &Commands::CertifyCertificateCommand::start, Qt::QueuedConnection);
loop.exec();
}
return true;
}
namespace
{
/**
* Returns the Import of an OpenPGP key, if a single certificate was imported and this was an OpenPGP key.
* Otherwise, returns a null Import.
*/
auto getSingleOpenPGPImport(const std::vector &res)
{
static const Import nullImport;
if (!isImportFromSingleSource(res)) {
return nullImport;
}
const auto numImported = std::accumulate(res.cbegin(), res.cend(), 0, [](auto s, const auto &r) {
return s + r.result.numImported();
});
if (numImported > 1) {
return nullImport;
}
if ((res.size() >= 1) && (res[0].protocol == GpgME::OpenPGP) && (res[0].result.numImported() == 1) && (res[0].result.imports().size() == 1)) {
return res[0].result.imports()[0];
} else if ((res.size() == 2) && (res[1].protocol == GpgME::OpenPGP) && (res[1].result.numImported() == 1) && (res[1].result.imports().size() == 1)) {
return res[1].result.imports()[0];
}
return nullImport;
}
}
void ImportCertificatesCommand::Private::showDetails(const std::vector &res,
const std::vector &groups)
{
const auto singleOpenPGPImport = getSingleOpenPGPImport(res);
if (!singleOpenPGPImport.isNull()) {
if (showPleaseCertify(singleOpenPGPImport)) {
return;
}
}
setImportResultProxyModel(res);
information(make_message_report(res, groups),
i18n("Certificate Import Result"));
}
static QString make_error_message(const Error &err, const QString &id)
{
Q_ASSERT(err);
Q_ASSERT(!err.isCanceled());
return id.isEmpty()
? i18n("An error occurred while trying "
"to import the certificate:
"
"%1
",
QString::fromLocal8Bit(err.asString()))
: i18n("An error occurred while trying "
"to import the certificate %1:
"
"%2
",
id, QString::fromLocal8Bit(err.asString()));
}
void ImportCertificatesCommand::Private::showError(QWidget *parent, const Error &err, const QString &id)
{
if (parent) {
KMessageBox::error(parent, make_error_message(err, id), i18n("Certificate Import Failed"));
} else {
showError(err, id);
}
}
void ImportCertificatesCommand::Private::showError(const Error &err, const QString &id)
{
error(make_error_message(err, id), i18n("Certificate Import Failed"));
}
void ImportCertificatesCommand::Private::setWaitForMoreJobs(bool wait)
{
if (wait == waitForMoreJobs) {
return;
}
waitForMoreJobs = wait;
if (!waitForMoreJobs) {
tryToFinish();
}
}
void ImportCertificatesCommand::Private::importResult(const ImportResult &result, QGpgME::Job *finishedJob)
{
if (!finishedJob) {
finishedJob = qobject_cast(q->sender());
}
Q_ASSERT(finishedJob);
auto it = std::find_if(std::cbegin(jobs), std::cend(jobs),
[finishedJob](const auto &job) { return job.job == finishedJob; });
Q_ASSERT(it != std::cend(jobs));
if (it == std::cend(jobs)) {
qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Finished job not found";
return;
}
const auto job = *it;
jobs.erase(std::remove(std::begin(jobs), std::end(jobs), job), std::end(jobs));
increaseProgressValue();
importResult({job.id, job.protocol, job.type, result});
}
void ImportCertificatesCommand::Private::importResult(const ImportResultData &result)
{
qCDebug(KLEOPATRA_LOG) << __func__ << result.id;
results.push_back(result);
tryToFinish();
}
static void handleOwnerTrust(const std::vector &results)
{
//iterate over all imported certificates
for (const auto &r: results) {
//when a new certificate got a secret key
if (r.result.numSecretKeysImported() >= 1) {
const char *fingerPr = r.result.imports()[0].fingerprint();
GpgME::Error err;
QScopedPointer
ctx(Context::createForProtocol(GpgME::Protocol::OpenPGP));
if (!ctx){
qCWarning(KLEOPATRA_LOG) << "Failed to get context";
continue;
}
const Key toTrustOwner = ctx->key(fingerPr, err , false);
if (toTrustOwner.isNull()) {
return;
}
const auto toTrustOwnerUserIDs{toTrustOwner.userIDs()};
// ki18n(" ") as initializer because initializing with empty string leads to
// (I18N_EMPTY_MESSAGE)
const KLocalizedString uids = std::accumulate(toTrustOwnerUserIDs.cbegin(), toTrustOwnerUserIDs.cend(), KLocalizedString{ki18n(" ")}, [](KLocalizedString temp, const auto &uid) {
return kxi18nc("@info", "%1- %2
").subs(temp).subs(Formatting::prettyNameAndEMail(uid));
});
const QString str = xi18nc("@info",
"You have imported a Secret Key. "
"The key has the fingerprint: "
"%1 "
" "
"And claims the user IDs:"
"%2
"
" "
"Is this your own key? (Set trust level to ultimate)",
QString::fromUtf8(fingerPr),
uids);
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
int k = KMessageBox::questionTwoActions(nullptr,
str,
-#else
- int k = KMessageBox::questionYesNo(nullptr,
- str,
-#endif
i18nc("@title:window", "Secret key imported"),
KGuiItem(i18n("Import")),
KStandardGuiItem::cancel());
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (k == KMessageBox::ButtonCode::PrimaryAction) {
-#else
- if (k == KMessageBox::Yes) {
-#endif
//To use the ChangeOwnerTrustJob over
//the CryptoBackendFactory
const QGpgME::Protocol *const backend = QGpgME::openpgp();
if (!backend){
qCWarning(KLEOPATRA_LOG) << "Failed to get CryptoBackend";
return;
}
ChangeOwnerTrustJob *const j = backend->changeOwnerTrustJob();
j->start(toTrustOwner, Key::Ultimate);
}
}
}
}
static void validateImportedCertificate(const GpgME::Import &import)
{
if (const auto fpr = import.fingerprint()) {
auto key = KeyCache::instance()->findByFingerprint(fpr);
if (!key.isNull()) {
// this triggers a keylisting with validation for this certificate
key.update();
} else {
qCWarning(KLEOPATRA_LOG) << __func__ << "Certificate with fingerprint" << fpr << "not found";
}
}
}
static void handleExternalCMSImports(const std::vector &results)
{
// For external CMS Imports we have to manually do a keylist
// with validation to get the intermediate and root ca imported
// automatically if trusted-certs and extra-certs are used.
for (const auto &r : results) {
if (r.protocol == GpgME::CMS && r.type == ImportType::External
&& !importFailed(r) && !importWasCanceled(r)) {
const auto imports = r.result.imports();
std::for_each(std::begin(imports), std::end(imports), &validateImportedCertificate);
}
}
}
void ImportCertificatesCommand::Private::processResults()
{
importGroups();
#ifdef QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
if (Settings{}.retrieveSignerKeysAfterImport() && !importingSignerKeys) {
importingSignerKeys = true;
const auto missingSignerKeys = getMissingSignerKeyIds(results);
if (!missingSignerKeys.empty()) {
importSignerKeys(missingSignerKeys);
return;
}
}
#endif
handleExternalCMSImports(results);
// ensure that the progress dialog is closed before we show any other dialogs
setProgressToMaximum();
handleOwnerTrust(results);
showDetails(results, importedGroups);
auto tv = dynamic_cast (view());
if (!tv) {
qCDebug(KLEOPATRA_LOG) << "Failed to find treeview";
} else {
tv->expandAll();
}
finished();
}
void ImportCertificatesCommand::Private::tryToFinish()
{
if (waitForMoreJobs || !jobs.empty()) {
return;
}
auto keyCache = KeyCache::mutableInstance();
keyListConnection = connect(keyCache.get(), &KeyCache::keyListingDone,
q, [this]() { keyCacheUpdated(); });
keyCache->startKeyListing();
}
void ImportCertificatesCommand::Private::keyCacheUpdated()
{
disconnect(keyListConnection);
keyCacheAutoRefreshSuspension.reset();
const auto allIds = std::accumulate(std::cbegin(results), std::cend(results),
std::set{},
[](auto allIds, const auto &r) {
allIds.insert(r.id);
return allIds;
});
const auto canceledIds = std::accumulate(std::cbegin(results), std::cend(results),
std::set{},
[](auto canceledIds, const auto &r) {
if (importWasCanceled(r)) {
canceledIds.insert(r.id);
}
return canceledIds;
});
const auto totalConsidered = std::accumulate(std::cbegin(results), std::cend(results),
0,
[](auto totalConsidered, const auto &r) {
return totalConsidered + r.result.numConsidered();
});
if (totalConsidered == 0 && canceledIds.size() == allIds.size()) {
// nothing was considered for import and at least one import per id was
// canceled => treat the command as canceled
canceled();
return;
}
if (std::any_of(std::cbegin(results), std::cend(results), &importFailed)) {
// ensure that the progress dialog is closed before we show any other dialogs
setProgressToMaximum();
setImportResultProxyModel(results);
for (const auto &r : results) {
if (importFailed(r)) {
showError(r.result.error(), r.id);
}
}
finished();
return;
}
processResults();
}
static ImportedGroup storeGroup(const KeyGroup &group, const QString &id)
{
const auto status = KeyCache::instance()->group(group.id()).isNull() ?
ImportedGroup::Status::New :
ImportedGroup::Status::Updated;
if (status == ImportedGroup::Status::New) {
KeyCache::mutableInstance()->insert(group);
} else {
KeyCache::mutableInstance()->update(group);
}
return {id, group, status};
}
void ImportCertificatesCommand::Private::importGroups()
{
for (const auto &path : filesToImportGroupsFrom) {
const bool certificateImportSucceeded =
std::any_of(std::cbegin(results), std::cend(results),
[path](const auto &r) {
return r.id == path && !importFailed(r) && !importWasCanceled(r);
});
if (certificateImportSucceeded) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Importing groups from file" << path;
const auto groups = readKeyGroups(path);
std::transform(std::begin(groups), std::end(groups),
std::back_inserter(importedGroups),
[path](const auto &group) {
return storeGroup(group, path);
});
}
increaseProgressValue();
}
filesToImportGroupsFrom.clear();
}
static auto accumulateNewKeys(std::vector &fingerprints, const std::vector &imports)
{
return std::accumulate(std::begin(imports), std::end(imports),
fingerprints,
[](auto fingerprints, const auto &import) {
if (import.status() == Import::NewKey) {
fingerprints.push_back(import.fingerprint());
}
return fingerprints;
});
}
static auto accumulateNewOpenPGPKeys(const std::vector &results)
{
return std::accumulate(std::begin(results), std::end(results),
std::vector{},
[](auto fingerprints, const auto &r) {
if (r.protocol == GpgME::OpenPGP) {
fingerprints = accumulateNewKeys(fingerprints, r.result.imports());
}
return fingerprints;
});
}
std::set ImportCertificatesCommand::Private::getMissingSignerKeyIds(const std::vector &results)
{
auto newOpenPGPKeys = KeyCache::instance()->findByFingerprint(accumulateNewOpenPGPKeys(results));
// update all new OpenPGP keys to get information about certifications
std::for_each(std::begin(newOpenPGPKeys), std::end(newOpenPGPKeys), std::mem_fn(&Key::update));
auto missingSignerKeyIds = Kleo::getMissingSignerKeyIds(newOpenPGPKeys);
return missingSignerKeyIds;
}
void ImportCertificatesCommand::Private::importSignerKeys(const std::set &keyIds)
{
Q_ASSERT(!keyIds.empty());
setProgressLabelText(i18np("Fetching 1 signer key... (this can take a while)",
"Fetching %1 signer keys... (this can take a while)",
keyIds.size()));
setWaitForMoreJobs(true);
// start one import per key id to allow canceling the key retrieval without
// losing already retrieved keys
for (const auto &keyId : keyIds) {
startImport(GpgME::OpenPGP, {keyId}, QStringLiteral("Retrieve Signer Keys"));
}
setWaitForMoreJobs(false);
}
static std::unique_ptr get_import_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
return std::unique_ptr(backend->importJob());
} else {
return std::unique_ptr();
}
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const QByteArray &data, const QString &id,
[[maybe_unused]] const ImportOptions &options)
{
Q_ASSERT(protocol != UnknownProtocol);
if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) {
return;
}
std::unique_ptr job = get_import_job(protocol);
if (!job.get()) {
nonWorkingProtocols.push_back(protocol);
error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.",
Formatting::displayName(protocol)),
i18n("Certificate Import Failed"));
importResult({id, protocol, ImportType::Local, ImportResult{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector connections = {
connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { importResult(result); }),
connect(job.get(), &Job::progress,
q, &Command::progress)
};
#ifdef QGPGME_SUPPORTS_IMPORT_WITH_FILTER
job->setImportFilter(options.importFilter);
#endif
#ifdef QGPGME_SUPPORTS_IMPORT_WITH_KEY_ORIGIN
job->setKeyOrigin(options.keyOrigin, options.keyOriginUrl);
#endif
const GpgME::Error err = job->start(data);
if (err.code()) {
importResult({id, protocol, ImportType::Local, ImportResult{err}});
} else {
increaseProgressMaximum();
jobs.push_back({id, protocol, ImportType::Local, job.release(), connections});
}
}
static std::unique_ptr get_import_from_keyserver_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
return std::unique_ptr(backend->importFromKeyserverJob());
} else {
return std::unique_ptr();
}
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const std::vector &keys, const QString &id)
{
Q_ASSERT(protocol != UnknownProtocol);
if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) {
return;
}
std::unique_ptr job = get_import_from_keyserver_job(protocol);
if (!job.get()) {
nonWorkingProtocols.push_back(protocol);
error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.",
Formatting::displayName(protocol)),
i18n("Certificate Import Failed"));
importResult({id, protocol, ImportType::External, ImportResult{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector connections = {
connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { importResult(result); }),
connect(job.get(), &Job::progress,
q, &Command::progress)
};
const GpgME::Error err = job->start(keys);
if (err.code()) {
importResult({id, protocol, ImportType::External, ImportResult{err}});
} else {
increaseProgressMaximum();
jobs.push_back({id, protocol, ImportType::External, job.release(), connections});
}
}
static auto get_receive_keys_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
#ifdef QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
std::unique_ptr job{};
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
job.reset(backend->receiveKeysJob());
}
return job;
#else
return std::unique_ptr{};
#endif
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, [[maybe_unused]] const QStringList &keyIds, const QString &id)
{
Q_ASSERT(protocol != UnknownProtocol);
auto job = get_receive_keys_job(protocol);
if (!job.get()) {
qCWarning(KLEOPATRA_LOG) << "Failed to get ReceiveKeysJob for protocol" << Formatting::displayName(protocol);
importResult({id, protocol, ImportType::External, ImportResult{}});
return;
}
#ifdef QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector connections = {
connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) {
importResult(result);
}),
connect(job.get(), &Job::progress,
q, &Command::progress)
};
const GpgME::Error err = job->start(keyIds);
if (err.code()) {
importResult({id, protocol, ImportType::External, ImportResult{err}});
} else {
increaseProgressMaximum();
jobs.push_back({id, protocol, ImportType::External, job.release(), connections});
}
#endif
}
void ImportCertificatesCommand::Private::importGroupsFromFile(const QString &filename)
{
increaseProgressMaximum();
filesToImportGroupsFrom.push_back(filename);
}
void ImportCertificatesCommand::Private::setUpProgressDialog()
{
if (progressDialog) {
return;
}
progressDialog = new QProgressDialog{parentWidgetOrView()};
progressDialog->setModal(true);
progressDialog->setWindowTitle(progressWindowTitle);
progressDialog->setLabelText(progressLabelText);
progressDialog->setMinimumDuration(1000);
progressDialog->setMaximum(1);
progressDialog->setValue(0);
connect(progressDialog, &QProgressDialog::canceled, q, &Command::cancel);
connect(q, &Command::finished, progressDialog, [this]() { progressDialog->accept(); });
}
void ImportCertificatesCommand::Private::setProgressWindowTitle(const QString &title)
{
if (progressDialog) {
progressDialog->setWindowTitle(title);
} else {
progressWindowTitle = title;
}
}
void ImportCertificatesCommand::Private::setProgressLabelText(const QString &text)
{
if (progressDialog) {
progressDialog->setLabelText(text);
} else {
progressLabelText = text;
}
}
void ImportCertificatesCommand::Private::increaseProgressMaximum()
{
setUpProgressDialog();
progressDialog->setMaximum(progressDialog->maximum() + 1);
qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum();
}
void ImportCertificatesCommand::Private::increaseProgressValue()
{
progressDialog->setValue(progressDialog->value() + 1);
qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum();
}
void ImportCertificatesCommand::Private::setProgressToMaximum()
{
qCDebug(KLEOPATRA_LOG) << __func__;
progressDialog->setValue(progressDialog->maximum());
}
static void disconnectConnection(const QMetaObject::Connection &connection)
{
// trivial function for disconnecting a signal-slot connection because
// using a lambda seems to confuse older GCC / MinGW and unnecessarily
// capturing 'this' generates warnings
QObject::disconnect(connection);
}
void ImportCertificatesCommand::doCancel()
{
const auto jobsToCancel = d->jobs;
std::for_each(std::begin(jobsToCancel), std::end(jobsToCancel),
[this](const auto &job) {
qCDebug(KLEOPATRA_LOG) << "Canceling job" << job.job;
std::for_each(std::cbegin(job.connections), std::cend(job.connections),
&disconnectConnection);
job.job->slotCancel();
d->importResult(ImportResult{Error::fromCode(GPG_ERR_CANCELED)}, job.job);
});
}
#undef d
#undef q
#include "importcertificatescommand.moc"
#include "moc_importcertificatescommand.cpp"
diff --git a/src/commands/keytocardcommand.cpp b/src/commands/keytocardcommand.cpp
index e23be6b1a..f577441b8 100644
--- a/src/commands/keytocardcommand.cpp
+++ b/src/commands/keytocardcommand.cpp
@@ -1,530 +1,520 @@
/* commands/keytocardcommand.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-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "keytocardcommand.h"
#include "cardcommand_p.h"
#include "commands/authenticatepivcardapplicationcommand.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include "smartcard/utils.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36
# define GPG_ERROR_HAS_NO_AUTH
#endif
#include "kleopatra_debug.h"
-#include
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
class KeyToCardCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::KeyToCardCommand;
KeyToCardCommand *q_func() const
{
return static_cast(q);
}
public:
explicit Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey);
explicit Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName);
~Private() override;
private:
void start();
void startKeyToOpenPGPCard();
Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr &card);
void startKeyToPIVCard();
void authenticate();
void authenticationFinished();
void authenticationCanceled();
private:
std::string appName;
GpgME::Subkey subkey;
std::string cardSlot;
bool overwriteExistingAlreadyApproved = false;
bool hasBeenCanceled = false;
};
KeyToCardCommand::Private *KeyToCardCommand::d_func()
{
return static_cast(d.get());
}
const KeyToCardCommand::Private *KeyToCardCommand::d_func() const
{
return static_cast(d.get());
}
#define q q_func()
#define d d_func()
KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey_)
: CardCommand::Private(qq, "", nullptr)
, subkey(subkey_)
{
}
KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName_)
: CardCommand::Private(qq, serialNumber, nullptr)
, appName(appName_)
, cardSlot(slot)
{
}
KeyToCardCommand::Private::~Private()
{
}
namespace {
static std::shared_ptr getCardToTransferSubkeyTo(const Subkey &subkey, QWidget *parent)
{
const std::vector > suitableCards = KeyToCardCommand::getSuitableCards(subkey);
if (suitableCards.empty()) {
return std::shared_ptr();
} else if (suitableCards.size() == 1) {
return suitableCards[0];
}
QStringList options;
for (const auto &card: suitableCards) {
options.push_back(i18nc("smartcard application - serial number of smartcard", "%1 - %2",
displayAppName(card->appName()), card->displaySerialNumber()));
}
bool ok;
const QString choice = QInputDialog::getItem(parent, i18n("Select Card"),
i18n("Please select the card the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok);
if (!ok) {
return std::shared_ptr();
}
const int index = options.indexOf(choice);
return suitableCards[index];
}
}
void KeyToCardCommand::Private::start()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()";
if (!subkey.isNull() && serialNumber().empty()) {
const auto card = getCardToTransferSubkeyTo(subkey, parentWidgetOrView());
if (!card) {
finished();
return;
}
setSerialNumber(card->serialNumber());
appName = card->appName();
}
const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName);
if (!card) {
error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
if (card->appName() == SmartCard::OpenPGPCard::AppName) {
startKeyToOpenPGPCard();
} else if (card->appName() == SmartCard::PIVCard::AppName) {
startKeyToPIVCard();
} else {
error(i18n("Sorry! Transferring keys to this card is not supported."));
finished();
return;
}
}
namespace {
static int getOpenPGPCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent)
{
// Check if we need to ask the user for the slot
if ((subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt() && !subKey.canAuthenticate()) {
// Signing only
return 1;
}
if (subKey.canEncrypt() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canAuthenticate()) {
// Encrypt only
return 2;
}
if (subKey.canAuthenticate() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt()) {
// Auth only
return 3;
}
// Multiple uses, ask user.
QStringList options;
if (subKey.canSign() || subKey.canCertify()) {
options << i18nc("Placeholder is the number of a slot on a smart card", "Signature (%1)", 1);
}
if (subKey.canEncrypt()) {
options << i18nc("Placeholder is the number of a slot on a smart card", "Encryption (%1)", 2);
}
if (subKey.canAuthenticate()) {
options << i18nc("Placeholder is the number of a slot on a smart card", "Authentication (%1)", 3);
}
bool ok;
const QString choice = QInputDialog::getItem(parent, i18n("Select Card Slot"),
i18n("Please select the card slot the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok);
const int slot = options.indexOf(choice) + 1;
return ok ? slot : -1;
}
}
void KeyToCardCommand::Private::startKeyToOpenPGPCard() {
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToOpenPGPCard()";
const auto pgpCard = SmartCard::ReaderStatus::instance()->getCard(serialNumber());
if (!pgpCard) {
error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
if (subkey.isNull()) {
finished();
return;
}
if (subkey.parent().protocol() != GpgME::OpenPGP) {
error(i18n("Sorry! This key cannot be transferred to an OpenPGP card."));
finished();
return;
}
const auto slot = getOpenPGPCardSlotForKey(subkey, parentWidgetOrView());
if (slot < 1) {
finished();
return;
}
// Check if we need to do the overwrite warning.
std::string existingKey;
QString encKeyWarning;
if (slot == 1) {
existingKey = pgpCard->keyFingerprint(OpenPGPCard::pgpSigKeyRef());
} else if (slot == 2) {
existingKey = pgpCard->keyFingerprint(OpenPGPCard::pgpEncKeyRef());
encKeyWarning = i18n("It will no longer be possible to decrypt past communication "
"encrypted for the existing key.");
} else if (slot == 3) {
existingKey = pgpCard->keyFingerprint(OpenPGPCard::pgpAuthKeyRef());
}
if (!existingKey.empty()) {
const QString message = i18nc("@info",
"This card already contains a key in this slot. Continuing will overwrite that key.
"
"If there is no backup the existing key will be irrecoverably lost.
") +
i18n("The existing key has the fingerprint:") +
QStringLiteral("%1 ").arg(QString::fromStdString(existingKey)) +
encKeyWarning;
const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message,
i18nc("@title:window", "Overwrite existing key"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
if (choice != KMessageBox::Continue) {
finished();
return;
}
}
// Now do the deed
const auto time = QDateTime::fromSecsSinceEpoch(quint32(subkey.creationTime()), Qt::UTC);
const auto timestamp = time.toString(QStringLiteral("yyyyMMdd'T'HHmmss"));
const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 OPENPGP.%3 %4")
.arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber()))
.arg(slot)
.arg(timestamp);
ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, cmd.toUtf8(), q_func(), [this](const GpgME::Error &err) {
q->keyToOpenPGPCardDone(err);
});
}
namespace {
static std::vector getSigningCertificates()
{
std::vector signingCertificates = KeyCache::instance()->secretKeys();
const auto it = std::remove_if(signingCertificates.begin(), signingCertificates.end(),
[](const Key &key) {
return ! (key.protocol() == GpgME::CMS &&
!key.subkey(0).isNull() &&
key.subkey(0).canSign() &&
!key.subkey(0).canEncrypt() &&
key.subkey(0).isSecret() &&
!key.subkey(0).isCardKey());
});
signingCertificates.erase(it, signingCertificates.end());
return signingCertificates;
}
static std::vector getEncryptionCertificates()
{
std::vector encryptionCertificates = KeyCache::instance()->secretKeys();
const auto it = std::remove_if(encryptionCertificates.begin(), encryptionCertificates.end(),
[](const Key &key) {
return ! (key.protocol() == GpgME::CMS &&
!key.subkey(0).isNull() &&
key.subkey(0).canEncrypt() &&
key.subkey(0).isSecret() &&
!key.subkey(0).isCardKey());
});
encryptionCertificates.erase(it, encryptionCertificates.end());
return encryptionCertificates;
}
}
Subkey KeyToCardCommand::Private::getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr &/*card*/)
{
if (cardSlot != PIVCard::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) {
return Subkey();
}
const std::vector certificates = cardSlot == PIVCard::cardAuthenticationKeyRef() ? getSigningCertificates() : getEncryptionCertificates();
if (certificates.empty()) {
error(i18n("Sorry! No suitable certificate to write to this card slot was found."));
return Subkey();
}
auto dialog = new KeySelectionDialog(parentWidgetOrView());
dialog->setWindowTitle(i18nc("@title:window", "Select Certificate"));
dialog->setText(i18n("Please select the certificate whose key pair you want to write to the card:"));
dialog->setKeys(certificates);
if (dialog->exec() == QDialog::Rejected) {
return Subkey();
}
return dialog->selectedKey().subkey(0);
}
void KeyToCardCommand::Private::startKeyToPIVCard()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToPIVCard()";
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard(serialNumber());
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
if (cardSlot != PIVCard::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) {
// key to card is only supported for the Card Authentication key and the Key Management key
finished();
return;
}
if (subkey.isNull()) {
subkey = getSubkeyToTransferToPIVCard(cardSlot, pivCard);
}
if (subkey.isNull()) {
finished();
return;
}
if (subkey.parent().protocol() != GpgME::CMS) {
error(i18n("Sorry! This key cannot be transferred to a PIV card."));
finished();
return;
}
if (!subkey.canEncrypt() && !subkey.canSign()) {
error(i18n("Sorry! Only encryption keys and signing keys can be transferred to a PIV card."));
finished();
return;
}
// Check if we need to do the overwrite warning.
if (!overwriteExistingAlreadyApproved) {
const std::string existingKey = pivCard->keyInfo(cardSlot).grip;
if (!existingKey.empty() && (existingKey != subkey.keyGrip())) {
const QString decryptionWarning = (cardSlot == PIVCard::keyManagementKeyRef()) ?
i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.") :
QString();
const QString message = i18nc("@info",
"This card already contains a key in this slot. Continuing will overwrite that key.
"
"If there is no backup the existing key will be irrecoverably lost.
") +
i18n("The existing key has the key grip:") +
QStringLiteral("%1 ").arg(QString::fromStdString(existingKey)) +
decryptionWarning;
const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message,
i18nc("@title:window", "Overwrite existing key"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
if (choice != KMessageBox::Continue) {
finished();
return;
}
overwriteExistingAlreadyApproved = true;
}
}
const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3")
.arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber()))
.arg(QString::fromStdString(cardSlot));
ReaderStatus::mutableInstance()->startSimpleTransaction(pivCard, cmd.toUtf8(), q_func(), [this](const GpgME::Error &err) {
q->keyToPIVCardDone(err);
});
}
void KeyToCardCommand::Private::authenticate()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticate()";
auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView());
connect(cmd, &AuthenticatePIVCardApplicationCommand::finished,
q, [this]() { authenticationFinished(); });
connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled,
q, [this]() { authenticationCanceled(); });
cmd->start();
}
void KeyToCardCommand::Private::authenticationFinished()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationFinished()";
if (!hasBeenCanceled) {
startKeyToPIVCard();
}
}
void KeyToCardCommand::Private::authenticationCanceled()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationCanceled()";
hasBeenCanceled = true;
canceled();
}
KeyToCardCommand::KeyToCardCommand(const GpgME::Subkey &subkey)
: CardCommand(new Private(this, subkey))
{
}
KeyToCardCommand::KeyToCardCommand(const std::string& cardSlot, const std::string &serialNumber, const std::string &appName)
: CardCommand(new Private(this, cardSlot, serialNumber, appName))
{
}
KeyToCardCommand::~KeyToCardCommand()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::~KeyToCardCommand()";
}
// static
std::vector > KeyToCardCommand::getSuitableCards(const GpgME::Subkey &subkey)
{
std::vector > suitableCards;
if (subkey.isNull() || subkey.parent().protocol() != GpgME::OpenPGP) {
return suitableCards;
}
for (const auto &card: ReaderStatus::instance()->getCards()) {
if (card->appName() == OpenPGPCard::AppName) {
suitableCards.push_back(card);
}
}
return suitableCards;
}
void KeyToCardCommand::keyToOpenPGPCardDone(const GpgME::Error &err)
{
if (err) {
d->error(i18nc("@info",
"Moving the key to the card failed: %1", QString::fromUtf8(err.asString())));
} else if (!err.isCanceled()) {
/* TODO DELETE_KEY is too strong, because it also deletes the stub
* of the secret key. I could not find out how GnuPG does this. Question
* to GnuPG Developers is pending an answer
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (KMessageBox::questionTwoActions(d->parentWidgetOrView(),
-#else
- if (KMessageBox::questionYesNo(d->parentWidgetOrView(),
-
-#endif
i18n("Do you want to delete the key on this computer?"),
i18nc("@title:window",
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
"Key transferred to card")) == KMessageBox::ButtonCode::PrimaryAction) {
-#else
- "Key transferred to card")) == KMessageBox::Yes) {
-#endif
const QString cmd = QStringLiteral("DELETE_KEY --force %1").arg(d->subkey.keyGrip());
// Using readerstatus is a bit overkill but it's an easy way to talk to the agent.
ReaderStatus::mutableInstance()->startSimpleTransaction(card, cmd.toUtf8(), this, "deleteDone");
}
*/
d->success(i18nc("@info", "Successfully copied the key to the card."));
ReaderStatus::mutableInstance()->updateStatus();
}
d->finished();
}
void KeyToCardCommand::keyToPIVCardDone(const GpgME::Error &err)
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::keyToPIVCardDone():"
<< err.asString() << "(" << err.code() << ")";
if (err) {
#ifdef GPG_ERROR_HAS_NO_AUTH
// gpgme 1.13 reports "BAD PIN" instead of "NO AUTH"
if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) {
d->authenticate();
return;
}
#endif
d->error(i18nc("@info",
"Copying the key pair to the card failed: %1", QString::fromUtf8(err.asString())));
} else if (!err.isCanceled()) {
d->success(i18nc("@info", "Successfully copied the key pair to the card."));
ReaderStatus::mutableInstance()->updateStatus();
}
d->finished();
}
void KeyToCardCommand::deleteDone(const GpgME::Error &err)
{
if (err) {
d->error(i18nc("@info", "Failed to delete the key: %1", QString::fromUtf8(err.asString())));
}
d->finished();
}
void KeyToCardCommand::doStart()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::doStart()";
d->start();
}
void KeyToCardCommand::doCancel()
{
}
#undef q_func
#undef d_func
diff --git a/src/commands/revokecertificationcommand.cpp b/src/commands/revokecertificationcommand.cpp
index d85c985f1..8d58fad22 100644
--- a/src/commands/revokecertificationcommand.cpp
+++ b/src/commands/revokecertificationcommand.cpp
@@ -1,381 +1,360 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/revokecertificationcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "revokecertificationcommand.h"
#include "command_p.h"
#include "exportopenpgpcertstoservercommand.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
using namespace QGpgME;
namespace
{
enum class InputType {
Key,
UserIDs,
Certifications,
};
struct CertificationData {
UserID userId;
Key certificationKey;
};
static std::vector getCertificationKeys(const GpgME::UserID &userId)
{
std::vector keys;
if (userId.numSignatures() == 0) {
qCWarning(KLEOPATRA_LOG) << __func__ << "- Error: Signatures of user ID" << QString::fromUtf8(userId.id()) << "not available";
return keys;
}
std::vector revokableCertifications;
Kleo::copy_if(userId.signatures(), std::back_inserter(revokableCertifications), [](const auto &certification) {
return userCanRevokeCertification(certification) == CertificationCanBeRevoked;
});
Kleo::transform(revokableCertifications, std::back_inserter(keys), [](const auto &certification) {
return KeyCache::instance()->findByKeyIDOrFingerprint(certification.signerKeyID());
});
return keys;
}
static bool confirmRevocations(QWidget *parent, const std::vector &certifications)
{
KMessageBox::ButtonCode answer;
if (certifications.size() == 1) {
const auto [userId, certificationKey] = certifications.front();
const auto message = xi18nc("@info",
"You are about to revoke the certification of user ID %1 made with the key %2. ",
QString::fromUtf8(userId.id()),
Formatting::formatForComboBox(certificationKey));
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
answer = KMessageBox::questionTwoActions(parent,
-#else
- answer = KMessageBox::questionYesNo(parent,
-#endif
message,
i18nc("@title:window", "Confirm Revocation"),
KGuiItem{i18n("Revoke Certification")},
KStandardGuiItem::cancel());
} else {
QStringList l;
Kleo::transform(certifications, std::back_inserter(l), [](const auto &c) {
return i18n("User ID '%1' certified with key %2", QString::fromUtf8(c.userId.id()), Formatting::formatForComboBox(c.certificationKey));
});
const auto message = i18np("You are about to revoke the following certification:", //
"You are about to revoke the following %1 certifications:",
certifications.size());
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
answer = KMessageBox::questionTwoActionsList(parent,
-#else
- answer = KMessageBox::questionYesNoList(parent,
-#endif
message,
l,
i18nc("@title:window", "Confirm Revocation"),
KGuiItem{i18n("Revoke Certifications")},
KStandardGuiItem::cancel());
}
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
return answer == KMessageBox::ButtonCode::PrimaryAction;
-#else
- return answer == KMessageBox::Yes;
-#endif
}
}
class RevokeCertificationCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::RevokeCertificationCommand;
RevokeCertificationCommand *q_func() const
{
return static_cast(q);
}
public:
Private(InputType i, RevokeCertificationCommand *qq, KeyListController *c = nullptr);
void init();
private:
std::vector getCertificationsToRevoke();
void scheduleNextRevocation();
QGpgME::QuickJob *createJob();
void slotResult(const Error &err);
private:
InputType inputType = InputType::Key;
Key certificationTarget;
std::vector uids;
std::vector certificationsToRevoke;
std::vector completedRevocations;
QPointer job;
};
RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func()
{
return static_cast(d.get());
}
const RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func() const
{
return static_cast(d.get());
}
#define d d_func()
#define q q_func()
RevokeCertificationCommand::Private::Private(InputType i, RevokeCertificationCommand *qq, KeyListController *c)
: Command::Private{qq, c}
, inputType{i}
{
}
void RevokeCertificationCommand::Private::init()
{
const std::vector keys_ = keys();
if (keys_.size() != 1) {
qCWarning(KLEOPATRA_LOG) << q << "Expected exactly one key, but got" << keys_.size();
return;
}
if (keys_.front().protocol() != GpgME::OpenPGP) {
qCWarning(KLEOPATRA_LOG) << q << "Expected OpenPGP key, but got" << keys_.front().protocolAsString();
return;
}
certificationTarget = keys_.front();
}
std::vector RevokeCertificationCommand::Private::getCertificationsToRevoke()
{
if (inputType != InputType::Certifications) {
// ensure that the certifications of the key have been loaded
if (certificationTarget.userID(0).numSignatures() == 0) {
certificationTarget.update();
}
// build list of user IDs and revokable certifications
const auto userIDsToConsider = (inputType == InputType::Key) ? certificationTarget.userIDs() : uids;
for (const auto &userId : userIDsToConsider) {
Kleo::transform(getCertificationKeys(userId), std::back_inserter(certificationsToRevoke), [userId](const auto &k) {
return CertificationData{userId, k};
});
}
}
Kleo::erase_if(certificationsToRevoke, [](const auto &c) {
return c.certificationKey.isNull();
});
return certificationsToRevoke;
}
void RevokeCertificationCommand::Private::scheduleNextRevocation()
{
if (!certificationsToRevoke.empty()) {
const auto nextCertification = certificationsToRevoke.back();
job = createJob();
if (!job) {
qCWarning(KLEOPATRA_LOG) << q << "Failed to create job";
finished();
return;
}
job->startRevokeSignature(certificationTarget, nextCertification.certificationKey, {nextCertification.userId});
} else {
const auto message = xi18ncp("@info",
"The certification has been revoked successfully. "
"Do you want to publish the revocation? ",
"%1 certifications have been revoked successfully. "
"Do you want to publish the revocations? ",
completedRevocations.size());
const auto yesButton = KGuiItem{i18ncp("@action:button", "Publish Revocation", "Publish Revocations", completedRevocations.size()),
QIcon::fromTheme(QStringLiteral("view-certificate-export-server"))};
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
const auto answer = KMessageBox::questionTwoActions(parentWidgetOrView(),
-#else
- const auto answer = KMessageBox::questionYesNo(parentWidgetOrView(),
-#endif
message,
i18nc("@title:window", "Confirm Publication"),
yesButton,
KStandardGuiItem::cancel(),
{},
KMessageBox::Notify | KMessageBox::Dangerous);
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (answer == KMessageBox::ButtonCode::PrimaryAction) {
-#else
- if (answer == KMessageBox::Yes) {
-#endif
const auto cmd = new ExportOpenPGPCertsToServerCommand{certificationTarget};
cmd->start();
}
finished();
}
}
void RevokeCertificationCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled()) {
canceled();
return;
}
if (err) {
const auto failedRevocation = certificationsToRevoke.back();
error(xi18nc("@info",
"The revocation of the certification of user ID %1 made with key %2 failed: "
"%3 ",
Formatting::prettyNameAndEMail(failedRevocation.userId),
Formatting::formatForComboBox(failedRevocation.certificationKey),
QString::fromUtf8(err.asString())));
finished();
return;
}
completedRevocations.push_back(certificationsToRevoke.back());
certificationsToRevoke.pop_back();
scheduleNextRevocation();
}
QGpgME::QuickJob *RevokeCertificationCommand::Private::createJob()
{
const auto j = QGpgME::openpgp()->quickJob();
if (j) {
connect(j, &Job::progress, q, &Command::progress);
connect(j, &QGpgME::QuickJob::result, q, [this](const GpgME::Error &error) {
slotResult(error);
});
}
return j;
}
RevokeCertificationCommand::RevokeCertificationCommand(QAbstractItemView *v, KeyListController *c)
: Command{v, new Private{InputType::Key, this, c}}
{
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::Key &key)
: Command{key, new Private{InputType::Key, this}}
{
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID &uid)
: Command{uid.parent(), new Private{InputType::UserIDs, this}}
{
std::vector(1, uid).swap(d->uids);
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const std::vector &uids)
: Command{uids.empty() ? Key{} : uids.front().parent(), new Private{InputType::UserIDs, this}}
{
d->uids = uids;
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID::Signature &signature)
: Command{signature.parent().parent(), new Private{InputType::Certifications, this}}
{
if (!signature.isNull()) {
const Key certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID());
d->certificationsToRevoke = {{signature.parent(), certificationKey}};
}
d->init();
}
RevokeCertificationCommand::~RevokeCertificationCommand()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
}
// static
bool RevokeCertificationCommand::isSupported()
{
return engineInfo(GpgEngine).engineVersion() >= "2.2.24";
}
void RevokeCertificationCommand::doStart()
{
if (d->certificationTarget.isNull()) {
d->finished();
return;
}
if (!Kleo::all_of(d->uids, userIDBelongsToKey(d->certificationTarget))) {
qCWarning(KLEOPATRA_LOG) << this << "User ID <-> Key mismatch!";
d->finished();
return;
}
const auto certificationsToRevoke = d->getCertificationsToRevoke();
if (certificationsToRevoke.empty()) {
switch (d->inputType) {
case InputType::Key:
d->information(i18n("You cannot revoke any certifications of this key."));
break;
case InputType::UserIDs:
d->information(i18np("You cannot revoke any certifications of this user ID.", //
"You cannot revoke any certifications of these user IDs.",
d->uids.size()));
break;
case InputType::Certifications:
d->information(i18n("You cannot revoke this certification."));
break;
}
d->finished();
return;
}
if (!confirmRevocations(d->parentWidgetOrView(), certificationsToRevoke)) {
d->canceled();
return;
}
d->scheduleNextRevocation();
}
void RevokeCertificationCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
if (d->job) {
d->job->slotCancel();
}
}
#undef d
#undef q
#include "moc_revokecertificationcommand.cpp"
diff --git a/src/conf/cryptooperationsconfigwidget.cpp b/src/conf/cryptooperationsconfigwidget.cpp
index 90585abfe..49fa8d6fd 100644
--- a/src/conf/cryptooperationsconfigwidget.cpp
+++ b/src/conf/cryptooperationsconfigwidget.cpp
@@ -1,426 +1,416 @@
/*
cryptooperationsconfigwidget.cpp
This file is part of kleopatra, the KDE key manager
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "cryptooperationsconfigwidget.h"
#include "kleopatra_debug.h"
#include "emailoperationspreferences.h"
#include "fileoperationspreferences.h"
#include "settings.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include
#include
using namespace Kleo;
using namespace Kleo::Config;
CryptoOperationsConfigWidget::CryptoOperationsConfigWidget(QWidget *p, Qt::WindowFlags f)
: QWidget{p, f}
{
setupGui();
}
static void resetDefaults()
{
auto config = QGpgME::cryptoConfig();
if (!config) {
qCWarning(KLEOPATRA_LOG) << "Failed to obtain config";
return;
}
const QStringList componentList = config->componentList();
for (const auto &compName: componentList) {
auto comp = config->component(compName);
if (!comp) {
qCWarning(KLEOPATRA_LOG) << "Failed to find component:" << comp;
return;
}
const QStringList groupList = comp->groupList();
for (const auto &grpName: groupList) {
auto grp = comp->group(grpName);
if (!grp) {
qCWarning(KLEOPATRA_LOG) << "Failed to find group:" << grp << "in component:" << compName;
return;
}
const QStringList entries = grp->entryList();
for (const auto &entryName: entries) {
auto entry = grp->entry(entryName);
if (!entry) {
qCWarning(KLEOPATRA_LOG) << "Failed to find entry:" << entry << "in group:"<< grp << "in component:" << compName;
return;
}
entry->resetToDefault();
}
}
}
config->sync(true);
return;
}
void CryptoOperationsConfigWidget::applyProfile(const QString &profile)
{
if (profile.isEmpty()) {
return;
}
qCDebug(KLEOPATRA_LOG) << "Applying profile " << profile;
if (profile == i18n("default")) {
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (KMessageBox::warningTwoActions(
-#else
- if (KMessageBox::warningYesNo(
-
-#endif
this,
i18n("This means that every configuration option of the GnuPG System will be reset to its default."),
i18n("Apply profile"),
KStandardGuiItem::apply(),
KStandardGuiItem::cancel())
- #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
!= KMessageBox::ButtonCode::PrimaryAction) {
-#else
- != KMessageBox::Yes) {
- #endif
return;
}
resetDefaults();
KeyFilterManager::instance()->reload();
return;
}
mApplyBtn->setEnabled(false);
QDir datadir(QString::fromUtf8(GpgME::dirInfo("datadir")) + QStringLiteral("/../doc/gnupg/examples"));
const auto path = datadir.filePath(profile + QStringLiteral(".prf"));
auto gpgconf = new QProcess;
const auto ei = GpgME::engineInfo(GpgME::GpgConfEngine);
Q_ASSERT (ei.fileName());
gpgconf->setProgram(QFile::decodeName(ei.fileName()));
gpgconf->setProcessChannelMode(QProcess::MergedChannels);
gpgconf->setArguments(QStringList() << QStringLiteral("--runtime")
<< QStringLiteral("--apply-profile")
<< path);
qDebug() << "Starting" << ei.fileName() << "with args" << gpgconf->arguments();
#if QT_DEPRECATED_SINCE(5, 13)
connect(gpgconf, qOverload(&QProcess::finished),
#else
connect(gpgconf, &QProcess::finished,
#endif
this, [this, gpgconf, profile] () {
mApplyBtn->setEnabled(true);
if (gpgconf->exitStatus() != QProcess::NormalExit) {
KMessageBox::error(this, QStringLiteral("%1 ").arg(QString::fromUtf8(gpgconf->readAll())));
delete gpgconf;
return;
}
delete gpgconf;
KMessageBox::information(this,
i18nc("%1 is the name of the profile",
"The configuration profile \"%1\" was applied.", profile),
i18n("GnuPG Profile - Kleopatra"));
auto config = QGpgME::cryptoConfig();
if (config) {
config->clear();
}
KeyFilterManager::instance()->reload();
});
gpgconf->start();
}
// Get a list of available profile files and add a configuration
// group if there are any.
void CryptoOperationsConfigWidget::setupProfileGui(QBoxLayout *layout)
{
const Settings settings;
if (settings.profilesDisabled() || GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.20" || !layout) {
// Profile support is new in 2.1.20
qCDebug(KLEOPATRA_LOG) << "Profile settings disabled";
return;
}
QDir datadir(QString::fromUtf8(GpgME::dirInfo("datadir")) + QStringLiteral("/../doc/gnupg/examples"));
if (!datadir.exists()) {
qCDebug(KLEOPATRA_LOG) << "Failed to find gnupg's example profile directory" << datadir.path();
return;
}
const auto profiles = datadir.entryInfoList(QStringList() << QStringLiteral("*.prf"), QDir::Readable | QDir::Files, QDir::Name);
if (profiles.isEmpty()) {
qCDebug(KLEOPATRA_LOG) << "Failed to find any profiles in: " << datadir.path();
return;
}
auto genGrp = new QGroupBox(i18nc("@title", "General Operations"));
auto profLayout = new QHBoxLayout;
genGrp->setLayout(profLayout);
layout->addWidget(genGrp);
auto profLabel = new QLabel(i18n("Activate GnuPG Profile:"));
profLabel->setToolTip(i18n("A profile consists of various settings that can apply to multiple components of the GnuPG system."));
auto combo = new QComboBox;
profLabel->setBuddy(combo);
// Add an empty Item to avoid the impression that this GUI element
// shows the currently selected profile.
combo->addItem(QString());
// We don't translate "default" here because the other profile names are
// also not translated as they are taken directly from file.
combo->addItem(i18n("default"));
for (const auto &profile: profiles) {
combo->addItem(profile.baseName());
}
mApplyBtn = new QPushButton(i18n("Apply"));
mApplyBtn->setEnabled(false);
profLayout->addWidget(profLabel);
profLayout->addWidget(combo);
profLayout->addWidget(mApplyBtn);
profLayout->addStretch(1);
connect(mApplyBtn, &QPushButton::clicked, this, [this, combo] () {
applyProfile(combo->currentText());
});
connect(combo, &QComboBox::currentTextChanged, this, [this] (const QString &text) {
mApplyBtn->setEnabled(!text.isEmpty());
});
}
void CryptoOperationsConfigWidget::setupGui()
{
auto baseLay = new QVBoxLayout(this);
baseLay->setContentsMargins(0, 0, 0, 0);
auto mailGrp = new QGroupBox(i18n("EMail Operations"));
auto mailGrpLayout = new QVBoxLayout;
mQuickSignCB = new QCheckBox(i18n("Don't confirm signing certificate if there is only one valid certificate for the identity"));
mQuickEncryptCB = new QCheckBox(i18n("Don't confirm encryption certificates if there is exactly one valid certificate for each recipient"));
mailGrpLayout->addWidget(mQuickSignCB);
mailGrpLayout->addWidget(mQuickEncryptCB);
mailGrp->setLayout(mailGrpLayout);
baseLay->addWidget(mailGrp);
auto fileGrp = new QGroupBox(i18n("File Operations"));
auto fileGrpLay = new QVBoxLayout;
mPGPFileExtCB = new QCheckBox(i18n(R"(Create OpenPGP encrypted files with ".pgp" file extensions instead of ".gpg")"));
mASCIIArmorCB = new QCheckBox(i18n("Create signed or encrypted files as text files."));
mASCIIArmorCB->setToolTip(i18nc("@info", "Set this option to encode encrypted or signed files as base64 encoded text. "
"So that they can be opened with an editor or sent in a mail body. "
"This will increase file size by one third."));
mAutoDecryptVerifyCB = new QCheckBox(i18n("Automatically start operation based on input detection for decrypt/verify."));
mAutoExtractArchivesCB = new QCheckBox(i18n("Automatically extract file archives after decryption"));
mTmpDirCB = new QCheckBox(i18n("Create temporary decrypted files in the folder of the encrypted file."));
mTmpDirCB->setToolTip(i18nc("@info", "Set this option to avoid using the users temporary directory."));
mSymmetricOnlyCB = new QCheckBox(i18n("Use symmetric encryption only."));
mSymmetricOnlyCB->setToolTip(i18nc("@info", "Set this option to disable public key encryption."));
fileGrpLay->addWidget(mPGPFileExtCB);
fileGrpLay->addWidget(mAutoDecryptVerifyCB);
fileGrpLay->addWidget(mAutoExtractArchivesCB);
fileGrpLay->addWidget(mASCIIArmorCB);
fileGrpLay->addWidget(mTmpDirCB);
fileGrpLay->addWidget(mSymmetricOnlyCB);
auto comboLay = new QGridLayout;
mChecksumDefinitionCB.createWidgets(this);
mChecksumDefinitionCB.label()->setText(i18n("Checksum program to use when creating checksum files:"));
comboLay->addWidget(mChecksumDefinitionCB.label(), 0, 0);
comboLay->addWidget(mChecksumDefinitionCB.widget(), 0, 1);
mArchiveDefinitionCB.createWidgets(this);
mArchiveDefinitionCB.label()->setText(i18n("Archive command to use when archiving files:"));
comboLay->addWidget(mArchiveDefinitionCB.label(), 1, 0);
comboLay->addWidget(mArchiveDefinitionCB.widget(), 1, 1);
fileGrpLay->addLayout(comboLay);
fileGrp->setLayout(fileGrpLay);
baseLay->addWidget(fileGrp);
setupProfileGui(baseLay);
baseLay->addStretch(1);
for (auto cb : findChildren()) {
connect(cb, &QCheckBox::toggled, this, &CryptoOperationsConfigWidget::changed);
}
for (auto combo : findChildren()) {
connect(combo, qOverload(&QComboBox::currentIndexChanged), this, &CryptoOperationsConfigWidget::changed);
}
}
CryptoOperationsConfigWidget::~CryptoOperationsConfigWidget() {}
void CryptoOperationsConfigWidget::defaults()
{
EMailOperationsPreferences emailPrefs;
emailPrefs.setQuickSignEMail(emailPrefs.findItem(QStringLiteral("QuickSignEMail"))->getDefault().toBool());
emailPrefs.setQuickEncryptEMail(emailPrefs.findItem(QStringLiteral("QuickEncryptEMail"))->getDefault().toBool());
FileOperationsPreferences filePrefs;
filePrefs.setUsePGPFileExt(filePrefs.findItem(QStringLiteral("UsePGPFileExt"))->getDefault().toBool());
filePrefs.setAutoDecryptVerify(filePrefs.findItem(QStringLiteral("AutoDecryptVerify"))->getDefault().toBool());
filePrefs.setAutoExtractArchives(filePrefs.findItem(QStringLiteral("AutoExtractArchives"))->getDefault().toBool());
filePrefs.setAddASCIIArmor(filePrefs.findItem(QStringLiteral("AddASCIIArmor"))->getDefault().toBool());
filePrefs.setDontUseTmpDir(filePrefs.findItem(QStringLiteral("DontUseTmpDir"))->getDefault().toBool());
filePrefs.setSymmetricEncryptionOnly(filePrefs.findItem(QStringLiteral("SymmetricEncryptionOnly"))->getDefault().toBool());
filePrefs.setArchiveCommand(filePrefs.findItem(QStringLiteral("ArchiveCommand"))->getDefault().toString());
Settings settings;
settings.setChecksumDefinitionId(settings.findItem(QStringLiteral("ChecksumDefinitionId"))->getDefault().toString());
load(emailPrefs, filePrefs, settings);
}
void CryptoOperationsConfigWidget::load(const Kleo::EMailOperationsPreferences &emailPrefs,
const Kleo::FileOperationsPreferences &filePrefs,
const Kleo::Settings &settings)
{
mQuickSignCB->setChecked(emailPrefs.quickSignEMail());
mQuickSignCB->setEnabled(!emailPrefs.isImmutable(QStringLiteral("QuickSignEMail")));
mQuickEncryptCB->setChecked(emailPrefs.quickEncryptEMail());
mQuickEncryptCB->setEnabled(!emailPrefs.isImmutable(QStringLiteral("QuickEncryptEMail")));
mPGPFileExtCB->setChecked(filePrefs.usePGPFileExt());
mPGPFileExtCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("UsePGPFileExt")));
mAutoDecryptVerifyCB->setChecked(filePrefs.autoDecryptVerify());
mAutoDecryptVerifyCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("AutoDecryptVerify")));
mAutoExtractArchivesCB->setChecked(filePrefs.autoExtractArchives());
mAutoExtractArchivesCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("AutoExtractArchives")));
mASCIIArmorCB->setChecked(filePrefs.addASCIIArmor());
mASCIIArmorCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("AddASCIIArmor")));
mTmpDirCB->setChecked(filePrefs.dontUseTmpDir());
mTmpDirCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("DontUseTmpDir")));
mSymmetricOnlyCB->setChecked(filePrefs.symmetricEncryptionOnly());
mSymmetricOnlyCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("SymmetricEncryptionOnly")));
const auto defaultChecksumDefinitionId = settings.checksumDefinitionId();
{
const auto index = mChecksumDefinitionCB.widget()->findData(defaultChecksumDefinitionId);
if (index >= 0) {
mChecksumDefinitionCB.widget()->setCurrentIndex(index);
} else {
qCWarning(KLEOPATRA_LOG) << "No checksum definition found with id" << defaultChecksumDefinitionId;
}
}
mChecksumDefinitionCB.setEnabled(!settings.isImmutable(QStringLiteral("ChecksumDefinitionId")));
const auto ad_default_id = filePrefs.archiveCommand();
{
const auto index = mArchiveDefinitionCB.widget()->findData(ad_default_id);
if (index >= 0) {
mArchiveDefinitionCB.widget()->setCurrentIndex(index);
} else {
qCWarning(KLEOPATRA_LOG) << "No archive definition found with id" << ad_default_id;
}
}
mArchiveDefinitionCB.setEnabled(!filePrefs.isImmutable(QStringLiteral("ArchiveCommand")));
}
void CryptoOperationsConfigWidget::load()
{
mChecksumDefinitionCB.widget()->clear();
const auto cds = ChecksumDefinition::getChecksumDefinitions();
for (const std::shared_ptr &cd : cds) {
mChecksumDefinitionCB.widget()->addItem(cd->label(), QVariant{cd->id()});
}
// This is a weird hack but because we are a KCM we can't link
// against ArchiveDefinition which pulls in loads of other classes.
// So we do the parsing which archive definitions exist here ourself.
mArchiveDefinitionCB.widget()->clear();
if (KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"))) {
const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Archive Definition #")));
for (const QString &group : groups) {
const KConfigGroup cGroup(config, group);
const QString id = cGroup.readEntryUntranslated(QStringLiteral("id"));
const QString name = cGroup.readEntry("Name");
mArchiveDefinitionCB.widget()->addItem(name, QVariant(id));
}
}
load(EMailOperationsPreferences{}, FileOperationsPreferences{}, Settings{});
}
void CryptoOperationsConfigWidget::save()
{
EMailOperationsPreferences emailPrefs;
emailPrefs.setQuickSignEMail(mQuickSignCB ->isChecked());
emailPrefs.setQuickEncryptEMail(mQuickEncryptCB->isChecked());
emailPrefs.save();
FileOperationsPreferences filePrefs;
filePrefs.setUsePGPFileExt(mPGPFileExtCB->isChecked());
filePrefs.setAutoDecryptVerify(mAutoDecryptVerifyCB->isChecked());
filePrefs.setAutoExtractArchives(mAutoExtractArchivesCB->isChecked());
filePrefs.setAddASCIIArmor(mASCIIArmorCB->isChecked());
filePrefs.setDontUseTmpDir(mTmpDirCB->isChecked());
filePrefs.setSymmetricEncryptionOnly(mSymmetricOnlyCB->isChecked());
Settings settings;
const int idx = mChecksumDefinitionCB.widget()->currentIndex();
if (idx >= 0) {
const auto id = mChecksumDefinitionCB.widget()->itemData(idx).toString();
settings.setChecksumDefinitionId(id);
}
settings.save();
const int aidx = mArchiveDefinitionCB.widget()->currentIndex();
if (aidx >= 0) {
const QString id = mArchiveDefinitionCB.widget()->itemData(aidx).toString();
filePrefs.setArchiveCommand(id);
}
filePrefs.save();
}
diff --git a/src/conf/groupsconfigpage.cpp b/src/conf/groupsconfigpage.cpp
index 00f870ed0..46146391e 100644
--- a/src/conf/groupsconfigpage.cpp
+++ b/src/conf/groupsconfigpage.cpp
@@ -1,149 +1,140 @@
/*
conf/groupsconfigpage.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "groupsconfigpage.h"
#include "groupsconfigwidget.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "kleopatra_debug.h"
-#include
using namespace Kleo;
class GroupsConfigPage::Private
{
friend class ::GroupsConfigPage;
GroupsConfigPage *const q;
Private(GroupsConfigPage *qq);
public:
~Private() = default;
void setChanged(bool changed);
void onKeysMayHaveChanged();
private:
GroupsConfigWidget *widget = nullptr;
bool changed = false;
bool savingChanges = false;
};
GroupsConfigPage::Private::Private(GroupsConfigPage *qq)
: q(qq)
{
}
void GroupsConfigPage::Private::setChanged(bool state)
{
changed = state;
Q_EMIT q->changed(changed);
}
void GroupsConfigPage::Private::onKeysMayHaveChanged()
{
static QMutex mutex;
const UniqueLock lock{mutex, Kleo::tryToLock};
if (!lock) {
// prevent reentrace
return;
}
if (savingChanges) {
qCDebug(KLEOPATRA_LOG) << __func__ << "ignoring changes caused by ourselves";
return;
}
if (!changed) {
q->load();
} else {
auto buttonYes = KStandardGuiItem::ok();
buttonYes.setText(i18n("Save changes"));
auto buttonNo = KStandardGuiItem::cancel();
buttonNo.setText(i18n("Discard changes"));
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
const auto answer = KMessageBox::questionTwoActions(
-#else
- const auto answer = KMessageBox::questionYesNo(
-#endif
q->topLevelWidget(),
xi18nc("@info",
"The certificates or the certificate groups have been updated in the background. "
"Do you want to save your changes? "),
i18nc("@title::window", "Save changes?"),
buttonYes,
buttonNo);
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (answer == KMessageBox::ButtonCode::PrimaryAction) {
-#else
- if (answer == KMessageBox::Yes) {
-#endif
q->save();
} else {
q->load();
}
}
}
GroupsConfigPage::GroupsConfigPage(QWidget *parent)
: QWidget(parent)
, d(new Private(this))
{
auto layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
d->widget = new Kleo::GroupsConfigWidget(this);
if (QLayout *l = d->widget->layout()) {
l->setContentsMargins(0, 0, 0, 0);
}
layout->addWidget(d->widget);
connect(d->widget, &GroupsConfigWidget::changed, this, [this] () { d->setChanged(true); });
connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged,
this, [this]() { d->onKeysMayHaveChanged(); });
}
GroupsConfigPage::~GroupsConfigPage() = default;
bool GroupsConfigPage::hasChanged() const
{
return d->changed;
}
void GroupsConfigPage::load()
{
d->widget->setGroups(KeyCache::instance()->configurableGroups());
d->setChanged(false);
}
void GroupsConfigPage::save()
{
d->savingChanges = true;
KeyCache::mutableInstance()->saveConfigurableGroups(d->widget->groups());
d->savingChanges = false;
// reload after saving to ensure that the groups reflect the saved groups (e.g. in case of immutable entries)
load();
}
diff --git a/src/conf/kleopageconfigdialog.cpp b/src/conf/kleopageconfigdialog.cpp
index 8b915f2c9..e2b03913c 100644
--- a/src/conf/kleopageconfigdialog.cpp
+++ b/src/conf/kleopageconfigdialog.cpp
@@ -1,263 +1,250 @@
/*
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
SPDX-FileCopyrightText: 2003 Daniel Molkentin
SPDX-FileCopyrightText: 2003, 2006 Matthias Kretz
SPDX-FileCopyrightText: 2004 Frans Englich
SPDX-FileCopyrightText: 2006 Tobias Koenig
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include
#include "kleopageconfigdialog.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kleopatra_debug.h"
-#include
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(previous->widget());
bool canceled = false;
if (previousModule && mChangedModules.contains(previousModule)) {
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
const int queryUser = KMessageBox::warningTwoActionsCancel(
-#else
- const int queryUser = KMessageBox::warningYesNoCancel(
-#endif
this,
i18n("The settings of the current module have changed.\n"
"Do you want to apply the changes or discard them?"),
i18n("Apply Settings"),
KStandardGuiItem::apply(),
KStandardGuiItem::discard(),
KStandardGuiItem::cancel());
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (queryUser == KMessageBox::ButtonCode::PrimaryAction) {
-#else
- if (queryUser == KMessageBox::Yes) {
-#endif
previousModule->save();
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
} else if (queryUser == KMessageBox::ButtonCode::SecondaryAction) {
-#else
- } else if (queryUser == KMessageBox::No) {
-#endif
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(item->widget());
if (!module) {
return;
}
module->defaults();
clientChanged();
}
void KleoPageConfigDialog::slotUser1Clicked()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
KCModule *module = qobject_cast(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")) {
const QString exec =
QStandardPaths::findExecutable(QStringLiteral("khelpcenter"));
if (exec.isEmpty()) {
qCWarning(KLEOPATRA_LOG) << "Could not find khelpcenter in PATH.";
} else {
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, name);
item->setIcon(QIcon::fromTheme(icon));
connect(module, SIGNAL(changed(bool)), this, SLOT(moduleChanged(bool)));
mHelpUrls.insert(name, docPath);
}
void KleoPageConfigDialog::moduleChanged(bool state)
{
KCModule *module = qobject_cast(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(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);
}
}
diff --git a/src/crypto/autodecryptverifyfilescontroller.cpp b/src/crypto/autodecryptverifyfilescontroller.cpp
index dedb3c721..9fe80dc69 100644
--- a/src/crypto/autodecryptverifyfilescontroller.cpp
+++ b/src/crypto/autodecryptverifyfilescontroller.cpp
@@ -1,581 +1,562 @@
/* -*- 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
#include "autodecryptverifyfilescontroller.h"
#include "fileoperationspreferences.h"
#include
#include
#include
#include
#include "commands/decryptverifyfilescommand.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kleopatra_debug.h"
-#include
#include
#include
#include
#include
#include
#include
#include
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 slotDialogCanceled();
void schedule();
QString getEmbeddedFileName(const QString &fileName) const;
void exec();
std::vector > buildTasks(const QStringList &, QStringList &);
struct CryptoFile {
QString baseName;
QString fileName;
GpgME::Protocol protocol = GpgME::UnknownProtocol;
int classification = 0;
std::shared_ptr output;
};
QVector 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 > m_results;
std::vector > m_runnableTasks, m_completedTasks;
std::shared_ptr m_runningTask;
bool m_errorDetected = false;
DecryptVerifyOperation m_operation = DecryptVerify;
DecryptVerifyFilesDialog *m_dialog = nullptr;
std::unique_ptr m_workDir;
};
AutoDecryptVerifyFilesController::Private::Private(AutoDecryptVerifyFilesController *qq) : q(qq)
{
qRegisterMetaType();
}
void AutoDecryptVerifyFilesController::Private::slotDialogCanceled()
{
qCDebug(KLEOPATRA_LOG);
}
void AutoDecryptVerifyFilesController::Private::schedule()
{
if (!m_runningTask && !m_runnableTasks.empty()) {
const std::shared_ptr 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 &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 > 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. "
"You can manually select what to do with the files now. "
"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 coll(new TaskCollection);
for (const std::shared_ptr &i : std::as_const(m_runnableTasks)) {
q->connectTask(i);
}
coll->setTasks(m_runnableTasks);
m_dialog = new DecryptVerifyFilesDialog(coll);
m_dialog->setOutputLocation(heuristicBaseDirectory(m_passedFiles));
QTimer::singleShot(0, q, SLOT(schedule()));
if (m_dialog->exec() == 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.baseName();
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);
if (!moveDir(inpath, ofi.absoluteFilePath())) {
reportError(makeGnuPGError(GPG_ERR_GENERAL),
xi18n("Failed to move %1 to %2 .",
inpath, ofi.absoluteFilePath()));
}
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
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
const auto answer = KMessageBox::questionTwoActionsCancel(
m_dialog,
-#else
- const auto answer =
- KMessageBox::questionYesNoCancel(m_dialog,
-#endif
xi18n("Shall the file be saved with the original file name %1 ?", embeddedFileName),
i18n("Use Original File Name?"),
KGuiItem(xi18n("No, Save As %1 ", fi.fileName())),
KGuiItem(xi18n("Yes, Save As %1 ", embeddedFileName)));
if (answer == KMessageBox::Cancel) {
qCDebug(KLEOPATRA_LOG) << "Saving canceled for:" << inpath;
continue;
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
} else if (answer == KMessageBox::ButtonCode::SecondaryAction) {
-#else
- } else if (answer == KMessageBox::No) {
-#endif
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) {
-#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
sel = KMessageBox::questionTwoActionsCancel(m_dialog,
i18n("The file %1 already exists.\n"
-#else
- sel = KMessageBox::questionYesNoCancel(m_dialog,
- i18n("The file %1 already exists.\n"
-#endif
"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 KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (sel == KMessageBox::ButtonCode::SecondaryAction) { // Overwrite All
-#else
- if (sel == KMessageBox::No) { //Overwrite All
-#endif
overWriteAll = true;
}
if (!QFile::remove(outpath)) {
reportError(makeGnuPGError(GPG_ERR_GENERAL),
xi18n("Failed to delete %1 .",
outpath));
continue;
}
}
if (!QFile::rename(inpath, outpath)) {
reportError(makeGnuPGError(GPG_ERR_GENERAL),
xi18n("Failed to move %1 to %2 .",
inpath, outpath));
}
}
}
q->emitDoneOrError();
delete m_dialog;
m_dialog = nullptr;
}
QVector AutoDecryptVerifyFilesController::Private::classifyAndSortFiles(const QStringList &files)
{
const auto isSignature = [](int classification) -> bool {
return mayBeDetachedSignature(classification)
|| mayBeOpaqueSignature(classification)
|| (classification & Class::TypeMask) == Class::ClearsignedMessage;
};
QVector 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;
}
std::vector< std::shared_ptr > AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected)
{
// sort files so that we make sure we first decrypt and then verify
QVector cryptoFiles = classifyAndSortFiles(fileNames);
std::vector > 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 %1 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 %1 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;
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 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 %1 ", 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 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 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 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(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();
}
qCDebug(KLEOPATRA_LOG) << "Using:" << m_workDir->path() << "as temporary directory.";
const auto wd = QDir(m_workDir->path());
const auto output = ad ? ad->createOutputFromUnpackCommand(cFile.protocol, cFile.fileName, wd)
: 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 t(new VerifyOpaqueTask);
t->setInput(input);
t->setOutput(output);
t->setProtocol(cFile.protocol);
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 t(new DecryptVerifyTask);
t->setInput(input);
t->setOutput(output);
t->setProtocol(cFile.protocol);
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 &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);
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 &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 &dvr = std::dynamic_pointer_cast(result)) {
d->m_results.push_back(dvr);
}
QTimer::singleShot(0, this, SLOT(schedule()));
}
#include "moc_autodecryptverifyfilescontroller.cpp"
diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp
index 0e91a73b5..84ce0822a 100644
--- a/src/dialogs/certificatedetailswidget.cpp
+++ b/src/dialogs/certificatedetailswidget.cpp
@@ -1,1159 +1,1150 @@
/*
dialogs/certificatedetailswidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2017 Intevation GmbH
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-FileCopyrightText: 2022 Felix Tiede
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "certificatedetailswidget.h"
#include "kleopatra_debug.h"
#include "exportdialog.h"
#include "trustchainwidget.h"
#include "subkeyswidget.h"
#include "weboftrustdialog.h"
#include "commands/changepassphrasecommand.h"
#include "commands/changeexpirycommand.h"
#include "commands/certifycertificatecommand.h"
#ifdef MAILAKONADI_ENABLED
#include "commands/exportopenpgpcerttoprovidercommand.h"
#endif // MAILAKONADI_ENABLED
#include "commands/refreshcertificatecommand.h"
#include "commands/revokecertificationcommand.h"
#include "commands/revokeuseridcommand.h"
#include "commands/setprimaryuseridcommand.h"
#include "commands/adduseridcommand.h"
#include "commands/genrevokecommand.h"
#include "commands/detailscommand.h"
#include "commands/dumpcertificatecommand.h"
#include "utils/accessibility.h"
#include "utils/keys.h"
#include "utils/tags.h"
#include "view/infofield.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if __has_include()
#include
#define USE_RANGES
#endif
#include
Q_DECLARE_METATYPE(GpgME::UserID)
using namespace Kleo;
namespace
{
std::vector selectedUserIDs(const QTreeWidget *treeWidget) {
if (!treeWidget) {
return {};
}
std::vector userIDs;
const auto selected = treeWidget->selectedItems();
std::transform(selected.begin(), selected.end(), std::back_inserter(userIDs), [](const QTreeWidgetItem *item) {
return item->data(0, Qt::UserRole).value();
});
return userIDs;
}
}
class CertificateDetailsWidget::Private
{
public:
Private(CertificateDetailsWidget *qq);
void setupCommonProperties();
void updateUserIDActions();
void setUpUserIDTable();
void setUpSMIMEAdressList();
void setupPGPProperties();
void setupSMIMEProperties();
void revokeUserID(const GpgME::UserID &uid);
void revokeSelectedUserID();
void genRevokeCert();
void refreshCertificate();
void certifyUserIDs();
void revokeCertifications();
void webOfTrustClicked();
void exportClicked();
void addUserID();
void setPrimaryUserID(const GpgME::UserID &uid = {});
void changePassphrase();
void changeExpiration();
void keysMayHaveChanged();
void showTrustChainDialog();
void showMoreDetails();
void userIDTableContextMenuRequested(const QPoint &p);
QString tofuTooltipString(const GpgME::UserID &uid) const;
QIcon trustLevelIcon(const GpgME::UserID &uid) const;
QString trustLevelText(const GpgME::UserID &uid) const;
void showIssuerCertificate();
void updateKey();
void setUpdatedKey(const GpgME::Key &key);
void keyListDone(const GpgME::KeyListResult &,
const std::vector &, const QString &,
const GpgME::Error &);
void copyFingerprintToClipboard();
private:
CertificateDetailsWidget *const q;
public:
GpgME::Key key;
bool updateInProgress = false;
private:
InfoField *attributeField(const QString &attributeName)
{
const auto keyValuePairIt = ui.smimeAttributeFields.find(attributeName);
if (keyValuePairIt != ui.smimeAttributeFields.end()) {
return (*keyValuePairIt).second.get();
}
return nullptr;
}
private:
struct UI {
QWidget *userIDs = nullptr;
QLabel *userIDTableLabel = nullptr;
NavigatableTreeWidget *userIDTable = nullptr;
QPushButton *addUserIDBtn = nullptr;
QPushButton *setPrimaryUserIDBtn = nullptr;
QPushButton *certifyBtn = nullptr;
QPushButton *revokeCertificationsBtn = nullptr;
QPushButton *revokeUserIDBtn = nullptr;
QPushButton *webOfTrustBtn = nullptr;
std::map> smimeAttributeFields;
std::unique_ptr smimeTrustLevelField;
std::unique_ptr validFromField;
std::unique_ptr expiresField;
QAction *changeExpirationAction = nullptr;
std::unique_ptr