Commit 2c3e7b4c authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Simplify/change result type of KeyResolver and its internal helpers

KeyResolver:
* Use a single method for returning the result
* Remove the by-protocol mappings from the signing/encryption key result;
  the protocols of the keys are known by the keys

KeyResolverCore:
* Return a preferred solution and, if applicable, an alternative solution
  instead of hard to interpret by-protocol mappings

NewKeyApprovalDialog:
* Use preferred and alternative solutions of KeyResolverCore instead of
  merged key mappings
* In single-protocol mode use separate widgets for OpenPGP and S/MIME keys;
  this way the widgets keep the currently selected keys, when the user
  switches between OpenPGP and S/MIME
* In mixed-protocol mode use separate widgets for OpenPGP and S/MIME for
  the sender's signing and encryption keys and multi-protocols widgets
  for the recipients' encryption keys

GnuPG-bug-id: 5283
parent b31b915a
This diff is collapsed.
This diff is collapsed.
......@@ -48,13 +48,12 @@ public:
~Private() = default;
void showApprovalDialog(QWidget *parent);
void showApprovalDialog(KeyResolverCore::Result result, QWidget *parent);
void dialogAccepted();
KeyResolver *const q;
KeyResolverCore mCore;
QMap<Protocol, std::vector<Key>> mSigKeys;
QMap<Protocol, QMap<QString, std::vector<Key>>> mEncKeys;
Solution mResult;
Protocol mFormat;
bool mEncrypt;
......@@ -63,156 +62,35 @@ public:
// The cache is needed as a member variable to avoid rebuilding
// it between calls if we are the only user.
std::shared_ptr<const KeyCache> mCache;
std::shared_ptr<NewKeyApprovalDialog> mDialog;
std::unique_ptr<NewKeyApprovalDialog> mDialog;
Qt::WindowFlags mDialogWindowFlags;
Protocol mPreferredProtocol;
};
void KeyResolver::Private::showApprovalDialog(QWidget *parent)
void KeyResolver::Private::showApprovalDialog(KeyResolverCore::Result result, QWidget *parent)
{
const QString sender = mCore.normalizedSender();
const QMap<GpgME::Protocol, std::vector<GpgME::Key>> signingKeys = mCore.signingKeys();
const QStringList unresolvedPGP = mCore.unresolvedRecipients(OpenPGP);
const QStringList unresolvedCMS = mCore.unresolvedRecipients(CMS);
QMap<QString, std::vector<Key> > resolvedSig;
QStringList unresolvedSig;
const bool pgpOnly = unresolvedPGP.empty() && (!mSign || signingKeys.contains(OpenPGP));
const bool cmsOnly = unresolvedCMS.empty() && (!mSign || signingKeys.contains(CMS));
// First handle the signing keys
if (mSign) {
if (signingKeys.empty()) {
unresolvedSig << sender;
} else {
std::vector<Key> resolvedSigKeys;
for (const auto &keys: signingKeys) {
for (const auto &key: keys) {
resolvedSigKeys.push_back(key);
}
}
resolvedSig.insert(sender, resolvedSigKeys);
}
}
// Now build the encryption keys
QMap<QString, std::vector<Key> > resolvedRecp;
QStringList unresolvedRecp;
if (mEncrypt) {
// Use all unresolved recipients.
if (!cmsOnly && !pgpOnly) {
if (mFormat == UnknownProtocol) {
// In Auto Format we can now remove recipients that could
// be resolved either through CMS or PGP
for (const auto &addr: qAsConst(unresolvedPGP)) {
if (unresolvedCMS.contains(addr)) {
unresolvedRecp << addr;
}
}
} else if (mFormat == OpenPGP) {
unresolvedRecp = unresolvedPGP;
} else if (mFormat == CMS) {
unresolvedRecp = unresolvedCMS;
}
}
// Now Map all resolved encryption keys regardless of the format.
const QMap<Protocol, QMap<QString, std::vector<Key>>> encryptionKeys = mCore.encryptionKeys();
for (const auto &map: encryptionKeys.values()) {
for (auto it = map.cbegin(); it != map.cend(); ++it) {
const QString &addr = it.key();
const auto &keys = it.value();
if (!resolvedRecp.contains(addr) || !resolvedRecp[addr].size()) {
resolvedRecp.insert(addr, keys);
} else {
std::vector<Key> merged = resolvedRecp[addr];
// Add without duplication
for (const auto &k: keys) {
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?
Protocol forcedProto = mFormat;
// Start with the protocol for which every keys could be found.
Protocol presetProtocol;
if (mPreferredProtocol == CMS && cmsOnly) {
presetProtocol = CMS;
} else {
presetProtocol = pgpOnly ? OpenPGP :
cmsOnly ? CMS :
mPreferredProtocol;
}
mDialog = std::shared_ptr<NewKeyApprovalDialog>(new NewKeyApprovalDialog(resolvedSig,
resolvedRecp,
unresolvedSig,
unresolvedRecp,
sender,
mAllowMixed,
forcedProto,
presetProtocol,
parent,
mDialogWindowFlags));
mDialog = std::make_unique<NewKeyApprovalDialog>(mEncrypt,
mSign,
sender,
std::move(result.solution),
std::move(result.alternative),
mAllowMixed,
mFormat,
parent,
mDialogWindowFlags);
connect (mDialog.get(), &QDialog::accepted, q, [this] () {
dialogAccepted();
});
connect (mDialog.get(), &QDialog::rejected, q, [this] () {
Q_EMIT q->keysResolved(false, false);}
);
Q_EMIT q->keysResolved(false, false);
});
mDialog->open();
}
void KeyResolver::Private::dialogAccepted()
{
for (const auto &key: mDialog->signingKeys()) {
if (!mSigKeys.contains(key.protocol())) {
mSigKeys.insert(key.protocol(), std::vector<Key>());
}
mSigKeys[key.protocol()].push_back(key);
}
const auto &encMap = mDialog->encryptionKeys();
// First we fill the protocol-specific maps with
// the results of the dialog. Then we use the sender
// address to determine if a keys in the specific
// maps need updating.
bool isUnresolved = false;
for (const auto &addr: encMap.keys()) {
for (const auto &key: encMap[addr]) {
if (key.isNull()) {
isUnresolved = true;
}
if (!mEncKeys.contains(key.protocol())) {
mEncKeys.insert(key.protocol(), QMap<QString, std::vector<Key> >());
}
if (!mEncKeys[key.protocol()].contains(addr)) {
mEncKeys[key.protocol()].insert(addr, std::vector<Key>());
}
qCDebug (LIBKLEO_LOG) << "Adding" << addr << "for" << Formatting::displayName(key.protocol())
<< "fpr:" << key.primaryFingerprint();
mEncKeys[key.protocol()][addr].push_back(key);
}
}
if (isUnresolved) {
// TODO show warning
}
mResult = mDialog->result();
Q_EMIT q->keysResolved(true, false);
}
......@@ -223,16 +101,24 @@ void KeyResolver::start(bool showApproval, QWidget *parentWidget)
// nothing to do
return Q_EMIT keysResolved(true, true);
}
const bool success = d->mCore.resolve();
const auto result = d->mCore.resolve();
const bool success = (result.flags & KeyResolverCore::AllResolved);
if (success && !showApproval) {
d->mResult = std::move(result.solution);
// update protocol hint of solution if solution is single-protocol solution
const auto protocolsHint = result.flags & KeyResolverCore::ProtocolsMask;
if (protocolsHint == KeyResolverCore::OpenPGPOnly) {
d->mResult.protocol = OpenPGP;
} else if (protocolsHint == KeyResolverCore::CMSOnly) {
d->mResult.protocol = CMS;
}
Q_EMIT keysResolved(true, false);
return;
} else if (success) {
qCDebug(LIBKLEO_LOG) << "No need for the user showing approval anyway.";
}
d->showApprovalDialog(parentWidget);
d->showApprovalDialog(std::move(result), parentWidget);
}
KeyResolver::KeyResolver(bool encrypt, bool sign, Protocol fmt, bool allowMixed)
......@@ -262,14 +148,9 @@ void KeyResolver::setSigningKeys(const QStringList &fingerprints)
d->mCore.setSigningKeys(fingerprints);
}
QMap <Protocol, QMap<QString, std::vector<Key> > > KeyResolver::encryptionKeys() const
{
return d->mCore.encryptionKeys();
}
QMap <Protocol, std::vector<Key> > KeyResolver::signingKeys() const
KeyResolver::Solution KeyResolver::result() const
{
return d->mCore.signingKeys();
return d->mResult;
}
void KeyResolver::setDialogWindowFlags(Qt::WindowFlags flags)
......
......@@ -86,6 +86,13 @@ class KLEO_EXPORT KeyResolver : public QObject
Q_OBJECT
public:
struct Solution
{
GpgME::Protocol protocol = GpgME::UnknownProtocol;
std::vector<GpgME::Key> signingKeys;
QMap<QString, std::vector<GpgME::Key>> encryptionKeys;
};
/** Creates a new key resolver object.
*
* @param encrypt: Should encryption keys be selected.
......@@ -142,19 +149,11 @@ public:
void setMinimumValidity(int validity);
/**
* Get the encryption keys after resolution.
*
* @return the resolved sender / key pairs for encryption by format.
*/
QMap <GpgME::Protocol, QMap<QString, std::vector<GpgME::Key> > > encryptionKeys() const;
/**
* Get the signing keys to use after resolution.
* Get the result of the resolution.
*
* @return the resolved resolved sender / key pairs for signing
* by format.
* @return the resolved keys for signing and encryption.
*/
QMap <GpgME::Protocol, std::vector<GpgME::Key> > signingKeys() const;
Solution result() const;
/**
* Starts the key resolving procedure. Emits keysResolved on success or
......
......@@ -109,7 +109,7 @@ public:
void resolveEnc(Protocol proto);
void mergeEncryptionKeys();
QStringList unresolvedRecipients(GpgME::Protocol protocol) const;
bool resolve();
Result resolve();
KeyResolverCore *const q;
QString mSender;
......@@ -394,38 +394,39 @@ void KeyResolverCore::Private::resolveEnc(Protocol proto)
}
}
void KeyResolverCore::Private::mergeEncryptionKeys()
auto getBestEncryptionKeys(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol preferredProtocol)
{
for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) {
QMap<QString, std::vector<Key>> result;
for (auto it = encryptionKeys.begin(); it != encryptionKeys.end(); ++it) {
const QString &address = it.key();
auto &protocolKeysMap = it.value();
if (!protocolKeysMap[UnknownProtocol].empty()) {
// override keys are set for address
const std::vector<Key> &overrideKeys = protocolKeysMap[UnknownProtocol];
if (!overrideKeys.empty()) {
result.insert(address, overrideKeys);
continue;
}
const std::vector<Key> &keysOpenPGP = protocolKeysMap.value(OpenPGP);
const std::vector<Key> &keysCMS = protocolKeysMap.value(CMS);
const std::vector<Key> &keysOpenPGP = protocolKeysMap[OpenPGP];
const std::vector<Key> &keysCMS = protocolKeysMap[CMS];
if (keysOpenPGP.empty() && keysCMS.empty()) {
continue;
result.insert(address, {});
} else if (!keysOpenPGP.empty() && keysCMS.empty()) {
protocolKeysMap[UnknownProtocol] = keysOpenPGP;
result.insert(address, keysOpenPGP);
} else if (keysOpenPGP.empty() && !keysCMS.empty()) {
protocolKeysMap[UnknownProtocol] = keysCMS;
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 ((validityPGP > validityCMS)
|| (validityPGP == validityCMS && mPreferredProtocol == OpenPGP)) {
protocolKeysMap[UnknownProtocol] = keysOpenPGP;
} else if ((validityCMS > validityPGP)
|| (validityCMS == validityPGP && mPreferredProtocol == CMS)) {
protocolKeysMap[UnknownProtocol] = keysCMS;
if ((validityCMS > validityPGP) || (validityCMS == validityPGP && preferredProtocol == CMS)) {
result.insert(address, keysCMS);
} else {
protocolKeysMap[UnknownProtocol] = keysOpenPGP;
result.insert(address, keysOpenPGP);
}
}
}
return result;
}
QStringList KeyResolverCore::Private::unresolvedRecipients(GpgME::Protocol protocol) const
......@@ -458,14 +459,34 @@ bool anyCommonOverrideHasKeyOfType(const QMap<QString, QMap<Protocol, std::vecto
return anyKeyHasProtocol(protocolKeysMap.value(UnknownProtocol), protocol);
});
}
auto keysForProtocol(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol protocol)
{
QMap<QString, std::vector<Key>> 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;
}
bool KeyResolverCore::Private::resolve()
template<typename T>
auto concatenate(std::vector<T> v1, const std::vector<T> &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 true;
return {AllResolved, {}, {}};
}
// First resolve through overrides
......@@ -479,76 +500,127 @@ bool KeyResolverCore::Private::resolve()
|| (!mAllowMixed && commonOverridesNeedOpenPGP && commonOverridesNeedCMS)) {
// invalid protocol requirements -> clear intermediate result and abort resolution
mEncKeys.clear();
return false;
return {Error, {}, {}};
}
// Then look for signing / encryption keys
if (mFormat != CMS) {
if (mFormat == OpenPGP || mFormat == UnknownProtocol) {
resolveSign(OpenPGP);
resolveEnc(OpenPGP);
}
const bool pgpOnly = !hasUnresolvedRecipients(mEncKeys, OpenPGP) && (!mSign || mSigKeys.contains(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 != OpenPGP) {
if (mFormat == CMS || mFormat == UnknownProtocol) {
resolveSign(CMS);
resolveEnc(CMS);
}
const bool cmsOnly = !hasUnresolvedRecipients(mEncKeys, CMS) && (!mSign || mSigKeys.contains(CMS));
const bool cmsOnly = (!mEncrypt || !hasUnresolvedRecipients(mEncKeys, CMS)) && (!mSign || mSigKeys.contains(CMS));
if (mAllowMixed && mFormat == UnknownProtocol) {
mergeEncryptionKeys();
if (mFormat == CMS) {
return {
SolutionFlags((cmsOnly ? AllResolved : SomeUnresolved) | CMSOnly),
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
{}
};
}
const bool hasUnresolvedMerged = mAllowMixed && mFormat == UnknownProtocol && hasUnresolvedRecipients(mEncKeys, UnknownProtocol);
// Check if we need the user to select different keys.
bool needsUser = false;
if (!pgpOnly && !cmsOnly) {
if (mAllowMixed && mFormat == UnknownProtocol) {
needsUser = hasUnresolvedMerged;
// 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 {
needsUser = (mFormat == UnknownProtocol)
|| (mFormat == OpenPGP && !pgpOnly)
|| (mFormat == CMS && !cmsOnly);
// keys marked as mixed (UnknownProtocol) as hint that OpenPGP keys are also allowed in key approval
return {
SolutionFlags(AllResolved | CMSOnly),
{UnknownProtocol, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
{}
};
}
if (mSign) {
// So every recipient could be resolved through
// a combination of PGP and S/MIME do we also
// have signing keys for both?
needsUser |= !(mSigKeys.contains(OpenPGP) &&
mSigKeys.contains(CMS));
}
if (pgpOnly) {
if (!mAllowMixed) {
return {
SolutionFlags(AllResolved | OpenPGPOnly),
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}
};
} else {
// keys marked as mixed (UnknownProtocol) as hint that S/MIME keys are also allowed in key approval
return {
SolutionFlags(AllResolved | OpenPGPOnly),
{UnknownProtocol, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
{}
};
}
}
if (!needsUser) {
if (pgpOnly && cmsOnly) {
if (mPreferredProtocol == CMS) {
mSigKeys.remove(OpenPGP);
for (auto &protocolKeysMap: mEncKeys) {
protocolKeysMap.remove(OpenPGP);
}
} else {
mSigKeys.remove(CMS);
for (auto &protocolKeysMap: mEncKeys) {
protocolKeysMap.remove(CMS);
}
}
} else if (pgpOnly) {
mSigKeys.remove(CMS);
for (auto &protocolKeysMap: mEncKeys) {
protocolKeysMap.remove(CMS);
}
} else if (cmsOnly) {
mSigKeys.remove(OpenPGP);
for (auto &protocolKeysMap: mEncKeys) {
protocolKeysMap.remove(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)}
};
}
}
qCDebug(LIBKLEO_LOG) << "Automatic key resolution done.";
return true;
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},
{}
};
}
return false;
const bool allKeysAreOpenPGP = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys),
[] (const auto &keys) { return allKeysHaveProtocol(keys, OpenPGP); });
if (allKeysAreOpenPGP) {
// keys marked as mixed (UnknownProtocol) as hint that S/MIME keys are also allowed in key approval
return {
SolutionFlags(SomeUnresolved | OpenPGPOnly),
{UnknownProtocol, 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) {
// keys marked as mixed (UnknownProtocol) as hint that S/MIME keys are also allowed in key approval
return {
SolutionFlags(SomeUnresolved | CMSOnly),
{UnknownProtocol, 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)
......@@ -598,36 +670,7 @@ void KeyResolverCore::setMinimumValidity(int validity)
d->mMinimumValidity = validity;
}
bool KeyResolverCore::resolve()
KeyResolverCore::Result KeyResolverCore::resolve()
{
return d->resolve();
}
QMap <Protocol, std::vector<Key> > KeyResolverCore::signingKeys() const
{
return d->mSigKeys;
}
QMap<Protocol, QMap<QString, std::vector<Key>>> KeyResolverCore::encryptionKeys() const
{
QMap<Protocol, QMap<QString, std::vector<Key>>> result;
for (auto addressIt = d->mEncKeys.cbegin(); addressIt != d->mEncKeys.cend(); ++addressIt) {
const QString &address = addressIt.key();
const auto &protocolKeysMap = addressIt.value();
for (auto protocolIt = protocolKeysMap.cbegin(); protocolIt != protocolKeysMap.cend(); ++protocolIt) {
const Protocol protocol = protocolIt.key();
const auto &keys = protocolIt.value();
if (!keys.empty()) {
result[protocol][address] = keys;
}
}
}
return result;
}
QStringList KeyResolverCore::unresolvedRecipients(GpgME::Protocol protocol) const
{
return d->unresolvedRecipients(protocol);
}
......@@ -11,6 +11,8 @@
#pragma once
#include <Libkleo/KeyResolver>
#include "kleo_export.h"