Commit 68fda099 authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Improve group support in key resolver

* Remove group support from findBestByMailBox().
* Add findGroup() for looking up suitable group for a key protocol
  and some usage.

GnuPG-bug-id: 5283
parent 1b9f9eb1
......@@ -10,6 +10,7 @@
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyGroup>
#include <Libkleo/KeyResolverCore>
#include <QObject>
......@@ -53,6 +54,21 @@ inline bool qCompare(GpgME::Protocol const &t1, GpgME::Protocol const &t2, const
}
}
namespace
{
KeyGroup createGroup(const QString &name,
const std::vector<Key> &keys = std::vector<Key>(),
KeyGroup::Source source = KeyGroup::ApplicationConfig,
const QString &configName = QString())
{
const KeyGroup::Id groupId = (source == KeyGroup::ApplicationConfig) ?
(configName.isEmpty() ? name : configName) :
name;
KeyGroup g(groupId, name, keys, source);
return g;
}
}
class KeyResolverCoreTest: public QObject
{
Q_OBJECT
......@@ -64,6 +80,8 @@ private Q_SLOTS:
// hold a reference to the key cache to avoid rebuilding while the test is running
mKeyCache = KeyCache::instance();
// make sure that the key cache has been populated
(void)mKeyCache->keys();
}
void cleanup()
......@@ -618,6 +636,26 @@ private Q_SLOTS:
QVERIFY(result.flags & KeyResolverCore::Error);
}
void test_groups__group_with_one_openpgp_key__mixed_mode()
{
const std::vector<KeyGroup> groups = {
createGroup("group@example.net", {
testKey("sender-openpgp@example.net", OpenPGP)
})
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false);
resolver.setRecipients({"group@example.net"});
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 1);
QCOMPARE(result.solution.encryptionKeys.value("group@example.net")[0].primaryFingerprint(),
testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint());
}
private:
Key testKey(const char *email, Protocol protocol = UnknownProtocol)
{
......
......@@ -16,6 +16,7 @@
#include "keyresolvercore.h"
#include "kleo/keygroup.h"
#include "models/keycache.h"
#include "utils/formatting.h"
......@@ -295,21 +296,16 @@ void KeyResolverCore::Private::resolveSign(Protocol proto)
// Explicitly set
return;
}
const auto keys = mCache->findBestByMailBox(mSender.toUtf8().constData(), proto, KeyUsage::Sign);
for (const auto &key: keys) {
if (key.isNull()) {
continue;
}
if (!isAcceptableSigningKey(key)) {
qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint()
<< "for" << mSender;
return;
}
const auto key = mCache->findBestByMailBox(mSender.toUtf8().constData(), proto, KeyUsage::Sign);
if (key.isNull()) {
qCDebug(LIBKLEO_LOG) << "Failed to find" << Formatting::displayName(proto) << "signing key for" << mSender;
return;
}
if (!keys.empty() && !keys[0].isNull()) {
mSigKeys.insert(proto, keys);
if (!isAcceptableSigningKey(key)) {
qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint() << "for" << mSender;
return;
}
mSigKeys.insert(proto, {key});
}
void KeyResolverCore::Private::setSigningKeys(const QStringList &fingerprints)
......@@ -330,18 +326,8 @@ void KeyResolverCore::Private::setSigningKeys(const QStringList &fingerprints)
std::vector<Key> KeyResolverCore::Private::resolveRecipient(const QString &address, Protocol protocol)
{
const auto keys = mCache->findBestByMailBox(address.toUtf8().constData(), protocol, KeyUsage::Encrypt);
if (keys.empty() || keys[0].isNull()) {
qCDebug(LIBKLEO_LOG) << "Failed to find any" << Formatting::displayName(protocol) << "key for: " << address;
return {};
}
if (keys.size() == 1) {
if (!isAcceptableEncryptionKey(keys[0], address)) {
qCDebug(LIBKLEO_LOG) << "key for:" << address << keys[0].primaryFingerprint()
<< "has not enough validity";
return {};
}
} else {
const auto group = mCache->findGroup(address, protocol, KeyUsage::Encrypt);
if (!group.isNull() && group.keys().size() > 0) {
// 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.
......@@ -349,23 +335,33 @@ std::vector<Key> KeyResolverCore::Private::resolveRecipient(const QString &addre
// 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:" << address << keys[0].primaryFingerprint()
<< "has not enough validity";
unacceptable = true;
break;
}
}
if (unacceptable) {
const auto &keys = group.keys();
const bool allKeysAreAcceptable =
std::all_of(std::begin(keys), std::end(keys), [this] (const auto &key) { return isAcceptableEncryptionKey(key); });
if (!allKeysAreAcceptable) {
qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has at least one unacceptable key";
return {};
}
for (const auto &k: keys) {
qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << k.primaryFingerprint();
}
std::vector<Key> result;
std::copy(std::begin(keys), std::end(keys), std::back_inserter(result));
return result;
}
for (const auto &k: keys) {
qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << k.primaryFingerprint();
const auto key = mCache->findBestByMailBox(address.toUtf8().constData(), protocol, KeyUsage::Encrypt);
if (key.isNull()) {
qCDebug(LIBKLEO_LOG) << "Failed to find any" << Formatting::displayName(protocol) << "key for:" << address;
return {};
}
return keys;
if (!isAcceptableEncryptionKey(key, address)) {
qCDebug(LIBKLEO_LOG) << "key for:" << address << key.primaryFingerprint()
<< "has not enough validity";
return {};
}
qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << key.primaryFingerprint();
return {key};
}
// Try to find matching keys in the provided protocol for the unresolved addresses
......
......@@ -1663,27 +1663,20 @@ struct BestMatch
};
}
std::vector<GpgME::Key> KeyCache::findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const
GpgME::Key KeyCache::findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const
{
d->ensureCachePopulated();
std::vector<GpgME::Key> ret;
if (!addr) {
return ret;
}
if (proto == Protocol::OpenPGP) {
// Groups take precedence over automatic keys. We return all here
// so if a group key for example is expired we can show that.
ret = getGroupKeys(QString::fromUtf8(addr));
if (!ret.empty()) {
return ret;
}
return {};
}
// support lookup of email addresses enclosed in angle brackets
QByteArray address(addr);
if (address[0] == '<' && address[address.size() - 1] == '>') {
address = address.mid(1, address.size() - 2);
}
address = address.toLower();
BestMatch best;
for (const Key &k: findByEMailAddress(address.constData())) {
if (proto != Protocol::UnknownProtocol && k.protocol() != proto) {
......@@ -1723,8 +1716,54 @@ std::vector<GpgME::Key> KeyCache::findBestByMailBox(const char *addr, GpgME::Pro
}
}
}
ret.push_back(best.key);
return ret;
return best.key;
}
namespace
{
template<typename T>
bool allKeysAllowUsage(const T &keys, KeyUsage usage)
{
switch(usage) {
case KeyUsage::AnyUsage:
return true;
case KeyUsage::Sign:
return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canSign));
case KeyUsage::Encrypt:
return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canEncrypt));
case KeyUsage::Certify:
return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canCertify));
case KeyUsage::Authenticate:
return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canAuthenticate));
}
qCDebug(LIBKLEO_LOG) << __func__ << "called with invalid usage" << int(usage);
return false;
}
template<typename T>
bool allKeysHaveProtocol(const T &keys, Protocol protocol)
{
return std::all_of(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; });
}
}
KeyGroup KeyCache::findGroup(const QString &name, Protocol protocol, KeyUsage usage) const
{
d->ensureCachePopulated();
Q_ASSERT(usage == KeyUsage::Sign || usage == KeyUsage::Encrypt);
for (const auto &group : qAsConst(d->m_groups)) {
if (group.name() == name) {
const KeyGroup::Keys &keys = group.keys();
if (allKeysAllowUsage(keys, usage)
&& (protocol == UnknownProtocol || allKeysHaveProtocol(keys, protocol))) {
return group;
}
}
}
return {};
}
std::vector<Key> KeyCache::getGroupKeys(const QString &groupName) const
......@@ -1752,6 +1791,12 @@ void KeyCache::setKeys(const std::vector<GpgME::Key>& keys)
Q_EMIT keyListingDone(KeyListResult());
}
void KeyCache::setGroups(const std::vector<KeyGroup>& groups)
{
Q_ASSERT(d->m_initalized && "Call setKeys() before setting groups");
d->m_groups = groups;
Q_EMIT keysMayHaveChanged();
}
#include "moc_keycache_p.cpp"
#include "moc_keycache.cpp"
......@@ -91,8 +91,7 @@ public:
std::vector<GpgME::Key> findByEMailAddress(const char *email) const;
std::vector<GpgME::Key> findByEMailAddress(const std::string &email) const;
/** Look through the cache and search for the best key for a mailbox taking
* groups into account.
/** Look through the cache and search for the best key for a mailbox.
*
* The best key is the key with a UID for the provided mailbox that
* has the highest validity and a subkey that is capable for the given
......@@ -100,12 +99,18 @@ public:
* If more then one key have a UID with the same validity
* the most recently created key is taken.
*
* The return value is a list because of groups. A group is
* a gnupg config entry that allows a user to configure multiple
* keys for a single mailbox. Only supported for OpenPGP currently.
* @returns the "best" key for the mailbox. */
GpgME::Key findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const;
/**
* Looks for a group named @a name which contains keys with protocol @a protocol
* that are suitable for the usage @a usage.
*
* The first group that fulfills all conditions is returned.
*
* @returns only the "best" key for the mailbox, or a list if it is a group. */
std::vector<GpgME::Key> findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const;
* @returns a matching group or a null group if no matching group is found.
*/
KeyGroup findGroup(const QString &name, GpgME::Protocol protocol, KeyUsage usage) const;
const GpgME::Key &findByShortKeyID(const char *id) const;
const GpgME::Key &findByShortKeyID(const std::string &id) const;
......@@ -155,6 +160,8 @@ public:
/** Set the keys the cache shall contain. Marks cache as initialized. Use for tests only. */
void setKeys(const std::vector<GpgME::Key> &keys);
void setGroups(const std::vector<KeyGroup> &groups);
public Q_SLOTS:
void clear();
void startKeyListing(GpgME::Protocol proto = GpgME::UnknownProtocol)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment