diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 28f4277b9..3fc618e71 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,234 +1,235 @@ # target_include_directories does not handle empty include paths include_directories(${GPGME_INCLUDES}) add_definitions(-DTRANSLATION_DOMAIN=\"libkleopatra\") #add_definitions( -DQT_NO_CAST_FROM_ASCII ) #add_definitions( -DQT_NO_CAST_TO_ASCII ) kde_enable_exceptions() add_definitions( -DGPGMEPP_ERR_SOURCE_DEFAULT=13 ) # 13 is GPG_ERR_SOURCE_KLEO, even if gpg-error's too old to know about add_subdirectory( pics ) if (BUILD_TESTING) add_subdirectory( tests ) endif() ########### next target ############### set(libkleo_core_SRCS kleo/checksumdefinition.cpp kleo/debug.cpp kleo/defaultkeyfilter.cpp kleo/defaultkeygenerationjob.cpp kleo/dn.cpp kleo/enum.cpp kleo/kconfigbasedkeyfilter.cpp kleo/keyfiltermanager.cpp kleo/keygroup.cpp kleo/keyresolver.cpp kleo/kleoexception.cpp kleo/oidmap.cpp models/keycache.cpp models/keylistmodel.cpp models/keylistmodelinterface.cpp models/keylistsortfilterproxymodel.cpp models/keyrearrangecolumnsproxymodel.cpp models/subkeylistmodel.cpp models/useridlistmodel.cpp utils/filesystemwatcher.cpp utils/formatting.cpp utils/classify.cpp utils/gnupg.cpp utils/gnupg-registry.c utils/hex.cpp utils/compat.cpp ) ecm_qt_declare_logging_category(libkleo_core_SRCS HEADER libkleo_debug.h IDENTIFIER LIBKLEO_LOG CATEGORY_NAME org.kde.pim.libkleo DESCRIPTION "libkleo (kleo_core)" EXPORT LIBKLEO ) set(libkleo_ui_common_SRCS ui/dnattributeorderconfigwidget.cpp ui/kdhorizontalline.cpp ui/filenamerequester.cpp ui/messagebox.cpp ui/cryptoconfigmodule.cpp ui/cryptoconfigdialog.cpp ui/directoryserviceswidget.cpp ui/progressbar.cpp ui/progressdialog.cpp ui/auditlogviewer.cpp ) ecm_qt_declare_logging_category(libkleo_ui_common_SRCS HEADER kleo_ui_debug.h IDENTIFIER KLEO_UI_LOG CATEGORY_NAME org.kde.pim.kleo_ui DESCRIPTION "libkleo (kleo_ui)" OLD_CATEGORY_NAMES log_kleo_ui EXPORT LIBKLEO ) set(libkleo_ui_SRCS # make this a separate lib. ui/keylistview.cpp ui/keyselectiondialog.cpp ui/keyrequester.cpp ui/keyapprovaldialog.cpp ui/newkeyapprovaldialog.cpp ui/keyselectioncombo.cpp ) ki18n_wrap_ui(libkleo_ui_common_SRCS ui/directoryserviceswidget.ui ) set(kleo_LIB_SRCS ${libkleo_core_SRCS} ${libkleo_ui_SRCS} ${libkleo_ui_common_SRCS}) set(kleo_LIB_LIBS PUBLIC QGpgme Gpgmepp PRIVATE Qt5::Widgets KF5::I18n KF5::Completion KF5::ConfigCore KF5::CoreAddons KF5::WidgetsAddons KF5::ItemModels KF5::Codecs) if (KF5PimTextEdit_FOUND) add_definitions(-DHAVE_PIMTEXTEDIT) set(kleo_LIB_LIBS ${kleo_LIB_LIBS} PRIVATE KF5::PimTextEdit) endif() add_library(KF5Libkleo ${kleo_LIB_SRCS}) if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) set_target_properties(KF5Libkleo PROPERTIES UNITY_BUILD ON) endif() generate_export_header(KF5Libkleo BASE_NAME kleo) add_library(KF5::Libkleo ALIAS KF5Libkleo) if(WIN32) target_link_libraries(KF5Libkleo ${kleo_LIB_LIBS} ${GPGME_VANILLA_LIBRARIES} ) else() target_link_libraries(KF5Libkleo ${kleo_LIB_LIBS} ) endif() set_target_properties(KF5Libkleo PROPERTIES VERSION ${LIBKLEO_VERSION_STRING} SOVERSION ${LIBKLEO_SOVERSION} EXPORT_NAME Libkleo ) install(TARGETS KF5Libkleo EXPORT KF5LibkleoTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ) target_include_directories(KF5Libkleo PUBLIC "$") target_include_directories(KF5Libkleo INTERFACE "$") ecm_generate_headers(libkleo_CamelCase_HEADERS HEADER_NAMES ChecksumDefinition Debug DefaultKeyFilter DefaultKeyGenerationJob Dn Enum KConfigBasedKeyFilter KeyFilter KeyFilterManager KeyGroup KeyResolver + KeyResolverCore KleoException OidMap Predicates Stl_Util REQUIRED_HEADERS libkleo_HEADERS PREFIX Libkleo RELATIVE kleo ) ecm_generate_headers(libkleo_CamelCase_models_HEADERS HEADER_NAMES KeyCache KeyList KeyListModel KeyListModelInterface KeyListSortFilterProxyModel KeyRearrangeColumnsProxyModel SubkeyListModel UserIDListModel REQUIRED_HEADERS libkleo_models_HEADERS PREFIX Libkleo RELATIVE models ) ecm_generate_headers(libkleo_CamelCase_utils_HEADERS HEADER_NAMES Classify FileSystemWatcher Formatting GnuPG Compat REQUIRED_HEADERS libkleo_utils_HEADERS PREFIX Libkleo RELATIVE utils ) ecm_generate_headers(libkleo_CamelCase_ui_HEADERS HEADER_NAMES CryptoConfigDialog CryptoConfigModule DNAttributeOrderConfigWidget DirectoryServicesWidget FileNameRequester KDHorizontalLine KeyApprovalDialog NewKeyApprovalDialog KeyRequester KeySelectionCombo KeySelectionDialog MessageBox ProgressDialog REQUIRED_HEADERS libkleo_ui_HEADERS PREFIX Libkleo RELATIVE ui ) ecm_generate_pri_file(BASE_NAME Libkleo LIB_NAME KF5Libkleo DEPS "QGpgme" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/Libkleo ) install(FILES ${libkleo_CamelCase_HEADERS} ${libkleo_CamelCase_models_HEADERS} ${libkleo_CamelCase_ui_HEADERS} ${libkleo_CamelCase_utils_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/Libkleo COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kleo_export.h ${libkleo_HEADERS} ${libkleo_models_HEADERS} ${libkleo_ui_HEADERS} ${libkleo_utils_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/libkleo COMPONENT Devel ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) if ( WIN32 ) install ( FILES libkleopatrarc-win32.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) else () install ( FILES libkleopatrarc.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) endif () diff --git a/src/kleo/keyresolver.cpp b/src/kleo/keyresolver.cpp index 14d975d19..ce38b6898 100644 --- a/src/kleo/keyresolver.cpp +++ b/src/kleo/keyresolver.cpp @@ -1,597 +1,730 @@ /* -*- c++ -*- keyresolver.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2018 Intevation GmbH + SPDX-FileCopyrightText: 2021 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker Based on kpgp.cpp SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #include "keyresolver.h" +#include "keyresolvercore.h" + #include "models/keycache.h" #include "ui/newkeyapprovaldialog.h" #include "utils/formatting.h" #include #include "libkleo_debug.h" using namespace Kleo; using namespace GpgME; namespace { static inline bool ValidEncryptionKey(const Key &key) { if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt()) { return false; } return true; } static inline bool ValidSigningKey(const Key &key) { if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign() || !key.hasSecret()) { return false; } return true; } + } // namespace -class KeyResolver::Private +class KeyResolverCore::Private { public: - Private(KeyResolver* qq, bool enc, bool sig, Protocol fmt, bool allowMixed) : - q(qq), mFormat(fmt), mEncrypt(enc), mSign(sig), - mAllowMixed(allowMixed), - mCache(KeyCache::instance()), - mDialogWindowFlags(Qt::WindowFlags()), - mPreferredProtocol(UnknownProtocol), - mMinimumValidity(UserID::Marginal), - mCompliance(Formatting::complianceMode()) + Private(KeyResolverCore* qq, bool enc, bool sig, Protocol fmt) + : q(qq) + , mFormat(fmt) + , mEncrypt(enc) + , mSign(sig) + , mCache(KeyCache::instance()) + , mPreferredProtocol(UnknownProtocol) + , mMinimumValidity(UserID::Marginal) + , mCompliance(Formatting::complianceMode()) { } ~Private() = default; - bool isAcceptableSigningKey(const Key &key) + bool isAcceptableSigningKey(const Key &key); + bool isAcceptableEncryptionKey(const Key &key, const QString &address = QString()); + void addRecipients(const QStringList &addresses); + void resolveOverrides(); + void resolveSign(Protocol proto); + void setSigningKeys(const QStringList &fingerprints); + void resolveEnc(Protocol proto); + + KeyResolverCore *const q; + QString mSender; + QStringList mRecipients; + QMap> mSigKeys; + QMap>> mEncKeys; + QMap> mOverrides; + + QStringList mUnresolvedPGP; + QStringList mUnresolvedCMS; + + Protocol mFormat; + QStringList mFatalErrors; + bool mEncrypt; + bool mSign; + // The cache is needed as a member variable to avoid rebuilding + // it between calls if we are the only user. + std::shared_ptr mCache; + Protocol mPreferredProtocol; + int mMinimumValidity; + QString mCompliance; +}; + +class KeyResolver::Private +{ +public: + Private(KeyResolver* qq, bool enc, bool sig, Protocol fmt, bool allowMixed) + : q(qq) + , mCore(enc, sig, fmt) + , mFormat(fmt) + , mEncrypt(enc) + , mSign(sig) + , mAllowMixed(allowMixed) + , mCache(KeyCache::instance()) + , mDialogWindowFlags(Qt::WindowFlags()) + , mPreferredProtocol(UnknownProtocol) { - if (!ValidSigningKey(key)) { + } + + ~Private() = default; + + void showApprovalDialog(QWidget *parent); + void dialogAccepted(); + + KeyResolver *const q; + KeyResolverCore mCore; + QMap> mSigKeys; + QMap>> mEncKeys; + + Protocol mFormat; + bool mEncrypt; + bool mSign; + bool mAllowMixed; + // The cache is needed as a member variable to avoid rebuilding + // it between calls if we are the only user. + std::shared_ptr mCache; + std::shared_ptr mDialog; + Qt::WindowFlags mDialogWindowFlags; + Protocol mPreferredProtocol; +}; + +bool KeyResolverCore::Private::isAcceptableSigningKey(const Key &key) +{ + if (!ValidSigningKey(key)) { + return false; + } + if (mCompliance == QLatin1String("de-vs")) { + if (!Formatting::isKeyDeVs(key)) { + qCDebug(LIBKLEO_LOG) << "Rejected sig key" << key.primaryFingerprint() + << "because it is not de-vs compliant."; return false; } - if (mCompliance == QLatin1String("de-vs")) { - if (!Formatting::isKeyDeVs(key)) { - qCDebug(LIBKLEO_LOG) << "Rejected sig key" << key.primaryFingerprint() - << "because it is not de-vs compliant."; - return false; - } - } - return true; + } + return true; +} + +bool KeyResolverCore::Private::isAcceptableEncryptionKey(const Key &key, const QString &address) +{ + if (!ValidEncryptionKey(key)) { + return false; } - bool isAcceptableEncryptionKey(const Key &key, const QString &address = QString()) - { - if (!ValidEncryptionKey(key)) { + if (mCompliance == QLatin1String("de-vs")) { + if (!Formatting::isKeyDeVs(key)) { + qCDebug(LIBKLEO_LOG) << "Rejected enc key" << key.primaryFingerprint() + << "because it is not de-vs compliant."; return false; } + } - if (mCompliance == QLatin1String("de-vs")) { - if (!Formatting::isKeyDeVs(key)) { - qCDebug(LIBKLEO_LOG) << "Rejected enc key" << key.primaryFingerprint() - << "because it is not de-vs compliant."; - return false; + if (address.isEmpty()) { + return true; + } + for (const auto &uid: key.userIDs()) { + if (uid.addrSpec() == address.toStdString()) { + if (uid.validity() >= mMinimumValidity) { + return true; } } + } + return false; +} - if (address.isEmpty()) { - return true; - } - for (const auto &uid: key.userIDs()) { - if (uid.addrSpec() == address.toStdString()) { - if (uid.validity() >= mMinimumValidity) { - return true; - } - } - } - return false; +void KeyResolverCore::Private::addRecipients(const QStringList &addresses) +{ + if (!mEncrypt) { + return; } - void addRecpients (const QStringList &addresses) - { - if (!mEncrypt) { - return; + // Internally we work with normalized addresses. Normalization + // matches the gnupg one. + for (const auto &addr :addresses) { + // PGP Uids are defined to be UTF-8 (RFC 4880 §5.11) + const auto normalized = UserID::addrSpecFromString (addr.toUtf8().constData()); + if (normalized.empty()) { + // should not happen bug in the caller, non localized + // error for bug reporting. + mFatalErrors << QStringLiteral("The mail address for '%1' could not be extracted").arg(addr); + continue; } + const QString normStr = QString::fromUtf8(normalized.c_str()); - // Internally we work with normalized addresses. Normalization - // matches the gnupg one. - for (const auto &addr :addresses) { - // PGP Uids are defined to be UTF-8 (RFC 4880 §5.11) - const auto normalized = UserID::addrSpecFromString (addr.toUtf8().constData()); - if (normalized.empty()) { - // should not happen bug in the caller, non localized - // error for bug reporting. - mFatalErrors << QStringLiteral("The mail address for '%1' could not be extracted").arg(addr); - continue; - } - const QString normStr = QString::fromUtf8(normalized.c_str()); - - // Initially mark them as unresolved for both protocols - if (!mUnresolvedCMS.contains(normStr)) { - mUnresolvedCMS << normStr; - } - if (!mUnresolvedPGP.contains(normStr)) { - mUnresolvedPGP << normStr; - } - - mRecipients << normStr; + // Initially mark them as unresolved for both protocols + if (!mUnresolvedCMS.contains(normStr)) { + mUnresolvedCMS << normStr; + } + if (!mUnresolvedPGP.contains(normStr)) { + mUnresolvedPGP << normStr; } + + mRecipients << normStr; } +} - // Apply the overrides this is also where specific formats come in - void resolveOverrides() - { - if (!mEncrypt) { - // No encryption we are done. - return; +// Apply the overrides this is also where specific formats come in +void KeyResolverCore::Private::resolveOverrides() +{ + if (!mEncrypt) { + // No encryption we are done. + return; + } + for (Protocol fmt: mOverrides.keys()) { + // Iterate over the crypto message formats + if (mFormat != UnknownProtocol && mFormat != fmt && fmt != UnknownProtocol) { + // Skip overrides for the wrong format + continue; } - for (Protocol fmt: mOverrides.keys()) { - // Iterate over the crypto message formats - if (mFormat != UnknownProtocol && mFormat != fmt && fmt != UnknownProtocol) { - // Skip overrides for the wrong format - continue; - } - for (const auto &addr: mOverrides[fmt].keys()) { - // For all address overrides of this format. - for (const auto &fprOrId: mOverrides[fmt][addr]) { - // For all the keys configured for this address. - const auto key = mCache->findByKeyIDOrFingerprint(fprOrId.toUtf8().constData()); - if (key.isNull()) { - qCDebug (LIBKLEO_LOG) << "Failed to find override key for:" << addr - << "fpr:" << fprOrId; - continue; - } + for (const auto &addr: mOverrides[fmt].keys()) { + // For all address overrides of this format. + for (const auto &fprOrId: mOverrides[fmt][addr]) { + // For all the keys configured for this address. + const auto key = mCache->findByKeyIDOrFingerprint(fprOrId.toUtf8().constData()); + if (key.isNull()) { + qCDebug (LIBKLEO_LOG) << "Failed to find override key for:" << addr + << "fpr:" << fprOrId; + continue; + } - // Now add it to the resolved keys and remove it from our list - // of unresolved keys. - if (!mRecipients.contains(addr)) { - qCDebug(LIBKLEO_LOG) << "Override provided for an address that is " - "neither sender nor recipient. Address: " << addr; - continue; - } + // Now add it to the resolved keys and remove it from our list + // of unresolved keys. + if (!mRecipients.contains(addr)) { + qCDebug(LIBKLEO_LOG) << "Override provided for an address that is " + "neither sender nor recipient. Address: " << addr; + continue; + } - Protocol resolvedFmt = fmt; - if (fmt == UnknownProtocol) { - // Take the format from the key. - resolvedFmt = key.protocol(); - } + Protocol resolvedFmt = fmt; + if (fmt == UnknownProtocol) { + // Take the format from the key. + resolvedFmt = key.protocol(); + } - auto recpMap = mEncKeys.value(resolvedFmt); - auto keys = recpMap.value(addr); - keys.push_back(key); - recpMap.insert(addr, keys); - mEncKeys.insert(resolvedFmt, recpMap); - - // Now we can remove it from our unresolved lists. - if (key.protocol() == OpenPGP) { - mUnresolvedPGP.removeAll(addr); - } else { - mUnresolvedCMS.removeAll(addr); - } - qCDebug(LIBKLEO_LOG) << "Override" << addr << Formatting::displayName(resolvedFmt) << fprOrId; + auto recpMap = mEncKeys.value(resolvedFmt); + auto keys = recpMap.value(addr); + keys.push_back(key); + recpMap.insert(addr, keys); + mEncKeys.insert(resolvedFmt, recpMap); + + // Now we can remove it from our unresolved lists. + if (key.protocol() == OpenPGP) { + mUnresolvedPGP.removeAll(addr); + } else { + mUnresolvedCMS.removeAll(addr); } + qCDebug(LIBKLEO_LOG) << "Override" << addr << Formatting::displayName(resolvedFmt) << fprOrId; } } } +} - void resolveSign(Protocol proto) - { - if (mSigKeys.contains(proto)) { - // Explicitly set - return; +void KeyResolverCore::Private::resolveSign(Protocol proto) +{ + if (mSigKeys.contains(proto)) { + // Explicitly set + return; + } + const auto keys = mCache->findBestByMailBox(mSender.toUtf8().constData(), + proto, true, false); + for (const auto &key: keys) { + if (key.isNull()) { + continue; } - const auto keys = mCache->findBestByMailBox(mSender.toUtf8().constData(), - proto, true, false); - for (const auto &key: keys) { - if (key.isNull()) { - continue; - } - if (!isAcceptableSigningKey(key)) { - qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint() - << "for" << mSender; - return; - } + if (!isAcceptableSigningKey(key)) { + qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint() + << "for" << mSender; + return; } + } - if (!keys.empty() && !keys[0].isNull()) { - mSigKeys.insert(proto, keys); - } + if (!keys.empty() && !keys[0].isNull()) { + mSigKeys.insert(proto, keys); } +} - void setSigningKeys(const QStringList &fingerprints) - { - if (mSign) { - for (const auto &fpr: fingerprints) { - const auto key = mCache->findByKeyIDOrFingerprint(fpr.toUtf8().constData()); - if (key.isNull()) { - qCDebug(LIBKLEO_LOG) << "Failed to find signing key with fingerprint" << fpr; - continue; - } - auto list = mSigKeys.value(key.protocol()); - list.push_back(key); - mSigKeys.insert(key.protocol(), list); +void KeyResolverCore::Private::setSigningKeys(const QStringList &fingerprints) +{ + if (mSign) { + for (const auto &fpr: fingerprints) { + const auto key = mCache->findByKeyIDOrFingerprint(fpr.toUtf8().constData()); + if (key.isNull()) { + qCDebug(LIBKLEO_LOG) << "Failed to find signing key with fingerprint" << fpr; + continue; } + auto list = mSigKeys.value(key.protocol()); + list.push_back(key); + mSigKeys.insert(key.protocol(), list); } } +} - // Try to find matching keys in the provided protocol for the unresolved addresses - // only updates the any maps. - void resolveEnc(Protocol proto) - { - auto encMap = mEncKeys.value(proto); - QMutableStringListIterator it((proto == Protocol::OpenPGP) ? mUnresolvedPGP : mUnresolvedCMS); - while (it.hasNext()) { - const QString addr = it.next(); - const auto keys = mCache->findBestByMailBox(addr.toUtf8().constData(), - proto, false, true); - if (keys.empty() || keys[0].isNull()) { - qCDebug(LIBKLEO_LOG) << "Failed to find any" - << (proto == Protocol::OpenPGP ? "OpenPGP" : "CMS") - << "key for: " << addr; +// Try to find matching keys in the provided protocol for the unresolved addresses +// only updates the any maps. +void KeyResolverCore::Private::resolveEnc(Protocol proto) +{ + auto encMap = mEncKeys.value(proto); + QMutableStringListIterator it((proto == Protocol::OpenPGP) ? mUnresolvedPGP : mUnresolvedCMS); + while (it.hasNext()) { + const QString addr = it.next(); + const auto keys = mCache->findBestByMailBox(addr.toUtf8().constData(), + proto, false, true); + if (keys.empty() || keys[0].isNull()) { + qCDebug(LIBKLEO_LOG) << "Failed to find any" + << (proto == Protocol::OpenPGP ? "OpenPGP" : "CMS") + << "key for: " << addr; + continue; + } + if (keys.size() == 1) { + if (!isAcceptableEncryptionKey(keys[0], addr)) { + qCDebug(LIBKLEO_LOG) << "key for: " << addr << keys[0].primaryFingerprint() + << "has not enough validity"; continue; } - if (keys.size() == 1) { - if (!isAcceptableEncryptionKey(keys[0], addr)) { - qCDebug(LIBKLEO_LOG) << "key for: " << addr << keys[0].primaryFingerprint() - << "has not enough validity"; - continue; - } - } else { - // If we have one unacceptable group key we reject the - // whole group to avoid the situation where one key is - // skipped or the operation fails. - // - // We are in Autoresolve land here. In the GUI we - // will also show unacceptable group keys so that the - // user can see which key is not acceptable. - bool unacceptable = false; - for (const auto &key: keys) { - if (!isAcceptableEncryptionKey(key)) { - qCDebug(LIBKLEO_LOG) << "group key for: " << addr << keys[0].primaryFingerprint() - << "has not enough validity"; - unacceptable = true; - break; - } - } - if (unacceptable) { - continue; + } else { + // If we have one unacceptable group key we reject the + // whole group to avoid the situation where one key is + // skipped or the operation fails. + // + // We are in Autoresolve land here. In the GUI we + // will also show unacceptable group keys so that the + // user can see which key is not acceptable. + bool unacceptable = false; + for (const auto &key: keys) { + if (!isAcceptableEncryptionKey(key)) { + qCDebug(LIBKLEO_LOG) << "group key for: " << addr << keys[0].primaryFingerprint() + << "has not enough validity"; + unacceptable = true; + break; } } - encMap.insert(addr, keys); - for (const auto &k: keys) { - if (!k.isNull()) { - qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << addr - << "with key" << k.primaryFingerprint(); - } + if (unacceptable) { + continue; } - it.remove(); } - mEncKeys.insert(proto, encMap); + encMap.insert(addr, keys); + for (const auto &k: keys) { + if (!k.isNull()) { + qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << addr + << "with key" << k.primaryFingerprint(); + } + } + it.remove(); } + mEncKeys.insert(proto, encMap); +} - void showApprovalDialog(QWidget *parent) - { - QMap > resolvedSig; - QStringList unresolvedSig; - bool pgpOnly = mUnresolvedPGP.empty() && (!mSign || mSigKeys.contains(OpenPGP)); - bool cmsOnly = mUnresolvedCMS.empty() && (!mSign || mSigKeys.contains(CMS)); - // First handle the signing keys - if (mSign) { - if (mSigKeys.empty()) { - unresolvedSig << mSender; - } else { - std::vector resolvedSigKeys; - for (const auto &keys: qAsConst(mSigKeys)) { - for (const auto &key: keys) { - resolvedSigKeys.push_back(key); - } +void KeyResolver::Private::showApprovalDialog(QWidget *parent) +{ + const QString sender = mCore.normalizedSender(); + const QMap> signingKeys = mCore.signingKeys(); + const QStringList unresolvedPGP = mCore.unresolvedRecipients(OpenPGP); + const QStringList unresolvedCMS = mCore.unresolvedRecipients(CMS); + + QMap > resolvedSig; + QStringList unresolvedSig; + const bool pgpOnly = unresolvedPGP.empty() && (!mSign || signingKeys.contains(OpenPGP)); + const bool cmsOnly = unresolvedCMS.empty() && (!mSign || signingKeys.contains(CMS)); + // First handle the signing keys + if (mSign) { + if (signingKeys.empty()) { + unresolvedSig << sender; + } else { + std::vector resolvedSigKeys; + for (const auto &keys: signingKeys) { + for (const auto &key: keys) { + resolvedSigKeys.push_back(key); } - resolvedSig.insert(mSender, resolvedSigKeys); } + resolvedSig.insert(sender, resolvedSigKeys); } + } - // Now build the encryption keys - QMap > resolvedRecp; - QStringList unresolvedRecp; - - if (mEncrypt) { - // Use all unresolved recipients. - if (!cmsOnly && !pgpOnly) { - if (mFormat == UnknownProtocol) { - // In Auto Format we can now remove recipients that could - // be resolved either through CMS or PGP - for (const auto &addr: qAsConst(mUnresolvedPGP)) { - if (mUnresolvedCMS.contains(addr)) { - unresolvedRecp << addr; - } + // Now build the encryption keys + QMap > resolvedRecp; + QStringList unresolvedRecp; + + if (mEncrypt) { + // Use all unresolved recipients. + if (!cmsOnly && !pgpOnly) { + if (mFormat == UnknownProtocol) { + // In Auto Format we can now remove recipients that could + // be resolved either through CMS or PGP + for (const auto &addr: qAsConst(unresolvedPGP)) { + if (unresolvedCMS.contains(addr)) { + unresolvedRecp << addr; } - } else if (mFormat == OpenPGP) { - unresolvedRecp = mUnresolvedPGP; - } else if (mFormat == CMS) { - unresolvedRecp = mUnresolvedCMS; } + } else if (mFormat == OpenPGP) { + unresolvedRecp = unresolvedPGP; + } else if (mFormat == CMS) { + unresolvedRecp = unresolvedCMS; } + } - // Now Map all resolved encryption keys regardless of the format. - for (const auto &map: mEncKeys.values()) { - // Foreach format - for (const auto &addr: map.keys()) { - // Foreach sender - if (!resolvedRecp.contains(addr) || !resolvedRecp[addr].size()) { - resolvedRecp.insert(addr, map[addr]); - } else { - std::vector merged = resolvedRecp[addr]; - // Add without duplication - for (const auto &k: map[addr]) { - const auto it = std::find_if (merged.begin(), merged.end(), [k] (const Key &y) { - return (k.primaryFingerprint() && y.primaryFingerprint() && - !strcmp (k.primaryFingerprint(), y.primaryFingerprint())); - }); - if (it == merged.end()) { - merged.push_back(k); - } + // Now Map all resolved encryption keys regardless of the format. + const QMap>> encryptionKeys = mCore.encryptionKeys(); + for (const auto &map: encryptionKeys.values()) { + // Foreach format + for (const auto &addr: map.keys()) { + // Foreach sender + if (!resolvedRecp.contains(addr) || !resolvedRecp[addr].size()) { + resolvedRecp.insert(addr, map[addr]); + } else { + std::vector merged = resolvedRecp[addr]; + // Add without duplication + for (const auto &k: map[addr]) { + const auto it = std::find_if (merged.begin(), merged.end(), [k] (const Key &y) { + return (k.primaryFingerprint() && y.primaryFingerprint() && + !strcmp (k.primaryFingerprint(), y.primaryFingerprint())); + }); + if (it == merged.end()) { + merged.push_back(k); } - resolvedRecp[addr] = merged; } + resolvedRecp[addr] = merged; } } } + } - // Do we force the protocol? - Protocol forcedProto = mFormat; + // Do we force the protocol? + Protocol forcedProto = mFormat; - // Start with the protocol for which every keys could be found. - Protocol presetProtocol; + // Start with the protocol for which every keys could be found. + Protocol presetProtocol; - if (mPreferredProtocol == CMS && cmsOnly) { - presetProtocol = CMS; - } else { - presetProtocol = pgpOnly ? OpenPGP : - cmsOnly ? CMS : - mPreferredProtocol; - } + if (mPreferredProtocol == CMS && cmsOnly) { + presetProtocol = CMS; + } else { + presetProtocol = pgpOnly ? OpenPGP : + cmsOnly ? CMS : + mPreferredProtocol; + } - mDialog = std::shared_ptr(new NewKeyApprovalDialog(resolvedSig, - resolvedRecp, - unresolvedSig, - unresolvedRecp, - mSender, - mAllowMixed, - forcedProto, - presetProtocol, - parent, - mDialogWindowFlags)); - connect (mDialog.get(), &QDialog::accepted, q, [this] () { - dialogAccepted(); - }); - connect (mDialog.get(), &QDialog::rejected, q, [this] () { - Q_EMIT q->keysResolved(false, false);} - ); - mDialog->open(); - } - - void dialogAccepted() - { - // Update keymaps accordingly - mSigKeys.clear(); - for (const auto &key: mDialog->signingKeys()) { - if (!mSigKeys.contains(key.protocol())) { - mSigKeys.insert(key.protocol(), std::vector()); - } - mSigKeys[key.protocol()].push_back(key); + mDialog = std::shared_ptr(new NewKeyApprovalDialog(resolvedSig, + resolvedRecp, + unresolvedSig, + unresolvedRecp, + sender, + mAllowMixed, + forcedProto, + presetProtocol, + parent, + mDialogWindowFlags)); + connect (mDialog.get(), &QDialog::accepted, q, [this] () { + dialogAccepted(); + }); + connect (mDialog.get(), &QDialog::rejected, q, [this] () { + Q_EMIT q->keysResolved(false, false);} + ); + mDialog->open(); +} + +void KeyResolver::Private::dialogAccepted() +{ + for (const auto &key: mDialog->signingKeys()) { + if (!mSigKeys.contains(key.protocol())) { + mSigKeys.insert(key.protocol(), std::vector()); } - const auto &encMap = mDialog->encryptionKeys(); - // First we clear the Any Maps and fill them with - // the results of the dialog. Then we use the sender - // address to determine if a keys in the specific - // maps need updating. - mEncKeys.remove(OpenPGP); - mEncKeys.remove(CMS); - - bool isUnresolved = false; - for (const auto &addr: encMap.keys()) { - for (const auto &key: encMap[addr]) { - if (key.isNull()) { - isUnresolved = true; - } - if (!mEncKeys.contains(key.protocol())) { - mEncKeys.insert(key.protocol(), QMap >()); - } - if (!mEncKeys[key.protocol()].contains(addr)) { - mEncKeys[key.protocol()].insert(addr, std::vector()); - } - qCDebug (LIBKLEO_LOG) << "Adding" << addr << "for" << Formatting::displayName(key.protocol()) - << "fpr:" << key.primaryFingerprint(); + mSigKeys[key.protocol()].push_back(key); + } + + const auto &encMap = mDialog->encryptionKeys(); + // First we fill the protocol-specific maps with + // the results of the dialog. Then we use the sender + // address to determine if a keys in the specific + // maps need updating. - mEncKeys[key.protocol()][addr].push_back(key); + bool isUnresolved = false; + for (const auto &addr: encMap.keys()) { + for (const auto &key: encMap[addr]) { + if (key.isNull()) { + isUnresolved = true; } - } + if (!mEncKeys.contains(key.protocol())) { + mEncKeys.insert(key.protocol(), QMap >()); + } + if (!mEncKeys[key.protocol()].contains(addr)) { + mEncKeys[key.protocol()].insert(addr, std::vector()); + } + qCDebug (LIBKLEO_LOG) << "Adding" << addr << "for" << Formatting::displayName(key.protocol()) + << "fpr:" << key.primaryFingerprint(); - if (isUnresolved) { - // TODO show warning + mEncKeys[key.protocol()][addr].push_back(key); } + } - Q_EMIT q->keysResolved(true, false); + if (isUnresolved) { + // TODO show warning } - KeyResolver *const q; - QString mSender; - QStringList mRecipients; - QMap > mSigKeys; - QMap > >mEncKeys; - QMap > mOverrides; + Q_EMIT q->keysResolved(true, false); +} - QStringList mUnresolvedPGP, mUnresolvedCMS; +KeyResolverCore::KeyResolverCore(bool encrypt, bool sign, Protocol fmt) + : d(new Private(this, encrypt, sign, fmt)) +{ +} - Protocol mFormat; - QStringList mFatalErrors; - bool mEncrypt, mSign; - bool mAllowMixed; - // The cache is needed as a member variable to avoid rebuilding - // it between calls if we are the only user. - std::shared_ptr mCache; - std::shared_ptr mDialog; - Qt::WindowFlags mDialogWindowFlags; - Protocol mPreferredProtocol; - int mMinimumValidity; - QString mCompliance; -}; +KeyResolverCore::~KeyResolverCore() = default; -void KeyResolver::start(bool showApproval, QWidget *parentWidget) +bool KeyResolverCore::resolve() { qCDebug(LIBKLEO_LOG) << "Starting "; if (!d->mSign && !d->mEncrypt) { // nothing to do - return Q_EMIT keysResolved(true, true); + return true; } // First resolve through overrides d->resolveOverrides(); // Then look for signing / encryption keys if (d->mFormat != CMS) { d->resolveSign(OpenPGP); d->resolveEnc(OpenPGP); } bool pgpOnly = d->mUnresolvedPGP.empty() && (!d->mSign || d->mSigKeys.contains(OpenPGP)); if (d->mFormat != OpenPGP) { d->resolveSign(CMS); d->resolveEnc(CMS); } bool cmsOnly = d->mUnresolvedCMS.empty() && (!d->mSign || d->mSigKeys.contains(CMS)); // Check if we need the user to select different keys. bool needsUser = false; if (!pgpOnly && !cmsOnly) { for (const auto &unresolved: d->mUnresolvedPGP) { if (d->mUnresolvedCMS.contains(unresolved)) { // We have at least one unresolvable key. needsUser = true; break; } } if (d->mSign) { // So every recipient could be resolved through // a combination of PGP and S/MIME do we also // have signing keys for both? needsUser |= !(d->mSigKeys.contains(OpenPGP) && d->mSigKeys.contains(CMS)); } } - if (!needsUser && !showApproval) { + if (!needsUser) { if (pgpOnly && cmsOnly) { if (d->mPreferredProtocol == CMS) { d->mSigKeys.remove(OpenPGP); d->mEncKeys.remove(OpenPGP); } else { d->mSigKeys.remove(CMS); d->mEncKeys.remove(CMS); } } else if (pgpOnly) { d->mSigKeys.remove(CMS); d->mEncKeys.remove(CMS); } else if (cmsOnly) { d->mSigKeys.remove(OpenPGP); d->mEncKeys.remove(OpenPGP); } qCDebug(LIBKLEO_LOG) << "Automatic key resolution done."; + return true; + } + + return false; +} + +void KeyResolver::start(bool showApproval, QWidget *parentWidget) +{ + qCDebug(LIBKLEO_LOG) << "Starting "; + if (!d->mSign && !d->mEncrypt) { + // nothing to do + return Q_EMIT keysResolved(true, true); + } + const bool success = d->mCore.resolve(); + + if (success && !showApproval) { Q_EMIT keysResolved(true, false); return; - } else if (!needsUser) { + } else if (success) { qCDebug(LIBKLEO_LOG) << "No need for the user showing approval anyway."; } d->showApprovalDialog(parentWidget); } KeyResolver::KeyResolver(bool encrypt, bool sign, Protocol fmt, bool allowMixed) : d(new Private(this, encrypt, sign, fmt, allowMixed)) { } Kleo::KeyResolver::~KeyResolver() = default; +void KeyResolverCore::setRecipients(const QStringList &addresses) +{ + d->addRecipients(addresses); +} + void KeyResolver::setRecipients(const QStringList &addresses) { - d->addRecpients(addresses); + d->mCore.setRecipients(addresses); } -void KeyResolver::setSender(const QString &address) +void KeyResolverCore::setSender(const QString &address) { const auto normalized = UserID::addrSpecFromString (address.toUtf8().constData()); if (normalized.empty()) { // should not happen bug in the caller, non localized // error for bug reporting. d->mFatalErrors << QStringLiteral("The sender address '%1' could not be extracted").arg(address); return; } const auto normStr = QString::fromUtf8(normalized.c_str()); if (d->mSign) { d->mSender = normStr; } - d->addRecpients({address}); + d->addRecipients({address}); } -void KeyResolver::setOverrideKeys(const QMap > &overrides) +void KeyResolver::setSender(const QString &address) +{ + d->mCore.setSender(address); +} + +QString KeyResolverCore::normalizedSender() const +{ + return d->mSender; +} + +void KeyResolverCore::setOverrideKeys(const QMap > &overrides) { QMap normalizedOverrides; for (const auto fmt: overrides.keys()) { for (const auto &addr: overrides[fmt].keys()) { const auto normalized = QString::fromUtf8( UserID::addrSpecFromString (addr.toUtf8().constData()).c_str()); const auto fingerprints = overrides[fmt][addr]; normalizedOverrides.insert(addr, fingerprints); } d->mOverrides.insert(fmt, normalizedOverrides); } } -void KeyResolver::setSigningKeys(const QStringList &fingerprints) +void KeyResolver::setOverrideKeys(const QMap > &overrides) +{ + d->mCore.setOverrideKeys(overrides); +} + +void KeyResolverCore::setSigningKeys(const QStringList &fingerprints) { d->setSigningKeys(fingerprints); } -QMap > > KeyResolver::encryptionKeys() const +void KeyResolver::setSigningKeys(const QStringList &fingerprints) +{ + d->mCore.setSigningKeys(fingerprints); +} + +QMap > > KeyResolverCore::encryptionKeys() const { return d->mEncKeys; } -QMap > KeyResolver::signingKeys() const +QMap > > KeyResolver::encryptionKeys() const +{ + return d->mCore.encryptionKeys(); +} + +QMap > KeyResolverCore::signingKeys() const { return d->mSigKeys; } +QMap > KeyResolver::signingKeys() const +{ + return d->mCore.signingKeys(); +} + void KeyResolver::setDialogWindowFlags(Qt::WindowFlags flags) { d->mDialogWindowFlags = flags; } -void KeyResolver::setPreferredProtocol(Protocol proto) +void KeyResolverCore::setPreferredProtocol(Protocol proto) { d->mPreferredProtocol = proto; } -void KeyResolver::setMinimumValidity(int validity) +void KeyResolver::setPreferredProtocol(Protocol proto) +{ + d->mCore.setPreferredProtocol(proto); +} + +void KeyResolverCore::setMinimumValidity(int validity) { d->mMinimumValidity = validity; } + +void KeyResolver::setMinimumValidity(int validity) +{ + d->mCore.setMinimumValidity(validity); +} + +QStringList KeyResolverCore::unresolvedRecipients(GpgME::Protocol protocol) const +{ + if (protocol == OpenPGP) { + return d->mUnresolvedPGP; + } + if (protocol == CMS) { + return d->mUnresolvedCMS; + } + return {}; +} diff --git a/src/kleo/keyresolvercore.h b/src/kleo/keyresolvercore.h new file mode 100644 index 000000000..373270e96 --- /dev/null +++ b/src/kleo/keyresolvercore.h @@ -0,0 +1,70 @@ +/* -*- c++ -*- + kleo/keyresolvercore.h + + This file is part of libkleopatra, the KDE keymanagement library + SPDX-FileCopyrightText: 2018 Intevation GmbH + SPDX-FileCopyrightText: 2021 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef __LIBKLEO_KEYRESOLVERCORE_H__ +#define __LIBKLEO_KEYRESOLVERCORE_H__ + +#include "kleo_export.h" + +#include + +#include + +#include +#include + +class QString; +class QStringList; + +namespace GpgME +{ +class Key; +} + +namespace Kleo +{ + +class KLEO_EXPORT KeyResolverCore +{ +public: + explicit KeyResolverCore(bool encrypt, bool sign, + GpgME::Protocol format = GpgME::UnknownProtocol); + ~KeyResolverCore(); + + void setSender(const QString &sender); + QString normalizedSender() const; + + void setRecipients(const QStringList &addresses); + + void setSigningKeys(const QStringList &fingerprints); + + void setOverrideKeys(const QMap > &overrides); + + void setPreferredProtocol(GpgME::Protocol proto); + + void setMinimumValidity(int validity); + + bool resolve(); + + QMap > signingKeys() const; + + QMap > > encryptionKeys() const; + + QStringList unresolvedRecipients(GpgME::Protocol protocol) const; + +private: + class Private; + std::unique_ptr d; +}; + +} // namespace Kleo + +#endif // __LIBKLEO_KEYRESOLVER_H__