diff --git a/src/commands/importcertificatescommand.cpp b/src/commands/importcertificatescommand.cpp index 2c89ff918..c1fb45cc0 100644 --- a/src/commands/importcertificatescommand.cpp +++ b/src/commands/importcertificatescommand.cpp @@ -1,1090 +1,1089 @@ /* -*- 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 "kleopatra_debug.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 // for Qt::escape #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace QGpgME; static void disconnectConnection(const QMetaObject::Connection &connection) { // trivial function for disconnecting a signal-slot connection QObject::disconnect(connection); } 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, 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(QLatin1StringView("
")); } 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(QLatin1StringView{" "}).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(QLatin1StringView{}); } 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{QLatin1StringView{""}}; 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 += QLatin1StringView{"

"} + title + QLatin1String{"

"}; report += QLatin1StringView{"

"}; report += make_report(res, groups); report += QLatin1StringView{"

"}; } report += QLatin1StringView{""}; return report; } // Returns false on error, true if please certify was shown. bool ImportCertificatesCommand::Private::showPleaseCertify(const GpgME::Import &imp) { if (!Kleo::userHasCertificationKey()) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "No certification key available"; return false; } 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 = wrap_unique(GpgME::Context::createForProtocol(GpgME::OpenPGP)); if (!ctx) { // WTF qCWarning(KLEOPATRA_LOG) << "Failed to create OpenPGP proto"; return false; } ctx->addKeyListMode(KeyListMode::WithSecret); GpgME::Error err; const auto key = ctx->key(fpr, err, false); if (key.isNull() || err) { // No such key most likely not OpenPGP return false; } if (!Kleo::canBeCertified(key)) { // key is expired or revoked return false; } if (key.hasSecret()) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "Secret key is available -> skipping certification"; 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 = { i18n("A phone call to the person."), i18n("Using a business card."), i18n("Confirming it on a trusted website."), }; auto sel = KMessageBox::questionTwoActions(parentWidgetOrView(), i18n("In order to mark the certificate as valid 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("
      "))) + QStringLiteral("
  • ") + i18n("Do you wish to start this process now?"), i18nc("@title", "You have imported a new certificate (public key)"), KGuiItem(i18nc("@action:button", "Certify")), KStandardGuiItem::cancel(), QStringLiteral("CertifyQuestion")); if (sel == KMessageBox::ButtonCode::PrimaryAction) { 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; } auto consolidatedAuditLogEntries(const std::vector &res) { static const QString gpg = QStringLiteral("gpg"); static const QString gpgsm = QStringLiteral("gpgsm"); if (res.size() == 1) { return res.front().auditLog; } QStringList auditLogs; auto extractAndAnnotateAuditLog = [](const ImportResultData &r) { QString s; if (!r.id.isEmpty()) { const auto program = r.protocol == GpgME::OpenPGP ? gpg : gpgsm; const auto headerLine = i18nc("file name (imported with gpg/gpgsm)", "%1 (imported with %2)").arg(r.id, program); s += QStringLiteral("
    %1
    ").arg(headerLine); } if (r.auditLog.error().code() == GPG_ERR_NO_DATA) { s += QStringLiteral("") + i18nc("@info", "Audit log is empty.") + QStringLiteral(""); } else if (r.result.error().isCanceled()) { s += QStringLiteral("") + i18nc("@info", "Import was canceled.") + QStringLiteral(""); } else { s += r.auditLog.text(); } return s; }; std::transform(res.cbegin(), res.cend(), std::back_inserter(auditLogs), extractAndAnnotateAuditLog); return AuditLogEntry{auditLogs.join(QLatin1StringView{"
    "}), Error{}}; } } 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); MessageBox::information(parentWidgetOrView(), make_message_report(res, groups), consolidatedAuditLogEntries(res), i18n("Certificate Import Result")); } static QString make_error_message(const Error &err, const QString &id) { Q_ASSERT(err); Q_ASSERT(!err.isCanceled()); if (id.isEmpty()) { return i18n( "

    An error occurred while trying to import the certificate:

    " "

    %1

    ", Formatting::errorAsString(err)); } else { return i18n( "

    An error occurred while trying to import the certificate %1:

    " "

    %2

    ", id, Formatting::errorAsString(err)); } } void ImportCertificatesCommand::Private::showError(const ImportResultData &result) { MessageBox::error(parentWidgetOrView(), make_error_message(result.result.error(), result.id), result.auditLog); } void ImportCertificatesCommand::Private::setWaitForMoreJobs(bool wait) { if (wait == waitForMoreJobs) { return; } waitForMoreJobs = wait; if (!waitForMoreJobs) { tryToFinish(); } } void ImportCertificatesCommand::Private::onImportResult(const ImportResult &result, QGpgME::Job *finishedJob) { if (!finishedJob) { finishedJob = qobject_cast(q->sender()); } Q_ASSERT(finishedJob); qCDebug(KLEOPATRA_LOG) << q << __func__ << finishedJob; auto it = std::find_if(std::begin(runningJobs), std::end(runningJobs), [finishedJob](const auto &job) { return job.job == finishedJob; }); Q_ASSERT(it != std::end(runningJobs)); if (it == std::end(runningJobs)) { qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Finished job not found"; return; } Kleo::for_each(it->connections, &disconnectConnection); it->connections.clear(); increaseProgressValue(); const auto job = *it; addImportResult({job.id, job.protocol, job.type, result, AuditLogEntry::fromJob(finishedJob)}, job); } void ImportCertificatesCommand::Private::addImportResult(const ImportResultData &result, const ImportJobData &job) { qCDebug(KLEOPATRA_LOG) << q << __func__ << result.id << "Result:" << Formatting::errorAsString(result.result.error()); results.push_back(result); if (importFailed(result)) { showError(result); } if (job.job) { const auto count = std::erase(runningJobs, job); Q_ASSERT(count == 1); } tryToFinish(); } static void handleOwnerTrust(const std::vector &results, QWidget *dialog) { std::unordered_set askedAboutFingerprints; for (const auto &r : results) { if (r.protocol != GpgME::Protocol::OpenPGP) { qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping non-OpenPGP import"; continue; } const auto imports = r.result.imports(); for (const auto &import : imports) { if (!(import.status() & (Import::Status::NewKey | Import::Status::ContainedSecretKey))) { qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping already known imported public key"; continue; } const char *fpr = import.fingerprint(); if (!fpr) { qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping import without fingerprint"; continue; } if (Kleo::contains(askedAboutFingerprints, fpr)) { // imports of secret keys can result in multiple Imports for the same key qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping import for already handled fingerprint"; continue; } GpgME::Error err; auto ctx = wrap_unique(Context::createForProtocol(GpgME::Protocol::OpenPGP)); if (!ctx) { qCWarning(KLEOPATRA_LOG) << "Failed to get context"; continue; } ctx->addKeyListMode(KeyListMode::WithSecret); const Key toTrustOwner = ctx->key(fpr, err, false); if (toTrustOwner.isNull() || !toTrustOwner.hasSecret()) { continue; } if (toTrustOwner.ownerTrust() == Key::OwnerTrust::Ultimate) { qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping key with ultimate ownertrust"; continue; } 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 certificate with fingerprint" "%1" "" "and user IDs" "%2" "" "Is this your own certificate?", Formatting::prettyID(fpr), uids); int k = KMessageBox::questionTwoActionsCancel(dialog, str, i18nc("@title:window", "Mark Own Certificate"), KGuiItem{i18nc("@action:button", "Yes, It's Mine")}, KGuiItem{i18nc("@action:button", "No, It's Not Mine")}); askedAboutFingerprints.insert(fpr); if (k == KMessageBox::ButtonCode::PrimaryAction) { // 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); } else if (k == KMessageBox::ButtonCode::Cancel) { // do not bother the user with further "Is this yours?" questions return; } } } } 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(); if (Settings{}.retrieveSignerKeysAfterImport() && !importingSignerKeys) { importingSignerKeys = true; const auto missingSignerKeys = getMissingSignerKeyIds(results); if (!missingSignerKeys.empty()) { importSignerKeys(missingSignerKeys); return; } } handleExternalCMSImports(results); // ensure that the progress dialog is closed before we show any other dialogs setProgressToMaximum(); handleOwnerTrust(results, parentWidgetOrView()); 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() { qCDebug(KLEOPATRA_LOG) << q << __func__; if (waitForMoreJobs) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "Waiting for more jobs -> keep going"; return; } if (!runningJobs.empty()) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "There are unfinished jobs -> keep going"; return; } if (!pendingJobs.empty()) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "There are pending jobs -> start the next one"; auto job = pendingJobs.front(); pendingJobs.pop(); job.job->startNow(); runningJobs.push_back(job); return; } if (keyListConnection) { qCWarning(KLEOPATRA_LOG) << q << __func__ << "There is already a valid keyListConnection!"; } else { auto keyCache = KeyCache::mutableInstance(); keyListConnection = connect(keyCache.get(), &KeyCache::keyListingDone, q, [this]() { keyCacheUpdated(); }); keyCache->startKeyListing(); } } void ImportCertificatesCommand::Private::keyCacheUpdated() { qCDebug(KLEOPATRA_LOG) << q << __func__; if (!disconnect(keyListConnection)) { qCWarning(KLEOPATRA_LOG) << q << __func__ << "Failed to 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; } processResults(); } static ImportedGroup storeGroup(const KeyGroup &group, const QString &id, QWidget *parent) { if (Kleo::any_of(group.keys(), [](const auto &key) { return !key.hasEncrypt(); })) { KMessageBox::information(parent, xi18nc("@info", "The imported group%1contains certificates that cannot be used for encryption. " "This may lead to unexpected results.", group.name())); } 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, this](const auto &group) { return storeGroup(group, path, parentWidgetOrView()); }); } 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")); addImportResult({id, protocol, ImportType::Local, ImportResult{}, AuditLogEntry{}}); return; } keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh(); std::vector connections = { connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { onImportResult(result); }), connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress), }; job->setImportFilter(options.importFilter); job->setKeyOrigin(options.keyOrigin, options.keyOriginUrl); const GpgME::Error err = job->startLater(data); if (err.code()) { addImportResult({id, protocol, ImportType::Local, ImportResult{err}, AuditLogEntry{}}); } else { increaseProgressMaximum(); pendingJobs.push({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")); addImportResult({id, protocol, ImportType::External, ImportResult{}, AuditLogEntry{}}); return; } keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh(); std::vector connections = { connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { onImportResult(result); }), connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress), }; const GpgME::Error err = job->start(keys); if (err.code()) { addImportResult({id, protocol, ImportType::External, ImportResult{err}, AuditLogEntry{}}); } else { increaseProgressMaximum(); runningJobs.push_back({id, protocol, ImportType::External, job.release(), connections}); } } static auto get_receive_keys_job(GpgME::Protocol protocol) { Q_ASSERT(protocol != UnknownProtocol); std::unique_ptr job{}; if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) { job.reset(backend->receiveKeysJob()); } return job; } 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); addImportResult({id, protocol, ImportType::External, ImportResult{}, AuditLogEntry{}}); return; } keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh(); std::vector connections = { connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { onImportResult(result); }), connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress), }; const GpgME::Error err = job->start(keyIds); if (err.code()) { addImportResult({id, protocol, ImportType::External, ImportResult{err}, AuditLogEntry{}}); } else { increaseProgressMaximum(); runningJobs.push_back({id, protocol, ImportType::External, job.release(), connections}); } } void ImportCertificatesCommand::Private::importGroupsFromFile(const QString &filename) { increaseProgressMaximum(); filesToImportGroupsFrom.push_back(filename); } void ImportCertificatesCommand::Private::setUpProgressDialog() { if (progressDialog) { return; } progressDialog = new QProgressDialog{parentWidgetOrView()}; // use a non-modal progress dialog to avoid reentrancy problems (and crashes) if multiple jobs finish in the same event loop cycle // (cf. the warning for QProgressDialog::setValue() in the API documentation) progressDialog->setModal(false); 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()); } void ImportCertificatesCommand::doCancel() { const auto jobsToCancel = d->runningJobs; std::for_each(std::begin(jobsToCancel), std::end(jobsToCancel), [this](const auto &job) { if (!job.connections.empty()) { // ignore jobs without connections; they are already completed qCDebug(KLEOPATRA_LOG) << "Canceling job" << job.job; job.job->slotCancel(); d->onImportResult(ImportResult{Error::fromCode(GPG_ERR_CANCELED)}, job.job); } }); } #undef d #undef q #include "importcertificatescommand.moc" #include "moc_importcertificatescommand.cpp" diff --git a/src/crypto/encryptemailtask.cpp b/src/crypto/encryptemailtask.cpp index dda1e3224..473579a28 100644 --- a/src/crypto/encryptemailtask.cpp +++ b/src/crypto/encryptemailtask.cpp @@ -1,239 +1,238 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/encryptemailtask.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "encryptemailtask.h" #include #include #include #include #include #include #include #include #include #include #include #include -#include // for Qt::escape using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; namespace { class EncryptEMailResult : public Task::Result { const EncryptionResult m_result; const AuditLogEntry m_auditLog; public: EncryptEMailResult(const EncryptionResult &r, const AuditLogEntry &auditLog) : Task::Result() , m_result(r) , m_auditLog(auditLog) { } QString overview() const override; QString details() const override; GpgME::Error error() const override; QString errorString() const override; VisualCode code() const override; AuditLogEntry auditLog() const override; }; QString makeResultString(const EncryptionResult &res) { const Error err = res.error(); if (err.isCanceled()) { return i18n("Encryption canceled."); } if (err) { return i18n("Encryption failed: %1", Formatting::errorAsString(err).toHtmlEscaped()); } return i18n("Encryption succeeded."); } } class EncryptEMailTask::Private { friend class ::Kleo::Crypto::EncryptEMailTask; EncryptEMailTask *const q; public: explicit Private(EncryptEMailTask *qq); private: std::unique_ptr createJob(GpgME::Protocol proto); private: void slotResult(const EncryptionResult &); private: std::shared_ptr input; std::shared_ptr output; std::vector recipients; QPointer job; }; EncryptEMailTask::Private::Private(EncryptEMailTask *qq) : q(qq) , input() , output() , job(nullptr) { } EncryptEMailTask::EncryptEMailTask(QObject *p) : Task(p) , d(new Private(this)) { } EncryptEMailTask::~EncryptEMailTask() { } void EncryptEMailTask::setInput(const std::shared_ptr &input) { kleo_assert(!d->job); kleo_assert(input); d->input = input; } void EncryptEMailTask::setOutput(const std::shared_ptr &output) { kleo_assert(!d->job); kleo_assert(output); d->output = output; } void EncryptEMailTask::setRecipients(const std::vector &recipients) { kleo_assert(!d->job); kleo_assert(!recipients.empty()); d->recipients = recipients; } Protocol EncryptEMailTask::protocol() const { kleo_assert(!d->recipients.empty()); return d->recipients.front().protocol(); } QString EncryptEMailTask::label() const { return d->input ? d->input->label() : QString(); } unsigned long long EncryptEMailTask::inputSize() const { return d->input ? d->input->size() : 0; } void EncryptEMailTask::doStart() { kleo_assert(!d->job); kleo_assert(d->input); kleo_assert(d->output); kleo_assert(!d->recipients.empty()); std::unique_ptr job = d->createJob(protocol()); kleo_assert(job.get()); job->start(d->recipients, d->input->ioDevice(), d->output->ioDevice(), /*alwaysTrust=*/true); d->job = job.release(); } void EncryptEMailTask::cancel() { if (d->job) { d->job->slotCancel(); } } std::unique_ptr EncryptEMailTask::Private::createJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); bool shouldArmor = (proto == OpenPGP || q->asciiArmor()) && !output->binaryOpt(); std::unique_ptr encryptJob(backend->encryptJob(shouldArmor, /*textmode=*/false)); kleo_assert(encryptJob.get()); if (proto == CMS && !q->asciiArmor() && !output->binaryOpt()) { encryptJob->setOutputIsBase64Encoded(true); } connect(encryptJob.get(), &QGpgME::Job::jobProgress, q, &EncryptEMailTask::setProgress); connect(encryptJob.get(), SIGNAL(result(GpgME::EncryptionResult, QByteArray)), q, SLOT(slotResult(GpgME::EncryptionResult))); return encryptJob; } void EncryptEMailTask::Private::slotResult(const EncryptionResult &result) { const auto *const job = qobject_cast(q->sender()); if (result.error().code()) { output->cancel(); } else { output->finalize(); } q->emitResult(std::shared_ptr(new EncryptEMailResult(result, AuditLogEntry::fromJob(job)))); } QString EncryptEMailResult::overview() const { return makeOverview(makeResultString(m_result)); } QString EncryptEMailResult::details() const { return QString(); } GpgME::Error EncryptEMailResult::error() const { return m_result.error(); } QString EncryptEMailResult::errorString() const { return hasError() ? makeResultString(m_result) : QString(); } AuditLogEntry EncryptEMailResult::auditLog() const { return m_auditLog; } Task::Result::VisualCode EncryptEMailResult::code() const { if (m_result.error().isCanceled()) { return Warning; } return m_result.error().code() ? NeutralError : NeutralSuccess; } #include "moc_encryptemailtask.cpp" diff --git a/src/crypto/signemailtask.cpp b/src/crypto/signemailtask.cpp index 9d3aa0e1a..7f6f7e170 100644 --- a/src/crypto/signemailtask.cpp +++ b/src/crypto/signemailtask.cpp @@ -1,290 +1,289 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signemailtask.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signemailtask.h" #include #include #include #include #include #include #include #include #include #include #include #include -#include // for Qt::escape #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; namespace { class SignEMailResult : public Task::Result { const SigningResult m_result; const AuditLogEntry m_auditLog; public: explicit SignEMailResult(const SigningResult &r, const AuditLogEntry &auditLog) : Task::Result() , m_result(r) , m_auditLog(auditLog) { } QString overview() const override; QString details() const override; GpgME::Error error() const override; QString errorString() const override; VisualCode code() const override; AuditLogEntry auditLog() const override; }; QString makeResultString(const SigningResult &res) { const Error err = res.error(); if (err.isCanceled()) { return i18n("Signing canceled."); } if (err) { return i18n("Signing failed: %1", Formatting::errorAsString(err).toHtmlEscaped()); } return i18n("Signing succeeded."); } } class SignEMailTask::Private { friend class ::Kleo::Crypto::SignEMailTask; SignEMailTask *const q; public: explicit Private(SignEMailTask *qq); private: std::unique_ptr createJob(GpgME::Protocol proto); private: void slotResult(const SigningResult &); private: std::shared_ptr input; std::shared_ptr output; std::vector signers; bool detached; bool clearsign; QString micAlg; QPointer job; }; SignEMailTask::Private::Private(SignEMailTask *qq) : q(qq) , input() , output() , signers() , detached(false) , clearsign(false) , micAlg() , job(nullptr) { } SignEMailTask::SignEMailTask(QObject *p) : Task(p) , d(new Private(this)) { } SignEMailTask::~SignEMailTask() { } void SignEMailTask::setInput(const std::shared_ptr &input) { kleo_assert(!d->job); kleo_assert(input); d->input = input; } void SignEMailTask::setOutput(const std::shared_ptr &output) { kleo_assert(!d->job); kleo_assert(output); d->output = output; } void SignEMailTask::setSigners(const std::vector &signers) { kleo_assert(!d->job); kleo_assert(!signers.empty()); kleo_assert(std::none_of(signers.cbegin(), signers.cend(), std::mem_fn(&Key::isNull))); d->signers = signers; } void SignEMailTask::setDetachedSignature(bool detached) { kleo_assert(!d->job); d->detached = detached; d->clearsign = false; } void SignEMailTask::setClearsign(bool clear) { kleo_assert(!d->job); d->clearsign = clear; d->detached = false; } Protocol SignEMailTask::protocol() const { kleo_assert(!d->signers.empty()); return d->signers.front().protocol(); } QString SignEMailTask::label() const { return d->input ? d->input->label() : QString(); } unsigned long long SignEMailTask::inputSize() const { return d->input ? d->input->size() : 0; } void SignEMailTask::doStart() { kleo_assert(!d->job); kleo_assert(d->input); kleo_assert(d->output); kleo_assert(!d->signers.empty()); d->micAlg.clear(); std::unique_ptr job = d->createJob(protocol()); kleo_assert(job.get()); job->start(d->signers, d->input->ioDevice(), d->output->ioDevice(), d->clearsign ? GpgME::Clearsigned : d->detached ? GpgME::Detached : GpgME::NormalSignatureMode); d->job = job.release(); } void SignEMailTask::cancel() { if (d->job) { d->job->slotCancel(); } } std::unique_ptr SignEMailTask::Private::createJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); bool shouldArmor = (proto == OpenPGP || q->asciiArmor()) && !output->binaryOpt(); std::unique_ptr signJob(backend->signJob(/*armor=*/shouldArmor, /*textmode=*/false)); kleo_assert(signJob.get()); if (proto == CMS && !q->asciiArmor() && !output->binaryOpt()) { signJob->setOutputIsBase64Encoded(true); } connect(signJob.get(), &QGpgME::Job::jobProgress, q, &SignEMailTask::setProgress); connect(signJob.get(), SIGNAL(result(GpgME::SigningResult, QByteArray)), q, SLOT(slotResult(GpgME::SigningResult))); return signJob; } static QString collect_micalgs(const GpgME::SigningResult &result, GpgME::Protocol proto) { const std::vector css = result.createdSignatures(); QStringList micalgs; std::transform(css.begin(), css.end(), std::back_inserter(micalgs), [](const GpgME::CreatedSignature &sig) { return QString::fromLatin1(sig.hashAlgorithmAsString()).toLower(); }); if (proto == GpgME::OpenPGP) for (QStringList::iterator it = micalgs.begin(), end = micalgs.end(); it != end; ++it) { it->prepend(QLatin1StringView("pgp-")); } micalgs.sort(); micalgs.erase(std::unique(micalgs.begin(), micalgs.end()), micalgs.end()); return micalgs.join(QLatin1Char(',')); } void SignEMailTask::Private::slotResult(const SigningResult &result) { const auto *const job = qobject_cast(q->sender()); if (result.error().code()) { output->cancel(); } else { output->finalize(); micAlg = collect_micalgs(result, q->protocol()); } q->emitResult(std::shared_ptr(new SignEMailResult(result, AuditLogEntry::fromJob(job)))); } QString SignEMailTask::micAlg() const { return d->micAlg; } QString SignEMailResult::overview() const { return makeOverview(makeResultString(m_result)); } QString SignEMailResult::details() const { return QString(); } GpgME::Error SignEMailResult::error() const { return m_result.error(); } QString SignEMailResult::errorString() const { return hasError() ? makeResultString(m_result) : QString(); } Task::Result::VisualCode SignEMailResult::code() const { if (m_result.error().isCanceled()) { return Warning; } return m_result.error().code() ? NeutralError : NeutralSuccess; } AuditLogEntry SignEMailResult::auditLog() const { return m_auditLog; } #include "moc_signemailtask.cpp" diff --git a/src/crypto/signencrypttask.cpp b/src/crypto/signencrypttask.cpp index 8e9da4278..2b4aa33d3 100644 --- a/src/crypto/signencrypttask.cpp +++ b/src/crypto/signencrypttask.cpp @@ -1,973 +1,972 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signencrypttask.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signencrypttask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include -#include // for Qt::escape using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; namespace { QString formatInputOutputLabel(const QString &input, const QString &output, bool outputDeleted) { return i18nc("Input file --> Output file (rarr is arrow", "%1 → %2", input.toHtmlEscaped(), outputDeleted ? QStringLiteral("%1").arg(output.toHtmlEscaped()) : output.toHtmlEscaped()); } class ErrorResult : public Task::Result { public: ErrorResult(bool sign, bool encrypt, const Error &err, const QString &errStr, const QString &input, const QString &output, const AuditLogEntry &auditLog) : Task::Result() , m_sign(sign) , m_encrypt(encrypt) , m_error(err) , m_errString(errStr) , m_inputLabel(input) , m_outputLabel(output) , m_auditLog(auditLog) { } QString overview() const override; QString details() const override; GpgME::Error error() const override { return m_error; } QString errorString() const override { return m_errString; } VisualCode code() const override { return NeutralError; } AuditLogEntry auditLog() const override { return m_auditLog; } private: const bool m_sign; const bool m_encrypt; const Error m_error; const QString m_errString; const QString m_inputLabel; const QString m_outputLabel; const AuditLogEntry m_auditLog; }; namespace { struct LabelAndError { QString label; QString errorString; }; } class SignEncryptFilesResult : public Task::Result { public: SignEncryptFilesResult(const SigningResult &sr, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog) : Task::Result() , m_sresult(sr) , m_input{input} , m_output{output} , m_outputCreated(outputCreated) , m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString; Q_ASSERT(!m_sresult.isNull()); } SignEncryptFilesResult(const EncryptionResult &er, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog) : Task::Result() , m_eresult(er) , m_input{input} , m_output{output} , m_outputCreated(outputCreated) , m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString; Q_ASSERT(!m_eresult.isNull()); } SignEncryptFilesResult(const SigningResult &sr, const EncryptionResult &er, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog) : Task::Result() , m_sresult(sr) , m_eresult(er) , m_input{input} , m_output{output} , m_outputCreated(outputCreated) , m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString; Q_ASSERT(!m_sresult.isNull() || !m_eresult.isNull()); } QString overview() const override; QString details() const override; GpgME::Error error() const override; QString errorString() const override; VisualCode code() const override; AuditLogEntry auditLog() const override; private: const SigningResult m_sresult; const EncryptionResult m_eresult; const LabelAndError m_input; const LabelAndError m_output; const bool m_outputCreated; const AuditLogEntry m_auditLog; }; static QString makeSigningOverview(const Error &err) { if (err.isCanceled()) { return i18n("Signing canceled."); } if (err) { return i18n("Signing failed."); } return i18n("Signing succeeded."); } static QString makeResultOverview(const SigningResult &result) { return makeSigningOverview(result.error()); } static QString makeEncryptionOverview(const Error &err) { if (err.isCanceled()) { return i18n("Encryption canceled."); } if (err) { return i18n("Encryption failed."); } return i18n("Encryption succeeded."); } static QString makeResultOverview(const EncryptionResult &result) { return makeEncryptionOverview(result.error()); } static QString makeResultOverview(const SigningResult &sr, const EncryptionResult &er) { if (er.isNull() && sr.isNull()) { return QString(); } if (er.isNull()) { return makeResultOverview(sr); } if (sr.isNull()) { return makeResultOverview(er); } if (sr.error().isCanceled() || sr.error()) { return makeResultOverview(sr); } if (er.error().isCanceled() || er.error()) { return makeResultOverview(er); } return i18n("Signing and encryption succeeded."); } static QString escape(QString s) { s = s.toHtmlEscaped(); s.replace(QLatin1Char('\n'), QStringLiteral("
    ")); return s; } static QString makeResultDetails(const SigningResult &result, const QString &inputError, const QString &outputError) { const Error err = result.error(); if (err.code() == GPG_ERR_EIO) { if (!inputError.isEmpty()) { return i18n("Input error: %1", escape(inputError)); } else if (!outputError.isEmpty()) { return i18n("Output error: %1", escape(outputError)); } } if (err || err.isCanceled()) { return Formatting::errorAsString(err).toHtmlEscaped(); } return QString(); } static QString makeResultDetails(const EncryptionResult &result, const QString &inputError, const QString &outputError) { const Error err = result.error(); if (err.code() == GPG_ERR_EIO) { if (!inputError.isEmpty()) { return i18n("Input error: %1", escape(inputError)); } else if (!outputError.isEmpty()) { return i18n("Output error: %1", escape(outputError)); } } if (err || err.isCanceled()) { return Formatting::errorAsString(err).toHtmlEscaped(); } return i18n(" Encryption succeeded."); } } QString ErrorResult::overview() const { Q_ASSERT(m_error || m_error.isCanceled()); Q_ASSERT(m_sign || m_encrypt); const QString label = formatInputOutputLabel(m_inputLabel, m_outputLabel, true); const bool canceled = m_error.isCanceled(); if (m_sign && m_encrypt) { return canceled ? i18n("%1: Sign/encrypt canceled.", label) : i18n(" %1: Sign/encrypt failed.", label); } return i18nc("label: result. Example: foo -> foo.gpg: Encryption failed.", "%1: %2", label, m_sign ? makeSigningOverview(m_error) : makeEncryptionOverview(m_error)); } QString ErrorResult::details() const { return m_errString; } class SignEncryptTask::Private { friend class ::Kleo::Crypto::SignEncryptTask; SignEncryptTask *const q; public: explicit Private(SignEncryptTask *qq); private: QString inputLabel() const; QString outputLabel() const; bool removeExistingOutputFile(); void startSignEncryptJob(GpgME::Protocol proto); std::unique_ptr createSignJob(GpgME::Protocol proto); std::unique_ptr createSignEncryptJob(GpgME::Protocol proto); std::unique_ptr createEncryptJob(GpgME::Protocol proto); void startSignEncryptArchiveJob(GpgME::Protocol proto); std::unique_ptr createSignArchiveJob(GpgME::Protocol proto); std::unique_ptr createSignEncryptArchiveJob(GpgME::Protocol proto); std::unique_ptr createEncryptArchiveJob(GpgME::Protocol proto); std::shared_ptr makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog); private: void slotResult(const SigningResult &); void slotResult(const SigningResult &, const EncryptionResult &); void slotResult(const EncryptionResult &); void slotResult(const QGpgME::Job *, const SigningResult &, const EncryptionResult &); private: std::shared_ptr input; std::shared_ptr output; QStringList inputFileNames; QString outputFileName; std::vector signers; std::vector recipients; bool sign : 1; bool encrypt : 1; bool detached : 1; bool symmetric : 1; bool clearsign : 1; bool archive : 1; QPointer job; QString labelText; std::shared_ptr m_overwritePolicy; }; SignEncryptTask::Private::Private(SignEncryptTask *qq) : q{qq} , sign{true} , encrypt{true} , detached{false} , clearsign{false} , archive{false} , m_overwritePolicy{new OverwritePolicy{OverwritePolicy::Ask}} { q->setAsciiArmor(true); } std::shared_ptr SignEncryptTask::Private::makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog) { return std::shared_ptr(new ErrorResult(sign, encrypt, err, errStr, inputLabel(), outputLabel(), auditLog)); } SignEncryptTask::SignEncryptTask(QObject *p) : Task(p) , d(new Private(this)) { } SignEncryptTask::~SignEncryptTask() { } void SignEncryptTask::setInputFileName(const QString &fileName) { kleo_assert(!d->job); kleo_assert(!fileName.isEmpty()); d->inputFileNames = QStringList(fileName); } void SignEncryptTask::setInputFileNames(const QStringList &fileNames) { kleo_assert(!d->job); kleo_assert(!fileNames.empty()); d->inputFileNames = fileNames; } void SignEncryptTask::setInput(const std::shared_ptr &input) { kleo_assert(!d->job); kleo_assert(input); d->input = input; } void SignEncryptTask::setOutput(const std::shared_ptr &output) { kleo_assert(!d->job); kleo_assert(output); d->output = output; } void SignEncryptTask::setOutputFileName(const QString &fileName) { kleo_assert(!d->job); kleo_assert(!fileName.isEmpty()); d->outputFileName = fileName; } QString SignEncryptTask::outputFileName() const { return d->outputFileName; } void SignEncryptTask::setSigners(const std::vector &signers) { kleo_assert(!d->job); d->signers = signers; } void SignEncryptTask::setRecipients(const std::vector &recipients) { kleo_assert(!d->job); d->recipients = recipients; } void SignEncryptTask::setOverwritePolicy(const std::shared_ptr &policy) { kleo_assert(!d->job); d->m_overwritePolicy = policy; } void SignEncryptTask::setSign(bool sign) { kleo_assert(!d->job); d->sign = sign; } void SignEncryptTask::setEncrypt(bool encrypt) { kleo_assert(!d->job); d->encrypt = encrypt; } void SignEncryptTask::setDetachedSignature(bool detached) { kleo_assert(!d->job); d->detached = detached; } void SignEncryptTask::setEncryptSymmetric(bool symmetric) { kleo_assert(!d->job); d->symmetric = symmetric; } void SignEncryptTask::setClearsign(bool clearsign) { kleo_assert(!d->job); d->clearsign = clearsign; } void SignEncryptTask::setCreateArchive(bool archive) { kleo_assert(!d->job); d->archive = archive; } Protocol SignEncryptTask::protocol() const { if (d->sign && !d->signers.empty()) { return d->signers.front().protocol(); } if (d->encrypt || d->symmetric) { if (!d->recipients.empty()) { return d->recipients.front().protocol(); } else { return GpgME::OpenPGP; // symmetric OpenPGP encryption } } throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL), i18n("Cannot determine protocol for task")); } QString SignEncryptTask::label() const { if (!d->labelText.isEmpty()) { return d->labelText; } return d->inputLabel(); } QString SignEncryptTask::tag() const { return Formatting::displayName(protocol()); } unsigned long long SignEncryptTask::inputSize() const { return d->input ? d->input->size() : 0U; } static bool archiveJobsCanBeUsed(GpgME::Protocol protocol) { return (protocol == GpgME::OpenPGP) && QGpgME::SignEncryptArchiveJob::isSupported(); } void SignEncryptTask::doStart() { kleo_assert(!d->job); if (d->sign) { kleo_assert(!d->signers.empty()); if (d->archive) { kleo_assert(!d->detached && !d->clearsign); } } const auto proto = protocol(); if (d->archive && archiveJobsCanBeUsed(proto)) { d->startSignEncryptArchiveJob(proto); } else { d->startSignEncryptJob(proto); } } QString SignEncryptTask::Private::inputLabel() const { if (input) { return input->label(); } if (!inputFileNames.empty()) { const auto firstFile = QFileInfo{inputFileNames.front()}.fileName(); return inputFileNames.size() == 1 ? firstFile : i18nc(", ...", "%1, ...", firstFile); } return {}; } QString SignEncryptTask::Private::outputLabel() const { return output ? output->label() : QFileInfo{outputFileName}.fileName(); } bool SignEncryptTask::Private::removeExistingOutputFile() { if (QFile::exists(outputFileName)) { bool fileRemoved = false; // we should already have asked the user for overwrite permission if (m_overwritePolicy && (m_overwritePolicy->policy() == OverwritePolicy::Overwrite)) { qCDebug(KLEOPATRA_LOG) << __func__ << "going to remove file for overwriting" << outputFileName; fileRemoved = QFile::remove(outputFileName); if (!fileRemoved) { qCDebug(KLEOPATRA_LOG) << __func__ << "removing file to overwrite failed"; } } else { qCDebug(KLEOPATRA_LOG) << __func__ << "we have no permission to overwrite" << outputFileName; } if (!fileRemoved) { QMetaObject::invokeMethod( q, [this]() { slotResult(nullptr, SigningResult{}, EncryptionResult{Error::fromCode(GPG_ERR_EEXIST)}); }, Qt::QueuedConnection); return false; } } return true; } void SignEncryptTask::Private::startSignEncryptJob(GpgME::Protocol proto) { #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (proto == GpgME::OpenPGP) { // either input and output are both set (e.g. when encrypting the notepad), // or they are both unset (when encrypting files) kleo_assert((!input && !output) || (input && output)); } else { kleo_assert(input); if (!output) { output = Output::createFromFile(outputFileName, m_overwritePolicy); } } #else kleo_assert(input); if (!output) { output = Output::createFromFile(outputFileName, m_overwritePolicy); } #endif if (encrypt || symmetric) { Context::EncryptionFlags flags{Context::None}; if (proto == GpgME::OpenPGP) { flags = static_cast(flags | Context::AlwaysTrust); } if (symmetric) { flags = static_cast(flags | Context::Symmetric); qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag"; } if (sign) { std::unique_ptr job = createSignEncryptJob(proto); kleo_assert(job.get()); #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (proto == GpgME::OpenPGP && !input && !output) { kleo_assert(inputFileNames.size() == 1); job->setSigners(signers); job->setRecipients(recipients); job->setInputFile(inputFileNames.front()); job->setOutputFile(outputFileName); job->setEncryptionFlags(flags); if (!removeExistingOutputFile()) { return; } job->startIt(); } else { if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } job->start(signers, recipients, input->ioDevice(), output->ioDevice(), flags); } #else if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } job->start(signers, recipients, input->ioDevice(), output->ioDevice(), flags); #endif this->job = job.release(); } else { std::unique_ptr job = createEncryptJob(proto); kleo_assert(job.get()); #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (proto == GpgME::OpenPGP && !input && !output) { kleo_assert(inputFileNames.size() == 1); job->setRecipients(recipients); job->setInputFile(inputFileNames.front()); job->setOutputFile(outputFileName); job->setEncryptionFlags(flags); if (!removeExistingOutputFile()) { return; } job->startIt(); } else { if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } job->start(recipients, input->ioDevice(), output->ioDevice(), flags); } #else if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } job->start(recipients, input->ioDevice(), output->ioDevice(), flags); #endif this->job = job.release(); } } else if (sign) { std::unique_ptr job = createSignJob(proto); kleo_assert(job.get()); kleo_assert(!(detached && clearsign)); const GpgME::SignatureMode sigMode = detached ? GpgME::Detached : clearsign ? GpgME::Clearsigned : GpgME::NormalSignatureMode; #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (proto == GpgME::OpenPGP && !input && !output) { kleo_assert(inputFileNames.size() == 1); job->setSigners(signers); job->setInputFile(inputFileNames.front()); job->setOutputFile(outputFileName); job->setSigningFlags(sigMode); if (!removeExistingOutputFile()) { return; } job->startIt(); } else { job->start(signers, input->ioDevice(), output->ioDevice(), sigMode); } #else job->start(signers, input->ioDevice(), output->ioDevice(), sigMode); #endif this->job = job.release(); } else { kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!"); } } void SignEncryptTask::cancel() { qCDebug(KLEOPATRA_LOG) << this << __func__; if (d->job) { d->job->slotCancel(); } } std::unique_ptr SignEncryptTask::Private::createSignJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signJob(backend->signJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(signJob.get()); connect(signJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); connect(signJob.get(), SIGNAL(result(GpgME::SigningResult, QByteArray)), q, SLOT(slotResult(GpgME::SigningResult))); return signJob; } std::unique_ptr SignEncryptTask::Private::createSignEncryptJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signEncryptJob(backend->signEncryptJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(signEncryptJob.get()); connect(signEncryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); connect(signEncryptJob.get(), SIGNAL(result(GpgME::SigningResult, GpgME::EncryptionResult, QByteArray)), q, SLOT(slotResult(GpgME::SigningResult, GpgME::EncryptionResult))); return signEncryptJob; } std::unique_ptr SignEncryptTask::Private::createEncryptJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr encryptJob(backend->encryptJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(encryptJob.get()); connect(encryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); connect(encryptJob.get(), SIGNAL(result(GpgME::EncryptionResult, QByteArray)), q, SLOT(slotResult(GpgME::EncryptionResult))); return encryptJob; } void SignEncryptTask::Private::startSignEncryptArchiveJob(GpgME::Protocol proto) { kleo_assert(!input); kleo_assert(!output); #if !QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME output = Output::createFromFile(outputFileName, m_overwritePolicy); #endif const auto baseDirectory = heuristicBaseDirectory(inputFileNames); if (baseDirectory.isEmpty()) { throw Kleo::Exception(GPG_ERR_CONFLICT, i18n("Cannot find common base directory for these files:\n%1", inputFileNames.join(QLatin1Char('\n')))); } qCDebug(KLEOPATRA_LOG) << "heuristicBaseDirectory(" << inputFileNames << ") ->" << baseDirectory; const auto tempPaths = makeRelativeTo(baseDirectory, inputFileNames); const auto relativePaths = std::vector{tempPaths.begin(), tempPaths.end()}; qCDebug(KLEOPATRA_LOG) << "relative paths:" << relativePaths; if (encrypt || symmetric) { Context::EncryptionFlags flags{Context::None}; if (proto == GpgME::OpenPGP) { flags = static_cast(flags | Context::AlwaysTrust); } if (symmetric) { flags = static_cast(flags | Context::Symmetric); qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag"; } if (sign) { labelText = i18nc("@info", "Creating signed and encrypted archive ..."); std::unique_ptr job = createSignEncryptArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); #if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME job->setSigners(signers); job->setRecipients(recipients); job->setInputPaths(relativePaths); job->setOutputFile(outputFileName); job->setEncryptionFlags(flags); if (!removeExistingOutputFile()) { return; } job->startIt(); #else job->start(signers, recipients, relativePaths, output->ioDevice(), flags); #endif this->job = job.release(); } else { labelText = i18nc("@info", "Creating encrypted archive ..."); std::unique_ptr job = createEncryptArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); #if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME job->setRecipients(recipients); job->setInputPaths(relativePaths); job->setOutputFile(outputFileName); job->setEncryptionFlags(flags); if (!removeExistingOutputFile()) { return; } job->startIt(); #else job->start(recipients, relativePaths, output->ioDevice(), flags); #endif this->job = job.release(); } } else if (sign) { labelText = i18nc("@info", "Creating signed archive ..."); std::unique_ptr job = createSignArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); #if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME job->setSigners(signers); job->setInputPaths(relativePaths); job->setOutputFile(outputFileName); if (!removeExistingOutputFile()) { return; } job->startIt(); #else job->start(signers, relativePaths, output->ioDevice()); #endif this->job = job.release(); } else { kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!"); } } std::unique_ptr SignEncryptTask::Private::createSignArchiveJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signJob(backend->signArchiveJob(q->asciiArmor())); auto job = signJob.get(); kleo_assert(job); connect(job, &QGpgME::SignArchiveJob::dataProgress, q, &SignEncryptTask::setProgress); connect(job, &QGpgME::SignArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult) { slotResult(job, signResult, EncryptionResult{}); }); return signJob; } std::unique_ptr SignEncryptTask::Private::createSignEncryptArchiveJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signEncryptJob(backend->signEncryptArchiveJob(q->asciiArmor())); auto job = signEncryptJob.get(); kleo_assert(job); connect(job, &QGpgME::SignEncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress); connect(job, &QGpgME::SignEncryptArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult, const GpgME::EncryptionResult &encryptResult) { slotResult(job, signResult, encryptResult); }); return signEncryptJob; } std::unique_ptr SignEncryptTask::Private::createEncryptArchiveJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr encryptJob(backend->encryptArchiveJob(q->asciiArmor())); auto job = encryptJob.get(); kleo_assert(job); connect(job, &QGpgME::EncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress); connect(job, &QGpgME::EncryptArchiveJob::result, q, [this, job](const GpgME::EncryptionResult &encryptResult) { slotResult(job, SigningResult{}, encryptResult); }); return encryptJob; } void SignEncryptTask::Private::slotResult(const SigningResult &result) { slotResult(qobject_cast(q->sender()), result, EncryptionResult{}); } void SignEncryptTask::Private::slotResult(const SigningResult &sresult, const EncryptionResult &eresult) { slotResult(qobject_cast(q->sender()), sresult, eresult); } void SignEncryptTask::Private::slotResult(const EncryptionResult &result) { slotResult(qobject_cast(q->sender()), SigningResult{}, result); } void SignEncryptTask::Private::slotResult(const QGpgME::Job *job, const SigningResult &sresult, const EncryptionResult &eresult) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "job:" << job << "signing result:" << QGpgME::toLogString(sresult) << "encryption result:" << QGpgME::toLogString(eresult); const AuditLogEntry auditLog = AuditLogEntry::fromJob(job); bool outputCreated = false; if (input && input->failed()) { if (output) { output->cancel(); } q->emitResult(makeErrorResult(Error::fromCode(GPG_ERR_EIO), i18n("Input error: %1", escape(input->errorString())), auditLog)); return; } else if (sresult.error().code() || eresult.error().code()) { if (output) { output->cancel(); } if (!outputFileName.isEmpty() && eresult.error().code() != GPG_ERR_EEXIST) { // ensure that the output file is removed if the task was canceled or an error occurred; // unless a "file exists" error occurred because this means that the file with the name // of outputFileName wasn't created as result of this task if (QFile::exists(outputFileName)) { qCDebug(KLEOPATRA_LOG) << __func__ << "Removing output file" << outputFileName << "after error or cancel"; if (!QFile::remove(outputFileName)) { qCDebug(KLEOPATRA_LOG) << __func__ << "Removing output file" << outputFileName << "failed"; } } } } else { try { kleo_assert(!sresult.isNull() || !eresult.isNull()); if (output) { output->finalize(); } outputCreated = true; if (input) { input->finalize(); } } catch (const GpgME::Exception &e) { q->emitResult(makeErrorResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } } const LabelAndError inputInfo{inputLabel(), input ? input->errorString() : QString{}}; const LabelAndError outputInfo{outputLabel(), output ? output->errorString() : QString{}}; q->emitResult(std::shared_ptr(new SignEncryptFilesResult(sresult, eresult, inputInfo, outputInfo, outputCreated, auditLog))); } QString SignEncryptFilesResult::overview() const { const QString files = formatInputOutputLabel(m_input.label, m_output.label, !m_outputCreated); return files + QLatin1StringView(": ") + makeOverview(makeResultOverview(m_sresult, m_eresult)); } QString SignEncryptFilesResult::details() const { return errorString(); } GpgME::Error SignEncryptFilesResult::error() const { if (m_sresult.error().code()) { return m_sresult.error(); } if (m_eresult.error().code()) { return m_eresult.error(); } return {}; } QString SignEncryptFilesResult::errorString() const { const bool sign = !m_sresult.isNull(); const bool encrypt = !m_eresult.isNull(); kleo_assert(sign || encrypt); if (sign && encrypt) { return m_sresult.error().code() ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) : m_eresult.error().code() ? makeResultDetails(m_eresult, m_input.errorString, m_output.errorString) : QString(); } return sign ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) // : makeResultDetails(m_eresult, m_input.errorString, m_output.errorString); } Task::Result::VisualCode SignEncryptFilesResult::code() const { if (m_sresult.error().isCanceled() || m_eresult.error().isCanceled()) { return Warning; } return (m_sresult.error().code() || m_eresult.error().code()) ? NeutralError : NeutralSuccess; } AuditLogEntry SignEncryptFilesResult::auditLog() const { return m_auditLog; } #include "moc_signencrypttask.cpp" diff --git a/src/dialogs/setinitialpindialog.cpp b/src/dialogs/setinitialpindialog.cpp index 474125a40..1dead4f78 100644 --- a/src/dialogs/setinitialpindialog.cpp +++ b/src/dialogs/setinitialpindialog.cpp @@ -1,199 +1,198 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/setinitialpindialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "setinitialpindialog.h" #include "ui_setinitialpindialog.h" #include #include #include #include -#include // for Qt::escape #include using namespace Kleo; using namespace Kleo::Dialogs; using namespace GpgME; enum State { Unknown = 0, NotSet, AlreadySet, Ongoing, Ok, Failed, NumStates, }; const char *icons[] = { // PENDING(marc) use better icons, once available "", // Unknown "", // NotSet "security-medium", // AlreadySet "movie-process-working-kde", // Ongoing "security-high", // Ok "security-low", // Failed }; static_assert(sizeof icons / sizeof(*icons) == NumStates, ""); static_assert(sizeof("movie-") == 7, ""); static void update_widget(State state, bool delay, QLabel *resultLB, QLabel *lb, QPushButton *pb, QLabel *statusLB) { Q_ASSERT(state >= 0); Q_ASSERT(state < NumStates); const char *icon = icons[state]; if (qstrncmp(icon, "movie-", sizeof("movie-") - 1) == 0) { resultLB->setMovie(KIconLoader::global()->loadMovie(QLatin1StringView(icon + sizeof("movie-")), KIconLoader::NoGroup)); } else if (icon && *icon) { resultLB->setPixmap(QIcon::fromTheme(QLatin1StringView(icon)).pixmap(32)); } else { resultLB->setPixmap(QPixmap()); } lb->setEnabled((state == NotSet || state == Failed) && !delay); pb->setEnabled((state == NotSet || state == Failed) && !delay); if (state == AlreadySet) { statusLB->setText( xi18nc("@info", "No NullPin found. If this PIN was not set by you personally, the card might have been tampered with.")); } } static QString format_error(const Error &err) { if (err.isCanceled()) { return i18nc("@info", "Canceled setting PIN."); } if (err) return xi18nc("@info", "There was an error setting the PIN: %1.", Formatting::errorAsString(err).toHtmlEscaped()); else { return i18nc("@info", "PIN set successfully."); } } class SetInitialPinDialog::Private { friend class ::Kleo::Dialogs::SetInitialPinDialog; SetInitialPinDialog *const q; public: explicit Private(SetInitialPinDialog *qq) : q(qq) , nksState(Unknown) , sigGState(Unknown) , ui(q) { } private: void slotNksButtonClicked() { nksState = Ongoing; ui.nksStatusLB->clear(); updateWidgets(); Q_EMIT q->nksPinRequested(); } void slotSigGButtonClicked() { sigGState = Ongoing; ui.sigGStatusLB->clear(); updateWidgets(); Q_EMIT q->sigGPinRequested(); } private: void updateWidgets() { update_widget(nksState, false, ui.nksResultIcon, ui.nksLB, ui.nksPB, ui.nksStatusLB); update_widget(sigGState, nksState == NotSet || nksState == Failed || nksState == Ongoing, ui.sigGResultIcon, ui.sigGLB, ui.sigGPB, ui.sigGStatusLB); ui.closePB()->setEnabled(q->isComplete()); ui.cancelPB()->setEnabled(!q->isComplete()); } private: State nksState, sigGState; struct UI : public Ui::SetInitialPinDialog { explicit UI(Dialogs::SetInitialPinDialog *qq) : Ui::SetInitialPinDialog() { setupUi(qq); closePB()->setEnabled(false); connect(closePB(), &QAbstractButton::clicked, qq, &QDialog::accept); } QAbstractButton *closePB() const { Q_ASSERT(dialogButtonBox); return dialogButtonBox->button(QDialogButtonBox::Close); } QAbstractButton *cancelPB() const { Q_ASSERT(dialogButtonBox); return dialogButtonBox->button(QDialogButtonBox::Cancel); } } ui; }; SetInitialPinDialog::SetInitialPinDialog(QWidget *p) : QDialog(p) , d(new Private(this)) { } SetInitialPinDialog::~SetInitialPinDialog() { } void SetInitialPinDialog::setNksPinPresent(bool on) { d->nksState = on ? AlreadySet : NotSet; d->updateWidgets(); } void SetInitialPinDialog::setSigGPinPresent(bool on) { d->sigGState = on ? AlreadySet : NotSet; d->updateWidgets(); } void SetInitialPinDialog::setNksPinSettingResult(const Error &err) { d->ui.nksStatusLB->setText(format_error(err)); d->nksState = (err.isCanceled() ? NotSet // : err ? Failed : Ok); d->updateWidgets(); } void SetInitialPinDialog::setSigGPinSettingResult(const Error &err) { d->ui.sigGStatusLB->setText(format_error(err)); d->sigGState = (err.isCanceled() ? NotSet // : err ? Failed : Ok); d->updateWidgets(); } bool SetInitialPinDialog::isComplete() const { return (d->nksState == Ok || d->nksState == AlreadySet); } #include "moc_setinitialpindialog.cpp" diff --git a/src/main.cpp b/src/main.cpp index 294cce2ab..71c279337 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,286 +1,285 @@ /* main.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2001, 2002, 2004, 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 "aboutdata.h" #include "kleopatraapplication.h" #include "mainwindow.h" #include "accessibility/accessiblewidgetfactory.h" #include #include #include "utils/kuniqueservice.h" #include "utils/userinfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include "kleopatra_options.h" #include #include #include #include #include #include #include -#include // for Qt::escape #include #include #include #include #include #include #include #include QElapsedTimer startupTimer; static bool selfCheck() { Kleo::Commands::SelfTestCommand cmd(nullptr); cmd.setAutoDelete(false); cmd.setAutomaticMode(true); QEventLoop loop; QObject::connect(&cmd, &Kleo::Commands::SelfTestCommand::finished, &loop, &QEventLoop::quit); QTimer::singleShot(0, &cmd, &Kleo::Command::start); // start() may Q_EMIT finished()... loop.exec(); if (cmd.isCanceled()) { return false; } else { return true; } } static void fillKeyCache(Kleo::UiServer *server) { auto cmd = new Kleo::ReloadKeysCommand(nullptr); QObject::connect(cmd, SIGNAL(finished()), server, SLOT(enableCryptoCommands())); cmd->start(); } int main(int argc, char **argv) { startupTimer.start(); #ifdef Q_OS_WIN // note: requires https://invent.kde.org/frameworks/kiconthemes/-/merge_requests/109 qputenv("QT_QPA_PLATFORM", "windows:darkmode=2"); #endif KleopatraApplication app(argc, argv); // Set OrganizationDomain early as this is used to generate the service // name that will be registered on the bus. app.setOrganizationDomain(QStringLiteral("kde.org")); STARTUP_TIMING << "Application created"; /* Create the unique service ASAP to prevent double starts if * the application is started twice very quickly. */ KUniqueService service; QObject::connect(&service, &KUniqueService::activateRequested, &app, &KleopatraApplication::slotActivateRequested); QObject::connect(&app, &KleopatraApplication::setExitValue, &service, [&service](int i) { service.setExitValue(i); }); STARTUP_TIMING << "Service created"; KCrash::initialize(); QAccessible::installFactory(Kleo::accessibleWidgetFactory); qCDebug(KLEOPATRA_LOG) << "Application created"; app.setWindowIcon(QIcon::fromTheme(QStringLiteral("kleopatra"), app.windowIcon())); KLocalizedString::setApplicationDomain(QByteArrayLiteral("kleopatra")); // Initialize GpgME { const GpgME::Error gpgmeInitError = GpgME::initializeLibrary(0); if (gpgmeInitError) { KMessageBox::error(nullptr, xi18nc("@info", "The version of the GpgME library you are running against " "is older than the one that the GpgME++ library was built against." "Kleopatra will not function in this setting." "Please ask your administrator for help in resolving this issue."), i18nc("@title", "GpgME Too Old")); return EXIT_FAILURE; } STARTUP_TIMING << "GPGME Initialized"; } AboutData aboutData; KAboutData::setApplicationData(aboutData); if (Kleo::userIsElevated()) { /* This is a safeguard against bugreports that something fails because * of permission problems on windows. Some users still have the Windows * Vista behavior of running things as Administrator. This can break * GnuPG in horrible ways for example if a stale lockfile is left that * can't be removed without another elevation. * * Note: This is not the same as running as root on Linux. Elevated means * that you are temporarily running with the "normal" user environment but * with elevated permissions. * */ if (KMessageBox::warningContinueCancel(nullptr, xi18nc("@info", "Kleopatra cannot be run as adminstrator without " "breaking file permissions in the GnuPG data folder." "To manage keys for other users please manage them as a normal user and " "copy the AppData\\Roaming\\gnupg directory with proper permissions.") + xi18n("Are you sure that you want to continue?"), i18nc("@title", "Running as Administrator")) != KMessageBox::Continue) { return EXIT_FAILURE; } qCWarning(KLEOPATRA_LOG) << "User is running with administrative permissions."; } // Delay init after KUniqueservice call as this might already // have terminated us and so we can avoid overhead (e.g. keycache // setup / systray icon). app.init(); STARTUP_TIMING << "Application initialized"; QCommandLineParser parser; aboutData.setupCommandLine(&parser); kleopatra_options(&parser); parser.process(QApplication::arguments()); aboutData.processCommandLine(&parser); { const unsigned int threads = QThreadPool::globalInstance()->maxThreadCount(); QThreadPool::globalInstance()->setMaxThreadCount(qMax(2U, threads)); } Kleo::ChecksumDefinition::setInstallPath(Kleo::gpg4winInstallPath()); Kleo::ArchiveDefinition::setInstallPath(Kleo::gnupgInstallPath()); #ifndef DISABLE_UISERVER int rc; Kleo::UiServer *server = nullptr; try { server = new Kleo::UiServer(parser.value(QStringLiteral("uiserver-socket"))); STARTUP_TIMING << "UiServer created"; QObject::connect(server, &Kleo::UiServer::startKeyManagerRequested, &app, &KleopatraApplication::openOrRaiseMainWindow); QObject::connect(server, &Kleo::UiServer::startConfigDialogRequested, &app, &KleopatraApplication::openOrRaiseConfigDialog); #define REGISTER(Command) server->registerCommandFactory(std::shared_ptr(new Kleo::GenericAssuanCommandFactory)) REGISTER(CreateChecksumsCommand); REGISTER(DecryptCommand); REGISTER(DecryptFilesCommand); REGISTER(DecryptVerifyFilesCommand); REGISTER(EchoCommand); REGISTER(EncryptCommand); REGISTER(EncryptFilesCommand); REGISTER(EncryptSignFilesCommand); REGISTER(ImportFilesCommand); REGISTER(PrepEncryptCommand); REGISTER(PrepSignCommand); REGISTER(SelectCertificateCommand); REGISTER(SignCommand); REGISTER(SignEncryptFilesCommand); REGISTER(SignFilesCommand); REGISTER(VerifyChecksumsCommand); REGISTER(VerifyCommand); REGISTER(VerifyFilesCommand); #undef REGISTER server->start(); STARTUP_TIMING << "UiServer started"; } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Failed to start UI Server: " << e.what(); #ifdef Q_OS_WIN // We should probably change the UIServer to be only run on Windows at all because // only the Windows Explorer Plugin uses it. But the plan of GnuPG devs as of 2022 is to // change the Windows Explorer Plugin to use the command line and then remove the // UiServer for everyone. QMessageBox::information(nullptr, i18n("GPG UI Server Error"), i18nc("This error message is only shown on Windows when the socket to communicate with " "Windows Explorer could not be created. This often times means that the whole installation is " "buggy. e.g. GnuPG is not installed at all.", "The Kleopatra Windows Explorer Module could not be initialized.
    " "The error given was: %1
    " "This likely means that there is a problem with your installation. Try reinstalling or " "contact your Administrator for support.
    " "You can try to continue to use Kleopatra but there might be other problems.
    ", QString::fromUtf8(e.what()).toHtmlEscaped())); #endif } #endif // DISABLE_UISERVER const bool daemon = parser.isSet(QStringLiteral("daemon")); if (!daemon && app.isSessionRestored()) { app.restoreMainWindow(); } if (!selfCheck()) { return EXIT_FAILURE; } STARTUP_TIMING << "SelfCheck completed"; if (server) { fillKeyCache(server); } #ifndef QT_NO_SYSTEMTRAYICON app.startMonitoringSmartCard(); #endif app.setIgnoreNewInstance(false); if (!daemon) { const QString err = app.newInstance(parser); if (!err.isEmpty()) { std::cerr << i18n("Invalid arguments: %1", err).toLocal8Bit().constData() << "\n"; return EXIT_FAILURE; } STARTUP_TIMING << "new instance created"; } rc = app.exec(); app.setIgnoreNewInstance(true); QObject::disconnect(server, &Kleo::UiServer::startKeyManagerRequested, &app, &KleopatraApplication::openOrRaiseMainWindow); QObject::disconnect(server, &Kleo::UiServer::startConfigDialogRequested, &app, &KleopatraApplication::openOrRaiseConfigDialog); if (server) { server->stop(); server->waitForStopped(); delete server; } return rc; } diff --git a/src/selftest/gpgagentcheck.cpp b/src/selftest/gpgagentcheck.cpp index 57a84be16..b80bf41ad 100644 --- a/src/selftest/gpgagentcheck.cpp +++ b/src/selftest/gpgagentcheck.cpp @@ -1,96 +1,94 @@ /* -*- mode: c++; c-basic-offset:4 -*- selftest/gpgagentcheck.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "gpgagentcheck.h" #include "implementation_p.h" #include #include -#include // for Qt::escape - #include using namespace Kleo; using namespace Kleo::_detail; using namespace GpgME; namespace { class GpgAgentCheck : public SelfTestImplementation { public: explicit GpgAgentCheck() : SelfTestImplementation(i18nc("@title", "Gpg-Agent Connectivity")) { runTest(); } void runTest() { m_skipped = true; if (!hasFeature(AssuanEngineFeature, 0)) { m_error = i18n("GpgME library too old"); m_explanation = i18nc("@info", "Either the GpgME library itself is too old, " "or the GpgME++ library was compiled against " "an older GpgME that did not support connecting to gpg-agent."); m_proposedFix = xi18nc("@info", "Upgrade to gpgme 1.2.0 or higher, " "and ensure that gpgme++ was compiled against it."); } else if (ensureEngineVersion(GpgME::GpgConfEngine, 2, 1, 0)) { // 2.1 starts the agent on demand and requires it. So for 2.1.0 we can assume // autostart works and we don't need to care about the agent. m_skipped = false; m_passed = true; return; } else { Error error; const std::unique_ptr ctx = Context::createForEngine(AssuanEngine, &error); if (!ctx.get()) { m_error = i18n("GpgME does not support gpg-agent"); m_explanation = xi18nc("@info", "The GpgME library is new " "enough to support gpg-agent, " "but does not seem to do so in this installation." "The error returned was: %1.", Formatting::errorAsString(error).toHtmlEscaped()); // PENDING(marc) proposed fix? } else { m_skipped = false; const Error error = ctx->assuanTransact("GETINFO version"); if (error) { m_passed = false; m_error = i18n("unexpected error"); m_explanation = xi18nc("@info", "Unexpected error while asking gpg-agent " "for its version." "The error returned was: %1.", Formatting::errorAsString(error).toHtmlEscaped()); // PENDING(marc) proposed fix? } else { m_passed = true; } } } } }; } std::shared_ptr Kleo::makeGpgAgentConnectivitySelfTest() { return std::shared_ptr(new GpgAgentCheck); } diff --git a/src/selftest/uiservercheck.cpp b/src/selftest/uiservercheck.cpp index 866206976..dcc1d0249 100644 --- a/src/selftest/uiservercheck.cpp +++ b/src/selftest/uiservercheck.cpp @@ -1,72 +1,71 @@ /* -*- mode: c++; c-basic-offset:4 -*- selftest/uiservercheck.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "uiservercheck.h" #include "implementation_p.h" #include #include #include -#include // for Qt::escape #include using namespace Kleo; using namespace Kleo::_detail; namespace { class UiServerCheck : public SelfTestImplementation { public: explicit UiServerCheck() : SelfTestImplementation(i18nc("@title", "UiServer Connectivity")) { runTest(); } void runTest() { KleopatraClientCopy::Command command; { QEventLoop loop; loop.connect(&command, SIGNAL(finished()), SLOT(quit())); QMetaObject::invokeMethod(&command, "start", Qt::QueuedConnection); loop.exec(); } if (command.error()) { m_passed = false; m_error = i18n("not reachable"); m_explanation = xi18nc("@info", "Could not connect to UiServer: %1", command.errorString().toHtmlEscaped()); m_proposedFix = xi18nc("@info", "Check that your firewall is not set to block local connections " "(allow connections to localhost or 127.0.0.1)."); } else if (command.serverPid() != QCoreApplication::applicationPid()) { m_passed = false; m_error = i18n("multiple instances"); m_explanation = xi18nc("@info", "It seems another Kleopatra is running (with process-id %1)", command.serverPid()); m_proposedFix = xi18nc("@info", "Quit any other running instances of Kleopatra."); } else { m_passed = true; } } }; } std::shared_ptr Kleo::makeUiServerConnectivitySelfTest() { return std::shared_ptr(new UiServerCheck); }