diff --git a/src/kleo/keyresolver.cpp b/src/kleo/keyresolver.cpp index 19a8d3cf6..19611f6cd 100644 --- a/src/kleo/keyresolver.cpp +++ b/src/kleo/keyresolver.cpp @@ -1,693 +1,592 @@ /* -*- 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 "keyresolver.h" #include "models/keycache.h" #include "ui/newkeyapprovaldialog.h" #include "utils/formatting.h" #include #include "libkleo_debug.h" using namespace Kleo; +using namespace GpgME; namespace { -static inline bool ValidEncryptionKey(const GpgME::Key &key) +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 GpgME::Key &key) +static inline bool ValidSigningKey(const Key &key) { if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign() || !key.hasSecret()) { return false; } return true; } } // namespace class KeyResolver::Private { public: - Private(KeyResolver* qq, bool enc, bool sig, CryptoMessageFormat fmt, bool allowMixed) : + Private(KeyResolver* qq, bool enc, bool sig, Protocol fmt, bool allowMixed) : q(qq), mFormat(fmt), mEncrypt(enc), mSign(sig), mAllowMixed(allowMixed), mCache(KeyCache::instance()), mDialogWindowFlags(Qt::WindowFlags()), - mPreferredProtocol(GpgME::UnknownProtocol), - mMinimumValidity(GpgME::UserID::Marginal), + mPreferredProtocol(UnknownProtocol), + mMinimumValidity(UserID::Marginal), mCompliance(Formatting::complianceMode()) { } ~Private() = default; - bool isAcceptableSigningKey(const GpgME::Key &key) + bool 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 isAcceptableEncryptionKey(const GpgME::Key &key, const QString &address = QString()) + bool isAcceptableEncryptionKey(const 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) { 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()); + const auto normalized = UserID::addrSpecFromString (addr.toUtf8().constData()); if (normalized.empty()) { // should not happen bug in the caller, non localized // error for bug reporting. mFatalErrors << QStringLiteral("The mail address for '%1' could not be extracted").arg(addr); continue; } const QString normStr = QString::fromUtf8(normalized.c_str()); // Initially mark them as unresolved for both protocols if (!mUnresolvedCMS.contains(normStr)) { mUnresolvedCMS << normStr; } if (!mUnresolvedPGP.contains(normStr)) { mUnresolvedPGP << normStr; } mRecipients << normStr; } } // 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()) { + for (Protocol fmt: mOverrides.keys()) { // Iterate over the crypto message formats - if (mFormat != AutoFormat && mFormat != fmt && fmt != AutoFormat) { + if (mFormat != UnknownProtocol && mFormat != fmt && fmt != UnknownProtocol) { // Skip overrides for the wrong format continue; } for (const auto &addr: mOverrides[fmt].keys()) { // For all address overrides of this format. for (const auto &fprOrId: mOverrides[fmt][addr]) { // For all the keys configured for this address. const auto key = mCache->findByKeyIDOrFingerprint(fprOrId.toUtf8().constData()); if (key.isNull()) { qCDebug (LIBKLEO_LOG) << "Failed to find override key for:" << addr << "fpr:" << fprOrId; continue; } // Now add it to the resolved keys and remove it from our list // of unresolved keys. if (!mRecipients.contains(addr)) { qCDebug(LIBKLEO_LOG) << "Override provided for an address that is " "neither sender nor recipient. Address: " << addr; continue; } - CryptoMessageFormat resolvedFmt = fmt; - if (fmt == AutoFormat) { + Protocol resolvedFmt = fmt; + if (fmt == UnknownProtocol) { // Take the format from the key. - if (key.protocol() == GpgME::OpenPGP) { - resolvedFmt = AnyOpenPGP; - } else { - resolvedFmt = AnySMIME; - } + resolvedFmt = key.protocol(); } auto recpMap = mEncKeys.value(resolvedFmt); auto keys = recpMap.value(addr); keys.push_back(key); recpMap.insert(addr, keys); mEncKeys.insert(resolvedFmt, recpMap); // Now we can remove it from our unresolved lists. - if (key.protocol() == GpgME::OpenPGP) { + if (key.protocol() == OpenPGP) { mUnresolvedPGP.removeAll(addr); } else { mUnresolvedCMS.removeAll(addr); } - qCDebug(LIBKLEO_LOG) << "Override" << addr << cryptoMessageFormatToString (resolvedFmt) << fprOrId; + qCDebug(LIBKLEO_LOG) << "Override" << addr << Formatting::displayName(resolvedFmt) << fprOrId; } } } } - void resolveSign(GpgME::Protocol proto) + void resolveSign(Protocol proto) { - auto fmt = proto == GpgME::OpenPGP ? AnyOpenPGP : AnySMIME; - if (mSigKeys.contains(fmt)) { + if (mSigKeys.contains(proto)) { // Explicitly set return; } const auto keys = mCache->findBestByMailBox(mSender.toUtf8().constData(), proto, true, false); for (const auto &key: keys) { if (key.isNull()) { continue; } if (!isAcceptableSigningKey(key)) { qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint() << "for" << mSender; return; } } if (!keys.empty() && !keys[0].isNull()) { - mSigKeys.insert(fmt, keys); + mSigKeys.insert(proto, keys); } } - void setSigningKeys(const std::vector &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); + auto list = mSigKeys.value(key.protocol()); list.push_back(key); - mSigKeys.insert(sigFmt, list); + mSigKeys.insert(key.protocol(), list); } } } // Try to find matching keys in the provided protocol for the unresolved addresses // only updates the any maps. - void resolveEnc(GpgME::Protocol proto) + void resolveEnc(Protocol proto) { - auto fmt = proto == GpgME::OpenPGP ? AnyOpenPGP : AnySMIME; - auto encMap = mEncKeys.value(fmt); - QMutableStringListIterator it((proto == GpgME::Protocol::OpenPGP) ? mUnresolvedPGP : mUnresolvedCMS); + auto encMap = mEncKeys.value(proto); + QMutableStringListIterator it((proto == Protocol::OpenPGP) ? mUnresolvedPGP : mUnresolvedCMS); while (it.hasNext()) { const QString addr = it.next(); const auto keys = mCache->findBestByMailBox(addr.toUtf8().constData(), proto, false, true); if (keys.empty() || keys[0].isNull()) { qCDebug(LIBKLEO_LOG) << "Failed to find any" - << (proto == GpgME::Protocol::OpenPGP ? "OpenPGP" : "CMS") + << (proto == Protocol::OpenPGP ? "OpenPGP" : "CMS") << "key for: " << addr; continue; } if (keys.size() == 1) { if (!isAcceptableEncryptionKey(keys[0], addr)) { qCDebug(LIBKLEO_LOG) << "key for: " << addr << keys[0].primaryFingerprint() << "has not enough validity"; continue; } } 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; } } 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); - } - - 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); - } - - 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 (mEncKeys.contains(src) && mEncKeys.contains(target)) { - updateEncMap(mEncKeys[target], mEncKeys[src]); - } - } - - bool needsFormat(CryptoMessageFormat fmt) - { - return 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; + mEncKeys.insert(proto, encMap); } void showApprovalDialog(QWidget *parent) { - QMap > resolvedSig; + QMap > resolvedSig; QStringList unresolvedSig; - bool pgpOnly = mUnresolvedPGP.empty() && (!mSign || mSigKeys.contains(AnyOpenPGP)); - bool cmsOnly = mUnresolvedCMS.empty() && (!mSign || mSigKeys.contains(AnySMIME)); + bool pgpOnly = mUnresolvedPGP.empty() && (!mSign || mSigKeys.contains(OpenPGP)); + bool cmsOnly = mUnresolvedCMS.empty() && (!mSign || mSigKeys.contains(CMS)); // First handle the signing keys if (mSign) { if (mSigKeys.empty()) { unresolvedSig << mSender; } else { - std::vector resolvedSigKeys; + 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; + QMap > resolvedRecp; QStringList unresolvedRecp; if (mEncrypt) { // Use all unresolved recipients. if (!cmsOnly && !pgpOnly) { - if (mFormat & AutoFormat) { + if (mFormat == UnknownProtocol) { // In Auto Format we can now remove recipients that could // be resolved either through CMS or PGP for (const auto &addr: qAsConst(mUnresolvedPGP)) { if (mUnresolvedCMS.contains(addr)) { unresolvedRecp << addr; } } - } else if (mFormat & AnyOpenPGP) { + } else if (mFormat == OpenPGP) { unresolvedRecp = mUnresolvedPGP; - } else if (mFormat & AnySMIME) { + } else if (mFormat == CMS) { 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]; + 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) { + const auto it = std::find_if (merged.begin(), merged.end(), [k] (const Key &y) { return (k.primaryFingerprint() && y.primaryFingerprint() && !strcmp (k.primaryFingerprint(), y.primaryFingerprint())); }); if (it == merged.end()) { merged.push_back(k); } } resolvedRecp[addr] = merged; } } } } // Do we force the protocol? - GpgME::Protocol forcedProto = mFormat == AutoFormat ? GpgME::UnknownProtocol : - mFormat & AnyOpenPGP ? GpgME::OpenPGP : - GpgME::CMS; + Protocol forcedProto = mFormat; // Start with the protocol for which every keys could be found. - GpgME::Protocol presetProtocol; + Protocol presetProtocol; - if (mPreferredProtocol == GpgME::CMS && cmsOnly) { - presetProtocol = GpgME::CMS; + if (mPreferredProtocol == CMS && cmsOnly) { + presetProtocol = CMS; } else { - presetProtocol = pgpOnly ? GpgME::OpenPGP : - cmsOnly ? GpgME::CMS : + presetProtocol = pgpOnly ? OpenPGP : + cmsOnly ? CMS : mPreferredProtocol; } mDialog = std::shared_ptr(new NewKeyApprovalDialog(resolvedSig, resolvedRecp, unresolvedSig, unresolvedRecp, mSender, mAllowMixed, forcedProto, presetProtocol, parent, mDialogWindowFlags)); connect (mDialog.get(), &QDialog::accepted, q, [this] () { dialogAccepted(); }); connect (mDialog.get(), &QDialog::rejected, q, [this] () { Q_EMIT q->keysResolved(false, false);} ); mDialog->open(); } void dialogAccepted() { // Update keymaps accordingly mSigKeys.clear(); for (const auto &key: mDialog->signingKeys()) { - CryptoMessageFormat fmt = key.protocol() == GpgME::OpenPGP ? AnyOpenPGP : AnySMIME; - if (!mSigKeys.contains(fmt)) { - mSigKeys.insert(fmt, std::vector()); + if (!mSigKeys.contains(key.protocol())) { + mSigKeys.insert(key.protocol(), std::vector()); } - mSigKeys[fmt].push_back(key); + mSigKeys[key.protocol()].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); + mEncKeys.remove(OpenPGP); + mEncKeys.remove(CMS); bool isUnresolved = false; for (const auto &addr: encMap.keys()) { for (const auto &key: encMap[addr]) { if (key.isNull()) { isUnresolved = true; } - CryptoMessageFormat fmt = key.protocol() == GpgME::OpenPGP ? AnyOpenPGP : AnySMIME; - if (!mEncKeys.contains(fmt)) { - mEncKeys.insert(fmt, QMap >()); + if (!mEncKeys.contains(key.protocol())) { + mEncKeys.insert(key.protocol(), QMap >()); } - if (!mEncKeys[fmt].contains(addr)) { - mEncKeys[fmt].insert(addr, std::vector()); + if (!mEncKeys[key.protocol()].contains(addr)) { + mEncKeys[key.protocol()].insert(addr, std::vector()); } - qCDebug (LIBKLEO_LOG) << "Adding" << addr << "for" << cryptoMessageFormatToString (fmt) + qCDebug (LIBKLEO_LOG) << "Adding" << addr << "for" << Formatting::displayName(key.protocol()) << "fpr:" << key.primaryFingerprint(); - mEncKeys[fmt][addr].push_back(key); + mEncKeys[key.protocol()][addr].push_back(key); } } if (isUnresolved) { // TODO show warning } Q_EMIT q->keysResolved(true, false); } KeyResolver *const q; QString mSender; QStringList mRecipients; - QMap > mSigKeys; - QMap > >mEncKeys; - QMap > mOverrides; + QMap > mSigKeys; + QMap > >mEncKeys; + QMap > mOverrides; QStringList mUnresolvedPGP, mUnresolvedCMS; - CryptoMessageFormat mFormat; + Protocol mFormat; QStringList mFatalErrors; bool mEncrypt, mSign; bool mAllowMixed; // The cache is needed as a member variable to avoid rebuilding // it between calls if we are the only user. std::shared_ptr mCache; std::shared_ptr mDialog; Qt::WindowFlags mDialogWindowFlags; - GpgME::Protocol mPreferredProtocol; + 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); + if (d->mFormat != CMS) { + d->resolveSign(OpenPGP); + d->resolveEnc(OpenPGP); } - bool pgpOnly = d->mUnresolvedPGP.empty() && (!d->mSign || d->mSigKeys.contains(AnyOpenPGP)); + bool pgpOnly = d->mUnresolvedPGP.empty() && (!d->mSign || d->mSigKeys.contains(OpenPGP)); - if (d->mFormat & AnySMIME) { - d->resolveSign(GpgME::CMS); - d->resolveEnc(GpgME::CMS); + if (d->mFormat != OpenPGP) { + d->resolveSign(CMS); + d->resolveEnc(CMS); } - bool cmsOnly = d->mUnresolvedCMS.empty() && (!d->mSign || d->mSigKeys.contains(AnySMIME)); + bool cmsOnly = d->mUnresolvedCMS.empty() && (!d->mSign || d->mSigKeys.contains(CMS)); // Check if we need the user to select different keys. bool needsUser = false; if (!pgpOnly && !cmsOnly) { for (const auto &unresolved: d->mUnresolvedPGP) { if (d->mUnresolvedCMS.contains(unresolved)) { // We have at least one unresolvable key. needsUser = true; break; } } if (d->mSign) { // So every recipient could be resolved through // a combination of PGP and S/MIME do we also // have signing keys for both? - needsUser |= !(d->mSigKeys.contains(AnyOpenPGP) && - d->mSigKeys.contains(AnySMIME)); + needsUser |= !(d->mSigKeys.contains(OpenPGP) && + d->mSigKeys.contains(CMS)); } } if (!needsUser && !showApproval) { if (pgpOnly) { - d->mSigKeys.remove(AnySMIME); - d->mEncKeys.remove(AnySMIME); + d->mSigKeys.remove(CMS); + d->mEncKeys.remove(CMS); } if (cmsOnly) { - d->mSigKeys.remove(AnyOpenPGP); - d->mEncKeys.remove(AnyOpenPGP); + d->mSigKeys.remove(OpenPGP); + d->mEncKeys.remove(OpenPGP); } - 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) +KeyResolver::KeyResolver(bool encrypt, bool sign, Protocol fmt, bool allowMixed) : d(new Private(this, encrypt, sign, fmt, allowMixed)) { } Kleo::KeyResolver::~KeyResolver() = default; void KeyResolver::setRecipients(const QStringList &addresses) { d->addRecpients(addresses); } void KeyResolver::setSender(const QString &address) { - const auto normalized = GpgME::UserID::addrSpecFromString (address.toUtf8().constData()); + const auto normalized = UserID::addrSpecFromString (address.toUtf8().constData()); if (normalized.empty()) { // should not happen bug in the caller, non localized // error for bug reporting. d->mFatalErrors << QStringLiteral("The sender address '%1' could not be extracted").arg(address); return; } const auto normStr = QString::fromUtf8(normalized.c_str()); if (d->mSign) { d->mSender = normStr; } if (d->mEncrypt) { if (!d->mUnresolvedCMS.contains(normStr)) { d->mUnresolvedCMS << normStr; } if (!d->mUnresolvedPGP.contains(normStr)) { d->mUnresolvedPGP << normStr; } } } -void KeyResolver::setOverrideKeys(const QMap > &overrides) +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()); + 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 +QMap > > KeyResolver::encryptionKeys() const { return d->mEncKeys; } -QMap > KeyResolver::signingKeys() const +QMap > KeyResolver::signingKeys() const { return d->mSigKeys; } -QMap > KeyResolver::overrideKeys() const +QMap > KeyResolver::overrideKeys() const { return d->mOverrides; } void KeyResolver::setDialogWindowFlags(Qt::WindowFlags flags) { d->mDialogWindowFlags = flags; } -void KeyResolver::setPreferredProtocol(GpgME::Protocol proto) +void KeyResolver::setPreferredProtocol(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 35de4f856..cd64eaccf 100644 --- a/src/kleo/keyresolver.h +++ b/src/kleo/keyresolver.h @@ -1,210 +1,210 @@ /* -*- 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 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 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 format: 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, - CryptoMessageFormat format = AutoFormat, + GpgME::Protocol format = GpgME::UnknownProtocol, bool allowMixed = true); ~KeyResolver() override; /** * Set the list of 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 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 \ -> (\ \) + * @param overrides: A map of \ -> (\ \) */ - void setOverrideKeys(const QMap > &overrides); + 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); /** * 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; + QMap > > encryptionKeys() const; /** * Get the signing keys to use after resolution. * * @return the resolved resolved sender / key pairs for signing * by format. */ - QMap > signingKeys() const; + 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; + 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; 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 b9ead2c4f..2727dac66 100644 --- a/src/tests/test_keyresolver.cpp +++ b/src/tests/test_keyresolver.cpp @@ -1,164 +1,159 @@ /* 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 "utils/formatting.h" + #include #include #include #include #include using namespace Kleo; +using namespace GpgME; -void dumpKeys(const QMap > > &fmtMap) +void dumpKeys(const QMap > > &fmtMap) { - for (const CryptoMessageFormat fmt: fmtMap.keys()) { - qDebug () << "Format:" << cryptoMessageFormatToLabel(fmt) << fmt; + for (const Protocol fmt: fmtMap.keys()) { + qDebug () << "Format:" << Formatting::displayName(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) +void dumpSigKeys(const QMap > &fmtMap) { - for (const CryptoMessageFormat fmt: fmtMap.keys()) { - qDebug () << "Format:" << cryptoMessageFormatToLabel(fmt) << fmt; + for (const Protocol fmt: fmtMap.keys()) { + qDebug () << "Format:" << Formatting::displayName(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() << "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("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; + QMap > overrides; for (const QString &oride: parser.values(QStringLiteral("overrides"))) { const QStringList split = oride.split(QLatin1Char(':')); - CryptoMessageFormat fmt = AutoFormat; + Protocol fmt = UnknownProtocol; 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; + if (fmtStr == QLatin1String("openpgp")) { + fmt = OpenPGP; } 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; + fmt = CMS; } else if (fmtStr == QLatin1String("auto")) { - fmt = AutoFormat; + fmt = UnknownProtocol; } 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"