diff --git a/autotests/keyresolvercoretest.cpp b/autotests/keyresolvercoretest.cpp index a3d3ffac3..83826b883 100644 --- a/autotests/keyresolvercoretest.cpp +++ b/autotests/keyresolvercoretest.cpp @@ -1,838 +1,1140 @@ /* autotests/keyresolvercoretest.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include 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); } template <> inline char *toString(const GpgME::Protocol &t) { return qstrdup(Formatting::displayName(t).toLocal8Bit().constData()); } template <> inline bool qCompare(GpgME::Protocol const &t1, GpgME::Protocol const &t2, const char *actual, const char *expected, const char *file, int line) { return compare_helper(t1 == t2, "Compared values are not the same", toString(t1), toString(t2), actual, expected, file, line); } } namespace { KeyGroup createGroup(const QString &name, const std::vector &keys = std::vector(), KeyGroup::Source source = KeyGroup::ApplicationConfig, const QString &configName = QString()) { const KeyGroup::Id groupId = (source == KeyGroup::ApplicationConfig) ? (configName.isEmpty() ? name : configName) : name; KeyGroup g(groupId, name, keys, source); return g; } } class KeyResolverCoreTest: public QObject { Q_OBJECT 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(); // make sure that the key cache has been populated (void)mKeyCache->keys(); } void cleanup() { // verify that nobody else holds a reference to the key cache QVERIFY(mKeyCache.use_count() == 1); mKeyCache.reset(); // kill all running gpg daemons (void)QProcess::execute("gpgconf", {"--kill", "all"}); mGnupgHome.reset(); qunsetenv("GNUPGHOME"); } 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 auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); 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(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 auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); 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(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 auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); 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_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, OpenPGP); 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, OpenPGP); 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, CMS); 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 auto result = resolver.resolve(); 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(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_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 auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.value("unknown@example.net").size(), 0); } 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 auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); 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_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 auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); 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_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 auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); 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 "), {override}}}}}); const auto result = resolver.resolve(); 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 "), {override}}}}}); const auto result = resolver.resolve(); 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 "), {override}}}}}); const auto result = resolver.resolve(); 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(result.alternative.encryptionKeys.size(), 0); } 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 "), {override}}}}}); const auto result = resolver.resolve(); 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 "), {override}}}}}); const auto result = resolver.resolve(); 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 "), {override}}}}}); const auto result = resolver.resolve(); 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(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 "), {override1}}}}}); resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized "), {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(), testKey("sender-openpgp@example.net", OpenPGP).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 "), {override}}}}}); const auto result = resolver.resolve(); 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 "), {override}}}}}); const auto result = resolver.resolve(); 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 auto result = resolver.resolve(); 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 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 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 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 auto result = resolver.resolve(); QVERIFY(result.flags & KeyResolverCore::Error); } void test_groups__openpgp_only_mode__ignores_non_openpgp_only_groups() { const std::vector groups = { createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS) }), createGroup("group@example.net", { testKey("prefer-smime@example.net", CMS) }), createGroup("group@example.net", { testKey("prefer-openpgp@example.net", OpenPGP), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false, OpenPGP); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net")[0].primaryFingerprint(), testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint()); } void test_groups__smime_only_mode__ignores_non_smime_only_groups() { const std::vector groups = { createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS) }), createGroup("group@example.net", { testKey("prefer-smime@example.net", CMS) }), createGroup("group@example.net", { testKey("prefer-openpgp@example.net", OpenPGP), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false, CMS); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net")[0].primaryFingerprint(), testKey("prefer-smime@example.net", CMS).primaryFingerprint()); } void test_groups__single_protocol_mode__ignores_mixed_protocol_groups() { const std::vector groups = { createGroup("sender-mixed@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS) }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false); resolver.setAllowMixedProtocols(false); resolver.setRecipients({"sender-mixed@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.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()); } void test_groups__mixed_mode__single_protocol_groups_are_preferred_over_mixed_protocol_groups() { const std::vector groups = { createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS) }), createGroup("group@example.net", { testKey("prefer-smime@example.net", CMS) }), createGroup("group@example.net", { testKey("prefer-openpgp@example.net", OpenPGP), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net")[0].primaryFingerprint(), testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint()); } void test_groups__mixed_mode__openpgp_only_group_preferred_over_mixed_protocol_group() { const std::vector groups = { createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS) }), createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP) }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net")[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); } void test_groups__mixed_mode__smime_only_group_preferred_over_mixed_protocol_group() { const std::vector groups = { createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS) }), createGroup("group@example.net", { testKey("sender-smime@example.net", CMS) }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net")[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); } void test_groups__mixed_mode__mixed_protocol_groups_are_used() { const std::vector groups = { createGroup("sender-mixed@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS) }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false); resolver.setRecipients({"sender-mixed@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols); QCOMPARE(result.solution.protocol, UnknownProtocol); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 2); } + void test_groups_for_signing_key__openpgp_only_mode__prefers_groups_over_keys() + { + const std::vector groups = { + createGroup("sender-mixed@example.net", { + testKey("sender-openpgp@example.net", OpenPGP), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, OpenPGP); + resolver.setSender("sender-mixed@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); + QCOMPARE(result.solution.protocol, OpenPGP); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); + } + + void test_groups_for_signing_key__openpgp_only_mode__prefers_single_protocol_groups() + { + const std::vector groups = { + createGroup("sender-alias@example.net", { + testKey("sender-mixed@example.net", OpenPGP), + testKey("sender-mixed@example.net", CMS), + }), + createGroup("sender-alias@example.net", { + testKey("sender-openpgp@example.net", OpenPGP), + }), + createGroup("sender-alias@example.net", { + testKey("sender-smime@example.net", CMS), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, OpenPGP); + resolver.setSender("sender-alias@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); + QCOMPARE(result.solution.protocol, OpenPGP); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); + } + + void test_groups_for_signing_key__openpgp_only_mode__takes_key_of_mixed_protocol_groups() + { + const std::vector groups = { + createGroup("sender-alias@example.net", { + testKey("sender-mixed@example.net", OpenPGP), + testKey("sender-mixed@example.net", CMS), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, OpenPGP); + resolver.setSender("sender-alias@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); + QCOMPARE(result.solution.protocol, OpenPGP); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); + } + + void test_groups_for_signing_key__smime_only_mode__prefers_groups_over_keys() + { + const std::vector groups = { + createGroup("sender-mixed@example.net", { + testKey("sender-smime@example.net", CMS), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, CMS); + resolver.setSender("sender-mixed@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); + QCOMPARE(result.solution.protocol, CMS); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-smime@example.net", CMS).primaryFingerprint()); + } + + void test_groups_for_signing_key__smime_only_mode__prefers_single_protocol_groups() + { + const std::vector groups = { + createGroup("sender-alias@example.net", { + testKey("sender-mixed@example.net", OpenPGP), + testKey("sender-mixed@example.net", CMS), + }), + createGroup("sender-alias@example.net", { + testKey("sender-openpgp@example.net", OpenPGP), + }), + createGroup("sender-alias@example.net", { + testKey("sender-smime@example.net", CMS), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, CMS); + resolver.setSender("sender-alias@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); + QCOMPARE(result.solution.protocol, CMS); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-smime@example.net", CMS).primaryFingerprint()); + } + + void test_groups_for_signing_key__smime_only_mode__takes_key_of_mixed_protocol_groups() + { + const std::vector groups = { + createGroup("sender-alias@example.net", { + testKey("sender-mixed@example.net", OpenPGP), + testKey("sender-mixed@example.net", CMS), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true, CMS); + resolver.setSender("sender-alias@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); + QCOMPARE(result.solution.protocol, CMS); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-mixed@example.net", CMS).primaryFingerprint()); + } + + void test_groups_for_signing_key__single_protocol_mode__prefers_groups_over_keys() + { + const std::vector groups = { + createGroup("sender-mixed@example.net", { + testKey("sender-openpgp@example.net", OpenPGP), + testKey("sender-smime@example.net", CMS), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true); + resolver.setAllowMixedProtocols(false); + resolver.setSender("sender-mixed@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); + QCOMPARE(result.solution.protocol, OpenPGP); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); + QCOMPARE(result.alternative.signingKeys.size(), 1); + QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(), + testKey("sender-smime@example.net", CMS).primaryFingerprint()); + } + + void test_groups_for_signing_key__single_protocol_mode__prefers_single_protocol_groups() + { + const std::vector groups = { + createGroup("sender-alias@example.net", { + testKey("sender-mixed@example.net", OpenPGP), + testKey("sender-mixed@example.net", CMS), + }), + createGroup("sender-alias@example.net", { + testKey("sender-openpgp@example.net", OpenPGP), + }), + createGroup("sender-alias@example.net", { + testKey("sender-smime@example.net", CMS), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true); + resolver.setAllowMixedProtocols(false); + resolver.setSender("sender-alias@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); + QCOMPARE(result.solution.protocol, OpenPGP); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); + QCOMPARE(result.alternative.signingKeys.size(), 1); + QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(), + testKey("sender-smime@example.net", CMS).primaryFingerprint()); + } + + void test_groups_for_signing_key__mixed_mode__prefers_groups_over_keys() + { + const std::vector groups = { + createGroup("sender-mixed@example.net", { + testKey("sender-openpgp@example.net", OpenPGP), + testKey("sender-smime@example.net", CMS), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true); + resolver.setSender("sender-mixed@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); + QCOMPARE(result.solution.protocol, OpenPGP); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); + } + + void test_groups_for_signing_key__mixed_mode_with_smime_preferred__prefers_groups_over_keys() + { + const std::vector groups = { + createGroup("sender-mixed@example.net", { + testKey("sender-openpgp@example.net", OpenPGP), + testKey("sender-smime@example.net", CMS), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true); + resolver.setPreferredProtocol(CMS); + resolver.setSender("sender-mixed@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); + QCOMPARE(result.solution.protocol, CMS); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-smime@example.net", CMS).primaryFingerprint()); + } + + void test_groups_for_signing_key__mixed_mode__prefers_single_protocol_groups() + { + const std::vector groups = { + createGroup("sender-alias@example.net", { + testKey("sender-mixed@example.net", OpenPGP), + testKey("sender-mixed@example.net", CMS), + }), + createGroup("sender-alias@example.net", { + testKey("sender-openpgp@example.net", OpenPGP), + }), + createGroup("sender-alias@example.net", { + testKey("sender-smime@example.net", CMS), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true); + resolver.setSender("sender-alias@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); + QCOMPARE(result.solution.protocol, OpenPGP); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); + } + + void test_groups_for_signing_key__mixed_mode_with_smime_preferred__prefers_single_protocol_groups() + { + const std::vector groups = { + createGroup("sender-alias@example.net", { + testKey("sender-mixed@example.net", OpenPGP), + testKey("sender-mixed@example.net", CMS), + }), + createGroup("sender-alias@example.net", { + testKey("sender-openpgp@example.net", OpenPGP), + }), + createGroup("sender-alias@example.net", { + testKey("sender-smime@example.net", CMS), + }), + }; + KeyCache::mutableInstance()->setGroups(groups); + KeyResolverCore resolver(/*encrypt=*/ false, /*sign=*/ true); + resolver.setPreferredProtocol(CMS); + resolver.setSender("sender-alias@example.net"); + + const auto result = resolver.resolve(); + + QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); + QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); + QCOMPARE(result.solution.protocol, CMS); + QCOMPARE(result.solution.signingKeys.size(), 1); + QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), + testKey("sender-smime@example.net", CMS).primaryFingerprint()); + } + private: Key testKey(const char *email, Protocol protocol = UnknownProtocol) { const std::vector keys = KeyCache::instance()->findByEMailAddress(email); for (const auto &key: keys) { if (protocol == UnknownProtocol || key.protocol() == protocol) { return key; } } qWarning() << "No" << Formatting::displayName(protocol) << "test key found for" << email; return {}; } private: QSharedPointer mGnupgHome; std::shared_ptr mKeyCache; }; QTEST_MAIN(KeyResolverCoreTest) #include "keyresolvercoretest.moc" diff --git a/src/kleo/keyresolvercore.cpp b/src/kleo/keyresolvercore.cpp index 446332a08..0f39cdfb8 100644 --- a/src/kleo/keyresolvercore.cpp +++ b/src/kleo/keyresolvercore.cpp @@ -1,719 +1,785 @@ /* -*- 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 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 "kleo/keygroup.h" #include "models/keycache.h" #include "utils/formatting.h" #include #include "libkleo_debug.h" using namespace Kleo; using namespace GpgME; namespace { +QDebug operator<<(QDebug debug, const GpgME::Key &key) +{ + if (key.isNull()) { + debug << "Null"; + } else { + debug << Formatting::summaryLine(key); + } + return debug.maybeSpace(); +} + static inline bool ValidEncryptionKey(const Key &key) { if (key.isNull() || key.isRevoked() || key.isExpired() || 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(uid.validity())); } return overallValidity; } static int minimumValidity(const std::vector &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(validity, keyValidity(key, address)); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } bool allKeysHaveProtocol(const std::vector &keys, Protocol protocol) { return std::all_of(keys.cbegin(), keys.cend(), [protocol] (const Key &key) { return key.protocol() == protocol; }); } bool anyKeyHasProtocol(const std::vector &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> &overrides); void resolveOverrides(); std::vector resolveRecipientWithGroup(const QString &address, Protocol protocol); void resolveEncryptionGroups(); + std::vector resolveSenderWithGroup(const QString &address, Protocol protocol); + void resolveSigningGroups(); void resolveSign(Protocol proto); void setSigningKeys(const QStringList &fingerprints); std::vector resolveRecipient(const QString &address, Protocol protocol); void resolveEnc(Protocol proto); void mergeEncryptionKeys(); QStringList unresolvedRecipients(GpgME::Protocol protocol) const; Result resolve(); KeyResolverCore *const q; QString mSender; QStringList mRecipients; QMap> mSigKeys; QMap>> mEncKeys; QMap> 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 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> &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 resolveOverride(const QString &address, Protocol protocol, const QStringList &fingerprints) { std::vector 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)); } } } } +std::vector KeyResolverCore::Private::resolveSenderWithGroup(const QString &address, Protocol protocol) +{ + // prefer single-protocol groups over mixed-protocol groups + auto group = mCache->findGroup(address, protocol, KeyUsage::Sign); + if (group.isNull()) { + group = mCache->findGroup(address, UnknownProtocol, KeyUsage::Sign); + } + if (group.isNull()) { + return {}; + } + + // take the first key matching the protocol + const auto &keys = group.keys(); + const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; }); + if (it == std::end(keys)) { + qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has no" << Formatting::displayName(protocol) << "signing key"; + return {}; + } + const auto key = *it; + if (!isAcceptableSigningKey(key)) { + qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has unacceptable signing key" << key; + return {}; + } + return {key}; +} + +void KeyResolverCore::Private::resolveSigningGroups() +{ + auto &protocolKeysMap = mSigKeys; + if (!protocolKeysMap[UnknownProtocol].empty()) { + // already resolved by common override + return; + } + if (mFormat == OpenPGP) { + if (!protocolKeysMap[OpenPGP].empty()) { + // already resolved by override + return; + } + protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP); + } else if (mFormat == CMS) { + if (!protocolKeysMap[CMS].empty()) { + // already resolved by override + return; + } + protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS); + } else { + protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP); + protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS); + } +} + void KeyResolverCore::Private::resolveSign(Protocol proto) { - if (mSigKeys.contains(proto)) { + if (!mSigKeys[proto].empty()) { // Explicitly set return; } const auto key = mCache->findBestByMailBox(mSender.toUtf8().constData(), proto, KeyUsage::Sign); if (key.isNull()) { qCDebug(LIBKLEO_LOG) << "Failed to find" << Formatting::displayName(proto) << "signing key for" << mSender; return; } if (!isAcceptableSigningKey(key)) { qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint() << "for" << mSender; return; } mSigKeys.insert(proto, {key}); } 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; } mSigKeys[key.protocol()].push_back(key); } } } std::vector KeyResolverCore::Private::resolveRecipientWithGroup(const QString &address, Protocol protocol) { const auto group = mCache->findGroup(address, protocol, KeyUsage::Encrypt); if (group.isNull()) { return {}; } // 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. const auto &keys = group.keys(); const bool allKeysAreAcceptable = std::all_of(std::begin(keys), std::end(keys), [this] (const auto &key) { return isAcceptableEncryptionKey(key); }); if (!allKeysAreAcceptable) { qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has at least one unacceptable key"; return {}; } for (const auto &k: keys) { qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << k.primaryFingerprint(); } std::vector result; std::copy(std::begin(keys), std::end(keys), std::back_inserter(result)); return result; } void KeyResolverCore::Private::resolveEncryptionGroups() { for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) { const QString &address = it.key(); auto &protocolKeysMap = it.value(); if (!protocolKeysMap[UnknownProtocol].empty()) { // already resolved by common override continue; } if (mFormat == OpenPGP) { if (!protocolKeysMap[OpenPGP].empty()) { // already resolved by override continue; } protocolKeysMap[OpenPGP] = resolveRecipientWithGroup(address, OpenPGP); } else if (mFormat == CMS) { if (!protocolKeysMap[CMS].empty()) { // already resolved by override continue; } protocolKeysMap[CMS] = resolveRecipientWithGroup(address, CMS); } else { // prefer single-protocol groups over mixed-protocol groups const auto openPGPGroupKeys = resolveRecipientWithGroup(address, OpenPGP); const auto smimeGroupKeys = resolveRecipientWithGroup(address, CMS); if (!openPGPGroupKeys.empty() && !smimeGroupKeys.empty()) { protocolKeysMap[OpenPGP] = openPGPGroupKeys; protocolKeysMap[CMS] = smimeGroupKeys; } else if (openPGPGroupKeys.empty() && smimeGroupKeys.empty()) { // no single-protocol groups found; // if mixed protocols are allowed, then look for any group with encryption keys if (mAllowMixed) { protocolKeysMap[UnknownProtocol] = resolveRecipientWithGroup(address, UnknownProtocol); } } else { // there is a single-protocol group only for one protocol; use this group for all protocols protocolKeysMap[UnknownProtocol] = !openPGPGroupKeys.empty() ? openPGPGroupKeys : smimeGroupKeys; } } } } std::vector KeyResolverCore::Private::resolveRecipient(const QString &address, Protocol protocol) { const auto key = mCache->findBestByMailBox(address.toUtf8().constData(), protocol, KeyUsage::Encrypt); if (key.isNull()) { qCDebug(LIBKLEO_LOG) << "Failed to find any" << Formatting::displayName(protocol) << "key for:" << address; return {}; } if (!isAcceptableEncryptionKey(key, address)) { qCDebug(LIBKLEO_LOG) << "key for:" << address << key.primaryFingerprint() << "has not enough validity"; return {}; } qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << key.primaryFingerprint(); return {key}; } // Try to find matching keys in the provided protocol for the unresolved addresses 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 or group) continue; } const std::vector &commonOverrideOrGroup = protocolKeysMap[UnknownProtocol]; if (!commonOverrideOrGroup.empty()) { // there is a common override or group; use it for current protocol if possible if (allKeysHaveProtocol(commonOverrideOrGroup, proto)) { protocolKeysMap[proto] = commonOverrideOrGroup; continue; } else { qCDebug(LIBKLEO_LOG) << "Common override/group for" << address << "is unusable for" << Formatting::displayName(proto); continue; } } protocolKeysMap[proto] = resolveRecipient(address, proto); } } auto getBestEncryptionKeys(const QMap>> &encryptionKeys, Protocol preferredProtocol) { QMap> result; for (auto it = encryptionKeys.begin(); it != encryptionKeys.end(); ++it) { const QString &address = it.key(); auto &protocolKeysMap = it.value(); const std::vector &overrideKeys = protocolKeysMap[UnknownProtocol]; if (!overrideKeys.empty()) { result.insert(address, overrideKeys); continue; } const std::vector &keysOpenPGP = protocolKeysMap[OpenPGP]; const std::vector &keysCMS = protocolKeysMap[CMS]; if (keysOpenPGP.empty() && keysCMS.empty()) { result.insert(address, {}); } else if (!keysOpenPGP.empty() && keysCMS.empty()) { result.insert(address, keysOpenPGP); } else if (keysOpenPGP.empty() && !keysCMS.empty()) { 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 ((validityCMS > validityPGP) || (validityCMS == validityPGP && preferredProtocol == CMS)) { result.insert(address, keysCMS); } else { 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>> &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>> &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>> &encryptionKeys, Protocol protocol) { QMap> 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; } template auto concatenate(std::vector v1, const std::vector &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 {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 {Error, {}, {}}; } // Next look for matching groups of keys + if (mSign) { + resolveSigningGroups(); + } if (mEncrypt) { resolveEncryptionGroups(); } // Then look for signing / encryption keys if (mFormat == OpenPGP || mFormat == UnknownProtocol) { resolveSign(OpenPGP); resolveEnc(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 == CMS || mFormat == UnknownProtocol) { resolveSign(CMS); resolveEnc(CMS); } const bool cmsOnly = (!mEncrypt || !hasUnresolvedRecipients(mEncKeys, CMS)) && (!mSign || mSigKeys.contains(CMS)); if (mFormat == CMS) { return { SolutionFlags((cmsOnly ? AllResolved : SomeUnresolved) | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {} }; } // 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 { return { SolutionFlags(AllResolved | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {} }; } } if (pgpOnly) { if (!mAllowMixed) { return { SolutionFlags(AllResolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)} }; } else { return { SolutionFlags(AllResolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, 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)} }; } } 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}, {} }; } const bool allKeysAreOpenPGP = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [] (const auto &keys) { return allKeysHaveProtocol(keys, OpenPGP); }); if (allKeysAreOpenPGP) { return { SolutionFlags(SomeUnresolved | OpenPGPOnly), {OpenPGP, 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) { return { SolutionFlags(SomeUnresolved | CMSOnly), {CMS, 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> &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; } KeyResolverCore::Result KeyResolverCore::resolve() { return d->resolve(); } diff --git a/src/models/keycache.h b/src/models/keycache.h index 2bf6c792c..d10039082 100644 --- a/src/models/keycache.h +++ b/src/models/keycache.h @@ -1,194 +1,203 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/keycache.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include #include #include #include namespace GpgME { class Key; class DecryptionResult; class VerificationResult; class KeyListResult; class Subkey; } namespace KMime { namespace Types { class Mailbox; } } namespace Kleo { class FileSystemWatcher; class KeyGroup; enum class KeyUsage : char; class KLEO_EXPORT KeyCache : public QObject { Q_OBJECT protected: explicit KeyCache(); public: static std::shared_ptr instance(); static std::shared_ptr mutableInstance(); ~KeyCache(); void setGroupsEnabled(bool enabled); void setGroupsConfig(const QString &filename); void insert(const GpgME::Key &key); void insert(const std::vector &keys); bool insert(const KeyGroup &group); void refresh(const std::vector &keys); bool update(const KeyGroup &group); void remove(const GpgME::Key &key); void remove(const std::vector &keys); bool remove(const KeyGroup &group); void addFileSystemWatcher(const std::shared_ptr &watcher); void enableFileSystemWatcher(bool enable); void setRefreshInterval(int hours); int refreshInterval() const; void enableRemarks(bool enable); bool remarksEnabled() const; const std::vector &keys() const; std::vector secretKeys() const; std::vector groups() const; std::vector configurableGroups() const; void saveConfigurableGroups(const std::vector &groups); const GpgME::Key &findByFingerprint(const char *fpr) const; const GpgME::Key &findByFingerprint(const std::string &fpr) const; std::vector findByEMailAddress(const char *email) const; std::vector findByEMailAddress(const std::string &email) const; /** Look through the cache and search for the best key for a mailbox. * * The best key is the key with a UID for the provided mailbox that * has the highest validity and a subkey that is capable for the given * usage. * If more then one key have a UID with the same validity * the most recently created key is taken. * * @returns the "best" key for the mailbox. */ GpgME::Key findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const; /** * Looks for a group named @a name which contains keys with protocol @a protocol * that are suitable for the usage @a usage. * + * If @a protocol is GpgME::OpenPGP or GpgME::CMS, then only groups consisting of keys + * matching this protocol are considered. Use @a protocol GpgME::UnknownProtocol to consider + * any groups regardless of the protocol including mixed-protocol groups. + * + * If @a usage is not KeyUsage::AnyUsage, then only groups consisting of keys supporting this usage + * are considered. + * The validity of keys and the presence of a private key (necessary for signing, certification, and + * authentication) is not taken into account. + * * The first group that fulfills all conditions is returned. * * @returns a matching group or a null group if no matching group is found. */ KeyGroup findGroup(const QString &name, GpgME::Protocol protocol, KeyUsage usage) const; const GpgME::Key &findByShortKeyID(const char *id) const; const GpgME::Key &findByShortKeyID(const std::string &id) const; const GpgME::Key &findByKeyIDOrFingerprint(const char *id) const; const GpgME::Key &findByKeyIDOrFingerprint(const std::string &id) const; std::vector findByKeyIDOrFingerprint(const std::vector &ids) const; const GpgME::Subkey &findSubkeyByKeyGrip(const char *grip, GpgME::Protocol protocol = GpgME::UnknownProtocol) const; const GpgME::Subkey &findSubkeyByKeyGrip(const std::string &grip, GpgME::Protocol protocol = GpgME::UnknownProtocol) const; std::vector findSubkeysByKeyID(const std::vector &ids) const; std::vector findRecipients(const GpgME::DecryptionResult &result) const; std::vector findSigners(const GpgME::VerificationResult &result) const; std::vector findSigningKeysByMailbox(const QString &mb) const; std::vector findEncryptionKeysByMailbox(const QString &mb) const; /** Check for group keys. * * @returns A list of keys configured for groupName. Empty if no group cached.*/ std::vector getGroupKeys(const QString &groupName) const; enum Option { NoOption = 0, RecursiveSearch = 1, IncludeSubject = 2 }; Q_DECLARE_FLAGS(Options, Option) std::vector findSubjects(const GpgME::Key &key, Options option = RecursiveSearch) const; std::vector findSubjects(const std::vector &keys, Options options = RecursiveSearch) const; std::vector findSubjects(std::vector::const_iterator first, std::vector::const_iterator last, Options options = RecursiveSearch) const; std::vector findIssuers(const GpgME::Key &key, Options options = RecursiveSearch) const; std::vector findIssuers(const std::vector &keys, Options options = RecursiveSearch) const; std::vector findIssuers(std::vector::const_iterator first, std::vector::const_iterator last, Options options = RecursiveSearch) const; /** Check if at least one keylisting was finished. */ bool initialized() const; /** Check if all keys have OpenPGP Protocol. */ bool pgpOnly() const; /** Set the keys the cache shall contain. Marks cache as initialized. Use for tests only. */ void setKeys(const std::vector &keys); void setGroups(const std::vector &groups); public Q_SLOTS: void clear(); void startKeyListing(GpgME::Protocol proto = GpgME::UnknownProtocol) { reload(proto); } void reload(GpgME::Protocol proto = GpgME::UnknownProtocol); void cancelKeyListing(); Q_SIGNALS: //void changed( const GpgME::Key & key ); void aboutToRemove(const GpgME::Key &key); void added(const GpgME::Key &key); void keyListingDone(const GpgME::KeyListResult &result); void keysMayHaveChanged(); void groupAdded(const KeyGroup &group); void groupUpdated(const KeyGroup &group); void groupRemoved(const KeyGroup &group); private: class RefreshKeysJob; class Private; QScopedPointer const d; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(Kleo::KeyCache::Options)