diff --git a/src/kleo/keyresolver.h b/src/kleo/keyresolver.h index bf7a95e53..fced9cc2e 100644 --- a/src/kleo/keyresolver.h +++ b/src/kleo/keyresolver.h @@ -1,188 +1,187 @@ /* -*- c++ -*- keyresolver.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 */ #pragma once -#include "kleo_export.h" - -#include - #include #include #include -#include #include #include #include +#include "kleo_export.h" + +class QStringList; + namespace GpgME { class Key; } namespace Kleo { /** * Class to find Keys for E-Mail signing and encryption. * * The KeyResolver uses the Keycache to find keys for signing * or encryption. * * Overrides can be provided for address book integration. * * If no override key(s) are provided for an address and no * KeyGroup for this address is found, then the key * with a uid that matches the address and has the highest * validity is used. If both keys have the same validity, * then the key with the newest subkey is used. * * The KeyResolver also supports groups so the number of * encryption keys does not necessarily * need to match the amount of sender addresses. For this reason * maps are used to map addresses to lists of keys. * * The keys can be OpenPGP keys and S/MIME (CMS) keys. * As a caller you need to partition the keys by their protocol and * send one message for each protocol for the recipients and signed * by the signing keys. */ class KLEO_EXPORT KeyResolver : public QObject { Q_OBJECT public: /** * Solution represents the solution found by the KeyResolver. * @a protocol hints at the protocol of the signing and encryption keys, * i.e. if @a protocol is either @c GpgME::OpenPGP or @c GpgME::CMS, then * all keys have the corresponding protocol. Otherwise, the keys have * mixed protocols. * @a signingKeys contains the signing keys to use. It contains * zero or one OpenPGP key and zero or one S/MIME key. * @a encryptionKeys contains the encryption keys to use for the * different recipients. The keys of the map represent the normalized * email addresses of the recipients. */ struct Solution { GpgME::Protocol protocol = GpgME::UnknownProtocol; std::vector signingKeys; QMap> encryptionKeys; }; /** Creates a new key resolver object. * * @param encrypt: Should encryption keys be selected. * @param sign: Should signing keys be selected. * @param protocol: A specific key protocol (OpenPGP, S/MIME) for selection. Default: Both protocols. * @param allowMixed: Specify if multiple message formats may be resolved. **/ explicit KeyResolver(bool encrypt, bool sign, GpgME::Protocol protocol = GpgME::UnknownProtocol, bool allowMixed = true); ~KeyResolver() override; /** * Set the list of recipient addresses. * * @param addresses: A list of (not necessarily normalized) email addresses */ void setRecipients(const QStringList &addresses); /** * Set the sender's address. * * This address is added to the list of recipients (for encryption to self) * and it is used for signing key resolution, if the signing keys are not * explicitly set through setSigningKeys. * * @param sender: The sender of this message. */ void setSender(const QString &sender); /** * Set up possible override keys for recipients addresses. * The keys for the fingerprints are looked * up and used when found. * * Overrides for @c GpgME::UnknownProtocol are used regardless of the * protocol. Overrides for a specific protocol are only used for this * protocol. Overrides for @c GpgME::UnknownProtocol takes precendent over * overrides for a specific protocol. * * @param overrides: A map of \ -> (\ \) */ void setOverrideKeys(const QMap > &overrides); /** * Set explicit signing keys to use. */ void setSigningKeys(const QStringList &fingerprints); /** * Set the minimum user id validity for autoresolution. * * The default value is marginal * * @param validity int representation of a GpgME::UserID::Validity. */ void setMinimumValidity(int validity); /** * Get the result of the resolution. * * @return the resolved keys for signing and encryption. */ Solution result() const; /** * Starts the key resolving procedure. Emits keysResolved on success or * error. * * @param showApproval: If set to true a dialog listing the keys * will always be shown. * @param parentWidget: Optional, a Widget to use as parent for dialogs. */ void start(bool showApproval, QWidget *parentWidget = nullptr); /** * Set window flags for a possible dialog. */ void setDialogWindowFlags(Qt::WindowFlags flags); /** * Set the protocol that is preferred to be displayed first when * it is not clear from the keys. E.g. if both OpenPGP and S/MIME * can be resolved. */ void setPreferredProtocol(GpgME::Protocol proto); Q_SIGNALS: /** * Emitted when key resolution finished. * * @param success: The general result. If true continue sending, * if false abort. * @param sendUnencrypted: If there could be no key found for one of * the recipients the user was queried if the * mail should be sent out unencrypted. * sendUnencrypted is true if the user agreed * to this.*/ void keysResolved(bool success, bool sendUnencrypted); private: class Private; std::unique_ptr d; }; } // namespace Kleo diff --git a/src/kleo/keyresolvercore.cpp b/src/kleo/keyresolvercore.cpp index 0f39cdfb8..f291017c5 100644 --- a/src/kleo/keyresolvercore.cpp +++ b/src/kleo/keyresolvercore.cpp @@ -1,785 +1,786 @@ /* -*- c++ -*- kleo/keyresolvercore.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 "keyresolvercore.h" +#include "kleo/enum.h" #include "kleo/keygroup.h" #include "models/keycache.h" #include "utils/formatting.h" #include #include "libkleo_debug.h" using namespace Kleo; using namespace GpgME; namespace { QDebug operator<<(QDebug debug, const GpgME::Key &key) { if (key.isNull()) { debug << "Null"; } else { debug << Formatting::summaryLine(key); } return debug.maybeSpace(); } 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; } static int keyValidity(const Key &key, const QString &address) { // returns the validity of the UID matching the address or, if no UID matches, the maximal validity of all UIDs int overallValidity = UserID::Validity::Unknown; for (const auto &uid: key.userIDs()) { if (QString::fromStdString(uid.addrSpec()).toLower() == address.toLower()) { return uid.validity(); } overallValidity = std::max(overallValidity, static_cast(uid.validity())); } return overallValidity; } static int minimumValidity(const std::vector &keys, const QString &address) { const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [address] (int validity, const Key &key) { return std::min(validity, keyValidity(key, address)); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } bool allKeysHaveProtocol(const std::vector &keys, Protocol protocol) { return std::all_of(keys.cbegin(), keys.cend(), [protocol] (const Key &key) { return key.protocol() == protocol; }); } bool anyKeyHasProtocol(const std::vector &keys, Protocol protocol) { return std::any_of(std::begin(keys), std::end(keys), [protocol] (const Key &key) { return key.protocol() == protocol; }); } } // namespace class KeyResolverCore::Private { public: 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 isAcceptableEncryptionKey(const Key &key, const QString &address = QString()); void setSender(const QString &address); void addRecipients(const QStringList &addresses); void setOverrideKeys(const QMap> &overrides); void resolveOverrides(); std::vector resolveRecipientWithGroup(const QString &address, Protocol protocol); void resolveEncryptionGroups(); std::vector resolveSenderWithGroup(const QString &address, Protocol protocol); void resolveSigningGroups(); void resolveSign(Protocol proto); void setSigningKeys(const QStringList &fingerprints); std::vector resolveRecipient(const QString &address, Protocol protocol); void resolveEnc(Protocol proto); void mergeEncryptionKeys(); QStringList unresolvedRecipients(GpgME::Protocol protocol) const; Result resolve(); KeyResolverCore *const q; QString mSender; QStringList mRecipients; QMap> mSigKeys; QMap>> mEncKeys; QMap> mOverrides; 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; bool mAllowMixed = true; Protocol mPreferredProtocol; int mMinimumValidity; QString mCompliance; }; 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; } } return true; } bool KeyResolverCore::Private::isAcceptableEncryptionKey(const Key &key, const QString &address) { if (!ValidEncryptionKey(key)) { 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; } void KeyResolverCore::Private::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. mFatalErrors << QStringLiteral("The sender address '%1' could not be extracted").arg(address); return; } const auto normStr = QString::fromUtf8(normalized.c_str()); if (mSign) { mSender = normStr; } addRecipients({address}); } void KeyResolverCore::Private::addRecipients(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()); mRecipients << normStr; // Initially add empty lists of keys for both protocols mEncKeys[normStr] = {{CMS, {}}, {OpenPGP, {}}}; } } void KeyResolverCore::Private::setOverrideKeys(const QMap> &overrides) { for (auto protocolIt = overrides.cbegin(); protocolIt != overrides.cend(); ++protocolIt) { const Protocol &protocol = protocolIt.key(); const auto &addressFingerprintMap = protocolIt.value(); for (auto addressIt = addressFingerprintMap.cbegin(); addressIt != addressFingerprintMap.cend(); ++addressIt) { const QString &address = addressIt.key(); const QStringList &fingerprints = addressIt.value(); const QString normalizedAddress = QString::fromUtf8(UserID::addrSpecFromString(address.toUtf8().constData()).c_str()); mOverrides[normalizedAddress][protocol] = fingerprints; } } } namespace { std::vector resolveOverride(const QString &address, Protocol protocol, const QStringList &fingerprints) { std::vector keys; for (const auto &fprOrId: fingerprints) { const Key key = KeyCache::instance()->findByKeyIDOrFingerprint(fprOrId.toUtf8().constData()); if (key.isNull()) { // FIXME: Report to caller qCDebug (LIBKLEO_LOG) << "Failed to find override key for:" << address << "fpr:" << fprOrId; continue; } if (protocol != UnknownProtocol && key.protocol() != protocol) { qCDebug(LIBKLEO_LOG) << "Ignoring key" << Formatting::summaryLine(key) << "given as" << Formatting::displayName(protocol) << "override for" << address; continue; } qCDebug(LIBKLEO_LOG) << "Using key" << Formatting::summaryLine(key) << "as" << Formatting::displayName(protocol) << "override for" << address; keys.push_back(key); } return keys; } } void KeyResolverCore::Private::resolveOverrides() { if (!mEncrypt) { // No encryption we are done. return; } for (auto addressIt = mOverrides.cbegin(); addressIt != mOverrides.cend(); ++addressIt) { const QString &address = addressIt.key(); const auto &protocolFingerprintsMap = addressIt.value(); if (!mRecipients.contains(address)) { qCDebug(LIBKLEO_LOG) << "Overrides provided for an address that is " "neither sender nor recipient. Address:" << address; continue; } const QStringList commonOverride = protocolFingerprintsMap.value(UnknownProtocol); if (!commonOverride.empty()) { mEncKeys[address][UnknownProtocol] = resolveOverride(address, UnknownProtocol, commonOverride); if (protocolFingerprintsMap.contains(OpenPGP)) { qCDebug(LIBKLEO_LOG) << "Ignoring OpenPGP-specific override for" << address << "in favor of common override"; } if (protocolFingerprintsMap.contains(CMS)) { qCDebug(LIBKLEO_LOG) << "Ignoring S/MIME-specific override for" << address << "in favor of common override"; } } else { if (mFormat != CMS) { mEncKeys[address][OpenPGP] = resolveOverride(address, OpenPGP, protocolFingerprintsMap.value(OpenPGP)); } if (mFormat != OpenPGP) { mEncKeys[address][CMS] = resolveOverride(address, CMS, protocolFingerprintsMap.value(CMS)); } } } } std::vector KeyResolverCore::Private::resolveSenderWithGroup(const QString &address, Protocol protocol) { // prefer single-protocol groups over mixed-protocol groups auto group = mCache->findGroup(address, protocol, KeyUsage::Sign); if (group.isNull()) { group = mCache->findGroup(address, UnknownProtocol, KeyUsage::Sign); } if (group.isNull()) { return {}; } // take the first key matching the protocol const auto &keys = group.keys(); const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; }); if (it == std::end(keys)) { qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has no" << Formatting::displayName(protocol) << "signing key"; return {}; } const auto key = *it; if (!isAcceptableSigningKey(key)) { qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has unacceptable signing key" << key; return {}; } return {key}; } void KeyResolverCore::Private::resolveSigningGroups() { auto &protocolKeysMap = mSigKeys; if (!protocolKeysMap[UnknownProtocol].empty()) { // already resolved by common override return; } if (mFormat == OpenPGP) { if (!protocolKeysMap[OpenPGP].empty()) { // already resolved by override return; } protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP); } else if (mFormat == CMS) { if (!protocolKeysMap[CMS].empty()) { // already resolved by override return; } protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS); } else { protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP); protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS); } } void KeyResolverCore::Private::resolveSign(Protocol proto) { if (!mSigKeys[proto].empty()) { // Explicitly set return; } const auto key = mCache->findBestByMailBox(mSender.toUtf8().constData(), proto, KeyUsage::Sign); if (key.isNull()) { qCDebug(LIBKLEO_LOG) << "Failed to find" << Formatting::displayName(proto) << "signing key for" << mSender; return; } if (!isAcceptableSigningKey(key)) { qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint() << "for" << mSender; return; } mSigKeys.insert(proto, {key}); } 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; } mSigKeys[key.protocol()].push_back(key); } } } std::vector KeyResolverCore::Private::resolveRecipientWithGroup(const QString &address, Protocol protocol) { const auto group = mCache->findGroup(address, protocol, KeyUsage::Encrypt); if (group.isNull()) { return {}; } // 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. const auto &keys = group.keys(); const bool allKeysAreAcceptable = std::all_of(std::begin(keys), std::end(keys), [this] (const auto &key) { return isAcceptableEncryptionKey(key); }); if (!allKeysAreAcceptable) { qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has at least one unacceptable key"; return {}; } for (const auto &k: keys) { qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << k.primaryFingerprint(); } std::vector result; std::copy(std::begin(keys), std::end(keys), std::back_inserter(result)); return result; } void KeyResolverCore::Private::resolveEncryptionGroups() { for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) { const QString &address = it.key(); auto &protocolKeysMap = it.value(); if (!protocolKeysMap[UnknownProtocol].empty()) { // already resolved by common override continue; } if (mFormat == OpenPGP) { if (!protocolKeysMap[OpenPGP].empty()) { // already resolved by override continue; } protocolKeysMap[OpenPGP] = resolveRecipientWithGroup(address, OpenPGP); } else if (mFormat == CMS) { if (!protocolKeysMap[CMS].empty()) { // already resolved by override continue; } protocolKeysMap[CMS] = resolveRecipientWithGroup(address, CMS); } else { // prefer single-protocol groups over mixed-protocol groups const auto openPGPGroupKeys = resolveRecipientWithGroup(address, OpenPGP); const auto smimeGroupKeys = resolveRecipientWithGroup(address, CMS); if (!openPGPGroupKeys.empty() && !smimeGroupKeys.empty()) { protocolKeysMap[OpenPGP] = openPGPGroupKeys; protocolKeysMap[CMS] = smimeGroupKeys; } else if (openPGPGroupKeys.empty() && smimeGroupKeys.empty()) { // no single-protocol groups found; // if mixed protocols are allowed, then look for any group with encryption keys if (mAllowMixed) { protocolKeysMap[UnknownProtocol] = resolveRecipientWithGroup(address, UnknownProtocol); } } else { // there is a single-protocol group only for one protocol; use this group for all protocols protocolKeysMap[UnknownProtocol] = !openPGPGroupKeys.empty() ? openPGPGroupKeys : smimeGroupKeys; } } } } std::vector KeyResolverCore::Private::resolveRecipient(const QString &address, Protocol protocol) { const auto key = mCache->findBestByMailBox(address.toUtf8().constData(), protocol, KeyUsage::Encrypt); if (key.isNull()) { qCDebug(LIBKLEO_LOG) << "Failed to find any" << Formatting::displayName(protocol) << "key for:" << address; return {}; } if (!isAcceptableEncryptionKey(key, address)) { qCDebug(LIBKLEO_LOG) << "key for:" << address << key.primaryFingerprint() << "has not enough validity"; return {}; } qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << key.primaryFingerprint(); return {key}; } // Try to find matching keys in the provided protocol for the unresolved addresses void KeyResolverCore::Private::resolveEnc(Protocol proto) { for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) { const QString &address = it.key(); auto &protocolKeysMap = it.value(); if (!protocolKeysMap[proto].empty()) { // already resolved for current protocol (by override or group) continue; } const std::vector &commonOverrideOrGroup = protocolKeysMap[UnknownProtocol]; if (!commonOverrideOrGroup.empty()) { // there is a common override or group; use it for current protocol if possible if (allKeysHaveProtocol(commonOverrideOrGroup, proto)) { protocolKeysMap[proto] = commonOverrideOrGroup; continue; } else { qCDebug(LIBKLEO_LOG) << "Common override/group for" << address << "is unusable for" << Formatting::displayName(proto); continue; } } protocolKeysMap[proto] = resolveRecipient(address, proto); } } auto getBestEncryptionKeys(const QMap>> &encryptionKeys, Protocol preferredProtocol) { QMap> result; for (auto it = encryptionKeys.begin(); it != encryptionKeys.end(); ++it) { const QString &address = it.key(); auto &protocolKeysMap = it.value(); const std::vector &overrideKeys = protocolKeysMap[UnknownProtocol]; if (!overrideKeys.empty()) { result.insert(address, overrideKeys); continue; } const std::vector &keysOpenPGP = protocolKeysMap[OpenPGP]; const std::vector &keysCMS = protocolKeysMap[CMS]; if (keysOpenPGP.empty() && keysCMS.empty()) { result.insert(address, {}); } else if (!keysOpenPGP.empty() && keysCMS.empty()) { result.insert(address, keysOpenPGP); } else if (keysOpenPGP.empty() && !keysCMS.empty()) { result.insert(address, keysCMS); } else { // check whether OpenPGP keys or S/MIME keys have higher validity const int validityPGP = minimumValidity(keysOpenPGP, address); const int validityCMS = minimumValidity(keysCMS, address); if ((validityCMS > validityPGP) || (validityCMS == validityPGP && preferredProtocol == CMS)) { result.insert(address, keysCMS); } else { result.insert(address, keysOpenPGP); } } } return result; } QStringList KeyResolverCore::Private::unresolvedRecipients(GpgME::Protocol protocol) const { QStringList result; result.reserve(mEncKeys.size()); for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) { const auto &protocolKeysMap = it.value(); if (protocolKeysMap.value(protocol).empty()) { result.push_back(it.key()); } } return result; } namespace { bool hasUnresolvedRecipients(const QMap>> &encryptionKeys, Protocol protocol) { return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys), [protocol] (const auto &protocolKeysMap) { return protocolKeysMap.value(protocol).empty(); }); } bool anyCommonOverrideHasKeyOfType(const QMap>> &encryptionKeys, Protocol protocol) { return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys), [protocol] (const auto &protocolKeysMap) { return anyKeyHasProtocol(protocolKeysMap.value(UnknownProtocol), protocol); }); } auto keysForProtocol(const QMap>> &encryptionKeys, Protocol protocol) { QMap> keys; for (auto it = std::begin(encryptionKeys), end = std::end(encryptionKeys); it != end; ++it) { const QString &address = it.key(); const auto &protocolKeysMap = it.value(); keys.insert(address, protocolKeysMap.value(protocol)); } return keys; } template auto concatenate(std::vector v1, const std::vector &v2) { v1.reserve(v1.size() + v2.size()); v1.insert(std::end(v1), std::begin(v2), std::end(v2)); return v1; } } KeyResolverCore::Result KeyResolverCore::Private::resolve() { qCDebug(LIBKLEO_LOG) << "Starting "; if (!mSign && !mEncrypt) { // nothing to do return {AllResolved, {}, {}}; } // First resolve through overrides resolveOverrides(); // check protocols needed for overrides const bool commonOverridesNeedOpenPGP = anyCommonOverrideHasKeyOfType(mEncKeys, OpenPGP); const bool commonOverridesNeedCMS = anyCommonOverrideHasKeyOfType(mEncKeys, CMS); if ((mFormat == OpenPGP && commonOverridesNeedCMS) || (mFormat == CMS && commonOverridesNeedOpenPGP) || (!mAllowMixed && commonOverridesNeedOpenPGP && commonOverridesNeedCMS)) { // invalid protocol requirements -> clear intermediate result and abort resolution mEncKeys.clear(); return {Error, {}, {}}; } // Next look for matching groups of keys if (mSign) { resolveSigningGroups(); } if (mEncrypt) { resolveEncryptionGroups(); } // Then look for signing / encryption keys if (mFormat == OpenPGP || mFormat == UnknownProtocol) { resolveSign(OpenPGP); resolveEnc(OpenPGP); } const bool pgpOnly = (!mEncrypt || !hasUnresolvedRecipients(mEncKeys, OpenPGP)) && (!mSign || mSigKeys.contains(OpenPGP)); if (mFormat == OpenPGP) { return { SolutionFlags((pgpOnly ? AllResolved : SomeUnresolved) | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {} }; } if (mFormat == CMS || mFormat == UnknownProtocol) { resolveSign(CMS); resolveEnc(CMS); } const bool cmsOnly = (!mEncrypt || !hasUnresolvedRecipients(mEncKeys, CMS)) && (!mSign || mSigKeys.contains(CMS)); if (mFormat == CMS) { return { SolutionFlags((cmsOnly ? AllResolved : SomeUnresolved) | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {} }; } // check if single-protocol solution has been found if (cmsOnly && (!pgpOnly || mPreferredProtocol == CMS)) { if (!mAllowMixed) { return { SolutionFlags(AllResolved | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)} }; } else { return { SolutionFlags(AllResolved | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {} }; } } if (pgpOnly) { if (!mAllowMixed) { return { SolutionFlags(AllResolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)} }; } else { return { SolutionFlags(AllResolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {} }; } } if (!mAllowMixed) { // return incomplete single-protocol solution if (mPreferredProtocol == CMS) { return { SolutionFlags(SomeUnresolved | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)} }; } else { return { SolutionFlags(SomeUnresolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)} }; } } const auto bestEncryptionKeys = getBestEncryptionKeys(mEncKeys, mPreferredProtocol); const bool allAddressesAreResolved = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [] (const auto &keys) { return !keys.empty(); }); if (allAddressesAreResolved) { return { SolutionFlags(AllResolved | MixedProtocols), {UnknownProtocol, concatenate(mSigKeys.value(OpenPGP), mSigKeys.value(CMS)), bestEncryptionKeys}, {} }; } const bool allKeysAreOpenPGP = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [] (const auto &keys) { return allKeysHaveProtocol(keys, OpenPGP); }); if (allKeysAreOpenPGP) { return { SolutionFlags(SomeUnresolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), bestEncryptionKeys}, {} }; } const bool allKeysAreCMS = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [] (const auto &keys) { return allKeysHaveProtocol(keys, CMS); }); if (allKeysAreCMS) { return { SolutionFlags(SomeUnresolved | CMSOnly), {CMS, mSigKeys.value(CMS), bestEncryptionKeys}, {} }; } return { SolutionFlags(SomeUnresolved | MixedProtocols), {UnknownProtocol, concatenate(mSigKeys.value(OpenPGP), mSigKeys.value(CMS)), bestEncryptionKeys}, {} }; } KeyResolverCore::KeyResolverCore(bool encrypt, bool sign, Protocol fmt) : d(new Private(this, encrypt, sign, fmt)) { } KeyResolverCore::~KeyResolverCore() = default; void KeyResolverCore::setSender(const QString &address) { d->setSender(address); } QString KeyResolverCore::normalizedSender() const { return d->mSender; } void KeyResolverCore::setRecipients(const QStringList &addresses) { d->addRecipients(addresses); } void KeyResolverCore::setSigningKeys(const QStringList &fingerprints) { d->setSigningKeys(fingerprints); } void KeyResolverCore::setOverrideKeys(const QMap> &overrides) { d->setOverrideKeys(overrides); } void KeyResolverCore::setAllowMixedProtocols(bool allowMixed) { d->mAllowMixed = allowMixed; } void KeyResolverCore::setPreferredProtocol(Protocol proto) { d->mPreferredProtocol = proto; } void KeyResolverCore::setMinimumValidity(int validity) { d->mMinimumValidity = validity; } KeyResolverCore::Result KeyResolverCore::resolve() { return d->resolve(); } diff --git a/src/kleo/keyresolvercore.h b/src/kleo/keyresolvercore.h index 15d7643dd..496e7f50e 100644 --- a/src/kleo/keyresolvercore.h +++ b/src/kleo/keyresolvercore.h @@ -1,87 +1,87 @@ /* -*- 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 */ #pragma once #include -#include "kleo_export.h" - #include #include #include #include +#include "kleo_export.h" + class QString; class QStringList; namespace GpgME { class Key; } namespace Kleo { class KLEO_EXPORT KeyResolverCore { public: enum SolutionFlags { SomeUnresolved = 0, AllResolved = 1, OpenPGPOnly = 2, CMSOnly = 4, MixedProtocols = OpenPGPOnly | CMSOnly, Error = 0x1000, ResolvedMask = AllResolved | Error, ProtocolsMask = OpenPGPOnly | CMSOnly | Error, }; struct Result { SolutionFlags flags; KeyResolver::Solution solution; KeyResolver::Solution alternative; }; 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 setAllowMixedProtocols(bool allowMixed); void setPreferredProtocol(GpgME::Protocol proto); void setMinimumValidity(int validity); Result resolve(); private: class Private; std::unique_ptr d; }; } // namespace Kleo diff --git a/src/ui/newkeyapprovaldialog.cpp b/src/ui/newkeyapprovaldialog.cpp index d53418dbe..93f1c0022 100644 --- a/src/ui/newkeyapprovaldialog.cpp +++ b/src/ui/newkeyapprovaldialog.cpp @@ -1,901 +1,903 @@ /* -*- c++ -*- newkeyapprovaldialog.cpp 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 */ #include "newkeyapprovaldialog.h" -#include "kleo/defaultkeyfilter.h" + #include "keyselectioncombo.h" #include "progressdialog.h" +#include "kleo/defaultkeyfilter.h" #include "utils/formatting.h" -#include "libkleo_debug.h" +#include +#include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include -#include -#include +#include + #include #include -#include -#include +#include "libkleo_debug.h" using namespace Kleo; using namespace GpgME; QDebug operator<<(QDebug debug, const GpgME::Key &key) { if (key.isNull()) { debug << "Null"; } else { debug << Formatting::summaryLine(key); } return debug.maybeSpace(); } namespace { class EncryptFilter: public DefaultKeyFilter { public: EncryptFilter() : DefaultKeyFilter() { setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_encryptFilter = std::shared_ptr(new EncryptFilter); class OpenPGPFilter: public DefaultKeyFilter { public: OpenPGPFilter() : DefaultKeyFilter() { setIsOpenPGP(DefaultKeyFilter::Set); setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_pgpEncryptFilter = std::shared_ptr (new OpenPGPFilter); class OpenPGPSignFilter: public DefaultKeyFilter { public: OpenPGPSignFilter() : DefaultKeyFilter() { /* Also list unusable keys to make it transparent why they are unusable */ setDisabled(DefaultKeyFilter::NotSet); setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanSign(DefaultKeyFilter::Set); setHasSecret(DefaultKeyFilter::Set); setIsOpenPGP(DefaultKeyFilter::Set); } }; static std::shared_ptr s_pgpSignFilter = std::shared_ptr (new OpenPGPSignFilter); class SMIMEFilter: public DefaultKeyFilter { public: SMIMEFilter(): DefaultKeyFilter() { setIsOpenPGP(DefaultKeyFilter::NotSet); setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_smimeEncryptFilter = std::shared_ptr (new SMIMEFilter); class SMIMESignFilter: public DefaultKeyFilter { public: SMIMESignFilter(): DefaultKeyFilter() { setDisabled(DefaultKeyFilter::NotSet); setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanSign(DefaultKeyFilter::Set); setIsOpenPGP(DefaultKeyFilter::NotSet); setHasSecret(DefaultKeyFilter::Set); } }; static std::shared_ptr s_smimeSignFilter = std::shared_ptr (new SMIMESignFilter); /* Some decoration and a button to remove the filter for a keyselectioncombo */ class ComboWidget: public QWidget { Q_OBJECT public: explicit ComboWidget(KeySelectionCombo *combo): mCombo(combo), mFilterBtn(new QPushButton) { auto hLay = new QHBoxLayout(this); auto infoBtn = new QPushButton; infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); infoBtn->setIconSize(QSize(22,22)); infoBtn->setFlat(true); hLay->addWidget(infoBtn); hLay->addWidget(combo, 1); hLay->addWidget(mFilterBtn, 0); connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn] () { QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0), mCombo->currentData(Qt::ToolTipRole).toString(), infoBtn, QRect(), 30000); }); // FIXME: This is ugly to enforce but otherwise the // icon is broken. combo->setMinimumHeight(22); mFilterBtn->setMinimumHeight(23); updateFilterButton(); connect(mFilterBtn, &QPushButton::clicked, this, [this] () { const QString curFilter = mCombo->idFilter(); if (curFilter.isEmpty()) { setIdFilter(mLastIdFilter); mLastIdFilter = QString(); } else { setIdFilter(QString()); mLastIdFilter = curFilter; } }); } void setIdFilter(const QString &id) { mCombo->setIdFilter(id); updateFilterButton(); } void updateFilterButton() { if (mCombo->idFilter().isEmpty()) { mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters"))); mFilterBtn->setToolTip(i18n("Show keys matching the email address")); } else { mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters"))); mFilterBtn->setToolTip(i18n("Show all keys")); } } KeySelectionCombo *combo() { return mCombo; } GpgME::Protocol fixedProtocol() const { return mFixedProtocol; } void setFixedProtocol(GpgME::Protocol proto) { mFixedProtocol = proto; } private: KeySelectionCombo *mCombo; QPushButton *mFilterBtn; QString mLastIdFilter; GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol; }; static enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key) { enum GpgME::UserID::Validity validity = GpgME::UserID::Validity::Unknown; for (const auto &uid: key.userIDs()) { if (validity == GpgME::UserID::Validity::Unknown || validity > uid.validity()) { validity = uid.validity(); } } return validity; } static bool key_has_addr(const GpgME::Key &key, const QString &addr) { for (const auto &uid: key.userIDs()) { if (QString::fromStdString(uid.addrSpec()).toLower() == addr.toLower()) { return true; } } return false; } bool anyKeyHasProtocol(const std::vector &keys, Protocol protocol) { return std::any_of(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; }); } Key findfirstKeyOfType(const std::vector &keys, Protocol protocol) { const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; }); return it != std::end(keys) ? *it : Key(); } } // namespace class NewKeyApprovalDialog::Private { private: enum Action { Unset, GenerateKey, IgnoreKey, }; public: enum { OpenPGPButtonId = 1, SMIMEButtonId = 2 }; Private(NewKeyApprovalDialog *qq, bool encrypt, bool sign, GpgME::Protocol forcedProtocol, GpgME::Protocol presetProtocol, const QString &sender, bool allowMixed) : mForcedProtocol{forcedProtocol} , mSender{sender} , mSign{sign} , mEncrypt{encrypt} , mAllowMixed{allowMixed} , q{qq} { Q_ASSERT(forcedProtocol == GpgME::UnknownProtocol || presetProtocol == GpgME::UnknownProtocol || presetProtocol == forcedProtocol); Q_ASSERT(!allowMixed || (allowMixed && forcedProtocol == GpgME::UnknownProtocol)); Q_ASSERT(!(!allowMixed && presetProtocol == GpgME::UnknownProtocol)); // We do the translation here to avoid having the same string multiple times. mGenerateTooltip = i18nc("@info:tooltip for a 'Generate new key pair' action " "in a combobox when a user does not yet have an OpenPGP or S/MIME key.", "Generate a new key using your E-Mail address.

