diff --git a/src/commands/importcertificatescommand.cpp b/src/commands/importcertificatescommand.cpp index c5ff0150..ddc9c4b2 100644 --- a/src/commands/importcertificatescommand.cpp +++ b/src/commands/importcertificatescommand.cpp @@ -1,704 +1,704 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/importcertificatescommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007, 2008 Klarälvdalens Datakonsult AB Copyright (c) 2016 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #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 // for Qt::escape #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace QGpgME; namespace { make_comparator_str(ByImportFingerprint, .fingerprint()); class ImportResultProxyModel : public AbstractKeyListSortFilterProxyModel { Q_OBJECT public: ImportResultProxyModel(const std::vector &results, const QStringList &ids, QObject *parent = nullptr) : AbstractKeyListSortFilterProxyModel(parent) { updateFindCache(results, ids); } ~ImportResultProxyModel() override {} ImportResultProxyModel *clone() const override { // compiler-generated copy ctor is fine! return new ImportResultProxyModel(*this); } void setImportResults(const std::vector &results, const QStringList &ids) { updateFindCache(results, ids); 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(FingerprintRole).toString(); // find information: const std::vector::const_iterator it = qBinaryFind(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(FingerprintRole).toString(); return std::binary_search(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), fpr.toLatin1().constData(), ByImportFingerprint()); } private: void updateFindCache(const std::vector &results, const QStringList &ids) { Q_ASSERT(results.size() == static_cast(ids.size())); m_importsByFingerprint.clear(); m_idsByFingerprint.clear(); m_results = results; for (unsigned int i = 0, end = results.size(); i != end; ++i) { const std::vector imports = results[i].imports(); m_importsByFingerprint.insert(m_importsByFingerprint.end(), imports.begin(), imports.end()); const QString &id = ids[i]; for (std::vector::const_iterator it = imports.begin(), end = imports.end(); it != end; ++it) { m_idsByFingerprint[it->fingerprint()].insert(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; }; } ImportCertificatesCommand::Private::Private(ImportCertificatesCommand *qq, KeyListController *c) : Command::Private(qq, c), waitForMoreJobs(false), containedExternalCMSCerts(false), nonWorkingProtocols(), idsByJob(), jobs(), results(), ids() { } ImportCertificatesCommand::Private::~Private() {} #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() {} static QString format_ids(const QStringList &ids) { QStringList escapedIds; for (const QString &id : ids) { if (!id.isEmpty()) { escapedIds << id.toHtmlEscaped(); } } return escapedIds.join(QStringLiteral("
")); } static QString make_tooltip(const QStringList &ids) { if (ids.empty()) { return QString(); } if (ids.size() == 1) if (ids.front().isEmpty()) { return QString(); } 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, const QStringList &ids) { if (std::none_of(results.cbegin(), results.cend(), std::mem_fn(&ImportResult::numConsidered))) { return; } q->addTemporaryView(i18nc("@title:tab", "Imported Certificates"), new ImportResultProxyModel(results, ids), make_tooltip(ids)); 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 &res, const QString &id = QString()) { const KLocalizedString normalLine = ki18n("%1%2"); const KLocalizedString boldLine = ki18n("%1%2"); const KLocalizedString headerLine = ki18n("%1"); #define SUM( x ) sum( res, &ImportResult::x ) QStringList lines; if (!id.isEmpty()) { lines.push_back(headerLine.subs(id).toString()); } lines.push_back(normalLine.subs(i18n("Total number processed:")) .subs(SUM(numConsidered)).toString()); lines.push_back(normalLine.subs(i18n("Imported:")) .subs(SUM(numImported)).toString()); if (const int n = SUM(newSignatures)) lines.push_back(normalLine.subs(i18n("New signatures:")) .subs(n).toString()); if (const int n = SUM(newUserIDs)) lines.push_back(normalLine.subs(i18n("New user IDs:")) .subs(n).toString()); if (const int n = SUM(numKeysWithoutUserID)) lines.push_back(normalLine.subs(i18n("Certificates without user IDs:")) .subs(n).toString()); if (const int n = SUM(newSubkeys)) lines.push_back(normalLine.subs(i18n("New subkeys:")) .subs(n).toString()); if (const int n = SUM(newRevocations)) lines.push_back(boldLine.subs(i18n("Newly revoked:")) .subs(n).toString()); if (const int n = SUM(notImported)) lines.push_back(boldLine.subs(i18n("Not imported:")) .subs(n).toString()); if (const int n = SUM(numUnchanged)) lines.push_back(normalLine.subs(i18n("Unchanged:")) .subs(n).toString()); if (const int n = SUM(numSecretKeysConsidered)) lines.push_back(normalLine.subs(i18n("Secret keys processed:")) .subs(n).toString()); if (const int n = SUM(numSecretKeysImported)) lines.push_back(normalLine.subs(i18n("Secret keys imported:")) .subs(n).toString()); if (const int n = SUM(numSecretKeysConsidered) - SUM(numSecretKeysImported) - SUM(numSecretKeysUnchanged)) if (n > 0) lines.push_back(boldLine.subs(i18n("Secret keys not imported:")) .subs(n).toString()); if (const int n = SUM(numSecretKeysUnchanged)) lines.push_back(normalLine.subs(i18n("Secret keys unchanged:")) .subs(n).toString()); #if GPGMEPP_VERSION > 0x10A00 // 1.10.0 if (const int n = SUM(numV3KeysSkipped)) lines.push_back(normalLine.subs(i18n("Deprecated PGP-2 keys skipped:")) .subs(n).toString()); #endif #undef sum return lines.join(QString()); } static QString make_message_report(const std::vector &res, const QStringList &ids) { Q_ASSERT(res.size() == static_cast(ids.size())); if (res.empty()) { return i18n("No imports (should not happen, please report a bug)."); } if (res.size() == 1) return ids.front().isEmpty() ? i18n("

Detailed results of certificate import:

" "%1
", make_report(res)) : i18n("

Detailed results of importing %1:

" "%2
", ids.front(), make_report(res)); return i18n("

Detailed results of certificate import:

