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

Take groups into account when resolving signing keys

GnuPG-bug-id: 5283
parent 50bea92e
Pipeline #60064 passed with stage
in 7 minutes and 7 seconds
......@@ -816,6 +816,308 @@ private Q_SLOTS:
QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 2);
}
void test_groups_for_signing_key__openpgp_only_mode__prefers_groups_over_keys()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-mixed@example.net", {
testKey("sender-openpgp@example.net", OpenPGP),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, OpenPGP);
resolver.setSender("sender-mixed@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
QCOMPARE(result.solution.protocol, OpenPGP);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint());
}
void test_groups_for_signing_key__openpgp_only_mode__prefers_single_protocol_groups()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-alias@example.net", {
testKey("sender-mixed@example.net", OpenPGP),
testKey("sender-mixed@example.net", CMS),
}),
createGroup("sender-alias@example.net", {
testKey("sender-openpgp@example.net", OpenPGP),
}),
createGroup("sender-alias@example.net", {
testKey("sender-smime@example.net", CMS),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, OpenPGP);
resolver.setSender("sender-alias@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
QCOMPARE(result.solution.protocol, OpenPGP);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint());
}
void test_groups_for_signing_key__openpgp_only_mode__takes_key_of_mixed_protocol_groups()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-alias@example.net", {
testKey("sender-mixed@example.net", OpenPGP),
testKey("sender-mixed@example.net", CMS),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, OpenPGP);
resolver.setSender("sender-alias@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
QCOMPARE(result.solution.protocol, OpenPGP);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint());
}
void test_groups_for_signing_key__smime_only_mode__prefers_groups_over_keys()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-mixed@example.net", {
testKey("sender-smime@example.net", CMS),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, CMS);
resolver.setSender("sender-mixed@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly);
QCOMPARE(result.solution.protocol, CMS);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-smime@example.net", CMS).primaryFingerprint());
}
void test_groups_for_signing_key__smime_only_mode__prefers_single_protocol_groups()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-alias@example.net", {
testKey("sender-mixed@example.net", OpenPGP),
testKey("sender-mixed@example.net", CMS),
}),
createGroup("sender-alias@example.net", {
testKey("sender-openpgp@example.net", OpenPGP),
}),
createGroup("sender-alias@example.net", {
testKey("sender-smime@example.net", CMS),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, CMS);
resolver.setSender("sender-alias@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly);
QCOMPARE(result.solution.protocol, CMS);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-smime@example.net", CMS).primaryFingerprint());
}
void test_groups_for_signing_key__smime_only_mode__takes_key_of_mixed_protocol_groups()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-alias@example.net", {
testKey("sender-mixed@example.net", OpenPGP),
testKey("sender-mixed@example.net", CMS),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, CMS);
resolver.setSender("sender-alias@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly);
QCOMPARE(result.solution.protocol, CMS);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-mixed@example.net", CMS).primaryFingerprint());
}
void test_groups_for_signing_key__single_protocol_mode__prefers_groups_over_keys()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-mixed@example.net", {
testKey("sender-openpgp@example.net", OpenPGP),
testKey("sender-smime@example.net", CMS),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true);
resolver.setAllowMixedProtocols(false);
resolver.setSender("sender-mixed@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
QCOMPARE(result.solution.protocol, OpenPGP);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint());
QCOMPARE(result.alternative.signingKeys.size(), 1);
QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(),
testKey("sender-smime@example.net", CMS).primaryFingerprint());
}
void test_groups_for_signing_key__single_protocol_mode__prefers_single_protocol_groups()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-alias@example.net", {
testKey("sender-mixed@example.net", OpenPGP),
testKey("sender-mixed@example.net", CMS),
}),
createGroup("sender-alias@example.net", {
testKey("sender-openpgp@example.net", OpenPGP),
}),
createGroup("sender-alias@example.net", {
testKey("sender-smime@example.net", CMS),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true);
resolver.setAllowMixedProtocols(false);
resolver.setSender("sender-alias@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
QCOMPARE(result.solution.protocol, OpenPGP);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint());
QCOMPARE(result.alternative.signingKeys.size(), 1);
QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(),
testKey("sender-smime@example.net", CMS).primaryFingerprint());
}
void test_groups_for_signing_key__mixed_mode__prefers_groups_over_keys()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-mixed@example.net", {
testKey("sender-openpgp@example.net", OpenPGP),
testKey("sender-smime@example.net", CMS),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true);
resolver.setSender("sender-mixed@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
QCOMPARE(result.solution.protocol, OpenPGP);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint());
}
void test_groups_for_signing_key__mixed_mode_with_smime_preferred__prefers_groups_over_keys()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-mixed@example.net", {
testKey("sender-openpgp@example.net", OpenPGP),
testKey("sender-smime@example.net", CMS),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true);
resolver.setPreferredProtocol(CMS);
resolver.setSender("sender-mixed@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly);
QCOMPARE(result.solution.protocol, CMS);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-smime@example.net", CMS).primaryFingerprint());
}
void test_groups_for_signing_key__mixed_mode__prefers_single_protocol_groups()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-alias@example.net", {
testKey("sender-mixed@example.net", OpenPGP),
testKey("sender-mixed@example.net", CMS),
}),
createGroup("sender-alias@example.net", {
testKey("sender-openpgp@example.net", OpenPGP),
}),
createGroup("sender-alias@example.net", {
testKey("sender-smime@example.net", CMS),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true);
resolver.setSender("sender-alias@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
QCOMPARE(result.solution.protocol, OpenPGP);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint());
}
void test_groups_for_signing_key__mixed_mode_with_smime_preferred__prefers_single_protocol_groups()
{
const std::vector<KeyGroup> groups = {
createGroup("sender-alias@example.net", {
testKey("sender-mixed@example.net", OpenPGP),
testKey("sender-mixed@example.net", CMS),
}),
createGroup("sender-alias@example.net", {
testKey("sender-openpgp@example.net", OpenPGP),
}),
createGroup("sender-alias@example.net", {
testKey("sender-smime@example.net", CMS),
}),
};
KeyCache::mutableInstance()->setGroups(groups);
KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true);
resolver.setPreferredProtocol(CMS);
resolver.setSender("sender-alias@example.net");
const auto result = resolver.resolve();
QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly);
QCOMPARE(result.solution.protocol, CMS);
QCOMPARE(result.solution.signingKeys.size(), 1);
QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
testKey("sender-smime@example.net", CMS).primaryFingerprint());
}
private:
Key testKey(const char *email, Protocol protocol = UnknownProtocol)
{
......
......@@ -29,6 +29,16 @@ using namespace GpgME;
namespace {
QDebug operator<<(QDebug debug, const GpgME::Key &key)
{
if (key.isNull()) {
debug << "Null";
} else {
debug << Formatting::summaryLine(key);
}
return debug.maybeSpace();
}
static inline bool ValidEncryptionKey(const Key &key)
{
if (key.isNull() || key.isRevoked() || key.isExpired() ||
......@@ -106,6 +116,8 @@ public:
void resolveOverrides();
std::vector<Key> resolveRecipientWithGroup(const QString &address, Protocol protocol);
void resolveEncryptionGroups();
std::vector<Key> resolveSenderWithGroup(const QString &address, Protocol protocol);
void resolveSigningGroups();
void resolveSign(Protocol proto);
void setSigningKeys(const QStringList &fingerprints);
std::vector<Key> resolveRecipient(const QString &address, Protocol protocol);
......@@ -292,9 +304,60 @@ void KeyResolverCore::Private::resolveOverrides()
}
}
std::vector<Key> KeyResolverCore::Private::resolveSenderWithGroup(const QString &address, Protocol protocol)
{
// prefer single-protocol groups over mixed-protocol groups
auto group = mCache->findGroup(address, protocol, KeyUsage::Sign);
if (group.isNull()) {
group = mCache->findGroup(address, UnknownProtocol, KeyUsage::Sign);
}
if (group.isNull()) {
return {};
}
// take the first key matching the protocol
const auto &keys = group.keys();
const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; });
if (it == std::end(keys)) {
qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has no" << Formatting::displayName(protocol) << "signing key";
return {};
}
const auto key = *it;
if (!isAcceptableSigningKey(key)) {
qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has unacceptable signing key" << key;
return {};
}
return {key};
}
void KeyResolverCore::Private::resolveSigningGroups()
{
auto &protocolKeysMap = mSigKeys;
if (!protocolKeysMap[UnknownProtocol].empty()) {
// already resolved by common override
return;
}
if (mFormat == OpenPGP) {
if (!protocolKeysMap[OpenPGP].empty()) {
// already resolved by override
return;
}
protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP);
} else if (mFormat == CMS) {
if (!protocolKeysMap[CMS].empty()) {
// already resolved by override
return;
}
protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS);
} else {
protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP);
protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS);
}
}
void KeyResolverCore::Private::resolveSign(Protocol proto)
{
if (mSigKeys.contains(proto)) {
if (!mSigKeys[proto].empty()) {
// Explicitly set
return;
}
......@@ -546,6 +609,9 @@ KeyResolverCore::Result KeyResolverCore::Private::resolve()
}
// Next look for matching groups of keys
if (mSign) {
resolveSigningGroups();
}
if (mEncrypt) {
resolveEncryptionGroups();
}
......
......@@ -106,6 +106,15 @@ public:
* Looks for a group named @a name which contains keys with protocol @a protocol
* that are suitable for the usage @a usage.
*
* If @a protocol is GpgME::OpenPGP or GpgME::CMS, then only groups consisting of keys
* matching this protocol are considered. Use @a protocol GpgME::UnknownProtocol to consider
* any groups regardless of the protocol including mixed-protocol groups.
*
* If @a usage is not KeyUsage::AnyUsage, then only groups consisting of keys supporting this usage
* are considered.
* The validity of keys and the presence of a private key (necessary for signing, certification, and
* authentication) is not taken into account.
*
* The first group that fulfills all conditions is returned.
*
* @returns a matching group or a null group if no matching group is found.
......
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