diff --git a/autotests/newkeyapprovaldialogtest.cpp b/autotests/newkeyapprovaldialogtest.cpp index 5c9465180..8f97f3e21 100644 --- a/autotests/newkeyapprovaldialogtest.cpp +++ b/autotests/newkeyapprovaldialogtest.cpp @@ -1,1186 +1,1181 @@ /* autotests/newkeyapprovaldialogtest.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; namespace QTest { template <> inline char *toString(const bool &t) { return t ? qstrdup("true") : qstrdup("false"); } template <> inline bool qCompare(bool const &t1, bool const &t2, const char *actual, const char *expected, const char *file, int line) { return compare_helper(t1 == t2, "Compared values are not the same", toString(t1), toString(t2), actual, expected, file, line); } template <> inline char *toString(const GpgME::Protocol &t) { return qstrdup(Formatting::displayName(t).toLocal8Bit().constData()); } template <> inline bool qCompare(GpgME::Protocol const &t1, GpgME::Protocol const &t2, const char *actual, const char *expected, const char *file, int line) { return compare_helper(t1 == t2, "Compared values are not the same", toString(t1), toString(t2), actual, expected, file, line); } } namespace { // copied from NewKeyApprovalDialog::Private enum Action { Unset, GenerateKey, IgnoreKey, }; -enum class KeyUsage { - AnyUsage, - Sign, - Encrypt, -}; auto mapValidity(GpgME::UserID::Validity validity) { switch (validity) { default: case GpgME::UserID::Unknown: return GPGME_VALIDITY_UNKNOWN; case GpgME::UserID::Undefined: return GPGME_VALIDITY_UNDEFINED; case GpgME::UserID::Never: return GPGME_VALIDITY_NEVER; case GpgME::UserID::Marginal: return GPGME_VALIDITY_MARGINAL; case GpgME::UserID::Full: return GPGME_VALIDITY_FULL; case GpgME::UserID::Ultimate: return GPGME_VALIDITY_ULTIMATE; } } GpgME::Key createTestKey(const char *uid, GpgME::Protocol protocol = GpgME::UnknownProtocol, KeyUsage usage = KeyUsage::AnyUsage, GpgME::UserID::Validity validity = GpgME::UserID::Full) { static int count = 0; count++; gpgme_key_t key; gpgme_key_from_uid(&key, uid); Q_ASSERT(key); Q_ASSERT(key->uids); 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()); key->revoked = 0; key->expired = 0; key->disabled = 0; key->can_encrypt = int(usage == KeyUsage::AnyUsage || usage == KeyUsage::Encrypt); key->can_sign = int(usage == KeyUsage::AnyUsage || usage == KeyUsage::Sign); key->secret = 1; key->uids->validity = mapValidity(validity); return GpgME::Key(key, false); } auto testKey(const char *address, GpgME::Protocol protocol = GpgME::UnknownProtocol) { const auto email = GpgME::UserID::addrSpecFromString(address); const auto keys = KeyCache::instance()->findByEMailAddress(email); for (const auto &key: keys) { if (protocol == GpgME::UnknownProtocol || key.protocol() == protocol) { return key; } } return GpgME::Key(); } void waitForKeySelectionCombosBeingInitialized(const QDialog *dialog) { QVERIFY(dialog); auto combo = dialog->findChild(); QVERIFY(combo); const auto spy = std::make_unique(combo, &KeySelectionCombo::keyListingFinished); QVERIFY(spy->isValid()); QVERIFY(spy->wait(10)); } template struct Widgets { std::vector visible; std::vector hidden; }; template Widgets visibleAndHiddenWidgets(const QList &widgets) { Widgets 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; } enum Visibility { IsHidden, IsVisible }; enum CheckedState { IsUnchecked, IsChecked }; template void verifyProtocolButton(const T *button, Visibility expectedVisibility, CheckedState expectedCheckedState) { QVERIFY(button); QCOMPARE(button->isVisible(), expectedVisibility == IsVisible); QCOMPARE(button->isChecked(), expectedCheckedState == IsChecked); } template void verifyWidgetVisibility(const T *widget, Visibility expectedVisibility) { QVERIFY(widget); QCOMPARE(widget->isVisible(), expectedVisibility == IsVisible); } template void verifyWidgetsVisibility(const QList &widgets, Visibility expectedVisibility) { for (auto w: widgets) { verifyWidgetVisibility(w, expectedVisibility); } } void verifyProtocolLabels(const QList &labels, int expectedNumber, Visibility expectedVisibility) { QCOMPARE(labels.size(), expectedNumber); verifyWidgetsVisibility(labels, expectedVisibility); } bool listsOfKeysAreEqual(const std::vector &l1, const std::vector &l2) { return std::equal(std::begin(l1), std::end(l1), std::begin(l2), std::end(l2), ByFingerprint()); } void verifySolution(const KeyResolver::Solution &actual, const KeyResolver::Solution &expected) { QCOMPARE(actual.protocol, expected.protocol); QVERIFY(listsOfKeysAreEqual(actual.signingKeys, expected.signingKeys)); QVERIFY(std::equal(actual.encryptionKeys.constKeyValueBegin(), actual.encryptionKeys.constKeyValueEnd(), expected.encryptionKeys.constKeyValueBegin(), expected.encryptionKeys.constKeyValueEnd(), [] (const auto& kv1, const auto& kv2) { return kv1.first == kv2.first && listsOfKeysAreEqual(kv1.second, kv2.second); })); } void switchKeySelectionCombosFromGenerateKeyToIgnoreKey(const QList &combos) { for (auto combo: combos) { if (combo->currentData(Qt::UserRole).toInt() == GenerateKey) { const auto ignoreIndex = combo->findData(IgnoreKey); QVERIFY(ignoreIndex != -1); combo->setCurrentIndex(ignoreIndex); } } } } class NewKeyApprovalDialogTest: public QObject { Q_OBJECT private Q_SLOTS: void init() { // hold a reference to the key cache to avoid rebuilding while the test is running mKeyCache = KeyCache::instance(); KeyCache::mutableInstance()->setKeys({ createTestKey("sender@example.net", GpgME::OpenPGP, KeyUsage::AnyUsage), createTestKey("sender@example.net", GpgME::CMS, KeyUsage::AnyUsage), createTestKey("Full Trust ", GpgME::OpenPGP, KeyUsage::Encrypt), createTestKey("Trusted S/MIME ", GpgME::CMS, KeyUsage::Encrypt), createTestKey("Marginal Validity ", GpgME::OpenPGP, KeyUsage::Encrypt, GpgME::UserID::Marginal), }); } void cleanup() { // verify that nobody else holds a reference to the key cache QVERIFY(mKeyCache.use_count() == 1); mKeyCache.reset(); } void test__verify_test_keys() { QVERIFY(!testKey("sender@example.net", GpgME::OpenPGP).isNull()); QVERIFY(!testKey("sender@example.net", GpgME::CMS).isNull()); QVERIFY(!testKey("Full Trust ", GpgME::OpenPGP).isNull()); QVERIFY(!testKey("Trusted S/MIME ", GpgME::CMS).isNull()); QVERIFY(!testKey("Marginal Validity ", GpgME::OpenPGP).isNull()); } void test__both_protocols_allowed__mixed_not_allowed__openpgp_preferred() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = false; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::OpenPGP, {testKey("sender@example.net", GpgME::OpenPGP)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP)}} } }; const KeyResolver::Solution alternativeSolution = { GpgME::CMS, {testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::CMS)}} } }; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild("openpgp button"), IsVisible, IsChecked); verifyProtocolButton(dialog->findChild("smime button"), IsVisible, IsUnchecked); const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(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(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__both_protocols_allowed__mixed_not_allowed__smime_preferred() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = false; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::CMS, {testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = { GpgME::OpenPGP, {testKey("sender@example.net", GpgME::OpenPGP)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP)}} } }; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild("openpgp button"), IsVisible, IsUnchecked); verifyProtocolButton(dialog->findChild("smime button"), IsVisible, IsChecked); const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(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(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__openpgp_only() { const GpgME::Protocol forcedProtocol = GpgME::OpenPGP; const bool allowMixed = false; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::OpenPGP, {testKey("sender@example.net", GpgME::OpenPGP)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP)}} } }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild("openpgp button"), IsHidden, IsChecked); verifyProtocolButton(dialog->findChild("smime button"), IsHidden, IsUnchecked); const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(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(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, {testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild("openpgp button"), IsHidden, IsUnchecked); verifyProtocolButton(dialog->findChild("smime button"), IsHidden, IsChecked); const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(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(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, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("unknown@example.net"), {}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild("openpgp button"), IsVisible, IsChecked); verifyProtocolButton(dialog->findChild("smime button"), IsVisible, IsChecked); verifyProtocolLabels(dialog->findChildren("protocol label"), 4, IsVisible); const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(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(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__openpgp_only_preferred_solution() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::OpenPGP, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("unknown@example.net"), {}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild("openpgp button"), IsVisible, IsChecked); verifyProtocolButton(dialog->findChild("smime button"), IsVisible, IsUnchecked); verifyProtocolLabels(dialog->findChildren("protocol label"), 4, IsHidden); const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(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), preferredSolution.signingKeys[1].primaryFingerprint()); const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(QStringLiteral("encryption key"))); QCOMPARE(encryptionKeyWidgets.visible.size(), 3); QCOMPARE(encryptionKeyWidgets.hidden.size(), 1); // 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.hidden[0]->property("address").toString(), sender); QCOMPARE(encryptionKeyWidgets.hidden[0]->defaultKey(GpgME::CMS), preferredSolution.encryptionKeys.value(sender)[1].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::UnknownProtocol), preferredSolution.encryptionKeys.value("prefer-openpgp@example.net")[0].primaryFingerprint()); QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "unknown@example.net"); QVERIFY(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::UnknownProtocol).isEmpty()); } void test__both_protocols_allowed__mixed_allowed__smime_only_preferred_solution() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::CMS, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("unknown@example.net"), {}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild("openpgp button"), IsVisible, IsUnchecked); verifyProtocolButton(dialog->findChild("smime button"), IsVisible, IsChecked); verifyProtocolLabels(dialog->findChildren("protocol label"), 4, IsHidden); const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(QStringLiteral("signing key"))); QCOMPARE(signingKeyWidgets.visible.size(), 1); QCOMPARE(signingKeyWidgets.hidden.size(), 1); QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::CMS), preferredSolution.signingKeys[1].primaryFingerprint()); QCOMPARE(signingKeyWidgets.hidden[0]->defaultKey(GpgME::OpenPGP), preferredSolution.signingKeys[0].primaryFingerprint()); const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(QStringLiteral("encryption key"))); QCOMPARE(encryptionKeyWidgets.visible.size(), 3); QCOMPARE(encryptionKeyWidgets.hidden.size(), 1); // encryption key widgets for sender come first QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender); QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::CMS), preferredSolution.encryptionKeys.value(sender)[1].primaryFingerprint()); QCOMPARE(encryptionKeyWidgets.hidden[0]->property("address").toString(), sender); QCOMPARE(encryptionKeyWidgets.hidden[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-smime@example.net"); QCOMPARE(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::UnknownProtocol), preferredSolution.encryptionKeys.value("prefer-smime@example.net")[0].primaryFingerprint()); QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "unknown@example.net"); QVERIFY(encryptionKeyWidgets.visible[2]->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"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("unknown@example.net"), {}}, {QStringLiteral("sender@example.net"), {}} } }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(QStringLiteral("signing key"))); QCOMPARE(signingKeyWidgets.visible.size(), 2); QCOMPARE(signingKeyWidgets.hidden.size(), 0); const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(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, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("unknown@example.net"), {}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, false, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(QStringLiteral("signing key"))); QCOMPARE(signingKeyWidgets.visible.size(), 0); QCOMPARE(signingKeyWidgets.hidden.size(), 0); const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(QStringLiteral("encryption key"))); QCOMPARE(encryptionKeyWidgets.visible.size(), 5); QCOMPARE(encryptionKeyWidgets.hidden.size(), 0); } void test__ok_button_shows_generate_if_generate_is_selected() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::OpenPGP, {}, // no signing keys to get "Generate key" choice in OpenPGP combo {{QStringLiteral("sender@example.net"), {}}} // no encryption keys to get "Generate key" choice in OpenPGP combo }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto okButton = dialog->findChild("ok button"); QVERIFY(okButton); QVERIFY(okButton->text() != "Generate"); { // get the first signing key combo which is the OpenPGP one const auto signingKeyCombo = dialog->findChild("signing key"); verifyWidgetVisibility(signingKeyCombo, IsVisible); const auto originalIndex = signingKeyCombo->currentIndex(); const auto generateIndex = signingKeyCombo->findData(GenerateKey); QVERIFY(generateIndex != -1); signingKeyCombo->setCurrentIndex(generateIndex); QCOMPARE(okButton->text(), "Generate"); signingKeyCombo->setCurrentIndex(originalIndex); QVERIFY(okButton->text() != "Generate"); } { // get the first encryption key combo which is the OpenPGP one for the sender const auto encryptionKeyCombo = dialog->findChild("encryption key"); verifyWidgetVisibility(encryptionKeyCombo, IsVisible); const auto originalIndex = encryptionKeyCombo->currentIndex(); const auto generateIndex = encryptionKeyCombo->findData(GenerateKey); QVERIFY(generateIndex != -1); encryptionKeyCombo->setCurrentIndex(generateIndex); QCOMPARE(okButton->text(), "Generate"); encryptionKeyCombo->setCurrentIndex(originalIndex); QVERIFY(okButton->text() != "Generate"); } } void test__ok_button_does_not_show_generate_if_generate_is_selected_in_hidden_combos() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::CMS, // enables S/MIME as default protocol, hides OpenPGP combos {}, // no signing keys to get "Generate key" choice in OpenPGP combo {{QStringLiteral("sender@example.net"), {}}} // no encryption keys to get "Generate key" choice in OpenPGP combo }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto okButton = dialog->findChild("ok button"); QVERIFY(okButton); QVERIFY(okButton->text() != "Generate"); { // get the first signing key combo which is the OpenPGP one const auto signingKeyCombo = dialog->findChild("signing key"); verifyWidgetVisibility(signingKeyCombo, IsHidden); const auto originalIndex = signingKeyCombo->currentIndex(); const auto generateIndex = signingKeyCombo->findData(GenerateKey); QVERIFY(generateIndex != -1); signingKeyCombo->setCurrentIndex(generateIndex); QVERIFY(okButton->text() != "Generate"); signingKeyCombo->setCurrentIndex(originalIndex); QVERIFY(okButton->text() != "Generate"); } { // get the first encryption key combo which is the OpenPGP one for the sender const auto encryptionKeyCombo = dialog->findChild("encryption key"); verifyWidgetVisibility(encryptionKeyCombo, IsHidden); const auto originalIndex = encryptionKeyCombo->currentIndex(); const auto generateIndex = encryptionKeyCombo->findData(GenerateKey); QVERIFY(generateIndex != -1); encryptionKeyCombo->setCurrentIndex(generateIndex); QVERIFY(okButton->text() != "Generate"); encryptionKeyCombo->setCurrentIndex(originalIndex); QVERIFY(okButton->text() != "Generate"); } } void test__ok_button_is_disabled_if_ignore_is_selected_in_all_visible_encryption_combos() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::OpenPGP, {}, // no signing keys to get "Generate key" choice in OpenPGP combo {{QStringLiteral("sender@example.net"), {}}} // no encryption keys to get "Generate key" choice in OpenPGP combo }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto okButton = dialog->findChild("ok button"); QVERIFY(okButton); QVERIFY(okButton->isEnabled()); const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren(QStringLiteral("encryption key"))); for (auto combo: encryptionKeyWidgets.visible) { const auto ignoreIndex = combo->findData(IgnoreKey); QVERIFY(ignoreIndex != -1); combo->setCurrentIndex(ignoreIndex); } QVERIFY(!okButton->isEnabled()); } void test__vs_de_compliance__all_keys_fully_valid() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::UnknownProtocol, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = {}; Tests::FakeCryptoConfigStringValue fakeCompliance{"gpg", "compliance", QStringLiteral("de-vs")}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto complianceLabel = dialog->findChild("compliance label"); verifyWidgetVisibility(complianceLabel, IsVisible); QVERIFY(!complianceLabel->text().contains(" not ")); } void test__vs_de_compliance__not_all_keys_fully_valid() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::UnknownProtocol, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("marginal-openpgp@example.net"), {testKey("Marginal Validity ", GpgME::OpenPGP)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = {}; Tests::FakeCryptoConfigStringValue fakeCompliance{"gpg", "compliance", QStringLiteral("de-vs")}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto complianceLabel = dialog->findChild("compliance label"); verifyWidgetVisibility(complianceLabel, IsVisible); QVERIFY(complianceLabel->text().contains(" not ")); } void test__vs_de_compliance__null_keys_are_ignored() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::UnknownProtocol, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("unknown@example.net"), {}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = {}; Tests::FakeCryptoConfigStringValue fakeCompliance{"gpg", "compliance", QStringLiteral("de-vs")}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto complianceLabel = dialog->findChild("compliance label"); verifyWidgetVisibility(complianceLabel, IsVisible); QVERIFY(!complianceLabel->text().contains(" not ")); } void test__result_does_not_include_null_keys() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("unknown@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::UnknownProtocol, {}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("unknown@example.net"), {}} } }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren()); const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted}; QVERIFY(dialogAcceptedSpy.isValid()); const auto okButton = dialog->findChild("ok button"); QVERIFY(okButton); QVERIFY(okButton->isEnabled()); okButton->click(); QCOMPARE(dialogAcceptedSpy.count(), 1); verifySolution(dialog->result(), { GpgME::UnknownProtocol, {}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, } }); } void test__result_has_keys_for_both_protocols_if_both_are_needed() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::UnknownProtocol, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren()); const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted}; QVERIFY(dialogAcceptedSpy.isValid()); const auto okButton = dialog->findChild("ok button"); QVERIFY(okButton); QVERIFY(okButton->isEnabled()); okButton->click(); QCOMPARE(dialogAcceptedSpy.count(), 1); verifySolution(dialog->result(), preferredSolution); } void test__result_has_only_openpgp_keys_if_openpgp_protocol_selected() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::UnknownProtocol, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren()); const auto smimeButton = dialog->findChild("smime button"); QVERIFY(smimeButton); smimeButton->click(); QVERIFY(!smimeButton->isChecked()); const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted}; QVERIFY(dialogAcceptedSpy.isValid()); const auto okButton = dialog->findChild("ok button"); QVERIFY(okButton); QVERIFY(okButton->isEnabled()); okButton->click(); QCOMPARE(dialogAcceptedSpy.count(), 1); verifySolution(dialog->result(), { GpgME::OpenPGP, {testKey("sender@example.net", GpgME::OpenPGP)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP)}} } }); } void test__result_has_only_smime_keys_if_smime_protocol_selected() { const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; const bool allowMixed = true; const QString sender = QStringLiteral("sender@example.net"); const KeyResolver::Solution preferredSolution = { GpgME::UnknownProtocol, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}} } }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren()); const auto openPGPButton = dialog->findChild("openpgp button"); QVERIFY(openPGPButton); openPGPButton->click(); QVERIFY(!openPGPButton->isChecked()); const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted}; QVERIFY(dialogAcceptedSpy.isValid()); const auto okButton = dialog->findChild("ok button"); QVERIFY(okButton); QVERIFY(okButton->isEnabled()); okButton->click(); QCOMPARE(dialogAcceptedSpy.count(), 1); verifySolution(dialog->result(), { GpgME::CMS, {testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::CMS)}} } }); } private: std::shared_ptr mKeyCache; }; QTEST_MAIN(NewKeyApprovalDialogTest) #include "newkeyapprovaldialogtest.moc" diff --git a/src/kleo/enum.h b/src/kleo/enum.h index 30d40078b..2653c5c5f 100644 --- a/src/kleo/enum.h +++ b/src/kleo/enum.h @@ -1,87 +1,95 @@ /* kleo/enum.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" class QString; #include namespace GpgME { class Key; class UserID; } namespace Kleo { +enum class KeyUsage : char { + AnyUsage, + Sign, + Encrypt, + Certify, + Authenticate, +}; + enum CryptoMessageFormat { InlineOpenPGPFormat = 1, OpenPGPMIMEFormat = 2, SMIMEFormat = 4, SMIMEOpaqueFormat = 8, AnyOpenPGP = InlineOpenPGPFormat | OpenPGPMIMEFormat, AnySMIME = SMIMEOpaqueFormat | SMIMEFormat, AutoFormat = AnyOpenPGP | AnySMIME }; KLEO_EXPORT QString cryptoMessageFormatToLabel(CryptoMessageFormat f); KLEO_EXPORT const char *cryptoMessageFormatToString(CryptoMessageFormat f); KLEO_EXPORT QStringList cryptoMessageFormatsToStringList(unsigned int f); KLEO_EXPORT CryptoMessageFormat stringToCryptoMessageFormat(const QString &s); KLEO_EXPORT unsigned int stringListToCryptoMessageFormats(const QStringList &sl); enum Action { Conflict, DoIt, DontDoIt, Ask, AskOpportunistic, Impossible }; enum EncryptionPreference { UnknownPreference = 0, NeverEncrypt = 1, AlwaysEncrypt = 2, AlwaysEncryptIfPossible = 3, AlwaysAskForEncryption = 4, AskWheneverPossible = 5, MaxEncryptionPreference = AskWheneverPossible }; KLEO_EXPORT QString encryptionPreferenceToLabel(EncryptionPreference pref); KLEO_EXPORT const char *encryptionPreferenceToString(EncryptionPreference pref); KLEO_EXPORT EncryptionPreference stringToEncryptionPreference(const QString &str); enum SigningPreference { UnknownSigningPreference = 0, NeverSign = 1, AlwaysSign = 2, AlwaysSignIfPossible = 3, AlwaysAskForSigning = 4, AskSigningWheneverPossible = 5, MaxSigningPreference = AskSigningWheneverPossible }; KLEO_EXPORT QString signingPreferenceToLabel(SigningPreference pref); KLEO_EXPORT const char *signingPreferenceToString(SigningPreference pref); KLEO_EXPORT SigningPreference stringToSigningPreference(const QString &str); enum TrustLevel { Level0, Level1, Level2, Level3, Level4 }; KLEO_EXPORT TrustLevel trustLevel(const GpgME::Key &key); KLEO_EXPORT TrustLevel trustLevel(const GpgME::UserID &uid); } diff --git a/src/kleo/keyresolvercore.cpp b/src/kleo/keyresolvercore.cpp index 87fcf84dd..c646ccb2a 100644 --- a/src/kleo/keyresolvercore.cpp +++ b/src/kleo/keyresolvercore.cpp @@ -1,672 +1,671 @@ /* -*- c++ -*- kleo/keyresolvercore.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker Based on kpgp.cpp SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #include "keyresolvercore.h" #include "models/keycache.h" #include "utils/formatting.h" #include #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(uid.validity())); } return overallValidity; } static int minimumValidity(const std::vector &keys, const QString &address) { const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [address] (int validity, const Key &key) { return std::min(validity, keyValidity(key, address)); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } bool allKeysHaveProtocol(const std::vector &keys, Protocol protocol) { return std::all_of(keys.cbegin(), keys.cend(), [protocol] (const Key &key) { return key.protocol() == protocol; }); } bool anyKeyHasProtocol(const std::vector &keys, Protocol protocol) { return std::any_of(std::begin(keys), std::end(keys), [protocol] (const Key &key) { return key.protocol() == protocol; }); } } // namespace class KeyResolverCore::Private { public: Private(KeyResolverCore* qq, bool enc, bool sig, Protocol fmt) : q(qq) , mFormat(fmt) , mEncrypt(enc) , mSign(sig) , mCache(KeyCache::instance()) , mPreferredProtocol(UnknownProtocol) , mMinimumValidity(UserID::Marginal) , mCompliance(Formatting::complianceMode()) { } ~Private() = default; bool isAcceptableSigningKey(const Key &key); bool isAcceptableEncryptionKey(const Key &key, const QString &address = QString()); void setSender(const QString &address); void addRecipients(const QStringList &addresses); void setOverrideKeys(const QMap> &overrides); void resolveOverrides(); void resolveSign(Protocol proto); void setSigningKeys(const QStringList &fingerprints); std::vector resolveRecipient(const QString &address, Protocol protocol); void resolveEnc(Protocol proto); void mergeEncryptionKeys(); QStringList unresolvedRecipients(GpgME::Protocol protocol) const; Result resolve(); KeyResolverCore *const q; QString mSender; QStringList mRecipients; QMap> mSigKeys; QMap>> mEncKeys; QMap> mOverrides; Protocol mFormat; QStringList mFatalErrors; bool mEncrypt; bool mSign; // The cache is needed as a member variable to avoid rebuilding // it between calls if we are the only user. std::shared_ptr mCache; bool mAllowMixed = true; Protocol mPreferredProtocol; int mMinimumValidity; QString mCompliance; }; bool KeyResolverCore::Private::isAcceptableSigningKey(const Key &key) { if (!ValidSigningKey(key)) { return false; } if (mCompliance == QLatin1String("de-vs")) { if (!Formatting::isKeyDeVs(key)) { qCDebug(LIBKLEO_LOG) << "Rejected sig key" << key.primaryFingerprint() << "because it is not de-vs compliant."; return false; } } return true; } bool KeyResolverCore::Private::isAcceptableEncryptionKey(const Key &key, const QString &address) { if (!ValidEncryptionKey(key)) { return false; } if (mCompliance == QLatin1String("de-vs")) { if (!Formatting::isKeyDeVs(key)) { qCDebug(LIBKLEO_LOG) << "Rejected enc key" << key.primaryFingerprint() << "because it is not de-vs compliant."; return false; } } if (address.isEmpty()) { return true; } for (const auto &uid: key.userIDs()) { if (uid.addrSpec() == address.toStdString()) { if (uid.validity() >= mMinimumValidity) { return true; } } } return false; } void KeyResolverCore::Private::setSender(const QString &address) { const auto normalized = UserID::addrSpecFromString (address.toUtf8().constData()); if (normalized.empty()) { // should not happen bug in the caller, non localized // error for bug reporting. mFatalErrors << QStringLiteral("The sender address '%1' could not be extracted").arg(address); return; } const auto normStr = QString::fromUtf8(normalized.c_str()); if (mSign) { mSender = normStr; } addRecipients({address}); } void KeyResolverCore::Private::addRecipients(const QStringList &addresses) { if (!mEncrypt) { return; } // Internally we work with normalized addresses. Normalization // matches the gnupg one. for (const auto &addr: addresses) { // PGP Uids are defined to be UTF-8 (RFC 4880 §5.11) const auto normalized = UserID::addrSpecFromString (addr.toUtf8().constData()); if (normalized.empty()) { // should not happen bug in the caller, non localized // error for bug reporting. mFatalErrors << QStringLiteral("The mail address for '%1' could not be extracted").arg(addr); continue; } const QString normStr = QString::fromUtf8(normalized.c_str()); mRecipients << normStr; // Initially add empty lists of keys for both protocols mEncKeys[normStr] = {{CMS, {}}, {OpenPGP, {}}}; } } void KeyResolverCore::Private::setOverrideKeys(const QMap> &overrides) { for (auto protocolIt = overrides.cbegin(); protocolIt != overrides.cend(); ++protocolIt) { const Protocol &protocol = protocolIt.key(); const auto &addressFingerprintMap = protocolIt.value(); for (auto addressIt = addressFingerprintMap.cbegin(); addressIt != addressFingerprintMap.cend(); ++addressIt) { const QString &address = addressIt.key(); const QStringList &fingerprints = addressIt.value(); const QString normalizedAddress = QString::fromUtf8(UserID::addrSpecFromString(address.toUtf8().constData()).c_str()); mOverrides[normalizedAddress][protocol] = fingerprints; } } } namespace { std::vector resolveOverride(const QString &address, Protocol protocol, const QStringList &fingerprints) { std::vector keys; for (const auto &fprOrId: fingerprints) { const Key key = KeyCache::instance()->findByKeyIDOrFingerprint(fprOrId.toUtf8().constData()); if (key.isNull()) { // FIXME: Report to caller qCDebug (LIBKLEO_LOG) << "Failed to find override key for:" << address << "fpr:" << fprOrId; continue; } if (protocol != UnknownProtocol && key.protocol() != protocol) { qCDebug(LIBKLEO_LOG) << "Ignoring key" << Formatting::summaryLine(key) << "given as" << Formatting::displayName(protocol) << "override for" << address; continue; } qCDebug(LIBKLEO_LOG) << "Using key" << Formatting::summaryLine(key) << "as" << Formatting::displayName(protocol) << "override for" << address; keys.push_back(key); } return keys; } } void KeyResolverCore::Private::resolveOverrides() { if (!mEncrypt) { // No encryption we are done. return; } for (auto addressIt = mOverrides.cbegin(); addressIt != mOverrides.cend(); ++addressIt) { const QString &address = addressIt.key(); const auto &protocolFingerprintsMap = addressIt.value(); if (!mRecipients.contains(address)) { qCDebug(LIBKLEO_LOG) << "Overrides provided for an address that is " "neither sender nor recipient. Address:" << address; continue; } const QStringList commonOverride = protocolFingerprintsMap.value(UnknownProtocol); if (!commonOverride.empty()) { mEncKeys[address][UnknownProtocol] = resolveOverride(address, UnknownProtocol, commonOverride); if (protocolFingerprintsMap.contains(OpenPGP)) { qCDebug(LIBKLEO_LOG) << "Ignoring OpenPGP-specific override for" << address << "in favor of common override"; } if (protocolFingerprintsMap.contains(CMS)) { qCDebug(LIBKLEO_LOG) << "Ignoring S/MIME-specific override for" << address << "in favor of common override"; } } else { if (mFormat != CMS) { mEncKeys[address][OpenPGP] = resolveOverride(address, OpenPGP, protocolFingerprintsMap.value(OpenPGP)); } if (mFormat != OpenPGP) { mEncKeys[address][CMS] = resolveOverride(address, CMS, protocolFingerprintsMap.value(CMS)); } } } } void KeyResolverCore::Private::resolveSign(Protocol proto) { if (mSigKeys.contains(proto)) { // Explicitly set return; } - const auto keys = mCache->findBestByMailBox(mSender.toUtf8().constData(), - proto, true, false); + const auto keys = mCache->findBestByMailBox(mSender.toUtf8().constData(), proto, KeyUsage::Sign); for (const auto &key: keys) { if (key.isNull()) { continue; } if (!isAcceptableSigningKey(key)) { qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint() << "for" << mSender; return; } } 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 KeyResolverCore::Private::resolveRecipient(const QString &address, Protocol protocol) { - const auto keys = mCache->findBestByMailBox(address.toUtf8().constData(), protocol, false, true); + const auto keys = mCache->findBestByMailBox(address.toUtf8().constData(), protocol, KeyUsage::Encrypt); if (keys.empty() || keys[0].isNull()) { qCDebug(LIBKLEO_LOG) << "Failed to find any" << Formatting::displayName(protocol) << "key for: " << address; return {}; } if (keys.size() == 1) { if (!isAcceptableEncryptionKey(keys[0], address)) { qCDebug(LIBKLEO_LOG) << "key for:" << address << keys[0].primaryFingerprint() << "has not enough validity"; return {}; } } else { // 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 &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); } } auto getBestEncryptionKeys(const QMap>> &encryptionKeys, Protocol preferredProtocol) { QMap> result; for (auto it = encryptionKeys.begin(); it != encryptionKeys.end(); ++it) { const QString &address = it.key(); auto &protocolKeysMap = it.value(); const std::vector &overrideKeys = protocolKeysMap[UnknownProtocol]; if (!overrideKeys.empty()) { result.insert(address, overrideKeys); continue; } const std::vector &keysOpenPGP = protocolKeysMap[OpenPGP]; const std::vector &keysCMS = protocolKeysMap[CMS]; if (keysOpenPGP.empty() && keysCMS.empty()) { result.insert(address, {}); } else if (!keysOpenPGP.empty() && keysCMS.empty()) { result.insert(address, keysOpenPGP); } else if (keysOpenPGP.empty() && !keysCMS.empty()) { result.insert(address, keysCMS); } else { // check whether OpenPGP keys or S/MIME keys have higher validity const int validityPGP = minimumValidity(keysOpenPGP, address); const int validityCMS = minimumValidity(keysCMS, address); if ((validityCMS > validityPGP) || (validityCMS == validityPGP && preferredProtocol == CMS)) { result.insert(address, keysCMS); } else { result.insert(address, keysOpenPGP); } } } return result; } QStringList KeyResolverCore::Private::unresolvedRecipients(GpgME::Protocol protocol) const { QStringList result; result.reserve(mEncKeys.size()); for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) { const auto &protocolKeysMap = it.value(); if (protocolKeysMap.value(protocol).empty()) { result.push_back(it.key()); } } return result; } namespace { bool hasUnresolvedRecipients(const QMap>> &encryptionKeys, Protocol protocol) { return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys), [protocol] (const auto &protocolKeysMap) { return protocolKeysMap.value(protocol).empty(); }); } bool anyCommonOverrideHasKeyOfType(const QMap>> &encryptionKeys, Protocol protocol) { return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys), [protocol] (const auto &protocolKeysMap) { return anyKeyHasProtocol(protocolKeysMap.value(UnknownProtocol), protocol); }); } auto keysForProtocol(const QMap>> &encryptionKeys, Protocol protocol) { QMap> keys; for (auto it = std::begin(encryptionKeys), end = std::end(encryptionKeys); it != end; ++it) { const QString &address = it.key(); const auto &protocolKeysMap = it.value(); keys.insert(address, protocolKeysMap.value(protocol)); } return keys; } template auto concatenate(std::vector v1, const std::vector &v2) { v1.reserve(v1.size() + v2.size()); v1.insert(std::end(v1), std::begin(v2), std::end(v2)); return v1; } } KeyResolverCore::Result KeyResolverCore::Private::resolve() { qCDebug(LIBKLEO_LOG) << "Starting "; if (!mSign && !mEncrypt) { // nothing to do return {AllResolved, {}, {}}; } // First resolve through overrides resolveOverrides(); // check protocols needed for overrides const bool commonOverridesNeedOpenPGP = anyCommonOverrideHasKeyOfType(mEncKeys, OpenPGP); const bool commonOverridesNeedCMS = anyCommonOverrideHasKeyOfType(mEncKeys, CMS); if ((mFormat == OpenPGP && commonOverridesNeedCMS) || (mFormat == CMS && commonOverridesNeedOpenPGP) || (!mAllowMixed && commonOverridesNeedOpenPGP && commonOverridesNeedCMS)) { // invalid protocol requirements -> clear intermediate result and abort resolution mEncKeys.clear(); return {Error, {}, {}}; } // Then look for signing / encryption keys if (mFormat == OpenPGP || mFormat == UnknownProtocol) { resolveSign(OpenPGP); resolveEnc(OpenPGP); } const bool pgpOnly = (!mEncrypt || !hasUnresolvedRecipients(mEncKeys, OpenPGP)) && (!mSign || mSigKeys.contains(OpenPGP)); if (mFormat == OpenPGP) { return { SolutionFlags((pgpOnly ? AllResolved : SomeUnresolved) | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {} }; } if (mFormat == CMS || mFormat == UnknownProtocol) { resolveSign(CMS); resolveEnc(CMS); } const bool cmsOnly = (!mEncrypt || !hasUnresolvedRecipients(mEncKeys, CMS)) && (!mSign || mSigKeys.contains(CMS)); if (mFormat == CMS) { return { SolutionFlags((cmsOnly ? AllResolved : SomeUnresolved) | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {} }; } // check if single-protocol solution has been found if (cmsOnly && (!pgpOnly || mPreferredProtocol == CMS)) { if (!mAllowMixed) { return { SolutionFlags(AllResolved | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)} }; } else { return { SolutionFlags(AllResolved | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {} }; } } if (pgpOnly) { if (!mAllowMixed) { return { SolutionFlags(AllResolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)} }; } else { return { SolutionFlags(AllResolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {} }; } } if (!mAllowMixed) { // return incomplete single-protocol solution if (mPreferredProtocol == CMS) { return { SolutionFlags(SomeUnresolved | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)} }; } else { return { SolutionFlags(SomeUnresolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)} }; } } const auto bestEncryptionKeys = getBestEncryptionKeys(mEncKeys, mPreferredProtocol); const bool allAddressesAreResolved = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [] (const auto &keys) { return !keys.empty(); }); if (allAddressesAreResolved) { return { SolutionFlags(AllResolved | MixedProtocols), {UnknownProtocol, concatenate(mSigKeys.value(OpenPGP), mSigKeys.value(CMS)), bestEncryptionKeys}, {} }; } const bool allKeysAreOpenPGP = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [] (const auto &keys) { return allKeysHaveProtocol(keys, OpenPGP); }); if (allKeysAreOpenPGP) { return { SolutionFlags(SomeUnresolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), bestEncryptionKeys}, {} }; } const bool allKeysAreCMS = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [] (const auto &keys) { return allKeysHaveProtocol(keys, CMS); }); if (allKeysAreCMS) { return { SolutionFlags(SomeUnresolved | CMSOnly), {CMS, mSigKeys.value(CMS), bestEncryptionKeys}, {} }; } return { SolutionFlags(SomeUnresolved | MixedProtocols), {UnknownProtocol, concatenate(mSigKeys.value(OpenPGP), mSigKeys.value(CMS)), bestEncryptionKeys}, {} }; } KeyResolverCore::KeyResolverCore(bool encrypt, bool sign, Protocol fmt) : d(new Private(this, encrypt, sign, fmt)) { } KeyResolverCore::~KeyResolverCore() = default; void KeyResolverCore::setSender(const QString &address) { d->setSender(address); } QString KeyResolverCore::normalizedSender() const { return d->mSender; } void KeyResolverCore::setRecipients(const QStringList &addresses) { d->addRecipients(addresses); } void KeyResolverCore::setSigningKeys(const QStringList &fingerprints) { d->setSigningKeys(fingerprints); } void KeyResolverCore::setOverrideKeys(const QMap> &overrides) { d->setOverrideKeys(overrides); } void KeyResolverCore::setAllowMixedProtocols(bool allowMixed) { d->mAllowMixed = allowMixed; } void KeyResolverCore::setPreferredProtocol(Protocol proto) { d->mPreferredProtocol = proto; } void KeyResolverCore::setMinimumValidity(int validity) { d->mMinimumValidity = validity; } KeyResolverCore::Result KeyResolverCore::resolve() { return d->resolve(); } diff --git a/src/models/keycache.cpp b/src/models/keycache.cpp index 21f453a10..9a055dabb 100644 --- a/src/models/keycache.cpp +++ b/src/models/keycache.cpp @@ -1,1757 +1,1757 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/keycache.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "keycache.h" #include "keycache_p.h" +#include "kleo/enum.h" #include "kleo/keygroup.h" #include "kleo/predicates.h" #include "kleo/stl_util.h" #include "kleo/dn.h" #include "utils/compat.h" #include "utils/filesystemwatcher.h" #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #include #include #include #include #include "kleo/debug.h" #include "libkleo_debug.h" using namespace Kleo; using namespace GpgME; using namespace KMime::Types; static const unsigned int hours2ms = 1000 * 60 * 60; static const QString groupNamePrefix = QStringLiteral("Group-"); // // // KeyCache // // namespace { make_comparator_str(ByEMail, .first.c_str()); QStringList getFingerprints(const KeyGroup::Keys &keys) { QStringList fingerprints; fingerprints.reserve(keys.size()); std::transform(keys.cbegin(), keys.cend(), std::back_inserter(fingerprints), [] (const Key &key) { return QString::fromLatin1(key.primaryFingerprint()); }); return fingerprints; } } class KeyCache::Private { friend class ::Kleo::KeyCache; KeyCache *const q; public: explicit Private(KeyCache *qq) : q(qq), m_refreshInterval(1), m_initalized(false), m_pgpOnly(true), m_remarks_enabled(false) { connect(&m_autoKeyListingTimer, &QTimer::timeout, q, [this]() { q->startKeyListing(); }); updateAutoKeyListingTimer(); } ~Private() { if (m_refreshJob) { m_refreshJob->cancel(); } } template < template