" "%1
", make_report(res, i18n("Totals"))); } // 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; } 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."); auto sel = KMessageBox::questionYesNo(parentWidgetOrView(), 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("
      "))) + QStringLiteral("
  • ") + i18n("Do you wish to start this process now?"), i18nc("@title", "You have imported a new certificate (public key)"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("CertifyQuestion")); if (sel == KMessageBox::Yes) { QEventLoop loop; auto cmd = new Commands::CertifyCertificateCommand(key); cmd->setParentWidget(parentWidgetOrView()); loop.connect(cmd, SIGNAL(finished()), SLOT(quit())); QMetaObject::invokeMethod(cmd, &Commands::CertifyCertificateCommand::start, Qt::QueuedConnection); loop.exec(); } return true; } void ImportCertificatesCommand::Private::showDetails(QWidget *parent, const std::vector &res, const QStringList &ids) { if (res.size() == 1 && res[0].numImported() == 1 && res[0].imports().size() == 1) { if (showPleaseCertify(res[0].imports()[0])) { return; } } setImportResultProxyModel(res, ids); KMessageBox::information(parent, make_message_report(res, ids), i18n("Certificate Import Result")); } void ImportCertificatesCommand::Private::showDetails(const std::vector &res, const QStringList &ids) { showDetails(parentWidgetOrView(), res, ids); } 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; tryToFinish(); } void ImportCertificatesCommand::Private::importResult(const ImportResult &result) { jobs.erase(std::remove(jobs.begin(), jobs.end(), q->sender()), jobs.end()); importResult(result, idsByJob[q->sender()]); } void ImportCertificatesCommand::Private::importResult(const ImportResult &result, const QString &id) { results.push_back(result); ids.push_back(id); tryToFinish(); } static void handleOwnerTrust(const std::vector &results) { //iterate over all imported certificates for (const ImportResult &result : results) { //when a new certificate got a secret key if (result.numSecretKeysImported() >= 1) { const char *fingerPr = 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; } QStringList uids; uids.reserve(toTrustOwner.userIDs().size()); Q_FOREACH (const UserID &uid, toTrustOwner.userIDs()) { uids << 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.join(QStringLiteral(""))); int k = KMessageBox::questionYesNo(nullptr, str, i18nc("@title:window", "Secret key imported")); if (k == KMessageBox::Yes) { //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); } } } } void ImportCertificatesCommand::Private::handleExternalCMSImports() { QStringList fingerprints; // 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. Q_FOREACH (const ImportResult &result, results) { Q_FOREACH (const Import &import, result.imports()) { if (!import.fingerprint()) { continue; } fingerprints << QString::fromLatin1(import.fingerprint()); } } auto job = QGpgME::smime()->keyListJob(false, true, true); // Old connect here because of Windows. connect(job, SIGNAL(result(GpgME::KeyListResult,std::vector,QString,GpgME::Error)), q, SLOT(keyListDone(GpgME::KeyListResult,std::vector,QString,GpgME::Error))); job->start(fingerprints, false); } void ImportCertificatesCommand::Private::keyListDone(const GpgME::KeyListResult &, const std::vector &keys, - const QString &, GpgME::Error) + const QString &, const GpgME::Error&) { KeyCache::mutableInstance()->refresh(keys); showDetails(results, ids); 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; } if (std::any_of(results.cbegin(), results.cend(), [](const GpgME::ImportResult &result) { return result.error().code(); })) { setImportResultProxyModel(results, ids); if (std::all_of(results.cbegin(), results.cend(), [](const GpgME::ImportResult &result) { return result.error().isCanceled(); })) { Q_EMIT q->canceled(); } else { for (unsigned int i = 0, end = results.size(); i != end; ++i) if (const Error err = results[i].error()) { showError(err, ids[i]); } } } else { if (containedExternalCMSCerts) { handleExternalCMSImports(); // We emit finished and do show details // after the keylisting. return; } else { handleOwnerTrust(results); } showDetails(results, ids); } finished(); } 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) { 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(ImportResult(), id); return; } connect(job.get(), SIGNAL(result(GpgME::ImportResult)), q, SLOT(importResult(GpgME::ImportResult))); connect(job.get(), &Job::progress, q, &Command::progress); const GpgME::Error err = job->start(data); if (err.code()) { importResult(ImportResult(err), id); } else { jobs.push_back(job.release()); idsByJob[jobs.back()] = id; } } 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(ImportResult(), id); return; } if (protocol == GpgME::CMS) { containedExternalCMSCerts = true; } connect(job.get(), SIGNAL(result(GpgME::ImportResult)), q, SLOT(importResult(GpgME::ImportResult))); connect(job.get(), &Job::progress, q, &Command::progress); const GpgME::Error err = job->start(keys); if (err.code()) { importResult(ImportResult(err), id); } else { jobs.push_back(job.release()); idsByJob[jobs.back()] = id; } } void ImportCertificatesCommand::doCancel() { std::for_each(d->jobs.begin(), d->jobs.end(), [](Job *job) { job->slotCancel(); }); } #undef d #undef q #include "moc_importcertificatescommand.cpp" #include "importcertificatescommand.moc" diff --git a/src/commands/importcertificatescommand_p.h b/src/commands/importcertificatescommand_p.h index 1e0e04b6..a7eda63b 100644 --- a/src/commands/importcertificatescommand_p.h +++ b/src/commands/importcertificatescommand_p.h @@ -1,117 +1,117 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/importcertificatescommand_p.h This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007, 2008 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef __KLEOPATRA_IMPORTCERTIFICATESCOMMAND_P_H__ #define __KLEOPATRA_IMPORTCERTIFICATESCOMMAND_P_H__ #include "command_p.h" #include "importcertificatescommand.h" #include #include #include namespace GpgME { class ImportResult; class Import; class KeyListResult; class Error; } namespace QGpgME { class AbstractImportJob; } class QByteArray; class Kleo::ImportCertificatesCommand::Private : public Command::Private { friend class ::Kleo::ImportCertificatesCommand; Kleo::ImportCertificatesCommand *q_func() const { return static_cast(q); } public: explicit Private(ImportCertificatesCommand *qq, KeyListController *c); ~Private(); void setWaitForMoreJobs(bool waiting); void startImport(GpgME::Protocol proto, const QByteArray &data, const QString &id = QString()); void startImport(GpgME::Protocol proto, const std::vector &keys, const QString &id = QString()); void importResult(const GpgME::ImportResult &); void importResult(const GpgME::ImportResult &, const QString &); void showError(QWidget *parent, const GpgME::Error &error, const QString &id = QString()); void showError(const GpgME::Error &error, const QString &id = QString()); void showDetails(QWidget *parent, const std::vector &results, const QStringList &ids); void showDetails(const std::vector &results, const QStringList &ids); void setImportResultProxyModel(const std::vector &results, const QStringList &ids); bool showPleaseCertify(const GpgME::Import &imp); void keyListDone(const GpgME::KeyListResult &result, const std::vector &keys, - const QString &, GpgME::Error); + const QString &, const GpgME::Error&); private: void handleExternalCMSImports(); void tryToFinish(); private: bool waitForMoreJobs; bool containedExternalCMSCerts; std::vector nonWorkingProtocols; std::map idsByJob; std::vector jobs; std::vector results; QStringList ids; }; inline Kleo::ImportCertificatesCommand::Private *Kleo::ImportCertificatesCommand::d_func() { return static_cast(d.get()); } inline const Kleo::ImportCertificatesCommand::Private *Kleo::ImportCertificatesCommand::d_func() const { return static_cast(d.get()); } inline Kleo::ImportCertificatesCommand::ImportCertificatesCommand(Private *pp) : Command(pp) {} inline Kleo::ImportCertificatesCommand::ImportCertificatesCommand(QAbstractItemView *v, Private *pp) : Command(v, pp) {} #endif /* __KLEOPATRA_IMPORTCERTIFICATESCOMMAND_P_H__ */ diff --git a/src/commands/importpaperkeycommand.cpp b/src/commands/importpaperkeycommand.cpp index e43b9c64..6b1c8ec2 100644 --- a/src/commands/importpaperkeycommand.cpp +++ b/src/commands/importpaperkeycommand.cpp @@ -1,247 +1,247 @@ /* commands/importperkeycommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "importpaperkeycommand.h" #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include "command_p.h" using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; ImportPaperKeyCommand::ImportPaperKeyCommand(const GpgME::Key &k) : GnuPGProcessCommand(k) { } QStringList ImportPaperKeyCommand::arguments() const { const Key key = d->key(); QStringList result; result << paperKeyInstallPath() << QStringLiteral("--pubring") << mTmpDir.path() + QStringLiteral("/pubkey.gpg") << QStringLiteral("--secrets") << mTmpDir.path() + QStringLiteral("/secrets.txt") << QStringLiteral("--output") << mTmpDir.path() + QStringLiteral("/seckey.gpg"); return result; } void ImportPaperKeyCommand::exportResult(const GpgME::Error &err, const QByteArray &data) { if (err) { d->error(QString::fromUtf8(err.asString()), errorCaption()); d->finished(); return; } if (!mTmpDir.isValid()) { // Should not happen so no i18n d->error(QStringLiteral("Failed to get temporary directory"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to get temporary dir"; d->finished(); return; } const QString fileName = mTmpDir.path() + QStringLiteral("/pubkey.gpg"); QFile f(fileName); if (!f.open(QIODevice::WriteOnly)) { d->error(QStringLiteral("Failed to create temporary file"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file"; d->finished(); return; } f.write(data); f.close(); // Copy and sanitize input a bit QFile input(mFileName); if (!input.open(QIODevice::ReadOnly)) { d->error(xi18n("Cannot open %1 for reading.", mFileName), errorCaption()); d->finished(); return; } const QString outName = mTmpDir.path() + QStringLiteral("/secrets.txt"); QFile out(outName); if (!out.open(QIODevice::WriteOnly)) { // Should not happen d->error(QStringLiteral("Failed to create temporary file"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file for writing"; d->finished(); return; } QTextStream in(&input); while (!in.atEnd()) { // Paperkey is picky, tabs may not be part. Neither may be empty lines. const QString line = in.readLine().trimmed().replace(QLatin1Char('\t'), QStringLiteral(" ")) + QLatin1Char('\n'); out.write(line.toUtf8()); } input.close(); out.close(); GnuPGProcessCommand::doStart(); } void ImportPaperKeyCommand::postSuccessHook(QWidget *) { qCDebug(KLEOPATRA_LOG) << "Paperkey secrets restore finished successfully."; QFile secKey(mTmpDir.path() + QStringLiteral("/seckey.gpg")); if (!secKey.open(QIODevice::ReadOnly)) { d->error(QStringLiteral("Failed to open temporary secret"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file"; finished(); return; } auto data = secKey.readAll(); secKey.close(); auto importjob = QGpgME::openpgp()->importJob(); auto result = importjob->exec(data); delete importjob; if (result.error()) { d->error(QString::fromUtf8(result.error().asString()), errorCaption()); finished(); return; } if (!result.numSecretKeysImported() || (result.numSecretKeysUnchanged() == result.numSecretKeysImported())) { d->error(i18n("Failed to restore any secret keys."), errorCaption()); finished(); return; } // Refresh the key after success KeyCache::mutableInstance()->reload(OpenPGP); finished(); d->information(xi18nc("@info", "Successfully restored the secret key parts from %1", mFileName)); return; } void ImportPaperKeyCommand::doStart() { if (paperKeyInstallPath().isNull()) { KMessageBox::sorry(d->parentWidgetOrView(), xi18nc("@info", "Kleopatra uses " "PaperKey to import your " "text backup." "Please make sure it is installed."), i18nc("@title", "Failed to find PaperKey executable.")); return; } mFileName = QFileDialog::getOpenFileName(d->parentWidgetOrView(), i18n("Select input file"), QString(), QStringLiteral("%1 (*.txt)").arg(i18n("Paper backup")) #ifdef Q_OS_WIN /* For whatever reason at least with Qt 5.6.1 the native file dialog crashes in * my (aheinecke) Windows 10 environment when invoked here. * In other places it works, with the same arguments as in other places (e.g. import) * it works. But not here. Maybe it's our (gpg4win) build? But why did it only * crash here? * * It does not crash immediately, the program flow continues for a while before it * crashes so this is hard to debug. * * There are some reports about this * QTBUG-33119 QTBUG-41416 where different people describe "bugs" but they * describe them differently also not really reproducible. * Anyway this works for now and for such an exotic feature its good enough for now. */ , 0, QFileDialog::DontUseNativeDialog #endif ); if (mFileName.isEmpty()) { d->finished(); return; } auto exportJob = QGpgME::openpgp()->publicKeyExportJob(); // Do not change to new style connect without testing on // Windows / mingw first for compatibility please. - connect(exportJob, SIGNAL(result(GpgME::Error,QByteArray)), - this, SLOT(exportResult(GpgME::Error,QByteArray))); + connect(exportJob, &QGpgME::ExportJob::result, + this, &ImportPaperKeyCommand::exportResult); exportJob->start(QStringList() << QLatin1String(d->key().primaryFingerprint())); } QString ImportPaperKeyCommand::errorCaption() const { return i18nc("@title:window", "Error importing secret key"); } QString ImportPaperKeyCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "The GPG process that tried to restore the secret key " "ended prematurely because of an unexpected error." "Please check the output of %1 for details.", args.join(QLatin1Char(' '))); } QString ImportPaperKeyCommand::errorExitMessage(const QStringList &args) const { return xi18nc("@info", "An error occurred while trying to restore the secret key. " "The output from %1 was:" "%2", args[0], errorString()); } QString ImportPaperKeyCommand::successMessage(const QStringList &) const { return QString(); } diff --git a/src/crypto/gui/decryptverifyfileswizard.cpp b/src/crypto/gui/decryptverifyfileswizard.cpp index 2f936d02..2e514cf9 100644 --- a/src/crypto/gui/decryptverifyfileswizard.cpp +++ b/src/crypto/gui/decryptverifyfileswizard.cpp @@ -1,276 +1,276 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/decryptverifywizard.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "decryptverifyfileswizard.h" #include "decryptverifyoperationwidget.h" #include #include #include #include #include #include #include #include "Libkleo/FileNameRequester" #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; namespace { class HLine : public QFrame { Q_OBJECT public: - explicit HLine(QWidget *p = nullptr, Qt::WindowFlags f = 0) + explicit HLine(QWidget *p = nullptr, Qt::WindowFlags f = nullptr) : QFrame(p, f) { setFrameStyle(QFrame::HLine | QFrame::Sunken); } }; class OperationsWidget : public WizardPage { Q_OBJECT public: explicit OperationsWidget(QWidget *p = nullptr); ~OperationsWidget() override; void setOutputDirectory(const QString &dir) { m_ui.outputDirectoryFNR.setFileName(dir); } QString outputDirectory() const { return m_ui.outputDirectoryFNR.fileName(); } bool useOutputDirectory() const { return m_ui.useOutputDirectoryCB.isChecked(); } void ensureIndexAvailable(unsigned int idx); DecryptVerifyOperationWidget *widget(unsigned int idx) { return m_widgets.at(idx); } bool isComplete() const override { return true; } private: std::vector m_widgets; struct UI { QCheckBox useOutputDirectoryCB; QLabel outputDirectoryLB; FileNameRequester outputDirectoryFNR; ScrollArea scrollArea; // ### replace with KDScrollArea when done QVBoxLayout vlay; QHBoxLayout hlay; explicit UI(OperationsWidget *q); } m_ui; }; } class DecryptVerifyFilesWizard::Private { friend class ::Kleo::Crypto::Gui::DecryptVerifyFilesWizard; DecryptVerifyFilesWizard *const q; public: Private(DecryptVerifyFilesWizard *qq); ~Private(); void ensureIndexAvailable(unsigned int idx) { operationsPage.ensureIndexAvailable(idx); } private: OperationsWidget operationsPage; Gui::ResultPage resultPage; }; DecryptVerifyFilesWizard::DecryptVerifyFilesWizard(QWidget *p, Qt::WindowFlags f) : Wizard(p, f), d(new Private(this)) { } DecryptVerifyFilesWizard::~DecryptVerifyFilesWizard() {} void DecryptVerifyFilesWizard::setOutputDirectory(const QString &dir) { d->operationsPage.setOutputDirectory(dir); } QString DecryptVerifyFilesWizard::outputDirectory() const { return d->operationsPage.outputDirectory(); } bool DecryptVerifyFilesWizard::useOutputDirectory() const { return d->operationsPage.useOutputDirectory(); } DecryptVerifyOperationWidget *DecryptVerifyFilesWizard::operationWidget(unsigned int idx) { d->ensureIndexAvailable(idx); return d->operationsPage.widget(idx); } void DecryptVerifyFilesWizard::onNext(int id) { if (id == OperationsPage) { QTimer::singleShot(0, this, &DecryptVerifyFilesWizard::operationPrepared); } Wizard::onNext(id); } void DecryptVerifyFilesWizard::setTaskCollection(const std::shared_ptr &coll) { kleo_assert(coll); d->resultPage.setTaskCollection(coll); } DecryptVerifyFilesWizard::Private::Private(DecryptVerifyFilesWizard *qq) : q(qq), operationsPage(q), resultPage(q) { q->setPage(DecryptVerifyFilesWizard::OperationsPage, &operationsPage); q->setPage(DecryptVerifyFilesWizard::ResultPage, &resultPage); std::vector order; order.push_back(DecryptVerifyFilesWizard::OperationsPage); order.push_back(DecryptVerifyFilesWizard::ResultPage); q->setPageOrder(order); operationsPage.setCommitPage(true); } DecryptVerifyFilesWizard::Private::~Private() {} OperationsWidget::OperationsWidget(QWidget *p) : WizardPage(p), m_widgets(), m_ui(this) { setTitle(i18n("Choose operations to be performed")); setSubTitle(i18n("Here you can check and, if needed, override " "the operations Kleopatra detected for the input given.")); setCommitPage(true); setCustomNextButton(KGuiItem(i18n("&Decrypt/Verify"))); } OperationsWidget::~OperationsWidget() {} OperationsWidget::UI::UI(OperationsWidget *q) : useOutputDirectoryCB(i18n("Create all output files in a single folder"), q), outputDirectoryLB(i18n("&Output folder:"), q), outputDirectoryFNR(q), scrollArea(q), vlay(q), hlay() { KDAB_SET_OBJECT_NAME(useOutputDirectoryCB); KDAB_SET_OBJECT_NAME(outputDirectoryLB); KDAB_SET_OBJECT_NAME(outputDirectoryFNR); KDAB_SET_OBJECT_NAME(scrollArea); KDAB_SET_OBJECT_NAME(vlay); KDAB_SET_OBJECT_NAME(hlay); outputDirectoryFNR.setFilter(QDir::Dirs); useOutputDirectoryCB.setChecked(true); connect(&useOutputDirectoryCB, &QCheckBox::toggled, &outputDirectoryLB, &QLabel::setEnabled); connect(&useOutputDirectoryCB, &QCheckBox::toggled, &outputDirectoryFNR, &FileNameRequester::setEnabled); Q_ASSERT(qobject_cast(scrollArea.widget()->layout())); static_cast(scrollArea.widget()->layout())->addStretch(1); outputDirectoryLB.setBuddy(&outputDirectoryFNR); hlay.setMargin(0); vlay.addWidget(&scrollArea, 1); vlay.addWidget(&useOutputDirectoryCB); vlay.addLayout(&hlay); hlay.addWidget(&outputDirectoryLB); hlay.addWidget(&outputDirectoryFNR); } void OperationsWidget::ensureIndexAvailable(unsigned int idx) { if (idx < m_widgets.size()) { return; } Q_ASSERT(m_ui.scrollArea.widget()); Q_ASSERT(qobject_cast(m_ui.scrollArea.widget()->layout())); QBoxLayout &blay = *static_cast(m_ui.scrollArea.widget()->layout()); for (unsigned int i = m_widgets.size(); i < idx + 1; ++i) { if (i) { blay.insertWidget(blay.count() - 1, new HLine(m_ui.scrollArea.widget())); } DecryptVerifyOperationWidget *w = new DecryptVerifyOperationWidget(m_ui.scrollArea.widget()); blay.insertWidget(blay.count() - 1, w); w->show(); m_widgets.push_back(w); } } #include "decryptverifyfileswizard.moc" diff --git a/src/crypto/gui/resultitemwidget.cpp b/src/crypto/gui/resultitemwidget.cpp index 7cc1f413..cbd1a47c 100644 --- a/src/crypto/gui/resultitemwidget.cpp +++ b/src/crypto/gui/resultitemwidget.cpp @@ -1,387 +1,387 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/resultitemwidget.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB 2016 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "resultitemwidget.h" #include "utils/auditlog.h" #include "commands/command.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/lookupcertificatescommand.h" #include "crypto/decryptverifytask.h" #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #if GPGMEPP_VERSION > 0x10B01 // > 1.11.1 # define GPGME_HAS_LEGACY_NOMDC #endif using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; namespace { // TODO move out of here static QColor colorForVisualCode(Task::Result::VisualCode code) { switch (code) { case Task::Result::AllGood: return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color(); case Task::Result::NeutralError: case Task::Result::Warning: return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NormalBackground).color(); case Task::Result::Danger: return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color(); case Task::Result::NeutralSuccess: default: return QColor(0x00, 0x80, 0xFF); // light blue } } static QColor txtColorForVisualCode(Task::Result::VisualCode code) { switch (code) { case Task::Result::AllGood: return KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::PositiveText).color(); case Task::Result::NeutralError: case Task::Result::Warning: return KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::NormalText).color(); case Task::Result::Danger: return KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::NegativeText).color(); case Task::Result::NeutralSuccess: default: return QColor(0xFF, 0xFF, 0xFF); // white } } } class ResultItemWidget::Private { ResultItemWidget *const q; public: explicit Private(const std::shared_ptr &result, ResultItemWidget *qq) : q(qq), m_result(result), m_detailsLabel(nullptr), m_actionsLabel(nullptr), m_closeButton(nullptr), m_importCanceled(false) { Q_ASSERT(m_result); } void slotLinkActivated(const QString &); void updateShowDetailsLabel(); void addKeyImportButton(QBoxLayout *lay, bool search); void addIgnoreMDCButton(QBoxLayout *lay); void oneImportFinished(); const std::shared_ptr m_result; QLabel *m_detailsLabel; QLabel *m_actionsLabel; QPushButton *m_closeButton; bool m_importCanceled; }; void ResultItemWidget::Private::oneImportFinished() { if (m_importCanceled) { return; } if (m_result->parentTask()) { m_result->parentTask()->start(); } q->setVisible(false); } void ResultItemWidget::Private::addIgnoreMDCButton(QBoxLayout *lay) { if (!m_result || !lay) { return; } const auto dvResult = dynamic_cast(m_result.get()); if (!dvResult) { return; } const auto decResult = dvResult->decryptionResult(); #ifdef GPGME_HAS_LEGACY_NOMDC if (decResult.isNull() || !decResult.error() || !decResult.isLegacyCipherNoMDC()) #endif { return; } auto btn = new QPushButton(i18n("Force decryption")); btn->setFixedSize(btn->sizeHint()); connect (btn, &QPushButton::clicked, q, [this] () { if (m_result->parentTask()) { const auto dvTask = dynamic_cast(m_result->parentTask().data()); dvTask->setIgnoreMDCError(true); dvTask->start(); q->setVisible(false); } else { qCWarning(KLEOPATRA_LOG) << "Failed to get parent task"; } }); lay->addWidget(btn); } void ResultItemWidget::Private::addKeyImportButton(QBoxLayout *lay, bool search) { if (!m_result || !lay) { return; } const auto dvResult = dynamic_cast(m_result.get()); if (!dvResult) { return; } const auto verifyResult = dvResult->verificationResult(); if (verifyResult.isNull()) { return; } - for (const auto sig: verifyResult.signatures()) { + for (const auto &sig: verifyResult.signatures()) { if (!(sig.summary() & GpgME::Signature::KeyMissing)) { continue; } auto btn = new QPushButton; QString suffix; const auto keyid = QLatin1String(sig.fingerprint()); if (verifyResult.numSignatures() > 1) { suffix = QLatin1Char(' ') + keyid; } btn = new QPushButton(search ? i18nc("1 is optional keyid. No space is intended as it can be empty.", "Search%1", suffix) : i18nc("1 is optional keyid. No space is intended as it can be empty.", "Import%1", suffix)); if (search) { btn->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); connect (btn, &QPushButton::clicked, q, [this, btn, keyid] () { btn->setEnabled(false); m_importCanceled = false; auto cmd = new Kleo::Commands::LookupCertificatesCommand(keyid, nullptr); connect(cmd, &Kleo::Commands::LookupCertificatesCommand::canceled, q, [this]() { m_importCanceled = true; }); connect(cmd, &Kleo::Commands::LookupCertificatesCommand::finished, q, [this, btn]() { btn->setEnabled(true); oneImportFinished(); }); cmd->setParentWidget(q); cmd->start(); }); } else { btn->setIcon(QIcon::fromTheme(QStringLiteral("view-certificate-import"))); connect (btn, &QPushButton::clicked, q, [this, btn] () { btn->setEnabled(false); m_importCanceled = false; auto cmd = new Kleo::ImportCertificateFromFileCommand(); connect(cmd, &Kleo::ImportCertificateFromFileCommand::canceled, q, [this]() { m_importCanceled = true; }); connect(cmd, &Kleo::ImportCertificateFromFileCommand::finished, q, [this, btn]() { btn->setEnabled(true); oneImportFinished(); }); cmd->setParentWidget(q); cmd->start(); }); } btn->setFixedSize(btn->sizeHint()); lay->addWidget(btn); } } static QUrl auditlog_url_template() { QUrl url(QStringLiteral("kleoresultitem://showauditlog")); return url; } void ResultItemWidget::Private::updateShowDetailsLabel() { if (!m_actionsLabel || !m_detailsLabel) { return; } const auto parentTask = m_result->parentTask(); QString auditLogLink; if (parentTask && parentTask->protocol() == GpgME::OpenPGP) { if (m_result->hasError()) { auditLogLink = m_result->auditLog().formatLink(auditlog_url_template(), i18n("Diagnostics")); } } else { auditLogLink = m_result->auditLog().formatLink(auditlog_url_template()); } m_actionsLabel->setText(auditLogLink); } ResultItemWidget::ResultItemWidget(const std::shared_ptr &result, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), d(new Private(result, this)) { const QColor color = colorForVisualCode(d->m_result->code()); const QColor txtColor = txtColorForVisualCode(d->m_result->code()); const QString styleSheet = QStringLiteral("QFrame,QLabel { background-color: %1; margin: 0px; }" "QFrame#resultFrame{ border-color: %2; border-style: solid; border-radius: 3px; border-width: 1px }" "QLabel { color: %3; padding: 5px; border-radius: 3px }").arg(color.name()).arg(color.darker(150).name()).arg(txtColor.name()); QVBoxLayout *topLayout = new QVBoxLayout(this); QFrame *frame = new QFrame; frame->setObjectName(QStringLiteral("resultFrame")); frame->setStyleSheet(styleSheet); topLayout->addWidget(frame); QHBoxLayout *layout = new QHBoxLayout(frame); QVBoxLayout *vlay = new QVBoxLayout(); QLabel *overview = new QLabel; overview->setWordWrap(true); overview->setTextFormat(Qt::RichText); overview->setText(d->m_result->overview()); overview->setFocusPolicy(Qt::StrongFocus); overview->setStyleSheet(styleSheet); connect(overview, SIGNAL(linkActivated(QString)), this, SLOT(slotLinkActivated(QString))); vlay->addWidget(overview); layout->addLayout(vlay); const QString details = d->m_result->details(); QVBoxLayout *actionLayout = new QVBoxLayout; layout->addLayout(actionLayout); d->addKeyImportButton(actionLayout, false); // TODO: Only show if auto-key-retrieve is not set. d->addKeyImportButton(actionLayout, true); d->addIgnoreMDCButton(actionLayout); d->m_actionsLabel = new QLabel; connect(d->m_actionsLabel, SIGNAL(linkActivated(QString)), this, SLOT(slotLinkActivated(QString))); actionLayout->addWidget(d->m_actionsLabel); d->m_actionsLabel->setFocusPolicy(Qt::StrongFocus); d->m_actionsLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); d->m_actionsLabel->setStyleSheet(styleSheet); d->m_detailsLabel = new QLabel; d->m_detailsLabel->setWordWrap(true); d->m_detailsLabel->setTextFormat(Qt::RichText); d->m_detailsLabel->setText(details); d->m_detailsLabel->setFocusPolicy(Qt::StrongFocus); d->m_detailsLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); d->m_detailsLabel->setStyleSheet(styleSheet); connect(d->m_detailsLabel, SIGNAL(linkActivated(QString)), this, SLOT(slotLinkActivated(QString))); vlay->addWidget(d->m_detailsLabel); d->m_closeButton = new QPushButton; KGuiItem::assign(d->m_closeButton, KStandardGuiItem::close()); d->m_closeButton->setFixedSize(d->m_closeButton->sizeHint()); connect(d->m_closeButton, &QAbstractButton::clicked, this, &ResultItemWidget::closeButtonClicked); actionLayout->addWidget(d->m_closeButton); d->m_closeButton->setVisible(false); layout->setStretch(0, 1); actionLayout->addStretch(-1); vlay->addStretch(-1); d->updateShowDetailsLabel(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum); } ResultItemWidget::~ResultItemWidget() { } void ResultItemWidget::showCloseButton(bool show) { d->m_closeButton->setVisible(show); } bool ResultItemWidget::hasErrorResult() const { return d->m_result->hasError(); } void ResultItemWidget::Private::slotLinkActivated(const QString &link) { Q_ASSERT(m_result); qCDebug(KLEOPATRA_LOG) << "Link activated: " << link; if (link.startsWith(QLatin1String("key:"))) { auto split = link.split(QLatin1Char(':')); auto fpr = split.value(1); if (split.size() == 2 && isFingerprint(fpr)) { /* There might be a security consideration here if somehow * a short keyid is used in a link and it collides with another. * So we additionally check that it really is a fingerprint. */ auto cmd = Command::commandForQuery(fpr); cmd->setParentWId(q->effectiveWinId()); cmd->start(); } else { qCWarning(KLEOPATRA_LOG) << "key link invalid " << link; } return; } const QUrl url(link); if (url.host() == QLatin1String("showauditlog")) { q->showAuditLog(); return; } qCWarning(KLEOPATRA_LOG) << "Unexpected link scheme: " << link; } void ResultItemWidget::showAuditLog() { MessageBox::auditLog(parentWidget(), d->m_result->auditLog().text()); } #include "moc_resultitemwidget.cpp" diff --git a/src/crypto/signencryptfilescontroller.cpp b/src/crypto/signencryptfilescontroller.cpp index db142d56..8e9f067b 100644 --- a/src/crypto/signencryptfilescontroller.cpp +++ b/src/crypto/signencryptfilescontroller.cpp @@ -1,726 +1,726 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signencryptfilescontroller.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007 Klarälvdalens Datakonsult AB 2017 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "signencryptfilescontroller.h" #include "signencrypttask.h" #include "certificateresolver.h" #include "crypto/gui/signencryptfileswizard.h" #include "crypto/taskcollection.h" #include "fileoperationspreferences.h" #include "utils/input.h" #include "utils/output.h" #include "utils/kleo_assert.h" #include "utils/archivedefinition.h" #include "utils/path-helper.h" #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; using namespace KMime::Types; class SignEncryptFilesController::Private { friend class ::Kleo::Crypto::SignEncryptFilesController; SignEncryptFilesController *const q; public: explicit Private(SignEncryptFilesController *qq); ~Private(); private: void slotWizardOperationPrepared(); void slotWizardCanceled(); private: void ensureWizardCreated(); void ensureWizardVisible(); void updateWizardMode(); void cancelAllTasks(); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void schedule(); std::shared_ptr takeRunnable(GpgME::Protocol proto); static void assertValidOperation(unsigned int); static QString titleForOperation(unsigned int op); private: std::vector< std::shared_ptr > runnable, completed; std::shared_ptr cms, openpgp; QPointer wizard; QStringList files; unsigned int operation; Protocol protocol; }; SignEncryptFilesController::Private::Private(SignEncryptFilesController *qq) : q(qq), runnable(), cms(), openpgp(), wizard(), files(), operation(SignAllowed | EncryptAllowed | ArchiveAllowed), protocol(UnknownProtocol) { } SignEncryptFilesController::Private::~Private() { qCDebug(KLEOPATRA_LOG); } QString SignEncryptFilesController::Private::titleForOperation(unsigned int op) { const bool signDisallowed = (op & SignMask) == SignDisallowed; const bool encryptDisallowed = (op & EncryptMask) == EncryptDisallowed; const bool archiveSelected = (op & ArchiveMask) == ArchiveForced; kleo_assert(!signDisallowed || !encryptDisallowed); if (!signDisallowed && encryptDisallowed) { if (archiveSelected) { return i18n("Archive and Sign Files"); } else { return i18n("Sign Files"); } } if (signDisallowed && !encryptDisallowed) { if (archiveSelected) { return i18n("Archive and Encrypt Files"); } else { return i18n("Encrypt Files"); } } if (archiveSelected) { return i18n("Archive and Sign/Encrypt Files"); } else { return i18n("Sign/Encrypt Files"); } } SignEncryptFilesController::SignEncryptFilesController(QObject *p) : Controller(p), d(new Private(this)) { } SignEncryptFilesController::SignEncryptFilesController(const std::shared_ptr &ctx, QObject *p) : Controller(ctx, p), d(new Private(this)) { } SignEncryptFilesController::~SignEncryptFilesController() { qCDebug(KLEOPATRA_LOG); if (d->wizard && !d->wizard->isVisible()) { delete d->wizard; } //d->wizard->close(); ### ? } void SignEncryptFilesController::setProtocol(Protocol proto) { kleo_assert(d->protocol == UnknownProtocol || d->protocol == proto); d->protocol = proto; d->ensureWizardCreated(); } Protocol SignEncryptFilesController::protocol() const { return d->protocol; } // static void SignEncryptFilesController::Private::assertValidOperation(unsigned int op) { kleo_assert((op & SignMask) == SignDisallowed || (op & SignMask) == SignAllowed || (op & SignMask) == SignSelected); kleo_assert((op & EncryptMask) == EncryptDisallowed || (op & EncryptMask) == EncryptAllowed || (op & EncryptMask) == EncryptSelected); kleo_assert((op & ArchiveMask) == ArchiveDisallowed || (op & ArchiveMask) == ArchiveAllowed || (op & ArchiveMask) == ArchiveForced); kleo_assert((op & ~(SignMask | EncryptMask | ArchiveMask)) == 0); } void SignEncryptFilesController::setOperationMode(unsigned int mode) { Private::assertValidOperation(mode); d->operation = mode; d->updateWizardMode(); } void SignEncryptFilesController::Private::updateWizardMode() { if (!wizard) { return; } wizard->setWindowTitle(titleForOperation(operation)); const unsigned int signOp = (operation & SignMask); const unsigned int encrOp = (operation & EncryptMask); const unsigned int archOp = (operation & ArchiveMask); if (signOp == SignDisallowed) { wizard->setSigningUserMutable(false); wizard->setSigningPreset(false); } else { wizard->setSigningUserMutable(true); wizard->setSigningPreset(signOp == SignSelected); } if (encrOp == EncryptDisallowed) { wizard->setEncryptionPreset(false); wizard->setEncryptionUserMutable(false); } else { wizard->setEncryptionUserMutable(true); wizard->setEncryptionPreset(false); wizard->setEncryptionPreset(encrOp == EncryptSelected); } wizard->setArchiveForced(archOp == ArchiveForced); wizard->setArchiveMutable(archOp == ArchiveAllowed); } unsigned int SignEncryptFilesController::operationMode() const { return d->operation; } static const char *extension(bool pgp, bool sign, bool encrypt, bool ascii, bool detached) { unsigned int cls = pgp ? Class::OpenPGP : Class::CMS; if (encrypt) { cls |= Class::CipherText; } else if (sign) { cls |= detached ? Class::DetachedSignature : Class::OpaqueSignature; } cls |= ascii ? Class::Ascii : Class::Binary; const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt(); if (const char *const ext = outputFileExtension(cls, usePGPFileExt)) { return ext; } else { return "out"; } } static std::shared_ptr getDefaultAd() { std::vector > ads = ArchiveDefinition::getArchiveDefinitions(); Q_ASSERT(!ads.empty()); std::shared_ptr ad = ads.front(); const FileOperationsPreferences prefs; Q_FOREACH (const std::shared_ptr toCheck, ads) { if (toCheck->id() == prefs.archiveCommand()) { ad = toCheck; break; } } return ad; } static QMap buildOutputNames(const QStringList &files, const bool archive) { QMap nameMap; // Build the default names for the wizard. QString baseNameCms; QString baseNamePgp; const QFileInfo firstFile(files.first()); if (archive) { QString baseName; baseName = QDir(heuristicBaseDirectory(files)).absoluteFilePath(files.size() > 1 ? i18nc("base name of an archive file, e.g. archive.zip or archive.tar.gz", "archive") : firstFile.baseName()); const auto ad = getDefaultAd(); baseNamePgp = baseName + QLatin1Char('.') + ad->extensions(GpgME::OpenPGP).first() + QLatin1Char('.'); baseNameCms = baseName + QLatin1Char('.') + ad->extensions(GpgME::CMS).first() + QLatin1Char('.'); } else { baseNameCms = baseNamePgp = files.first() + QLatin1Char('.'); } const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); nameMap.insert(SignEncryptFilesWizard::SignatureCMS, baseNameCms + QString::fromLatin1(extension(false, true, false, ascii, true))); nameMap.insert(SignEncryptFilesWizard::EncryptedCMS, baseNameCms + QString::fromLatin1(extension(false, false, true, ascii, false))); nameMap.insert(SignEncryptFilesWizard::CombinedPGP, baseNamePgp + QString::fromLatin1(extension(true, true, true, ascii, false))); nameMap.insert(SignEncryptFilesWizard::EncryptedPGP, baseNamePgp + QString::fromLatin1(extension(true, false, true, ascii, false))); nameMap.insert(SignEncryptFilesWizard::SignaturePGP, baseNamePgp + QString::fromLatin1(extension(true, true, false, ascii, true))); nameMap.insert(SignEncryptFilesWizard::Directory, heuristicBaseDirectory(files)); return nameMap; } -static QMap buildOutputNamesForDir(const QString &file, const QMap orig) +static QMap buildOutputNamesForDir(const QString &file, const QMap &orig) { QMap ret; const QString dir = orig.value(SignEncryptFilesWizard::Directory); if (dir.isEmpty()) { return orig; } // Build the default names for the wizard. const QFileInfo fi(file); const QString baseName = dir + QLatin1Char('/') + fi.fileName() + QLatin1Char('.'); const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); ret.insert(SignEncryptFilesWizard::SignatureCMS, baseName + QString::fromLatin1(extension(false, true, false, ascii, true))); ret.insert(SignEncryptFilesWizard::EncryptedCMS, baseName + QString::fromLatin1(extension(false, false, true, ascii, false))); ret.insert(SignEncryptFilesWizard::CombinedPGP, baseName + QString::fromLatin1(extension(true, true, true, ascii, false))); ret.insert(SignEncryptFilesWizard::EncryptedPGP, baseName + QString::fromLatin1(extension(true, false, true, ascii, false))); ret.insert(SignEncryptFilesWizard::SignaturePGP, baseName + QString::fromLatin1(extension(true, true, false, ascii, true))); return ret; } void SignEncryptFilesController::setFiles(const QStringList &files) { kleo_assert(!files.empty()); d->files = files; bool archive = false; if (files.size() > 1) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveAllowed); archive = true; } for (const auto &file: files) { if (QFileInfo(file).isDir()) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveForced); archive = true; break; } } d->ensureWizardCreated(); d->wizard->setOutputNames(buildOutputNames(files, archive)); } void SignEncryptFilesController::Private::slotWizardCanceled() { qCDebug(KLEOPATRA_LOG); reportError(gpg_error(GPG_ERR_CANCELED), i18n("User cancel")); } void SignEncryptFilesController::start() { d->ensureWizardVisible(); } static std::shared_ptr createSignEncryptTaskForFileInfo(const QFileInfo &fi, bool ascii, const std::vector &recipients, const std::vector &signers, const QString &outputName, bool symmetric) { const std::shared_ptr task(new SignEncryptTask); Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric); task->setAsciiArmor(ascii); if (!signers.empty()) { task->setSign(true); task->setSigners(signers); task->setDetachedSignature(true); } else { task->setSign(false); } if (!recipients.empty()) { task->setEncrypt(true); task->setRecipients(recipients); task->setDetachedSignature(false); } else { task->setEncrypt(false); } task->setEncryptSymmetric(symmetric); const QString input = fi.absoluteFilePath(); task->setInputFileName(input); task->setInput(Input::createFromFile(input)); task->setOutputFileName(outputName); return task; } static std::shared_ptr createArchiveSignEncryptTaskForFiles(const QStringList &files, const std::shared_ptr &ad, bool pgp, bool ascii, const std::vector &recipients, const std::vector &signers, const QString& outputName, bool symmetric) { const std::shared_ptr task(new SignEncryptTask); task->setEncryptSymmetric(symmetric); Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric); task->setAsciiArmor(ascii); if (!signers.empty()) { task->setSign(true); task->setSigners(signers); task->setDetachedSignature(false); } else { task->setSign(false); } if (!recipients.empty()) { task->setEncrypt(true); task->setRecipients(recipients); } else { task->setEncrypt(false); } kleo_assert(ad); const Protocol proto = pgp ? OpenPGP : CMS; task->setInputFileNames(files); task->setInput(ad->createInputFromPackCommand(proto, files)); task->setOutputFileName(outputName); return task; } static std::vector< std::shared_ptr > createSignEncryptTasksForFileInfo(const QFileInfo &fi, bool ascii, const std::vector &pgpRecipients, const std::vector &pgpSigners, - const std::vector &cmsRecipients, const std::vector &cmsSigners, const QMap outputNames, + const std::vector &cmsRecipients, const std::vector &cmsSigners, const QMap &outputNames, bool symmetric) { std::vector< std::shared_ptr > result; const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty(); const bool cms = !cmsSigners.empty() || !cmsRecipients.empty(); result.reserve(pgp + cms); if (pgp || symmetric) { // Symmetric encryption is only supported for PGP int outKind = 0; if ((!pgpRecipients.empty() || symmetric)&& !pgpSigners.empty()) { outKind = SignEncryptFilesWizard::CombinedPGP; } else if (!pgpRecipients.empty() || symmetric) { outKind = SignEncryptFilesWizard::EncryptedPGP; } else { outKind = SignEncryptFilesWizard::SignaturePGP; } result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric)); } if (cms) { // There is no combined sign / encrypt in gpgsm so we create one sign task // and one encrypt task. Which leaves us with the age old dilemma, encrypt // then sign, or sign then encrypt. Ugly. if (!cmsSigners.empty()) { result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, std::vector(), cmsSigners, outputNames[SignEncryptFilesWizard::SignatureCMS], false)); } if (!cmsRecipients.empty()) { result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, cmsRecipients, std::vector(), outputNames[SignEncryptFilesWizard::EncryptedCMS], false)); } } return result; } static std::vector< std::shared_ptr > createArchiveSignEncryptTasksForFiles(const QStringList &files, const std::shared_ptr &ad, bool ascii, const std::vector &pgpRecipients, const std::vector &pgpSigners, const std::vector &cmsRecipients, const std::vector &cmsSigners, - const QMap outputNames, bool symmetric) + const QMap &outputNames, bool symmetric) { std::vector< std::shared_ptr > result; const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty(); const bool cms = !cmsSigners.empty() || !cmsRecipients.empty(); result.reserve(pgp + cms); if (pgp || symmetric) { int outKind = 0; if ((!pgpRecipients.empty() || symmetric) && !pgpSigners.empty()) { outKind = SignEncryptFilesWizard::CombinedPGP; } else if (!pgpRecipients.empty() || symmetric) { outKind = SignEncryptFilesWizard::EncryptedPGP; } else { outKind = SignEncryptFilesWizard::SignaturePGP; } result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, true, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric)); } if (cms) { if (!cmsSigners.empty()) { result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, false, ascii, std::vector(), cmsSigners, outputNames[SignEncryptFilesWizard::SignatureCMS], false)); } if (!cmsRecipients.empty()) { result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, false, ascii, cmsRecipients, std::vector(), outputNames[SignEncryptFilesWizard::EncryptedCMS], false)); } } return result; } void SignEncryptFilesController::Private::slotWizardOperationPrepared() { try { kleo_assert(wizard); kleo_assert(!files.empty()); const bool archive = (wizard->outputNames().value(SignEncryptFilesWizard::Directory).isNull() && files.size() > 1) || ((operation & ArchiveMask) == ArchiveForced); const QVector recipients = wizard->resolvedRecipients(); const QVector signers = wizard->resolvedSigners(); const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); QVector pgpRecipients, cmsRecipients, pgpSigners, cmsSigners; Q_FOREACH (const Key k, recipients) { if (k.protocol() == GpgME::OpenPGP) { pgpRecipients << k; } else { cmsRecipients << k; } } Q_FOREACH (Key k, signers) { if (k.protocol() == GpgME::OpenPGP) { pgpSigners << k; } else { cmsSigners << k; } } std::vector< std::shared_ptr > tasks; if (!archive) { tasks.reserve(files.size()); } if (archive) { tasks = createArchiveSignEncryptTasksForFiles(files, getDefaultAd(), ascii, pgpRecipients.toStdVector(), pgpSigners.toStdVector(), cmsRecipients.toStdVector(), cmsSigners.toStdVector(), wizard->outputNames(), wizard->encryptSymmetric()); } else { Q_FOREACH (const QString &file, files) { const std::vector< std::shared_ptr > created = createSignEncryptTasksForFileInfo(QFileInfo(file), ascii, pgpRecipients.toStdVector(), pgpSigners.toStdVector(), cmsRecipients.toStdVector(), cmsSigners.toStdVector(), buildOutputNamesForDir(file, wizard->outputNames()), wizard->encryptSymmetric()); tasks.insert(tasks.end(), created.begin(), created.end()); } } const std::shared_ptr overwritePolicy(new OverwritePolicy(wizard)); Q_FOREACH (const std::shared_ptr &i, tasks) { i->setOverwritePolicy(overwritePolicy); } kleo_assert(runnable.empty()); runnable.swap(tasks); Q_FOREACH (const std::shared_ptr &task, runnable) { q->connectTask(task); } std::shared_ptr coll(new TaskCollection); std::vector > tmp; std::copy(runnable.begin(), runnable.end(), std::back_inserter(tmp)); coll->setTasks(tmp); wizard->setTaskCollection(coll); QTimer::singleShot(0, q, SLOT(schedule())); } catch (const Kleo::Exception &e) { reportError(e.error().encodedError(), e.message()); } catch (const std::exception &e) { reportError(gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unexpected exception in SignEncryptFilesController::Private::slotWizardOperationPrepared: %1", QString::fromLocal8Bit(e.what()))); } catch (...) { reportError(gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception in SignEncryptFilesController::Private::slotWizardOperationPrepared")); } } void SignEncryptFilesController::Private::schedule() { if (!cms) if (const std::shared_ptr t = takeRunnable(CMS)) { t->start(); cms = t; } if (!openpgp) if (const std::shared_ptr t = takeRunnable(OpenPGP)) { t->start(); openpgp = t; } if (!cms && !openpgp) { kleo_assert(runnable.empty()); q->emitDoneOrError(); } } std::shared_ptr SignEncryptFilesController::Private::takeRunnable(GpgME::Protocol proto) { const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr &task) { return task->protocol() == proto; }); if (it == runnable.end()) { return std::shared_ptr(); } const std::shared_ptr result = *it; runnable.erase(it); return result; } void SignEncryptFilesController::doTaskDone(const Task *task, const std::shared_ptr &result) { Q_UNUSED(result) Q_ASSERT(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 if (task == d->cms.get()) { d->completed.push_back(d->cms); d->cms.reset(); } else if (task == d->openpgp.get()) { d->completed.push_back(d->openpgp); d->openpgp.reset(); } QTimer::singleShot(0, this, SLOT(schedule())); } void SignEncryptFilesController::cancel() { qCDebug(KLEOPATRA_LOG); try { if (d->wizard) { d->wizard->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void SignEncryptFilesController::Private::cancelAllTasks() { // we just kill all runnable tasks - this will not result in // signal emissions. runnable.clear(); // a cancel() will result in a call to if (cms) { cms->cancel(); } if (openpgp) { openpgp->cancel(); } } void SignEncryptFilesController::Private::ensureWizardCreated() { if (wizard) { return; } std::unique_ptr w(new SignEncryptFilesWizard); w->setAttribute(Qt::WA_DeleteOnClose); connect(w.get(), SIGNAL(operationPrepared()), q, SLOT(slotWizardOperationPrepared()), Qt::QueuedConnection); connect(w.get(), SIGNAL(rejected()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); wizard = w.release(); updateWizardMode(); } void SignEncryptFilesController::Private::ensureWizardVisible() { ensureWizardCreated(); q->bringToForeground(wizard); } #include "moc_signencryptfilescontroller.cpp" diff --git a/src/dialogs/exportdialog.cpp b/src/dialogs/exportdialog.cpp index 910460b5..76a57adc 100644 --- a/src/dialogs/exportdialog.cpp +++ b/src/dialogs/exportdialog.cpp @@ -1,201 +1,201 @@ /* Copyright (c) 2017 Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "exportdialog.h" #include "kleopatra_debug.h" #include "view/waitwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; class ExportWidget::Private { public: Private(ExportWidget *qq) : q(qq) {} void setupUi(); GpgME::Key key; QTextEdit *textEdit; WaitWidget *waitWidget; private: ExportWidget *q; }; void ExportWidget::Private::setupUi() { auto vlay = new QVBoxLayout(q); textEdit = new QTextEdit; textEdit->setVisible(false); textEdit->setReadOnly(true); auto fixedFont = QFont(QStringLiteral("Monospace")); fixedFont.setStyleHint(QFont::TypeWriter); textEdit->setFont(fixedFont); textEdit->setReadOnly(true); vlay->addWidget(textEdit); waitWidget = new WaitWidget; waitWidget->setText(i18n("Exporting ...")); vlay->addWidget(waitWidget); } ExportWidget::ExportWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { d->setupUi(); } ExportWidget::~ExportWidget() { } static QString injectComments(const GpgME::Key &key, const QByteArray &data) { QString ret = QString::fromUtf8(data); if (key.protocol() != GpgME::OpenPGP) { return ret; } auto overView = Formatting::toolTip(key, Formatting::Fingerprint | Formatting::UserIDs | Formatting::Issuer | Formatting::Subject | Formatting::ExpiryDates | Formatting::CertificateType | Formatting::CertificateUsage); // Fixup the HTML coming from the toolTip for our own format. overView.remove(QLatin1String("")); overView.replace(QLatin1String(""), QLatin1String("\t")); overView.replace(QLatin1String(""), QLatin1String("\n")); overView.remove(QLatin1String("")); overView.remove(QLatin1String("\n
    ")); overView.replace(QLatin1String("<"), QLatin1String("<")); overView.replace(QLatin1String(">"), QLatin1String(">")); auto overViewLines = overView.split(QLatin1Char('\n')); // Format comments so that they fit for RFC 4880 - auto comments = QString::fromLatin1("Comment: "); + auto comments = QStringLiteral("Comment: "); comments += overViewLines.join(QStringLiteral("\nComment: ")) + QLatin1Char('\n'); ret.insert(37 /* -----BEGIN PGP PUBLIC KEY BLOCK-----\n */, comments); return ret; } void ExportWidget::exportResult(const GpgME::Error &err, const QByteArray &data) { d->waitWidget->setVisible(false); d->textEdit->setVisible(true); if (err) { /* Should not happen. But well,.. */ d->textEdit->setText(i18nc("%1 is error message", "Failed to export: '%1'", QString::fromLatin1(err.asString()))); } d->textEdit->setText(injectComments(d->key, data)); } void ExportWidget::setKey(const GpgME::Key &key) { d->waitWidget->setVisible(true); d->textEdit->setVisible(false); d->key = key; auto protocol = key.protocol() == GpgME::CMS ? QGpgME::smime() : QGpgME::openpgp(); auto job = protocol->publicKeyExportJob(true); /* New style connect does not work on Windows. */ - connect(job, SIGNAL(result(GpgME::Error,QByteArray)), - this, SLOT(exportResult(GpgME::Error,QByteArray))); + connect(job, &QGpgME::ExportJob::result, + this, &ExportWidget::exportResult); job->start(QStringList() << QLatin1String(key.primaryFingerprint())); } GpgME::Key ExportWidget::key() const { return d->key; } ExportDialog::ExportDialog(QWidget *parent) : QDialog(parent), mWidget(new ExportWidget(this)) { KConfigGroup dialog(KSharedConfig::openConfig(), "ExportDialog"); const auto size = dialog.readEntry("Size", QSize(600, 800)); if (size.isValid()) { resize(size); } setWindowTitle(i18n("Export...")); auto l = new QVBoxLayout(this); l->addWidget(mWidget); auto bbox = new QDialogButtonBox(this); auto btn = bbox->addButton(QDialogButtonBox::Close); connect(btn, &QPushButton::pressed, this, &QDialog::accept); l->addWidget(bbox); } ExportDialog::~ExportDialog() { KConfigGroup dialog(KSharedConfig::openConfig(), "ExportDialog"); dialog.writeEntry("Size", size()); dialog.sync(); } void ExportDialog::setKey(const GpgME::Key &key) { mWidget->setKey(key); } GpgME::Key ExportDialog::key() const { return mWidget->key(); } diff --git a/src/dialogs/updatenotification.cpp b/src/dialogs/updatenotification.cpp index 67df158b..9c30e4c6 100644 --- a/src/dialogs/updatenotification.cpp +++ b/src/dialogs/updatenotification.cpp @@ -1,244 +1,244 @@ /* dialogs/updatenotification.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "updatenotification.h" #include "utils/gnupg-helper.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 using namespace Kleo; namespace { static void gpgconf_set_update_check(bool value) { auto conf = QGpgME::cryptoConfig(); auto entry = conf->entry(QStringLiteral("dirmngr"), QStringLiteral("Enforcement"), QStringLiteral("allow-version-check")); if (!entry) { qCDebug(KLEOPATRA_LOG) << "allow-version-check entry not found"; return; } if (entry->boolValue() != value) { entry->setBoolValue(value); conf->sync(true); } } } // namespace void UpdateNotification::forceUpdateCheck(QWidget *parent) { auto proc = new QProcess; proc->setProgram(gnupgInstallPath() + QStringLiteral("/gpg-connect-agent.exe")); proc->setArguments(QStringList() << QStringLiteral("--dirmngr") << QStringLiteral("loadswdb --force") << QStringLiteral("/bye")); auto progress = new QProgressDialog(i18n("Searching for updates..."), i18n("Cancel"), 0, 0, parent); progress->setMinimumDuration(0); progress->show(); connect(progress, &QProgressDialog::canceled, [progress, proc] () { proc->kill(); qCDebug(KLEOPATRA_LOG) << "Update force canceled. Output:" << QString::fromLocal8Bit(proc->readAllStandardOutput()) << "stderr:" << QString::fromLocal8Bit(proc->readAllStandardError()); }); connect(proc, static_cast(&QProcess::finished), [parent, progress, proc](int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(KLEOPATRA_LOG) << "Update force exited with status:" << exitStatus << "code:" << exitCode; delete progress; proc->deleteLater(); UpdateNotification::checkUpdate(parent, exitStatus == QProcess::NormalExit); }); qCDebug(KLEOPATRA_LOG) << "Starting:" << proc->program() << "args" << proc->arguments(); proc->start(); } void UpdateNotification::checkUpdate(QWidget *parent, bool force) { #ifdef Q_OS_WIN KConfigGroup updatecfg(KSharedConfig::openConfig(), "UpdateNotification"); if (updatecfg.readEntry("NeverShow", false) && !force) { return; } // Gpg defaults to no update check. For Gpg4win we want this // enabled if the user does not explicitly disable update // checks neverShow would be true in that case or // we would have set AllowVersionCheck once and the user // explicitly removed that. if (force || updatecfg.readEntry("AllowVersionCheckSetOnce", false)) { gpgconf_set_update_check (true); updatecfg.writeEntry("AllowVersionCheckSetOnce", true); } const auto current = gpg4winVersion(); GpgME::Error err; const auto lastshown = updatecfg.readEntry("LastShown", QDateTime()); if (!force && lastshown.isValid() && lastshown.addSecs(20 * 60 * 60) > QDateTime::currentDateTime()) { qDebug() << QDateTime::currentDateTime().addSecs(20 * 60 * 60); return; } const auto results = GpgME::SwdbResult::query("gpg4win", current.toUtf8().constData(), &err); if (err) { qCDebug(KLEOPATRA_LOG) << "update check failed: " << err.asString(); return; } if (results.size() != 1) { /* Should not happen */ qCDebug(KLEOPATRA_LOG) << "more then one result"; return; } const auto result = results[0]; if (result.update()) { const QString newVersion = QStringLiteral("%1.%2.%3").arg(result.version().major) .arg(result.version().minor) .arg(result.version().patch); qCDebug(KLEOPATRA_LOG) << "Have update to version:" << newVersion; UpdateNotification notifier(parent, newVersion); notifier.exec(); updatecfg.writeEntry("LastShown", QDateTime::currentDateTime()); updatecfg.sync(); } else { qCDebug(KLEOPATRA_LOG) << "No update for:" << current; if (force) { KMessageBox::information(parent, i18nc("@info", "No update found in the available version database."), i18nc("@title", "Up to date")); } } #else Q_UNUSED(parent); Q_UNUSED(force); #endif } UpdateNotification::UpdateNotification(QWidget *parent, const QString &version) : QDialog(parent) { resize(400, 200); auto lay = new QGridLayout(this); auto logo = new QLabel; logo->setMaximumWidth(110); setAttribute(Qt::WA_QuitOnClose, false); KIconLoader *const il = KIconLoader::global(); - const QString iconPath = il->iconPath(QLatin1String("gpg4win"), + const QString iconPath = il->iconPath(QStringLiteral("gpg4win"), KIconLoader::User); logo->setPixmap(QIcon(iconPath).pixmap(100, 100)); auto label = new QLabel; const QString boldVersion = QStringLiteral("%1").arg(version); label->setText (i18nc("%1 is the version number", "Version %1 is available.", boldVersion) + QStringLiteral("

    ") + i18nc("Link to NEWS style changelog", "See the new features.")); label->setOpenExternalLinks(true); label->setTextInteractionFlags(Qt::TextBrowserInteraction); label->setWordWrap(true); setWindowTitle(i18n("Update available!")); setWindowIcon(QIcon(QLatin1String("gpg4win"))); lay->addWidget(logo, 0, 0); lay->addWidget(label, 0, 1); const auto chk = new QCheckBox (i18n("Show this notification for future updates.")); lay->addWidget(chk, 1, 0, 1, -1); KConfigGroup updatecfg(KSharedConfig::openConfig(), "UpdateNotification"); chk->setChecked(!updatecfg.readEntry("NeverShow", false)); const auto bb = new QDialogButtonBox(); const auto b = bb->addButton(i18n("&Get update"), QDialogButtonBox::AcceptRole); b->setDefault(true); b->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); bb->addButton(QDialogButtonBox::Cancel); lay->addWidget(bb, 2, 0, 1, -1); connect (bb, &QDialogButtonBox::accepted, this, [this, chk]() { QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.gpg4win.org/download.html"))); KConfigGroup updatecfg(KSharedConfig::openConfig(), "UpdateNotification"); updatecfg.writeEntry("NeverShow", !chk->isChecked()); gpgconf_set_update_check (chk->isChecked()); QDialog::accept(); }); connect (bb, &QDialogButtonBox::rejected, this, [this, chk]() { KConfigGroup updatecfg(KSharedConfig::openConfig(), "UpdateNotification"); updatecfg.writeEntry("NeverShow", !chk->isChecked()); gpgconf_set_update_check (chk->isChecked()); QDialog::reject(); }); } diff --git a/src/dialogs/weboftrustwidget.cpp b/src/dialogs/weboftrustwidget.cpp index 276748ee..f84289d5 100644 --- a/src/dialogs/weboftrustwidget.cpp +++ b/src/dialogs/weboftrustwidget.cpp @@ -1,161 +1,161 @@ /* Copyright (c) 2017 Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "weboftrustwidget.h" #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include "commands/command.h" #include #include using namespace Kleo; class WebOfTrustWidget::Private { public: Private(WebOfTrustWidget *qq): keyListJob(nullptr), q(qq) { certificationsTV = new QTreeView; certificationsTV->setModel(&certificationsModel); certificationsTV->setAllColumnsShowFocus(true); certificationsTV->setSelectionMode(QAbstractItemView::ExtendedSelection); auto vLay = new QVBoxLayout(q); vLay->addWidget(certificationsTV); connect(certificationsTV, &QAbstractItemView::doubleClicked, q, [this] (const QModelIndex &idx) { certificationDblClicked(idx); }); } void certificationDblClicked(const QModelIndex &idx) { if (!idx.isValid()) { return; } if (!idx.parent().isValid()) { // No parent -> root item. return; } // grab the keyid const auto query = certificationsModel.data(idx.sibling(idx.row(), 0)).toString(); // Show details widget or search auto cmd = Command::commandForQuery(query); cmd->setParentWId(q->winId()); cmd->start(); } void startSignatureListing() { if (keyListJob) { return; } QGpgME::KeyListJob *const job = QGpgME::openpgp()->keyListJob(/*remote*/false, /*includeSigs*/true, /*validate*/true); if (!job) { return; } /* Old style connect here again as QGPGME newstyle connects with * default arguments don't work on windows. */ - connect(job, SIGNAL(result(GpgME::KeyListResult)), - q, SLOT(signatureListingDone(GpgME::KeyListResult))); + connect(job, &QGpgME::KeyListJob::result, + q, &WebOfTrustWidget::signatureListingDone); - connect(job, SIGNAL(nextKey(GpgME::Key)), - q, SLOT(signatureListingNextKey(GpgME::Key))); + connect(job, &QGpgME::KeyListJob::nextKey, + q, &WebOfTrustWidget::signatureListingNextKey); job->start(QStringList(QString::fromLatin1(key.primaryFingerprint()))); keyListJob = job; } GpgME::Key key; UserIDListModel certificationsModel; QGpgME::KeyListJob *keyListJob; QTreeView *certificationsTV; private: WebOfTrustWidget *q; }; WebOfTrustWidget::WebOfTrustWidget(QWidget *parent) : QWidget(parent), d(new Private(this)) { } GpgME::Key WebOfTrustWidget::key() const { return d->key; } void WebOfTrustWidget::setKey(const GpgME::Key &key) { if (key.protocol() != GpgME::OpenPGP) { qCDebug(KLEOPATRA_LOG) << "Trust chain is only supported for CMS keys"; return; } d->key = key; d->certificationsModel.setKey(key); d->certificationsTV->expandAll(); d->certificationsTV->header()->resizeSections(QHeaderView::ResizeToContents); d->startSignatureListing(); } WebOfTrustWidget::~WebOfTrustWidget() { } void WebOfTrustWidget::signatureListingNextKey(const GpgME::Key &key) { GpgME::Key merged = key; merged.mergeWith(d->key); setKey(merged); } void WebOfTrustWidget::signatureListingDone(const GpgME::KeyListResult &result) { if (result.error()) { KMessageBox::information(this, xi18nc("@info", "An error occurred while loading the certifications: " "%1", QString::fromLocal8Bit(result.error().asString())), i18nc("@title", "Certifications Loading Failed")); } d->keyListJob = nullptr; } diff --git a/src/newcertificatewizard/newcertificatewizard.cpp b/src/newcertificatewizard/newcertificatewizard.cpp index 7d440d8b..28e0f488 100644 --- a/src/newcertificatewizard/newcertificatewizard.cpp +++ b/src/newcertificatewizard/newcertificatewizard.cpp @@ -1,2049 +1,2049 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/newcertificatewizard.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB 2016, 2017 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "newcertificatewizard.h" #include "ui_chooseprotocolpage.h" #include "ui_enterdetailspage.h" #include "ui_overviewpage.h" #include "ui_keycreationpage.h" #include "ui_resultpage.h" #include "ui_advancedsettingsdialog.h" #include #include #include #include #include #include "utils/gnupg-helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::NewCertificateUi; using namespace Kleo::Commands; using namespace GpgME; static const char RSA_KEYSIZES_ENTRY[] = "RSAKeySizes"; static const char DSA_KEYSIZES_ENTRY[] = "DSAKeySizes"; static const char ELG_KEYSIZES_ENTRY[] = "ELGKeySizes"; static const char RSA_KEYSIZE_LABELS_ENTRY[] = "RSAKeySizeLabels"; static const char DSA_KEYSIZE_LABELS_ENTRY[] = "DSAKeySizeLabels"; static const char ELG_KEYSIZE_LABELS_ENTRY[] = "ELGKeySizeLabels"; static const char PGP_KEY_TYPE_ENTRY[] = "PGPKeyType"; static const char CMS_KEY_TYPE_ENTRY[] = "CMSKeyType"; // This should come from gpgme in the future // For now we only support the basic 2.1 curves and check // for GnuPG 2.1. The whole subkey / usage generation needs // new api and a reworked dialog. (ah 10.3.16) // EDDSA should be supported, too. static const QStringList curveNames { { QStringLiteral("brainpoolP256r1") }, { QStringLiteral("brainpoolP384r1") }, { QStringLiteral("brainpoolP512r1") }, { QStringLiteral("NIST P-256") }, { QStringLiteral("NIST P-384") }, { QStringLiteral("NIST P-521") }, }; static void set_tab_order(const QList &wl) { kdtools::for_each_adjacent_pair(wl.begin(), wl.end(), &QWidget::setTabOrder); } enum KeyAlgo { RSA, DSA, ELG, ECDSA, ECDH, EDDSA }; static bool is_algo(Subkey::PubkeyAlgo algo, KeyAlgo what) { switch (algo) { case Subkey::AlgoRSA: case Subkey::AlgoRSA_E: case Subkey::AlgoRSA_S: return what == RSA; case Subkey::AlgoELG_E: case Subkey::AlgoELG: return what == ELG; case Subkey::AlgoDSA: return what == DSA; case Subkey::AlgoECDSA: return what == ECDSA; case Subkey::AlgoECDH: return what == ECDH; case Subkey::AlgoEDDSA: return what == EDDSA; default: break; } return false; } static bool is_rsa(unsigned int algo) { return is_algo(static_cast(algo), RSA); } static bool is_dsa(unsigned int algo) { return is_algo(static_cast(algo), DSA); } static bool is_elg(unsigned int algo) { return is_algo(static_cast(algo), ELG); } static bool is_ecdsa(unsigned int algo) { return is_algo(static_cast(algo), ECDSA); } static bool is_eddsa(unsigned int algo) { return is_algo(static_cast(algo), EDDSA); } static bool is_ecdh(unsigned int algo) { return is_algo(static_cast(algo), ECDH); } static void force_set_checked(QAbstractButton *b, bool on) { // work around Qt bug (tested: 4.1.4, 4.2.3, 4.3.4) const bool autoExclusive = b->autoExclusive(); b->setAutoExclusive(false); b->setChecked(b->isEnabled() && on); b->setAutoExclusive(autoExclusive); } static void set_keysize(QComboBox *cb, unsigned int strength) { if (!cb) { return; } const int idx = cb->findData(static_cast(strength)); if (idx < 0) { qCWarning(KLEOPATRA_LOG) << "keysize " << strength << " not allowed"; } cb->setCurrentIndex(idx); } static unsigned int get_keysize(const QComboBox *cb) { if (!cb) { return 0; } const int idx = cb->currentIndex(); if (idx < 0) { return 0; } return cb->itemData(idx).toInt(); } static void set_curve(QComboBox *cb, const QString &curve) { if (!cb) { return; } const int idx = cb->findText(curve); if (idx < 0) { // Can't happen as we don't have them configurable. qCWarning(KLEOPATRA_LOG) << "curve " << curve << " not allowed"; } cb->setCurrentIndex(idx); } static QString get_curve(const QComboBox *cb) { if (!cb) { return QString(); } return cb->currentText(); } // Extract the algo information from default_pubkey_algo format // // and put it into the return values size, algo and curve. // // Values look like: // RSA-2048 // rsa2048/cert,sign+rsa2048/enc // brainpoolP256r1+brainpoolP256r1 static void parseAlgoString(const QString &algoString, int *size, Subkey::PubkeyAlgo *algo, QString &curve) { const auto split = algoString.split(QLatin1Char('/')); bool isEncrypt = split.size() == 2 && split[1].contains(QStringLiteral("enc")); // Normalize const auto lowered = split[0].toLower().remove(QLatin1Char('-')); if (!algo || !size) { return; } *algo = Subkey::AlgoUnknown; if (lowered.startsWith(QLatin1String("rsa"))) { *algo = Subkey::AlgoRSA; } else if (lowered.startsWith(QLatin1String("dsa"))) { *algo = Subkey::AlgoDSA; } else if (lowered.startsWith(QLatin1String("elg"))) { *algo = Subkey::AlgoELG; } if (*algo != Subkey::AlgoUnknown) { bool ok; - *size = lowered.right(lowered.size() - 3).toInt(&ok); + *size = lowered.rightRef(lowered.size() - 3).toInt(&ok); if (!ok) { qCWarning(KLEOPATRA_LOG) << "Could not extract size from: " << lowered; *size = 2048; } return; } // Now the ECC Algorithms if (lowered.startsWith(QLatin1String("ed25519"))) { // Special handling for this as technically // this is a cv25519 curve used for EDDSA curve = split[0]; *algo = Subkey::AlgoEDDSA; return; } if (lowered.startsWith(QLatin1String("cv25519")) || lowered.startsWith(QLatin1String("nist")) || lowered.startsWith(QLatin1String("brainpool")) || lowered.startsWith(QLatin1String("secp"))) { curve = split[0]; *algo = isEncrypt ? Subkey::AlgoECDH : Subkey::AlgoECDSA; return; } qCWarning(KLEOPATRA_LOG) << "Failed to parse default_pubkey_algo:" << algoString; } Q_DECLARE_METATYPE(GpgME::Subkey::PubkeyAlgo) namespace Kleo { namespace NewCertificateUi { class WizardPage : public QWizardPage { Q_OBJECT protected: explicit WizardPage(QWidget *parent = nullptr) : QWizardPage(parent) {} NewCertificateWizard *wizard() const { Q_ASSERT(static_cast(QWizardPage::wizard()) == qobject_cast(QWizardPage::wizard())); return static_cast(QWizardPage::wizard()); } QAbstractButton *button(QWizard::WizardButton button) const { return QWizardPage::wizard() ? QWizardPage::wizard()->button(button) : nullptr; } bool isButtonVisible(QWizard::WizardButton button) const { if (const QAbstractButton *const b = this->button(button)) { return b->isVisible(); } else { return false; } } QDir tmpDir() const; protected Q_SLOTS: void setButtonVisible(QWizard::WizardButton button, bool visible) { if (QAbstractButton *const b = this->button(button)) { b->setVisible(visible); } } protected: #define FIELD(type, name) type name() const { return field( QLatin1String(#name) ).value(); } FIELD(bool, pgp) FIELD(bool, signingAllowed) FIELD(bool, encryptionAllowed) FIELD(bool, certificationAllowed) FIELD(bool, authenticationAllowed) FIELD(QString, name) FIELD(QString, email) FIELD(QString, dn) FIELD(Subkey::PubkeyAlgo, keyType) FIELD(int, keyStrength) FIELD(QString, keyCurve) FIELD(Subkey::PubkeyAlgo, subkeyType) FIELD(int, subkeyStrength) FIELD(QString, subkeyCurve) FIELD(QDate, expiryDate) FIELD(QStringList, additionalUserIDs) FIELD(QStringList, additionalEMailAddresses) FIELD(QStringList, dnsNames) FIELD(QStringList, uris) FIELD(QString, url) FIELD(QString, error) FIELD(QString, result) FIELD(QString, fingerprint) #undef FIELD }; } // namespace NewCertificateUi } // namespace Kleo using namespace Kleo::NewCertificateUi; namespace { class AdvancedSettingsDialog : public QDialog { Q_OBJECT Q_PROPERTY(QStringList additionalUserIDs READ additionalUserIDs WRITE setAdditionalUserIDs) Q_PROPERTY(QStringList additionalEMailAddresses READ additionalEMailAddresses WRITE setAdditionalEMailAddresses) Q_PROPERTY(QStringList dnsNames READ dnsNames WRITE setDnsNames) Q_PROPERTY(QStringList uris READ uris WRITE setUris) Q_PROPERTY(uint keyStrength READ keyStrength WRITE setKeyStrength) Q_PROPERTY(Subkey::PubkeyAlgo keyType READ keyType WRITE setKeyType) Q_PROPERTY(QString keyCurve READ keyCurve WRITE setKeyCurve) Q_PROPERTY(uint subkeyStrength READ subkeyStrength WRITE setSubkeyStrength) Q_PROPERTY(QString subkeyCurve READ subkeyCurve WRITE setSubkeyCurve) Q_PROPERTY(Subkey::PubkeyAlgo subkeyType READ subkeyType WRITE setSubkeyType) Q_PROPERTY(bool signingAllowed READ signingAllowed WRITE setSigningAllowed) Q_PROPERTY(bool encryptionAllowed READ encryptionAllowed WRITE setEncryptionAllowed) Q_PROPERTY(bool certificationAllowed READ certificationAllowed WRITE setCertificationAllowed) Q_PROPERTY(bool authenticationAllowed READ authenticationAllowed WRITE setAuthenticationAllowed) Q_PROPERTY(QDate expiryDate READ expiryDate WRITE setExpiryDate) public: explicit AdvancedSettingsDialog(QWidget *parent = nullptr) : QDialog(parent), protocol(UnknownProtocol), pgpDefaultAlgorithm(Subkey::AlgoELG_E), cmsDefaultAlgorithm(Subkey::AlgoRSA), keyTypeImmutable(false), ui(), mECCSupported(engineIsVersion(2, 1, 0)), mEdDSASupported(engineIsVersion(2, 1, 15)) { qRegisterMetaType("Subkey::PubkeyAlgo"); ui.setupUi(this); const QDate today = QDate::currentDate(); ui.expiryDE->setMinimumDate(today); ui.expiryDE->setDate(today.addYears(2)); ui.expiryCB->setChecked(true); ui.emailLW->setDefaultValue(i18n("new email")); ui.dnsLW->setDefaultValue(i18n("new dns name")); ui.uriLW->setDefaultValue(i18n("new uri")); fillKeySizeComboBoxen(); } void setProtocol(GpgME::Protocol proto) { if (protocol == proto) { return; } protocol = proto; loadDefaultKeyType(); } void setAdditionalUserIDs(const QStringList &items) { ui.uidLW->setItems(items); } QStringList additionalUserIDs() const { return ui.uidLW->items(); } void setAdditionalEMailAddresses(const QStringList &items) { ui.emailLW->setItems(items); } QStringList additionalEMailAddresses() const { return ui.emailLW->items(); } void setDnsNames(const QStringList &items) { ui.dnsLW->setItems(items); } QStringList dnsNames() const { return ui.dnsLW->items(); } void setUris(const QStringList &items) { ui.uriLW->setItems(items); } QStringList uris() const { return ui.uriLW->items(); } void setKeyStrength(unsigned int strength) { set_keysize(ui.rsaKeyStrengthCB, strength); set_keysize(ui.dsaKeyStrengthCB, strength); } unsigned int keyStrength() const { return ui.dsaRB->isChecked() ? get_keysize(ui.dsaKeyStrengthCB) : ui.rsaRB->isChecked() ? get_keysize(ui.rsaKeyStrengthCB) : 0; } void setKeyType(Subkey::PubkeyAlgo algo) { QRadioButton *const rb = is_rsa(algo) ? ui.rsaRB : is_dsa(algo) ? ui.dsaRB : is_ecdsa(algo) || is_eddsa(algo) ? ui.ecdsaRB : nullptr; if (rb) { rb->setChecked(true); } } Subkey::PubkeyAlgo keyType() const { return ui.dsaRB->isChecked() ? Subkey::AlgoDSA : ui.rsaRB->isChecked() ? Subkey::AlgoRSA : ui.ecdsaRB->isChecked() ? ui.ecdsaKeyCurvesCB->currentText() == QStringLiteral("ed25519") ? Subkey::AlgoEDDSA : Subkey::AlgoECDSA : Subkey::AlgoUnknown; } void setKeyCurve(const QString &curve) { set_curve(ui.ecdsaKeyCurvesCB, curve); } QString keyCurve() const { return get_curve(ui.ecdsaKeyCurvesCB); } void setSubkeyType(Subkey::PubkeyAlgo algo) { ui.elgCB->setChecked(is_elg(algo)); ui.rsaSubCB->setChecked(is_rsa(algo)); ui.ecdhCB->setChecked(is_ecdh(algo)); } Subkey::PubkeyAlgo subkeyType() const { if (ui.elgCB->isChecked()) { return Subkey::AlgoELG_E; } else if (ui.rsaSubCB->isChecked()) { return Subkey::AlgoRSA; } else if (ui.ecdhCB->isChecked()) { return Subkey::AlgoECDH; } return Subkey::AlgoUnknown; } void setSubkeyCurve(const QString &curve) { set_curve(ui.ecdhKeyCurvesCB, curve); } QString subkeyCurve() const { return get_curve(ui.ecdhKeyCurvesCB); } void setSubkeyStrength(unsigned int strength) { if (subkeyType() == Subkey::AlgoRSA) { set_keysize(ui.rsaKeyStrengthSubCB, strength); } else { set_keysize(ui.elgKeyStrengthCB, strength); } } unsigned int subkeyStrength() const { if (subkeyType() == Subkey::AlgoRSA) { return get_keysize(ui.rsaKeyStrengthSubCB); } return get_keysize(ui.elgKeyStrengthCB); } void setSigningAllowed(bool on) { ui.signingCB->setChecked(on); } bool signingAllowed() const { return ui.signingCB->isChecked(); } void setEncryptionAllowed(bool on) { ui.encryptionCB->setChecked(on); } bool encryptionAllowed() const { return ui.encryptionCB->isChecked(); } void setCertificationAllowed(bool on) { ui.certificationCB->setChecked(on); } bool certificationAllowed() const { return ui.certificationCB->isChecked(); } void setAuthenticationAllowed(bool on) { ui.authenticationCB->setChecked(on); } bool authenticationAllowed() const { return ui.authenticationCB->isChecked(); } void setExpiryDate(const QDate &date) { if (date.isValid()) { ui.expiryDE->setDate(date); } else { ui.expiryCB->setChecked(false); } } QDate expiryDate() const { return ui.expiryCB->isChecked() ? ui.expiryDE->date() : QDate(); } Q_SIGNALS: void changed(); private Q_SLOTS: void slotKeyMaterialSelectionChanged() { const unsigned int algo = keyType(); const unsigned int sk_algo = subkeyType(); if (protocol == OpenPGP) { if (!keyTypeImmutable) { ui.elgCB->setEnabled(is_dsa(algo)); ui.rsaSubCB->setEnabled(is_rsa(algo)); ui.ecdhCB->setEnabled(is_ecdsa(algo) || is_eddsa(algo)); if (sender() == ui.dsaRB || sender() == ui.rsaRB || sender() == ui.ecdsaRB) { ui.elgCB->setChecked(is_dsa(algo)); ui.ecdhCB->setChecked(is_ecdsa(algo) || is_eddsa(algo)); ui.rsaSubCB->setChecked(is_rsa(algo)); } if (is_rsa(algo)) { ui.encryptionCB->setEnabled(true); ui.encryptionCB->setChecked(true); ui.signingCB->setEnabled(true); ui.signingCB->setChecked(true); ui.authenticationCB->setEnabled(true); if (is_rsa(sk_algo)) { ui.encryptionCB->setEnabled(false); ui.encryptionCB->setChecked(true); } else { ui.encryptionCB->setEnabled(true); } } else if (is_dsa(algo)) { ui.encryptionCB->setEnabled(false); if (is_elg(sk_algo)) { ui.encryptionCB->setChecked(true); } else { ui.encryptionCB->setChecked(false); } } else if (is_ecdsa(algo) || is_eddsa(algo)) { ui.signingCB->setEnabled(true); ui.signingCB->setChecked(true); ui.authenticationCB->setEnabled(true); ui.encryptionCB->setEnabled(false); ui.encryptionCB->setChecked(is_ecdh(sk_algo)); } } } else { //assert( is_rsa( keyType() ) ); // it can happen through misconfiguration by the admin that no key type is selectable at all } } void slotSigningAllowedToggled(bool on) { if (!on && protocol == CMS && !encryptionAllowed()) { setEncryptionAllowed(true); } } void slotEncryptionAllowedToggled(bool on) { if (!on && protocol == CMS && !signingAllowed()) { setSigningAllowed(true); } } private: void fillKeySizeComboBoxen(); void loadDefaultKeyType(); void loadDefaultGnuPGKeyType(); void updateWidgetVisibility(); private: GpgME::Protocol protocol; unsigned int pgpDefaultAlgorithm; unsigned int cmsDefaultAlgorithm; bool keyTypeImmutable; Ui_AdvancedSettingsDialog ui; bool mECCSupported; bool mEdDSASupported; }; class ChooseProtocolPage : public WizardPage { Q_OBJECT public: explicit ChooseProtocolPage(QWidget *p = nullptr) : WizardPage(p), initialized(false), ui() { ui.setupUi(this); registerField(QStringLiteral("pgp"), ui.pgpCLB); } void setProtocol(Protocol proto) { if (proto == OpenPGP) { ui.pgpCLB->setChecked(true); } else if (proto == CMS) { ui.x509CLB->setChecked(true); } else { force_set_checked(ui.pgpCLB, false); force_set_checked(ui.x509CLB, false); } } Protocol protocol() const { return ui.pgpCLB->isChecked() ? OpenPGP : ui.x509CLB->isChecked() ? CMS : UnknownProtocol; } void initializePage() override { if (!initialized) { connect(ui.pgpCLB, &QAbstractButton::clicked, wizard(), &QWizard::next, Qt::QueuedConnection); connect(ui.x509CLB, &QAbstractButton::clicked, wizard(), &QWizard::next, Qt::QueuedConnection); } initialized = true; } bool isComplete() const override { return protocol() != UnknownProtocol; } private: bool initialized : 1; Ui_ChooseProtocolPage ui; }; struct Line { QString attr; QString label; QString regex; QLineEdit *edit; }; class EnterDetailsPage : public WizardPage { Q_OBJECT public: explicit EnterDetailsPage(QWidget *p = nullptr) : WizardPage(p), dialog(this), ui() { ui.setupUi(this); // set errorLB to have a fixed height of two lines: ui.errorLB->setText(QStringLiteral("2
    1")); ui.errorLB->setFixedHeight(ui.errorLB->minimumSizeHint().height()); ui.errorLB->clear(); connect(ui.resultLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); // The email doesn't necessarily show up in ui.resultLE: connect(ui.emailLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); connect(ui.addEmailToDnCB, &QAbstractButton::toggled, this, &EnterDetailsPage::slotUpdateResultLabel); registerDialogPropertiesAsFields(); registerField(QStringLiteral("dn"), ui.resultLE); registerField(QStringLiteral("name"), ui.nameLE); registerField(QStringLiteral("email"), ui.emailLE); updateForm(); } bool isComplete() const override; void initializePage() override { updateForm(); dialog.setProtocol(pgp() ? OpenPGP : CMS); } void cleanupPage() override { saveValues(); } private: void updateForm(); void clearForm(); void saveValues(); void registerDialogPropertiesAsFields(); private: QString pgpUserID() const; QString cmsDN() const; private Q_SLOTS: void slotAdvancedSettingsClicked(); void slotUpdateResultLabel() { ui.resultLE->setText(pgp() ? pgpUserID() : cmsDN()); } private: QVector lineList; QList dynamicWidgets; QMap savedValues; AdvancedSettingsDialog dialog; Ui_EnterDetailsPage ui; }; class OverviewPage : public WizardPage { Q_OBJECT public: explicit OverviewPage(QWidget *p = nullptr) : WizardPage(p), ui() { ui.setupUi(this); setCommitPage(true); setButtonText(QWizard::CommitButton, i18nc("@action", "Create")); } void initializePage() override { slotShowDetails(); } private Q_SLOTS: void slotShowDetails() { ui.textBrowser->setHtml(i18nFormatGnupgKeyParms(ui.showAllDetailsCB->isChecked())); } private: QStringList i18nKeyUsages() const; QStringList i18nSubkeyUsages() const; QStringList i18nCombinedKeyUsages() const; QString i18nFormatGnupgKeyParms(bool details) const; private: Ui_OverviewPage ui; }; class KeyCreationPage : public WizardPage { Q_OBJECT public: explicit KeyCreationPage(QWidget *p = nullptr) : WizardPage(p), ui() { ui.setupUi(this); } bool isComplete() const override { return !job; } void initializePage() override { startJob(); } private: void startJob() { const auto proto = pgp() ? QGpgME::openpgp() : QGpgME::smime(); if (!proto) { return; } QGpgME::KeyGenerationJob *const j = proto->keyGenerationJob(); if (!j) { return; } - connect(j, SIGNAL(result(GpgME::KeyGenerationResult,QByteArray,QString)), - this, SLOT(slotResult(GpgME::KeyGenerationResult,QByteArray,QString))); + connect(j, &QGpgME::KeyGenerationJob::result, + this, &KeyCreationPage::slotResult); if (const Error err = j->start(createGnupgKeyParms())) setField(QStringLiteral("error"), i18n("Could not start key pair creation: %1", QString::fromLocal8Bit(err.asString()))); else { job = j; } } QStringList keyUsages() const; QStringList subkeyUsages() const; QString createGnupgKeyParms() const; private Q_SLOTS: void slotResult(const GpgME::KeyGenerationResult &result, const QByteArray &request, const QString &auditLog) { Q_UNUSED(auditLog); if (result.error().code() || (pgp() && !result.fingerprint())) { setField(QStringLiteral("error"), result.error().isCanceled() ? i18n("Operation canceled.") : i18n("Could not create key pair: %1", QString::fromLocal8Bit(result.error().asString()))); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), QString()); } else if (pgp()) { setField(QStringLiteral("error"), QString()); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), i18n("Key pair created successfully.\n" "Fingerprint: %1", QLatin1String(result.fingerprint()))); } else { QFile file(tmpDir().absoluteFilePath(QStringLiteral("request.p10"))); if (!file.open(QIODevice::WriteOnly)) { setField(QStringLiteral("error"), i18n("Could not write output file %1: %2", file.fileName(), file.errorString())); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), QString()); } else { file.write(request); setField(QStringLiteral("error"), QString()); setField(QStringLiteral("url"), QUrl::fromLocalFile(file.fileName()).toString()); setField(QStringLiteral("result"), i18n("Key pair created successfully.")); } } // Ensure that we have the key in the keycache if (pgp() && !result.error().code() && result.fingerprint()) { auto ctx = Context::createForProtocol(OpenPGP); if (ctx) { // Check is pretty useless something very buggy in that case. Error e; const auto key = ctx->key(result.fingerprint(), e, true); if (!key.isNull()) { KeyCache::mutableInstance()->insert(key); } else { qCDebug(KLEOPATRA_LOG) << "Failed to find newly generated key."; } delete ctx; } } setField(QStringLiteral("fingerprint"), result.fingerprint() ? QString::fromLatin1(result.fingerprint()) : QString()); job = nullptr; Q_EMIT completeChanged(); QMetaObject::invokeMethod(wizard(), "next", Qt::QueuedConnection); } private: QPointer job; Ui_KeyCreationPage ui; }; class ResultPage : public WizardPage { Q_OBJECT public: explicit ResultPage(QWidget *p = nullptr) : WizardPage(p), initialized(false), successfullyCreatedSigningCertificate(false), successfullyCreatedEncryptionCertificate(false), ui() { ui.setupUi(this); ui.dragQueen->setPixmap(QIcon::fromTheme(QStringLiteral("kleopatra")).pixmap(64, 64)); registerField(QStringLiteral("error"), ui.errorTB, "plainText"); registerField(QStringLiteral("result"), ui.resultTB, "plainText"); registerField(QStringLiteral("url"), ui.dragQueen, "url"); // hidden field, since QWizard can't deal with non-widget-backed fields... QLineEdit *le = new QLineEdit(this); le->hide(); registerField(QStringLiteral("fingerprint"), le); } void initializePage() override { const bool error = isError(); if (error) { setTitle(i18nc("@title", "Key Creation Failed")); setSubTitle(i18n("Key pair creation failed. Please find details about the failure below.")); } else { setTitle(i18nc("@title", "Key Pair Successfully Created")); setSubTitle(i18n("Your new key pair was created successfully. Please find details on the result and some suggested next steps below.")); } ui.resultTB ->setVisible(!error); ui.errorTB ->setVisible(error); ui.dragQueen ->setVisible(!error &&!pgp()); ui.restartWizardPB ->setVisible(error); ui.nextStepsGB ->setVisible(!error); ui.saveRequestToFilePB ->setVisible(!pgp()); ui.makeBackupPB ->setVisible(pgp()); ui.createRevocationRequestPB->setVisible(pgp() &&false); // not implemented ui.sendCertificateByEMailPB ->setVisible(pgp()); ui.sendRequestByEMailPB ->setVisible(!pgp()); ui.uploadToKeyserverPB ->setVisible(pgp()); if (!error && !pgp()) { if (signingAllowed() && !encryptionAllowed()) { successfullyCreatedSigningCertificate = true; } else if (!signingAllowed() && encryptionAllowed()) { successfullyCreatedEncryptionCertificate = true; } else { successfullyCreatedEncryptionCertificate = successfullyCreatedSigningCertificate = true; } } ui.createSigningCertificatePB->setVisible(successfullyCreatedEncryptionCertificate &&!successfullyCreatedSigningCertificate); ui.createEncryptionCertificatePB->setVisible(successfullyCreatedSigningCertificate &&!successfullyCreatedEncryptionCertificate); setButtonVisible(QWizard::CancelButton, error); if (!initialized) connect(ui.restartWizardPB, &QAbstractButton::clicked, wizard(), &QWizard::restart); initialized = true; } void cleanupPage() override { setButtonVisible(QWizard::CancelButton, true); } bool isError() const { return !ui.errorTB->document()->isEmpty(); } bool isComplete() const override { return !isError(); } private: Key key() const { return KeyCache::instance()->findByFingerprint(fingerprint().toLatin1().constData()); } private Q_SLOTS: void slotSaveRequestToFile() { QString fileName = FileDialog::getSaveFileName(this, i18nc("@title", "Save Request"), QStringLiteral("imp"), i18n("PKCS#10 Requests (*.p10)")); if (fileName.isEmpty()) { return; } if (!fileName.endsWith(QLatin1String(".p10"), Qt::CaseInsensitive)) { fileName += QLatin1String(".p10"); } QFile src(QUrl(url()).toLocalFile()); if (!src.copy(fileName)) KMessageBox::error(this, xi18nc("@info", "Could not copy temporary file %1 " "to file %2: %3", src.fileName(), fileName, src.errorString()), i18nc("@title", "Error Saving Request")); else KMessageBox::information(this, xi18nc("@info", "Successfully wrote request to %1." "You should now send the request to the Certification Authority (CA).", fileName), i18nc("@title", "Request Saved")); } void slotSendRequestByEMail() { if (pgp()) { return; } const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); invokeMailer(config.readEntry("CAEmailAddress"), // to i18n("Please process this certificate."), // subject i18n("Please process this certificate and inform the sender about the location to fetch the resulting certificate.\n\nThanks,\n"), // body QUrl(url()).toLocalFile()); // attachment } void slotSendCertificateByEMail() { if (!pgp() || exportCertificateCommand) { return; } ExportCertificateCommand *cmd = new ExportCertificateCommand(key()); connect(cmd, &ExportCertificateCommand::finished, this, &ResultPage::slotSendCertificateByEMailContinuation); cmd->setOpenPGPFileName(tmpDir().absoluteFilePath(fingerprint() + QLatin1String(".asc"))); cmd->start(); exportCertificateCommand = cmd; } void slotSendCertificateByEMailContinuation() { if (!exportCertificateCommand) { return; } // ### better error handling? const QString fileName = exportCertificateCommand->openPGPFileName(); qCDebug(KLEOPATRA_LOG) << "fileName" << fileName; exportCertificateCommand = nullptr; if (fileName.isEmpty()) { return; } invokeMailer(QString(), // to i18n("My new public OpenPGP key"), // subject i18n("Please find attached my new public OpenPGP key."), // body fileName); } QByteArray ol_quote(QByteArray str) { #ifdef Q_OS_WIN return "\"\"" + str.replace('"', "\\\"") + "\"\""; //return '"' + str.replace( '"', "\\\"" ) + '"'; #else return str; #endif } void invokeMailer(const QString &to, const QString &subject, const QString &body, const QString &attachment) { qCDebug(KLEOPATRA_LOG) << "to:" << to << "subject:" << subject << "body:" << body << "attachment:" << attachment; // RFC 2368 says body's linebreaks need to be encoded as // "%0D%0A", so normalize body to CRLF: //body.replace(QLatin1Char('\n'), QStringLiteral("\r\n")).remove(QStringLiteral("\r\r")); QUrlQuery query; query.addQueryItem(QStringLiteral("subject"), subject); query.addQueryItem(QStringLiteral("body"), body); if (!attachment.isEmpty()) { query.addQueryItem(QStringLiteral("attach"), attachment); } QUrl url; url.setScheme(QStringLiteral("mailto")); url.setQuery(query); qCDebug(KLEOPATRA_LOG) << "openUrl" << url; QDesktopServices::openUrl(url); KMessageBox::information(this, xi18nc("@info", "Kleopatra tried to send a mail via your default mail client." "Some mail clients are known not to support attachments when invoked this way." "If your mail client does not have an attachment, then drag the Kleopatra icon and drop it on the message compose window of your mail client." "If that does not work, either, save the request to a file, and then attach that."), i18nc("@title", "Sending Mail"), QStringLiteral("newcertificatewizard-mailto-troubles")); } void slotUploadCertificateToDirectoryServer() { if (pgp()) { (new ExportOpenPGPCertsToServerCommand(key()))->start(); } } void slotBackupCertificate() { if (pgp()) { (new ExportSecretKeyCommand(key()))->start(); } } void slotCreateRevocationRequest() { } void slotCreateSigningCertificate() { if (successfullyCreatedSigningCertificate) { return; } toggleSignEncryptAndRestart(); } void slotCreateEncryptionCertificate() { if (successfullyCreatedEncryptionCertificate) { return; } toggleSignEncryptAndRestart(); } private: void toggleSignEncryptAndRestart() { if (!wizard()) { return; } if (KMessageBox::warningContinueCancel( this, i18nc("@info", "This operation will delete the certification request. " "Please make sure that you have sent or saved it before proceeding."), i18nc("@title", "Certification Request About To Be Deleted")) != KMessageBox::Continue) { return; } const bool sign = signingAllowed(); const bool encr = encryptionAllowed(); setField(QStringLiteral("signingAllowed"), !sign); setField(QStringLiteral("encryptionAllowed"), !encr); // restart and skip to Overview Page: wizard()->restart(); for (int i = wizard()->currentId(); i < NewCertificateWizard::OverviewPageId; ++i) { wizard()->next(); } } private: bool initialized : 1; bool successfullyCreatedSigningCertificate : 1; bool successfullyCreatedEncryptionCertificate : 1; QPointer exportCertificateCommand; Ui_ResultPage ui; }; } class NewCertificateWizard::Private { friend class ::Kleo::NewCertificateWizard; friend class ::Kleo::NewCertificateUi::WizardPage; NewCertificateWizard *const q; public: explicit Private(NewCertificateWizard *qq) : q(qq), tmp(QDir::temp().absoluteFilePath(QStringLiteral("kleo-"))), ui(q) { q->setWindowTitle(i18nc("@title", "Key Pair Creation Wizard")); } private: QTemporaryDir tmp; struct Ui { ChooseProtocolPage chooseProtocolPage; EnterDetailsPage enterDetailsPage; OverviewPage overviewPage; KeyCreationPage keyCreationPage; ResultPage resultPage; explicit Ui(NewCertificateWizard *q) : chooseProtocolPage(q), enterDetailsPage(q), overviewPage(q), keyCreationPage(q), resultPage(q) { KDAB_SET_OBJECT_NAME(chooseProtocolPage); KDAB_SET_OBJECT_NAME(enterDetailsPage); KDAB_SET_OBJECT_NAME(overviewPage); KDAB_SET_OBJECT_NAME(keyCreationPage); KDAB_SET_OBJECT_NAME(resultPage); q->setOptions(DisabledBackButtonOnLastPage); q->setPage(ChooseProtocolPageId, &chooseProtocolPage); q->setPage(EnterDetailsPageId, &enterDetailsPage); q->setPage(OverviewPageId, &overviewPage); q->setPage(KeyCreationPageId, &keyCreationPage); q->setPage(ResultPageId, &resultPage); q->setStartId(ChooseProtocolPageId); } } ui; }; NewCertificateWizard::NewCertificateWizard(QWidget *p) : QWizard(p), d(new Private(this)) { } NewCertificateWizard::~NewCertificateWizard() {} void NewCertificateWizard::setProtocol(Protocol proto) { d->ui.chooseProtocolPage.setProtocol(proto); setStartId(proto == UnknownProtocol ? ChooseProtocolPageId : EnterDetailsPageId); } Protocol NewCertificateWizard::protocol() const { return d->ui.chooseProtocolPage.protocol(); } static QString pgpLabel(const QString &attr) { if (attr == QLatin1String("NAME")) { return i18n("Name"); } if (attr == QLatin1String("EMAIL")) { return i18n("EMail"); } return QString(); } static QString attributeLabel(const QString &attr, bool pgp) { if (attr.isEmpty()) { return QString(); } const QString label = pgp ? pgpLabel(attr) : Kleo::DNAttributeMapper::instance()->name2label(attr); if (!label.isEmpty()) if (pgp) { return label; } else return i18nc("Format string for the labels in the \"Your Personal Data\" page", "%1 (%2)", label, attr); else { return attr; } } #if 0 //Not used anywhere static QString attributeLabelWithColor(const QString &attr, bool pgp) { const QString result = attributeLabel(attr, pgp); if (result.isEmpty()) { return QString(); } else { return result + ':'; } } #endif static QString attributeFromKey(QString key) { return key.remove(QLatin1Char('!')); } static const char *oidForAttributeName(const QString &attr) { QByteArray attrUtf8 = attr.toUtf8(); for (unsigned int i = 0; i < numOidMaps; ++i) if (qstricmp(attrUtf8.constData(), oidmap[i].name) == 0) { return oidmap[i].oid; } return nullptr; } QDir WizardPage::tmpDir() const { return wizard() ? QDir(wizard()->d->tmp.path()) : QDir::home(); } void EnterDetailsPage::registerDialogPropertiesAsFields() { const QMetaObject *const mo = dialog.metaObject(); for (unsigned int i = mo->propertyOffset(), end = i + mo->propertyCount(); i != end; ++i) { const QMetaProperty mp = mo->property(i); if (mp.isValid()) { registerField(QLatin1String(mp.name()), &dialog, mp.name(), SIGNAL(accepted())); } } } void EnterDetailsPage::saveValues() { for (const Line &line : qAsConst(lineList)) { savedValues[ attributeFromKey(line.attr) ] = line.edit->text().trimmed(); } } void EnterDetailsPage::clearForm() { qDeleteAll(dynamicWidgets); dynamicWidgets.clear(); lineList.clear(); ui.nameLE->hide(); ui.nameLE->clear(); ui.nameLB->hide(); ui.nameRequiredLB->hide(); ui.emailLE->hide(); ui.emailLE->clear(); ui.emailLB->hide(); ui.emailRequiredLB->hide(); ui.addEmailToDnCB->hide(); } static int row_index_of(QWidget *w, QGridLayout *l) { const int idx = l->indexOf(w); int r, c, rs, cs; l->getItemPosition(idx, &r, &c, &rs, &cs); return r; } static QLineEdit *adjust_row(QGridLayout *l, int row, const QString &label, const QString &preset, QValidator *validator, bool readonly, bool required) { Q_ASSERT(l); Q_ASSERT(row >= 0); Q_ASSERT(row < l->rowCount()); QLabel *lb = qobject_cast(l->itemAtPosition(row, 0)->widget()); Q_ASSERT(lb); QLineEdit *le = qobject_cast(l->itemAtPosition(row, 1)->widget()); Q_ASSERT(le); lb->setBuddy(le); // For better accessibility QLabel *reqLB = qobject_cast(l->itemAtPosition(row, 2)->widget()); Q_ASSERT(reqLB); lb->setText(i18nc("interpunctation for labels", "%1:", label)); le->setText(preset); reqLB->setText(required ? i18n("(required)") : i18n("(optional)")); delete le->validator(); if (validator) { if (!validator->parent()) { validator->setParent(le); } le->setValidator(validator); } le->setReadOnly(readonly && le->hasAcceptableInput()); lb->show(); le->show(); reqLB->show(); return le; } static int add_row(QGridLayout *l, QList *wl) { Q_ASSERT(l); Q_ASSERT(wl); const int row = l->rowCount(); QWidget *w1, *w2, *w3; l->addWidget(w1 = new QLabel(l->parentWidget()), row, 0); l->addWidget(w2 = new QLineEdit(l->parentWidget()), row, 1); l->addWidget(w3 = new QLabel(l->parentWidget()), row, 2); wl->push_back(w1); wl->push_back(w2); wl->push_back(w3); return row; } void EnterDetailsPage::updateForm() { clearForm(); const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); QStringList attrOrder = config.readEntry(pgp() ? "OpenPGPAttributeOrder" : "DNAttributeOrder", QStringList()); if (attrOrder.empty()) { if (pgp()) { attrOrder << QStringLiteral("NAME") << QStringLiteral("EMAIL"); } else { attrOrder << QStringLiteral("CN!") << QStringLiteral("L") << QStringLiteral("OU") << QStringLiteral("O!") << QStringLiteral("C!") << QStringLiteral("EMAIL!"); } } QList widgets; widgets.push_back(ui.nameLE); widgets.push_back(ui.emailLE); QMap lines; Q_FOREACH (const QString &rawKey, attrOrder) { const QString key = rawKey.trimmed().toUpper(); const QString attr = attributeFromKey(key); if (attr.isEmpty()) { continue; } const QString preset = savedValues.value(attr, config.readEntry(attr, QString())); const bool required = key.endsWith(QLatin1Char('!')); const bool readonly = config.isEntryImmutable(attr); const QString label = config.readEntry(attr + QLatin1String("_label"), attributeLabel(attr, pgp())); const QString regex = config.readEntry(attr + QLatin1String("_regex")); int row; bool known = true; QValidator *validator = nullptr; if (attr == QLatin1String("EMAIL")) { row = row_index_of(ui.emailLE, ui.gridLayout); validator = regex.isEmpty() ? Validation::email() : Validation::email(QRegExp(regex)); if (!pgp()) { ui.addEmailToDnCB->show(); } } else if (attr == QLatin1String("NAME") || attr == QLatin1String("CN")) { if ((pgp() && attr == QLatin1String("CN")) || (!pgp() && attr == QLatin1String("NAME"))) { continue; } if (pgp()) { validator = regex.isEmpty() ? Validation::pgpName() : Validation::pgpName(QRegExp(regex)); } row = row_index_of(ui.nameLE, ui.gridLayout); } else { known = false; row = add_row(ui.gridLayout, &dynamicWidgets); } if (!validator && !regex.isEmpty()) { validator = new QRegExpValidator(QRegExp(regex), nullptr); } QLineEdit *le = adjust_row(ui.gridLayout, row, label, preset, validator, readonly, required); const Line line = { key, label, regex, le }; lines[row] = line; if (!known) { widgets.push_back(le); } // don't connect twice: disconnect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel); connect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel); } // create lineList in visual order, so requirementsAreMet() // complains from top to bottom: lineList.reserve(lines.count()); std::copy(lines.cbegin(), lines.cend(), std::back_inserter(lineList)); widgets.push_back(ui.resultLE); widgets.push_back(ui.addEmailToDnCB); widgets.push_back(ui.advancedPB); const KEMailSettings e; if (ui.nameLE->text().isEmpty()) { ui.nameLE->setText(e.getSetting(KEMailSettings::RealName)); } if (ui.emailLE->text().isEmpty()) { ui.emailLE->setText(e.getSetting(KEMailSettings::EmailAddress)); } set_tab_order(widgets); } QString EnterDetailsPage::cmsDN() const { DN dn; for (QVector::const_iterator it = lineList.begin(), end = lineList.end(); it != end; ++it) { const QString text = it->edit->text().trimmed(); if (text.isEmpty()) { continue; } QString attr = attributeFromKey(it->attr); if (attr == QLatin1String("EMAIL") && !ui.addEmailToDnCB->isChecked()) { continue; } if (const char *const oid = oidForAttributeName(attr)) { attr = QString::fromUtf8(oid); } dn.append(DN::Attribute(attr, text)); } return dn.dn(); } QString EnterDetailsPage::pgpUserID() const { return Formatting::prettyNameAndEMail(OpenPGP, QString(), ui.nameLE->text().trimmed(), ui.emailLE->text().trimmed(), QString()); } static bool has_intermediate_input(const QLineEdit *le) { QString text = le->text(); int pos = le->cursorPosition(); const QValidator *const v = le->validator(); return v && v->validate(text, pos) == QValidator::Intermediate; } static bool requirementsAreMet(const QVector &list, QString &error) { bool allEmpty = true; for (const Line &line : list) { const QLineEdit *le = line.edit; if (!le) { continue; } const QString key = line.attr; qCDebug(KLEOPATRA_LOG) << "requirementsAreMet(): checking \"" << key << "\" against \"" << le->text() << "\":"; if (le->text().trimmed().isEmpty()) { if (key.endsWith(QLatin1Char('!'))) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is required, but empty.", line.label); } else error = xi18nc("@info", "%1 is required, but empty." "Local Admin rule: %2", line.label, line.regex); return false; } } else if (has_intermediate_input(le)) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is incomplete.", line.label); } else error = xi18nc("@info", "%1 is incomplete." "Local Admin rule: %2", line.label, line.regex); return false; } else if (!le->hasAcceptableInput()) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is invalid.", line.label); } else error = xi18nc("@info", "%1 is invalid." "Local Admin rule: %2", line.label, line.regex); return false; } else { allEmpty = false; } } // Ensure that at least one value is acceptable return !allEmpty; } bool EnterDetailsPage::isComplete() const { QString error; const bool ok = requirementsAreMet(lineList, error); ui.errorLB->setText(error); return ok; } void EnterDetailsPage::slotAdvancedSettingsClicked() { dialog.exec(); } QStringList KeyCreationPage::keyUsages() const { QStringList usages; if (signingAllowed()) { usages << QStringLiteral("sign"); } if (encryptionAllowed() && !is_ecdh(subkeyType()) && !is_dsa(keyType()) && !is_rsa(subkeyType())) { usages << QStringLiteral("encrypt"); } if (0) // not needed in pgp (implied) and not supported in cms if (certificationAllowed()) { usages << QStringLiteral("certify"); } if (authenticationAllowed()) { usages << QStringLiteral("auth"); } return usages; } QStringList OverviewPage::i18nKeyUsages() const { QStringList usages; if (signingAllowed()) { usages << i18n("Sign"); } if (encryptionAllowed() && !is_ecdh(subkeyType()) && !is_dsa(keyType()) && !is_rsa(subkeyType())) { usages << i18n("Encrypt"); } if (0) // not needed in pgp (implied) and not supported in cms if (certificationAllowed()) { usages << i18n("Certify"); } if (authenticationAllowed()) { usages << i18n("Authenticate"); } return usages; } QStringList KeyCreationPage::subkeyUsages() const { QStringList usages; if (encryptionAllowed() && (is_dsa(keyType()) || is_rsa(subkeyType()) || is_ecdh(subkeyType()))) { Q_ASSERT(subkeyType()); usages << QStringLiteral("encrypt"); } return usages; } QStringList OverviewPage::i18nSubkeyUsages() const { QStringList usages; if (encryptionAllowed() && (is_dsa(keyType()) || is_rsa(subkeyType()) || is_ecdh(subkeyType()))) { Q_ASSERT(subkeyType()); usages << i18n("Encrypt"); } return usages; } QStringList OverviewPage::i18nCombinedKeyUsages() const { return i18nSubkeyUsages() + i18nKeyUsages(); } namespace { template struct Row { QString key; T value; Row(const QString &k, const T &v) : key(k), value(v) {} }; template QTextStream &operator<<(QTextStream &s, const Row &row) { if (row.key.isEmpty()) { return s; } else { return s << "" << row.key << "" << row.value << ""; } } } QString OverviewPage::i18nFormatGnupgKeyParms(bool details) const { QString result; QTextStream s(&result); s << ""; if (pgp()) { if (!name().isEmpty()) { s << Row< >(i18n("Name:"), name()); } } if (!email().isEmpty()) { s << Row< >(i18n("Email Address:"), email()); } if (!pgp()) { s << Row< >(i18n("Subject-DN:"), DN(dn()).dn(QStringLiteral(",
    "))); } if (details) { s << Row< >(i18n("Key Type:"), QLatin1String(Subkey::publicKeyAlgorithmAsString(keyType()))); if (is_ecdsa(keyType()) || is_eddsa(keyType())) { s << Row< >(i18n("Key Curve:"), keyCurve()); } else if (const unsigned int strength = keyStrength()) { s << Row< >(i18n("Key Strength:"), i18np("1 bit", "%1 bits", strength)); } else { s << Row< >(i18n("Key Strength:"), i18n("default")); } s << Row< >(i18n("Usage:"), i18nCombinedKeyUsages().join(i18nc("separator for key usages", ", "))); if (const Subkey::PubkeyAlgo subkey = subkeyType()) { s << Row< >(i18n("Subkey Type:"), QLatin1String(Subkey::publicKeyAlgorithmAsString(subkey))); if (is_ecdh(subkeyType())) { s << Row< >(i18n("Key Curve:"), subkeyCurve()); } else if (const unsigned int strength = subkeyStrength()) { s << Row< >(i18n("Subkey Strength:"), i18np("1 bit", "%1 bits", strength)); } else { s << Row< >(i18n("Subkey Strength:"), i18n("default")); } s << Row< >(i18n("Subkey Usage:"), i18nSubkeyUsages().join(i18nc("separator for key usages", ", "))); } } if (pgp() && details && expiryDate().isValid()) { s << Row< >(i18n("Valid Until:"), QLocale().toString(expiryDate())); } if (!pgp() && details) { Q_FOREACH (const QString &email, additionalEMailAddresses()) { s << Row< >(i18n("Add. Email Address:"), email); } Q_FOREACH (const QString &dns, dnsNames()) { s << Row< >(i18n("DNS Name:"), dns); } Q_FOREACH (const QString &uri, uris()) { s << Row< >(i18n("URI:"), uri); } } return result; } static QString encode_dns(const QString &dns) { return QLatin1String(QUrl::toAce(dns)); } static QString encode_email(const QString &email) { const int at = email.lastIndexOf(QLatin1Char('@')); if (at < 0) { return email; } return email.left(at + 1) + encode_dns(email.mid(at + 1)); } QString KeyCreationPage::createGnupgKeyParms() const { QString result; QTextStream s(&result); s << "" << endl; if (pgp()) { s << "%ask-passphrase" << endl; } s << "key-type: " << Subkey::publicKeyAlgorithmAsString(keyType()) << endl; if (is_ecdsa(keyType()) || is_eddsa(keyType())) { s << "key-curve: " << keyCurve() << endl; } else if (const unsigned int strength = keyStrength()) { s << "key-length: " << strength << endl; } s << "key-usage: " << keyUsages().join(QLatin1Char(' ')) << endl; if (const Subkey::PubkeyAlgo subkey = subkeyType()) { s << "subkey-type: " << Subkey::publicKeyAlgorithmAsString(subkey) << endl; if (is_ecdh(subkeyType())) { s << "subkey-curve: " << subkeyCurve() << endl; } else if (const unsigned int strength = subkeyStrength()) { s << "subkey-length: " << strength << endl; } s << "subkey-usage: " << subkeyUsages().join(QLatin1Char(' ')) << endl; } if (pgp() && expiryDate().isValid()) { s << "expire-date: " << expiryDate().toString(Qt::ISODate) << endl; } if (pgp()) { if (!name().isEmpty()) { s << "name-real: " << name() << endl; } if (!email().isEmpty()) { s << "name-email: " << email() << endl; } } else { s << "name-dn: " << dn() << endl; s << "name-email: " << encode_email(email()) << endl; Q_FOREACH (const QString &email, additionalEMailAddresses()) { s << "name-email: " << encode_email(email) << endl; } Q_FOREACH (const QString &dns, dnsNames()) { s << "name-dns: " << encode_dns(dns) << endl; } Q_FOREACH (const QString &uri, uris()) { s << "name-uri: " << uri << endl; } } s << "" << endl; qCDebug(KLEOPATRA_LOG) << '\n' << result; return result; } static void fill_combobox(QComboBox &cb, const QList &sizes, const QStringList &labels) { cb.clear(); for (int i = 0, end = sizes.size(); i != end; ++i) { cb.addItem(i < labels.size() && !labels[i].trimmed().isEmpty() ? sizes[i] < 0 ? i18ncp("%2: some admin-supplied text, %1: key size in bits", "%2 (1 bit; default)", "%2 (%1 bits; default)", -sizes[i], labels[i].trimmed()) : i18ncp("%2: some admin-supplied text, %1: key size in bits", "%2 (1 bit)", "%2 (%1 bits)", sizes[i], labels[i].trimmed()) : sizes[i] < 0 ? i18ncp("%1: key size in bits", "1 bit (default)", "%1 bits (default)", -sizes[i]) : i18ncp("%1: key size in bits", "1 bit", "%1 bits", sizes[i]), std::abs(sizes[i])); if (sizes[i] < 0) { cb.setCurrentIndex(cb.count() - 1); } } } void AdvancedSettingsDialog::fillKeySizeComboBoxen() { const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); const QList rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList() << -2048 << 3072 << 4096); const QList dsaKeySizes = config.readEntry(DSA_KEYSIZES_ENTRY, QList() << -2048); const QList elgKeySizes = config.readEntry(ELG_KEYSIZES_ENTRY, QList() << -2048 << 3072 << 4096); const QStringList rsaKeySizeLabels = config.readEntry(RSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList dsaKeySizeLabels = config.readEntry(DSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList elgKeySizeLabels = config.readEntry(ELG_KEYSIZE_LABELS_ENTRY, QStringList()); fill_combobox(*ui.rsaKeyStrengthCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui.rsaKeyStrengthSubCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui.dsaKeyStrengthCB, dsaKeySizes, dsaKeySizeLabels); fill_combobox(*ui.elgKeyStrengthCB, elgKeySizes, elgKeySizeLabels); if (mEdDSASupported) { // If supported we recommend cv25519 ui.ecdsaKeyCurvesCB->addItem(QStringLiteral("ed25519")); ui.ecdhKeyCurvesCB->addItem(QStringLiteral("cv25519")); } ui.ecdhKeyCurvesCB->addItems(curveNames); ui.ecdsaKeyCurvesCB->addItems(curveNames); } // Try to load the default key type from GnuPG void AdvancedSettingsDialog::loadDefaultGnuPGKeyType() { const auto conf = QGpgME::cryptoConfig(); if (!conf) { qCWarning(KLEOPATRA_LOG) << "Failed to obtain cryptoConfig."; return; } const auto entry = conf->entry(protocol == CMS ? QStringLiteral("gpgsm") : QStringLiteral("gpg"), QStringLiteral("Configuration"), QStringLiteral("default_pubkey_algo")); if (!entry) { qCDebug(KLEOPATRA_LOG) << "GnuPG does not have default key type. Fallback to RSA"; setKeyType(Subkey::AlgoRSA); setSubkeyType(Subkey::AlgoRSA); return; } qCDebug(KLEOPATRA_LOG) << "Have default key type: " << entry->stringValue(); // Format is [/usage]+[/usage] const auto split = entry->stringValue().split(QLatin1Char('+')); int size = 0; Subkey::PubkeyAlgo algo = Subkey::AlgoUnknown; QString curve; parseAlgoString(split[0], &size, &algo, curve); if (algo == Subkey::AlgoUnknown) { setSubkeyType(Subkey::AlgoRSA); return; } setKeyType(algo); if (is_rsa(algo) || is_elg(algo) || is_dsa(algo)) { setKeyStrength(size); } else { setKeyCurve(curve); } if (split.size() == 2) { auto algoString = split[1]; // If it has no usage we assume encrypt subkey if (!algoString.contains(QLatin1Char('/'))) { algoString += QStringLiteral("/enc"); } parseAlgoString(algoString, &size, &algo, curve); if (algo == Subkey::AlgoUnknown) { setSubkeyType(Subkey::AlgoRSA); return; } setSubkeyType(algo); if (is_rsa(algo) || is_elg(algo)) { setSubkeyStrength(size); } else { setSubkeyCurve(curve); } } } void AdvancedSettingsDialog::loadDefaultKeyType() { if (protocol != CMS && protocol != OpenPGP) { return; } const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); const QString entry = protocol == CMS ? QLatin1String(CMS_KEY_TYPE_ENTRY) : QLatin1String(PGP_KEY_TYPE_ENTRY); const QString keyType = config.readEntry(entry).trimmed().toUpper(); if (protocol == OpenPGP && keyType == QLatin1String("DSA")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoUnknown); } else if (protocol == OpenPGP && keyType == QLatin1String("DSA+ELG")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoELG_E); #if GPGMEPP_VERSION > 0x10800 // GPGME 1.8.0 has a bug that makes the gpgconf engine // return garbage so we don't load it for this } else if (keyType.isEmpty() && engineIsVersion(2, 1, 17)) { loadDefaultGnuPGKeyType(); #endif } else { if (!keyType.isEmpty() && keyType != QLatin1String("RSA")) qCWarning(KLEOPATRA_LOG) << "invalid value \"" << qPrintable(keyType) << "\" for entry \"[CertificateCreationWizard]" << qPrintable(entry) << "\""; setKeyType(Subkey::AlgoRSA); setSubkeyType(Subkey::AlgoRSA); } keyTypeImmutable = config.isEntryImmutable(entry); updateWidgetVisibility(); } void AdvancedSettingsDialog::updateWidgetVisibility() { // Personal Details Page if (protocol == OpenPGP) { // ### hide until multi-uid is implemented if (ui.tabWidget->indexOf(ui.personalTab) != -1) { ui.tabWidget->removeTab(ui.tabWidget->indexOf(ui.personalTab)); } } else { if (ui.tabWidget->indexOf(ui.personalTab) == -1) { ui.tabWidget->addTab(ui.personalTab, tr2i18n("Personal Details", nullptr)); } } ui.uidGB->setVisible(protocol == OpenPGP); ui.uidGB->setEnabled(false); ui.uidGB->setToolTip(i18nc("@info:tooltip", "Adding more than one User ID is not yet implemented.")); ui.emailGB->setVisible(protocol == CMS); ui.dnsGB->setVisible(protocol == CMS); ui.uriGB->setVisible(protocol == CMS); ui.ecdhCB->setVisible(mECCSupported); ui.ecdhKeyCurvesCB->setVisible(mECCSupported); ui.ecdsaKeyCurvesCB->setVisible(mECCSupported); ui.ecdsaRB->setVisible(mECCSupported); if (mEdDSASupported) { // We use the same radio button for EdDSA as we use for // ECDSA GnuPG does the same and this is really super technical // land. ui.ecdsaRB->setText(QStringLiteral("ECDSA/EdDSA")); } bool deVsHack = Kleo::gpgComplianceP("de-vs"); if (deVsHack) { // GnuPG Provides no API to query which keys are compliant for // a mode. If we request a different one it will error out so // we have to remove the options. // // Does anyone want to use NIST anyway? int i; while ((i = ui.ecdsaKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 || (i = ui.ecdsaKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) { ui.ecdsaKeyCurvesCB->removeItem(i); } while ((i = ui.ecdhKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 || (i = ui.ecdhKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) { ui.ecdhKeyCurvesCB->removeItem(i); } } // Technical Details Page if (keyTypeImmutable) { ui.rsaRB->setEnabled(false); ui.rsaSubCB->setEnabled(false); ui.dsaRB->setEnabled(false); ui.elgCB->setEnabled(false); ui.ecdsaRB->setEnabled(false); ui.ecdhCB->setEnabled(false); } else { ui.rsaRB->setEnabled(true); ui.rsaSubCB->setEnabled(protocol == OpenPGP); ui.dsaRB->setEnabled(protocol == OpenPGP && !deVsHack); ui.elgCB->setEnabled(protocol == OpenPGP && !deVsHack); ui.ecdsaRB->setEnabled(protocol == OpenPGP); ui.ecdhCB->setEnabled(protocol == OpenPGP); } ui.certificationCB->setVisible(protocol == OpenPGP); // gpgsm limitation? ui.authenticationCB->setVisible(protocol == OpenPGP); if (protocol == OpenPGP) { // pgp keys must have certify capability ui.certificationCB->setChecked(true); ui.certificationCB->setEnabled(false); } if (protocol == CMS) { ui.encryptionCB->setEnabled(true); ui.rsaSubCB->setChecked(false); ui.rsaKeyStrengthSubCB->setEnabled(false); } ui.expiryDE->setVisible(protocol == OpenPGP); ui.expiryCB->setVisible(protocol == OpenPGP); slotKeyMaterialSelectionChanged(); } #include "newcertificatewizard.moc" diff --git a/src/smartcard/card.cpp b/src/smartcard/card.cpp index 362b480a..bd57166b 100644 --- a/src/smartcard/card.cpp +++ b/src/smartcard/card.cpp @@ -1,151 +1,151 @@ /* smartcard/card.h This file is part of Kleopatra, the KDE keymanager Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "card.h" #include "readerstatus.h" using namespace Kleo; using namespace Kleo::SmartCard; Card::Card(): mCanLearn(false), mHasNullPin(false), mStatus(Status::NoCard), mAppType(UnknownApplication), mAppVersion(-1) { } void Card::setStatus(Status s) { mStatus = s; } Card::Status Card::status() const { return mStatus; } void Card::setSerialNumber(const std::string &sn) { mSerialNumber = sn; } std::string Card::serialNumber() const { return mSerialNumber; } Card::AppType Card::appType() const { return mAppType; } void Card::setAppType(AppType t) { mAppType = t; } void Card::setAppVersion(int version) { mAppVersion = version; } int Card::appVersion() const { return mAppVersion; } std::vector Card::pinStates() const { return mPinStates; } -void Card::setPinStates(std::vector pinStates) +void Card::setPinStates(const std::vector &pinStates) { mPinStates = pinStates; } void Card::setSlot(int slot) { mSlot = slot; } int Card::slot() const { return mSlot; } bool Card::hasNullPin() const { return mHasNullPin; } void Card::setHasNullPin(bool value) { mHasNullPin = value; } bool Card::canLearnKeys() const { return mCanLearn; } void Card::setCanLearnKeys(bool value) { mCanLearn = value; } bool Card::operator == (const Card& other) const { return mStatus == other.status() && mSerialNumber == other.serialNumber() && mAppType == other.appType() && mAppVersion == other.appVersion() && mPinStates == other.pinStates() && mSlot == other.slot() && mCanLearn == other.canLearnKeys() && mHasNullPin == other.hasNullPin(); } bool Card::operator != (const Card& other) const { return !operator==(other); } void Card::setErrorMsg(const QString &msg) { mErrMsg = msg; } QString Card::errorMsg() const { return mErrMsg; } diff --git a/src/smartcard/card.h b/src/smartcard/card.h index cfc0857c..a82ec369 100644 --- a/src/smartcard/card.h +++ b/src/smartcard/card.h @@ -1,130 +1,130 @@ #ifndef SMARTCARD_CARD_H #define SMARTCARD_CARD_H /* smartcard/card.h This file is part of Kleopatra, the KDE keymanager Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include #include namespace Kleo { namespace SmartCard { class ReaderStatus; /** Class to work with Smartcards or other Hardware tokens. */ class Card { public: enum AppType { UnknownApplication, OpenPGPApplication, NksApplication, P15Application, DinSigApplication, GeldkarteApplication, NumAppTypes }; enum PinState { UnknownPinState, NullPin, PinBlocked, NoPin, PinOk, NumPinStates }; enum Status { NoCard, CardPresent, CardActive, CardUsable, _NumScdStates, CardError = _NumScdStates, NumStates }; Card(); virtual ~Card() {} virtual bool operator == (const Card& other) const; bool operator != (const Card& other) const; void setStatus(Status s); Status status() const; virtual void setSerialNumber(const std::string &sn); std::string serialNumber() const; AppType appType() const; void setAppType(AppType type); void setAppVersion(int version); int appVersion() const; std::vector pinStates() const; - void setPinStates(std::vector pinStates); + void setPinStates(const std::vector &pinStates); void setSlot(int slot); int slot() const; bool hasNullPin() const; void setHasNullPin(bool value); bool canLearnKeys() const; void setCanLearnKeys(bool value); QString errorMsg() const; void setErrorMsg(const QString &msg); private: bool mCanLearn; bool mHasNullPin; Status mStatus; std::string mSerialNumber; AppType mAppType; int mAppVersion; std::vector mPinStates; int mSlot; QString mErrMsg; }; } // namespace Smartcard } // namespace Kleopatra #endif // SMARTCARD_CARD_H diff --git a/src/smartcard/readerstatus.cpp b/src/smartcard/readerstatus.cpp index af0cdfd7..ae701520 100644 --- a/src/smartcard/readerstatus.cpp +++ b/src/smartcard/readerstatus.cpp @@ -1,682 +1,682 @@ /* -*- mode: c++; c-basic-offset:4 -*- smartcard/readerstatus.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2009 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "readerstatus.h" #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include "openpgpcard.h" #include "netkeycard.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils/kdtoolsglobal.h" using namespace Kleo; using namespace Kleo::SmartCard; using namespace GpgME; static ReaderStatus *self = nullptr; static const char *flags[] = { "NOCARD", "PRESENT", "ACTIVE", "USABLE", }; static_assert(sizeof flags / sizeof * flags == Card::_NumScdStates, ""); static const char *prettyFlags[] = { "NoCard", "CardPresent", "CardActive", "CardUsable", "CardError", }; static_assert(sizeof prettyFlags / sizeof * prettyFlags == Card::NumStates, ""); #if 0 We need this once we have support for multiple readers in scdaemons interface. static unsigned int parseFileName(const QString &fileName, bool *ok) { QRegExp rx(QLatin1String("reader_(\\d+)\\.status")); if (ok) { *ok = false; } if (rx.exactMatch(QFileInfo(fileName).fileName())) { return rx.cap(1).toUInt(ok, 10); } return 0; } #endif Q_DECLARE_METATYPE(GpgME::Error) namespace { static QDebug operator<<(QDebug s, const std::vector< std::pair > &v) { typedef std::pair pair; s << '('; for (const pair &p : v) { s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << endl; } return s << ')'; } static const char *app_types[] = { "_", // will hopefully never be used as an app-type :) "openpgp", "nks", "p15", "dinsig", "geldkarte", }; static_assert(sizeof app_types / sizeof * app_types == Card::NumAppTypes, ""); static Card::AppType parse_app_type(const std::string &s) { qCDebug(KLEOPATRA_LOG) << "parse_app_type(" << s.c_str() << ")"; const char **it = std::find_if(std::begin(app_types), std::end(app_types), [&s](const char *type) { return ::strcasecmp(s.c_str(), type) == 0; }); if (it == std::end(app_types)) { qCDebug(KLEOPATRA_LOG) << "App type not found"; return Card::UnknownApplication; } return static_cast(it - std::begin(app_types)); } static int parse_app_version(const std::string &s) { return std::atoi(s.c_str()); } static Card::PinState parse_pin_state(const QString &s) { bool ok; int i = s.toInt(&ok); if (!ok) { qCDebug(KLEOPATRA_LOG) << "Failed to parse pin state" << s; return Card::UnknownPinState; } switch (i) { case -4: return Card::NullPin; case -3: return Card::PinBlocked; case -2: return Card::NoPin; case -1: return Card::UnknownPinState; default: if (i < 0) { return Card::UnknownPinState; } else { return Card::PinOk; } } } static std::unique_ptr gpgagent_transact(std::shared_ptr &gpgAgent, const char *command, Error &err) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << ")"; err = gpgAgent->assuanTransact(command); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << "):" << QString::fromLocal8Bit(err.asString()); if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) { qCDebug(KLEOPATRA_LOG) << "Assuan problem, killing context"; gpgAgent.reset(); } return std::unique_ptr(); } std::unique_ptr t = gpgAgent->takeLastAssuanTransaction(); return std::unique_ptr(dynamic_cast(t.release())); } const std::vector< std::pair > gpgagent_statuslines(std::shared_ptr gpgAgent, const char *what, Error &err) { const std::unique_ptr t = gpgagent_transact(gpgAgent, what, err); if (t.get()) { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): got" << t->statusLines(); return t->statusLines(); } else { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): t == NULL"; return std::vector >(); } } static const std::string gpgagent_status(const std::shared_ptr &gpgAgent, const char *what, Error &err) { const auto lines = gpgagent_statuslines (gpgAgent, what, err); // The status is only the last attribute // e.g. for SCD SERIALNO it would only be "SERIALNO" and for SCD GETATTR FOO // it would only be FOO const char *p = strrchr(what, ' '); const char *needle = (p + 1) ? (p + 1) : what; for (const auto &pair: lines) { if (pair.first == needle) { return pair.second; } } return std::string(); } static const std::string scd_getattr_status(std::shared_ptr &gpgAgent, const char *what, Error &err) { std::string cmd = "SCD GETATTR "; cmd += what; return gpgagent_status(gpgAgent, cmd.c_str(), err); } static void handle_openpgp_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto ret = new OpenPGPCard(); ret->setSerialNumber(ci->serialNumber()); const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --keypairinfo", err); if (err.code()) { ci->setStatus(Card::CardError); return; } ret->setKeyPairInfo(info); ci.reset(ret); } static void handle_netkey_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto nkCard = new NetKeyCard(); nkCard->setSerialNumber(ci->serialNumber()); ci.reset(nkCard); ci->setAppVersion(parse_app_version(scd_getattr_status(gpg_agent, "NKS-VERSION", err))); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "NKS-VERSION resulted in error" << err.asString(); ci->setErrorMsg(QStringLiteral ("NKS-VERSION failed: ") + QString::fromUtf8(err.asString())); return; } if (ci->appVersion() != 3) { qCDebug(KLEOPATRA_LOG) << "not a NetKey v3 card, giving up. Version:" << ci->appVersion(); ci->setErrorMsg(QStringLiteral("NetKey v%1 cards are not supported.").arg(ci->appVersion())); return; } // the following only works for NKS v3... const auto chvStatus = QString::fromStdString( scd_getattr_status(gpg_agent, "CHV-STATUS", err)).split(QStringLiteral(" ")); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "no CHV-STATUS" << err.asString(); ci->setErrorMsg(QStringLiteral ("CHV-Status failed: ") + QString::fromUtf8(err.asString())); return; } std::vector states; // CHV Status for NKS v3 is // Pin1 (Normal pin) Pin2 (Normal PUK) // SigG1 SigG PUK. int num = 0; for (const auto &state: chvStatus) { const auto parsed = parse_pin_state (state); states.push_back(parsed); if (parsed == Card::NullPin) { if (num == 0) { ci->setHasNullPin(true); } } ++num; } nkCard->setPinStates(states); // check for keys to learn: const std::unique_ptr result = gpgagent_transact(gpg_agent, "SCD LEARN --keypairinfo", err); if (err.code() || !result.get()) { if (err) { ci->setErrorMsg(QString::fromLatin1(err.asString())); } else { ci->setErrorMsg(QStringLiteral("Invalid internal state. No result.")); } return; } const std::vector keyPairInfos = result->statusLine("KEYPAIRINFO"); if (keyPairInfos.empty()) { return; } nkCard->setKeyPairInfo(keyPairInfos); } static std::shared_ptr get_card_status(unsigned int slot, std::shared_ptr &gpg_agent) { qCDebug(KLEOPATRA_LOG) << "get_card_status(" << slot << ',' << gpg_agent.get() << ')'; auto ci = std::shared_ptr (new Card()); if (slot != 0 || !gpg_agent) { // In the future scdaemon should support multiple slots but // not yet (2.1.18) return ci; } Error err; ci->setSerialNumber(gpgagent_status(gpg_agent, "SCD SERIALNO", err)); if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); return ci; } if (err.code()) { ci->setStatus(Card::CardError); return ci; } ci->setStatus(Card::CardPresent); const auto verbatimType = scd_getattr_status(gpg_agent, "APPTYPE", err); ci->setAppType(parse_app_type(verbatimType)); if (err.code()) { return ci; } // Handle different card types if (ci->appType() == Card::NksApplication) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found Netkey card" << ci->serialNumber().c_str() << "end"; handle_netkey_card(ci, gpg_agent); return ci; } else if (ci->appType() == Card::OpenPGPApplication) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found OpenPGP card" << ci->serialNumber().c_str() << "end"; handle_openpgp_card(ci, gpg_agent); return ci; } else { qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << verbatimType.c_str(); return ci; } return ci; } static std::vector > update_cardinfo(std::shared_ptr &gpgAgent) { // Multiple smartcard readers are only supported internally by gnupg // but not by scdaemon (Status gnupg 2.1.18) // We still pretend that there can be multiple cards inserted // at once but we don't handle it yet. const auto ci = get_card_status(0, gpgAgent); return std::vector >(1, ci); } } // namespace struct Transaction { QByteArray command; QPointer receiver; const char *slot; }; static const Transaction updateTransaction = { "__update__", nullptr, nullptr }; static const Transaction quitTransaction = { "__quit__", nullptr, nullptr }; namespace { class ReaderStatusThread : public QThread { Q_OBJECT public: explicit ReaderStatusThread(QObject *parent = nullptr) : QThread(parent), m_gnupgHomePath(Kleo::gnupgHomeDirectory()), m_transactions(1, updateTransaction) // force initial scan { connect(this, &ReaderStatusThread::oneTransactionFinished, this, &ReaderStatusThread::slotOneTransactionFinished); } std::vector > cardInfos() const { const QMutexLocker locker(&m_mutex); return m_cardInfos; } Card::Status cardStatus(unsigned int slot) const { const QMutexLocker locker(&m_mutex); if (slot < m_cardInfos.size()) { return m_cardInfos[slot]->status(); } else { return Card::NoCard; } } void addTransaction(const Transaction &t) { const QMutexLocker locker(&m_mutex); m_transactions.push_back(t); m_waitForTransactions.wakeOne(); } Q_SIGNALS: void anyCardHasNullPinChanged(bool); void anyCardCanLearnKeysChanged(bool); void cardChanged(unsigned int); - void oneTransactionFinished(GpgME::Error err); + void oneTransactionFinished(const GpgME::Error &err); public Q_SLOTS: void ping() { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::ping()"; addTransaction(updateTransaction); } void stop() { const QMutexLocker locker(&m_mutex); m_transactions.push_front(quitTransaction); m_waitForTransactions.wakeOne(); } private Q_SLOTS: - void slotOneTransactionFinished(GpgME::Error err) + void slotOneTransactionFinished(const GpgME::Error &err) { std::list ft; KDAB_SYNCHRONIZED(m_mutex) ft.splice(ft.begin(), m_finishedTransactions); Q_FOREACH (const Transaction &t, ft) if (t.receiver && t.slot && *t.slot) { QMetaObject::invokeMethod(t.receiver, t.slot, Qt::DirectConnection, Q_ARG(GpgME::Error, err)); } } private: void run() override { while (true) { std::shared_ptr gpgAgent; QByteArray command; bool nullSlot = false; std::list item; std::vector > oldCards; Error err; std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return; } gpgAgent = std::shared_ptr(c.release()); KDAB_SYNCHRONIZED(m_mutex) { while (m_transactions.empty()) { // go to sleep waiting for more work: qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: waiting for commands"; m_waitForTransactions.wait(&m_mutex); } // splice off the first transaction without // copying, so we own it without really importing // it into this thread (the QPointer isn't // thread-safe): item.splice(item.end(), m_transactions, m_transactions.begin()); // make local copies of the interesting stuff so // we can release the mutex again: command = item.front().command; nullSlot = !item.front().slot; oldCards = m_cardInfos; } qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: new iteration command=" << command << " ; nullSlot=" << nullSlot; // now, let's see what we got: if (nullSlot && command == quitTransaction.command) { return; // quit } if ((nullSlot && command == updateTransaction.command)) { std::vector > newCards = update_cardinfo(gpgAgent); newCards.resize(std::max(newCards.size(), oldCards.size())); oldCards.resize(std::max(newCards.size(), oldCards.size())); KDAB_SYNCHRONIZED(m_mutex) m_cardInfos = newCards; std::vector >::const_iterator nit = newCards.begin(), nend = newCards.end(), oit = oldCards.begin(), oend = oldCards.end(); unsigned int idx = 0; bool anyLC = false; bool anyNP = false; bool anyError = false; while (nit != nend && oit != oend) { const auto optr = (*oit).get(); const auto nptr = (*nit).get(); if ((optr && !nptr) || (!optr && nptr) || (optr && nptr && *optr != *nptr)) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: slot" << idx << ": card Changed"; Q_EMIT cardChanged(idx); } if ((*nit)->canLearnKeys()) { anyLC = true; } if ((*nit)->hasNullPin()) { anyNP = true; } if ((*nit)->status() == Card::CardError) { anyError = true; } ++nit; ++oit; ++idx; } Q_EMIT anyCardHasNullPinChanged(anyNP); Q_EMIT anyCardCanLearnKeysChanged(anyLC); if (anyError) { gpgAgent.reset(); } } else { GpgME::Error err; (void)gpgagent_transact(gpgAgent, command.constData(), err); KDAB_SYNCHRONIZED(m_mutex) // splice 'item' into m_finishedTransactions: m_finishedTransactions.splice(m_finishedTransactions.end(), item); Q_EMIT oneTransactionFinished(err); } } } private: mutable QMutex m_mutex; QWaitCondition m_waitForTransactions; const QString m_gnupgHomePath; // protected by m_mutex: std::vector > m_cardInfos; std::list m_transactions, m_finishedTransactions; }; } class ReaderStatus::Private : ReaderStatusThread { friend class Kleo::SmartCard::ReaderStatus; ReaderStatus *const q; public: explicit Private(ReaderStatus *qq) : ReaderStatusThread(qq), q(qq), watcher() { KDAB_SET_OBJECT_NAME(watcher); qRegisterMetaType("Kleo::SmartCard::Card::Status"); qRegisterMetaType("GpgME::Error"); watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status"))); watcher.addPath(Kleo::gnupgHomeDirectory()); watcher.setDelay(100); connect(this, &::ReaderStatusThread::cardChanged, q, &ReaderStatus::cardChanged); connect(this, &::ReaderStatusThread::anyCardHasNullPinChanged, q, &ReaderStatus::anyCardHasNullPinChanged); connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged, q, &ReaderStatus::anyCardCanLearnKeysChanged); connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping); } ~Private() { stop(); if (!wait(100)) { terminate(); wait(); } } private: bool anyCardHasNullPinImpl() const { const auto cis = cardInfos(); return std::any_of(cis.cbegin(), cis.cend(), [](const std::shared_ptr &ci) { return ci->hasNullPin(); }); } bool anyCardCanLearnKeysImpl() const { const auto cis = cardInfos(); return std::any_of(cis.cbegin(), cis.cend(), [](const std::shared_ptr &ci) { return ci->canLearnKeys(); }); } private: FileSystemWatcher watcher; }; ReaderStatus::ReaderStatus(QObject *parent) : QObject(parent), d(new Private(this)) { self = this; } ReaderStatus::~ReaderStatus() { self = nullptr; } // slot void ReaderStatus::startMonitoring() { d->start(); } // static ReaderStatus *ReaderStatus::mutableInstance() { return self; } // static const ReaderStatus *ReaderStatus::instance() { return self; } Card::Status ReaderStatus::cardStatus(unsigned int slot) const { return d->cardStatus(slot); } bool ReaderStatus::anyCardHasNullPin() const { return d->anyCardHasNullPinImpl(); } bool ReaderStatus::anyCardCanLearnKeys() const { return d->anyCardCanLearnKeysImpl(); } std::vector ReaderStatus::pinStates(unsigned int slot) const { const auto ci = d->cardInfos(); if (slot < ci.size()) { return ci[slot]->pinStates(); } else { return std::vector(); } } void ReaderStatus::startSimpleTransaction(const QByteArray &command, QObject *receiver, const char *slot) { const Transaction t = { command, receiver, slot }; d->addTransaction(t); } void ReaderStatus::updateStatus() { d->ping(); } std::vector > ReaderStatus::getCards() const { return d->cardInfos(); } #include "readerstatus.moc" diff --git a/src/utils/kdpipeiodevice.cpp b/src/utils/kdpipeiodevice.cpp index b3518439..aedd4e91 100644 --- a/src/utils/kdpipeiodevice.cpp +++ b/src/utils/kdpipeiodevice.cpp @@ -1,1048 +1,1048 @@ /* Copyright (C) 2007 Klarälvdalens Datakonsult AB KDPipeIODevice is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. KDPipeIODevice is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with KDPipeIODevice; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "kdpipeiodevice.h" #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #ifdef Q_OS_WIN32 # ifndef NOMINMAX # define NOMINMAX # endif # include # include #else # include # include #endif #ifndef KDAB_CHECK_THIS # define KDAB_CHECK_CTOR (void)1 # define KDAB_CHECK_DTOR KDAB_CHECK_CTOR # define KDAB_CHECK_THIS KDAB_CHECK_CTOR #endif #define LOCKED( d ) const QMutexLocker locker( &d->mutex ) #define synchronized( d ) if ( int i = 0 ) {} else for ( const QMutexLocker locker( &d->mutex ) ; !i ; ++i ) const unsigned int BUFFER_SIZE = 4096; const bool ALLOW_QIODEVICE_BUFFERING = true; namespace { KDPipeIODevice::DebugLevel s_debugLevel = KDPipeIODevice::NoDebug; } #define QDebug if( s_debugLevel == KDPipeIODevice::NoDebug ){}else qDebug namespace { class Reader : public QThread { Q_OBJECT public: Reader(int fd, Qt::HANDLE handle); ~Reader() override; qint64 readData(char *data, qint64 maxSize); unsigned int bytesInBuffer() const { return (wptr + sizeof buffer - rptr) % sizeof buffer; } bool bufferFull() const { return bytesInBuffer() == sizeof buffer - 1; } bool bufferEmpty() const { return bytesInBuffer() == 0; } bool bufferContains(char ch) { const unsigned int bib = bytesInBuffer(); for (unsigned int i = rptr; i < rptr + bib; ++i) if (buffer[i % sizeof buffer] == ch) { return true; } return false; } void notifyReadyRead(); Q_SIGNALS: void readyRead(); protected: void run() override; private: int fd; Qt::HANDLE handle; public: QMutex mutex; QWaitCondition waitForCancelCondition; QWaitCondition bufferNotFullCondition; QWaitCondition bufferNotEmptyCondition; QWaitCondition hasStarted; QWaitCondition readyReadSentCondition; QWaitCondition blockedConsumerIsDoneCondition; bool cancel; bool eof; bool error; bool eofShortCut; int errorCode; bool isReading; bool consumerBlocksOnUs; private: unsigned int rptr, wptr; char buffer[BUFFER_SIZE + 1]; // need to keep one byte free to detect empty state }; Reader::Reader(int fd_, Qt::HANDLE handle_) : QThread(), fd(fd_), handle(handle_), mutex(), bufferNotFullCondition(), bufferNotEmptyCondition(), hasStarted(), cancel(false), eof(false), error(false), eofShortCut(false), errorCode(0), isReading(false), consumerBlocksOnUs(false), rptr(0), wptr(0) { } Reader::~Reader() {} class Writer : public QThread { Q_OBJECT public: Writer(int fd, Qt::HANDLE handle); ~Writer() override; qint64 writeData(const char *data, qint64 size); unsigned int bytesInBuffer() const { return numBytesInBuffer; } bool bufferFull() const { return numBytesInBuffer == sizeof buffer; } bool bufferEmpty() const { return numBytesInBuffer == 0; } Q_SIGNALS: void bytesWritten(qint64); protected: void run() override; private: int fd; Qt::HANDLE handle; public: QMutex mutex; QWaitCondition bufferEmptyCondition; QWaitCondition bufferNotEmptyCondition; QWaitCondition hasStarted; bool cancel; bool error; int errorCode; private: unsigned int numBytesInBuffer; char buffer[BUFFER_SIZE]; }; } Writer::Writer(int fd_, Qt::HANDLE handle_) : QThread(), fd(fd_), handle(handle_), mutex(), bufferEmptyCondition(), bufferNotEmptyCondition(), hasStarted(), cancel(false), error(false), errorCode(0), numBytesInBuffer(0) { } Writer::~Writer() {} class KDPipeIODevice::Private : public QObject { Q_OBJECT friend class ::KDPipeIODevice; KDPipeIODevice *const q; public: explicit Private(KDPipeIODevice *qq); ~Private(); bool doOpen(int, Qt::HANDLE, OpenMode); bool startReaderThread(); bool startWriterThread(); void stopThreads(); public Q_SLOTS: void emitReadyRead(); private: int fd; Qt::HANDLE handle; Reader *reader; Writer *writer; bool triedToStartReader; bool triedToStartWriter; }; KDPipeIODevice::DebugLevel KDPipeIODevice::debugLevel() { return s_debugLevel; } void KDPipeIODevice::setDebugLevel(KDPipeIODevice::DebugLevel level) { s_debugLevel = level; } KDPipeIODevice::Private::Private(KDPipeIODevice *qq) : QObject(qq), q(qq), fd(-1), handle(nullptr), reader(nullptr), writer(nullptr), triedToStartReader(false), triedToStartWriter(false) { } KDPipeIODevice::Private::~Private() { QDebug("KDPipeIODevice::~Private(): Destroying %p", (void *) q); } KDPipeIODevice::KDPipeIODevice(QObject *p) : QIODevice(p), d(new Private(this)) { KDAB_CHECK_CTOR; } KDPipeIODevice::KDPipeIODevice(int fd, OpenMode mode, QObject *p) : QIODevice(p), d(new Private(this)) { KDAB_CHECK_CTOR; open(fd, mode); } KDPipeIODevice::KDPipeIODevice(Qt::HANDLE handle, OpenMode mode, QObject *p) : QIODevice(p), d(new Private(this)) { KDAB_CHECK_CTOR; open(handle, mode); } KDPipeIODevice::~KDPipeIODevice() { KDAB_CHECK_DTOR; if (isOpen()) { close(); } delete d; d = nullptr; } bool KDPipeIODevice::open(int fd, OpenMode mode) { KDAB_CHECK_THIS; #ifdef Q_OS_WIN32 return d->doOpen(fd, (HANDLE)_get_osfhandle(fd), mode); #else return d->doOpen(fd, nullptr, mode); #endif } bool KDPipeIODevice::open(Qt::HANDLE h, OpenMode mode) { KDAB_CHECK_THIS; #ifdef Q_OS_WIN32 return d->doOpen(-1, h, mode); #else Q_UNUSED(h); Q_UNUSED(mode); Q_ASSERT(!"KDPipeIODevice::open( Qt::HANDLE, OpenMode ) should never be called except on Windows."); return false; #endif } bool KDPipeIODevice::Private::startReaderThread() { if (triedToStartReader) { return true; } triedToStartReader = true; if (reader && !reader->isRunning() && !reader->isFinished()) { QDebug("KDPipeIODevice::Private::startReaderThread(): locking reader (CONSUMER THREAD)"); LOCKED(reader); QDebug("KDPipeIODevice::Private::startReaderThread(): locked reader (CONSUMER THREAD)"); reader->start(QThread::HighestPriority); QDebug("KDPipeIODevice::Private::startReaderThread(): waiting for hasStarted (CONSUMER THREAD)"); const bool hasStarted = reader->hasStarted.wait(&reader->mutex, 1000); QDebug("KDPipeIODevice::Private::startReaderThread(): returned from hasStarted (CONSUMER THREAD)"); return hasStarted; } return true; } bool KDPipeIODevice::Private::startWriterThread() { if (triedToStartWriter) { return true; } triedToStartWriter = true; if (writer && !writer->isRunning() && !writer->isFinished()) { LOCKED(writer); writer->start(QThread::HighestPriority); if (!writer->hasStarted.wait(&writer->mutex, 1000)) { return false; } } return true; } void KDPipeIODevice::Private::emitReadyRead() { QPointer thisPointer(this); QDebug("KDPipeIODevice::Private::emitReadyRead %p", (void *) this); Q_EMIT q->readyRead(); if (!thisPointer) { return; } if (reader) { QDebug("KDPipeIODevice::Private::emitReadyRead %p: locking reader (CONSUMER THREAD)", ( void *) this); synchronized(reader) { QDebug("KDPipeIODevice::Private::emitReadyRead %p: locked reader (CONSUMER THREAD)", ( void *) this); reader->readyReadSentCondition.wakeAll(); QDebug("KDPipeIODevice::Private::emitReadyRead %p: buffer empty: %d reader in ReadFile: %d", (void *)this, reader->bufferEmpty(), reader->isReading); } } QDebug("KDPipeIODevice::Private::emitReadyRead %p leaving", (void *) this); } bool KDPipeIODevice::Private::doOpen(int fd_, Qt::HANDLE handle_, OpenMode mode_) { if (q->isOpen()) { return false; } #ifdef Q_OS_WIN32 if (!handle_) { return false; } #else if (fd_ < 0) { return false; } #endif if (!(mode_ & ReadWrite)) { return false; // need to have at least read -or- write } std::unique_ptr reader_; std::unique_ptr writer_; if (mode_ & ReadOnly) { reader_.reset(new Reader(fd_, handle_)); QDebug("KDPipeIODevice::doOpen (%p): created reader (%p) for fd %d", (void *)this, (void *)reader_.get(), fd_); connect(reader_.get(), &Reader::readyRead, this, &Private::emitReadyRead, Qt::QueuedConnection); } if (mode_ & WriteOnly) { writer_.reset(new Writer(fd_, handle_)); QDebug("KDPipeIODevice::doOpen (%p): created writer (%p) for fd %d", (void *)this, (void *)writer_.get(), fd_); connect(writer_.get(), &Writer::bytesWritten, q, &QIODevice::bytesWritten, Qt::QueuedConnection); } // commit to *this: fd = fd_; handle = handle_; reader = reader_.release(); writer = writer_.release(); q->setOpenMode(mode_ | Unbuffered); return true; } int KDPipeIODevice::descriptor() const { KDAB_CHECK_THIS; return d->fd; } Qt::HANDLE KDPipeIODevice::handle() const { KDAB_CHECK_THIS; return d->handle; } qint64 KDPipeIODevice::bytesAvailable() const { KDAB_CHECK_THIS; const qint64 base = QIODevice::bytesAvailable(); if (!d->triedToStartReader) { d->startReaderThread(); return base; } if (d->reader) { synchronized(d->reader) { const qint64 inBuffer = d->reader->bytesInBuffer(); return base + inBuffer; } } return base; } qint64 KDPipeIODevice::bytesToWrite() const { KDAB_CHECK_THIS; d->startWriterThread(); const qint64 base = QIODevice::bytesToWrite(); if (d->writer) { synchronized(d->writer) return base + d->writer->bytesInBuffer(); } return base; } bool KDPipeIODevice::canReadLine() const { KDAB_CHECK_THIS; d->startReaderThread(); if (QIODevice::canReadLine()) { return true; } if (d->reader) { synchronized(d->reader) return d->reader->bufferContains('\n'); } return true; } bool KDPipeIODevice::isSequential() const { return true; } bool KDPipeIODevice::atEnd() const { KDAB_CHECK_THIS; d->startReaderThread(); if (!QIODevice::atEnd()) { QDebug("%p: KDPipeIODevice::atEnd returns false since QIODevice::atEnd does (with bytesAvailable=%ld)", (void *)this, static_cast(bytesAvailable())); return false; } if (!isOpen()) { return true; } if (d->reader->eofShortCut) { return true; } LOCKED(d->reader); const bool eof = (d->reader->error || d->reader->eof) && d->reader->bufferEmpty(); if (!eof) { if (!d->reader->error && !d->reader->eof) { QDebug("%p: KDPipeIODevice::atEnd returns false since !reader->error && !reader->eof", (void *)(this)); } if (!d->reader->bufferEmpty()) { QDebug("%p: KDPipeIODevice::atEnd returns false since !reader->bufferEmpty()", (void *) this); } } return eof; } bool KDPipeIODevice::waitForBytesWritten(int msecs) { KDAB_CHECK_THIS; d->startWriterThread(); Writer *const w = d->writer; if (!w) { return true; } LOCKED(w); QDebug("KDPipeIODevice::waitForBytesWritten (%p,w=%p): entered locked area", (void *)this, (void *) w); return w->bufferEmpty() || w->error || w->bufferEmptyCondition.wait(&w->mutex, msecs); } bool KDPipeIODevice::waitForReadyRead(int msecs) { KDAB_CHECK_THIS; QDebug("KDPipeIODEvice::waitForReadyRead()(%p)", (void *) this); d->startReaderThread(); if (ALLOW_QIODEVICE_BUFFERING) { if (bytesAvailable() > 0) { return true; } } Reader *const r = d->reader; if (!r || r->eofShortCut) { return true; } LOCKED(r); if (r->bytesInBuffer() != 0 || r->eof || r->error) { return true; } Q_ASSERT(false); // ### wtf? return r->bufferNotEmptyCondition.wait(&r->mutex, msecs); } template class TemporaryValue { public: TemporaryValue(T &var_, const T &tv) : var(var_), oldValue(var_) { var = tv; } ~TemporaryValue() { var = oldValue; } private: T &var; const T oldValue; }; bool KDPipeIODevice::readWouldBlock() const { d->startReaderThread(); LOCKED(d->reader); return d->reader->bufferEmpty() && !d->reader->eof && !d->reader->error; } bool KDPipeIODevice::writeWouldBlock() const { d->startWriterThread(); LOCKED(d->writer); return !d->writer->bufferEmpty() && !d->writer->error; } qint64 KDPipeIODevice::readData(char *data, qint64 maxSize) { KDAB_CHECK_THIS; - QDebug("%p: KDPipeIODevice::readData: data=%p, maxSize=%lld", (void *)this, data, maxSize); + QDebug("%p: KDPipeIODevice::readData: data=%s, maxSize=%lld", (void *)this, data, maxSize); d->startReaderThread(); Reader *const r = d->reader; Q_ASSERT(r); //assert( r->isRunning() ); // wrong (might be eof, error) Q_ASSERT(data || maxSize == 0); Q_ASSERT(maxSize >= 0); if (r->eofShortCut) { QDebug("%p: KDPipeIODevice::readData: hit eofShortCut, returning 0", (void *)this); return 0; } if (maxSize < 0) { maxSize = 0; } if (ALLOW_QIODEVICE_BUFFERING) { if (bytesAvailable() > 0) { maxSize = std::min(maxSize, bytesAvailable()); // don't block } } QDebug("%p: KDPipeIODevice::readData: try to lock reader (CONSUMER THREAD)", (void *) this); LOCKED(r); QDebug("%p: KDPipeIODevice::readData: locked reader (CONSUMER THREAD)", (void *) this); r->readyReadSentCondition.wakeAll(); if (/* maxSize > 0 && */ r->bufferEmpty() && !r->error && !r->eof) { // ### block on maxSize == 0? QDebug("%p: KDPipeIODevice::readData: waiting for bufferNotEmptyCondition (CONSUMER THREAD)", (void *) this); const TemporaryValue tmp(d->reader->consumerBlocksOnUs, true); r->bufferNotEmptyCondition.wait(&r->mutex); r->blockedConsumerIsDoneCondition.wakeAll(); QDebug("%p: KDPipeIODevice::readData: woke up from bufferNotEmptyCondition (CONSUMER THREAD)", (void *) this); } if (r->bufferEmpty()) { QDebug("%p: KDPipeIODevice::readData: got empty buffer, signal eof", (void *) this); // woken with an empty buffer must mean either EOF or error: Q_ASSERT(r->eof || r->error); r->eofShortCut = true; return r->eof ? 0 : -1; } QDebug("%p: KDPipeIODevice::readData: got bufferNotEmptyCondition, trying to read %lld bytes", (void *)this, maxSize); const qint64 bytesRead = r->readData(data, maxSize); QDebug("%p: KDPipeIODevice::readData: read %lld bytes", (void *)this, bytesRead); QDebug("%p (fd=%d): KDPipeIODevice::readData: %s", (void *)this, d->fd, data); return bytesRead; } qint64 Reader::readData(char *data, qint64 maxSize) { qint64 numRead = rptr < wptr ? wptr - rptr : sizeof buffer - rptr; if (numRead > maxSize) { numRead = maxSize; } - QDebug("%p: KDPipeIODevice::readData: data=%p, maxSize=%lld; rptr=%u, wptr=%u (bytesInBuffer=%u); -> numRead=%lld", + QDebug("%p: KDPipeIODevice::readData: data=%s, maxSize=%lld; rptr=%u, wptr=%u (bytesInBuffer=%u); -> numRead=%lld", (void *)this, data, maxSize, rptr, wptr, bytesInBuffer(), numRead); memcpy(data, buffer + rptr, numRead); rptr = (rptr + numRead) % sizeof buffer; if (!bufferFull()) { QDebug("%p: KDPipeIODevice::readData: signal bufferNotFullCondition", (void *) this); bufferNotFullCondition.wakeAll(); } return numRead; } qint64 KDPipeIODevice::writeData(const char *data, qint64 size) { KDAB_CHECK_THIS; d->startWriterThread(); Writer *const w = d->writer; Q_ASSERT(w); Q_ASSERT(w->error || w->isRunning()); Q_ASSERT(data || size == 0); Q_ASSERT(size >= 0); LOCKED(w); while (!w->error && !w->bufferEmpty()) { QDebug("%p: KDPipeIODevice::writeData: wait for empty buffer", (void *) this); w->bufferEmptyCondition.wait(&w->mutex); QDebug("%p: KDPipeIODevice::writeData: empty buffer signaled", (void *) this); } if (w->error) { return -1; } Q_ASSERT(w->bufferEmpty()); return w->writeData(data, size); } qint64 Writer::writeData(const char *data, qint64 size) { Q_ASSERT(bufferEmpty()); if (size > static_cast(sizeof buffer)) { size = sizeof buffer; } memcpy(buffer, data, size); numBytesInBuffer = size; if (!bufferEmpty()) { bufferNotEmptyCondition.wakeAll(); } return size; } void KDPipeIODevice::Private::stopThreads() { if (triedToStartWriter) { if (writer && q->bytesToWrite() > 0) { q->waitForBytesWritten(-1); } Q_ASSERT(q->bytesToWrite() == 0); } if (Reader *&r = reader) { disconnect(r, &Reader::readyRead, this, &Private::emitReadyRead); synchronized(r) { // tell thread to cancel: r->cancel = true; // and wake it, so it can terminate: r->waitForCancelCondition.wakeAll(); r->bufferNotFullCondition.wakeAll(); r->readyReadSentCondition.wakeAll(); } } if (Writer *&w = writer) { synchronized(w) { // tell thread to cancel: w->cancel = true; // and wake it, so it can terminate: w->bufferNotEmptyCondition.wakeAll(); } } } void KDPipeIODevice::close() { KDAB_CHECK_THIS; QDebug("KDPipeIODevice::close(%p)", (void *) this); if (!isOpen()) { return; } // tell clients we're about to close: Q_EMIT aboutToClose(); d->stopThreads(); #define waitAndDelete( t ) if ( t ) { t->wait(); QThread* const t2 = t; t = 0; delete t2; } QDebug("KPipeIODevice::close(%p): wait and closing writer %p", (void *)this, (void *) d->writer); waitAndDelete(d->writer); QDebug("KPipeIODevice::close(%p): wait and closing reader %p", (void *)this, (void *) d->reader); if (d->reader) { LOCKED(d->reader); d->reader->readyReadSentCondition.wakeAll(); } waitAndDelete(d->reader); #undef waitAndDelete #ifdef Q_OS_WIN32 if (d->fd != -1) { _close(d->fd); } else { CloseHandle(d->handle); } #else ::close(d->fd); #endif setOpenMode(NotOpen); d->fd = -1; d->handle = nullptr; } void Reader::run() { LOCKED(this); // too bad QThread doesn't have that itself; a signal isn't enough hasStarted.wakeAll(); QDebug("%p: Reader::run: started", (void *) this); while (true) { if (!cancel && (eof || error)) { //notify the client until the buffer is empty and then once //again so he receives eof/error. After that, wait for him //to cancel const bool wasEmpty = bufferEmpty(); QDebug("%p: Reader::run: received eof(%d) or error(%d), waking everyone", (void *)this, eof, error); notifyReadyRead(); if (!cancel && wasEmpty) { waitForCancelCondition.wait(&mutex); } } else if (!cancel && !bufferFull() && !bufferEmpty()) { QDebug("%p: Reader::run: buffer no longer empty, waking everyone", (void *) this); notifyReadyRead(); } while (!cancel && !error && bufferFull()) { notifyReadyRead(); if (!cancel && bufferFull()) { QDebug("%p: Reader::run: buffer is full, going to sleep", (void *)this); bufferNotFullCondition.wait(&mutex); } } if (cancel) { QDebug("%p: Reader::run: detected cancel", (void *)this); goto leave; } if (!eof && !error) { if (rptr == wptr) { // optimize for larger chunks in case the buffer is empty rptr = wptr = 0; } unsigned int numBytes = (rptr + sizeof buffer - wptr - 1) % sizeof buffer; if (numBytes > sizeof buffer - wptr) { numBytes = sizeof buffer - wptr; } QDebug("%p: Reader::run: rptr=%d, wptr=%d -> numBytes=%d", (void *)this, rptr, wptr, numBytes); Q_ASSERT(numBytes > 0); QDebug("%p: Reader::run: trying to read %d bytes from fd %d", (void *)this, numBytes, fd); #ifdef Q_OS_WIN32 isReading = true; mutex.unlock(); DWORD numRead; const bool ok = ReadFile(handle, buffer + wptr, numBytes, &numRead, 0); mutex.lock(); isReading = false; if (ok) { if (numRead == 0) { QDebug("%p: Reader::run: got eof (numRead==0)", (void *) this); eof = true; } } else { // !ok errorCode = static_cast(GetLastError()); if (errorCode == ERROR_BROKEN_PIPE) { Q_ASSERT(numRead == 0); QDebug("%p: Reader::run: got eof (broken pipe)", (void *) this); eof = true; } else { Q_ASSERT(numRead == 0); QDebug("%p: Reader::run: got error: %s (%d)", (void *) this, strerror(errorCode), errorCode); error = true; } } #else qint64 numRead; mutex.unlock(); do { numRead = ::read(fd, buffer + wptr, numBytes); } while (numRead == -1 && errno == EINTR); mutex.lock(); if (numRead < 0) { errorCode = errno; error = true; QDebug("%p: Reader::run: got error: %d", (void *)this, errorCode); } else if (numRead == 0) { QDebug("%p: Reader::run: eof detected", (void *)this); eof = true; } #endif QDebug("%p (fd=%d): Reader::run: read %ld bytes", (void *) this, fd, static_cast(numRead)); QDebug("%p (fd=%d): Reader::run: %s", (void *)this, fd, buffer); if (numRead > 0) { QDebug("%p: Reader::run: buffer before: rptr=%4d, wptr=%4d", (void *)this, rptr, wptr); wptr = (wptr + numRead) % sizeof buffer; QDebug("%p: Reader::run: buffer after: rptr=%4d, wptr=%4d", (void *)this, rptr, wptr); } } } leave: QDebug("%p: Reader::run: terminated", (void *)this); } void Reader::notifyReadyRead() { QDebug("notifyReadyRead: %d bytes available", bytesInBuffer()); Q_ASSERT(!cancel); if (consumerBlocksOnUs) { bufferNotEmptyCondition.wakeAll(); blockedConsumerIsDoneCondition.wait(&mutex); return; } QDebug("notifyReadyRead: Q_EMIT signal"); Q_EMIT readyRead(); readyReadSentCondition.wait(&mutex); QDebug("notifyReadyRead: returning from waiting, leave"); } void Writer::run() { LOCKED(this); // too bad QThread doesn't have that itself; a signal isn't enough hasStarted.wakeAll(); qCDebug(KLEOPATRA_LOG) << this << "Writer::run: started"; while (true) { while (!cancel && bufferEmpty()) { qCDebug(KLEOPATRA_LOG) << this << "Writer::run: buffer is empty, wake bufferEmptyCond listeners"; bufferEmptyCondition.wakeAll(); Q_EMIT bytesWritten(0); qCDebug(KLEOPATRA_LOG) << this << "Writer::run: buffer is empty, going to sleep"; bufferNotEmptyCondition.wait(&mutex); qCDebug(KLEOPATRA_LOG) << this << "Writer::run: woke up"; } if (cancel) { qCDebug(KLEOPATRA_LOG) << this << "Writer::run: detected cancel"; goto leave; } Q_ASSERT(numBytesInBuffer > 0); qCDebug(KLEOPATRA_LOG) << this << "Writer::run: Trying to write " << numBytesInBuffer << "bytes"; qint64 totalWritten = 0; do { mutex.unlock(); #ifdef Q_OS_WIN32 DWORD numWritten; QDebug("%p (fd=%d): Writer::run: buffer before WriteFile (numBytes=%lld): %s:", (void *) this, fd, numBytesInBuffer, buffer); QDebug("%p (fd=%d): Writer::run: Going into WriteFile", (void *) this, fd); if (!WriteFile(handle, buffer + totalWritten, numBytesInBuffer - totalWritten, &numWritten, 0)) { mutex.lock(); errorCode = static_cast(GetLastError()); QDebug("%p: Writer::run: got error code: %d", (void *) this, errorCode); error = true; goto leave; } #else qint64 numWritten; do { numWritten = ::write(fd, buffer + totalWritten, numBytesInBuffer - totalWritten); } while (numWritten == -1 && errno == EINTR); if (numWritten < 0) { mutex.lock(); errorCode = errno; QDebug("%p: Writer::run: got error code: %s (%d)", (void *)this, strerror(errorCode), errorCode); error = true; goto leave; } #endif QDebug("%p (fd=%d): Writer::run: buffer after WriteFile (numBytes=%u): %s:", (void *)this, fd, numBytesInBuffer, buffer); totalWritten += numWritten; mutex.lock(); } while (totalWritten < numBytesInBuffer); qCDebug(KLEOPATRA_LOG) << this << "Writer::run: wrote " << totalWritten << "bytes"; numBytesInBuffer = 0; qCDebug(KLEOPATRA_LOG) << this << "Writer::run: buffer is empty, wake bufferEmptyCond listeners"; bufferEmptyCondition.wakeAll(); Q_EMIT bytesWritten(totalWritten); } leave: qCDebug(KLEOPATRA_LOG) << this << "Writer::run: terminating"; numBytesInBuffer = 0; qCDebug(KLEOPATRA_LOG) << this << "Writer::run: buffer is empty, wake bufferEmptyCond listeners"; bufferEmptyCondition.wakeAll(); Q_EMIT bytesWritten(0); } // static std::pair KDPipeIODevice::makePairOfConnectedPipes() { KDPipeIODevice *read = nullptr; KDPipeIODevice *write = nullptr; #ifdef Q_OS_WIN32 HANDLE rh; HANDLE wh; SECURITY_ATTRIBUTES sa; memset(&sa, 0, sizeof(sa)); sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; if (CreatePipe(&rh, &wh, &sa, BUFFER_SIZE)) { read = new KDPipeIODevice; read->open(rh, ReadOnly); write = new KDPipeIODevice; write->open(wh, WriteOnly); } #else int fds[2]; if (pipe(fds) == 0) { read = new KDPipeIODevice; read->open(fds[0], ReadOnly); write = new KDPipeIODevice; write->open(fds[1], WriteOnly); } #endif return std::make_pair(read, write); } #ifdef KDAB_DEFINE_CHECKS KDAB_DEFINE_CHECKS(KDPipeIODevice) { if (!isOpen()) { Q_ASSERT(openMode() == NotOpen); Q_ASSERT(!d->reader); Q_ASSERT(!d->writer); #ifdef Q_OS_WIN32 Q_ASSERT(!d->handle); #else Q_ASSERT(d->fd < 0); #endif } else { Q_ASSERT(openMode() != NotOpen); Q_ASSERT(openMode() & ReadWrite); if (openMode() & ReadOnly) { Q_ASSERT(d->reader); synchronized(d->reader) Q_ASSERT(d->reader->eof || d->reader->error || d->reader->isRunning()); } if (openMode() & WriteOnly) { Q_ASSERT(d->writer); synchronized(d->writer) Q_ASSERT(d->writer->error || d->writer->isRunning()); } #ifdef Q_OS_WIN32 Q_ASSERT(d->handle); #else Q_ASSERT(d->fd >= 0); #endif } } #endif // KDAB_DEFINE_CHECKS #include "kdpipeiodevice.moc" diff --git a/src/utils/path-helper.cpp b/src/utils/path-helper.cpp index f74fda24..e47f7442 100644 --- a/src/utils/path-helper.cpp +++ b/src/utils/path-helper.cpp @@ -1,173 +1,173 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/path-helper.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2009 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "path-helper.h" #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include using namespace Kleo; static QString commonPrefix(const QString &s1, const QString &s2) { return QString(s1.data(), std::mismatch(s1.data(), s1.data() + std::min(s1.size(), s2.size()), s2.data()).first - s1.data()); } static QString longestCommonPrefix(const QStringList &sl) { if (sl.empty()) { return QString(); } QString result = sl.front(); for (const QString &s : sl) { result = commonPrefix(s, result); } return result; } QString Kleo::heuristicBaseDirectory(const QStringList &fileNames) { QStringList dirs; for (const QString &fileName : fileNames) { dirs.push_back(QFileInfo(fileName).path() + QLatin1Char('/')); } qCDebug(KLEOPATRA_LOG) << "dirs" << dirs; const QString candidate = longestCommonPrefix(dirs); const int idx = candidate.lastIndexOf(QLatin1Char('/')); return candidate.left(idx); } QStringList Kleo::makeRelativeTo(const QString &base, const QStringList &fileNames) { if (base.isEmpty()) { return fileNames; } else { return makeRelativeTo(QDir(base), fileNames); } } QStringList Kleo::makeRelativeTo(const QDir &baseDir, const QStringList &fileNames) { QStringList rv; rv.reserve(fileNames.size()); std::transform(fileNames.cbegin(), fileNames.cend(), std::back_inserter(rv), [&baseDir](const QString &file) { return baseDir.relativeFilePath(file); }); return rv; } void Kleo::recursivelyRemovePath(const QString &path) { const QFileInfo fi(path); if (fi.isDir()) { QDir dir(path); Q_FOREACH (const QString &fname, dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot)) { recursivelyRemovePath(dir.filePath(fname)); } const QString dirName = fi.fileName(); dir.cdUp(); if (!dir.rmdir(dirName)) { throw Exception(GPG_ERR_EPERM, i18n("Cannot remove directory %1", path)); } } else { QFile file(path); if (!file.remove()) { throw Exception(GPG_ERR_EPERM, i18n("Cannot remove file %1: %2", path, file.errorString())); } } } bool Kleo::recursivelyCopy(const QString &src,const QString &dest) { QDir srcDir(src); if(!srcDir.exists()) { return false; } QDir destDir(dest); if(!destDir.exists() && !destDir.mkdir(dest)) { return false; } - for(const auto file: srcDir.entryList(QDir::Files)) { + for(const auto &file: srcDir.entryList(QDir::Files)) { const QString srcName = src + QDir::separator() + file; const QString destName = dest + QDir::separator() + file; if(!QFile::copy(srcName, destName)) { return false; } } - for (const auto dir: srcDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) { + for (const auto &dir: srcDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) { const QString srcName = src + QDir::separator() + dir; const QString destName = dest + QDir::separator() + dir; if (!recursivelyCopy(srcName, destName)) { return false; } } return true; } bool Kleo::moveDir(const QString &src, const QString &dest) { if (QStorageInfo(src).device() == QStorageInfo(dest).device()) { // Easy same partition we can use qt. return QFile::rename(src, dest); } // first copy if (!recursivelyCopy(src, dest)) { return false; } // Then delete original recursivelyRemovePath(src); return true; } diff --git a/src/view/pgpcardwidget.cpp b/src/view/pgpcardwidget.cpp index 7750aabe..8a81e40c 100644 --- a/src/view/pgpcardwidget.cpp +++ b/src/view/pgpcardwidget.cpp @@ -1,506 +1,506 @@ /* view/pgpcardwiget.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "pgpcardwidget.h" #include "kleopatra_debug.h" #include "smartcard/openpgpcard.h" #include "smartcard/readerstatus.h" #include "dialogs/gencardkeydialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if GPGMEPP_VERSION > 0x10801 // 1.8.1 // TODO remove ifdef once > 1.8.1 is required #include # define GPGME_CAN_GENCARDKEY #endif using namespace Kleo; using namespace Kleo::SmartCard; namespace { #ifdef GPGME_CAN_GENCARDKEY class GenKeyThread: public QThread { Q_OBJECT public: explicit GenKeyThread(const GenCardKeyDialog::KeyParams ¶ms, const std::string &serial): mSerial(serial), mParams(params) { } GpgME::Error error() { return mErr; } std::string bkpFile() { return mBkpFile; } protected: void run() override { GpgME::GpgGenCardKeyInteractor *ei = new GpgME::GpgGenCardKeyInteractor(mSerial); ei->setKeySize(mParams.keysize); ei->setNameUtf8(mParams.name.toStdString()); ei->setEmailUtf8(mParams.email.toStdString()); ei->setDoBackup(mParams.backup); const auto ctx = std::shared_ptr (GpgME::Context::createForProtocol(GpgME::OpenPGP)); QGpgME::QByteArrayDataProvider dp; GpgME::Data data(&dp); mErr = ctx->cardEdit(GpgME::Key(), std::unique_ptr (ei), data); mBkpFile = ei->backupFileName(); } private: GpgME::Error mErr; std::string mSerial; GenCardKeyDialog::KeyParams mParams; std::string mBkpFile; }; #endif } // Namespace PGPCardWidget::PGPCardWidget(): mSerialNumber(new QLabel), mCardHolderLabel(new QLabel), mVersionLabel(new QLabel), mSigningKey(new QLabel), mEncryptionKey(new QLabel), mAuthKey(new QLabel), mUrlLabel(new QLabel), mCardIsEmpty(false) { auto grid = new QGridLayout; int row = 0; // Set up the scroll are auto area = new QScrollArea; area->setFrameShape(QFrame::NoFrame); area->setWidgetResizable(true); auto areaWidget = new QWidget; auto areaVLay = new QVBoxLayout(areaWidget); areaVLay->addLayout(grid); areaVLay->addStretch(1); area->setWidget(areaWidget); auto myLayout = new QVBoxLayout(this); myLayout->addWidget(area); // Version and Serialnumber grid->addWidget(mVersionLabel, row++, 0, 1, 2); mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); grid->addWidget(new QLabel(i18n("Serial number:")), row, 0); grid->addWidget(mSerialNumber, row++, 1); mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction); // Cardholder Row grid->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.", "Cardholder:")), row, 0); grid->addWidget(mCardHolderLabel, row, 1); mCardHolderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); auto nameButtton = new QPushButton; nameButtton->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); nameButtton->setToolTip(i18n("Change")); grid->addWidget(nameButtton, row++, 2); connect(nameButtton, &QPushButton::clicked, this, &PGPCardWidget::changeNameRequested); // URL Row grid->addWidget(new QLabel(i18nc("The URL under which a public key that " "corresponds to a smartcard can be downloaded", "Pubkey URL:")), row, 0); grid->addWidget(mUrlLabel, row, 1); mUrlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); auto urlButtton = new QPushButton; urlButtton->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); urlButtton->setToolTip(i18n("Change")); grid->addWidget(urlButtton, row++, 2); connect(urlButtton, &QPushButton::clicked, this, &PGPCardWidget::changeUrlRequested); // The keys auto line1 = new QFrame(); line1->setFrameShape(QFrame::HLine); grid->addWidget(line1, row++, 0, 1, 4); grid->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Keys:"))), row++, 0); grid->addWidget(new QLabel(i18n("Signature:")), row, 0); grid->addWidget(mSigningKey, row++, 1); mSigningKey->setTextInteractionFlags(Qt::TextBrowserInteraction); grid->addWidget(new QLabel(i18n("Encryption:")), row, 0); grid->addWidget(mEncryptionKey, row++, 1); mEncryptionKey->setTextInteractionFlags(Qt::TextBrowserInteraction); grid->addWidget(new QLabel(i18n("Authentication:")), row, 0); grid->addWidget(mAuthKey, row++, 1); mAuthKey->setTextInteractionFlags(Qt::TextBrowserInteraction); auto line2 = new QFrame(); line2->setFrameShape(QFrame::HLine); grid->addWidget(line2, row++, 0, 1, 4); grid->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Actions:"))), row++, 0); auto actionLayout = new QHBoxLayout; #ifdef GPGME_CAN_GENCARDKEY auto generateButton = new QPushButton(i18n("Generate new Keys")); generateButton->setToolTip(i18n("Create a new primary key and generate subkeys on the card.")); actionLayout->addWidget(generateButton); connect(generateButton, &QPushButton::clicked, this, &PGPCardWidget::genkeyRequested); #endif auto pinButtton = new QPushButton(i18n("Change PIN")); pinButtton->setToolTip(i18n("Change the PIN required to unblock the smartcard.")); actionLayout->addWidget(pinButtton); connect(pinButtton, &QPushButton::clicked, this, [this] () {doChangePin(1);}); auto pukButton = new QPushButton(i18n("Change Admin PIN")); pukButton->setToolTip(i18n("Change the PIN required to unlock the smartcard.")); actionLayout->addWidget(pukButton); connect(pukButton, &QPushButton::clicked, this, [this] () {doChangePin(3);}); auto resetCodeButton = new QPushButton(i18n("Change Reset Code")); pukButton->setToolTip(i18n("Change the PIN required to reset the smartcard to an empty state.")); actionLayout->addWidget(resetCodeButton); connect(resetCodeButton, &QPushButton::clicked, this, [this] () {doChangePin(2);}); actionLayout->addStretch(-1); grid->addLayout(actionLayout, row++, 0, 1, 4); grid->setColumnStretch(4, -1); } void PGPCardWidget::setCard(const OpenPGPCard *card) { const QString version = QString::fromStdString(card->cardVersion()); mIs21 = version == QLatin1String("2.1"); mVersionLabel->setText(i18nc("First placeholder is manufacturer, second placeholder is a version number", "%1 OpenPGP v%2 card", QString::fromStdString(card->manufacturer()), version)); const QString sn = QString::fromStdString(card->serialNumber()).mid(16, 12); mSerialNumber->setText(sn); mRealSerial = card->serialNumber(); const auto holder = QString::fromStdString(card->cardHolder()); const auto url = QString::fromStdString(card->pubkeyUrl()); mCardHolderLabel->setText(holder.isEmpty() ? i18n("not set") : holder); mUrl = url; mUrlLabel->setText(url.isEmpty() ? i18n("not set") : QStringLiteral("%1").arg(url.toHtmlEscaped())); mUrlLabel->setOpenExternalLinks(true); updateKey(mSigningKey, card->sigFpr()); updateKey(mEncryptionKey, card->encFpr()); updateKey(mAuthKey, card->authFpr()); mCardIsEmpty = card->authFpr().empty() && card->sigFpr().empty() && card->encFpr().empty(); } void PGPCardWidget::doChangePin(int slot) { ReaderStatus::mutableInstance() ->startSimpleTransaction(QStringLiteral("SCD PASSWD %1").arg(slot).toUtf8().constData(), this, "changePinResult"); } #ifdef GPGME_CAN_GENCARDKEY void PGPCardWidget::doGenKey(GenCardKeyDialog *dlg) { const auto params = dlg->getKeyParams(); delete dlg; auto progress = new QProgressDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog); progress->setAutoClose(true); progress->setMinimumDuration(0); progress->setMaximum(0); progress->setMinimum(0); progress->setModal(true); progress->setCancelButton(nullptr); progress->setWindowTitle(i18n("Generating keys")); progress->setLabel(new QLabel(i18n("This may take several minutes..."))); GenKeyThread *workerThread = new GenKeyThread(params, mRealSerial); connect(workerThread, &QThread::finished, this, [this, workerThread, progress] { progress->accept(); delete progress; genKeyDone(workerThread->error(), workerThread->bkpFile()); delete workerThread; }); workerThread->start(); progress->exec(); } void PGPCardWidget::genKeyDone(const GpgME::Error &err, const std::string &backup) { if (err) { KMessageBox::error(this, i18nc("@info", "Failed to generate new key: %1", QString::fromLatin1(err.asString())), i18nc("@title", "Error")); return; } if (err.isCanceled()) { return; } if (!backup.empty()) { const auto bkpFile = QString::fromStdString(backup); QFileInfo fi(bkpFile); const auto target = QFileDialog::getSaveFileName(this, i18n("Save backup of encryption key"), fi.fileName(), QStringLiteral("%1 (*.gpg)").arg(i18n("Backup Key"))); if (!target.isEmpty() && !QFile::copy(bkpFile, target)) { KMessageBox::error(this, i18nc("@info", "Failed to move backup. The backup key is still stored under: %1", bkpFile), i18nc("@title", "Error")); } else if (!target.isEmpty()) { QFile::remove(bkpFile); } } KMessageBox::information(this, i18nc("@info", "Successfully generated a new key for this card."), i18nc("@title", "Success")); } #else void PGPCardWidget::doGenKey(GenCardKeyDialog *) {} void PGPCardWidget::genKeyDone(const GpgME::Error &, const std::string &) {} #endif void PGPCardWidget::genkeyRequested() { if (!mCardIsEmpty) { auto ret = KMessageBox::warningContinueCancel(this, i18n("The existing keys on this card will be deleted " "and replaced by new keys.") + QStringLiteral("

    ") + i18n("It will no longer be possible to decrypt past communication " "encrypted for the existing key."), i18n("Secret Key Deletion"), KStandardGuiItem::guiItem(KStandardGuiItem::Delete), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (ret != KMessageBox::Continue) { return; } } GenCardKeyDialog *dlg = new GenCardKeyDialog(this); std::vector sizes; sizes.push_back(1024); sizes.push_back(2048); sizes.push_back(3072); // There is probably a better way to check for capabilities if (mIs21) { sizes.push_back(4096); } dlg->setSupportedSizes(sizes); connect(dlg, &QDialog::accepted, this, [this, dlg] () {doGenKey(dlg);}); connect(dlg, &QDialog::rejected, dlg, &QObject::deleteLater); dlg->setModal(true); dlg->show(); } void PGPCardWidget::changePinResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "PIN change failed: %1", QString::fromLatin1(err.asString())), i18nc("@title", "Error")); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "Code successfully changed."), i18nc("@title", "Success")); } } void PGPCardWidget::changeNameRequested() { QString text = mCardHolderLabel->text(); while (true) { bool ok = false; text = QInputDialog::getText(this, i18n("Change cardholder"), i18n("New name:"), QLineEdit::Normal, text, &ok, Qt::WindowFlags(), Qt::ImhLatinOnly); if (!ok) { return; } // Some additional restrictions imposed by gnupg if (text.contains(QLatin1Char('<'))) { KMessageBox::error(this, i18nc("@info", "The \"<\" character may not be used."), i18nc("@title", "Error")); continue; } if (text.contains(QLatin1String(" "))) { KMessageBox::error(this, i18nc("@info", "Double spaces are not allowed"), i18nc("@title", "Error")); continue; } if (text.size() > 38) { KMessageBox::error(this, i18nc("@info", "The size of the name may not exceed 38 characters."), i18nc("@title", "Error")); } break; } auto parts = text.split(QLatin1Char(' ')); const auto lastName = parts.takeLast(); - const auto formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<')); + const QString formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<')); ReaderStatus::mutableInstance() ->startSimpleTransaction(QStringLiteral("SCD SETATTR DISP-NAME %1").arg(formatted).toUtf8().constData(), this, "changeNameResult"); } void PGPCardWidget::changeNameResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "Name change failed: %1", QString::fromLatin1(err.asString())), i18nc("@title", "Error")); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "Name successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::changeUrlRequested() { QString text = mUrl; while (true) { bool ok = false; text = QInputDialog::getText(this, i18n("Change the URL where the pubkey can be found"), i18n("New pubkey URL:"), QLineEdit::Normal, text, &ok, Qt::WindowFlags(), Qt::ImhLatinOnly); if (!ok) { return; } // Some additional restrictions imposed by gnupg if (text.size() > 254) { KMessageBox::error(this, i18nc("@info", "The size of the URL may not exceed 254 characters."), i18nc("@title", "Error")); } break; } ReaderStatus::mutableInstance() ->startSimpleTransaction(QStringLiteral("SCD SETATTR PUBKEY-URL %1").arg(text).toUtf8().constData(), this, "changeUrlResult"); } void PGPCardWidget::changeUrlResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "URL change failed: %1", QString::fromLatin1(err.asString())), i18nc("@title", "Error")); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "URL successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::updateKey(QLabel *label, const std::string &fpr) { label->setText(QString::fromStdString(fpr)); if (fpr.empty()) { label->setText(i18n("Slot empty")); return; } std::vector vec; std::string keyid = fpr; keyid.erase(0, keyid.size() - 16); vec.push_back(keyid); const auto subkeys = KeyCache::instance()->findSubkeysByKeyID(vec); if (subkeys.empty() || subkeys[0].isNull()) { label->setToolTip(i18n("Public key not found.")); return; } QStringList toolTips; for (const auto &sub: subkeys) { // Yep you can have one subkey associated with multiple // primary keys. toolTips << Formatting::toolTip(sub.parent(), Formatting::Validity | Formatting::StorageLocation | Formatting::ExpiryDates | Formatting::UserIDs | Formatting::Fingerprint); } label->setToolTip(toolTips.join(QStringLiteral("
    "))); return; } #include "pgpcardwidget.moc" diff --git a/src/view/searchbar.cpp b/src/view/searchbar.cpp index c37c58bd..1fd3873a 100644 --- a/src/view/searchbar.cpp +++ b/src/view/searchbar.cpp @@ -1,208 +1,208 @@ /* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*- view/searchbar.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "searchbar.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; class SearchBar::Private { friend class ::Kleo::SearchBar; SearchBar *const q; public: explicit Private(SearchBar *qq); ~Private(); private: void slotKeyFilterChanged(int idx) { Q_EMIT q->keyFilterChanged(keyFilter(idx)); } std::shared_ptr keyFilter(int idx) const { const QModelIndex mi = KeyFilterManager::instance()->model()->index(idx, 0); return KeyFilterManager::instance()->fromModelIndex(mi); } std::shared_ptr currentKeyFilter() const { return keyFilter(combo->currentIndex()); } QString currentKeyFilterID() const { if (const std::shared_ptr f = currentKeyFilter()) { return f->id(); } else { return QString(); } } void listNotCertifiedKeys() const { lineEdit->clear(); combo->setCurrentIndex(combo->findData(QStringLiteral("not-validated-certificates"))); Q_EMIT q->keyFilterChanged(keyFilter(combo->currentIndex())); } /* List all OpenPGP keys and see if we find one with a UID that is * not at least fully valid. If we find one, show the certify * button. */ /* XXX: It would be nice to do this every time the user certifies * a key. */ void showOrHideCertifyButton() const { QGpgME::KeyListJob *job = QGpgME::openpgp()->keyListJob(); connect(job, &QGpgME::KeyListJob::result, job, - [this, job](GpgME::KeyListResult, std::vector keys, QString, GpgME::Error) + [this, job](const GpgME::KeyListResult&, const std::vector &keys, const QString&, const GpgME::Error&) { for (const auto &key: keys) { if (Kleo::keyValidity(key) < GpgME::UserID::Validity::Full) { certifyButton->show(); return; } } certifyButton->hide(); }); job->start(QStringList()); } private: QLineEdit *lineEdit; QComboBox *combo; QPushButton *certifyButton; }; SearchBar::Private::Private(SearchBar *qq) : q(qq) { QHBoxLayout *layout = new QHBoxLayout(q); layout->setMargin(0); lineEdit = new QLineEdit(q); lineEdit->setClearButtonEnabled(true); lineEdit->setPlaceholderText(i18n("Search...")); layout->addWidget(lineEdit, /*stretch=*/1); combo = new QComboBox(q); layout->addWidget(combo); certifyButton = new QPushButton(q); certifyButton->setIcon(QIcon::fromTheme(QStringLiteral("security-medium"))); certifyButton->setToolTip(i18n("Some certificates are not yet certified. " "Click here to see a list of these certificates." "

    " "Certification is required to make sure that the certificates " "actually belong to the identity they claim to belong to.")); certifyButton->hide(); layout->addWidget(certifyButton); showOrHideCertifyButton(); combo->setModel(KeyFilterManager::instance()->model()); KDAB_SET_OBJECT_NAME(layout); KDAB_SET_OBJECT_NAME(lineEdit); KDAB_SET_OBJECT_NAME(combo); KDAB_SET_OBJECT_NAME(certifyButton); connect(lineEdit, &QLineEdit::textChanged, q, &SearchBar::stringFilterChanged); connect(combo, SIGNAL(currentIndexChanged(int)), q, SLOT(slotKeyFilterChanged(int))); connect(certifyButton, SIGNAL(clicked()), q, SLOT(listNotCertifiedKeys())); } SearchBar::Private::~Private() {} SearchBar::SearchBar(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), d(new Private(this)) { } SearchBar::~SearchBar() {} void SearchBar::updateClickMessage(const QString &shortcutStr) { d->lineEdit->setPlaceholderText(i18n("Search...<%1>", shortcutStr)); } // slot void SearchBar::setStringFilter(const QString &filter) { d->lineEdit->setText(filter); } // slot void SearchBar::setKeyFilter(const std::shared_ptr &kf) { const QModelIndex idx = KeyFilterManager::instance()->toModelIndex(kf); if (idx.isValid()) { d->combo->setCurrentIndex(idx.row()); } else { d->combo->setCurrentIndex(0); } } // slot void SearchBar::setChangeStringFilterEnabled(bool on) { d->lineEdit->setEnabled(on); } // slot void SearchBar::setChangeKeyFilterEnabled(bool on) { d->combo->setEnabled(on); } QLineEdit *SearchBar::lineEdit() const { return d->lineEdit; } #include "moc_searchbar.cpp" diff --git a/src/view/welcomewidget.cpp b/src/view/welcomewidget.cpp index 148c57b8..9925ed3b 100644 --- a/src/view/welcomewidget.cpp +++ b/src/view/welcomewidget.cpp @@ -1,159 +1,159 @@ /* view/smartcardwidget.h This file is part of Kleopatra, the KDE keymanager Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "welcomewidget.h" #include #include #include #include #include #include #include "commands/importcertificatefromfilecommand.h" #include "commands/newcertificatecommand.h" #include static const QString templ = QStringLiteral( "

    %1

    " // Welcome "

    %2

    %3

    " // Intro + Explanation "
    • %4
    • %5
    " // "

    %6

    " // More info ""); using namespace Kleo; class WelcomeWidget::Private { public: Private(WelcomeWidget *qq): q(qq) { auto vLay = new QVBoxLayout(q); auto hLay = new QHBoxLayout; const QString welcome = i18nc("%1 is version", "Welcome to Kleopatra %1", - QString::fromLatin1(KLEOPATRA_VERSION_STRING)); + QStringLiteral(KLEOPATRA_VERSION_STRING)); const QString introduction = i18n("Kleopatra is a front-end for the crypto software GnuPG."); const QString keyExplanation = i18n("For most actions you need either a public key (certificate) or your own private key."); const QString privateKeyExplanation = i18n("The private key is needed to decrypt or sign."); const QString publicKeyExplanation = i18n("The public key can be used by others to verify your identity or encrypt to you."); const QString wikiUrl = i18nc("More info about public key cryptography, please link to your local version of Wikipedia", "https://en.wikipedia.org/wiki/Public-key_cryptography"); const QString learnMore = i18nc("%1 is link a wiki article", "You can learn more about this on Wikipedia.", wikiUrl); auto label = new QLabel(templ.arg(welcome).arg(introduction).arg(keyExplanation).arg(privateKeyExplanation).arg(publicKeyExplanation).arg(learnMore)); label->setTextInteractionFlags(Qt::TextBrowserInteraction); label->setOpenExternalLinks(true); auto genKeyAction = new QAction(q); genKeyAction->setText(i18n("New Key Pair...")); genKeyAction->setIcon(QIcon::fromTheme(QStringLiteral("view-certificate-add"))); auto importAction = new QAction(q); importAction->setText(i18n("Import...")); importAction->setIcon(QIcon::fromTheme(QStringLiteral("view-certificate-import"))); connect(importAction, &QAction::triggered, q, [this] () { import(); }); connect(genKeyAction, &QAction::triggered, q, [this] () { generate(); }); mGenerateBtn = new QToolButton(); mGenerateBtn->setDefaultAction(genKeyAction); mGenerateBtn->setIconSize(QSize(64, 64)); mGenerateBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); mGenerateBtn->setToolTip(i18n("Create a new OpenPGP key pair") + QStringLiteral("
    ") + i18n("To create an S/MIME certificate request use \"New Key Pair\" from the 'File' Menu instead")); mImportBtn = new QToolButton(); mImportBtn->setDefaultAction(importAction); mImportBtn->setIconSize(QSize(64, 64)); mImportBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); mImportBtn->setToolTip(i18n("Import from a file.") + QStringLiteral("
    ") + i18n("To import from a public keyserver use \"Lookup on Server\" instead.")); auto btnLayout = new QHBoxLayout; btnLayout->addStretch(-1); btnLayout->addWidget(mGenerateBtn); btnLayout->addWidget(mImportBtn); btnLayout->addStretch(-1); vLay->addStretch(-1); vLay->addLayout(hLay); vLay->addLayout(btnLayout); vLay->addStretch(-1); hLay->addStretch(-1); hLay->addWidget(label); hLay->addStretch(-1); } void import() { mImportBtn->setEnabled(false); auto cmd = new Kleo::ImportCertificateFromFileCommand(); cmd->setParentWidget(q); QObject::connect(cmd, &Kleo::ImportCertificateFromFileCommand::finished, q, [this]() { mImportBtn->setEnabled(true); }); cmd->start(); } void generate() { mGenerateBtn->setEnabled(false); auto cmd = new Commands::NewCertificateCommand(); cmd->setProtocol(GpgME::OpenPGP); cmd->setParentWidget(q); QObject::connect(cmd, &Commands::NewCertificateCommand::finished, q, [this]() { mGenerateBtn->setEnabled(true); }); cmd->start(); } WelcomeWidget *q; QToolButton *mGenerateBtn; QToolButton *mImportBtn; }; WelcomeWidget::WelcomeWidget (QWidget *parent): QWidget(parent), d(new Private(this)) { } diff --git a/tests/test_verify.cpp b/tests/test_verify.cpp index fe6f1a7f..8c557c28 100644 --- a/tests/test_verify.cpp +++ b/tests/test_verify.cpp @@ -1,233 +1,233 @@ /* This file is part of Kleopatra's test suite. Copyright (c) 2007 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "kleo_test.h" #include #include #include #include #include #include #include #include #include #include #include // Replace this with a gpgme version check once GnuPG Bug #2092 // ( https://bugs.gnupg.org/gnupg/issue2092 ) is fixed. #define GPGME_MULTITHREADED_KEYLIST_BROKEN Q_DECLARE_METATYPE(GpgME::VerificationResult) class VerifyTest : public QObject { Q_OBJECT private: // Data shared with all tests QByteArray mSignature; QByteArray mSignedData; const QGpgME::Protocol *mBackend; QEventLoop mEventLoop; // Data for testParallelVerifyAndKeyListJobs() QList mParallelVerifyJobs; QList mParallelKeyListJobs; // Data for testMixedParallelJobs() QList mRunningJobs; int mJobsStarted; public Q_SLOTS: void slotParallelKeyListJobFinished() { mParallelKeyListJobs.removeAll(static_cast(sender())); // When all jobs are done, quit the event loop if (mParallelVerifyJobs.isEmpty() && mParallelKeyListJobs.isEmpty()) { mEventLoop.quit(); } } - void slotParallelVerifyJobFinished(GpgME::VerificationResult result) + void slotParallelVerifyJobFinished(const GpgME::VerificationResult &result) { // Verify the result of the job is correct QVERIFY(mParallelVerifyJobs.contains(static_cast(sender()))); QCOMPARE(result.signature(0).validity(), GpgME::Signature::Full); mParallelVerifyJobs.removeAll(static_cast(sender())); // Start a key list job QGpgME::KeyListJob *job = mBackend->keyListJob(); mParallelKeyListJobs.append(job); connect(job, &QGpgME::Job::done, this, &VerifyTest::slotParallelKeyListJobFinished); QVERIFY(!job->start(QStringList())); } void someJobDone() { // Don't bother checking any results here mRunningJobs.removeAll(static_cast(sender())); } void startAnotherJob() { static int counter = 0; counter++; // Randomly kill a running job if (counter % 10 == 0 && !mRunningJobs.isEmpty()) { mRunningJobs.at(counter % mRunningJobs.size())->slotCancel(); } // Randomly either start a keylist or a verify job QGpgME::Job *job; if (counter % 2 == 0) { QGpgME::VerifyDetachedJob *vdj = mBackend->verifyDetachedJob(); QVERIFY(!vdj->start(mSignature, mSignedData)); job = vdj; } else { QGpgME::KeyListJob *klj = mBackend->keyListJob(); QVERIFY(!klj->start(QStringList())); job = klj; } mRunningJobs.append(job); connect(job, &QGpgME::Job::done, this, &VerifyTest::someJobDone); // Quit after 2500 jobs, that should be enough mJobsStarted++; if (mJobsStarted >= 2500) { QTimer::singleShot(1000, &mEventLoop, &QEventLoop::quit); } else { QTimer::singleShot(0, this, &VerifyTest::startAnotherJob); } } private Q_SLOTS: void initTestCase() { qRegisterMetaType(); const QString sigFileName = QLatin1String(KLEO_TEST_DATADIR) + QLatin1String("/test.data.sig"); const QString dataFileName = QLatin1String(KLEO_TEST_DATADIR) + QLatin1String("/test.data"); QFile sigFile(sigFileName); QVERIFY(sigFile.open(QFile::ReadOnly)); QFile dataFile(dataFileName); QVERIFY(dataFile.open(QFile::ReadOnly)); mSignature = sigFile.readAll(); mSignedData = dataFile.readAll(); mBackend = QGpgME::openpgp(); } void testVerify() { QGpgME::VerifyDetachedJob *job = mBackend->verifyDetachedJob(); QSignalSpy spy(job, SIGNAL(result(GpgME::VerificationResult))); QVERIFY(spy.isValid()); GpgME::Error err = job->start(mSignature, mSignedData); QVERIFY(!err); QTest::qWait(1000); // ### we need to enter the event loop, can be done nicer though QCOMPARE(spy.count(), 1); GpgME::VerificationResult result = spy.takeFirst().at(0).value(); QCOMPARE(result.numSignatures(), 1U); GpgME::Signature sig = result.signature(0); QCOMPARE(sig.summary() & GpgME::Signature::KeyMissing, 0); QCOMPARE((quint64) sig.creationTime(), Q_UINT64_C(1530524124)); QCOMPARE(sig.validity(), GpgME::Signature::Full); } /* Test that the decrypt verify job also works with signed only, not * encrypted PGP messages */ void testDecryptVerifyOpaqueSigned() { const QString sigFileName = QLatin1String(KLEO_TEST_DATADIR) + QLatin1String("/test.data.signed-opaque.asc"); std::pair result; QByteArray plaintext; QFile sigFile(sigFileName); QVERIFY(sigFile.open(QFile::ReadOnly)); const QByteArray ciphertext = sigFile.readAll(); QGpgME::DecryptVerifyJob *job = mBackend->decryptVerifyJob(); result = job->exec(ciphertext, plaintext); QVERIFY(result.first.error().code()); QVERIFY(result.second.numSignatures()); GpgME::Signature sig = result.second.signature(0); QVERIFY(sig.validity() == GpgME::Signature::Validity::Full); QVERIFY(!sig.status().code()); QVERIFY(QString::fromUtf8(plaintext).startsWith( QLatin1String("/* -*- mode: c++; c-basic-offset:4 -*-"))); } #ifndef GPGME_MULTITHREADED_KEYLIST_BROKEN // The following two tests are disabled because they trigger an // upstream bug in gpgme. See: https://bugs.gnupg.org/gnupg/issue2092 // Which has a testcase attached that does similar things using gpgme // directly and triggers various problems. void testParallelVerifyAndKeyListJobs() { // ### Increasing 10 to 500 makes the verify jobs fail! // ^ This should also be reevaluated if the underlying bug in gpgme // is fixed. for (int i = 0; i < 10; ++i) { QGpgME::VerifyDetachedJob *job = mBackend->verifyDetachedJob(); mParallelVerifyJobs.append(job); QVERIFY(!job->start(mSignature, mSignedData)); connect(job, SIGNAL(result(GpgME::VerificationResult)), this, SLOT(slotParallelVerifyJobFinished(GpgME::VerificationResult))); } mEventLoop.exec(); } void testMixedParallelJobs() { mJobsStarted = 0; QTimer::singleShot(0, this, SLOT(startAnotherJob())); mEventLoop.exec(); } #endif }; QTEST_KLEOMAIN(VerifyTest) #include "test_verify.moc"