Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34212027
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
185 KB
Subscribers
None
View Options
diff --git a/autotests/keyresolvercoretest.cpp b/autotests/keyresolvercoretest.cpp
index fb5e4868..a92f4ed2 100644
--- a/autotests/keyresolvercoretest.cpp
+++ b/autotests/keyresolvercoretest.cpp
@@ -1,516 +1,620 @@
/*
autotests/keyresolvercoretest.cpp
This file is part of libkleopatra's test suite.
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <Libkleo/KeyCache>
#include <Libkleo/KeyResolverCore>
#include <QObject>
#include <QTest>
#include <gpgme++/key.h>
#include <memory>
using namespace Kleo;
using namespace GpgME;
namespace QTest
{
template <>
inline bool qCompare(GpgME::UserID::Validity const &t1, GpgME::UserID::Validity const &t2, const char *actual, const char *expected,
const char *file, int line)
{
return qCompare(int(t1), int(t2), actual, expected, file, line);
}
+
+template <>
+inline bool qCompare(int const &t1, KeyResolverCore::SolutionFlags const &t2, const char *actual, const char *expected,
+ const char *file, int line)
+{
+ return qCompare(int(t1), int(t2), actual, expected, file, line);
+}
}
class KeyResolverCoreTest: public QObject
{
Q_OBJECT
private Q_SLOTS:
void init()
{
mGnupgHome = QTest::qExtractTestData("/fixtures/keyresolvercoretest");
qputenv("GNUPGHOME", mGnupgHome->path().toLocal8Bit());
// hold a reference to the key cache to avoid rebuilding while the test is running
mKeyCache = KeyCache::instance();
}
void cleanup()
{
// verify that nobody else holds a reference to the key cache
QVERIFY(mKeyCache.use_count() == 1);
mKeyCache.reset();
mGnupgHome.reset();
}
void test_verify_test_keys()
{
{
const Key openpgp = testKey("sender-mixed@example.net", OpenPGP);
QVERIFY(openpgp.hasSecret() && openpgp.canEncrypt() && openpgp.canSign());
QCOMPARE(openpgp.userID(0).validity(), UserID::Ultimate);
const Key smime = testKey("sender-mixed@example.net", CMS);
QVERIFY(smime.hasSecret() && smime.canEncrypt() && smime.canSign());
QCOMPARE(smime.userID(0).validity(), UserID::Full);
}
{
const Key openpgp = testKey("sender-openpgp@example.net", OpenPGP);
QVERIFY(openpgp.hasSecret() && openpgp.canEncrypt() && openpgp.canSign());
QCOMPARE(openpgp.userID(0).validity(), UserID::Ultimate);
}
{
const Key smime = testKey("sender-smime@example.net", CMS);
QVERIFY(smime.hasSecret() && smime.canEncrypt() && smime.canSign());
QCOMPARE(smime.userID(0).validity(), UserID::Full);
}
{
const Key openpgp = testKey("prefer-openpgp@example.net", OpenPGP);
QVERIFY(openpgp.canEncrypt());
QCOMPARE(openpgp.userID(0).validity(), UserID::Ultimate);
const Key smime = testKey("prefer-openpgp@example.net", CMS);
QVERIFY(smime.canEncrypt());
QCOMPARE(smime.userID(0).validity(), UserID::Full);
}
{
const Key openpgp = testKey("full-validity@example.net", OpenPGP);
QVERIFY(openpgp.canEncrypt());
QCOMPARE(openpgp.userID(0).validity(), UserID::Full);
const Key smime = testKey("full-validity@example.net", CMS);
QVERIFY(smime.canEncrypt());
QCOMPARE(smime.userID(0).validity(), UserID::Full);
}
{
const Key openpgp = testKey("prefer-smime@example.net", OpenPGP);
QVERIFY(openpgp.canEncrypt());
QCOMPARE(openpgp.userID(0).validity(), UserID::Marginal);
const Key smime = testKey("prefer-smime@example.net", CMS);
QVERIFY(smime.canEncrypt());
QCOMPARE(smime.userID(0).validity(), UserID::Full);
}
}
void test_openpgp_is_used_if_openpgp_only_and_smime_only_are_both_possible()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
+ resolver.setAllowMixedProtocols(false);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.signingKeys().value(OpenPGP).size(), 1);
- QCOMPARE(resolver.signingKeys().value(OpenPGP)[0].primaryFingerprint(),
+ 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());
- QCOMPARE(resolver.signingKeys().value(CMS).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-mixed@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-mixed@example.net")[0].primaryFingerprint(),
+ QCOMPARE(result.solution.encryptionKeys.size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(),
testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint());
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0);
+ QCOMPARE(result.alternative.protocol, CMS);
+ QCOMPARE(result.alternative.signingKeys.size(), 1);
+ QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", CMS).primaryFingerprint());
+ QCOMPARE(result.alternative.encryptionKeys.size(), 1);
+ QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net").size(), 1);
+ QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", CMS).primaryFingerprint());
}
void test_openpgp_is_used_if_openpgp_only_and_smime_only_are_both_possible_with_preference_for_openpgp()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
+ resolver.setAllowMixedProtocols(false);
resolver.setPreferredProtocol(OpenPGP);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.signingKeys().value(OpenPGP).size(), 1);
- QCOMPARE(resolver.signingKeys().value(OpenPGP)[0].primaryFingerprint(),
+ 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());
- QCOMPARE(resolver.signingKeys().value(CMS).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-mixed@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-mixed@example.net")[0].primaryFingerprint(),
+ QCOMPARE(result.solution.encryptionKeys.size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(),
testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint());
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0);
+ QCOMPARE(result.alternative.protocol, CMS);
+ QCOMPARE(result.alternative.signingKeys.size(), 1);
+ QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", CMS).primaryFingerprint());
+ QCOMPARE(result.alternative.encryptionKeys.size(), 1);
+ QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net").size(), 1);
+ QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", CMS).primaryFingerprint());
}
void test_smime_is_used_if_openpgp_only_and_smime_only_are_both_possible_with_preference_for_smime()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
+ resolver.setAllowMixedProtocols(false);
resolver.setPreferredProtocol(CMS);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.signingKeys().value(OpenPGP).size(), 0);
- QCOMPARE(resolver.signingKeys().value(CMS).size(), 1);
- QCOMPARE(resolver.signingKeys().value(CMS)[0].primaryFingerprint(),
+ 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());
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-mixed@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-mixed@example.net")[0].primaryFingerprint(),
+ QCOMPARE(result.solution.encryptionKeys.size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(),
testKey("sender-mixed@example.net", CMS).primaryFingerprint());
+ QCOMPARE(result.alternative.protocol, OpenPGP);
+ QCOMPARE(result.alternative.signingKeys.size(), 1);
+ QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint());
+ QCOMPARE(result.alternative.encryptionKeys.size(), 1);
+ QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net").size(), 1);
+ QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint());
}
- void test_in_mixed_mode_keys_with_higher_validity_are_preferred()
+ void test_in_mixed_mode_openpgp_is_used_if_openpgp_only_and_smime_only_are_both_possible()
+ {
+ KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
+ resolver.setSender(QStringLiteral("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, UnknownProtocol);
+ QCOMPARE(result.solution.signingKeys.size(), 1);
+ QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint());
+ QCOMPARE(result.solution.encryptionKeys.size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint());
+ // no alternative solution is proposed
+ QCOMPARE(result.alternative.protocol, UnknownProtocol);
+ QCOMPARE(result.alternative.encryptionKeys.size(), 0);
+ }
+
+ void test_in_mixed_mode_openpgp_is_used_if_openpgp_only_and_smime_only_are_both_possible_with_preference_for_openpgp()
+ {
+ KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
+ resolver.setPreferredProtocol(OpenPGP);
+ resolver.setSender(QStringLiteral("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, UnknownProtocol);
+ QCOMPARE(result.solution.signingKeys.size(), 1);
+ QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint());
+ QCOMPARE(result.solution.encryptionKeys.size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint());
+ // no alternative solution is proposed
+ QCOMPARE(result.alternative.protocol, UnknownProtocol);
+ QCOMPARE(result.alternative.encryptionKeys.size(), 0);
+ }
+
+ void test_in_mixed_mode_smime_is_used_if_openpgp_only_and_smime_only_are_both_possible_with_preference_for_smime()
+ {
+ KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
+ resolver.setPreferredProtocol(CMS);
+ resolver.setSender(QStringLiteral("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, UnknownProtocol);
+ QCOMPARE(result.solution.signingKeys.size(), 1);
+ QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", CMS).primaryFingerprint());
+ QCOMPARE(result.solution.encryptionKeys.size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(),
+ testKey("sender-mixed@example.net", CMS).primaryFingerprint());
+ // no alternative solution is proposed
+ QCOMPARE(result.alternative.protocol, UnknownProtocol);
+ QCOMPARE(result.alternative.encryptionKeys.size(), 0);
+ }
+
+ void test_in_mixed_mode_keys_with_higher_validity_are_preferred_if_both_protocols_are_needed()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false);
resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net", "prefer-openpgp@example.net", "prefer-smime@example.net"});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 4);
- QVERIFY(resolver.encryptionKeys().value(UnknownProtocol).contains("sender-openpgp@example.net"));
- QVERIFY(resolver.encryptionKeys().value(UnknownProtocol).contains("sender-smime@example.net"));
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("prefer-openpgp@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("prefer-openpgp@example.net")[0].primaryFingerprint(),
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols);
+ QCOMPARE(result.solution.protocol, UnknownProtocol);
+ QCOMPARE(result.solution.encryptionKeys.size(), 4);
+ QVERIFY(result.solution.encryptionKeys.contains("sender-openpgp@example.net"));
+ QVERIFY(result.solution.encryptionKeys.contains("sender-smime@example.net"));
+ QCOMPARE(result.solution.encryptionKeys.value("prefer-openpgp@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("prefer-openpgp@example.net")[0].primaryFingerprint(),
testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint());
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("prefer-smime@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("prefer-smime@example.net")[0].primaryFingerprint(),
+ QCOMPARE(result.solution.encryptionKeys.value("prefer-smime@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("prefer-smime@example.net")[0].primaryFingerprint(),
testKey("prefer-smime@example.net", CMS).primaryFingerprint());
+ // no alternative solution is proposed
+ QCOMPARE(result.alternative.protocol, UnknownProtocol);
+ QCOMPARE(result.alternative.encryptionKeys.size(), 0);
}
- void test_reports_failure_if_both_protocols_are_allowed_but_no_keys_are_found_for_an_address()
+ void test_reports_unresolved_addresses_if_both_protocols_are_allowed_but_no_keys_are_found_for_an_address()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false);
resolver.setRecipients({"unknown@example.net"});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(!success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0);
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
+ QCOMPARE(result.solution.encryptionKeys.value("unknown@example.net").size(), 0);
}
- void test_reports_failure_if_openpgp_is_requested_and_no_openpgp_keys_are_found_for_an_adddress()
+ void test_reports_unresolved_addresses_if_openpgp_is_requested_and_no_openpgp_keys_are_found_for_an_address()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false, OpenPGP);
resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net"});
- const bool success = resolver.resolve();
- QVERIFY(!success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 1);
- QVERIFY(resolver.encryptionKeys().value(OpenPGP).contains("sender-openpgp@example.net"));
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0);
+ const auto result = resolver.resolve();
+
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
+ QCOMPARE(result.solution.encryptionKeys.size(), 2);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 0);
+ QCOMPARE(result.alternative.encryptionKeys.size(), 0);
}
- void test_reports_failure_if_smime_is_requested_and_no_smime_keys_are_found_for_an_adddress()
+ void test_reports_unresolved_addresses_if_smime_is_requested_and_no_smime_keys_are_found_for_an_address()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false, CMS);
resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net"});
- const bool success = resolver.resolve();
- QVERIFY(!success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 1);
- QVERIFY(resolver.encryptionKeys().value(CMS).contains("sender-smime@example.net"));
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0);
+ const auto result = resolver.resolve();
+
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly);
+ QCOMPARE(result.solution.encryptionKeys.size(), 2);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 0);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 1);
+ QCOMPARE(result.alternative.encryptionKeys.size(), 0);
}
- void test_reports_failure_if_mixed_protocols_are_not_allowed_but_needed()
+ void test_reports_unresolved_addresses_if_mixed_protocols_are_not_allowed_but_needed()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false);
resolver.setAllowMixedProtocols(false);
resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net"});
- const bool success = resolver.resolve();
- QVERIFY(!success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 1);
- QVERIFY(resolver.encryptionKeys().value(OpenPGP).contains("sender-openpgp@example.net"));
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 1);
- QVERIFY(resolver.encryptionKeys().value(CMS).contains("sender-smime@example.net"));
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0);
+ const auto result = resolver.resolve();
+
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
+ QCOMPARE(result.solution.encryptionKeys.size(), 2);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 0);
+ QCOMPARE(result.alternative.encryptionKeys.size(), 2);
+ QCOMPARE(result.alternative.encryptionKeys.value("sender-openpgp@example.net").size(), 0);
+ QCOMPARE(result.alternative.encryptionKeys.value("sender-smime@example.net").size(), 1);
}
void test_openpgp_overrides_are_used_if_both_protocols_are_allowed()
{
const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint();
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
+ resolver.setAllowMixedProtocols(false);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
resolver.setRecipients({"full-validity@example.net"});
resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized <full-validity@example.net>"), {override}}}}});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net")[0].primaryFingerprint(), override);
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("full-validity@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("full-validity@example.net")[0].primaryFingerprint(), override);
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), override);
+ QCOMPARE(result.alternative.encryptionKeys.value("full-validity@example.net").size(), 1);
+ QCOMPARE(result.alternative.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(),
+ testKey("full-validity@example.net", CMS).primaryFingerprint());
}
void test_openpgp_overrides_are_used_if_openpgp_only_is_requested()
{
const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint();
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true, OpenPGP);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
resolver.setRecipients({"full-validity@example.net"});
resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized <full-validity@example.net>"), {override}}}}});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net")[0].primaryFingerprint(), override);
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0);
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), override);
+ QCOMPARE(result.alternative.encryptionKeys.size(), 0);
}
void test_openpgp_overrides_are_ignored_if_smime_only_is_requested()
{
const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint();
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true, CMS);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
resolver.setRecipients({"full-validity@example.net"});
resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized <full-validity@example.net>"), {override}}}}});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net")[0].primaryFingerprint(),
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(),
testKey("full-validity@example.net", CMS).primaryFingerprint());
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0);
+ QCOMPARE(result.alternative.encryptionKeys.size(), 0);
}
- void test_smime_overrides_are_used_if_both_protocols_are_allowed()
+ void test_smime_overrides_are_used_if_both_protocols_are_allowed_and_smime_is_preferred()
{
const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint();
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
+ resolver.setAllowMixedProtocols(false);
resolver.setPreferredProtocol(CMS);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
resolver.setRecipients({"full-validity@example.net"});
resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized <full-validity@example.net>"), {override}}}}});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net")[0].primaryFingerprint(), override);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("full-validity@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("full-validity@example.net")[0].primaryFingerprint(), override);
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), override);
+ QCOMPARE(result.alternative.encryptionKeys.value("full-validity@example.net").size(), 1);
+ QCOMPARE(result.alternative.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(),
+ testKey("full-validity@example.net", OpenPGP).primaryFingerprint());
}
void test_smime_overrides_are_used_if_smime_only_is_requested()
{
const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint();
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true, CMS);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
resolver.setRecipients({"full-validity@example.net"});
resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized <full-validity@example.net>"), {override}}}}});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net")[0].primaryFingerprint(), override);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0);
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), override);
+ QCOMPARE(result.alternative.encryptionKeys.size(), 0);
}
void test_smime_overrides_are_ignored_if_openpgp_only_is_requested()
{
const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint();
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true, OpenPGP);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
resolver.setRecipients({"full-validity@example.net"});
resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized <full-validity@example.net>"), {override}}}}});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net")[0].primaryFingerprint(),
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(),
testKey("full-validity@example.net", OpenPGP).primaryFingerprint());
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0);
+ QCOMPARE(result.alternative.encryptionKeys.size(), 0);
}
void test_overrides_for_wrong_protocol_are_ignored()
{
const QString override1 = testKey("full-validity@example.net", CMS).primaryFingerprint();
const QString override2 = testKey("full-validity@example.net", OpenPGP).primaryFingerprint();
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net"});
resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized <sender-openpgp@example.net>"), {override1}}}}});
resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized <sender-smime@example.net>"), {override2}}}}});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-openpgp@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-openpgp@example.net")[0].primaryFingerprint(),
- testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint());
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-smime@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-smime@example.net")[0].primaryFingerprint(),
- testKey("sender-smime@example.net", CMS).primaryFingerprint());
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-openpgp@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-openpgp@example.net")[0].primaryFingerprint(),
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net")[0].primaryFingerprint(),
testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint());
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-smime@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-smime@example.net")[0].primaryFingerprint(),
+ QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net")[0].primaryFingerprint(),
testKey("sender-smime@example.net", CMS).primaryFingerprint());
}
void test_openpgp_only_common_overrides_are_used_for_openpgp()
{
const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint();
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
resolver.setRecipients({"sender-openpgp@example.net"});
resolver.setOverrideKeys({{UnknownProtocol, {{QStringLiteral("Needs to be normalized <sender-openpgp@example.net>"), {override}}}}});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-openpgp@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-openpgp@example.net")[0].primaryFingerprint(), override);
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-openpgp@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-openpgp@example.net")[0].primaryFingerprint(), override);
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net")[0].primaryFingerprint(), override);
}
void test_smime_only_common_overrides_are_used_for_smime()
{
const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint();
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
resolver.setRecipients({"sender-smime@example.net"});
resolver.setOverrideKeys({{UnknownProtocol, {{QStringLiteral("Needs to be normalized <sender-smime@example.net>"), {override}}}}});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-smime@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-smime@example.net")[0].primaryFingerprint(), override);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-smime@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-smime@example.net")[0].primaryFingerprint(), override);
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net")[0].primaryFingerprint(), override);
}
void test_mixed_protocol_common_overrides_override_protocol_specific_resolution()
{
const QString override1 = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint();
const QString override2 = testKey("prefer-smime@example.net", CMS).primaryFingerprint();
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
resolver.setOverrideKeys({{UnknownProtocol, {{QStringLiteral("sender-mixed@example.net"), {override1, override2}}}}});
- const bool success = resolver.resolve();
+ const auto result = resolver.resolve();
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-mixed@example.net").size(), 2);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-mixed@example.net")[0].primaryFingerprint(), override1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-mixed@example.net")[1].primaryFingerprint(), override2);
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 2);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), override1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[1].primaryFingerprint(), override2);
}
void test_common_overrides_override_protocol_specific_overrides()
{
const QString override1 = testKey("full-validity@example.net", OpenPGP).primaryFingerprint();
const QString override2 = testKey("full-validity@example.net", CMS).primaryFingerprint();
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true);
resolver.setSender(QStringLiteral("sender-mixed@example.net"));
resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net"});
resolver.setOverrideKeys({
{OpenPGP, {
{QStringLiteral("sender-openpgp@example.net"), {testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint()}}
}},
{CMS, {
{QStringLiteral("sender-smime@example.net"), {testKey("prefer-smime@example.net", CMS).primaryFingerprint()}}
}},
{UnknownProtocol, {
{QStringLiteral("sender-openpgp@example.net"), {override1}},
{QStringLiteral("sender-smime@example.net"), {override2}}
}}
});
- const bool success = resolver.resolve();
-
- QVERIFY(success);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-openpgp@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-openpgp@example.net")[0].primaryFingerprint(), override1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-openpgp@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-openpgp@example.net")[0].primaryFingerprint(), override1);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-smime@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-smime@example.net")[0].primaryFingerprint(), override2);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-smime@example.net").size(), 1);
- QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-smime@example.net")[0].primaryFingerprint(), override2);
+ const auto result = resolver.resolve();
+
+ QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved);
+ QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net")[0].primaryFingerprint(), override1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 1);
+ QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net")[0].primaryFingerprint(), override2);
}
void test_reports_failure_if_openpgp_is_requested_but_common_overrides_require_smime()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false, OpenPGP);
resolver.setRecipients({"sender-mixed@example.net"});
resolver.setOverrideKeys({{UnknownProtocol, {
{QStringLiteral("sender-mixed@example.net"), {testKey("prefer-smime@example.net", CMS).primaryFingerprint()}}
}}});
- const bool success = resolver.resolve();
- QVERIFY(!success);
- QVERIFY(resolver.encryptionKeys().empty());
+ const auto result = resolver.resolve();
+
+ QVERIFY(result.flags & KeyResolverCore::Error);
}
void test_reports_failure_if_smime_is_requested_but_common_overrides_require_openpgp()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false, CMS);
resolver.setRecipients({"sender-mixed@example.net"});
resolver.setOverrideKeys({{UnknownProtocol, {
{QStringLiteral("sender-mixed@example.net"), {testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint()}}
}}});
- const bool success = resolver.resolve();
- QVERIFY(!success);
- QVERIFY(resolver.encryptionKeys().empty());
+ const auto result = resolver.resolve();
+
+ QVERIFY(result.flags & KeyResolverCore::Error);
}
void test_reports_failure_if_mixed_protocols_are_not_allowed_but_required_by_common_overrides()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false);
resolver.setAllowMixedProtocols(false);
resolver.setRecipients({"sender-mixed@example.net"});
resolver.setOverrideKeys({{UnknownProtocol, {
{QStringLiteral("sender-mixed@example.net"), {
testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(),
testKey("prefer-smime@example.net", CMS).primaryFingerprint()
}}
}}});
- const bool success = resolver.resolve();
- QVERIFY(!success);
- QVERIFY(resolver.encryptionKeys().empty());
+ const auto result = resolver.resolve();
+
+ QVERIFY(result.flags & KeyResolverCore::Error);
}
private:
Key testKey(const char *email, Protocol protocol = UnknownProtocol)
{
const std::vector<Key> keys = KeyCache::instance()->findByEMailAddress(email);
for (const auto &key: keys) {
if (protocol == UnknownProtocol || key.protocol() == protocol) {
return key;
}
}
return Key();
}
private:
QSharedPointer<QTemporaryDir> mGnupgHome;
std::shared_ptr<const KeyCache> mKeyCache;
};
QTEST_MAIN(KeyResolverCoreTest)
#include "keyresolvercoretest.moc"
diff --git a/autotests/newkeyapprovaldialogtest.cpp b/autotests/newkeyapprovaldialogtest.cpp
index c4a13c89..8b8f25f4 100644
--- a/autotests/newkeyapprovaldialogtest.cpp
+++ b/autotests/newkeyapprovaldialogtest.cpp
@@ -1,225 +1,466 @@
/*
autotests/newkeyapprovaldialogtest.cpp
This file is part of libkleopatra's test suite.
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <Libkleo/KeySelectionCombo>
#include <Libkleo/NewKeyApprovalDialog>
#include <QObject>
#include <QTest>
#include <gpgme++/key.h>
#include <gpgme.h>
#include <memory>
#include <set>
using namespace Kleo;
namespace
{
GpgME::Key createTestKey(const char *uid, GpgME::Protocol protocol = GpgME::UnknownProtocol)
{
static int count = 0;
count++;
gpgme_key_t key;
gpgme_key_from_uid(&key, uid);
Q_ASSERT(key);
if (protocol != GpgME::UnknownProtocol) {
key->protocol = protocol == GpgME::OpenPGP ? GPGME_PROTOCOL_OpenPGP : GPGME_PROTOCOL_CMS;
}
const QByteArray fingerprint = QByteArray::number(count, 16).rightJustified(40, '0');
key->fpr = strdup(fingerprint.constData());
return GpgME::Key(key, false);
}
template <typename T>
-QList<T *> visibleWidgets(const QList<T *> &widgets)
+struct Widgets
{
- QList<T *> result;
- std::copy_if(widgets.begin(), widgets.end(),
- std::back_inserter(result),
- std::mem_fn(&QWidget::isVisible));
+ std::vector<T *> visible;
+ std::vector<T *> hidden;
+};
+
+template <typename T>
+Widgets<T> visibleAndHiddenWidgets(const QList<T *> &widgets)
+{
+ Widgets<T> result;
+ std::partition_copy(std::begin(widgets), std::end(widgets),
+ std::back_inserter(result.visible),
+ std::back_inserter(result.hidden),
+ std::mem_fn(&QWidget::isVisible));
return result;
}
}
class NewKeyApprovalDialogTest: public QObject
{
Q_OBJECT
private Q_SLOTS:
- void test_all_resolved_exclusive_prefer_OpenPGP()
+ void test__both_protocols_allowed__mixed_not_allowed__openpgp_preferred()
{
- const QStringList unresolvedSenders;
- const QStringList unresolvedRecipients;
+ const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
+ const bool allowMixed = false;
const QString sender = QStringLiteral("sender@example.net");
- bool allowMixed = false;
- GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
- GpgME::Protocol presetProtocol = GpgME::OpenPGP;
- const auto dialog = std::make_unique<NewKeyApprovalDialog>(resolved_senders_openpgp_and_smime(),
- resolved_recipients_openpgp_and_smime(),
- unresolvedSenders,
- unresolvedRecipients,
+ const KeyResolver::Solution preferredSolution = {
+ GpgME::OpenPGP,
+ {createTestKey("sender@example.net", GpgME::OpenPGP)},
+ {
+ {QStringLiteral("prefer-openpgp@example.net"), {createTestKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
+ {QStringLiteral("prefer-smime@example.net"), {}},
+ {QStringLiteral("sender@example.net"), {createTestKey("sender@example.net", GpgME::OpenPGP)}}
+ }
+ };
+ const KeyResolver::Solution alternativeSolution = {
+ GpgME::CMS,
+ {createTestKey("sender@example.net", GpgME::CMS)},
+ {
+ {QStringLiteral("prefer-openpgp@example.net"), {}},
+ {QStringLiteral("prefer-smime@example.net"), {createTestKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
+ {QStringLiteral("sender@example.net"), {createTestKey("sender@example.net", GpgME::CMS)}}
+ }
+ };
+
+ const auto dialog = std::make_unique<NewKeyApprovalDialog>(true,
+ true,
sender,
+ preferredSolution,
+ alternativeSolution,
allowMixed,
- forcedProtocol,
- presetProtocol);
+ forcedProtocol);
dialog->show();
- const QList<KeySelectionCombo *> signingKeyWidgets = dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key"));
- QCOMPARE(signingKeyWidgets.size(), 2);
- const auto visibleSigningKeyWidgets = visibleWidgets(signingKeyWidgets);
- QCOMPARE(visibleSigningKeyWidgets.size(), 1);
- for (auto combo: visibleSigningKeyWidgets) {
- QVERIFY(combo);
- QVERIFY2(!combo->defaultKey(GpgME::OpenPGP).isEmpty(), "visible signing key widget should default to OpenPGP key");
- }
- const QList<KeySelectionCombo *> encryptionKeyWidgets = dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key"));
- QCOMPARE(encryptionKeyWidgets.size(), 4);
- const auto visibleEncryptionKeyWidgets = visibleWidgets(encryptionKeyWidgets);
- QCOMPARE(visibleEncryptionKeyWidgets.size(), 3);
- QCOMPARE(visibleEncryptionKeyWidgets[0]->property("address").toString(), sender);
- QVERIFY2(!visibleEncryptionKeyWidgets[0]->defaultKey(GpgME::OpenPGP).isEmpty(),
- "encryption key widget for sender's OpenPGP key is first visible widget");
- for (auto combo: visibleEncryptionKeyWidgets) {
- QVERIFY(combo);
- QVERIFY2(combo->property("address").toString() != sender || !combo->defaultKey(GpgME::OpenPGP).isEmpty(),
- "encryption key widget for sender's CMS key should be hidden");
- }
+
+ const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
+ QCOMPARE(signingKeyWidgets.visible.size(), 1);
+ QCOMPARE(signingKeyWidgets.hidden.size(), 1);
+ QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP),
+ preferredSolution.signingKeys[0].primaryFingerprint());
+ QCOMPARE(signingKeyWidgets.hidden[0]->defaultKey(GpgME::CMS),
+ alternativeSolution.signingKeys[0].primaryFingerprint());
+
+ const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
+ QCOMPARE(encryptionKeyWidgets.visible.size(), 3);
+ QCOMPARE(encryptionKeyWidgets.hidden.size(), 3);
+
+ // encryption key widgets for sender come first (visible for OpenPGP, hidden for S/MIME)
+ QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
+ QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP),
+ preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());
+ QCOMPARE(encryptionKeyWidgets.hidden[0]->property("address").toString(), sender);
+ QCOMPARE(encryptionKeyWidgets.hidden[0]->defaultKey(GpgME::CMS),
+ alternativeSolution.encryptionKeys.value(sender)[0].primaryFingerprint());
+
+ // encryption key widgets for other recipients follow (visible for OpenPGP, hidden for S/MIME)
+ QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), "prefer-openpgp@example.net");
+ QCOMPARE(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::OpenPGP),
+ preferredSolution.encryptionKeys.value("prefer-openpgp@example.net")[0].primaryFingerprint());
+ QCOMPARE(encryptionKeyWidgets.hidden[1]->property("address").toString(), "prefer-openpgp@example.net");
+ QVERIFY(encryptionKeyWidgets.hidden[1]->defaultKey(GpgME::CMS).isEmpty());
+ QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-smime@example.net");
+ QVERIFY(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::OpenPGP).isEmpty());
+ QCOMPARE(encryptionKeyWidgets.hidden[2]->property("address").toString(), "prefer-smime@example.net");
+ QCOMPARE(encryptionKeyWidgets.hidden[2]->defaultKey(GpgME::CMS),
+ alternativeSolution.encryptionKeys.value("prefer-smime@example.net")[0].primaryFingerprint());
}
- void test_all_resolved_exclusive_prefer_SMIME()
+ void test__both_protocols_allowed__mixed_not_allowed__smime_preferred()
{
- const QStringList unresolvedSenders;
- const QStringList unresolvedRecipients;
+ const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
+ const bool allowMixed = false;
const QString sender = QStringLiteral("sender@example.net");
- bool allowMixed = false;
- GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
- GpgME::Protocol presetProtocol = GpgME::CMS;
- const auto dialog = std::make_unique<NewKeyApprovalDialog>(resolved_senders_openpgp_and_smime(),
- resolved_recipients_openpgp_and_smime(),
- unresolvedSenders,
- unresolvedRecipients,
+ const KeyResolver::Solution preferredSolution = {
+ GpgME::CMS,
+ {createTestKey("sender@example.net", GpgME::CMS)},
+ {
+ {QStringLiteral("prefer-openpgp@example.net"), {}},
+ {QStringLiteral("prefer-smime@example.net"), {createTestKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
+ {QStringLiteral("sender@example.net"), {createTestKey("sender@example.net", GpgME::CMS)}}
+ }
+ };
+ const KeyResolver::Solution alternativeSolution = {
+ GpgME::OpenPGP,
+ {createTestKey("sender@example.net", GpgME::OpenPGP)},
+ {
+ {QStringLiteral("prefer-openpgp@example.net"), {createTestKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
+ {QStringLiteral("prefer-smime@example.net"), {}},
+ {QStringLiteral("sender@example.net"), {createTestKey("sender@example.net", GpgME::OpenPGP)}}
+ }
+ };
+
+ const auto dialog = std::make_unique<NewKeyApprovalDialog>(true,
+ true,
sender,
+ preferredSolution,
+ alternativeSolution,
allowMixed,
- forcedProtocol,
- presetProtocol);
+ forcedProtocol);
dialog->show();
- const QList<KeySelectionCombo *> signingKeyWidgets = dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key"));
- QCOMPARE(signingKeyWidgets.size(), 2);
- const auto visibleSigningKeyWidgets = visibleWidgets(signingKeyWidgets);
- QCOMPARE(visibleSigningKeyWidgets.size(), 1);
- for (auto combo: visibleSigningKeyWidgets) {
- QVERIFY(combo);
- QVERIFY2(!combo->defaultKey(GpgME::CMS).isEmpty(), "visible signing key widget should default to S/MIME key");
- }
- const QList<KeySelectionCombo *> encryptionKeyWidgets = dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key"));
- QCOMPARE(encryptionKeyWidgets.size(), 4);
- const auto visibleEncryptionKeyWidgets = visibleWidgets(encryptionKeyWidgets);
- QCOMPARE(visibleEncryptionKeyWidgets.size(), 3);
- QCOMPARE(visibleEncryptionKeyWidgets[0]->property("address").toString(), sender);
- QVERIFY2(!visibleEncryptionKeyWidgets[0]->defaultKey(GpgME::CMS).isEmpty(),
- "encryption key widget for sender's CMS key is first visible widget");
- for (auto combo: visibleEncryptionKeyWidgets) {
- QVERIFY(combo);
- QVERIFY2(combo->property("address").toString() != sender || !combo->defaultKey(GpgME::CMS).isEmpty(),
- "encryption key widget for sender's OpenPGP key should be hidden");
- }
+
+ const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
+ QCOMPARE(signingKeyWidgets.visible.size(), 1);
+ QCOMPARE(signingKeyWidgets.hidden.size(), 1);
+ QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::CMS),
+ preferredSolution.signingKeys[0].primaryFingerprint());
+ QCOMPARE(signingKeyWidgets.hidden[0]->defaultKey(GpgME::OpenPGP),
+ alternativeSolution.signingKeys[0].primaryFingerprint());
+
+ const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
+ QCOMPARE(encryptionKeyWidgets.visible.size(), 3);
+ QCOMPARE(encryptionKeyWidgets.hidden.size(), 3);
+
+ // encryption key widgets for sender come first (visible for S/MIME, hidden for OpenPGP)
+ QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
+ QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::CMS),
+ preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());
+ QCOMPARE(encryptionKeyWidgets.hidden[0]->property("address").toString(), sender);
+ QCOMPARE(encryptionKeyWidgets.hidden[0]->defaultKey(GpgME::OpenPGP),
+ alternativeSolution.encryptionKeys.value(sender)[0].primaryFingerprint());
+
+ // encryption key widgets for other recipients follow (visible for OpenPGP, hidden for S/MIME)
+ QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), "prefer-openpgp@example.net");
+ QVERIFY(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::CMS).isEmpty());
+ QCOMPARE(encryptionKeyWidgets.hidden[1]->property("address").toString(), "prefer-openpgp@example.net");
+ QCOMPARE(encryptionKeyWidgets.hidden[1]->defaultKey(GpgME::OpenPGP),
+ alternativeSolution.encryptionKeys.value("prefer-openpgp@example.net")[0].primaryFingerprint());
+ QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-smime@example.net");
+ QCOMPARE(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::CMS),
+ preferredSolution.encryptionKeys.value("prefer-smime@example.net")[0].primaryFingerprint());
+ QCOMPARE(encryptionKeyWidgets.hidden[2]->property("address").toString(), "prefer-smime@example.net");
+ QVERIFY(encryptionKeyWidgets.hidden[2]->defaultKey(GpgME::OpenPGP).isEmpty());
}
- void test_all_resolved_allow_mixed_no_protocol_preference()
+ void test__openpgp_only()
{
- const QStringList unresolvedSenders;
- const QStringList unresolvedRecipients;
+ const GpgME::Protocol forcedProtocol = GpgME::OpenPGP;
+ const bool allowMixed = false;
const QString sender = QStringLiteral("sender@example.net");
- bool allowMixed = true;
- GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
- GpgME::Protocol presetProtocol = GpgME::UnknownProtocol;
- const auto resolvedSenders = resolved_senders_openpgp_and_smime();
- auto resolvedRecipients = resolved_recipients_openpgp_and_smime();
- resolvedRecipients["prefer-smime@example.net"].push_back(createTestKey("OpenPGP <prefer-smime@example.net>", GpgME::OpenPGP));
- resolvedRecipients.insert("smime-only@example.net", {createTestKey("S/MIME Only <smime-only@example.net>", GpgME::CMS)});
- const auto dialog = std::make_unique<NewKeyApprovalDialog>(resolvedSenders,
- resolvedRecipients,
- unresolvedSenders,
- unresolvedRecipients,
+ const KeyResolver::Solution preferredSolution = {
+ GpgME::OpenPGP,
+ {createTestKey("sender@example.net", GpgME::OpenPGP)},
+ {
+ {QStringLiteral("prefer-openpgp@example.net"), {createTestKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
+ {QStringLiteral("prefer-smime@example.net"), {}},
+ {QStringLiteral("sender@example.net"), {createTestKey("sender@example.net", GpgME::OpenPGP)}}
+ }
+ };
+ const KeyResolver::Solution alternativeSolution = {};
+
+ const auto dialog = std::make_unique<NewKeyApprovalDialog>(true,
+ true,
+ sender,
+ preferredSolution,
+ alternativeSolution,
+ allowMixed,
+ forcedProtocol);
+ dialog->show();
+
+ const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
+ QCOMPARE(signingKeyWidgets.visible.size(), 1);
+ QCOMPARE(signingKeyWidgets.hidden.size(), 0);
+ QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP),
+ preferredSolution.signingKeys[0].primaryFingerprint());
+
+ const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
+ QCOMPARE(encryptionKeyWidgets.visible.size(), 3);
+ QCOMPARE(encryptionKeyWidgets.hidden.size(), 0);
+
+ // encryption key widget for sender comes first
+ QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
+ QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP),
+ preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());
+
+ // encryption key widgets for other recipients follow
+ QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), "prefer-openpgp@example.net");
+ QCOMPARE(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::OpenPGP),
+ preferredSolution.encryptionKeys.value("prefer-openpgp@example.net")[0].primaryFingerprint());
+ QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-smime@example.net");
+ QVERIFY(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::OpenPGP).isEmpty());
+ }
+
+ void test__smime_only()
+ {
+ const GpgME::Protocol forcedProtocol = GpgME::CMS;
+ const bool allowMixed = false;
+ const QString sender = QStringLiteral("sender@example.net");
+ const KeyResolver::Solution preferredSolution = {
+ GpgME::CMS,
+ {createTestKey("sender@example.net", GpgME::CMS)},
+ {
+ {QStringLiteral("prefer-openpgp@example.net"), {}},
+ {QStringLiteral("prefer-smime@example.net"), {createTestKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
+ {QStringLiteral("sender@example.net"), {createTestKey("sender@example.net", GpgME::CMS)}}
+ }
+ };
+ const KeyResolver::Solution alternativeSolution = {};
+
+ const auto dialog = std::make_unique<NewKeyApprovalDialog>(true,
+ true,
sender,
+ preferredSolution,
+ alternativeSolution,
allowMixed,
- forcedProtocol,
- presetProtocol);
+ forcedProtocol);
dialog->show();
- const QList<KeySelectionCombo *> signingKeyWidgets = dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key"));
- QCOMPARE(signingKeyWidgets.size(), 2);
- for (auto widget : signingKeyWidgets) {
- QVERIFY2(widget->isVisible(), "signing key widget should be visible");
- }
- // first signing key widget should default to sender's OpenPGP key, the other to sender's S/MIME key
- QCOMPARE(signingKeyWidgets[0]->defaultKey(GpgME::OpenPGP),
- QString::fromLatin1(resolvedSenders["sender@example.net"][0].primaryFingerprint()));
- QCOMPARE(signingKeyWidgets[1]->defaultKey(GpgME::CMS),
- QString::fromLatin1(resolvedSenders["sender@example.net"][1].primaryFingerprint()));
-
- const QList<KeySelectionCombo *> encryptionKeyWidgets = dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key"));
- QCOMPARE(encryptionKeyWidgets.size(), 5);
- for (auto widget : encryptionKeyWidgets) {
- QVERIFY2(widget->isVisible(),
- qPrintable(QString("encryption key widget should be visible for address %1").arg(widget->property("address").toString())));
- }
- // first two encryption key widgets shall be widgets for sender's keys
- QCOMPARE(encryptionKeyWidgets[0]->property("address").toString(), sender);
- QCOMPARE(encryptionKeyWidgets[0]->defaultKey(GpgME::OpenPGP),
- QString::fromLatin1(resolvedRecipients["sender@example.net"][0].primaryFingerprint()));
- QCOMPARE(encryptionKeyWidgets[1]->property("address").toString(), sender);
- QCOMPARE(encryptionKeyWidgets[1]->defaultKey(GpgME::CMS),
- QString::fromLatin1(resolvedRecipients["sender@example.net"][1].primaryFingerprint()));
- // further encryption key widgets shall be widgets for keys of recipients, where OpenPGP keys are preferred due to no specific preset
- QCOMPARE(encryptionKeyWidgets[2]->property("address").toString(), QStringLiteral("prefer-openpgp@example.net"));
- QCOMPARE(encryptionKeyWidgets[2]->defaultKey(),
- QString::fromLatin1(resolvedRecipients["prefer-openpgp@example.net"][0].primaryFingerprint()));
- QCOMPARE(encryptionKeyWidgets[3]->property("address").toString(), QStringLiteral("prefer-smime@example.net"));
- QCOMPARE(encryptionKeyWidgets[3]->defaultKey(),
- QString::fromLatin1(resolvedRecipients["prefer-smime@example.net"][1].primaryFingerprint()));
- QCOMPARE(encryptionKeyWidgets[4]->property("address").toString(), QStringLiteral("smime-only@example.net"));
- QCOMPARE(encryptionKeyWidgets[4]->defaultKey(),
- QString::fromLatin1(resolvedRecipients["smime-only@example.net"][0].primaryFingerprint()));
+ const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
+ QCOMPARE(signingKeyWidgets.visible.size(), 1);
+ QCOMPARE(signingKeyWidgets.hidden.size(), 0);
+ QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::CMS),
+ preferredSolution.signingKeys[0].primaryFingerprint());
+
+ const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
+ QCOMPARE(encryptionKeyWidgets.visible.size(), 3);
+ QCOMPARE(encryptionKeyWidgets.hidden.size(), 0);
+
+ // encryption key widget for sender comes first
+ QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
+ QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::CMS),
+ preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());
+
+ // encryption key widgets for other recipients follow
+ QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), "prefer-openpgp@example.net");
+ QVERIFY(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::CMS).isEmpty());
+ QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-smime@example.net");
+ QCOMPARE(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::CMS),
+ preferredSolution.encryptionKeys.value("prefer-smime@example.net")[0].primaryFingerprint());
+ }
+
+ void test__both_protocols_allowed__mixed_allowed()
+ {
+ const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
+ const bool allowMixed = true;
+ const QString sender = QStringLiteral("sender@example.net");
+ const KeyResolver::Solution preferredSolution = {
+ GpgME::UnknownProtocol,
+ {createTestKey("sender@example.net", GpgME::OpenPGP), createTestKey("sender@example.net", GpgME::CMS)},
+ {
+ {QStringLiteral("prefer-openpgp@example.net"), {createTestKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
+ {QStringLiteral("prefer-smime@example.net"), {createTestKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
+ {QStringLiteral("unknown@example.net"), {}},
+ {QStringLiteral("sender@example.net"), {createTestKey("sender@example.net", GpgME::OpenPGP), createTestKey("sender@example.net", GpgME::CMS)}}
+ }
+ };
+ const KeyResolver::Solution alternativeSolution = {};
+
+ const auto dialog = std::make_unique<NewKeyApprovalDialog>(true,
+ true,
+ sender,
+ preferredSolution,
+ alternativeSolution,
+ allowMixed,
+ forcedProtocol);
+ dialog->show();
+
+ const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
+ QCOMPARE(signingKeyWidgets.visible.size(), 2);
+ QCOMPARE(signingKeyWidgets.hidden.size(), 0);
+ QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP),
+ preferredSolution.signingKeys[0].primaryFingerprint());
+ QCOMPARE(signingKeyWidgets.visible[1]->defaultKey(GpgME::CMS),
+ preferredSolution.signingKeys[1].primaryFingerprint());
+
+ const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
+ QCOMPARE(encryptionKeyWidgets.visible.size(), 5);
+ QCOMPARE(encryptionKeyWidgets.hidden.size(), 0);
+
+ // encryption key widgets for sender come first
+ QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
+ QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP),
+ preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());
+ QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), sender);
+ QCOMPARE(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::CMS),
+ preferredSolution.encryptionKeys.value(sender)[1].primaryFingerprint());
+
+ // encryption key widgets for other recipients follow
+ QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-openpgp@example.net");
+ QCOMPARE(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::UnknownProtocol),
+ preferredSolution.encryptionKeys.value("prefer-openpgp@example.net")[0].primaryFingerprint());
+ QCOMPARE(encryptionKeyWidgets.visible[3]->property("address").toString(), "prefer-smime@example.net");
+ QCOMPARE(encryptionKeyWidgets.visible[3]->defaultKey(GpgME::UnknownProtocol),
+ preferredSolution.encryptionKeys.value("prefer-smime@example.net")[0].primaryFingerprint());
+ QCOMPARE(encryptionKeyWidgets.visible[4]->property("address").toString(), "unknown@example.net");
+ QVERIFY(encryptionKeyWidgets.visible[4]->defaultKey(GpgME::UnknownProtocol).isEmpty());
+ }
+
+ void test__both_protocols_allowed__mixed_allowed__no_sender_keys()
+ {
+ const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
+ const bool allowMixed = true;
+ const QString sender = QStringLiteral("sender@example.net");
+ const KeyResolver::Solution preferredSolution = {
+ GpgME::UnknownProtocol,
+ {},
+ {
+ {QStringLiteral("prefer-openpgp@example.net"), {createTestKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
+ {QStringLiteral("prefer-smime@example.net"), {createTestKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
+ {QStringLiteral("unknown@example.net"), {}},
+ {QStringLiteral("sender@example.net"), {}}
+ }
+ };
+ const KeyResolver::Solution alternativeSolution = {};
+
+ const auto dialog = std::make_unique<NewKeyApprovalDialog>(true,
+ true,
+ sender,
+ preferredSolution,
+ alternativeSolution,
+ allowMixed,
+ forcedProtocol);
+ dialog->show();
+
+ const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
+ QCOMPARE(signingKeyWidgets.visible.size(), 2);
+ QCOMPARE(signingKeyWidgets.hidden.size(), 0);
+
+ const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
+ QCOMPARE(encryptionKeyWidgets.visible.size(), 5);
+ QCOMPARE(encryptionKeyWidgets.hidden.size(), 0);
+
+ // encryption key widgets for sender come first
+ QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
+ QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), sender);
+
+ // encryption key widgets for other recipients follow
+ QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-openpgp@example.net");
+ QCOMPARE(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::UnknownProtocol),
+ preferredSolution.encryptionKeys.value("prefer-openpgp@example.net")[0].primaryFingerprint());
+ QCOMPARE(encryptionKeyWidgets.visible[3]->property("address").toString(), "prefer-smime@example.net");
+ QCOMPARE(encryptionKeyWidgets.visible[3]->defaultKey(GpgME::UnknownProtocol),
+ preferredSolution.encryptionKeys.value("prefer-smime@example.net")[0].primaryFingerprint());
+ QCOMPARE(encryptionKeyWidgets.visible[4]->property("address").toString(), "unknown@example.net");
+ QVERIFY(encryptionKeyWidgets.visible[4]->defaultKey(GpgME::UnknownProtocol).isEmpty());
+ }
+
+ void test__both_protocols_allowed__mixed_allowed__encrypt_only()
+ {
+ const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
+ const bool allowMixed = true;
+ const QString sender = QStringLiteral("sender@example.net");
+ const KeyResolver::Solution preferredSolution = {
+ GpgME::UnknownProtocol,
+ {createTestKey("sender@example.net", GpgME::OpenPGP), createTestKey("sender@example.net", GpgME::CMS)},
+ {
+ {QStringLiteral("prefer-openpgp@example.net"), {createTestKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
+ {QStringLiteral("prefer-smime@example.net"), {createTestKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
+ {QStringLiteral("unknown@example.net"), {}},
+ {QStringLiteral("sender@example.net"), {createTestKey("sender@example.net", GpgME::OpenPGP), createTestKey("sender@example.net", GpgME::CMS)}}
+ }
+ };
+ const KeyResolver::Solution alternativeSolution = {};
+
+ const auto dialog = std::make_unique<NewKeyApprovalDialog>(true,
+ false,
+ sender,
+ preferredSolution,
+ alternativeSolution,
+ allowMixed,
+ forcedProtocol);
+ dialog->show();
+
+ const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
+ QCOMPARE(signingKeyWidgets.visible.size(), 0);
+ QCOMPARE(signingKeyWidgets.hidden.size(), 0);
+
+ const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
+ QCOMPARE(encryptionKeyWidgets.visible.size(), 5);
+ QCOMPARE(encryptionKeyWidgets.hidden.size(), 0);
}
private:
QMap<QString, std::vector<GpgME::Key> > resolved_senders_openpgp_and_smime()
{
return {
{QStringLiteral("sender@example.net"), {
createTestKey("sender@example.net", GpgME::OpenPGP),
createTestKey("sender@example.net", GpgME::CMS)
}}
};
}
QMap<QString, std::vector<GpgME::Key> > resolved_recipients_openpgp_and_smime()
{
return {
{QStringLiteral("prefer-openpgp@example.net"), {
createTestKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)
}},
{QStringLiteral("prefer-smime@example.net"), {
createTestKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)
}},
{QStringLiteral("sender@example.net"), {
createTestKey("sender@example.net", GpgME::OpenPGP),
createTestKey("sender@example.net", GpgME::CMS)
}}
};
}
};
QTEST_MAIN(NewKeyApprovalDialogTest)
#include "newkeyapprovaldialogtest.moc"
diff --git a/src/kleo/keyresolver.cpp b/src/kleo/keyresolver.cpp
index 58926750..81dd0820 100644
--- a/src/kleo/keyresolver.cpp
+++ b/src/kleo/keyresolver.cpp
@@ -1,288 +1,169 @@
/* -*- 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
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
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 "keyresolvercore.h"
#include "models/keycache.h"
#include "ui/newkeyapprovaldialog.h"
#include "utils/formatting.h"
#include <gpgme++/key.h>
#include "libkleo_debug.h"
using namespace Kleo;
using namespace GpgME;
class KeyResolver::Private
{
public:
Private(KeyResolver* qq, bool enc, bool sig, Protocol fmt, bool allowMixed)
: q(qq)
, mCore(enc, sig, fmt)
, mFormat(fmt)
, mEncrypt(enc)
, mSign(sig)
, mAllowMixed(allowMixed)
, mCache(KeyCache::instance())
, mDialogWindowFlags(Qt::WindowFlags())
, mPreferredProtocol(UnknownProtocol)
{
mCore.setAllowMixedProtocols(allowMixed);
}
~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;
bool 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<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);
}
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);
}
- 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)
: d(new Private(this, encrypt, sign, fmt, allowMixed))
{
}
Kleo::KeyResolver::~KeyResolver() = default;
void KeyResolver::setRecipients(const QStringList &addresses)
{
d->mCore.setRecipients(addresses);
}
void KeyResolver::setSender(const QString &address)
{
d->mCore.setSender(address);
}
void KeyResolver::setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList> > &overrides)
{
d->mCore.setOverrideKeys(overrides);
}
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)
{
d->mDialogWindowFlags = flags;
}
void KeyResolver::setPreferredProtocol(Protocol proto)
{
d->mCore.setPreferredProtocol(proto);
}
void KeyResolver::setMinimumValidity(int validity)
{
d->mCore.setMinimumValidity(validity);
}
diff --git a/src/kleo/keyresolver.h b/src/kleo/keyresolver.h
index 2518f34b..adfa55f8 100644
--- a/src/kleo/keyresolver.h
+++ b/src/kleo/keyresolver.h
@@ -1,200 +1,199 @@
/* -*- 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
*/
#pragma once
#include "kleo_export.h"
#include <Libkleo/Enum>
#include <QMap>
#include <QObject>
#include <QString>
#include <QStringList>
#include <gpgme++/global.h>
#include <memory>
#include <vector>
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:
+ 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.
* @param sign: Should signing keys be selected.
* @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,
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 \<protocol\> -> (\<address\> \<fingerprints\>)
*/
void setOverrideKeys(const QMap<GpgME::Protocol, QMap<QString, QStringList> > &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 <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
* 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);
/**
* 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<Private> d;
};
} // namespace Kleo
diff --git a/src/kleo/keyresolvercore.cpp b/src/kleo/keyresolvercore.cpp
index bdac3b94..b22b66c1 100644
--- a/src/kleo/keyresolvercore.cpp
+++ b/src/kleo/keyresolvercore.cpp
@@ -1,633 +1,676 @@
/* -*- c++ -*-
kleo/keyresolvercore.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2018 Intevation GmbH
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
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 "keyresolvercore.h"
#include "models/keycache.h"
#include "utils/formatting.h"
#include <gpgme++/key.h>
#include "libkleo_debug.h"
using namespace Kleo;
using namespace GpgME;
namespace {
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 Key &key)
{
if (key.isNull() || key.isRevoked() || key.isExpired() ||
key.isDisabled() || !key.canSign() || !key.hasSecret()) {
return false;
}
return true;
}
static int keyValidity(const Key &key, const QString &address)
{
// returns the validity of the UID matching the address or, if no UID matches, the maximal validity of all UIDs
int overallValidity = UserID::Validity::Unknown;
for (const auto &uid: key.userIDs()) {
if (QString::fromStdString(uid.addrSpec()).toLower() == address.toLower()) {
return uid.validity();
}
overallValidity = std::max(overallValidity, static_cast<int>(uid.validity()));
}
return overallValidity;
}
static int minimumValidity(const std::vector<Key> &keys, const QString &address)
{
const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1,
[address] (int validity, const Key &key) {
return std::min<int>(validity, keyValidity(key, address));
});
return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
}
bool allKeysHaveProtocol(const std::vector<Key> &keys, Protocol protocol)
{
return std::all_of(keys.cbegin(), keys.cend(), [protocol] (const Key &key) { return key.protocol() == protocol; });
}
bool anyKeyHasProtocol(const std::vector<Key> &keys, Protocol protocol)
{
return std::any_of(std::begin(keys), std::end(keys), [protocol] (const Key &key) { return key.protocol() == protocol; });
}
} // namespace
class KeyResolverCore::Private
{
public:
Private(KeyResolverCore* qq, bool enc, bool sig, Protocol fmt)
: q(qq)
, mFormat(fmt)
, mEncrypt(enc)
, mSign(sig)
, mCache(KeyCache::instance())
, mPreferredProtocol(UnknownProtocol)
, mMinimumValidity(UserID::Marginal)
, mCompliance(Formatting::complianceMode())
{
}
~Private() = default;
bool isAcceptableSigningKey(const Key &key);
bool isAcceptableEncryptionKey(const Key &key, const QString &address = QString());
void setSender(const QString &address);
void addRecipients(const QStringList &addresses);
void setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList>> &overrides);
void resolveOverrides();
void resolveSign(Protocol proto);
void setSigningKeys(const QStringList &fingerprints);
std::vector<Key> resolveRecipient(const QString &address, Protocol protocol);
void resolveEnc(Protocol proto);
void mergeEncryptionKeys();
QStringList unresolvedRecipients(GpgME::Protocol protocol) const;
- bool resolve();
+ Result resolve();
KeyResolverCore *const q;
QString mSender;
QStringList mRecipients;
QMap<Protocol, std::vector<Key>> mSigKeys;
QMap<QString, QMap<Protocol, std::vector<Key>>> mEncKeys;
QMap<QString, QMap<Protocol, QStringList>> mOverrides;
Protocol mFormat;
QStringList mFatalErrors;
bool mEncrypt;
bool mSign;
// 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;
bool mAllowMixed = true;
Protocol mPreferredProtocol;
int mMinimumValidity;
QString mCompliance;
};
bool KeyResolverCore::Private::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 KeyResolverCore::Private::isAcceptableEncryptionKey(const Key &key, const QString &address)
{
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 KeyResolverCore::Private::setSender(const QString &address)
{
const auto normalized = UserID::addrSpecFromString (address.toUtf8().constData());
if (normalized.empty()) {
// should not happen bug in the caller, non localized
// error for bug reporting.
mFatalErrors << QStringLiteral("The sender address '%1' could not be extracted").arg(address);
return;
}
const auto normStr = QString::fromUtf8(normalized.c_str());
if (mSign) {
mSender = normStr;
}
addRecipients({address});
}
void KeyResolverCore::Private::addRecipients(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 = 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());
mRecipients << normStr;
// Initially add empty lists of keys for both protocols
mEncKeys[normStr] = {{CMS, {}}, {OpenPGP, {}}};
}
}
void KeyResolverCore::Private::setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList>> &overrides)
{
for (auto protocolIt = overrides.cbegin(); protocolIt != overrides.cend(); ++protocolIt) {
const Protocol &protocol = protocolIt.key();
const auto &addressFingerprintMap = protocolIt.value();
for (auto addressIt = addressFingerprintMap.cbegin(); addressIt != addressFingerprintMap.cend(); ++addressIt) {
const QString &address = addressIt.key();
const QStringList &fingerprints = addressIt.value();
const QString normalizedAddress = QString::fromUtf8(UserID::addrSpecFromString(address.toUtf8().constData()).c_str());
mOverrides[normalizedAddress][protocol] = fingerprints;
}
}
}
namespace
{
std::vector<Key> resolveOverride(const QString &address, Protocol protocol, const QStringList &fingerprints)
{
std::vector<Key> keys;
for (const auto &fprOrId: fingerprints) {
const Key key = KeyCache::instance()->findByKeyIDOrFingerprint(fprOrId.toUtf8().constData());
if (key.isNull()) {
// FIXME: Report to caller
qCDebug (LIBKLEO_LOG) << "Failed to find override key for:" << address << "fpr:" << fprOrId;
continue;
}
if (protocol != UnknownProtocol && key.protocol() != protocol) {
qCDebug(LIBKLEO_LOG) << "Ignoring key" << Formatting::summaryLine(key) << "given as" << Formatting::displayName(protocol) << "override for"
<< address;
continue;
}
qCDebug(LIBKLEO_LOG) << "Using key" << Formatting::summaryLine(key) << "as" << Formatting::displayName(protocol) << "override for" << address;
keys.push_back(key);
}
return keys;
}
}
void KeyResolverCore::Private::resolveOverrides()
{
if (!mEncrypt) {
// No encryption we are done.
return;
}
for (auto addressIt = mOverrides.cbegin(); addressIt != mOverrides.cend(); ++addressIt) {
const QString &address = addressIt.key();
const auto &protocolFingerprintsMap = addressIt.value();
if (!mRecipients.contains(address)) {
qCDebug(LIBKLEO_LOG) << "Overrides provided for an address that is "
"neither sender nor recipient. Address:" << address;
continue;
}
const QStringList commonOverride = protocolFingerprintsMap.value(UnknownProtocol);
if (!commonOverride.empty()) {
mEncKeys[address][UnknownProtocol] = resolveOverride(address, UnknownProtocol, commonOverride);
if (protocolFingerprintsMap.contains(OpenPGP)) {
qCDebug(LIBKLEO_LOG) << "Ignoring OpenPGP-specific override for" << address << "in favor of common override";
}
if (protocolFingerprintsMap.contains(CMS)) {
qCDebug(LIBKLEO_LOG) << "Ignoring S/MIME-specific override for" << address << "in favor of common override";
}
} else {
if (mFormat != CMS) {
mEncKeys[address][OpenPGP] = resolveOverride(address, OpenPGP, protocolFingerprintsMap.value(OpenPGP));
}
if (mFormat != OpenPGP) {
mEncKeys[address][CMS] = resolveOverride(address, CMS, protocolFingerprintsMap.value(CMS));
}
}
}
}
void KeyResolverCore::Private::resolveSign(Protocol proto)
{
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(proto, keys);
}
}
void KeyResolverCore::Private::setSigningKeys(const QStringList &fingerprints)
{
if (mSign) {
for (const auto &fpr: fingerprints) {
const auto key = mCache->findByKeyIDOrFingerprint(fpr.toUtf8().constData());
if (key.isNull()) {
qCDebug(LIBKLEO_LOG) << "Failed to find signing key with fingerprint" << fpr;
continue;
}
auto list = mSigKeys.value(key.protocol());
list.push_back(key);
mSigKeys.insert(key.protocol(), list);
}
}
}
std::vector<Key> KeyResolverCore::Private::resolveRecipient(const QString &address, Protocol protocol)
{
const auto keys = mCache->findBestByMailBox(address.toUtf8().constData(), protocol, false, true);
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 {
// 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:" << address << keys[0].primaryFingerprint()
<< "has not enough validity";
unacceptable = true;
break;
}
}
if (unacceptable) {
return {};
}
}
for (const auto &k: keys) {
qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << k.primaryFingerprint();
}
return keys;
}
// Try to find matching keys in the provided protocol for the unresolved addresses
void KeyResolverCore::Private::resolveEnc(Protocol proto)
{
for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) {
const QString &address = it.key();
auto &protocolKeysMap = it.value();
if (!protocolKeysMap[proto].empty()) {
// already resolved for current protocol (by override)
continue;
}
const std::vector<Key> &commonOverride = protocolKeysMap[UnknownProtocol];
if (!commonOverride.empty()) {
// there is a common override; use it for current protocol if possible
if (allKeysHaveProtocol(commonOverride, proto)) {
protocolKeysMap[proto] = commonOverride;
continue;
} else {
qCDebug(LIBKLEO_LOG) << "Common override for" << address << "is unusable for" << Formatting::displayName(proto);
continue;
}
}
protocolKeysMap[proto] = resolveRecipient(address, 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
{
QStringList result;
result.reserve(mEncKeys.size());
for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) {
const auto &protocolKeysMap = it.value();
if (protocolKeysMap.value(protocol).empty()) {
result.push_back(it.key());
}
}
return result;
}
namespace
{
bool hasUnresolvedRecipients(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol protocol)
{
return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys),
[protocol] (const auto &protocolKeysMap) {
return protocolKeysMap.value(protocol).empty();
});
}
bool anyCommonOverrideHasKeyOfType(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol protocol)
{
return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys),
[protocol] (const auto &protocolKeysMap) {
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
resolveOverrides();
// check protocols needed for overrides
const bool commonOverridesNeedOpenPGP = anyCommonOverrideHasKeyOfType(mEncKeys, OpenPGP);
const bool commonOverridesNeedCMS = anyCommonOverrideHasKeyOfType(mEncKeys, CMS);
if ((mFormat == OpenPGP && commonOverridesNeedCMS)
|| (mFormat == CMS && commonOverridesNeedOpenPGP)
|| (!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)
: d(new Private(this, encrypt, sign, fmt))
{
}
KeyResolverCore::~KeyResolverCore() = default;
void KeyResolverCore::setSender(const QString &address)
{
d->setSender(address);
}
QString KeyResolverCore::normalizedSender() const
{
return d->mSender;
}
void KeyResolverCore::setRecipients(const QStringList &addresses)
{
d->addRecipients(addresses);
}
void KeyResolverCore::setSigningKeys(const QStringList &fingerprints)
{
d->setSigningKeys(fingerprints);
}
void KeyResolverCore::setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList>> &overrides)
{
d->setOverrideKeys(overrides);
}
void KeyResolverCore::setAllowMixedProtocols(bool allowMixed)
{
d->mAllowMixed = allowMixed;
}
void KeyResolverCore::setPreferredProtocol(Protocol proto)
{
d->mPreferredProtocol = proto;
}
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);
-}
diff --git a/src/kleo/keyresolvercore.h b/src/kleo/keyresolvercore.h
index 490d1c69..15d7643d 100644
--- a/src/kleo/keyresolvercore.h
+++ b/src/kleo/keyresolvercore.h
@@ -1,70 +1,87 @@
/* -*- c++ -*-
kleo/keyresolvercore.h
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2018 Intevation GmbH
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
+#include <Libkleo/KeyResolver>
+
#include "kleo_export.h"
#include <QMap>
#include <gpgme++/global.h>
#include <memory>
#include <vector>
class QString;
class QStringList;
namespace GpgME
{
class Key;
}
namespace Kleo
{
class KLEO_EXPORT KeyResolverCore
{
public:
+ enum SolutionFlags
+ {
+ SomeUnresolved = 0,
+ AllResolved = 1,
+
+ OpenPGPOnly = 2,
+ CMSOnly = 4,
+ MixedProtocols = OpenPGPOnly | CMSOnly,
+
+ Error = 0x1000,
+
+ ResolvedMask = AllResolved | Error,
+ ProtocolsMask = OpenPGPOnly | CMSOnly | Error,
+ };
+ struct Result
+ {
+ SolutionFlags flags;
+ KeyResolver::Solution solution;
+ KeyResolver::Solution alternative;
+ };
+
explicit KeyResolverCore(bool encrypt, bool sign,
GpgME::Protocol format = GpgME::UnknownProtocol);
~KeyResolverCore();
void setSender(const QString &sender);
QString normalizedSender() const;
void setRecipients(const QStringList &addresses);
void setSigningKeys(const QStringList &fingerprints);
void setOverrideKeys(const QMap<GpgME::Protocol, QMap<QString, QStringList> > &overrides);
void setAllowMixedProtocols(bool allowMixed);
void setPreferredProtocol(GpgME::Protocol proto);
void setMinimumValidity(int validity);
- bool resolve();
-
- QMap<GpgME::Protocol, std::vector<GpgME::Key> > signingKeys() const;
-
- QMap<GpgME::Protocol, QMap<QString, std::vector<GpgME::Key> > > encryptionKeys() const;
-
- QStringList unresolvedRecipients(GpgME::Protocol protocol) const;
+ Result resolve();
private:
class Private;
std::unique_ptr<Private> d;
};
} // namespace Kleo
diff --git a/src/tests/test_keyresolver.cpp b/src/tests/test_keyresolver.cpp
index b2bf0eca..59f133ab 100644
--- a/src/tests/test_keyresolver.cpp
+++ b/src/tests/test_keyresolver.cpp
@@ -1,150 +1,146 @@
/*
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 <QCommandLineParser>
#include <QApplication>
#include <QDebug>
#include <QTimer>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace GpgME;
-void dumpKeys(const QMap <Protocol, QMap<QString, std::vector<Key> > > &fmtMap)
+void dumpKeys(const QMap<QString, std::vector<Key>> &keysMap)
{
- 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();
- }
+ for (auto it = std::begin(keysMap); it != std::end(keysMap); ++it) {
+ const auto &address = it.key();
+ const auto &keys = it.value();
+ qDebug() << "Address:" << address;
+ qDebug() << "Keys:";
+ for (const auto &key: keys) {
+ qDebug() << key.primaryFingerprint();
}
}
}
-void dumpSigKeys(const QMap <Protocol, std::vector<Key> > &fmtMap)
+void dumpSigKeys(const std::vector<Key> &keys)
{
- for (const Protocol fmt: fmtMap.keys()) {
- qDebug () << "Format:" << Formatting::displayName(fmt) << fmt;
- qDebug() << "Keys:";
- for (const auto &key: fmtMap[fmt]) {
- qDebug () << key.primaryFingerprint();
- }
+ for (const auto &key: keys) {
+ 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);
}
+ const auto result = resolver->result();
qDebug() << "Resolved Signing keys:";
- dumpSigKeys (resolver->signingKeys());
+ dumpSigKeys(result.signingKeys);
qDebug() << "Resolved Encryption keys:";
- dumpKeys (resolver->encryptionKeys());
+ dumpKeys(result.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({QStringLiteral("overrides"), QStringLiteral("o")},
QStringLiteral("Override where format can be:\n"
"OpenPGP\n"
"SMIME\n"
"Auto"),
QStringLiteral("mailbox:fpr,fpr,...[:format]")));
parser.addOption(QCommandLineOption({QStringLiteral("sender"), QStringLiteral("s")},
QStringLiteral("Mailbox of the sender"),
QStringLiteral("mailbox")));
parser.addOption(QCommandLineOption({QStringLiteral("sigkeys"), QStringLiteral("k")},
QStringLiteral("Explicit signing keys"),
QStringLiteral("signing key")));
parser.addOption(QCommandLineOption({QStringLiteral("encrypt"), QStringLiteral("e")},
QStringLiteral("Only select encryption keys")));
parser.addOption(QCommandLineOption({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 <Protocol, QMap <QString, QStringList> > overrides;
for (const QString &oride: parser.values(QStringLiteral("overrides"))) {
const QStringList split = oride.split(QLatin1Char(':'));
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("openpgp")) {
fmt = OpenPGP;
} else if (fmtStr == QLatin1String("smime")) {
fmt = CMS;
} else if (fmtStr == QLatin1String("auto")) {
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"
diff --git a/src/ui/newkeyapprovaldialog.cpp b/src/ui/newkeyapprovaldialog.cpp
index 5698eb6c..86e3eac3 100644
--- a/src/ui/newkeyapprovaldialog.cpp
+++ b/src/ui/newkeyapprovaldialog.cpp
@@ -1,833 +1,870 @@
/* -*- 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 <QApplication>
#include <QButtonGroup>
#include <QDesktopWidget>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QLabel>
#include <QMap>
#include <QPushButton>
#include <QRadioButton>
#include <QScrollArea>
#include <QToolTip>
#include <QVBoxLayout>
#include <QGpgME/DefaultKeyGenerationJob>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include <gpgme++/keygenerationresult.h>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <KMessageBox>
using namespace Kleo;
using namespace GpgME;
+QDebug operator<<(QDebug debug, const GpgME::Key &key)
+{
+ if (key.isNull()) {
+ debug << "Null";
+ } else {
+ debug << Formatting::summaryLine(key);
+ }
+ return debug.maybeSpace();
+}
+
namespace {
+static std::shared_ptr<KeyFilter> s_defaultFilter= std::shared_ptr<KeyFilter> (new DefaultKeyFilter);
+
class OpenPGPFilter: public DefaultKeyFilter
{
public:
OpenPGPFilter() : DefaultKeyFilter()
{
setIsOpenPGP(DefaultKeyFilter::Set);
setCanEncrypt(DefaultKeyFilter::Set);
}
};
static std::shared_ptr<KeyFilter> s_pgpFilter = std::shared_ptr<KeyFilter> (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<KeyFilter> s_pgpSignFilter = std::shared_ptr<KeyFilter> (new OpenPGPSignFilter);
+
class SMIMEFilter: public DefaultKeyFilter
{
public:
SMIMEFilter(): DefaultKeyFilter()
{
setIsOpenPGP(DefaultKeyFilter::NotSet);
setCanEncrypt(DefaultKeyFilter::Set);
}
};
static std::shared_ptr<KeyFilter> s_smimeFilter = std::shared_ptr<KeyFilter> (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<KeyFilter> s_smimeSignFilter = std::shared_ptr<KeyFilter> (new SMIMESignFilter);
-static std::shared_ptr<KeyFilter> s_defaultFilter= std::shared_ptr<KeyFilter> (new DefaultKeyFilter);
-class SignFilter: public DefaultKeyFilter
-{
-public:
- SignFilter(): DefaultKeyFilter()
- {
- setHasSecret(DefaultKeyFilter::Set);
- }
-};
-static std::shared_ptr<KeyFilter> s_signFilter = std::shared_ptr<KeyFilter> (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);
});
// FIXME: This is ugly to enforce but otherwise the
// icon is broken.
combo->setMinimumHeight(22);
mFilterBtn->setMinimumHeight(23);
updateFilterButton();
connect(mFilterBtn, &QPushButton::clicked, this, [this] () {
const QString curFilter = mCombo->idFilter();
if (curFilter.isEmpty()) {
setIdFilter(mLastIdFilter);
mLastIdFilter = QString();
} else {
setIdFilter(QString());
mLastIdFilter = curFilter;
}
});
}
void setIdFilter(const QString &id)
{
mCombo->setIdFilter(id);
updateFilterButton();
}
void updateFilterButton()
{
if (mCombo->idFilter().isEmpty()) {
mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters")));
mFilterBtn->setToolTip(i18n("Show keys matching the email address"));
} else {
mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters")));
mFilterBtn->setToolTip(i18n("Show all keys"));
}
}
KeySelectionCombo *combo()
{
return mCombo;
}
GpgME::Protocol fromOverride() const
{
return mFromOverride;
}
void setFromOverride(GpgME::Protocol proto)
{
mFromOverride = proto;
}
GpgME::Protocol fixedProtocol() const
{
return mFixedProtocol;
}
void setFixedProtocol(GpgME::Protocol proto)
{
mFixedProtocol = proto;
}
private:
KeySelectionCombo *mCombo;
QPushButton *mFilterBtn;
QString mLastIdFilter;
GpgME::Protocol mFromOverride;
GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol;
};
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;
}
+bool anyKeyHasProtocol(const std::vector<Key> &keys, Protocol protocol)
+{
+ return std::any_of(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; });
+}
+
+Key findfirstKeyOfType(const std::vector<Key> &keys, Protocol protocol)
+{
+ const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; });
+ return it != std::end(keys) ? *it : Key();
+}
+
} // namespace
class NewKeyApprovalDialog::Private
{
private:
enum Action {
Unset,
GenerateKey,
IgnoreKey,
};
public:
Private(NewKeyApprovalDialog *qq,
+ bool encrypt,
+ bool sign,
GpgME::Protocol forcedProtocol,
GpgME::Protocol presetProtocol,
const QString &sender,
bool allowMixed)
- : mForcedProtocol(forcedProtocol)
- , mPreferredProtocol(presetProtocol)
- , mSender(sender)
- , mAllowMixed(allowMixed)
- , q(qq)
+ : mForcedProtocol{forcedProtocol}
+ , mSender{sender}
+ , mSign{sign}
+ , mEncrypt{encrypt}
+ , mAllowMixed{allowMixed}
+ , q{qq}
{
Q_ASSERT(forcedProtocol == GpgME::UnknownProtocol || presetProtocol == GpgME::UnknownProtocol || presetProtocol == forcedProtocol);
Q_ASSERT(!allowMixed || (allowMixed && forcedProtocol == GpgME::UnknownProtocol));
// 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.<br/><br/>"
"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 (mForcedProtocol != GpgME::UnknownProtocol) {
- mPreferredProtocol = mForcedProtocol;
- }
- if (mPreferredProtocol == GpgME::UnknownProtocol) {
- mPreferredProtocol = GpgME::OpenPGP;
- }
if (mAllowMixed) {
smimeBtn->setVisible(false);
pgpBtn->setVisible(false);
} else if (mForcedProtocol != GpgME::UnknownProtocol) {
pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP);
smimeBtn->setChecked(mForcedProtocol == GpgME::CMS);
pgpBtn->setVisible(false);
smimeBtn->setVisible(false);
} else {
- pgpBtn->setChecked(mPreferredProtocol == GpgME::OpenPGP);
- smimeBtn->setChecked(mPreferredProtocol == GpgME::CMS);
+ pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP);
+ smimeBtn->setChecked(presetProtocol == GpgME::CMS);
}
- updateFilter();
-
-
QObject::connect (mFormatBtns, static_cast<void (QButtonGroup::*)(QAbstractButton *, bool)> (&QButtonGroup::buttonToggled),
q, [this](QAbstractButton *, bool) {
- updateFilter();
+ updateWidgetVisibility();
});
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;
Protocol currentProtocol()
{
- if (!mAllowMixed && mFormatBtns->checkedId() == 1) {
+ if (mAllowMixed) {
+ return UnknownProtocol;
+ } else if (mFormatBtns->checkedId() == 1) {
return OpenPGP;
- } else if (!mAllowMixed && mFormatBtns->checkedId() == 2) {
+ } else if (mFormatBtns->checkedId() == 2) {
return CMS;
}
return UnknownProtocol;
}
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 */
const Protocol protocol = currentProtocol();
- mAcceptedEnc.clear();
- mAcceptedSig.clear();
+ mAcceptedResult.encryptionKeys.clear();
+ mAcceptedResult.signingKeys.clear();
for (const auto combo: qAsConst(mEncCombos)) {
const auto &addr = combo->property("address").toString();
const auto &key = combo->currentKey();
if (!combo->isVisible()) {
continue;
}
if (protocol != UnknownProtocol && key.protocol() != protocol) {
continue;
}
- if (mAcceptedEnc.contains(addr)) {
- mAcceptedEnc[addr].push_back(key);
- } else {
- std::vector<GpgME::Key> vec;
- vec.push_back(key);
- mAcceptedEnc.insert(addr, vec);
- }
+ mAcceptedResult.encryptionKeys[addr].push_back(key);
}
for (const auto combo: qAsConst(mSigningCombos)) {
const auto key = combo->currentKey();
if (!combo->isVisible()) {
continue;
}
if (protocol != UnknownProtocol && key.protocol() != protocol) {
continue;
}
- mAcceptedSig.push_back(combo->currentKey());
+ mAcceptedResult.signingKeys.push_back(key);
}
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()
+ void updateWidgetVisibility()
{
const Protocol protocol = currentProtocol();
- switch (protocol) {
- case CMS:
- mCurEncFilter = s_smimeFilter;
- mCurSigFilter = s_smimeSignFilter;
- break;
- case OpenPGP:
- mCurEncFilter = s_pgpFilter;
- mCurSigFilter = s_pgpSignFilter;
- break;
- default:
- mCurEncFilter = s_defaultFilter;
- mCurSigFilter = s_signFilter;
- }
for (auto combo: qAsConst(mSigningCombos)) {
auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
if (!widget) {
qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget";
continue;
}
- if (widget->fixedProtocol() == GpgME::UnknownProtocol) {
- combo->setKeyFilter(mCurSigFilter);
- }
- widget->setVisible(mAllowMixed ||
- widget->fromOverride() == GpgME::UnknownProtocol ||
- (protocol != UnknownProtocol && widget->fromOverride() == protocol));
+ widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == protocol);
}
for (auto combo: qAsConst(mEncCombos)) {
auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
if (!widget) {
qCDebug(LIBKLEO_LOG) << "Failed to find combo widget";
continue;
}
- if (widget->fixedProtocol() == GpgME::UnknownProtocol) {
- combo->setKeyFilter(mCurEncFilter);
- }
- widget->setVisible(mAllowMixed ||
- widget->fromOverride() == GpgME::UnknownProtocol ||
- (protocol != UnknownProtocol && widget->fromOverride() == protocol));
+ widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == protocol);
}
}
- ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key)
+ ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, Protocol protocol = UnknownProtocol)
{
+ Q_ASSERT(!key.isNull() || protocol != UnknownProtocol);
+ protocol = !key.isNull() ? key.protocol() : protocol;
+
auto combo = new KeySelectionCombo();
auto comboWidget = new ComboWidget(combo);
#ifndef NDEBUG
combo->setObjectName(QStringLiteral("signing key"));
#endif
- if (!key.isNull()) {
- if (mAllowMixed) {
- if (key.protocol() == GpgME::OpenPGP) {
- combo->setKeyFilter(s_pgpSignFilter);
- } else if (key.protocol() == GpgME::CMS) {
- combo->setKeyFilter(s_smimeSignFilter);
- }
- comboWidget->setFixedProtocol(key.protocol());
- }
- combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), key.protocol());
+ if (protocol == GpgME::OpenPGP) {
+ combo->setKeyFilter(s_pgpSignFilter);
+ } else if (protocol == GpgME::CMS) {
+ combo->setKeyFilter(s_smimeSignFilter);
}
- if (!combo->keyFilter()) {
- combo->setKeyFilter(mCurSigFilter);
+ if (key.isNull() || key_has_addr(key, mSender)) {
+ comboWidget->setIdFilter(mSender);
+ }
+ comboWidget->setFixedProtocol(protocol);
+ if (!key.isNull()) {
+ combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), protocol);
}
- if (key.isNull() && mForcedProtocol != GpgME::CMS) {
+ if (key.isNull() && protocol == OpenPGP) {
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<int>::of(&QComboBox::currentIndexChanged), q, [this] () {
updateOkButton();
});
return comboWidget;
}
- void addSigningKeys(const QMap<QString, std::vector<GpgME::Key> > &resolved,
- const QStringList &unresolved)
+ void setSigningKeys(std::vector<GpgME::Key> preferredKeys, std::vector<GpgME::Key> alternativeKeys)
{
- if (resolved.empty() && unresolved.empty()) {
- return;
+ auto group = new QGroupBox(i18nc("Caption for signing key selection", "Signing keys for '%1", mSender));
+ group->setAlignment(Qt::AlignLeft);
+ auto sigLayout = new QVBoxLayout(group);
+
+ const bool mayNeedOpenPGP = mForcedProtocol != CMS;
+ const bool mayNeedCMS = mForcedProtocol != OpenPGP;
+ if (mayNeedOpenPGP) {
+ if (mAllowMixed) {
+ sigLayout->addWidget(new QLabel(Formatting::displayName(OpenPGP)));
+ }
+ const Key preferredKey = findfirstKeyOfType(preferredKeys, OpenPGP);
+ const Key alternativeKey = findfirstKeyOfType(alternativeKeys, OpenPGP);
+ if (!preferredKey.isNull()) {
+ qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
+ auto comboWidget = createSigningCombo(mSender, preferredKey);
+ sigLayout->addWidget(comboWidget);
+ } else if (!alternativeKey.isNull()) {
+ qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
+ auto comboWidget = createSigningCombo(mSender, alternativeKey);
+ sigLayout->addWidget(comboWidget);
+ } else {
+ qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for OpenPGP key";
+ auto comboWidget = createSigningCombo(mSender, Key(), OpenPGP);
+ sigLayout->addWidget(comboWidget);
+ }
}
- 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->setIdFilter(addr);
- }
- if (resolved[addr].size() > 1) {
- comboWidget->setFromOverride(key.protocol());
- }
+ if (mayNeedCMS) {
+ if (mAllowMixed) {
+ sigLayout->addWidget(new QLabel(Formatting::displayName(CMS)));
+ }
+ const Key preferredKey = findfirstKeyOfType(preferredKeys, CMS);
+ const Key alternativeKey = findfirstKeyOfType(alternativeKeys, CMS);
+ if (!preferredKey.isNull()) {
+ qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
+ auto comboWidget = createSigningCombo(mSender, preferredKey);
+ sigLayout->addWidget(comboWidget);
+ } else if (!alternativeKey.isNull()) {
+ qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
+ auto comboWidget = createSigningCombo(mSender, alternativeKey);
+ sigLayout->addWidget(comboWidget);
+ } else {
+ qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for S/MIME key";
+ auto comboWidget = createSigningCombo(mSender, Key(), CMS);
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->setIdFilter(addr);
- sigLayout->addWidget(comboWidget);
- }
+ mScrollLayout->addWidget(group);
}
- ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key)
+ ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, Protocol fixedProtocol)
{
auto combo = new KeySelectionCombo(false);
auto comboWidget = new ComboWidget(combo);
#ifndef NDEBUG
combo->setObjectName(QStringLiteral("encryption key"));
#endif
- combo->setKeyFilter(mCurEncFilter);
+ if (fixedProtocol == GpgME::OpenPGP) {
+ combo->setKeyFilter(s_pgpFilter);
+ } else if (fixedProtocol == GpgME::CMS) {
+ combo->setKeyFilter(s_smimeFilter);
+ } else {
+ combo->setKeyFilter(s_defaultFilter);
+ }
+ if (key.isNull() || key_has_addr (key, addr)) {
+ comboWidget->setIdFilter(addr);
+ }
+ comboWidget->setFixedProtocol(fixedProtocol);
if (!key.isNull()) {
- if (mAllowMixed && addr == mSender) {
- if (key.protocol() == GpgME::OpenPGP) {
- combo->setKeyFilter(s_pgpFilter);
- } else if (key.protocol() == GpgME::CMS) {
- combo->setKeyFilter(s_smimeFilter);
- }
- comboWidget->setFixedProtocol(key.protocol());
- }
- if (mAllowMixed && addr != mSender) {
- // do not set the key for a specific protocol if mixed protocols are allowed
- combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()));
- }
- else {
- combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), key.protocol());
- }
+ combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol);
}
- if (!combo->keyFilter()) {
- combo->setKeyFilter(mCurEncFilter);
- }
- if (mSender == addr && key.isNull()) {
+ if (addr == mSender && key.isNull() && fixedProtocol == OpenPGP) {
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.<br/><br/>"
"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)) {
- comboWidget->setIdFilter(addr);
- }
-
connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () {
updateOkButton();
});
connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged), q, [this] () {
updateOkButton();
});
mEncCombos << combo;
mAllCombos << combo;
combo->setProperty("address", addr);
return comboWidget;
}
- void addEncryptionAddr(const QString &addr, const std::vector<GpgME::Key> &keys,
+ void addEncryptionAddr(const QString &addr,
+ Protocol preferredKeysProtocol, const std::vector<GpgME::Key> &preferredKeys,
+ Protocol alternativeKeysProtocol, const std::vector<GpgME::Key> &alternativeKeys,
QGridLayout *encGrid)
{
- if (addr != mSender) {
- encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0);
- }
- if (keys.empty()) {
- ComboWidget* comboWidget = createEncryptionCombo(addr, GpgME::Key());
- encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
- } else {
- // in mixed mode prefer the keys with the preferred protocol for the other recipients
- std::vector<GpgME::Key> preferredKeys;
- if (mAllowMixed && addr != mSender) {
- std::copy_if(keys.cbegin(), keys.cend(),
- std::back_inserter(preferredKeys),
- [this] (const auto &key) { return key.protocol() == mPreferredProtocol; });
+ if (addr == mSender) {
+ const bool mayNeedOpenPGP = mForcedProtocol != CMS;
+ const bool mayNeedCMS = mForcedProtocol != OpenPGP;
+ if (mayNeedOpenPGP) {
+ if (mAllowMixed) {
+ encGrid->addWidget(new QLabel(Formatting::displayName(OpenPGP)), encGrid->rowCount(), 0);
+ }
+ for (const auto &key : preferredKeys) {
+ if (key.protocol() == OpenPGP) {
+ qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
+ auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
+ encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
+ }
+ }
+ for (const auto &key : alternativeKeys) {
+ if (key.protocol() == OpenPGP) {
+ qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
+ auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
+ encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
+ }
+ }
+ if (!anyKeyHasProtocol(preferredKeys, OpenPGP) && !anyKeyHasProtocol(alternativeKeys, OpenPGP)) {
+ qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for OpenPGP key";
+ auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), OpenPGP);
+ encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
+ }
}
- const std::vector<GpgME::Key> &encryptionKeys = !preferredKeys.empty() ? preferredKeys : keys;
- for (const auto &key: encryptionKeys) {
- ComboWidget* comboWidget = createEncryptionCombo(addr, key);
- if (keys.size() > 1) {
- comboWidget->setFromOverride(key.protocol());
+ if (mayNeedCMS) {
+ if (mAllowMixed) {
+ encGrid->addWidget(new QLabel(Formatting::displayName(CMS)), encGrid->rowCount(), 0);
+ }
+ for (const auto &key : preferredKeys) {
+ if (key.protocol() == CMS) {
+ qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
+ auto comboWidget = createEncryptionCombo(addr, key, CMS);
+ encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
+ }
+ }
+ for (const auto &key : alternativeKeys) {
+ if (key.protocol() == CMS) {
+ qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
+ auto comboWidget = createEncryptionCombo(addr, key, CMS);
+ encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
+ }
+ }
+ if (!anyKeyHasProtocol(preferredKeys, CMS) && !anyKeyHasProtocol(alternativeKeys, CMS)) {
+ qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for S/MIME key";
+ auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), CMS);
+ encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
+ }
+ } else {
+ encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0);
+
+ for (const auto &key : preferredKeys) {
+ qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
+ auto comboWidget = createEncryptionCombo(addr, key, preferredKeysProtocol);
+ encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
+ }
+ for (const auto &key : alternativeKeys) {
+ qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
+ auto comboWidget = createEncryptionCombo(addr, key, alternativeKeysProtocol);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
+ if (!mAllowMixed) {
+ if (preferredKeys.empty()) {
+ qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(preferredKeysProtocol) << "key";
+ auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), preferredKeysProtocol);
+ encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
+ }
+ if (alternativeKeys.empty() && alternativeKeysProtocol != UnknownProtocol) {
+ qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(alternativeKeysProtocol) << "key";
+ auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), alternativeKeysProtocol);
+ encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
+ }
+ } else {
+ if (preferredKeys.empty() && alternativeKeys.empty()) {
+ qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for any key";
+ auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), UnknownProtocol);
+ encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
+ }
+ }
}
}
- void addEncryptionKeys(const QMap<QString, std::vector<GpgME::Key> > &resolved,
- const QStringList &unresolved)
+ void setEncryptionKeys(Protocol preferredKeysProtocol, const QMap<QString, std::vector<GpgME::Key>> &preferredKeys,
+ Protocol alternativeKeysProtocol, const QMap<QString, std::vector<GpgME::Key>> &alternativeKeys)
{
- if (resolved.empty() && unresolved.empty()) {
- return;
- }
-
{
auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender));
group->setAlignment(Qt::AlignLeft);
- auto encGrid = new QGridLayout;
- group->setLayout(encGrid);
+ auto encGrid = new QGridLayout(group);
+
+ addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid);
+
mScrollLayout->addWidget(group);
- addEncryptionAddr(mSender, resolved.value(mSender), encGrid);
}
auto group = new QGroupBox(i18n("Encrypt to others:"));
group->setAlignment(Qt::AlignLeft);
auto encGrid = new QGridLayout;
group->setLayout(encGrid);
mScrollLayout->addWidget(group);
- for (const QString &addr: resolved.keys()) {
- if (addr != mSender) {
- addEncryptionAddr(addr, resolved[addr], encGrid);
- }
- }
- for (const QString &addr: unresolved) {
- if (addr != mSender) {
- addEncryptionAddr(addr, {}, encGrid);
+ for (auto it = std::begin(preferredKeys); it != std::end(preferredKeys); ++it) {
+ const auto &address = it.key();
+ const auto &keys = it.value();
+ if (address != mSender) {
+ addEncryptionAddr(address, preferredKeysProtocol, keys, alternativeKeysProtocol, alternativeKeys.value(address), 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()) {
+ if (!mEncrypt) {
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;
const Protocol protocol = currentProtocol();
for (const auto combo: qAsConst(mEncCombos)) {
const auto &key = combo->currentKey();
if (!combo->isVisible()) {
continue;
}
if (protocol != UnknownProtocol && key.protocol() != protocol) {
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 (protocol != UnknownProtocol && key.protocol() != protocol) {
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);
}
GpgME::Protocol mForcedProtocol;
- GpgME::Protocol mPreferredProtocol;
QList<KeySelectionCombo *> mSigningCombos;
QList<KeySelectionCombo *> mEncCombos;
QList<KeySelectionCombo *> mAllCombos;
QScrollArea *mScrollArea;
QVBoxLayout *mScrollLayout;
QPushButton *mOkButton;
QVBoxLayout *mMainLay;
QButtonGroup *mFormatBtns;
- std::shared_ptr<KeyFilter> mCurSigFilter;
- std::shared_ptr<KeyFilter> mCurEncFilter;
QString mSender;
+ bool mSign;
+ bool mEncrypt;
bool mAllowMixed;
NewKeyApprovalDialog *q;
QList <QGpgME::Job *> mRunningJobs;
GpgME::Error mLastError;
QLabel *mComplianceLbl;
- QMap<QString, std::vector<GpgME::Key> > mAcceptedEnc;
- std::vector<GpgME::Key> mAcceptedSig;
+ KeyResolver::Solution mAcceptedResult;
QString mGenerateTooltip;
};
-NewKeyApprovalDialog::NewKeyApprovalDialog(const QMap<QString, std::vector<GpgME::Key> > &resolvedSigningKeys,
- const QMap<QString, std::vector<GpgME::Key> > &resolvedRecp,
- const QStringList &unresolvedSigKeys,
- const QStringList &unresolvedRecp,
+NewKeyApprovalDialog::NewKeyApprovalDialog(bool encrypt,
+ bool sign,
const QString &sender,
+ KeyResolver::Solution preferredSolution,
+ KeyResolver::Solution alternativeSolution,
bool allowMixed,
GpgME::Protocol forcedProtocol,
- GpgME::Protocol presetProtocol,
QWidget *parent,
- Qt::WindowFlags f): QDialog(parent, f),
- d(new Private(this, forcedProtocol, presetProtocol, sender, allowMixed))
+ Qt::WindowFlags f)
+ : QDialog(parent, f)
+ , d{std::make_unique<Private>(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)}
{
- d->addSigningKeys(resolvedSigningKeys, unresolvedSigKeys);
- d->addEncryptionKeys(resolvedRecp, unresolvedRecp);
- d->updateFilter();
+ if (sign) {
+ d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys));
+ }
+ if (encrypt) {
+ d->setEncryptionKeys(preferredSolution.protocol, std::move(preferredSolution.encryptionKeys),
+ alternativeSolution.protocol, std::move(alternativeSolution.encryptionKeys));
+ }
+ d->updateWidgetVisibility();
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<GpgME::Key> NewKeyApprovalDialog::signingKeys()
-{
- return d->mAcceptedSig;
-}
-
-QMap <QString, std::vector<GpgME::Key> > NewKeyApprovalDialog::encryptionKeys()
+KeyResolver::Solution NewKeyApprovalDialog::result()
{
- return d->mAcceptedEnc;
+ return d->mAcceptedResult;
}
#include "newkeyapprovaldialog.moc"
diff --git a/src/ui/newkeyapprovaldialog.h b/src/ui/newkeyapprovaldialog.h
index e185223b..8571ba62 100644
--- a/src/ui/newkeyapprovaldialog.h
+++ b/src/ui/newkeyapprovaldialog.h
@@ -1,90 +1,80 @@
/* -*- 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
*/
#pragma once
+#include <Libkleo/KeyResolver>
+
#include "kleo_export.h"
#include <QDialog>
#include <gpgme++/key.h>
#include <vector>
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 sender: The address of the sender, this may be used if signing is not
+ * specified to identify a recipient for which "Generate Key" should
+ * be offered.
+ * @param preferredSolution: The preferred signing and/or encryption keys for the sender
+ * and the recipients.
+ * @param alternativeSolution: An alternative set of signing and/or encryption keys for the sender
+ * and the recipients. Typically, S/MIME-only, if preferred solution is OpenPGP-only,
+ * and vice versa. Ignored, if mixed protocol selection is allowed.
+ * @param allowMixed: Whether or not the dialog should allow mixed S/MIME / OpenPGP 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<QString, std::vector<GpgME::Key> > &resolvedSigningKeys,
- const QMap<QString, std::vector<GpgME::Key> > &resolvedRecp,
- const QStringList &unresolvedSigKeys,
- const QStringList &unresolvedRecp,
- const QString &senderAddr,
+ explicit NewKeyApprovalDialog(bool encrypt,
+ bool sign,
+ const QString &sender,
+ KeyResolver::Solution preferredSolution,
+ KeyResolver::Solution alternativeSolution,
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<GpgME::Key> signingKeys();
- /** @brief The selected encryption Keys. Only valid after Dialog was accepted. */
- QMap<QString, std::vector<GpgME::Key> > encryptionKeys();
+ /** @brief The selected signing and/or encryption keys. Only valid after the dialog was accepted. */
+ KeyResolver::Solution result();
private:
class Private;
std::unique_ptr<Private> d;
};
} // namespace kleo
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Dec 19, 3:27 AM (1 d, 10 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
fa/05/bcb9f2389205fd5b38e9e250dee4
Attached To
rLIBKLEO Libkleo
Event Timeline
Log In to Comment