diff --git a/autotests/keyresolvercoretest.cpp b/autotests/keyresolvercoretest.cpp index 3c7fe04cb..660b45c32 100644 --- a/autotests/keyresolvercoretest.cpp +++ b/autotests/keyresolvercoretest.cpp @@ -1,384 +1,402 @@ /* autotests/keyresolvercoretest.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; namespace QTest { template <> inline bool qCompare(GpgME::UserID::Validity const &t1, GpgME::UserID::Validity const &t2, const char *actual, const char *expected, const char *file, int line) { return qCompare(int(t1), int(t2), actual, expected, file, line); } } class KeyResolverCoreTest: public QObject { Q_OBJECT private Q_SLOTS: void init() { mGnupgHome = QTest::qExtractTestData("/fixtures/keyresolvercoretest"); qputenv("GNUPGHOME", mGnupgHome->path().toLocal8Bit()); // hold a reference to the key cache to avoid rebuilding while the test is running mKeyCache = KeyCache::instance(); } void cleanup() { // verify that nobody else holds a reference to the key cache QVERIFY(mKeyCache.use_count() == 1); mKeyCache.reset(); mGnupgHome.reset(); } void test_verify_test_keys() { { const Key openpgp = testKey("sender-mixed@example.net", OpenPGP); QVERIFY(openpgp.hasSecret() && openpgp.canEncrypt() && openpgp.canSign()); QCOMPARE(openpgp.userID(0).validity(), UserID::Ultimate); const Key smime = testKey("sender-mixed@example.net", CMS); QVERIFY(smime.hasSecret() && smime.canEncrypt() && smime.canSign()); QCOMPARE(smime.userID(0).validity(), UserID::Full); } { const Key openpgp = testKey("sender-openpgp@example.net", OpenPGP); QVERIFY(openpgp.hasSecret() && openpgp.canEncrypt() && openpgp.canSign()); QCOMPARE(openpgp.userID(0).validity(), UserID::Ultimate); } { const Key smime = testKey("sender-smime@example.net", CMS); QVERIFY(smime.hasSecret() && smime.canEncrypt() && smime.canSign()); QCOMPARE(smime.userID(0).validity(), UserID::Full); } { const Key openpgp = testKey("prefer-openpgp@example.net", OpenPGP); QVERIFY(openpgp.canEncrypt()); QCOMPARE(openpgp.userID(0).validity(), UserID::Ultimate); const Key smime = testKey("prefer-openpgp@example.net", CMS); QVERIFY(smime.canEncrypt()); QCOMPARE(smime.userID(0).validity(), UserID::Full); } { const Key openpgp = testKey("full-validity@example.net", OpenPGP); QVERIFY(openpgp.canEncrypt()); QCOMPARE(openpgp.userID(0).validity(), UserID::Full); const Key smime = testKey("full-validity@example.net", CMS); QVERIFY(smime.canEncrypt()); QCOMPARE(smime.userID(0).validity(), UserID::Full); } { const Key openpgp = testKey("prefer-smime@example.net", OpenPGP); QVERIFY(openpgp.canEncrypt()); QCOMPARE(openpgp.userID(0).validity(), UserID::Marginal); const Key smime = testKey("prefer-smime@example.net", CMS); QVERIFY(smime.canEncrypt()); QCOMPARE(smime.userID(0).validity(), UserID::Full); } } void test_openpgp_is_used_if_openpgp_only_and_smime_only_are_both_possible() { KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.signingKeys().value(OpenPGP).size(), 1); QCOMPARE(resolver.signingKeys().value(OpenPGP)[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(resolver.signingKeys().value(CMS).size(), 0); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 1); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-mixed@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0); } void test_openpgp_is_used_if_openpgp_only_and_smime_only_are_both_possible_with_preference_for_openpgp() { KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true); resolver.setPreferredProtocol(OpenPGP); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.signingKeys().value(OpenPGP).size(), 1); QCOMPARE(resolver.signingKeys().value(OpenPGP)[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(resolver.signingKeys().value(CMS).size(), 0); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 1); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-mixed@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0); } void test_smime_is_used_if_openpgp_only_and_smime_only_are_both_possible_with_preference_for_smime() { KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true); resolver.setPreferredProtocol(CMS); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.signingKeys().value(OpenPGP).size(), 0); QCOMPARE(resolver.signingKeys().value(CMS).size(), 1); QCOMPARE(resolver.signingKeys().value(CMS)[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0); QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 1); QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-mixed@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); } void test_in_mixed_mode_keys_with_higher_validity_are_preferred() { KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false); resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net", "prefer-openpgp@example.net", "prefer-smime@example.net"}); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 4); QVERIFY(resolver.encryptionKeys().value(UnknownProtocol).contains("sender-openpgp@example.net")); QVERIFY(resolver.encryptionKeys().value(UnknownProtocol).contains("sender-smime@example.net")); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("prefer-openpgp@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("prefer-openpgp@example.net")[0].primaryFingerprint(), testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("prefer-smime@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("prefer-smime@example.net")[0].primaryFingerprint(), testKey("prefer-smime@example.net", CMS).primaryFingerprint()); } void test_encryption_keys_result_has_no_entry_for_unresolved_recipients() { KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false); resolver.setRecipients({"prefer-smime@example.net", "unknown@example.net"}); const bool success = resolver.resolve(); QVERIFY(!success); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 1); QVERIFY(resolver.encryptionKeys().value(OpenPGP).contains("prefer-smime@example.net")); QVERIFY(!resolver.encryptionKeys().value(OpenPGP).contains("unknown@example.net")); QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 1); QVERIFY(resolver.encryptionKeys().value(CMS).contains("prefer-smime@example.net")); QVERIFY(!resolver.encryptionKeys().value(CMS).contains("unknown@example.net")); } void test_openpgp_overrides_are_used_if_both_protocols_are_allowed() { const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net")[0].primaryFingerprint(), override); QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("full-validity@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("full-validity@example.net")[0].primaryFingerprint(), override); } void test_openpgp_overrides_are_used_if_openpgp_only_is_requested() { const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true, OpenPGP); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net")[0].primaryFingerprint(), override); QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0); } void test_openpgp_overrides_are_ignored_if_smime_only_is_requested() { const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true, CMS); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0); QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net")[0].primaryFingerprint(), testKey("full-validity@example.net", CMS).primaryFingerprint()); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0); } void test_smime_overrides_are_used_if_both_protocols_are_allowed() { const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true); resolver.setPreferredProtocol(CMS); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0); QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net")[0].primaryFingerprint(), override); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("full-validity@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("full-validity@example.net")[0].primaryFingerprint(), override); } void test_smime_overrides_are_used_if_smime_only_is_requested() { const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true, CMS); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0); QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(CMS).value("full-validity@example.net")[0].primaryFingerprint(), override); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0); } void test_smime_overrides_are_ignored_if_openpgp_only_is_requested() { const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true, OpenPGP); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("full-validity@example.net")[0].primaryFingerprint(), testKey("full-validity@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 0); } void test_overrides_for_wrong_protocol_are_ignored() { const QString override1 = testKey("full-validity@example.net", CMS).primaryFingerprint(); const QString override2 = testKey("full-validity@example.net", OpenPGP).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net"}); resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized "), {override1}}}}}); resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized "), {override2}}}}}); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-openpgp@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-openpgp@example.net")[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-smime@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-smime@example.net")[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-openpgp@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-openpgp@example.net")[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-smime@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-smime@example.net")[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); } void test_openpgp_only_common_overrides_are_used_for_openpgp() { const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"sender-openpgp@example.net"}); resolver.setOverrideKeys({{UnknownProtocol, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-openpgp@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).value("sender-openpgp@example.net")[0].primaryFingerprint(), override); QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-openpgp@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-openpgp@example.net")[0].primaryFingerprint(), override); } void test_smime_only_common_overrides_are_used_for_smime() { const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"sender-smime@example.net"}); resolver.setOverrideKeys({{UnknownProtocol, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const bool success = resolver.resolve(); QVERIFY(success); QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0); QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-smime@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(CMS).value("sender-smime@example.net")[0].primaryFingerprint(), override); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-smime@example.net").size(), 1); QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-smime@example.net")[0].primaryFingerprint(), override); } + void test_mixed_protocol_common_overrides_override_protocol_specific_resolution() + { + const QString override1 = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(); + const QString override2 = testKey("prefer-smime@example.net", CMS).primaryFingerprint(); + KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ true); + resolver.setSender(QStringLiteral("sender-mixed@example.net")); + resolver.setOverrideKeys({{UnknownProtocol, {{QStringLiteral("sender-mixed@example.net"), {override1, override2}}}}}); + + const bool success = resolver.resolve(); + + QVERIFY(success); + QCOMPARE(resolver.encryptionKeys().value(OpenPGP).size(), 0); + QCOMPARE(resolver.encryptionKeys().value(CMS).size(), 0); + QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-mixed@example.net").size(), 2); + QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-mixed@example.net")[0].primaryFingerprint(), override1); + QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("sender-mixed@example.net")[1].primaryFingerprint(), override2); + } + private: Key testKey(const char *email, Protocol protocol = UnknownProtocol) { const std::vector keys = KeyCache::instance()->findByEMailAddress(email); for (const auto &key: keys) { if (protocol == UnknownProtocol || key.protocol() == protocol) { return key; } } return Key(); } private: QSharedPointer mGnupgHome; std::shared_ptr mKeyCache; }; QTEST_MAIN(KeyResolverCoreTest) #include "keyresolvercoretest.moc" diff --git a/src/kleo/keyresolvercore.cpp b/src/kleo/keyresolvercore.cpp index 10f36bcf8..4b271a29d 100644 --- a/src/kleo/keyresolvercore.cpp +++ b/src/kleo/keyresolvercore.cpp @@ -1,573 +1,590 @@ /* -*- 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; }); +} + } // 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; bool 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; } } } // Apply the overrides this is also where specific formats come in 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; } for (auto protocolIt = protocolFingerprintsMap.cbegin(); protocolIt != protocolFingerprintsMap.cend(); ++protocolIt) { const Protocol protocol = protocolIt.key(); const QStringList &fingerprints = protocolIt.value(); if ((mFormat == OpenPGP && protocol == CMS) || (mFormat == CMS && protocol == OpenPGP)) { // Skip overrides for the wrong format continue; } + std::vector keys; for (const auto &fprOrId: fingerprints) { const Key key = mCache->findByKeyIDOrFingerprint(fprOrId.toUtf8().constData()); if (key.isNull()) { 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; } - - Protocol resolvedFmt = protocol; - if (protocol == UnknownProtocol) { - // Take the format from the key. - resolvedFmt = key.protocol(); - } - mEncKeys[address][resolvedFmt].push_back(key); - - qCDebug(LIBKLEO_LOG) << "Override" << address << Formatting::displayName(resolvedFmt) << fprOrId; + qCDebug(LIBKLEO_LOG) << "Using key" << Formatting::summaryLine(key) << "as" << Formatting::displayName(protocol) << "override for" << address; + keys.push_back(key); } + mEncKeys[address][protocol] = keys; } } } void KeyResolverCore::Private::resolveSign(Protocol proto) { if (mSigKeys.contains(proto)) { // Explicitly set return; } const auto keys = mCache->findBestByMailBox(mSender.toUtf8().constData(), proto, true, false); for (const auto &key: keys) { if (key.isNull()) { continue; } if (!isAcceptableSigningKey(key)) { qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint() << "for" << mSender; return; } } if (!keys.empty() && !keys[0].isNull()) { mSigKeys.insert(proto, keys); } } void KeyResolverCore::Private::setSigningKeys(const QStringList &fingerprints) { if (mSign) { for (const auto &fpr: fingerprints) { const auto key = mCache->findByKeyIDOrFingerprint(fpr.toUtf8().constData()); if (key.isNull()) { qCDebug(LIBKLEO_LOG) << "Failed to find signing key with fingerprint" << fpr; continue; } auto list = mSigKeys.value(key.protocol()); list.push_back(key); mSigKeys.insert(key.protocol(), list); } } } std::vector KeyResolverCore::Private::resolveRecipient(const QString &address, Protocol protocol) { const auto keys = mCache->findBestByMailBox(address.toUtf8().constData(), protocol, false, true); if (keys.empty() || keys[0].isNull()) { qCDebug(LIBKLEO_LOG) << "Failed to find any" << Formatting::displayName(protocol) << "key for: " << address; return {}; } if (keys.size() == 1) { if (!isAcceptableEncryptionKey(keys[0], address)) { qCDebug(LIBKLEO_LOG) << "key for:" << address << keys[0].primaryFingerprint() << "has not enough validity"; return {}; } } else { // If we have one unacceptable group key we reject the // whole group to avoid the situation where one key is // skipped or the operation fails. // // We are in Autoresolve land here. In the GUI we // will also show unacceptable group keys so that the // user can see which key is not acceptable. bool unacceptable = false; for (const auto &key: keys) { if (!isAcceptableEncryptionKey(key)) { qCDebug(LIBKLEO_LOG) << "group key for:" << address << keys[0].primaryFingerprint() << "has not enough validity"; unacceptable = true; break; } } if (unacceptable) { return {}; } } for (const auto &k: keys) { qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << k.primaryFingerprint(); } return keys; } // Try to find matching keys in the provided protocol for the unresolved addresses void KeyResolverCore::Private::resolveEnc(Protocol proto) { for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) { const QString &address = it.key(); auto &protocolKeysMap = it.value(); if (!protocolKeysMap[proto].empty()) { + // already resolved for current protocol (by override) continue; } + const std::vector &commonOverride = protocolKeysMap[UnknownProtocol]; + if (!commonOverride.empty()) { + // there is a common override; use it for current protocol if possible + if (allKeysHaveProtocol(commonOverride, proto)) { + protocolKeysMap[proto] = commonOverride; + continue; + } else { + qCDebug(LIBKLEO_LOG) << "Common override for" << address << "is unusable for" << Formatting::displayName(proto); + continue; + } + } protocolKeysMap[proto] = resolveRecipient(address, proto); } } void KeyResolverCore::Private::mergeEncryptionKeys() { for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) { const QString &address = it.key(); auto &protocolKeysMap = it.value(); if (!protocolKeysMap[UnknownProtocol].empty()) { // override keys are set for address continue; } const std::vector &keysOpenPGP = protocolKeysMap.value(OpenPGP); const std::vector &keysCMS = protocolKeysMap.value(CMS); if (keysOpenPGP.empty() && keysCMS.empty()) { continue; } else if (!keysOpenPGP.empty() && keysCMS.empty()) { protocolKeysMap[UnknownProtocol] = keysOpenPGP; } else if (keysOpenPGP.empty() && !keysCMS.empty()) { protocolKeysMap[UnknownProtocol] = keysCMS; } else { // check whether OpenPGP keys or S/MIME keys have higher validity const int validityPGP = minimumValidity(keysOpenPGP, address); const int validityCMS = minimumValidity(keysCMS, address); if ((validityPGP > validityCMS) || (validityPGP == validityCMS && mPreferredProtocol == OpenPGP)) { protocolKeysMap[UnknownProtocol] = keysOpenPGP; } else if ((validityCMS > validityPGP) || (validityCMS == validityPGP && mPreferredProtocol == CMS)) { protocolKeysMap[UnknownProtocol] = keysCMS; } else { protocolKeysMap[UnknownProtocol] = keysOpenPGP; } } } } 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; } bool KeyResolverCore::Private::resolve() { qCDebug(LIBKLEO_LOG) << "Starting "; if (!mSign && !mEncrypt) { // nothing to do return true; } // First resolve through overrides resolveOverrides(); // Then look for signing / encryption keys if (mFormat != CMS) { resolveSign(OpenPGP); resolveEnc(OpenPGP); } const QStringList unresolvedPGP = unresolvedRecipients(OpenPGP); bool pgpOnly = unresolvedPGP.empty() && (!mSign || mSigKeys.contains(OpenPGP)); if (mFormat != OpenPGP) { resolveSign(CMS); resolveEnc(CMS); } const QStringList unresolvedCMS = unresolvedRecipients(CMS); bool cmsOnly = unresolvedCMS.empty() && (!mSign || mSigKeys.contains(CMS)); if (mAllowMixed && mFormat == UnknownProtocol) { mergeEncryptionKeys(); } + const QStringList unresolvedMerged = unresolvedRecipients(UnknownProtocol); // Check if we need the user to select different keys. bool needsUser = false; if (!pgpOnly && !cmsOnly) { - for (const auto &unresolved: unresolvedPGP) { - if (unresolvedCMS.contains(unresolved)) { - // We have at least one unresolvable key. - needsUser = true; - break; + if (mAllowMixed && mFormat == UnknownProtocol) { + needsUser = !unresolvedMerged.empty(); + } else { + for (const auto &unresolved: unresolvedPGP) { + if (unresolvedCMS.contains(unresolved)) { + // We have at least one unresolvable key. + needsUser = true; + break; + } } } if (mSign) { // So every recipient could be resolved through // a combination of PGP and S/MIME do we also // have signing keys for both? needsUser |= !(mSigKeys.contains(OpenPGP) && mSigKeys.contains(CMS)); } } if (!needsUser) { if (pgpOnly && cmsOnly) { if (mPreferredProtocol == CMS) { mSigKeys.remove(OpenPGP); for (auto &protocolKeysMap: mEncKeys) { protocolKeysMap.remove(OpenPGP); } } else { mSigKeys.remove(CMS); for (auto &protocolKeysMap: mEncKeys) { protocolKeysMap.remove(CMS); } } } else if (pgpOnly) { mSigKeys.remove(CMS); for (auto &protocolKeysMap: mEncKeys) { protocolKeysMap.remove(CMS); } } else if (cmsOnly) { mSigKeys.remove(OpenPGP); for (auto &protocolKeysMap: mEncKeys) { protocolKeysMap.remove(OpenPGP); } } qCDebug(LIBKLEO_LOG) << "Automatic key resolution done."; return true; } return false; } 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; } bool KeyResolverCore::resolve() { return d->resolve(); } QMap > KeyResolverCore::signingKeys() const { return d->mSigKeys; } QMap>> KeyResolverCore::encryptionKeys() const { QMap>> result; for (auto addressIt = d->mEncKeys.cbegin(); addressIt != d->mEncKeys.cend(); ++addressIt) { const QString &address = addressIt.key(); const auto &protocolKeysMap = addressIt.value(); for (auto protocolIt = protocolKeysMap.cbegin(); protocolIt != protocolKeysMap.cend(); ++protocolIt) { const Protocol protocol = protocolIt.key(); const auto &keys = protocolIt.value(); if (!keys.empty()) { result[protocol][address] = keys; } } } return result; } QStringList KeyResolverCore::unresolvedRecipients(GpgME::Protocol protocol) const { return d->unresolvedRecipients(protocol); }