Page MenuHome GnuPG

No OneTemporary

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

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

Event Timeline