diff --git a/src/kleo/keyresolver.cpp b/src/kleo/keyresolver.cpp index d66f36bc6..ba8d020c8 100644 --- a/src/kleo/keyresolver.cpp +++ b/src/kleo/keyresolver.cpp @@ -1,739 +1,738 @@ /* -*- 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 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 - -#include "libkleo_debug.h" #include "keyresolver.h" + #include "models/keycache.h" +#include "ui/newkeyapprovaldialog.h" #include "utils/formatting.h" -#include "ui/newkeyapprovaldialog.h" +#include -#include +#include "libkleo_debug.h" using namespace Kleo; namespace { static inline bool ValidEncryptionKey(const GpgME::Key &key) { if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt()) { return false; } return true; } static inline bool ValidSigningKey(const GpgME::Key &key) { if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign() || !key.hasSecret()) { return false; } return true; } } // namespace class KeyResolver::Private { public: Private(KeyResolver* qq, bool enc, bool sig, CryptoMessageFormat fmt, bool allowMixed) : q(qq), mFormat(fmt), mEncrypt(enc), mSign(sig), mNag(true), mAllowMixed(allowMixed), mCache(KeyCache::instance()), mDialogWindowFlags(Qt::WindowFlags()), mPreferredProtocol(GpgME::UnknownProtocol), mMinimumValidity(GpgME::UserID::Marginal), mCompliance(Formatting::complianceMode()) { } - ~Private() - { - } + ~Private() = default; bool isAcceptableSigningKey(const GpgME::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 isAcceptableEncryptionKey(const GpgME::Key &key, const QString &address = QString()) { 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 addRecpients (const QStringList &addresses, bool hidden) { 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 = GpgME::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; } // Add it to the according recipient lists if (hidden) { mHiddenRecipients << normStr; } else { mRecipients << normStr; } } } // Apply the overrides this is also where specific formats come in void resolveOverrides() { if (!mEncrypt) { // No encryption we are done. return; } for (CryptoMessageFormat fmt: mOverrides.keys()) { // Iterate over the crypto message formats if (mFormat != AutoFormat && mFormat != fmt && fmt != AutoFormat) { // 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; } // Now add it to the resolved keys and remove it from our list // of unresolved keys. QMap > > *targetMap; if (mRecipients.contains(addr)) { targetMap = &mEncKeys; } else if (mHiddenRecipients.contains(addr)) { targetMap = &mBccKeys; } else { qCWarning(LIBKLEO_LOG) << "Override provided for an address that is " "neither sender nor recipient. Address: " << addr; continue; } CryptoMessageFormat resolvedFmt = fmt; if (fmt == AutoFormat) { // Take the format from the key. if (key.protocol() == GpgME::OpenPGP) { resolvedFmt = AnyOpenPGP; } else { resolvedFmt = AnySMIME; } } auto recpMap = targetMap->value(resolvedFmt); auto keys = recpMap.value(addr); keys.push_back(key); recpMap.insert(addr, keys); targetMap->insert(resolvedFmt, recpMap); // Now we can remove it from our unresolved lists. if (key.protocol() == GpgME::OpenPGP) { mUnresolvedPGP.removeAll(addr); } else { mUnresolvedCMS.removeAll(addr); } qCDebug(LIBKLEO_LOG) << "Override" << addr << cryptoMessageFormatToString (resolvedFmt) << fprOrId; } } } } void resolveSign(GpgME::Protocol proto) { auto fmt = proto == GpgME::OpenPGP ? AnyOpenPGP : AnySMIME; if (mSigKeys.contains(fmt)) { // Explicitly set return; } 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 (!keys.empty() && !keys[0].isNull()) { mSigKeys.insert(fmt, keys); } } void setSigningKeys(const std::vector &keys) { if (mSign) { for (const auto &key: keys) { const auto sigFmt = key.protocol() == GpgME::Protocol::OpenPGP ? AnyOpenPGP : AnySMIME; auto list = mSigKeys.value(sigFmt); list.push_back(key); mSigKeys.insert(sigFmt, list); } } } // Try to find matching keys in the provided protocol for the unresolved addresses // only updates the any maps. void resolveEnc(GpgME::Protocol proto) { auto fmt = proto == GpgME::OpenPGP ? AnyOpenPGP : AnySMIME; auto encMap = mEncKeys.value(fmt); auto hiddenMap = mBccKeys.value(fmt); QMutableStringListIterator it((proto == GpgME::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 == GpgME::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; } } 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; } } if (mHiddenRecipients.contains(addr)) { hiddenMap.insert(addr, keys); } else { 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(fmt, encMap); mBccKeys.insert(fmt, hiddenMap); } void encMapToSpecific(CryptoMessageFormat anyFormat, CryptoMessageFormat specificFormat, QMap > >&encMap) { Q_ASSERT(anyFormat & specificFormat); if (!encMap.contains(anyFormat)) { return; } for (const auto &addr: encMap[anyFormat].keys()) { if (!encMap.contains(specificFormat)) { encMap.insert(specificFormat, QMap >()); } encMap[specificFormat].insert(addr, encMap[anyFormat][addr]); } encMap.remove(anyFormat); } void reduceToSingle(CryptoMessageFormat targetFmt) { // We a have a specific format so we need to map any keys // into that format. This ignores overrides as the format // was explicitly set. CryptoMessageFormat srcFmt = (targetFmt & AnySMIME) ? AnySMIME : AnyOpenPGP; if (mSigKeys.contains(srcFmt)) { mSigKeys.insert(targetFmt, mSigKeys.take(srcFmt)); } encMapToSpecific(srcFmt, targetFmt, mEncKeys); encMapToSpecific(srcFmt, targetFmt, mBccKeys); } void updateEncMap(QMap > &target, QMap > &src) { for (const auto &addr: target.keys()) { if (src.contains(addr)) { target.insert(addr, src[addr]); } } } void updateEncMaps(CryptoMessageFormat target, CryptoMessageFormat src) { if (mBccKeys.contains(src) && mBccKeys.contains(target)) { updateEncMap(mBccKeys[target], mBccKeys[src]); } if (mEncKeys.contains(src) && mEncKeys.contains(target)) { updateEncMap(mEncKeys[target], mEncKeys[src]); } } bool needsFormat(CryptoMessageFormat fmt) { return mBccKeys.contains(fmt) || mEncKeys.contains(fmt); } void selectFormats() { // Check if we can find a single common specific format that works if (mFormat != AutoFormat && mFormat != AnyOpenPGP && mFormat != AnySMIME) { reduceToSingle(mFormat); } // OpenPGP // By default prefer OpenPGPMIME bool needTwoPGP = needsFormat(OpenPGPMIMEFormat) && needsFormat(InlineOpenPGPFormat); reduceToSingle(OpenPGPMIMEFormat); if (needTwoPGP) { // We need two messages as we have conflicting preferences. // Then we need to check that if we sign the PGP MIME Message we // also sign the inline one. if (mSigKeys.contains(OpenPGPMIMEFormat)) { mSigKeys.insert(InlineOpenPGPFormat, mSigKeys[OpenPGPMIMEFormat]); } // Then it's also possible that a user updated a key in the // UI so we need to check that too. updateEncMaps(InlineOpenPGPFormat, OpenPGPMIMEFormat); } // Similar for S/MIME bool needTwoSMIME = needsFormat(SMIMEOpaqueFormat) && needsFormat(SMIMEFormat); // Here we prefer real S/MIME reduceToSingle(SMIMEFormat); if (needTwoSMIME) { if (mSigKeys.contains(SMIMEFormat)) { mSigKeys.insert(SMIMEOpaqueFormat, mSigKeys[SMIMEFormat]); } updateEncMaps(SMIMEOpaqueFormat, SMIMEFormat); } return; } void showApprovalDialog(QWidget *parent) { QMap > resolvedSig; QStringList unresolvedSig; bool pgpOnly = mUnresolvedPGP.empty() && (!mSign || mSigKeys.contains(AnyOpenPGP)); bool cmsOnly = mUnresolvedCMS.empty() && (!mSign || mSigKeys.contains(AnySMIME)); // 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); } } resolvedSig.insert(mSender, resolvedSigKeys); } } // Now build the encryption keys QMap > resolvedRecp; QStringList unresolvedRecp; if (mEncrypt) { // Use all unresolved recipients. if (!cmsOnly && !pgpOnly) { if (mFormat & AutoFormat) { // 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; } } } else if (mFormat & AnyOpenPGP) { unresolvedRecp = mUnresolvedPGP; } else if (mFormat & AnySMIME) { unresolvedRecp = mUnresolvedCMS; } } // 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 GpgME::Key &y) { return (k.primaryFingerprint() && y.primaryFingerprint() && !strcmp (k.primaryFingerprint(), y.primaryFingerprint())); }); if (it == merged.end()) { merged.push_back(k); } } resolvedRecp[addr] = merged; } } } } // Do we force the protocol? GpgME::Protocol forcedProto = mFormat == AutoFormat ? GpgME::UnknownProtocol : mFormat & AnyOpenPGP ? GpgME::OpenPGP : GpgME::CMS; // Start with the protocol for which every keys could be found. GpgME::Protocol presetProtocol; if (mPreferredProtocol == GpgME::CMS && cmsOnly) { presetProtocol = GpgME::CMS; } else { presetProtocol = pgpOnly ? GpgME::OpenPGP : cmsOnly ? GpgME::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()) { CryptoMessageFormat fmt = key.protocol() == GpgME::OpenPGP ? AnyOpenPGP : AnySMIME; if (!mSigKeys.contains(fmt)) { mSigKeys.insert(fmt, std::vector()); } mSigKeys[fmt].push_back(key); } 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(AnyOpenPGP); mEncKeys.remove(AnySMIME); mBccKeys.remove(AnyOpenPGP); mBccKeys.remove(AnySMIME); bool isUnresolved = false; for (const auto &addr: encMap.keys()) { for (const auto &key: encMap[addr]) { if (key.isNull()) { isUnresolved = true; } CryptoMessageFormat fmt = key.protocol() == GpgME::OpenPGP ? AnyOpenPGP : AnySMIME; // Should we add to hidden or normal? QMap > > *targetMap = mHiddenRecipients.contains(addr) ? &mBccKeys : &mEncKeys; if (!targetMap->contains(fmt)) { targetMap->insert(fmt, QMap >()); } if (!(*targetMap)[fmt].contains(addr)) { (*targetMap)[fmt].insert(addr, std::vector()); } qCDebug (LIBKLEO_LOG) << "Adding" << addr << "for" << cryptoMessageFormatToString (fmt) << "fpr:" << key.primaryFingerprint(); (*targetMap)[fmt][addr].push_back(key); } } if (isUnresolved) { // TODO show warning } Q_EMIT q->keysResolved(true, false); } KeyResolver *const q; QString mSender; QStringList mRecipients; QStringList mHiddenRecipients; QMap > mSigKeys; QMap > >mEncKeys; QMap > >mBccKeys; QMap > mOverrides; QStringList mUnresolvedPGP, mUnresolvedCMS; CryptoMessageFormat mFormat; QStringList mFatalErrors; bool mEncrypt, mSign, mNag; 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; GpgME::Protocol mPreferredProtocol; int mMinimumValidity; QString mCompliance; }; 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); } // First resolve through overrides d->resolveOverrides(); // Then look for signing / encryption keys if (d->mFormat & AnyOpenPGP) { d->resolveSign(GpgME::OpenPGP); d->resolveEnc(GpgME::OpenPGP); } bool pgpOnly = d->mUnresolvedPGP.empty() && (!d->mSign || d->mSigKeys.contains(AnyOpenPGP)); if (d->mFormat & AnySMIME) { d->resolveSign(GpgME::CMS); d->resolveEnc(GpgME::CMS); } bool cmsOnly = d->mUnresolvedCMS.empty() && (!d->mSign || d->mSigKeys.contains(AnySMIME)); // 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(AnyOpenPGP) && d->mSigKeys.contains(AnySMIME)); } } if (!needsUser && !showApproval) { if (pgpOnly) { d->mSigKeys.remove(AnySMIME); d->mEncKeys.remove(AnySMIME); d->mBccKeys.remove(AnySMIME); } if (cmsOnly) { d->mSigKeys.remove(AnyOpenPGP); d->mEncKeys.remove(AnyOpenPGP); d->mBccKeys.remove(AnyOpenPGP); } d->selectFormats(); qCDebug(LIBKLEO_LOG) << "Automatic key resolution done."; Q_EMIT keysResolved(true, false); return; } else if (!needsUser) { qCDebug(LIBKLEO_LOG) << "No need for the user showing approval anyway."; } d->showApprovalDialog(parentWidget); } KeyResolver::KeyResolver(bool encrypt, bool sign, CryptoMessageFormat fmt, bool allowMixed) : d(new Private(this, encrypt, sign, fmt, allowMixed)) { } +Kleo::KeyResolver::~KeyResolver() = default; + void KeyResolver::setRecipients(const QStringList &addresses) { d->addRecpients(addresses, false); } void KeyResolver::setSender(const QString &address) { const auto normalized = GpgME::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; } if (d->mEncrypt) { if (!d->mUnresolvedCMS.contains(normStr)) { d->mUnresolvedCMS << normStr; } if (!d->mUnresolvedPGP.contains(normStr)) { d->mUnresolvedPGP << normStr; } } } void KeyResolver::setHiddenRecipients(const QStringList &addresses) { d->addRecpients(addresses, true); } void KeyResolver::setOverrideKeys(const QMap > &overrides) { QMap normalizedOverrides; for (const auto fmt: overrides.keys()) { for (const auto &addr: overrides[fmt].keys()) { const auto normalized = QString::fromUtf8( GpgME::UserID::addrSpecFromString (addr.toUtf8().constData()).c_str()); const auto fingerprints = overrides[fmt][addr]; normalizedOverrides.insert(addr, fingerprints); } d->mOverrides.insert(fmt, normalizedOverrides); } } QMap > > KeyResolver::encryptionKeys() const { return d->mEncKeys; } QMap > > KeyResolver::hiddenKeys() const { return d->mBccKeys; } QMap > KeyResolver::signingKeys() const { return d->mSigKeys; } QMap > KeyResolver::overrideKeys() const { return d->mOverrides; } void KeyResolver::enableNagging(bool value) { d->mNag = value; } void KeyResolver::setDialogWindowFlags(Qt::WindowFlags flags) { d->mDialogWindowFlags = flags; } void KeyResolver::setPreferredProtocol(GpgME::Protocol proto) { d->mPreferredProtocol = proto; } void KeyResolver::setMinimumValidity(int validity) { d->mMinimumValidity = validity; } diff --git a/src/kleo/keyresolver.h b/src/kleo/keyresolver.h index 2ca061cd7..740e4a79c 100644 --- a/src/kleo/keyresolver.h +++ b/src/kleo/keyresolver.h @@ -1,232 +1,236 @@ /* -*- c++ -*- keyresolver.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEO_KEYRESOLVER_H__ #define __KLEO_KEYRESOLVER_H__ #include "kleo_export.h" -#include -#include +#include -#include #include -#include #include +#include +#include + +#include #include +#include -#include +namespace GpgME +{ +class Key; +} namespace Kleo { /** * Class to find Keys for E-Mail encryption. * * The KeyResolver uses the Keycache to find keys for signing * or encryption. * * Overrides can be provided for address book integration if the * format is not Auto overrides will only be respected if they * match the format provided in the constructor. * * If no override key is provided for an address the key * with a uid that matches the address and has the highest * validity is used. If both keys have the same validity * the newest subkey is used. * * The KeyResolver also supports groups so the number of * encryption keys / hidden encryption keys does not necessarily * need to match the amount of sender addresses. For this reason * maps are used heavily to map: * * CryptoFormat * - Addresses * -- For each Address a List of Keys * * As a caller you should iterate over the CryptoFormats and * send one message for each format for the recipients and signed * by the signing keys. * * If the CryptoMessageFormat is Auto the minimum number * of CryptoMessageFormats is returned that respects all overrides. * * ----- * Planned: * * As the central place to manage mail encryption / signing keys * the Keyresolver will also show various warning / nagging messages * and offer solutions if nagging is not explicitly turned off. * These include: * * - If own keys or subkeys are about to expire: * Offer to extend their expiration date. * * - (S/MIME) If they are about to expire: Offer * to generate a new CSR. * * - If a user has not marked a key as backed up and * uses it for encryption for several mails. * * - If a user has multiple keys for a sender address and they * are not cross signed. Offer to cross sign / publish. */ class KLEO_EXPORT KeyResolver : public QObject { Q_OBJECT public: /** Creates a new key resolver object. * * @param encrypt: Should encryption keys be selected. * @param sign: Should signing keys be selected. * @param format: A specific format for selection. Default Auto. * @param allowMixed: Specify if multiple message formats may be resolved. **/ explicit KeyResolver(bool encrypt, bool sign, CryptoMessageFormat format = AutoFormat, bool allowMixed = true); - ~KeyResolver() {} + ~KeyResolver() override; /** * Set the list of (To/CC) recipient addresses. Also looks * up possible keys, but doesn't interact with the user. * * @param addresses: A list of unnormalized addresses */ void setRecipients(const QStringList &addresses); /** * Set the senders address. * * Sender address will be added to encryption keys and 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 the list of hidden (BCC) recipient addresses. Also looks * up possible keys, but doesn't interact with the user. * * @param addresses: A list of unnormalized addresses. */ void setHiddenRecipients(const QStringList &addresses); /** * Set up possible override keys for recipients / sender * addresses. The keys for the fingerprints are looked * up and used when found. Does not interact with the user. * * @param overrides: A map of \ -> (\ \) */ void setOverrideKeys(const QMap > &overrides); /** * Set explicit signing keys. If this was set for a * protocol the sender address will be only used as an additional encryption * recipient for that protocol. */ void setSigningKeys(const QStringList &fingerprints); /** * Turn Nagging messages off or on, default on. * See class description about nagging. * * @param value: Turn nagging on or off. */ void enableNagging(bool value); /** * 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 encryption keys after resolution. * * @return the resolved sender / key pairs for encryption by format. */ QMap > > encryptionKeys() const; /** * Get the secondary encryption keys after resolution. * The Map will only contain values if hidden recipients * were set. * * @return the resolved resolved sender / key pairs for encryption * by format. */ QMap > > hiddenKeys() const; /** * Get the signing keys to use after resolution. * * @return the resolved resolved sender / key pairs for signing * by format. */ QMap > signingKeys() 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); /** * Access possibly updated Override Keys * * @return A map of email's with new overrides and the according * cryptoformat / fingerprint. Should be saved somehow. */ QMap > overrideKeys() const; /** * 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; - friend class Private; - std::shared_ptr d; + std::unique_ptr d; }; } // namespace Kleo #endif // __KLEO_KEYRESOLVER_H__ diff --git a/src/tests/test_keyresolver.cpp b/src/tests/test_keyresolver.cpp index ce8fa8be4..a1bef8ff2 100644 --- a/src/tests/test_keyresolver.cpp +++ b/src/tests/test_keyresolver.cpp @@ -1,168 +1,170 @@ /* test_keyresolver.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-License-Identifier: GPL-2.0-only */ #include "kleo/keyresolver.h" #include #include #include #include +#include + using namespace Kleo; void dumpKeys(const QMap > > &fmtMap) { for (const CryptoMessageFormat fmt: fmtMap.keys()) { qDebug () << "Format:" << cryptoMessageFormatToLabel(fmt) << fmt; for (const auto &mbox: fmtMap[fmt].keys()) { qDebug() << "Address:" << mbox; qDebug() << "Keys:"; for (const auto &key: fmtMap[fmt][mbox]) { qDebug () << key.primaryFingerprint(); } } } } void dumpSigKeys(const QMap > &fmtMap) { for (const CryptoMessageFormat fmt: fmtMap.keys()) { qDebug () << "Format:" << cryptoMessageFormatToLabel(fmt) << fmt; qDebug() << "Keys:"; for (const auto &key: fmtMap[fmt]) { qDebug () << key.primaryFingerprint(); } } } class SignalRecipient: public QObject { Q_OBJECT public: SignalRecipient(KeyResolver *res) : resolver(res) {} void keysResolved(bool success, bool sendUnencrypted) { if (!success) { qDebug() << "Canceled"; exit(1); } qDebug() << "Resolved Signing keys:"; dumpSigKeys (resolver->signingKeys()); qDebug() << "Resolved Encryption keys:"; dumpKeys (resolver->encryptionKeys()); qDebug() << "Resolved Hidden keys:"; dumpKeys (resolver->hiddenKeys()); qDebug() << "Send Unencrypted:" << sendUnencrypted; exit(0); } private: KeyResolver *resolver; }; int main(int argc, char **argv) { QApplication app(argc, argv); QCommandLineParser parser; parser.setApplicationDescription(QStringLiteral("Test KeyResolver class")); parser.addHelpOption(); parser.addPositionalArgument(QStringLiteral("recipients"), QStringLiteral("Recipients to resolve"), QStringLiteral("[mailboxes]")); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("overrides") << QStringLiteral("o"), QStringLiteral("Override where format can be:\n" "InlineOpenPGP\n" "OpenPGPMIME\n" "SMIME\n" "SMIMEOpaque\n" "AnyOpenPGP\n" "AnySMIME\n" "Auto"), QStringLiteral("mailbox:fpr,fpr,..:format"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("sender") << QStringLiteral("s"), QStringLiteral("Mailbox of the sender"), QStringLiteral("mailbox"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("hidden") << QStringLiteral("h"), QStringLiteral("hidden recipients"), QStringLiteral("A hidden / bcc recipient"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("sigkeys") << QStringLiteral("k"), QStringLiteral("signing key"), QStringLiteral("Explicit signing keys"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("encrypt") << QStringLiteral("e"), QStringLiteral("Only select encryption keys"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("approval") << QStringLiteral("a"), QStringLiteral("Always show approval dlg"))); parser.process(app); const QStringList recps = parser.positionalArguments(); if (recps.size() < 1) { parser.showHelp(1); } KeyResolver resolver(true, !parser.isSet(QStringLiteral("encrypt"))); resolver.setRecipients(recps); resolver.setSender(parser.value(QStringLiteral("sender"))); QMap > overrides; for (const QString &oride: parser.values(QStringLiteral("overrides"))) { const QStringList split = oride.split(QLatin1Char(':')); CryptoMessageFormat fmt = AutoFormat; if (split.size() < 2 || split.size() > 3) { parser.showHelp(1); } if (split.size() == 3) { const QString fmtStr = split[2].toLower(); if (fmtStr == QLatin1String("inlineopenpgp")) { fmt = InlineOpenPGPFormat; } else if (fmtStr == QLatin1String("openpgpmime")) { fmt = OpenPGPMIMEFormat; } else if (fmtStr == QLatin1String("smime")) { fmt = SMIMEFormat; } else if (fmtStr == QLatin1String("smimeopaque")) { fmt = SMIMEOpaqueFormat; } else if (fmtStr == QLatin1String("anyopenpgp")) { fmt = AnyOpenPGP; } else if (fmtStr == QLatin1String("anysmime")) { fmt = AnySMIME; } else if (fmtStr == QLatin1String("auto")) { fmt = AutoFormat; } else { parser.showHelp(1); } } const QStringList fingerprints = split[1].split(QLatin1Char(',')); auto map = overrides.value(fmt); map.insert(split[0], fingerprints); overrides.insert(fmt, map); } resolver.setOverrideKeys(overrides); auto recp = new SignalRecipient(&resolver); QObject::connect (&resolver, &KeyResolver::keysResolved, recp, &SignalRecipient::keysResolved); QTimer::singleShot(1000, [&parser, &resolver]() { resolver.start(parser.isSet(QStringLiteral("approval"))); }); app.exec(); return 0; } #include "test_keyresolver.moc" diff --git a/src/ui/newkeyapprovaldialog.cpp b/src/ui/newkeyapprovaldialog.cpp index 6723a400b..4ec527ab5 100644 --- a/src/ui/newkeyapprovaldialog.cpp +++ b/src/ui/newkeyapprovaldialog.cpp @@ -1,748 +1,750 @@ /* -*- c++ -*- newkeyapprovaldialog.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "newkeyapprovaldialog.h" #include "kleo/defaultkeyfilter.h" #include "keyselectioncombo.h" #include "progressdialog.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 using namespace Kleo; namespace { class OpenPGPFilter: public DefaultKeyFilter { public: OpenPGPFilter() : DefaultKeyFilter() { setIsOpenPGP(DefaultKeyFilter::Set); setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_pgpFilter = 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_smimeFilter = 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); static std::shared_ptr s_defaultFilter= std::shared_ptr (new DefaultKeyFilter); class SignFilter: public DefaultKeyFilter { public: SignFilter(): DefaultKeyFilter() { setHasSecret(DefaultKeyFilter::Set); } }; static std::shared_ptr s_signFilter = std::shared_ptr (new SignFilter); /* 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), mFromOverride(GpgME::UnknownProtocol) { 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); }); // Assume that combos start out with a filter mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters"))); mFilterBtn->setToolTip(i18n("Remove Filter")); // FIXME: This is ugly to enforce but otherwise the // icon is broken. combo->setMinimumHeight(22); mFilterBtn->setMinimumHeight(23); connect(mFilterBtn, &QPushButton::clicked, this, [this] () { const QString curFilter = mCombo->idFilter(); if (curFilter.isEmpty()) { mCombo->setIdFilter(mLastIdFilter); mLastIdFilter = QString(); mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters"))); mFilterBtn->setToolTip(i18n("Remove Filter")); } else { mLastIdFilter = curFilter; mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters"))); mFilterBtn->setToolTip(i18n("Add Filter")); mCombo->setIdFilter(QString()); } }); } KeySelectionCombo *combo() { return mCombo; } GpgME::Protocol fromOverride() const { return mFromOverride; } void setFromOverride(GpgME::Protocol proto) { mFromOverride = proto; } private: KeySelectionCombo *mCombo; QPushButton *mFilterBtn; QString mLastIdFilter; GpgME::Protocol mFromOverride; }; 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; } } // namespace class NewKeyApprovalDialog::Private { private: enum Action { Unset, GenerateKey, IgnoreKey, }; public: Private(NewKeyApprovalDialog *pub, GpgME::Protocol forcedProtocol, GpgME::Protocol presetProtocol, const QString &sender, bool allowMixed): mProto(forcedProtocol), mSender(sender), mAllowMixed(allowMixed), q(pub) { // 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); 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; auto pgpBtn = new QRadioButton(i18n("OpenPGP")); auto smimeBtn = new QRadioButton(i18n("S/MIME")); mFormatBtns->addButton(pgpBtn, 1); mFormatBtns->addButton(smimeBtn, 2); mFormatBtns->setExclusive(true); fmtLayout->addStretch(-1); fmtLayout->addWidget(pgpBtn); fmtLayout->addWidget(smimeBtn); mMainLay->addLayout(fmtLayout); // Handle force / preset if (forcedProtocol == GpgME::OpenPGP) { pgpBtn->setChecked(true); pgpBtn->setVisible(false); smimeBtn->setVisible(false); } else if (forcedProtocol == GpgME::CMS) { smimeBtn->setChecked(true); pgpBtn->setVisible(false); smimeBtn->setVisible(false); } else if (presetProtocol == GpgME::CMS) { smimeBtn->setChecked(true); } else if (!mAllowMixed) { pgpBtn->setChecked(true); } else if (mAllowMixed) { smimeBtn->setVisible(false); pgpBtn->setVisible(false); } updateFilter(); QObject::connect (mFormatBtns, static_cast (&QButtonGroup::buttonToggled), q, [this](QAbstractButton *, bool) { updateFilter(); }); mMainLay->addWidget(mScrollArea); mComplianceLbl = new QLabel; mComplianceLbl->setVisible(false); auto btnLayout = new QHBoxLayout; btnLayout->addWidget(mComplianceLbl); btnLayout->addWidget(btnBox); mMainLay->addLayout(btnLayout); q->setLayout(mMainLay); } + ~Private() = default; + 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 */ bool isPGP = mFormatBtns->checkedId() == 1; bool isSMIME = mFormatBtns->checkedId() == 2; mAcceptedEnc.clear(); mAcceptedSig.clear(); for (const auto combo: qAsConst(mEncCombos)) { const auto &addr = combo->property("address").toString(); const auto &key = combo->currentKey(); if (!combo->isVisible()) { continue; } if (isSMIME && key.protocol() != GpgME::CMS) { continue; } if (isPGP && key.protocol() != GpgME::OpenPGP) { continue; } if (mAcceptedEnc.contains(addr)) { mAcceptedEnc[addr].push_back(key); } else { std::vector vec; vec.push_back(key); mAcceptedEnc.insert(addr, vec); } } for (const auto combo: qAsConst(mSigningCombos)) { const auto key = combo->currentKey(); if (!combo->isVisible()) { continue; } if (isSMIME && key.protocol() != GpgME::CMS) { continue; } if (isPGP && key.protocol() != GpgME::OpenPGP) { continue; } mAcceptedSig.push_back(combo->currentKey()); } q->accept(); } void accepted() { // We can assume everything was validly resolved, otherwise // the OK button would have been disabled. // Handle custom items now. for (auto combo: qAsConst(mAllCombos)) { auto act = combo->currentData(Qt::UserRole).toInt(); if (act == GenerateKey) { generateKey(combo); // Only generate once return; } } checkAccepted(); } void updateFilter() { bool isPGP = mFormatBtns->checkedId() == 1; bool isSMIME = mFormatBtns->checkedId() == 2; if (isSMIME) { mCurEncFilter = s_smimeFilter; mCurSigFilter = s_smimeSignFilter; } else if (isPGP) { mCurEncFilter = s_pgpFilter; mCurSigFilter = s_pgpSignFilter; } else { mCurEncFilter = s_defaultFilter; mCurSigFilter = s_signFilter; } for (auto combo: qAsConst(mSigningCombos)) { combo->setKeyFilter(mCurSigFilter); auto widget = qobject_cast (combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget"; continue; } widget->setVisible(widget->fromOverride() == GpgME::UnknownProtocol || ((isSMIME && widget->fromOverride() == GpgME::CMS) || (isPGP && widget->fromOverride() == GpgME::OpenPGP))); } for (auto combo: qAsConst(mEncCombos)) { combo->setKeyFilter(mCurEncFilter); auto widget = qobject_cast (combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find combo widget"; continue; } widget->setVisible(widget->fromOverride() == GpgME::UnknownProtocol || ((isSMIME && widget->fromOverride() == GpgME::CMS) || (isPGP && widget->fromOverride() == GpgME::OpenPGP))); } } ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key) { auto combo = new KeySelectionCombo(); combo->setKeyFilter(mCurSigFilter); if (!key.isNull()) { combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), key.protocol()); } if (key.isNull() && mProto != GpgME::CMS) { 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 new ComboWidget(combo); } void addSigningKeys(const QMap > &resolved, const QStringList &unresolved) { if (resolved.empty() && unresolved.empty()) { return; } for (const QString &addr: resolved.keys()) { auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", addr)); group->setAlignment(Qt::AlignLeft); mScrollLayout->addWidget(group); auto sigLayout = new QVBoxLayout; group->setLayout(sigLayout); for (const auto &key: resolved[addr]) { auto comboWidget = createSigningCombo(addr, key); if (key_has_addr (key, addr)) { comboWidget->combo()->setIdFilter(addr); } if (resolved[addr].size() > 1) { comboWidget->setFromOverride(key.protocol()); } sigLayout->addWidget(comboWidget); } } for (const QString &addr: qAsConst(unresolved)) { auto group = new QGroupBox(i18nc("Caption for signing key selection, no key found", "No key found for the address '%1':", addr)); group->setAlignment(Qt::AlignLeft); mScrollLayout->addWidget(group); auto sigLayout = new QHBoxLayout; group->setLayout(sigLayout); auto comboWidget = createSigningCombo(addr, GpgME::Key()); comboWidget->combo()->setIdFilter(addr); sigLayout->addWidget(comboWidget); } } void addEncryptionAddr(const QString &addr, const std::vector &keys, QGridLayout *encGrid) { encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0); for (const auto &key: keys) { auto combo = new KeySelectionCombo(false); combo->setKeyFilter(mCurEncFilter); if (!key.isNull()) { combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), key.protocol()); } if (mSender == addr && key.isNull()) { 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.")); if (key.isNull() || key_has_addr (key, addr)) { combo->setIdFilter(addr); } connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () { updateOkButton(); }); connect(combo, QOverload::of(&QComboBox::currentIndexChanged), q, [this] () { updateOkButton(); }); mEncCombos << combo; mAllCombos << combo; combo->setProperty("address", addr); auto comboWidget = new ComboWidget(combo); if (keys.size() > 1) { comboWidget->setFromOverride(key.protocol()); } encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } void addEncryptionKeys(const QMap > &resolved, const QStringList &unresolved) { if (resolved.empty() && unresolved.empty()) { return; } auto group = new QGroupBox(i18n("Encrypt to:")); group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout; group->setLayout(encGrid); mScrollLayout->addWidget(group); for (const QString &addr: resolved.keys()) { addEncryptionAddr(addr, resolved[addr], encGrid); } std::vector dummy; dummy.push_back(GpgME::Key()); for (const QString &addr: unresolved) { addEncryptionAddr(addr, dummy, encGrid); } encGrid->setColumnStretch(1, -1); mScrollLayout->addStretch(-1); } void updateOkButton() { static QString origOkText = mOkButton->text(); bool isGenerate = false; bool isAllIgnored = true; // Check if generate is selected. for (auto combo: mAllCombos) { auto act = combo->currentData(Qt::UserRole).toInt(); if (act == GenerateKey) { mOkButton->setText(i18n("Generate")); isGenerate = true; } if (act != IgnoreKey) { isAllIgnored = false; } } // 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. if (!mEncCombos.size()) { mOkButton->setEnabled(true); } else { mOkButton->setEnabled(!isAllIgnored); } if (!isGenerate) { mOkButton->setText(origOkText); } if (Formatting::complianceMode() != QLatin1String("de-vs")) { return; } // Handle compliance bool de_vs = true; bool isPGP = mFormatBtns->checkedId() == 1; bool isSMIME = mFormatBtns->checkedId() == 2; for (const auto combo: qAsConst(mEncCombos)) { const auto &key = combo->currentKey(); if (!combo->isVisible()) { continue; } if (isSMIME && key.protocol() != GpgME::CMS) { continue; } if (isPGP && key.protocol() != GpgME::OpenPGP) { continue; } if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } if (de_vs) { for (const auto combo: qAsConst(mSigningCombos)) { const auto key = combo->currentKey(); if (!combo->isVisible()) { continue; } if (isSMIME && key.protocol() != GpgME::CMS) { continue; } if (isPGP && key.protocol() != GpgME::OpenPGP) { 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); } void selectionChanged() { bool isPGP = false; bool isCMS = false; for (const auto combo: mEncCombos) { isPGP |= combo->currentKey().protocol() == GpgME::OpenPGP; isCMS |= combo->currentKey().protocol() == GpgME::CMS; if (isPGP && isCMS) { break; } } } - ~Private() {} - GpgME::Protocol mProto; QList mSigningCombos; QList mEncCombos; QList mAllCombos; QScrollArea *mScrollArea; QVBoxLayout *mScrollLayout; QPushButton *mOkButton; QVBoxLayout *mMainLay; QButtonGroup *mFormatBtns; std::shared_ptr mCurSigFilter; std::shared_ptr mCurEncFilter; QString mSender; bool mAllowMixed; NewKeyApprovalDialog *q; QList mRunningJobs; GpgME::Error mLastError; QLabel *mComplianceLbl; QMap > mAcceptedEnc; std::vector mAcceptedSig; QString mGenerateTooltip; }; NewKeyApprovalDialog::NewKeyApprovalDialog(const QMap > &resolvedSigningKeys, const QMap > &resolvedRecp, const QStringList &unresolvedSigKeys, const QStringList &unresolvedRecp, const QString &sender, bool allowMixed, GpgME::Protocol forcedProtocol, GpgME::Protocol presetProtocol, QWidget *parent, Qt::WindowFlags f): QDialog(parent, f), d(new Private(this, forcedProtocol, presetProtocol, sender, allowMixed)) { d->addSigningKeys(resolvedSigningKeys, unresolvedSigKeys); d->addEncryptionKeys(resolvedRecp, unresolvedRecp); d->updateFilter(); 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; + std::vector NewKeyApprovalDialog::signingKeys() { return d->mAcceptedSig; } QMap > NewKeyApprovalDialog::encryptionKeys() { return d->mAcceptedEnc; } #include "newkeyapprovaldialog.moc" diff --git a/src/ui/newkeyapprovaldialog.h b/src/ui/newkeyapprovaldialog.h index bb0f68972..61f90ea92 100644 --- a/src/ui/newkeyapprovaldialog.h +++ b/src/ui/newkeyapprovaldialog.h @@ -1,90 +1,92 @@ /* -*- c++ -*- newkeyapprovaldialog.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEO_NEWKEYAPPROVALDIALOG_H__ #define __KLEO_NEWKEYAPPROVALDIALOG_H__ #include "kleo_export.h" #include #include #include 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 resolvedSigningKeys: A map of signing addresses and Keys. Usually the * map would contain a single element and a single key * but configuration may allow more. * @param resolvedRecp: A map of a recipient address and the keys for that address. Multiple * keys could for example be configured through Address book or GnuPG * Groups. * @param unresolvedSigKeys: A list of signing addresses for which no key was found. Should * usually be only one. If resolved and unresolved sig keys are * empty it is assumed signing was not selected. * @param unresolvedRecp: A list of encryption target addresses if both unresolved and * resolved recipients are empty it is assumed no encryption should * take place. * @param senderAddr: The address of the sender, this may be used if singing is not * specified to identify a recipient for which "Generate Key" should * be offered. * @param allowMixed: Whether or not the dialog should allow mixed CMS / PGP key selection. * @param forcedProtocol: A specific forced protocol. * @param presetProtocol: A specific preselected protocol. If Protocol is unknown it will allow * both (depending on allowMixed) S/MIME and OpenPGP. * @param parent: The parent widget. * @param f: The Qt window flags. */ explicit NewKeyApprovalDialog(const QMap > &resolvedSigningKeys, const QMap > &resolvedRecp, const QStringList &unresolvedSigKeys, const QStringList &unresolvedRecp, const QString &senderAddr, bool allowMixed, GpgME::Protocol forcedProtocol, GpgME::Protocol presetProtocol, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + ~NewKeyApprovalDialog() override; + /** @brief The selected signing Keys. Only valid after Dialog was accepted. */ std::vector signingKeys(); /** @brief The selected encryption Keys. Only valid after Dialog was accepted. */ QMap > encryptionKeys(); private: class Private; - std::shared_ptr d; + std::unique_ptr d; }; } // namespace kleo #endif //__KLEO_NEWKEYAPPROVALDIALOG_H__