" "The key is necessary to decrypt and sign E-Mails. " "You will be asked for a passphrase to protect this key and the protected key " "will be stored in your home directory."); mMainLay = new QVBoxLayout; QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mOkButton = btnBox->button(QDialogButtonBox::Ok); #ifndef NDEBUG mOkButton->setObjectName(QStringLiteral("ok button")); #endif QObject::connect (btnBox, &QDialogButtonBox::accepted, q, [this] () { accepted(); }); QObject::connect (btnBox, &QDialogButtonBox::rejected, q, &QDialog::reject); mScrollArea = new QScrollArea; mScrollArea->setWidget(new QWidget); mScrollLayout = new QVBoxLayout; mScrollArea->widget()->setLayout(mScrollLayout); mScrollArea->setWidgetResizable(true); mScrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); mScrollArea->setFrameStyle(QFrame::NoFrame); mScrollLayout->setContentsMargins(0, 0, 0, 0); q->setWindowTitle(i18nc("@title:window", "Security approval")); auto fmtLayout = new QHBoxLayout; mFormatBtns = new QButtonGroup(qq); QAbstractButton *pgpBtn; QAbstractButton *smimeBtn; if (mAllowMixed) { pgpBtn = new QCheckBox(i18n("OpenPGP")); smimeBtn = new QCheckBox(i18n("S/MIME")); } else { pgpBtn = new QRadioButton(i18n("OpenPGP")); smimeBtn = new QRadioButton(i18n("S/MIME")); } #ifndef NDEBUG pgpBtn->setObjectName(QStringLiteral("openpgp button")); smimeBtn->setObjectName(QStringLiteral("smime button")); #endif mFormatBtns->addButton(pgpBtn, OpenPGPButtonId); mFormatBtns->addButton(smimeBtn, SMIMEButtonId); mFormatBtns->setExclusive(!mAllowMixed); fmtLayout->addStretch(-1); fmtLayout->addWidget(pgpBtn); fmtLayout->addWidget(smimeBtn); mMainLay->addLayout(fmtLayout); if (mForcedProtocol != GpgME::UnknownProtocol) { pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP); smimeBtn->setChecked(mForcedProtocol == GpgME::CMS); pgpBtn->setVisible(false); smimeBtn->setVisible(false); } else { pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP || presetProtocol == GpgME::UnknownProtocol); smimeBtn->setChecked(presetProtocol == GpgME::CMS || presetProtocol == GpgME::UnknownProtocol); } QObject::connect(mFormatBtns, &QButtonGroup::idClicked, q, [this](int buttonId) { // ensure that at least one protocol button is checked if (mAllowMixed && !mFormatBtns->button(OpenPGPButtonId)->isChecked() && !mFormatBtns->button(SMIMEButtonId)->isChecked()) { mFormatBtns->button(buttonId == OpenPGPButtonId ? SMIMEButtonId : OpenPGPButtonId)->setChecked(true); } updateWidgets(); }); mMainLay->addWidget(mScrollArea); mComplianceLbl = new QLabel; mComplianceLbl->setVisible(false); #ifndef NDEBUG mComplianceLbl->setObjectName(QStringLiteral("compliance label")); #endif auto btnLayout = new QHBoxLayout; btnLayout->addWidget(mComplianceLbl); btnLayout->addWidget(btnBox); mMainLay->addLayout(btnLayout); q->setLayout(mMainLay); } ~Private() = default; Protocol currentProtocol() { const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked(); const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked(); if (mAllowMixed) { if (openPGPButtonChecked && !smimeButtonChecked) { return OpenPGP; } if (!openPGPButtonChecked && smimeButtonChecked) { return CMS; } } else if (openPGPButtonChecked) { return OpenPGP; } else if (smimeButtonChecked) { return CMS; } return UnknownProtocol; } auto findVisibleKeySelectionComboWithGenerateKey() { const auto it = std::find_if(std::begin(mAllCombos), std::end(mAllCombos), [] (auto combo) { return combo->isVisible() && combo->currentData(Qt::UserRole).toInt() == GenerateKey; }); return it != std::end(mAllCombos) ? *it : nullptr; } void generateKey(KeySelectionCombo *combo) { const auto &addr = combo->property("address").toString(); auto job = new QGpgME::DefaultKeyGenerationJob(q); auto progress = new Kleo::ProgressDialog(job, i18n("Generating key for '%1'...", addr) + QStringLiteral("\n\n") + i18n("This can take several minutes."), q); progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint); progress->setWindowTitle(i18nc("@title:window", "Key generation")); progress->setModal(true); progress->setAutoClose(true); progress->setMinimumDuration(0); progress->setValue(0); mRunningJobs << job; connect (job, &QGpgME::DefaultKeyGenerationJob::result, q, [this, job, combo] (const GpgME::KeyGenerationResult &result) { handleKeyGenResult(result, job, combo); }); job->start(addr, QString()); return; } void handleKeyGenResult(const GpgME::KeyGenerationResult &result, QGpgME::Job *job, KeySelectionCombo *combo) { mLastError = result.error(); if (!mLastError || mLastError.isCanceled()) { combo->setDefaultKey(QString::fromLatin1(result.fingerprint()), GpgME::OpenPGP); connect (combo, &KeySelectionCombo::keyListingFinished, q, [this, job] () { mRunningJobs.removeAll(job); }); combo->refreshKeys(); } else { mRunningJobs.removeAll(job); } } void checkAccepted() { if (mLastError || mLastError.isCanceled()) { KMessageBox::error(q, QString::fromLocal8Bit(mLastError.asString()), i18n("Operation Failed")); mRunningJobs.clear(); return; } if (!mRunningJobs.empty()) { return; } /* Save the keys */ mAcceptedResult.protocol = currentProtocol(); for (const auto combo: qAsConst(mEncCombos)) { const auto addr = combo->property("address").toString(); const auto key = combo->currentKey(); if (!combo->isVisible() || key.isNull()) { continue; } mAcceptedResult.encryptionKeys[addr].push_back(key); } for (const auto combo: qAsConst(mSigningCombos)) { const auto key = combo->currentKey(); if (!combo->isVisible() || key.isNull()) { continue; } mAcceptedResult.signingKeys.push_back(key); } q->accept(); } void accepted() { // We can assume everything was validly resolved, otherwise // the OK button would have been disabled. // Handle custom items now. if (auto combo = findVisibleKeySelectionComboWithGenerateKey()) { generateKey(combo); return; } checkAccepted(); } auto encryptionKeyFilter(Protocol protocol) { switch (protocol) { case OpenPGP: return s_pgpEncryptFilter; case CMS: return s_smimeEncryptFilter; default: return s_encryptFilter; } } void updateWidgets() { const Protocol protocol = currentProtocol(); const auto encryptionFilter = encryptionKeyFilter(protocol); for (auto combo: qAsConst(mSigningCombos)) { auto widget = qobject_cast(combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget"; continue; } widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == UnknownProtocol || widget->fixedProtocol() == protocol); } for (auto combo: qAsConst(mEncCombos)) { auto widget = qobject_cast(combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find combo widget"; continue; } widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == UnknownProtocol || widget->fixedProtocol() == protocol); if (widget->isVisible() && combo->property("address") != mSender) { combo->setKeyFilter(encryptionFilter); } } // hide the labels indicating the protocol of the sender's keys if only a single protocol is active const auto protocolLabels = q->findChildren(QStringLiteral("protocol label")); for (auto label: protocolLabels) { label->setVisible(protocol == UnknownProtocol); } } auto createProtocolLabel(Protocol protocol) { auto label = new QLabel(Formatting::displayName(protocol)); label->setObjectName(QStringLiteral("protocol label")); return label; } ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, Protocol protocol = UnknownProtocol) { Q_ASSERT(!key.isNull() || protocol != UnknownProtocol); protocol = !key.isNull() ? key.protocol() : protocol; auto combo = new KeySelectionCombo(); auto comboWidget = new ComboWidget(combo); #ifndef NDEBUG combo->setObjectName(QStringLiteral("signing key")); #endif if (protocol == GpgME::OpenPGP) { combo->setKeyFilter(s_pgpSignFilter); } else if (protocol == GpgME::CMS) { combo->setKeyFilter(s_smimeSignFilter); } if (key.isNull() || key_has_addr(key, mSender)) { comboWidget->setIdFilter(mSender); } comboWidget->setFixedProtocol(protocol); if (!key.isNull()) { combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), protocol); } if (key.isNull() && protocol == OpenPGP) { combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip); } combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")), i18n("Don't confirm identity and integrity"), IgnoreKey, i18nc("@info:tooltip for not selecting a key for signing.", "The E-Mail will not be cryptographically signed.")); mSigningCombos << combo; mAllCombos << combo; combo->setProperty("address", addr); connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () { updateOkButton(); }); connect(combo, QOverload::of(&QComboBox::currentIndexChanged), q, [this] () { updateOkButton(); }); return comboWidget; } void setSigningKeys(std::vector preferredKeys, std::vector alternativeKeys) { auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", mSender)); group->setAlignment(Qt::AlignLeft); auto sigLayout = new QVBoxLayout(group); const bool mayNeedOpenPGP = mForcedProtocol != CMS; const bool mayNeedCMS = mForcedProtocol != OpenPGP; if (mayNeedOpenPGP) { if (mAllowMixed) { sigLayout->addWidget(createProtocolLabel(OpenPGP)); } const Key preferredKey = findfirstKeyOfType(preferredKeys, OpenPGP); const Key alternativeKey = findfirstKeyOfType(alternativeKeys, OpenPGP); if (!preferredKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey; auto comboWidget = createSigningCombo(mSender, preferredKey); sigLayout->addWidget(comboWidget); } else if (!alternativeKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey; auto comboWidget = createSigningCombo(mSender, alternativeKey); sigLayout->addWidget(comboWidget); } else { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for OpenPGP key"; auto comboWidget = createSigningCombo(mSender, Key(), OpenPGP); sigLayout->addWidget(comboWidget); } } if (mayNeedCMS) { if (mAllowMixed) { sigLayout->addWidget(createProtocolLabel(CMS)); } const Key preferredKey = findfirstKeyOfType(preferredKeys, CMS); const Key alternativeKey = findfirstKeyOfType(alternativeKeys, CMS); if (!preferredKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey; auto comboWidget = createSigningCombo(mSender, preferredKey); sigLayout->addWidget(comboWidget); } else if (!alternativeKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey; auto comboWidget = createSigningCombo(mSender, alternativeKey); sigLayout->addWidget(comboWidget); } else { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for S/MIME key"; auto comboWidget = createSigningCombo(mSender, Key(), CMS); sigLayout->addWidget(comboWidget); } } mScrollLayout->addWidget(group); } ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, Protocol fixedProtocol) { auto combo = new KeySelectionCombo(false); auto comboWidget = new ComboWidget(combo); #ifndef NDEBUG combo->setObjectName(QStringLiteral("encryption key")); #endif if (fixedProtocol == GpgME::OpenPGP) { combo->setKeyFilter(s_pgpEncryptFilter); } else if (fixedProtocol == GpgME::CMS) { combo->setKeyFilter(s_smimeEncryptFilter); } else { combo->setKeyFilter(s_encryptFilter); } if (key.isNull() || key_has_addr (key, addr)) { comboWidget->setIdFilter(addr); } comboWidget->setFixedProtocol(fixedProtocol); if (!key.isNull()) { combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol); } if (addr == mSender && key.isNull() && fixedProtocol == OpenPGP) { combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip); } combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")), i18n("No key. Recipient will be unable to decrypt."), IgnoreKey, i18nc("@info:tooltip for No Key selected for a specific recipient.", "Do not select a key for this recipient.

" "The recipient will receive the encrypted E-Mail, but it can only " "be decrypted with the other keys selected in this dialog.")); connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () { updateOkButton(); }); connect(combo, QOverload::of(&QComboBox::currentIndexChanged), q, [this] () { updateOkButton(); }); mEncCombos << combo; mAllCombos << combo; combo->setProperty("address", addr); return comboWidget; } void addEncryptionAddr(const QString &addr, Protocol preferredKeysProtocol, const std::vector &preferredKeys, Protocol alternativeKeysProtocol, const std::vector &alternativeKeys, QGridLayout *encGrid) { if (addr == mSender) { const bool mayNeedOpenPGP = mForcedProtocol != CMS; const bool mayNeedCMS = mForcedProtocol != OpenPGP; if (mayNeedOpenPGP) { if (mAllowMixed) { encGrid->addWidget(createProtocolLabel(OpenPGP), encGrid->rowCount(), 0); } for (const auto &key : preferredKeys) { if (key.protocol() == OpenPGP) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } for (const auto &key : alternativeKeys) { if (key.protocol() == OpenPGP) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (!anyKeyHasProtocol(preferredKeys, OpenPGP) && !anyKeyHasProtocol(alternativeKeys, OpenPGP)) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for OpenPGP key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (mayNeedCMS) { if (mAllowMixed) { encGrid->addWidget(createProtocolLabel(CMS), encGrid->rowCount(), 0); } for (const auto &key : preferredKeys) { if (key.protocol() == CMS) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } for (const auto &key : alternativeKeys) { if (key.protocol() == CMS) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (!anyKeyHasProtocol(preferredKeys, CMS) && !anyKeyHasProtocol(alternativeKeys, CMS)) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for S/MIME key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } } else { encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0); for (const auto &key : preferredKeys) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, preferredKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } for (const auto &key : alternativeKeys) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, alternativeKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } if (!mAllowMixed) { if (preferredKeys.empty()) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(preferredKeysProtocol) << "key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), preferredKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } if (alternativeKeys.empty() && alternativeKeysProtocol != UnknownProtocol) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(alternativeKeysProtocol) << "key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), alternativeKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } else { if (preferredKeys.empty() && alternativeKeys.empty()) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for any key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), UnknownProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } } } void setEncryptionKeys(Protocol preferredKeysProtocol, const QMap> &preferredKeys, Protocol alternativeKeysProtocol, const QMap> &alternativeKeys) { { auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender)); group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout(group); addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid); mScrollLayout->addWidget(group); } auto group = new QGroupBox(i18n("Encrypt to others:")); group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout; group->setLayout(encGrid); mScrollLayout->addWidget(group); for (auto it = std::begin(preferredKeys); it != std::end(preferredKeys); ++it) { const auto &address = it.key(); const auto &keys = it.value(); if (address != mSender) { addEncryptionAddr(address, preferredKeysProtocol, keys, alternativeKeysProtocol, alternativeKeys.value(address), encGrid); } } encGrid->setColumnStretch(1, -1); mScrollLayout->addStretch(-1); } void updateOkButton() { static QString origOkText = mOkButton->text(); const bool isGenerate = bool(findVisibleKeySelectionComboWithGenerateKey()); const bool allVisibleEncryptionKeysAreIgnored = std::all_of(std::begin(mEncCombos), std::end(mEncCombos), [] (auto combo) { return !combo->isVisible() || combo->currentData(Qt::UserRole).toInt() == IgnoreKey; }); // If we don't encrypt the ok button is always enabled. But otherwise // we only enable it if we encrypt to at least one recipient. mOkButton->setEnabled(!mEncrypt || !allVisibleEncryptionKeysAreIgnored); mOkButton->setText(isGenerate ? i18n("Generate") : origOkText); if (Formatting::complianceMode() != QLatin1String("de-vs")) { return; } // Handle compliance bool de_vs = true; const Protocol protocol = currentProtocol(); for (const auto combo: qAsConst(mAllCombos)) { if (!combo->isVisible()) { continue; } const auto key = combo->currentKey(); if (key.isNull()) { continue; } if (protocol != UnknownProtocol && key.protocol() != protocol) { continue; } if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } mOkButton->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); mOkButton->setStyleSheet(QStringLiteral("background-color: ") + (de_vs ? QStringLiteral("#D5FAE2") // KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() : QStringLiteral("#FAE9EB"))); //KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); mComplianceLbl->setText(de_vs ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication possible.", Formatting::deVsString()) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication not possible.", Formatting::deVsString())); mComplianceLbl->setVisible(true); } GpgME::Protocol mForcedProtocol; QList mSigningCombos; QList mEncCombos; QList mAllCombos; QScrollArea *mScrollArea; QVBoxLayout *mScrollLayout; QPushButton *mOkButton; QVBoxLayout *mMainLay; QButtonGroup *mFormatBtns; QString mSender; bool mSign; bool mEncrypt; bool mAllowMixed; NewKeyApprovalDialog *q; QList mRunningJobs; GpgME::Error mLastError; QLabel *mComplianceLbl; KeyResolver::Solution mAcceptedResult; QString mGenerateTooltip; }; NewKeyApprovalDialog::NewKeyApprovalDialog(bool encrypt, bool sign, const QString &sender, KeyResolver::Solution preferredSolution, KeyResolver::Solution alternativeSolution, bool allowMixed, GpgME::Protocol forcedProtocol, QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f) , d{std::make_unique(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)} { if (sign) { d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys)); } if (encrypt) { d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol, std::move(preferredSolution.encryptionKeys), allowMixed ? UnknownProtocol : alternativeSolution.protocol, std::move(alternativeSolution.encryptionKeys)); } d->updateWidgets(); d->updateOkButton(); const auto size = sizeHint(); const auto desk = QApplication::desktop()->screenGeometry(this); resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2))); } Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default; KeyResolver::Solution NewKeyApprovalDialog::result() { return d->mAcceptedResult; } #include "newkeyapprovaldialog.moc" diff --git a/src/ui/newkeyapprovaldialog.h b/src/ui/newkeyapprovaldialog.h index 4f7793756..991dc1696 100644 --- a/src/ui/newkeyapprovaldialog.h +++ b/src/ui/newkeyapprovaldialog.h @@ -1,82 +1,80 @@ /* -*- c++ -*- newkeyapprovaldialog.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 */ #pragma once #include -#include "kleo_export.h" - #include -#include +#include -#include +#include "kleo_export.h" namespace Kleo { /** @brief A dialog to show for encryption / signing key approval or selection. * * This class is intended to replace the old KeyApprovalDialog with a new * and simpler interface. * * Resolved recipients in this API means a recipient could be resolved * to a single useful key. An unresolved recipient is a recipient for * whom no key could be found. Import / Search will be offered for such * a recipient. Multiple keys for signing / recipient can come e.g. from * group configuration or Addressbook / Identity configuration. * * The Dialog uses the Level System for validity display and shows an * overall outgoing level. * */ class KLEO_EXPORT NewKeyApprovalDialog : public QDialog { Q_OBJECT public: /** @brief Create a new Key Approval Dialog. * * @param sender: The address of the sender, this may be used if signing is not * specified to identify a recipient for which "Generate Key" should * be offered. * @param preferredSolution: The preferred signing and/or encryption keys for the sender * and the recipients. * @param alternativeSolution: An alternative set of signing and/or encryption keys for the sender * and the recipients. Typically, S/MIME-only, if preferred solution is OpenPGP-only, * and vice versa. Ignored, if mixed protocol selection is allowed. * @param allowMixed: Whether or not the dialog should allow mixed S/MIME / OpenPGP key selection. * @param forcedProtocol: A specific forced protocol. * @param parent: The parent widget. * @param f: The Qt window flags. */ explicit NewKeyApprovalDialog(bool encrypt, bool sign, const QString &sender, KeyResolver::Solution preferredSolution, KeyResolver::Solution alternativeSolution, bool allowMixed, GpgME::Protocol forcedProtocol, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~NewKeyApprovalDialog() override; /** @brief The selected signing and/or encryption keys. Only valid after the dialog was accepted. */ KeyResolver::Solution result(); private: class Private; std::unique_ptr d; }; } // namespace kleo