diff --git a/autotests/newkeyapprovaldialogtest.cpp b/autotests/newkeyapprovaldialogtest.cpp index 48e9fa11..7dbe670e 100644 --- a/autotests/newkeyapprovaldialogtest.cpp +++ b/autotests/newkeyapprovaldialogtest.cpp @@ -1,1116 +1,1129 @@ /* 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/Compliance> #include <Libkleo/Formatting> #include <Libkleo/KeyCache> #include <Libkleo/KeySelectionCombo> #include <Libkleo/NewKeyApprovalDialog> #include <Libkleo/Predicates> #include <Libkleo/Test> #include <QCheckBox> #include <QGroupBox> #include <QLabel> #include <QObject> #include <QPushButton> #include <QRadioButton> #include <QSignalSpy> #include <QTest> #include <gpgme++/key.h> #include <gpgme.h> #include <memory> #include <set> +#include <gpgme++/gpgmepp_version.h> +#if GPGMEPP_VERSION >= 0x11700 // 1.23.0 +#define GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE 1 +#else +#define GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE 0 +#endif + 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, }; 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; } } // copied from gpgme; slightly modified void _gpgme_key_add_subkey(gpgme_key_t key, gpgme_subkey_t *r_subkey) { gpgme_subkey_t subkey; subkey = static_cast<gpgme_subkey_t>(calloc(1, sizeof *subkey)); Q_ASSERT(subkey); subkey->keyid = subkey->_keyid; subkey->_keyid[16] = '\0'; if (!key->subkeys) { key->subkeys = subkey; } if (key->_last_subkey) { key->_last_subkey->next = subkey; } key->_last_subkey = subkey; *r_subkey = subkey; } GpgME::Key createTestKey(const char *uid, GpgME::Protocol protocol = GpgME::UnknownProtocol, KeyCache::KeyUsage usage = KeyCache::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 == KeyCache::KeyUsage::AnyUsage || usage == KeyCache::KeyUsage::Encrypt); key->can_sign = int(usage == KeyCache::KeyUsage::AnyUsage || usage == KeyCache::KeyUsage::Sign); +#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE + key->has_encrypt = int(usage == KeyCache::KeyUsage::AnyUsage || usage == KeyCache::KeyUsage::Encrypt); + key->has_sign = int(usage == KeyCache::KeyUsage::AnyUsage || usage == KeyCache::KeyUsage::Sign); +#endif key->secret = 1; key->uids->validity = mapValidity(validity); key->keylist_mode = GPGME_KEYLIST_MODE_VALIDATE; // add a usable VS-NfD-compliant subkey gpgme_subkey_t subkey; _gpgme_key_add_subkey(key, &subkey); subkey->is_de_vs = 1; + subkey->can_encrypt = key->can_encrypt; + subkey->can_sign = key->can_sign; 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<KeySelectionCombo *>(); QVERIFY(combo); const auto spy = std::make_unique<QSignalSpy>(combo, &KeySelectionCombo::keyListingFinished); QVERIFY(spy->isValid()); QVERIFY(spy->wait(10)); } template<typename T> struct Widgets { 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; } enum Visibility { IsHidden, IsVisible, }; enum CheckedState { IsUnchecked, IsChecked, }; template<typename T> void verifyProtocolButton(const T *button, Visibility expectedVisibility, CheckedState expectedCheckedState) { QVERIFY(button); QCOMPARE(button->isVisible(), expectedVisibility == IsVisible); QCOMPARE(button->isChecked(), expectedCheckedState == IsChecked); } template<typename T> void verifyWidgetVisibility(const T *widget, Visibility expectedVisibility) { QVERIFY(widget); QCOMPARE(widget->isVisible(), expectedVisibility == IsVisible); } template<typename T> void verifyWidgetsVisibility(const QList<T> &widgets, Visibility expectedVisibility) { for (auto w : widgets) { verifyWidgetVisibility(w, expectedVisibility); } } void verifyProtocolLabels(const QList<QLabel *> &labels, int expectedNumber, Visibility expectedVisibility) { QCOMPARE(labels.size(), expectedNumber); verifyWidgetsVisibility(labels, expectedVisibility); } bool listsOfKeysAreEqual(const std::vector<GpgME::Key> &l1, const std::vector<GpgME::Key> &l2) { return std::equal(std::begin(l1), std::end(l1), std::begin(l2), std::end(l2), ByFingerprint<std::equal_to>()); } 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<KeySelectionCombo *> &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, KeyCache::KeyCache::KeyUsage::AnyUsage), createTestKey("sender@example.net", GpgME::CMS, KeyCache::KeyCache::KeyUsage::AnyUsage), createTestKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP, KeyCache::KeyCache::KeyUsage::Encrypt), createTestKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS, KeyCache::KeyCache::KeyUsage::Encrypt), createTestKey("Marginal Validity <marginal-openpgp@example.net>", GpgME::OpenPGP, KeyCache::KeyCache::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 <prefer-openpgp@example.net>", GpgME::OpenPGP).isNull()); QVERIFY(!testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS).isNull()); QVERIFY(!testKey("Marginal Validity <marginal-openpgp@example.net>", 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 <prefer-openpgp@example.net>", 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 <prefer-smime@example.net>", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::CMS)}}, }, }; const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("openpgp button")), IsVisible, IsChecked); verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("smime button")), IsVisible, IsUnchecked); 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__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 <prefer-smime@example.net>", 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 <prefer-openpgp@example.net>", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP)}}, }, }; const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("openpgp button")), IsVisible, IsUnchecked); verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("smime button")), IsVisible, IsChecked); 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__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 <prefer-openpgp@example.net>", 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<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("openpgp button")), IsHidden, IsChecked); verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("smime button")), IsHidden, IsUnchecked); 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, {testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), {testKey("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(); verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("openpgp button")), IsHidden, IsUnchecked); verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("smime button")), IsHidden, IsChecked); 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, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@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 = {}; const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("openpgp button")), IsVisible, IsChecked); verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("smime button")), IsVisible, IsChecked); verifyProtocolLabels(dialog->findChildren<QLabel *>(QStringLiteral("protocol label")), 4, IsVisible); 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__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 <prefer-openpgp@example.net>", 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<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("openpgp button")), IsVisible, IsChecked); verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("smime button")), IsVisible, IsUnchecked); verifyProtocolLabels(dialog->findChildren<QLabel *>(QStringLiteral("protocol label")), 4, IsHidden); 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), preferredSolution.signingKeys[1].primaryFingerprint()); const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(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 <prefer-smime@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 = {}; const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("openpgp button")), IsVisible, IsUnchecked); verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("smime button")), IsVisible, IsChecked); verifyProtocolLabels(dialog->findChildren<QLabel *>(QStringLiteral("protocol label")), 4, IsHidden); 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[1].primaryFingerprint()); QCOMPARE(signingKeyWidgets.hidden[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(), 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 <prefer-openpgp@example.net>", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("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, {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}, { {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@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 = {}; 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); } 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<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto okButton = dialog->findChild<QPushButton *>("ok button"); QVERIFY(okButton); QVERIFY(okButton->text() != "Generate"); { // get the first signing key combo which is the OpenPGP one const auto signingKeyCombo = dialog->findChild<KeySelectionCombo *>("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<KeySelectionCombo *>("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(), QStringLiteral("Generate")); encryptionKeyCombo->setCurrentIndex(originalIndex); QVERIFY(okButton->text() != QStringLiteral("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<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto okButton = dialog->findChild<QPushButton *>("ok button"); QVERIFY(okButton); QVERIFY(okButton->text() != "Generate"); { // get the first signing key combo which is the OpenPGP one const auto signingKeyCombo = dialog->findChild<KeySelectionCombo *>("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() != QStringLiteral("Generate")); signingKeyCombo->setCurrentIndex(originalIndex); QVERIFY(okButton->text() != QStringLiteral("Generate")); } { // get the first encryption key combo which is the OpenPGP one for the sender const auto encryptionKeyCombo = dialog->findChild<KeySelectionCombo *>("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() != QStringLiteral("Generate")); encryptionKeyCombo->setCurrentIndex(originalIndex); QVERIFY(okButton->text() != QStringLiteral("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<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto okButton = dialog->findChild<QPushButton *>(QStringLiteral("ok button")); QVERIFY(okButton); QVERIFY(okButton->isEnabled()); const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(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 <prefer-openpgp@example.net>", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", 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")}; Tests::FakeCryptoConfigIntValue fakeDeVsCompliance{"gpg", "compliance_de_vs", 1}; const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto complianceLabel = dialog->findChild<QLabel *>(QStringLiteral("compliance label")); verifyWidgetVisibility(complianceLabel, IsVisible); QVERIFY(!complianceLabel->text().contains(DeVSCompliance::name(false))); } 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 <marginal-openpgp@example.net>", 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")}; Tests::FakeCryptoConfigIntValue fakeDeVsCompliance{"gpg", "compliance_de_vs", 1}; const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto complianceLabel = dialog->findChild<QLabel *>(QStringLiteral("compliance label")); verifyWidgetVisibility(complianceLabel, IsVisible); QVERIFY(complianceLabel->text().contains(DeVSCompliance::name(false))); } 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")}; Tests::FakeCryptoConfigIntValue fakeDeVsCompliance{"gpg", "compliance_de_vs", 1}; const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); const auto complianceLabel = dialog->findChild<QLabel *>(QStringLiteral("compliance label")); verifyWidgetVisibility(complianceLabel, IsVisible); QVERIFY(!complianceLabel->text().contains(DeVSCompliance::name(false))); } void test__sign_and_encrypt_to_self_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("sender@example.net"), {testKey("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(); QVERIFY(!dialog->findChild<QGroupBox *>(QStringLiteral("encrypt-to-others box"))); } void test__sign_and_encrypt_to_self_and_others() { 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 <prefer-openpgp@example.net>", GpgME::OpenPGP)}}, {QStringLiteral("sender@example.net"), {testKey("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(); QVERIFY(dialog->findChild<QGroupBox *>(QStringLiteral("encrypt-to-others box"))); } 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 <prefer-openpgp@example.net>", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}}, {QStringLiteral("unknown@example.net"), {}}, }, }; const KeyResolver::Solution alternativeSolution = {}; const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren<KeySelectionCombo *>()); const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted}; QVERIFY(dialogAcceptedSpy.isValid()); const auto okButton = dialog->findChild<QPushButton *>(QStringLiteral("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 <prefer-openpgp@example.net>", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", 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 <prefer-openpgp@example.net>", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", 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<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren<KeySelectionCombo *>()); const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted}; QVERIFY(dialogAcceptedSpy.isValid()); const auto okButton = dialog->findChild<QPushButton *>(QStringLiteral("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 <prefer-openpgp@example.net>", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", 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<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren<KeySelectionCombo *>()); const auto smimeButton = dialog->findChild<QCheckBox *>(QStringLiteral("smime button")); QVERIFY(smimeButton); smimeButton->click(); QVERIFY(!smimeButton->isChecked()); const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted}; QVERIFY(dialogAcceptedSpy.isValid()); const auto okButton = dialog->findChild<QPushButton *>(QStringLiteral("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 <prefer-openpgp@example.net>", 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 <prefer-openpgp@example.net>", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", 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<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol); dialog->show(); waitForKeySelectionCombosBeingInitialized(dialog.get()); switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren<KeySelectionCombo *>()); const auto openPGPButton = dialog->findChild<QCheckBox *>(QStringLiteral("openpgp button")); QVERIFY(openPGPButton); openPGPButton->click(); QVERIFY(!openPGPButton->isChecked()); const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted}; QVERIFY(dialogAcceptedSpy.isValid()); const auto okButton = dialog->findChild<QPushButton *>(QStringLiteral("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 <prefer-smime@example.net>", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::CMS)}}, }}); } private: std::shared_ptr<const KeyCache> mKeyCache; }; QTEST_MAIN(NewKeyApprovalDialogTest) #include "newkeyapprovaldialogtest.moc" diff --git a/src/ui/newkeyapprovaldialog.cpp b/src/ui/newkeyapprovaldialog.cpp index 0f345d3e..bba05275 100644 --- a/src/ui/newkeyapprovaldialog.cpp +++ b/src/ui/newkeyapprovaldialog.cpp @@ -1,904 +1,904 @@ /* -*- c++ -*- newkeyapprovaldialog.cpp 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 */ #include <config-libkleo.h> #include "newkeyapprovaldialog.h" #include "keyselectioncombo.h" #include "progressdialog.h" #include <libkleo/compliance.h> #include <libkleo/debug.h> #include <libkleo/defaultkeyfilter.h> #include <libkleo/formatting.h> #include <libkleo/gnupg.h> #include <libkleo/keyhelpers.h> #include <libkleo/systeminfo.h> #include <libkleo_debug.h> #include <KColorScheme> #include <KLocalizedString> #include <KMessageBox> #include <QGpgME/DefaultKeyGenerationJob> #include <QGpgME/Job> #include <QButtonGroup> #include <QCheckBox> #include <QDialogButtonBox> #include <QGroupBox> #include <QHBoxLayout> #include <QLabel> #include <QMap> #include <QPushButton> #include <QRadioButton> #include <QScreen> #include <QScrollArea> #include <QToolTip> #include <QVBoxLayout> #include <gpgme++/key.h> #include <gpgme++/keygenerationresult.h> using namespace Kleo; using namespace GpgME; namespace { class EncryptFilter : public DefaultKeyFilter { public: EncryptFilter() : DefaultKeyFilter() { - setCanEncrypt(DefaultKeyFilter::Set); + setHasEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr<KeyFilter> s_encryptFilter = std::shared_ptr<KeyFilter>(new EncryptFilter); class OpenPGPFilter : public DefaultKeyFilter { public: OpenPGPFilter() : DefaultKeyFilter() { setIsOpenPGP(DefaultKeyFilter::Set); - setCanEncrypt(DefaultKeyFilter::Set); + setHasEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr<KeyFilter> s_pgpEncryptFilter = 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); + setHasEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr<KeyFilter> s_smimeEncryptFilter = 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); /* 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) { auto hLay = new QHBoxLayout(this); auto infoBtn = new QPushButton; infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); infoBtn->setIconSize(QSize(22, 22)); infoBtn->setFlat(true); infoBtn->setAccessibleName(i18nc("@action:button", "Show Details")); 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->setAccessibleName(i18nc("@action:button", "Show Matching Keys")); mFilterBtn->setToolTip(i18n("Show keys matching the email address")); } else { mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters"))); mFilterBtn->setAccessibleName(i18nc("@action:button short for 'Show all keys'", "Show All")); mFilterBtn->setToolTip(i18n("Show all keys")); } } KeySelectionCombo *combo() { return mCombo; } GpgME::Protocol fixedProtocol() const { return mFixedProtocol; } void setFixedProtocol(GpgME::Protocol proto) { mFixedProtocol = proto; } private: KeySelectionCombo *mCombo; QPushButton *mFilterBtn; QString mLastIdFilter; GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol; }; 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; } Key findfirstKeyOfType(const std::vector<Key> &keys, GpgME::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: enum { OpenPGPButtonId = 1, SMIMEButtonId = 2, }; Private(NewKeyApprovalDialog *qq, bool encrypt, bool sign, GpgME::Protocol forcedProtocol, GpgME::Protocol presetProtocol, const QString &sender, bool allowMixed) : 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)); Q_ASSERT(!(!allowMixed && presetProtocol == 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); #ifndef NDEBUG mOkButton->setObjectName(QStringLiteral("ok button")); #endif 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(qq); QAbstractButton *pgpBtn; QAbstractButton *smimeBtn; if (mAllowMixed) { pgpBtn = new QCheckBox(i18n("OpenPGP")); smimeBtn = new QCheckBox(i18n("S/MIME")); } else { pgpBtn = new QRadioButton(i18n("OpenPGP")); smimeBtn = new QRadioButton(i18n("S/MIME")); } #ifndef NDEBUG pgpBtn->setObjectName(QStringLiteral("openpgp button")); smimeBtn->setObjectName(QStringLiteral("smime button")); #endif mFormatBtns->addButton(pgpBtn, OpenPGPButtonId); mFormatBtns->addButton(smimeBtn, SMIMEButtonId); mFormatBtns->setExclusive(!mAllowMixed); connect(mFormatBtns, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), q, [this]() { updateOkButton(); }); fmtLayout->addStretch(-1); fmtLayout->addWidget(pgpBtn); fmtLayout->addWidget(smimeBtn); mMainLay->addLayout(fmtLayout); if (mForcedProtocol != GpgME::UnknownProtocol) { pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP); smimeBtn->setChecked(mForcedProtocol == GpgME::CMS); pgpBtn->setVisible(false); smimeBtn->setVisible(false); } else { pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP || presetProtocol == GpgME::UnknownProtocol); smimeBtn->setChecked(presetProtocol == GpgME::CMS || presetProtocol == GpgME::UnknownProtocol); } QObject::connect(mFormatBtns, &QButtonGroup::idClicked, q, [this](int buttonId) { // ensure that at least one protocol button is checked if (mAllowMixed && !mFormatBtns->button(OpenPGPButtonId)->isChecked() && !mFormatBtns->button(SMIMEButtonId)->isChecked()) { mFormatBtns->button(buttonId == OpenPGPButtonId ? SMIMEButtonId : OpenPGPButtonId)->setChecked(true); } updateWidgets(); }); mMainLay->addWidget(mScrollArea); mComplianceLbl = new QLabel; mComplianceLbl->setVisible(false); #ifndef NDEBUG mComplianceLbl->setObjectName(QStringLiteral("compliance label")); #endif auto btnLayout = new QHBoxLayout; btnLayout->addWidget(mComplianceLbl); btnLayout->addWidget(btnBox); mMainLay->addLayout(btnLayout); q->setLayout(mMainLay); } ~Private() = default; GpgME::Protocol currentProtocol() { const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked(); const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked(); if (mAllowMixed) { if (openPGPButtonChecked && !smimeButtonChecked) { return GpgME::OpenPGP; } if (!openPGPButtonChecked && smimeButtonChecked) { return GpgME::CMS; } } else if (openPGPButtonChecked) { return GpgME::OpenPGP; } else if (smimeButtonChecked) { return GpgME::CMS; } return GpgME::UnknownProtocol; } auto findVisibleKeySelectionComboWithGenerateKey() { const auto it = std::find_if(std::begin(mAllCombos), std::end(mAllCombos), [](auto combo) { return combo->isVisible() && combo->currentData(Qt::UserRole).toInt() == GenerateKey; }); return it != std::end(mAllCombos) ? *it : nullptr; } 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, Formatting::errorAsString(mLastError), i18n("Operation Failed")); mRunningJobs.clear(); return; } if (!mRunningJobs.empty()) { return; } /* Save the keys */ mAcceptedResult.protocol = currentProtocol(); for (const auto combo : std::as_const(mEncCombos)) { const auto addr = combo->property("address").toString(); const auto key = combo->currentKey(); if (!combo->isVisible() || key.isNull()) { continue; } mAcceptedResult.encryptionKeys[addr].push_back(key); } for (const auto combo : std::as_const(mSigningCombos)) { const auto key = combo->currentKey(); if (!combo->isVisible() || key.isNull()) { continue; } 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. if (auto combo = findVisibleKeySelectionComboWithGenerateKey()) { generateKey(combo); return; } checkAccepted(); } auto encryptionKeyFilter(GpgME::Protocol protocol) { switch (protocol) { case OpenPGP: return s_pgpEncryptFilter; case CMS: return s_smimeEncryptFilter; default: return s_encryptFilter; } } void updateWidgets() { const GpgME::Protocol protocol = currentProtocol(); const auto encryptionFilter = encryptionKeyFilter(protocol); for (auto combo : std::as_const(mSigningCombos)) { auto widget = qobject_cast<ComboWidget *>(combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget"; continue; } widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol); } for (auto combo : std::as_const(mEncCombos)) { auto widget = qobject_cast<ComboWidget *>(combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find combo widget"; continue; } widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol); if (widget->isVisible() && combo->property("address") != mSender) { combo->setKeyFilter(encryptionFilter); } } // hide the labels indicating the protocol of the sender's keys if only a single protocol is active const auto protocolLabels = q->findChildren<QLabel *>(QStringLiteral("protocol label")); for (auto label : protocolLabels) { label->setVisible(protocol == GpgME::UnknownProtocol); } } auto createProtocolLabel(GpgME::Protocol protocol) { auto label = new QLabel(Formatting::displayName(protocol)); label->setObjectName(QStringLiteral("protocol label")); return label; } ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol protocol = GpgME::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 (protocol == GpgME::OpenPGP) { combo->setKeyFilter(s_pgpSignFilter); } else if (protocol == GpgME::CMS) { combo->setKeyFilter(s_smimeSignFilter); } 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() && 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>(&QComboBox::currentIndexChanged), q, [this]() { updateOkButton(); }); return comboWidget; } void setSigningKeys(const std::vector<GpgME::Key> &preferredKeys, const std::vector<GpgME::Key> &alternativeKeys) { auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", 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(createProtocolLabel(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); } } if (mayNeedCMS) { if (mAllowMixed) { sigLayout->addWidget(createProtocolLabel(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); } } mScrollLayout->addWidget(group); } ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol fixedProtocol) { auto combo = new KeySelectionCombo(false); auto comboWidget = new ComboWidget(combo); #ifndef NDEBUG combo->setObjectName(QStringLiteral("encryption key")); #endif if (fixedProtocol == GpgME::OpenPGP) { combo->setKeyFilter(s_pgpEncryptFilter); } else if (fixedProtocol == GpgME::CMS) { combo->setKeyFilter(s_smimeEncryptFilter); } else { combo->setKeyFilter(s_encryptFilter); } if (key.isNull() || key_has_addr(key, addr)) { comboWidget->setIdFilter(addr); } comboWidget->setFixedProtocol(fixedProtocol); if (!key.isNull()) { combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol); } 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.")); connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() { updateOkButton(); }); connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), q, [this]() { updateOkButton(); }); mEncCombos << combo; mAllCombos << combo; combo->setProperty("address", addr); return comboWidget; } void addEncryptionAddr(const QString &addr, GpgME::Protocol preferredKeysProtocol, const std::vector<GpgME::Key> &preferredKeys, GpgME::Protocol alternativeKeysProtocol, const std::vector<GpgME::Key> &alternativeKeys, QGridLayout *encGrid) { if (addr == mSender) { const bool mayNeedOpenPGP = mForcedProtocol != CMS; const bool mayNeedCMS = mForcedProtocol != OpenPGP; if (mayNeedOpenPGP) { if (mAllowMixed) { encGrid->addWidget(createProtocolLabel(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); } } if (mayNeedCMS) { if (mAllowMixed) { encGrid->addWidget(createProtocolLabel(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 setEncryptionKeys(GpgME::Protocol preferredKeysProtocol, const QMap<QString, std::vector<GpgME::Key>> &preferredKeys, GpgME::Protocol alternativeKeysProtocol, const QMap<QString, std::vector<GpgME::Key>> &alternativeKeys) { { auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender)); #ifndef NDEBUG group->setObjectName(QStringLiteral("encrypt-to-self box")); #endif group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout(group); addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid); encGrid->setColumnStretch(1, -1); mScrollLayout->addWidget(group); } const bool hasOtherRecipients = std::any_of(preferredKeys.keyBegin(), preferredKeys.keyEnd(), [this](const auto &recipient) { return recipient != mSender; }); if (hasOtherRecipients) { auto group = new QGroupBox(i18n("Encrypt to others:")); #ifndef NDEBUG group->setObjectName(QStringLiteral("encrypt-to-others box")); #endif group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout{group}; 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->addWidget(group); } mScrollLayout->addStretch(-1); } void updateOkButton() { static QString origOkText = mOkButton->text(); const bool isGenerate = bool(findVisibleKeySelectionComboWithGenerateKey()); const bool allVisibleEncryptionKeysAreIgnored = std::all_of(std::begin(mEncCombos), std::end(mEncCombos), [](auto combo) { return !combo->isVisible() || combo->currentData(Qt::UserRole).toInt() == IgnoreKey; }); // 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. mOkButton->setEnabled(!mEncrypt || !allVisibleEncryptionKeysAreIgnored); mOkButton->setText(isGenerate ? i18n("Generate") : origOkText); if (!DeVSCompliance::isActive()) { return; } // Handle compliance bool de_vs = DeVSCompliance::isCompliant(); if (de_vs) { const GpgME::Protocol protocol = currentProtocol(); for (const auto combo : std::as_const(mAllCombos)) { if (!combo->isVisible()) { continue; } const auto key = combo->currentKey(); if (key.isNull()) { continue; } if (protocol != GpgME::UnknownProtocol && key.protocol() != protocol) { continue; } if (!DeVSCompliance::keyIsCompliant(key)) { de_vs = false; break; } } } DeVSCompliance::decorate(mOkButton, de_vs); mComplianceLbl->setText(DeVSCompliance::name(de_vs)); mComplianceLbl->setVisible(true); } GpgME::Protocol mForcedProtocol; QList<KeySelectionCombo *> mSigningCombos; QList<KeySelectionCombo *> mEncCombos; QList<KeySelectionCombo *> mAllCombos; QScrollArea *mScrollArea; QVBoxLayout *mScrollLayout; QPushButton *mOkButton; QVBoxLayout *mMainLay; QButtonGroup *mFormatBtns; QString mSender; bool mSign; bool mEncrypt; bool mAllowMixed; NewKeyApprovalDialog *q; QList<QGpgME::Job *> mRunningJobs; GpgME::Error mLastError; QLabel *mComplianceLbl; KeyResolver::Solution mAcceptedResult; QString mGenerateTooltip; }; NewKeyApprovalDialog::NewKeyApprovalDialog(bool encrypt, bool sign, const QString &sender, KeyResolver::Solution preferredSolution, KeyResolver::Solution alternativeSolution, bool allowMixed, GpgME::Protocol forcedProtocol, QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f) , d{std::make_unique<Private>(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)} { if (sign) { d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys)); } if (encrypt) { d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol, std::move(preferredSolution.encryptionKeys), allowMixed ? UnknownProtocol : alternativeSolution.protocol, std::move(alternativeSolution.encryptionKeys)); } d->updateWidgets(); d->updateOkButton(); const auto size = sizeHint(); const auto desk = screen()->size(); resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2))); } Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default; KeyResolver::Solution NewKeyApprovalDialog::result() { return d->mAcceptedResult; } #include "newkeyapprovaldialog.moc"