Page MenuHome GnuPG

No OneTemporary

diff --git a/autotests/newkeyapprovaldialogtest.cpp b/autotests/newkeyapprovaldialogtest.cpp
index 19350c9a..ada4ea12 100644
--- a/autotests/newkeyapprovaldialogtest.cpp
+++ b/autotests/newkeyapprovaldialogtest.cpp
@@ -1,1089 +1,1115 @@
/*
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 <Libkleo/Compliance>
#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>
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);
key->secret = 1;
key->uids->validity = mapValidity(validity);
+ // add a usable VS-NfD-compliant subkey
+ gpgme_subkey_t subkey;
+ _gpgme_key_add_subkey(key, &subkey);
+ subkey->is_de_vs = 1;
+
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/kleo/defaultkeyfilter.cpp b/src/kleo/defaultkeyfilter.cpp
index b50bdae4..0448ac38 100644
--- a/src/kleo/defaultkeyfilter.cpp
+++ b/src/kleo/defaultkeyfilter.cpp
@@ -1,527 +1,528 @@
/*
defaultkeyfilter.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "defaultkeyfilter.h"
+#include <libkleo/compliance.h>
#include <libkleo/formatting.h>
#include <libkleo/keyhelpers.h>
#include <functional>
#include <memory>
using namespace GpgME;
using namespace Kleo;
static bool is_card_key(const Key &key)
{
const std::vector<Subkey> sks = key.subkeys();
return std::find_if(sks.begin(), sks.end(), std::mem_fn(&Subkey::isCardKey)) != sks.end();
}
class DefaultKeyFilter::Private
{
public:
Private()
{
}
QColor mFgColor;
QColor mBgColor;
QString mName;
QString mIcon;
QString mId;
MatchContexts mMatchContexts = AnyMatchContext;
unsigned int mSpecificity = 0;
bool mItalic = false;
bool mBold = false;
bool mStrikeOut = false;
bool mUseFullFont = false;
QFont mFont;
TriState mRevoked = DoesNotMatter;
TriState mExpired = DoesNotMatter;
TriState mInvalid = DoesNotMatter;
TriState mDisabled = DoesNotMatter;
TriState mRoot = DoesNotMatter;
TriState mCanEncrypt = DoesNotMatter;
TriState mCanSign = DoesNotMatter;
TriState mCanCertify = DoesNotMatter;
TriState mCanAuthenticate = DoesNotMatter;
TriState mQualified = DoesNotMatter;
TriState mCardKey = DoesNotMatter;
TriState mHasSecret = DoesNotMatter;
TriState mIsOpenPGP = DoesNotMatter;
TriState mWasValidated = DoesNotMatter;
TriState mIsDeVs = DoesNotMatter;
TriState mBad = DoesNotMatter;
TriState mValidIfSMIME = DoesNotMatter;
LevelState mOwnerTrust = LevelDoesNotMatter;
GpgME::Key::OwnerTrust mOwnerTrustReferenceLevel = Key::OwnerTrust::Unknown;
LevelState mValidity = LevelDoesNotMatter;
GpgME::UserID::Validity mValidityReferenceLevel = UserID::Validity::Unknown;
};
DefaultKeyFilter::DefaultKeyFilter()
: KeyFilter{}
, d{new Private}
{
}
DefaultKeyFilter::~DefaultKeyFilter() = default;
bool DefaultKeyFilter::matches(const Key &key, MatchContexts contexts) const
{
if (!(d->mMatchContexts & contexts)) {
return false;
}
#ifdef MATCH
#undef MATCH
#endif
#define MATCH(member, method) \
do { \
if (member != DoesNotMatter && key.method() != bool(member == Set)) { \
return false; \
} \
} while (false)
#define IS_MATCH(what) MATCH(d->m##what, is##what)
#define CAN_MATCH(what) MATCH(d->mCan##what, can##what)
IS_MATCH(Revoked);
IS_MATCH(Expired);
IS_MATCH(Invalid);
IS_MATCH(Disabled);
IS_MATCH(Root);
CAN_MATCH(Encrypt);
CAN_MATCH(Sign);
CAN_MATCH(Certify);
CAN_MATCH(Authenticate);
IS_MATCH(Qualified);
if (d->mCardKey != DoesNotMatter) {
if ((d->mCardKey == Set && !is_card_key(key)) || (d->mCardKey == NotSet && is_card_key(key))) {
return false;
}
}
MATCH(d->mHasSecret, hasSecret);
#undef MATCH
if (d->mIsOpenPGP != DoesNotMatter && bool(key.protocol() == GpgME::OpenPGP) != bool(d->mIsOpenPGP == Set)) {
return false;
}
if (d->mWasValidated != DoesNotMatter && bool(key.keyListMode() & GpgME::Validate) != bool(d->mWasValidated == Set)) {
return false;
}
- if (d->mIsDeVs != DoesNotMatter && bool(Kleo::allUserIDsHaveFullValidity(key) && Formatting::isKeyDeVs(key)) != bool(d->mIsDeVs == Set)) {
+ if (d->mIsDeVs != DoesNotMatter && bool(Kleo::allUserIDsHaveFullValidity(key) && DeVSCompliance::allSubkeysAreCompliant(key)) != bool(d->mIsDeVs == Set)) {
return false;
}
if (d->mBad != DoesNotMatter &&
/* This is similar to GPGME::Key::isBad which was introduced in GPGME 1.13.0 */
bool(key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || key.isInvalid()) != bool(d->mBad == Set)) {
return false;
}
const UserID uid = key.userID(0);
if ((key.protocol() == GpgME::CMS) //
&& (d->mValidIfSMIME != DoesNotMatter) //
&& (bool(uid.validity() >= UserID::Full) != bool(d->mValidIfSMIME == Set))) {
return false;
}
switch (d->mOwnerTrust) {
default:
case LevelDoesNotMatter:
break;
case Is:
if (key.ownerTrust() != d->mOwnerTrustReferenceLevel) {
return false;
}
break;
case IsNot:
if (key.ownerTrust() == d->mOwnerTrustReferenceLevel) {
return false;
}
break;
case IsAtLeast:
if (static_cast<int>(key.ownerTrust()) < static_cast<int>(d->mOwnerTrustReferenceLevel)) {
return false;
}
break;
case IsAtMost:
if (static_cast<int>(key.ownerTrust()) > static_cast<int>(d->mOwnerTrustReferenceLevel)) {
return false;
}
break;
}
switch (d->mValidity) {
default:
case LevelDoesNotMatter:
break;
case Is:
if (uid.validity() != d->mValidityReferenceLevel) {
return false;
}
break;
case IsNot:
if (uid.validity() == d->mValidityReferenceLevel) {
return false;
}
break;
case IsAtLeast:
if (static_cast<int>(uid.validity()) < static_cast<int>(d->mValidityReferenceLevel)) {
return false;
}
break;
case IsAtMost:
if (static_cast<int>(uid.validity()) > static_cast<int>(d->mValidityReferenceLevel)) {
return false;
}
break;
}
return true;
}
KeyFilter::FontDescription DefaultKeyFilter::fontDescription() const
{
if (d->mUseFullFont) {
return FontDescription::create(font(), bold(), italic(), strikeOut());
} else {
return FontDescription::create(bold(), italic(), strikeOut());
}
}
void DefaultKeyFilter::setFgColor(const QColor &value)
{
d->mFgColor = value;
}
void DefaultKeyFilter::setBgColor(const QColor &value)
{
d->mBgColor = value;
}
void DefaultKeyFilter::setName(const QString &value)
{
d->mName = value;
}
void DefaultKeyFilter::setIcon(const QString &value)
{
d->mIcon = value;
}
void DefaultKeyFilter::setId(const QString &value)
{
d->mId = value;
}
void DefaultKeyFilter::setMatchContexts(MatchContexts value)
{
d->mMatchContexts = value;
}
void DefaultKeyFilter::setSpecificity(unsigned int value)
{
d->mSpecificity = value;
}
void DefaultKeyFilter::setItalic(bool value)
{
d->mItalic = value;
}
void DefaultKeyFilter::setBold(bool value)
{
d->mBold = value;
}
void DefaultKeyFilter::setStrikeOut(bool value)
{
d->mStrikeOut = value;
}
void DefaultKeyFilter::setUseFullFont(bool value)
{
d->mUseFullFont = value;
}
void DefaultKeyFilter::setFont(const QFont &value)
{
d->mFont = value;
}
void DefaultKeyFilter::setRevoked(DefaultKeyFilter::TriState value)
{
d->mRevoked = value;
}
void DefaultKeyFilter::setExpired(DefaultKeyFilter::TriState value)
{
d->mExpired = value;
}
void DefaultKeyFilter::setInvalid(DefaultKeyFilter::TriState value)
{
d->mInvalid = value;
}
void DefaultKeyFilter::setDisabled(DefaultKeyFilter::TriState value)
{
d->mDisabled = value;
}
void DefaultKeyFilter::setRoot(DefaultKeyFilter::TriState value)
{
d->mRoot = value;
}
void DefaultKeyFilter::setCanEncrypt(DefaultKeyFilter::TriState value)
{
d->mCanEncrypt = value;
}
void DefaultKeyFilter::setCanSign(DefaultKeyFilter::TriState value)
{
d->mCanSign = value;
}
void DefaultKeyFilter::setCanCertify(DefaultKeyFilter::TriState value)
{
d->mCanCertify = value;
}
void DefaultKeyFilter::setCanAuthenticate(DefaultKeyFilter::TriState value)
{
d->mCanAuthenticate = value;
}
void DefaultKeyFilter::setQualified(DefaultKeyFilter::TriState value)
{
d->mQualified = value;
}
void DefaultKeyFilter::setCardKey(DefaultKeyFilter::TriState value)
{
d->mCardKey = value;
}
void DefaultKeyFilter::setHasSecret(DefaultKeyFilter::TriState value)
{
d->mHasSecret = value;
}
void DefaultKeyFilter::setIsOpenPGP(DefaultKeyFilter::TriState value)
{
d->mIsOpenPGP = value;
}
void DefaultKeyFilter::setWasValidated(DefaultKeyFilter::TriState value)
{
d->mWasValidated = value;
}
void DefaultKeyFilter::setOwnerTrust(DefaultKeyFilter::LevelState value)
{
d->mOwnerTrust = value;
}
void DefaultKeyFilter::setOwnerTrustReferenceLevel(GpgME::Key::OwnerTrust value)
{
d->mOwnerTrustReferenceLevel = value;
}
void DefaultKeyFilter::setValidity(DefaultKeyFilter::LevelState value)
{
d->mValidity = value;
}
void DefaultKeyFilter::setValidityReferenceLevel(GpgME::UserID::Validity value)
{
d->mValidityReferenceLevel = value;
}
void DefaultKeyFilter::setIsDeVs(DefaultKeyFilter::TriState value)
{
d->mIsDeVs = value;
}
void DefaultKeyFilter::setIsBad(DefaultKeyFilter::TriState value)
{
d->mBad = value;
}
void DefaultKeyFilter::setValidIfSMIME(DefaultKeyFilter::TriState value)
{
d->mValidIfSMIME = value;
}
QColor DefaultKeyFilter::fgColor() const
{
return d->mFgColor;
}
QColor DefaultKeyFilter::bgColor() const
{
return d->mBgColor;
}
QString DefaultKeyFilter::name() const
{
return d->mName;
}
QString DefaultKeyFilter::icon() const
{
return d->mIcon;
}
QString DefaultKeyFilter::id() const
{
return d->mId;
}
QFont DefaultKeyFilter::font() const
{
return d->mFont;
}
KeyFilter::MatchContexts DefaultKeyFilter::availableMatchContexts() const
{
return d->mMatchContexts;
}
unsigned int DefaultKeyFilter::specificity() const
{
return d->mSpecificity;
}
bool DefaultKeyFilter::italic() const
{
return d->mItalic;
}
bool DefaultKeyFilter::bold() const
{
return d->mBold;
}
bool DefaultKeyFilter::strikeOut() const
{
return d->mStrikeOut;
}
bool DefaultKeyFilter::useFullFont() const
{
return d->mUseFullFont;
}
DefaultKeyFilter::TriState DefaultKeyFilter::revoked() const
{
return d->mRevoked;
}
DefaultKeyFilter::TriState DefaultKeyFilter::expired() const
{
return d->mExpired;
}
DefaultKeyFilter::TriState DefaultKeyFilter::invalid() const
{
return d->mInvalid;
}
DefaultKeyFilter::TriState DefaultKeyFilter::disabled() const
{
return d->mDisabled;
}
DefaultKeyFilter::TriState DefaultKeyFilter::root() const
{
return d->mRoot;
}
DefaultKeyFilter::TriState DefaultKeyFilter::canEncrypt() const
{
return d->mCanEncrypt;
}
DefaultKeyFilter::TriState DefaultKeyFilter::canSign() const
{
return d->mCanSign;
}
DefaultKeyFilter::TriState DefaultKeyFilter::canCertify() const
{
return d->mCanCertify;
}
DefaultKeyFilter::TriState DefaultKeyFilter::canAuthenticate() const
{
return d->mCanAuthenticate;
}
DefaultKeyFilter::TriState DefaultKeyFilter::qualified() const
{
return d->mQualified;
}
DefaultKeyFilter::TriState DefaultKeyFilter::cardKey() const
{
return d->mCardKey;
}
DefaultKeyFilter::TriState DefaultKeyFilter::hasSecret() const
{
return d->mHasSecret;
}
DefaultKeyFilter::TriState DefaultKeyFilter::isOpenPGP() const
{
return d->mIsOpenPGP;
}
DefaultKeyFilter::TriState DefaultKeyFilter::wasValidated() const
{
return d->mWasValidated;
}
DefaultKeyFilter::LevelState DefaultKeyFilter::ownerTrust() const
{
return d->mOwnerTrust;
}
GpgME::Key::OwnerTrust DefaultKeyFilter::ownerTrustReferenceLevel() const
{
return d->mOwnerTrustReferenceLevel;
}
DefaultKeyFilter::LevelState DefaultKeyFilter::validity() const
{
return d->mValidity;
}
GpgME::UserID::Validity DefaultKeyFilter::validityReferenceLevel() const
{
return d->mValidityReferenceLevel;
}
DefaultKeyFilter::TriState DefaultKeyFilter::isDeVS() const
{
return d->mIsDeVs;
}
DefaultKeyFilter::TriState DefaultKeyFilter::isBad() const
{
return d->mBad;
}
DefaultKeyFilter::TriState DefaultKeyFilter::validIfSMIME() const
{
return d->mValidIfSMIME;
}
diff --git a/src/kleo/keyresolvercore.cpp b/src/kleo/keyresolvercore.cpp
index 13786b9f..d38b6fe7 100644
--- a/src/kleo/keyresolvercore.cpp
+++ b/src/kleo/keyresolvercore.cpp
@@ -1,780 +1,780 @@
/* -*- c++ -*-
kleo/keyresolvercore.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2018 Intevation GmbH
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
Based on kpgp.cpp
SPDX-FileCopyrightText: 2001, 2002 the KPGP authors
See file libkdenetwork/AUTHORS.kpgp for details
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "keyresolvercore.h"
#include "enum.h"
#include "keygroup.h"
#include <libkleo/compliance.h>
#include <libkleo/formatting.h>
#include <libkleo/gnupg.h>
#include <libkleo/keycache.h>
#include "kleo/debug.h"
#include <libkleo_debug.h>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace GpgME;
namespace
{
static inline bool ValidEncryptionKey(const Key &key)
{
if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt()) {
return false;
}
return true;
}
static inline bool ValidSigningKey(const Key &key)
{
if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign() || !key.hasSecret()) {
return false;
}
return true;
}
static int keyValidity(const Key &key, const QString &address)
{
// returns the validity of the UID matching the address or, if no UID matches, the maximal validity of all UIDs
int overallValidity = UserID::Validity::Unknown;
for (const auto &uid : key.userIDs()) {
if (QString::fromStdString(uid.addrSpec()).toLower() == address.toLower()) {
return uid.validity();
}
overallValidity = std::max(overallValidity, static_cast<int>(uid.validity()));
}
return overallValidity;
}
static int minimumValidity(const std::vector<Key> &keys, const QString &address)
{
const int minValidity = std::accumulate(keys.cbegin(), //
keys.cend(),
UserID::Ultimate + 1,
[address](int validity, const Key &key) {
return std::min<int>(validity, keyValidity(key, address));
});
return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
}
bool allKeysHaveProtocol(const std::vector<Key> &keys, Protocol protocol)
{
return std::all_of(keys.cbegin(), keys.cend(), [protocol](const Key &key) {
return key.protocol() == protocol;
});
}
bool anyKeyHasProtocol(const std::vector<Key> &keys, Protocol protocol)
{
return std::any_of(std::begin(keys), std::end(keys), [protocol](const Key &key) {
return key.protocol() == protocol;
});
}
} // namespace
class KeyResolverCore::Private
{
public:
Private(KeyResolverCore *qq, bool enc, bool sig, Protocol fmt)
: q(qq)
, mFormat(fmt)
, mEncrypt(enc)
, mSign(sig)
, mCache(KeyCache::instance())
, mPreferredProtocol(UnknownProtocol)
, mMinimumValidity(UserID::Marginal)
{
}
~Private() = default;
bool isAcceptableSigningKey(const Key &key);
bool isAcceptableEncryptionKey(const Key &key, const QString &address = QString());
void setSender(const QString &address);
void addRecipients(const QStringList &addresses);
void setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList>> &overrides);
void resolveOverrides();
std::vector<Key> resolveRecipientWithGroup(const QString &address, Protocol protocol);
void resolveEncryptionGroups();
std::vector<Key> resolveSenderWithGroup(const QString &address, Protocol protocol);
void resolveSigningGroups();
void resolveSign(Protocol proto);
void setSigningKeys(const QStringList &fingerprints);
std::vector<Key> resolveRecipient(const QString &address, Protocol protocol);
void resolveEnc(Protocol proto);
void mergeEncryptionKeys();
Result resolve();
KeyResolverCore *const q;
QString mSender;
QStringList mRecipients;
QMap<Protocol, std::vector<Key>> mSigKeys;
QMap<QString, QMap<Protocol, std::vector<Key>>> mEncKeys;
QMap<QString, QMap<Protocol, QStringList>> mOverrides;
Protocol mFormat;
QStringList mFatalErrors;
bool mEncrypt;
bool mSign;
// The cache is needed as a member variable to avoid rebuilding
// it between calls if we are the only user.
std::shared_ptr<const KeyCache> mCache;
bool mAllowMixed = true;
Protocol mPreferredProtocol;
int mMinimumValidity;
};
bool KeyResolverCore::Private::isAcceptableSigningKey(const Key &key)
{
if (!ValidSigningKey(key)) {
return false;
}
if (DeVSCompliance::isCompliant()) {
- if (!Formatting::isKeyDeVs(key)) {
+ if (!DeVSCompliance::allSubkeysAreCompliant(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 (DeVSCompliance::isCompliant()) {
- if (!Formatting::isKeyDeVs(key)) {
+ if (!DeVSCompliance::allSubkeysAreCompliant(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());
mSender = normStr;
addRecipients({address});
}
void KeyResolverCore::Private::addRecipients(const QStringList &addresses)
{
if (!mEncrypt) {
return;
}
// Internally we work with normalized addresses. Normalization
// matches the gnupg one.
for (const auto &addr : addresses) {
// PGP Uids are defined to be UTF-8 (RFC 4880 §5.11)
const auto normalized = UserID::addrSpecFromString(addr.toUtf8().constData());
if (normalized.empty()) {
// should not happen bug in the caller, non localized
// error for bug reporting.
mFatalErrors << QStringLiteral("The mail address for '%1' could not be extracted").arg(addr);
continue;
}
const QString normStr = QString::fromUtf8(normalized.c_str());
mRecipients << normStr;
// Initially add empty lists of keys for both protocols
mEncKeys[normStr] = {{CMS, {}}, {OpenPGP, {}}};
}
}
void KeyResolverCore::Private::setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList>> &overrides)
{
for (auto protocolIt = overrides.cbegin(); protocolIt != overrides.cend(); ++protocolIt) {
const Protocol &protocol = protocolIt.key();
const auto &addressFingerprintMap = protocolIt.value();
for (auto addressIt = addressFingerprintMap.cbegin(); addressIt != addressFingerprintMap.cend(); ++addressIt) {
const QString &address = addressIt.key();
const QStringList &fingerprints = addressIt.value();
const QString normalizedAddress = QString::fromUtf8(UserID::addrSpecFromString(address.toUtf8().constData()).c_str());
mOverrides[normalizedAddress][protocol] = fingerprints;
}
}
}
namespace
{
std::vector<Key> resolveOverride(const QString &address, Protocol protocol, const QStringList &fingerprints)
{
std::vector<Key> keys;
for (const auto &fprOrId : fingerprints) {
const Key key = KeyCache::instance()->findByKeyIDOrFingerprint(fprOrId.toUtf8().constData());
if (key.isNull()) {
// FIXME: Report to caller
qCDebug(LIBKLEO_LOG) << "Failed to find override key for:" << address << "fpr:" << fprOrId;
continue;
}
if (protocol != UnknownProtocol && key.protocol() != protocol) {
qCDebug(LIBKLEO_LOG) << "Ignoring key" << Formatting::summaryLine(key) << "given as" << Formatting::displayName(protocol) << "override for"
<< address;
continue;
}
qCDebug(LIBKLEO_LOG) << "Using key" << Formatting::summaryLine(key) << "as" << Formatting::displayName(protocol) << "override for" << address;
keys.push_back(key);
}
return keys;
}
}
void KeyResolverCore::Private::resolveOverrides()
{
if (!mEncrypt) {
// No encryption we are done.
return;
}
for (auto addressIt = mOverrides.cbegin(); addressIt != mOverrides.cend(); ++addressIt) {
const QString &address = addressIt.key();
const auto &protocolFingerprintsMap = addressIt.value();
if (!mRecipients.contains(address)) {
qCDebug(LIBKLEO_LOG) << "Overrides provided for an address that is "
"neither sender nor recipient. Address:"
<< address;
continue;
}
const QStringList commonOverride = protocolFingerprintsMap.value(UnknownProtocol);
if (!commonOverride.empty()) {
mEncKeys[address][UnknownProtocol] = resolveOverride(address, UnknownProtocol, commonOverride);
if (protocolFingerprintsMap.contains(OpenPGP)) {
qCDebug(LIBKLEO_LOG) << "Ignoring OpenPGP-specific override for" << address << "in favor of common override";
}
if (protocolFingerprintsMap.contains(CMS)) {
qCDebug(LIBKLEO_LOG) << "Ignoring S/MIME-specific override for" << address << "in favor of common override";
}
} else {
if (mFormat != CMS) {
mEncKeys[address][OpenPGP] = resolveOverride(address, OpenPGP, protocolFingerprintsMap.value(OpenPGP));
}
if (mFormat != OpenPGP) {
mEncKeys[address][CMS] = resolveOverride(address, CMS, protocolFingerprintsMap.value(CMS));
}
}
}
}
std::vector<Key> KeyResolverCore::Private::resolveSenderWithGroup(const QString &address, Protocol protocol)
{
// prefer single-protocol groups over mixed-protocol groups
auto group = mCache->findGroup(address, protocol, KeyCache::KeyUsage::Sign);
if (group.isNull()) {
group = mCache->findGroup(address, UnknownProtocol, KeyCache::KeyUsage::Sign);
}
if (group.isNull()) {
return {};
}
// take the first key matching the protocol
const auto &keys = group.keys();
const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol](const auto &key) {
return key.protocol() == protocol;
});
if (it == std::end(keys)) {
qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has no" << Formatting::displayName(protocol) << "signing key";
return {};
}
const auto key = *it;
if (!isAcceptableSigningKey(key)) {
qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has unacceptable signing key" << key;
return {};
}
return {key};
}
void KeyResolverCore::Private::resolveSigningGroups()
{
auto &protocolKeysMap = mSigKeys;
if (!protocolKeysMap[UnknownProtocol].empty()) {
// already resolved by common override
return;
}
if (mFormat == OpenPGP) {
if (!protocolKeysMap[OpenPGP].empty()) {
// already resolved by override
return;
}
protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP);
} else if (mFormat == CMS) {
if (!protocolKeysMap[CMS].empty()) {
// already resolved by override
return;
}
protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS);
} else {
protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP);
protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS);
}
}
void KeyResolverCore::Private::resolveSign(Protocol proto)
{
if (!mSigKeys[proto].empty()) {
// Explicitly set
return;
}
const auto key = mCache->findBestByMailBox(mSender.toUtf8().constData(), proto, KeyCache::KeyUsage::Sign);
if (key.isNull()) {
qCDebug(LIBKLEO_LOG) << "Failed to find" << Formatting::displayName(proto) << "signing key for" << mSender;
return;
}
if (!isAcceptableSigningKey(key)) {
qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint() << "for" << mSender;
return;
}
mSigKeys.insert(proto, {key});
}
void KeyResolverCore::Private::setSigningKeys(const QStringList &fingerprints)
{
if (mSign) {
for (const auto &fpr : fingerprints) {
const auto key = mCache->findByKeyIDOrFingerprint(fpr.toUtf8().constData());
if (key.isNull()) {
qCDebug(LIBKLEO_LOG) << "Failed to find signing key with fingerprint" << fpr;
continue;
}
mSigKeys[key.protocol()].push_back(key);
}
}
}
std::vector<Key> KeyResolverCore::Private::resolveRecipientWithGroup(const QString &address, Protocol protocol)
{
const auto group = mCache->findGroup(address, protocol, KeyCache::KeyUsage::Encrypt);
if (group.isNull()) {
return {};
}
// If we have one unacceptable group key we reject the
// whole group to avoid the situation where one key is
// skipped or the operation fails.
//
// We are in Autoresolve land here. In the GUI we
// will also show unacceptable group keys so that the
// user can see which key is not acceptable.
const auto &keys = group.keys();
const bool allKeysAreAcceptable = std::all_of(std::begin(keys), std::end(keys), [this](const auto &key) {
return isAcceptableEncryptionKey(key);
});
if (!allKeysAreAcceptable) {
qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has at least one unacceptable key";
return {};
}
for (const auto &k : keys) {
qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << k.primaryFingerprint();
}
std::vector<Key> result;
std::copy(std::begin(keys), std::end(keys), std::back_inserter(result));
return result;
}
void KeyResolverCore::Private::resolveEncryptionGroups()
{
for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) {
const QString &address = it.key();
auto &protocolKeysMap = it.value();
if (!protocolKeysMap[UnknownProtocol].empty()) {
// already resolved by common override
continue;
}
if (mFormat == OpenPGP) {
if (!protocolKeysMap[OpenPGP].empty()) {
// already resolved by override
continue;
}
protocolKeysMap[OpenPGP] = resolveRecipientWithGroup(address, OpenPGP);
} else if (mFormat == CMS) {
if (!protocolKeysMap[CMS].empty()) {
// already resolved by override
continue;
}
protocolKeysMap[CMS] = resolveRecipientWithGroup(address, CMS);
} else {
// prefer single-protocol groups over mixed-protocol groups
const auto openPGPGroupKeys = resolveRecipientWithGroup(address, OpenPGP);
const auto smimeGroupKeys = resolveRecipientWithGroup(address, CMS);
if (!openPGPGroupKeys.empty() && !smimeGroupKeys.empty()) {
protocolKeysMap[OpenPGP] = openPGPGroupKeys;
protocolKeysMap[CMS] = smimeGroupKeys;
} else if (openPGPGroupKeys.empty() && smimeGroupKeys.empty()) {
// no single-protocol groups found;
// if mixed protocols are allowed, then look for any group with encryption keys
if (mAllowMixed) {
protocolKeysMap[UnknownProtocol] = resolveRecipientWithGroup(address, UnknownProtocol);
}
} else {
// there is a single-protocol group only for one protocol; use this group for all protocols
protocolKeysMap[UnknownProtocol] = !openPGPGroupKeys.empty() ? openPGPGroupKeys : smimeGroupKeys;
}
}
}
}
std::vector<Key> KeyResolverCore::Private::resolveRecipient(const QString &address, Protocol protocol)
{
const auto key = mCache->findBestByMailBox(address.toUtf8().constData(), protocol, KeyCache::KeyUsage::Encrypt);
if (key.isNull()) {
qCDebug(LIBKLEO_LOG) << "Failed to find any" << Formatting::displayName(protocol) << "key for:" << address;
return {};
}
if (!isAcceptableEncryptionKey(key, address)) {
qCDebug(LIBKLEO_LOG) << "key for:" << address << key.primaryFingerprint() << "has not enough validity";
return {};
}
qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << key.primaryFingerprint();
return {key};
}
// Try to find matching keys in the provided protocol for the unresolved addresses
void KeyResolverCore::Private::resolveEnc(Protocol proto)
{
for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) {
const QString &address = it.key();
auto &protocolKeysMap = it.value();
if (!protocolKeysMap[proto].empty()) {
// already resolved for current protocol (by override or group)
continue;
}
const std::vector<Key> &commonOverrideOrGroup = protocolKeysMap[UnknownProtocol];
if (!commonOverrideOrGroup.empty()) {
// there is a common override or group; use it for current protocol if possible
if (allKeysHaveProtocol(commonOverrideOrGroup, proto)) {
protocolKeysMap[proto] = commonOverrideOrGroup;
continue;
} else {
qCDebug(LIBKLEO_LOG) << "Common override/group for" << address << "is unusable for" << Formatting::displayName(proto);
continue;
}
}
protocolKeysMap[proto] = resolveRecipient(address, proto);
}
}
auto getBestEncryptionKeys(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol preferredProtocol)
{
QMap<QString, std::vector<Key>> result;
for (auto it = encryptionKeys.begin(); it != encryptionKeys.end(); ++it) {
const QString &address = it.key();
auto &protocolKeysMap = it.value();
const std::vector<Key> &overrideKeys = protocolKeysMap[UnknownProtocol];
if (!overrideKeys.empty()) {
result.insert(address, overrideKeys);
continue;
}
const std::vector<Key> &keysOpenPGP = protocolKeysMap[OpenPGP];
const std::vector<Key> &keysCMS = protocolKeysMap[CMS];
if (keysOpenPGP.empty() && keysCMS.empty()) {
result.insert(address, {});
} else if (!keysOpenPGP.empty() && keysCMS.empty()) {
result.insert(address, keysOpenPGP);
} else if (keysOpenPGP.empty() && !keysCMS.empty()) {
result.insert(address, keysCMS);
} else {
// check whether OpenPGP keys or S/MIME keys have higher validity
const int validityPGP = minimumValidity(keysOpenPGP, address);
const int validityCMS = minimumValidity(keysCMS, address);
if ((validityCMS > validityPGP) || (validityCMS == validityPGP && preferredProtocol == CMS)) {
result.insert(address, keysCMS);
} else {
result.insert(address, keysOpenPGP);
}
}
}
return result;
}
namespace
{
bool hasUnresolvedSender(const QMap<Protocol, std::vector<Key>> &signingKeys, Protocol protocol)
{
return signingKeys.value(protocol).empty();
}
bool hasUnresolvedRecipients(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol protocol)
{
return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys), [protocol](const auto &protocolKeysMap) {
return protocolKeysMap.value(protocol).empty();
});
}
bool anyCommonOverrideHasKeyOfType(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol protocol)
{
return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys), [protocol](const auto &protocolKeysMap) {
return anyKeyHasProtocol(protocolKeysMap.value(UnknownProtocol), protocol);
});
}
auto keysForProtocol(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol protocol)
{
QMap<QString, std::vector<Key>> keys;
for (auto it = std::begin(encryptionKeys), end = std::end(encryptionKeys); it != end; ++it) {
const QString &address = it.key();
const auto &protocolKeysMap = it.value();
keys.insert(address, protocolKeysMap.value(protocol));
}
return keys;
}
template<typename T>
auto concatenate(std::vector<T> v1, const std::vector<T> &v2)
{
v1.reserve(v1.size() + v2.size());
v1.insert(std::end(v1), std::begin(v2), std::end(v2));
return v1;
}
}
KeyResolverCore::Result KeyResolverCore::Private::resolve()
{
qCDebug(LIBKLEO_LOG) << "Starting ";
if (!mSign && !mEncrypt) {
// nothing to do
return {AllResolved, {}, {}};
}
// First resolve through overrides
resolveOverrides();
// check protocols needed for overrides
const bool commonOverridesNeedOpenPGP = anyCommonOverrideHasKeyOfType(mEncKeys, OpenPGP);
const bool commonOverridesNeedCMS = anyCommonOverrideHasKeyOfType(mEncKeys, CMS);
if ((mFormat == OpenPGP && commonOverridesNeedCMS) //
|| (mFormat == CMS && commonOverridesNeedOpenPGP) //
|| (!mAllowMixed && commonOverridesNeedOpenPGP && commonOverridesNeedCMS)) {
// invalid protocol requirements -> clear intermediate result and abort resolution
mEncKeys.clear();
return {Error, {}, {}};
}
// Next look for matching groups of keys
if (mSign) {
resolveSigningGroups();
}
if (mEncrypt) {
resolveEncryptionGroups();
}
// Then look for signing / encryption keys
if (mFormat == OpenPGP || mFormat == UnknownProtocol) {
resolveSign(OpenPGP);
resolveEnc(OpenPGP);
}
const bool pgpOnly = ((!mEncrypt || !hasUnresolvedRecipients(mEncKeys, OpenPGP)) //
&& (!mSign || !hasUnresolvedSender(mSigKeys, OpenPGP)));
if (mFormat == OpenPGP) {
return {
SolutionFlags((pgpOnly ? AllResolved : SomeUnresolved) | OpenPGPOnly),
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
{},
};
}
if (mFormat == CMS || mFormat == UnknownProtocol) {
resolveSign(CMS);
resolveEnc(CMS);
}
const bool cmsOnly = ((!mEncrypt || !hasUnresolvedRecipients(mEncKeys, CMS)) //
&& (!mSign || !hasUnresolvedSender(mSigKeys, CMS)));
if (mFormat == CMS) {
return {
SolutionFlags((cmsOnly ? AllResolved : SomeUnresolved) | CMSOnly),
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
{},
};
}
// check if single-protocol solution has been found
if (cmsOnly && (!pgpOnly || mPreferredProtocol == CMS)) {
if (!mAllowMixed) {
return {
SolutionFlags(AllResolved | CMSOnly),
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
};
} else {
return {
SolutionFlags(AllResolved | CMSOnly),
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
{},
};
}
}
if (pgpOnly) {
if (!mAllowMixed) {
return {
SolutionFlags(AllResolved | OpenPGPOnly),
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
};
} else {
return {
SolutionFlags(AllResolved | OpenPGPOnly),
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
{},
};
}
}
if (!mAllowMixed) {
// return incomplete single-protocol solution
if (mPreferredProtocol == CMS) {
return {
SolutionFlags(SomeUnresolved | CMSOnly),
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
};
} else {
return {
SolutionFlags(SomeUnresolved | OpenPGPOnly),
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
};
}
}
const auto bestEncryptionKeys = getBestEncryptionKeys(mEncKeys, mPreferredProtocol);
// we are in mixed mode, i.e. we need an OpenPGP signing key and an S/MIME signing key
const bool senderIsResolved = (!mSign || (!hasUnresolvedSender(mSigKeys, OpenPGP) && !hasUnresolvedSender(mSigKeys, CMS)));
const bool allRecipientsAreResolved = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [](const auto &keys) {
return !keys.empty();
});
if (senderIsResolved && allRecipientsAreResolved) {
return {
SolutionFlags(AllResolved | MixedProtocols),
{UnknownProtocol, concatenate(mSigKeys.value(OpenPGP), mSigKeys.value(CMS)), bestEncryptionKeys},
{},
};
}
const bool allKeysAreOpenPGP = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [](const auto &keys) {
return allKeysHaveProtocol(keys, OpenPGP);
});
if (allKeysAreOpenPGP) {
return {
SolutionFlags(SomeUnresolved | OpenPGPOnly),
{OpenPGP, mSigKeys.value(OpenPGP), bestEncryptionKeys},
{},
};
}
const bool allKeysAreCMS = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [](const auto &keys) {
return allKeysHaveProtocol(keys, CMS);
});
if (allKeysAreCMS) {
return {
SolutionFlags(SomeUnresolved | CMSOnly),
{CMS, mSigKeys.value(CMS), bestEncryptionKeys},
{},
};
}
return {
SolutionFlags(SomeUnresolved | MixedProtocols),
{UnknownProtocol, concatenate(mSigKeys.value(OpenPGP), mSigKeys.value(CMS)), bestEncryptionKeys},
{},
};
}
KeyResolverCore::KeyResolverCore(bool encrypt, bool sign, Protocol fmt)
: d(new Private(this, encrypt, sign, fmt))
{
}
KeyResolverCore::~KeyResolverCore() = default;
void KeyResolverCore::setSender(const QString &address)
{
d->setSender(address);
}
QString KeyResolverCore::normalizedSender() const
{
return d->mSender;
}
void KeyResolverCore::setRecipients(const QStringList &addresses)
{
d->addRecipients(addresses);
}
void KeyResolverCore::setSigningKeys(const QStringList &fingerprints)
{
d->setSigningKeys(fingerprints);
}
void KeyResolverCore::setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList>> &overrides)
{
d->setOverrideKeys(overrides);
}
void KeyResolverCore::setAllowMixedProtocols(bool allowMixed)
{
d->mAllowMixed = allowMixed;
}
void KeyResolverCore::setPreferredProtocol(Protocol proto)
{
d->mPreferredProtocol = proto;
}
void KeyResolverCore::setMinimumValidity(int validity)
{
d->mMinimumValidity = validity;
}
KeyResolverCore::Result KeyResolverCore::resolve()
{
return d->resolve();
}
diff --git a/src/ui/keyrequester.cpp b/src/ui/keyrequester.cpp
index fb48e745..13c67c58 100644
--- a/src/ui/keyrequester.cpp
+++ b/src/ui/keyrequester.cpp
@@ -1,520 +1,520 @@
/* -*- c++ -*-
keyrequester.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
Based on kpgpui.cpp
SPDX-FileCopyrightText: 2001, 2002 the KPGP authors
See file libkdenetwork/AUTHORS.kpgp for details
This file is part of KPGP, the KDE PGP/GnuPG support library.
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "keyrequester.h"
#include "keyselectiondialog.h"
#include <libkleo/algorithm.h>
#include <libkleo/compliance.h>
#include <libkleo/dn.h>
#include <libkleo/formatting.h>
#include <libkleo/keyhelpers.h>
#include <KLocalizedString>
#include <KMessageBox>
#include <QGpgME/KeyListJob>
#include <QApplication>
#include <QDialog>
#include <QHBoxLayout>
#include <QPushButton>
#include <QString>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
using namespace QGpgME;
using namespace Kleo;
Kleo::KeyRequester::KeyRequester(unsigned int allowedKeys, bool multipleKeys, QWidget *parent)
: QWidget(parent)
, mOpenPGPBackend(nullptr)
, mSMIMEBackend(nullptr)
, mMulti(multipleKeys)
, mKeyUsage(allowedKeys)
, mJobs(0)
, d(nullptr)
{
init();
}
Kleo::KeyRequester::KeyRequester(QWidget *parent)
: QWidget(parent)
, mOpenPGPBackend(nullptr)
, mSMIMEBackend(nullptr)
, mMulti(false)
, mKeyUsage(0)
, mJobs(0)
, d(nullptr)
{
init();
}
void Kleo::KeyRequester::init()
{
auto hlay = new QHBoxLayout(this);
hlay->setContentsMargins(0, 0, 0, 0);
if (DeVSCompliance::isCompliant()) {
mComplianceIcon = new QLabel{this};
mComplianceIcon->setPixmap(QIcon::fromTheme(QStringLiteral("emblem-question")).pixmap(22));
}
// the label where the key id is to be displayed:
mLabel = new QLabel(this);
mLabel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
// the button to unset any key:
mEraseButton = new QPushButton(this);
mEraseButton->setAutoDefault(false);
mEraseButton->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
mEraseButton->setIcon(
QIcon::fromTheme(QApplication::isRightToLeft() ? QStringLiteral("edit-clear-locationbar-ltr") : QStringLiteral("edit-clear-locationbar-rtl")));
mEraseButton->setToolTip(i18n("Clear"));
// the button to call the KeySelectionDialog:
mDialogButton = new QPushButton(i18n("Change..."), this);
mDialogButton->setAutoDefault(false);
if (mComplianceIcon) {
hlay->addWidget(mComplianceIcon);
}
hlay->addWidget(mLabel, 1);
hlay->addWidget(mEraseButton);
hlay->addWidget(mDialogButton);
connect(mEraseButton, &QPushButton::clicked, this, &SigningKeyRequester::slotEraseButtonClicked);
connect(mDialogButton, &QPushButton::clicked, this, &SigningKeyRequester::slotDialogButtonClicked);
setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
setAllowedKeys(mKeyUsage);
}
Kleo::KeyRequester::~KeyRequester()
{
}
const std::vector<GpgME::Key> &Kleo::KeyRequester::keys() const
{
return mKeys;
}
const GpgME::Key &Kleo::KeyRequester::key() const
{
static const GpgME::Key null = GpgME::Key::null;
if (mKeys.empty()) {
return null;
} else {
return mKeys.front();
}
}
void Kleo::KeyRequester::setKeys(const std::vector<GpgME::Key> &keys)
{
mKeys.clear();
for (auto it = keys.begin(); it != keys.end(); ++it) {
if (!it->isNull()) {
mKeys.push_back(*it);
}
}
updateKeys();
}
void Kleo::KeyRequester::setKey(const GpgME::Key &key)
{
mKeys.clear();
if (!key.isNull()) {
mKeys.push_back(key);
}
updateKeys();
}
QString Kleo::KeyRequester::fingerprint() const
{
if (mKeys.empty()) {
return QString();
} else {
return QLatin1String(mKeys.front().primaryFingerprint());
}
}
QStringList Kleo::KeyRequester::fingerprints() const
{
QStringList result;
for (auto it = mKeys.begin(); it != mKeys.end(); ++it) {
if (!it->isNull()) {
if (const char *fpr = it->primaryFingerprint()) {
result.push_back(QLatin1String(fpr));
}
}
}
return result;
}
void Kleo::KeyRequester::setFingerprint(const QString &fingerprint)
{
startKeyListJob(QStringList(fingerprint));
}
void Kleo::KeyRequester::setFingerprints(const QStringList &fingerprints)
{
startKeyListJob(fingerprints);
}
static bool keyIsCompliant(const GpgME::Key &key)
{
return (key.keyListMode() & GpgME::Validate) //
&& Kleo::allUserIDsHaveFullValidity(key) //
- && Formatting::isKeyDeVs(key);
+ && DeVSCompliance::allSubkeysAreCompliant(key);
}
void Kleo::KeyRequester::updateKeys()
{
if (mKeys.empty()) {
if (mComplianceIcon) {
mComplianceIcon->setPixmap(QIcon::fromTheme(QStringLiteral("emblem-unavailable")).pixmap(22));
mComplianceIcon->setToolTip(QString{});
}
mLabel->clear();
return;
}
if (mKeys.size() > 1) {
setMultipleKeysEnabled(true);
}
QStringList labelTexts;
QString toolTipText;
for (std::vector<GpgME::Key>::const_iterator it = mKeys.begin(); it != mKeys.end(); ++it) {
if (it->isNull()) {
continue;
}
const QString fpr = QLatin1String(it->primaryFingerprint());
labelTexts.push_back(fpr.right(8));
toolTipText += fpr.right(8) + QLatin1String(": ");
if (const char *uid = it->userID(0).id()) {
if (it->protocol() == GpgME::OpenPGP) {
toolTipText += QString::fromUtf8(uid);
} else {
toolTipText += Kleo::DN(uid).prettyDN();
}
} else {
toolTipText += xi18n("<placeholder>unknown</placeholder>");
}
toolTipText += QLatin1Char('\n');
}
if (mComplianceIcon) {
if (Kleo::all_of(mKeys, &keyIsCompliant)) {
mComplianceIcon->setPixmap(QIcon::fromTheme(QStringLiteral("emblem-success")).pixmap(22));
mComplianceIcon->setToolTip(DeVSCompliance::name(true));
} else {
mComplianceIcon->setPixmap(QIcon::fromTheme(QStringLiteral("emblem-information")).pixmap(22));
mComplianceIcon->setToolTip(DeVSCompliance::name(false));
}
}
mLabel->setText(labelTexts.join(QLatin1String(", ")));
mLabel->setToolTip(toolTipText);
}
#ifndef __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
#define __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
static void showKeyListError(QWidget *parent, const GpgME::Error &err)
{
Q_ASSERT(err);
const QString msg = i18n(
"<qt><p>An error occurred while fetching "
"the keys from the backend:</p>"
"<p><b>%1</b></p></qt>",
QString::fromLocal8Bit(err.asString()));
KMessageBox::error(parent, msg, i18n("Key Listing Failed"));
}
#endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
void Kleo::KeyRequester::startKeyListJob(const QStringList &fingerprints)
{
if (!mSMIMEBackend && !mOpenPGPBackend) {
return;
}
mTmpKeys.clear();
mJobs = 0;
unsigned int count = 0;
for (QStringList::const_iterator it = fingerprints.begin(); it != fingerprints.end(); ++it) {
if (!(*it).trimmed().isEmpty()) {
++count;
}
}
if (!count) {
// don't fall into the trap that an empty pattern means
// "return all keys" :)
setKey(GpgME::Key::null);
return;
}
if (mOpenPGPBackend) {
KeyListJob *job = mOpenPGPBackend->keyListJob(false); // local, no sigs
if (!job) {
KMessageBox::error(this,
i18n("The OpenPGP backend does not support listing keys. "
"Check your installation."),
i18n("Key Listing Failed"));
} else {
connect(job, &KeyListJob::result, this, &SigningKeyRequester::slotKeyListResult);
connect(job, &KeyListJob::nextKey, this, &SigningKeyRequester::slotNextKey);
const GpgME::Error err =
job->start(fingerprints, mKeyUsage & Kleo::KeySelectionDialog::SecretKeys && !(mKeyUsage & Kleo::KeySelectionDialog::PublicKeys));
if (err) {
showKeyListError(this, err);
} else {
++mJobs;
}
}
}
if (mSMIMEBackend) {
KeyListJob *job = mSMIMEBackend->keyListJob(false); // local, no sigs
if (!job) {
KMessageBox::error(this,
i18n("The S/MIME backend does not support listing keys. "
"Check your installation."),
i18n("Key Listing Failed"));
} else {
connect(job, &KeyListJob::result, this, &SigningKeyRequester::slotKeyListResult);
connect(job, &KeyListJob::nextKey, this, &SigningKeyRequester::slotNextKey);
const GpgME::Error err =
job->start(fingerprints, mKeyUsage & Kleo::KeySelectionDialog::SecretKeys && !(mKeyUsage & Kleo::KeySelectionDialog::PublicKeys));
if (err) {
showKeyListError(this, err);
} else {
++mJobs;
}
}
}
if (mJobs > 0) {
mEraseButton->setEnabled(false);
mDialogButton->setEnabled(false);
}
}
void Kleo::KeyRequester::slotNextKey(const GpgME::Key &key)
{
if (!key.isNull()) {
mTmpKeys.push_back(key);
}
}
void Kleo::KeyRequester::slotKeyListResult(const GpgME::KeyListResult &res)
{
if (res.error()) {
showKeyListError(this, res.error());
}
if (--mJobs <= 0) {
mEraseButton->setEnabled(true);
mDialogButton->setEnabled(true);
setKeys(mTmpKeys);
mTmpKeys.clear();
}
}
void Kleo::KeyRequester::slotDialogButtonClicked()
{
KeySelectionDialog *dlg = mKeys.empty() ? new KeySelectionDialog(mDialogCaption, mDialogMessage, mInitialQuery, mKeyUsage, mMulti, false, this)
: new KeySelectionDialog(mDialogCaption, mDialogCaption, mKeys, mKeyUsage, mMulti, false, this);
if (dlg->exec() == QDialog::Accepted) {
if (mMulti) {
setKeys(dlg->selectedKeys());
} else {
setKey(dlg->selectedKey());
}
Q_EMIT changed();
}
delete dlg;
}
void Kleo::KeyRequester::slotEraseButtonClicked()
{
if (!mKeys.empty()) {
Q_EMIT changed();
}
mKeys.clear();
updateKeys();
}
void Kleo::KeyRequester::setDialogCaption(const QString &caption)
{
mDialogCaption = caption;
}
void Kleo::KeyRequester::setDialogMessage(const QString &msg)
{
mDialogMessage = msg;
}
bool Kleo::KeyRequester::isMultipleKeysEnabled() const
{
return mMulti;
}
void Kleo::KeyRequester::setMultipleKeysEnabled(bool multi)
{
if (multi == mMulti) {
return;
}
if (!multi && !mKeys.empty()) {
mKeys.erase(mKeys.begin() + 1, mKeys.end());
}
mMulti = multi;
updateKeys();
}
unsigned int Kleo::KeyRequester::allowedKeys() const
{
return mKeyUsage;
}
void Kleo::KeyRequester::setAllowedKeys(unsigned int keyUsage)
{
mKeyUsage = keyUsage;
mOpenPGPBackend = nullptr;
mSMIMEBackend = nullptr;
if (mKeyUsage & KeySelectionDialog::OpenPGPKeys) {
mOpenPGPBackend = openpgp();
}
if (mKeyUsage & KeySelectionDialog::SMIMEKeys) {
mSMIMEBackend = smime();
}
if (mOpenPGPBackend && !mSMIMEBackend) {
mDialogCaption = i18n("OpenPGP Key Selection");
mDialogMessage = i18n("Please select an OpenPGP key to use.");
} else if (!mOpenPGPBackend && mSMIMEBackend) {
mDialogCaption = i18n("S/MIME Key Selection");
mDialogMessage = i18n("Please select an S/MIME key to use.");
} else {
mDialogCaption = i18n("Key Selection");
mDialogMessage = i18n("Please select an (OpenPGP or S/MIME) key to use.");
}
}
QPushButton *Kleo::KeyRequester::dialogButton()
{
return mDialogButton;
}
QPushButton *Kleo::KeyRequester::eraseButton()
{
return mEraseButton;
}
static inline unsigned int foo(bool openpgp, bool smime, bool trusted, bool valid)
{
unsigned int result = 0;
if (openpgp) {
result |= Kleo::KeySelectionDialog::OpenPGPKeys;
}
if (smime) {
result |= Kleo::KeySelectionDialog::SMIMEKeys;
}
if (trusted) {
result |= Kleo::KeySelectionDialog::TrustedKeys;
}
if (valid) {
result |= Kleo::KeySelectionDialog::ValidKeys;
}
return result;
}
static inline unsigned int encryptionKeyUsage(bool openpgp, bool smime, bool trusted, bool valid)
{
return foo(openpgp, smime, trusted, valid) | Kleo::KeySelectionDialog::EncryptionKeys | Kleo::KeySelectionDialog::PublicKeys;
}
static inline unsigned int signingKeyUsage(bool openpgp, bool smime, bool trusted, bool valid)
{
return foo(openpgp, smime, trusted, valid) | Kleo::KeySelectionDialog::SigningKeys | Kleo::KeySelectionDialog::SecretKeys;
}
Kleo::EncryptionKeyRequester::EncryptionKeyRequester(bool multi, unsigned int proto, QWidget *parent, bool onlyTrusted, bool onlyValid)
: KeyRequester(encryptionKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid), multi, parent)
, d(nullptr)
{
}
Kleo::EncryptionKeyRequester::EncryptionKeyRequester(QWidget *parent)
: KeyRequester(0, false, parent)
, d(nullptr)
{
}
Kleo::EncryptionKeyRequester::~EncryptionKeyRequester()
{
}
void Kleo::EncryptionKeyRequester::setAllowedKeys(unsigned int proto, bool onlyTrusted, bool onlyValid)
{
KeyRequester::setAllowedKeys(encryptionKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid));
}
Kleo::SigningKeyRequester::SigningKeyRequester(bool multi, unsigned int proto, QWidget *parent, bool onlyTrusted, bool onlyValid)
: KeyRequester(signingKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid), multi, parent)
, d(nullptr)
{
}
Kleo::SigningKeyRequester::SigningKeyRequester(QWidget *parent)
: KeyRequester(0, false, parent)
, d(nullptr)
{
}
Kleo::SigningKeyRequester::~SigningKeyRequester()
{
}
void Kleo::SigningKeyRequester::setAllowedKeys(unsigned int proto, bool onlyTrusted, bool onlyValid)
{
KeyRequester::setAllowedKeys(signingKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid));
}
void Kleo::KeyRequester::virtual_hook(int, void *)
{
}
void Kleo::EncryptionKeyRequester::virtual_hook(int id, void *data)
{
KeyRequester::virtual_hook(id, data);
}
void Kleo::SigningKeyRequester::virtual_hook(int id, void *data)
{
KeyRequester::virtual_hook(id, data);
}
diff --git a/src/ui/newkeyapprovaldialog.cpp b/src/ui/newkeyapprovaldialog.cpp
index fa827cf9..9421a9c1 100644
--- a/src/ui/newkeyapprovaldialog.cpp
+++ b/src/ui/newkeyapprovaldialog.cpp
@@ -1,907 +1,907 @@
/* -*- 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);
}
};
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);
}
};
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);
}
};
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;
}
bool anyKeyHasProtocol(const std::vector<Key> &keys, GpgME::Protocol protocol)
{
return std::any_of(std::begin(keys), std::end(keys), [protocol](const auto &key) {
return key.protocol() == protocol;
});
}
Key findfirstKeyOfType(const std::vector<Key> &keys, 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);
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, QString::fromLocal8Bit(mLastError.asString()), 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 (!Formatting::isKeyDeVs(key) || !Kleo::allUserIDsHaveFullValidity(key)) {
+ if (!DeVSCompliance::allSubkeysAreCompliant(key) || !Kleo::allUserIDsHaveFullValidity(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"
diff --git a/src/utils/algorithm.h b/src/utils/algorithm.h
index 657dcdcd..5815eb7c 100644
--- a/src/utils/algorithm.h
+++ b/src/utils/algorithm.h
@@ -1,133 +1,143 @@
/*
utils/algorithm.h
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <algorithm>
#include <iterator>
namespace Kleo
{
template<typename ForwardIterator, typename T>
ForwardIterator binary_find(ForwardIterator first, ForwardIterator last, const T &value)
{
const ForwardIterator it = std::lower_bound(first, last, value);
return (it == last || value < *it) ? last : it;
}
template<typename ForwardIterator, typename T, typename Compare>
ForwardIterator binary_find(ForwardIterator first, ForwardIterator last, const T &value, Compare comp)
{
const ForwardIterator it = std::lower_bound(first, last, value, comp);
return (it == last || comp(value, *it)) ? last : it;
}
template<typename Container, typename UnaryOperation>
Container transformInPlace(Container &&c, UnaryOperation op)
{
std::transform(std::begin(c), std::end(c), std::begin(c), op);
return std::move(c);
}
/** Convenience helper for checking if the predicate @p p returns @c true
* for all elements in the range @p range. Returns @c true if the range is empty.
* Use ranges::all_of() instead if you can use C++20.
*/
template<typename InputRange, typename UnaryPredicate>
bool all_of(const InputRange &range, UnaryPredicate p)
{
return std::all_of(std::begin(range), std::end(range), p);
}
/** Convenience helper for checking if a @p range contains at least one element
* for which predicate @p p returns @c true. Returns @c false if @p range is
* empty.
* Use ranges::any_of() instead if you can use C++20.
*/
template<typename InputRange, typename UnaryPredicate>
bool any_of(const InputRange &range, UnaryPredicate p)
{
return std::any_of(std::begin(range), std::end(range), p);
}
+/** Convenience helper for counting the number of elements in the range @p range
+ * for which the predicate @p p returns @c true.
+ * Use ranges::count_if() instead if you can use C++20.
+ */
+template<typename InputRange, typename UnaryPredicate>
+auto count_if(const InputRange &range, UnaryPredicate p)
+{
+ return std::count_if(std::begin(range), std::end(range), p);
+}
+
/** Convenience helper for applying the function @p f on all elements of the
* range @p range.
* Use ranges::for_each() instead if you can use C++20.
*/
template<typename InputRange, typename UnaryFunction>
UnaryFunction for_each(const InputRange &range, UnaryFunction f)
{
return std::for_each(std::begin(range), std::end(range), f);
}
/** Convenience helper for checking if a @p container contains an element
* with key equivalent to @p key. This is mainly meant to be used for the
* associative standard containers until we can use their corresponding
* member function in C++20.
*/
template<typename Container, typename Key>
bool contains(const Container &container, const Key &key)
{
return std::find(std::begin(container), std::end(container), key) != std::end(container);
}
/** Convenience helper for checking if a @p container contains an element
* for which predicate @p p returns @c true.
*/
template<typename Container, typename UnaryPredicate>
bool contains_if(const Container &container, UnaryPredicate p)
{
return std::find_if(std::begin(container), std::end(container), p) != std::end(container);
}
/**
* Convenience helper for copying elements of @p range.
* Use std::ranges::copy_if() instead if you can use C++20.
*/
template<typename InputRange, typename OutputIterator, typename UnaryPredicate>
OutputIterator copy(InputRange &&range, OutputIterator result)
{
return std::copy(std::begin(range), std::end(range), result);
}
/**
* Convenience helper for copying elements of @p range for which predicate @p p
* returns @c true.
* Use std::ranges::copy_if() instead if you can use C++20.
*/
template<typename InputRange, typename OutputIterator, typename UnaryPredicate>
OutputIterator copy_if(InputRange &&range, OutputIterator result, UnaryPredicate p)
{
return std::copy_if(std::begin(range), std::end(range), result, p);
}
/**
* Convenience helper for transforming the elements of @p range.
* Use std::ranges::transform() instead if you can use C++20.
*/
template<typename InputRange, typename OutputIterator, typename UnaryOperation>
OutputIterator transform(InputRange &&range, OutputIterator result, UnaryOperation op)
{
return std::transform(std::begin(range), std::end(range), result, op);
}
/**
* Convenience helper for removing elements from a vector @p v for which
* predicate @p p returns @c true.
* Use std::erase_if() instead if you can use C++20.
*/
template<typename Vector, typename UnaryPredicate>
void erase_if(Vector &v, UnaryPredicate p)
{
v.erase(std::remove_if(std::begin(v), std::end(v), p), std::end(v));
}
}
diff --git a/src/utils/compliance.cpp b/src/utils/compliance.cpp
index 003fda48..1a2b5b09 100644
--- a/src/utils/compliance.cpp
+++ b/src/utils/compliance.cpp
@@ -1,100 +1,124 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/compliance.cpp
This file is part of libkleopatra
SPDX-FileCopyrightText: 2022 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 "compliance.h"
#include "algorithm.h"
#include "cryptoconfig.h"
#include "gnupg.h"
#include "stringutils.h"
#include "systeminfo.h"
+#include <libkleo/debug.h>
#include <libkleo/keyfiltermanager.h>
+#include <libkleo_debug.h>
+
#include <KColorScheme>
#include <KLocalizedString>
#include <QPushButton>
+#include <gpgme++/key.h>
+
bool Kleo::DeVSCompliance::isActive()
{
return getCryptoConfigStringValue("gpg", "compliance") == QLatin1String{"de-vs"};
}
bool Kleo::DeVSCompliance::isCompliant()
{
if (!isActive()) {
return false;
}
// The pseudo option compliance_de_vs was fully added in 2.2.34;
// For versions between 2.2.28 and 2.2.33 there was a broken config
// value with a wrong type. So for them we add an extra check. This
// can be removed in future versions because for GnuPG we could assume
// non-compliance for older versions as versions of Kleopatra for
// which this matters are bundled with new enough versions of GnuPG anyway.
if (engineIsVersion(2, 2, 28) && !engineIsVersion(2, 2, 34)) {
return true;
}
return getCryptoConfigIntValue("gpg", "compliance_de_vs", 0) != 0;
}
bool Kleo::DeVSCompliance::algorithmIsCompliant(std::string_view algo)
{
static const std::vector<std::string> compliantAlgorithms = {
"brainpoolP256r1",
"brainpoolP384r1",
"brainpoolP512r1",
"rsa3072",
"rsa4096",
};
return !isActive() || Kleo::contains(compliantAlgorithms, algo);
}
+bool Kleo::DeVSCompliance::allSubkeysAreCompliant(const GpgME::Key &key)
+{
+ if (!isActive()) {
+ return true;
+ }
+ // there is at least one usable subkey
+ const auto usableSubkeys = Kleo::count_if(key.subkeys(), [](const auto &sub) {
+ return !sub.isExpired() && !sub.isRevoked();
+ });
+ if (usableSubkeys == 0) {
+ qCDebug(LIBKLEO_LOG) << __func__ << "No usable subkeys found for key" << key;
+ return false;
+ }
+ // and all usable subkeys are compliant
+ return Kleo::all_of(key.subkeys(), [](const auto &sub) {
+ return sub.isDeVs() || sub.isExpired() || sub.isRevoked();
+ });
+}
+
void Kleo::DeVSCompliance::decorate(QPushButton *button)
{
decorate(button, isCompliant());
}
void Kleo::DeVSCompliance::decorate(QPushButton *button, bool compliant)
{
if (!button) {
return;
}
if (compliant) {
button->setIcon(QIcon::fromTheme(QStringLiteral("security-high")));
if (!SystemInfo::isHighContrastModeActive()) {
const auto bgColor = KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name();
button->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; };").arg(bgColor));
}
} else {
button->setIcon(QIcon::fromTheme(QStringLiteral("security-medium")));
if (!SystemInfo::isHighContrastModeActive()) {
const auto bgColor = KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name();
button->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; };").arg(bgColor));
}
}
}
QString Kleo::DeVSCompliance::name()
{
return name(isCompliant());
}
QString Kleo::DeVSCompliance::name(bool compliant)
{
const auto filterId = compliant ? QStringLiteral("de-vs-filter") : QStringLiteral("not-de-vs-filter");
if (auto filter = KeyFilterManager::instance()->keyFilterByID(filterId)) {
return filter->name();
}
return compliant ? i18n("VS-NfD compliant") : i18n("Not VS-NfD compliant");
}
diff --git a/src/utils/compliance.h b/src/utils/compliance.h
index c07653de..025cd5ac 100644
--- a/src/utils/compliance.h
+++ b/src/utils/compliance.h
@@ -1,76 +1,90 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/compliance.h
This file is part of libkleopatra
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kleo_export.h"
#include <string_view>
class QPushButton;
class QString;
+namespace GpgME
+{
+class Key;
+}
+
namespace Kleo::DeVSCompliance
{
/**
* Returns true, if compliance mode "de-vs" is configured for GnuPG.
* Note: It does not check whether the used GnuPG is actually compliant.
*/
KLEO_EXPORT bool isActive();
/**
* Returns true, if compliance mode "de-vs" is configured for GnuPG and if
* GnuPG passes a basic compliance check, i.e. at least libgcrypt and the used
* RNG are compliant.
*/
KLEO_EXPORT bool isCompliant();
/**
* Returns true, if the given algorithm is compliant with compliance mode
* "de-vs". Always returns true, if compliance mode "de-vs" is not active.
*/
KLEO_EXPORT bool algorithmIsCompliant(std::string_view algo);
+/**
+ * Returns true, if all usable subkeys of the key \p key are compliant with
+ * compliance mode "de-vs". Usable subkeys are those that are neither revoked
+ * nor expired. If the key doesn't have any usable subkeys, then false is
+ * returned.
+ * Always returns true, if compliance mode "de-vs" is not active.
+ */
+KLEO_EXPORT bool allSubkeysAreCompliant(const GpgME::Key &key);
+
/**
* \overload
*
* Sets the appropriate icon and, unless high-contrast mode is active, the
* appropriate background color of \p button depending on the state of
* compliance.
*/
KLEO_EXPORT void decorate(QPushButton *button);
/**
* Sets the appropriate icon and, unless high-contrast mode is active, the
* appropriate background color of \p button depending on the value of
* \p compliant.
*/
KLEO_EXPORT void decorate(QPushButton *button, bool compliant);
/**
* \overload
*
* Returns a localized name for the compliance or non-compliance depending on
* the state of compliance.
*/
KLEO_EXPORT QString name();
/**
* Returns a localized name for the compliance or non-compliance depending on
* the value of \p compliant.
*
* \note The localized name is taken from the de-vs-filter filter resp. the
* not-de-vs-filter. This allows the customization of the name for different
* users because VS-NfD compliance is called differently in different
* environments, e.g. NATO RESTRICTED or EU RESTRICTED.
*/
KLEO_EXPORT QString name(bool compliant);
}
diff --git a/src/utils/formatting.cpp b/src/utils/formatting.cpp
index 4f47be3e..7d84fe46 100644
--- a/src/utils/formatting.cpp
+++ b/src/utils/formatting.cpp
@@ -1,1310 +1,1301 @@
/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
utils/formatting.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2021, 2022 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 "formatting.h"
#include "compliance.h"
#include "cryptoconfig.h"
#include "gnupg.h"
#include "keyhelpers.h"
#include <libkleo/dn.h>
#include <libkleo/keycache.h>
#include <libkleo/keygroup.h>
#include <KEmailAddress>
#include <KLocalizedString>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include <QDateTime>
#include <QIcon>
#include <QLocale>
#include <QRegularExpression>
#include <QString>
#include <QTextDocument> // for Qt::escape
#include <gpgme++/importresult.h>
#include <gpgme++/key.h>
using namespace GpgME;
using namespace Kleo;
//
// Name
//
QString Formatting::prettyName(int proto, const char *id, const char *name_, const char *comment_)
{
if (proto == GpgME::OpenPGP) {
const QString name = QString::fromUtf8(name_);
if (name.isEmpty()) {
return QString();
}
const QString comment = QString::fromUtf8(comment_);
if (comment.isEmpty()) {
return name;
}
return QStringLiteral("%1 (%2)").arg(name, comment);
}
if (proto == GpgME::CMS) {
const DN subject(id);
const QString cn = subject[QStringLiteral("CN")].trimmed();
if (cn.isEmpty()) {
return subject.prettyDN();
}
return cn;
}
return QString();
}
QString Formatting::prettyNameAndEMail(int proto, const char *id, const char *name_, const char *email_, const char *comment_)
{
return prettyNameAndEMail(proto, QString::fromUtf8(id), QString::fromUtf8(name_), prettyEMail(email_, id), QString::fromUtf8(comment_));
}
QString Formatting::prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment)
{
if (proto == GpgME::OpenPGP) {
if (name.isEmpty()) {
if (email.isEmpty()) {
return QString();
} else if (comment.isEmpty()) {
return QStringLiteral("<%1>").arg(email);
} else {
return QStringLiteral("(%2) <%1>").arg(email, comment);
}
}
if (email.isEmpty()) {
if (comment.isEmpty()) {
return name;
} else {
return QStringLiteral("%1 (%2)").arg(name, comment);
}
}
if (comment.isEmpty()) {
return QStringLiteral("%1 <%2>").arg(name, email);
} else {
return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment);
}
}
if (proto == GpgME::CMS) {
const DN subject(id);
const QString cn = subject[QStringLiteral("CN")].trimmed();
if (cn.isEmpty()) {
return subject.prettyDN();
}
return cn;
}
return QString();
}
QString Formatting::prettyUserID(const UserID &uid)
{
if (uid.parent().protocol() == GpgME::OpenPGP) {
return prettyNameAndEMail(uid);
}
const QByteArray id = QByteArray(uid.id()).trimmed();
if (id.startsWith('<')) {
return prettyEMail(uid.email(), uid.id());
}
if (id.startsWith('(')) {
// ### parse uri/dns:
return QString::fromUtf8(uid.id());
} else {
return DN(uid.id()).prettyDN();
}
}
QString Formatting::prettyKeyID(const char *id)
{
if (!id) {
return QString();
}
return QLatin1String("0x") + QString::fromLatin1(id).toUpper();
}
QString Formatting::prettyNameAndEMail(const UserID &uid)
{
return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment());
}
QString Formatting::prettyNameAndEMail(const Key &key)
{
return prettyNameAndEMail(key.userID(0));
}
QString Formatting::prettyName(const Key &key)
{
return prettyName(key.userID(0));
}
QString Formatting::prettyName(const UserID &uid)
{
return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment());
}
QString Formatting::prettyName(const UserID::Signature &sig)
{
return prettyName(GpgME::OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment());
}
//
// EMail
//
QString Formatting::prettyEMail(const Key &key)
{
for (unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) {
const QString email = prettyEMail(key.userID(i));
if (!email.isEmpty()) {
return email;
}
}
return QString();
}
QString Formatting::prettyEMail(const UserID &uid)
{
return prettyEMail(uid.email(), uid.id());
}
QString Formatting::prettyEMail(const UserID::Signature &sig)
{
return prettyEMail(sig.signerEmail(), sig.signerUserID());
}
QString Formatting::prettyEMail(const char *email_, const char *id)
{
QString email;
QString name;
QString comment;
if (email_ && KEmailAddress::splitAddress(QString::fromUtf8(email_), name, email, comment) == KEmailAddress::AddressOk) {
return email;
} else {
return DN(id)[QStringLiteral("EMAIL")].trimmed();
}
}
//
// Tooltip
//
namespace
{
static QString protect_whitespace(QString s)
{
static const QLatin1Char SP(' ');
static const QLatin1Char NBSP('\xA0');
return s.replace(SP, NBSP);
}
template<typename T_arg>
QString format_row(const QString &field, const T_arg &arg)
{
return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg);
}
QString format_row(const QString &field, const QString &arg)
{
return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg.toHtmlEscaped());
}
QString format_row(const QString &field, const char *arg)
{
return format_row(field, QString::fromUtf8(arg));
}
QString format_keytype(const Key &key)
{
const Subkey subkey = key.subkey(0);
if (key.hasSecret()) {
return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString()));
} else {
return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString()));
}
}
QString format_subkeytype(const Subkey &subkey)
{
const auto algo = subkey.publicKeyAlgorithm();
if (algo == Subkey::AlgoECC || algo == Subkey::AlgoECDSA || algo == Subkey::AlgoECDH || algo == Subkey::AlgoEDDSA) {
return QString::fromStdString(subkey.algoName());
}
return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString()));
}
QString format_keyusage(const Key &key)
{
QStringList capabilities;
if (key.canReallySign()) {
if (key.isQualified()) {
capabilities.push_back(i18n("Signing (Qualified)"));
} else {
capabilities.push_back(i18n("Signing"));
}
}
if (key.canEncrypt()) {
capabilities.push_back(i18n("Encryption"));
}
if (key.canCertify()) {
capabilities.push_back(i18n("Certifying User-IDs"));
}
if (key.canAuthenticate()) {
capabilities.push_back(i18n("SSH Authentication"));
}
return capabilities.join(QLatin1String(", "));
}
QString format_subkeyusage(const Subkey &subkey)
{
QStringList capabilities;
if (subkey.canSign()) {
if (subkey.isQualified()) {
capabilities.push_back(i18n("Signing (Qualified)"));
} else {
capabilities.push_back(i18n("Signing"));
}
}
if (subkey.canEncrypt()) {
capabilities.push_back(i18n("Encryption"));
}
if (subkey.canCertify()) {
capabilities.push_back(i18n("Certifying User-IDs"));
}
if (subkey.canAuthenticate()) {
capabilities.push_back(i18n("SSH Authentication"));
}
return capabilities.join(QLatin1String(", "));
}
static QString time_t2string(time_t t)
{
const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t));
return QLocale().toString(dt, QLocale::ShortFormat);
}
static QString make_red(const QString &txt)
{
return QLatin1String("<font color=\"red\">") + txt.toHtmlEscaped() + QLatin1String("</font>");
}
}
QString Formatting::toolTip(const Key &key, int flags)
{
if (flags == 0 || (key.protocol() != GpgME::CMS && key.protocol() != GpgME::OpenPGP)) {
return QString();
}
const Subkey subkey = key.subkey(0);
QString result;
if (flags & Validity) {
if (key.protocol() == GpgME::OpenPGP || (key.keyListMode() & Validate)) {
if (key.isRevoked()) {
result = make_red(i18n("Revoked"));
} else if (key.isExpired()) {
result = make_red(i18n("Expired"));
} else if (key.isDisabled()) {
result = i18n("Disabled");
} else if (key.keyListMode() & GpgME::Validate) {
unsigned int fullyTrusted = 0;
for (const auto &uid : key.userIDs()) {
if (uid.validity() >= UserID::Validity::Full) {
fullyTrusted++;
}
}
if (fullyTrusted == key.numUserIDs()) {
result = i18n("All User-IDs are certified.");
const auto compliance = complianceStringForKey(key);
if (!compliance.isEmpty()) {
result += QStringLiteral("<br>") + compliance;
}
} else {
result = i18np("One User-ID is not certified.", "%1 User-IDs are not certified.", key.numUserIDs() - fullyTrusted);
}
} else {
result = i18n("The validity cannot be checked at the moment.");
}
} else {
result = i18n("The validity cannot be checked at the moment.");
}
}
if (flags == Validity) {
return result;
}
result += QLatin1String("<table border=\"0\">");
if (key.protocol() == GpgME::CMS) {
if (flags & SerialNumber) {
result += format_row(i18n("Serial number"), key.issuerSerial());
}
if (flags & Issuer) {
result += format_row(i18n("Issuer"), key.issuerName());
}
}
if (flags & UserIDs) {
const std::vector<UserID> uids = key.userIDs();
if (!uids.empty()) {
result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User-ID"), prettyUserID(uids.front()));
}
if (uids.size() > 1) {
for (auto it = uids.begin() + 1, end = uids.end(); it != end; ++it) {
if (!it->isRevoked() && !it->isInvalid()) {
result += format_row(i18n("a.k.a."), prettyUserID(*it));
}
}
}
}
if (flags & ExpiryDates) {
result += format_row(i18n("Valid from"), time_t2string(subkey.creationTime()));
if (!subkey.neverExpires()) {
result += format_row(i18n("Valid until"), time_t2string(subkey.expirationTime()));
}
}
if (flags & CertificateType) {
result += format_row(i18n("Type"), format_keytype(key));
}
if (flags & CertificateUsage) {
result += format_row(i18n("Usage"), format_keyusage(key));
}
if (flags & KeyID) {
result += format_row(i18n("Key-ID"), QString::fromLatin1(key.shortKeyID()));
}
if (flags & Fingerprint) {
result += format_row(i18n("Fingerprint"), key.primaryFingerprint());
}
if (flags & OwnerTrust) {
if (key.protocol() == GpgME::OpenPGP) {
result += format_row(i18n("Certification trust"), ownerTrustShort(key));
} else if (key.isRoot()) {
result += format_row(i18n("Trusted issuer?"), key.userID(0).validity() == UserID::Ultimate ? i18n("Yes") : i18n("No"));
}
}
if (flags & StorageLocation) {
if (const char *card = subkey.cardSerialNumber()) {
result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
} else {
result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
}
}
if (flags & Subkeys) {
for (const auto &sub : key.subkeys()) {
result += QLatin1String("<hr/>");
result += format_row(i18n("Subkey"), sub.fingerprint());
if (sub.isRevoked()) {
result += format_row(i18n("Status"), i18n("Revoked"));
} else if (sub.isExpired()) {
result += format_row(i18n("Status"), i18n("Expired"));
}
if (flags & ExpiryDates) {
result += format_row(i18n("Valid from"), time_t2string(sub.creationTime()));
if (!sub.neverExpires()) {
result += format_row(i18n("Valid until"), time_t2string(sub.expirationTime()));
}
}
if (flags & CertificateType) {
result += format_row(i18n("Type"), format_subkeytype(sub));
}
if (flags & CertificateUsage) {
result += format_row(i18n("Usage"), format_subkeyusage(sub));
}
if (flags & StorageLocation) {
if (const char *card = sub.cardSerialNumber()) {
result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
} else {
result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
}
}
}
}
result += QLatin1String("</table>");
return result;
}
namespace
{
template<typename Container>
QString getValidityStatement(const Container &keys)
{
const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) {
return key.protocol() == GpgME::OpenPGP;
});
const bool allKeysAreValidated = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) {
return key.keyListMode() & Validate;
});
if (allKeysAreOpenPGP || allKeysAreValidated) {
const bool someKeysAreBad = std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::isBad));
if (someKeysAreBad) {
return i18n("Some keys are revoked, expired, disabled, or invalid.");
} else {
const bool allKeysAreFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity);
if (allKeysAreFullyValid) {
return i18n("All keys are certified.");
} else {
return i18n("Some keys are not certified.");
}
}
}
return i18n("The validity of the keys cannot be checked at the moment.");
}
}
QString Formatting::toolTip(const KeyGroup &group, int flags)
{
static const unsigned int maxNumKeysForTooltip = 20;
if (group.isNull()) {
return QString();
}
const KeyGroup::Keys &keys = group.keys();
if (keys.size() == 0) {
return i18nc("@info:tooltip", "This group does not contain any keys.");
}
const QString validity = (flags & Validity) ? getValidityStatement(keys) : QString();
if (flags == Validity) {
return validity;
}
// list either up to maxNumKeysForTooltip keys or (maxNumKeysForTooltip-1) keys followed by "and n more keys"
const unsigned int numKeysForTooltip = keys.size() > maxNumKeysForTooltip ? maxNumKeysForTooltip - 1 : keys.size();
QStringList result;
result.reserve(3 + 2 + numKeysForTooltip + 2);
if (!validity.isEmpty()) {
result.push_back(QStringLiteral("<p>"));
result.push_back(validity.toHtmlEscaped());
result.push_back(QStringLiteral("</p>"));
}
result.push_back(QStringLiteral("<p>"));
result.push_back(i18n("Keys:"));
{
auto it = keys.cbegin();
for (unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) {
result.push_back(QLatin1String("<br>") + Formatting::summaryLine(*it).toHtmlEscaped());
}
}
if (keys.size() > numKeysForTooltip) {
result.push_back(QLatin1String("<br>") + i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip));
}
result.push_back(QStringLiteral("</p>"));
return result.join(QLatin1Char('\n'));
}
//
// Creation and Expiration
//
namespace
{
static QDate time_t2date(time_t t)
{
if (!t) {
return {};
}
const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t));
return dt.date();
}
static QString accessible_date_format()
{
return i18nc(
"date format suitable for screen readers; "
"d: day as a number without a leading zero, "
"MMMM: localized month name, "
"yyyy: year as a four digit number",
"MMMM d, yyyy");
}
template<typename T>
QString expiration_date_string(const T &tee, const QString &noExpiration)
{
return tee.neverExpires() ? noExpiration : Formatting::dateString(time_t2date(tee.expirationTime()));
}
template<typename T>
QDate creation_date(const T &tee)
{
return time_t2date(tee.creationTime());
}
template<typename T>
QDate expiration_date(const T &tee)
{
return time_t2date(tee.expirationTime());
}
}
QString Formatting::dateString(time_t t)
{
return dateString(time_t2date(t));
}
QString Formatting::dateString(const QDate &date)
{
return QLocale().toString(date, QLocale::ShortFormat);
}
QString Formatting::accessibleDate(time_t t)
{
return accessibleDate(time_t2date(t));
}
QString Formatting::accessibleDate(const QDate &date)
{
return QLocale().toString(date, accessible_date_format());
}
QString Formatting::expirationDateString(const Key &key, const QString &noExpiration)
{
// if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD),
// then we assume that the date is valid; if the date is zero for a remote key, then
// we don't know if it's unknown or unlimited
return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) //
? i18nc("@info the expiration date of the key is unknown", "unknown")
: expiration_date_string(key.subkey(0), noExpiration);
}
QString Formatting::expirationDateString(const Subkey &subkey, const QString &noExpiration)
{
return expiration_date_string(subkey, noExpiration);
}
QString Formatting::expirationDateString(const UserID::Signature &sig, const QString &noExpiration)
{
return expiration_date_string(sig, noExpiration);
}
QDate Formatting::expirationDate(const Key &key)
{
return expiration_date(key.subkey(0));
}
QDate Formatting::expirationDate(const Subkey &subkey)
{
return expiration_date(subkey);
}
QDate Formatting::expirationDate(const UserID::Signature &sig)
{
return expiration_date(sig);
}
QString Formatting::accessibleExpirationDate(const Key &key, const QString &noExpiration)
{
// if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD),
// then we assume that the date is valid; if the date is zero for a remote key, then
// we don't know if it's unknown or unlimited
return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) //
? i18nc("@info the expiration date of the key is unknown", "unknown")
: accessibleExpirationDate(key.subkey(0), noExpiration);
}
QString Formatting::accessibleExpirationDate(const Subkey &subkey, const QString &noExpiration)
{
if (subkey.neverExpires()) {
return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration;
} else {
return accessibleDate(expirationDate(subkey));
}
}
QString Formatting::accessibleExpirationDate(const UserID::Signature &sig, const QString &noExpiration)
{
if (sig.neverExpires()) {
return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration;
} else {
return accessibleDate(expirationDate(sig));
}
}
QString Formatting::creationDateString(const Key &key)
{
return dateString(creation_date(key.subkey(0)));
}
QString Formatting::creationDateString(const Subkey &subkey)
{
return dateString(creation_date(subkey));
}
QString Formatting::creationDateString(const UserID::Signature &sig)
{
return dateString(creation_date(sig));
}
QDate Formatting::creationDate(const Key &key)
{
return creation_date(key.subkey(0));
}
QDate Formatting::creationDate(const Subkey &subkey)
{
return creation_date(subkey);
}
QDate Formatting::creationDate(const UserID::Signature &sig)
{
return creation_date(sig);
}
QString Formatting::accessibleCreationDate(const Key &key)
{
return accessibleDate(creationDate(key));
}
QString Formatting::accessibleCreationDate(const Subkey &subkey)
{
return accessibleDate(creationDate(subkey));
}
//
// Types
//
QString Formatting::displayName(GpgME::Protocol p)
{
if (p == GpgME::CMS) {
return i18nc("X.509/CMS encryption standard", "S/MIME");
}
if (p == GpgME::OpenPGP) {
return i18n("OpenPGP");
}
return i18nc("Unknown encryption protocol", "Unknown");
}
QString Formatting::type(const Key &key)
{
return displayName(key.protocol());
}
QString Formatting::type(const Subkey &subkey)
{
return QString::fromUtf8(subkey.publicKeyAlgorithmAsString());
}
QString Formatting::type(const KeyGroup &group)
{
Q_UNUSED(group)
return i18nc("a group of keys/certificates", "Group");
}
//
// Status / Validity
//
QString Formatting::ownerTrustShort(const Key &key)
{
return ownerTrustShort(key.ownerTrust());
}
QString Formatting::ownerTrustShort(Key::OwnerTrust trust)
{
switch (trust) {
case Key::Unknown:
return i18nc("unknown trust level", "unknown");
case Key::Never:
return i18n("untrusted");
case Key::Marginal:
return i18nc("marginal trust", "marginal");
case Key::Full:
return i18nc("full trust", "full");
case Key::Ultimate:
return i18nc("ultimate trust", "ultimate");
case Key::Undefined:
return i18nc("undefined trust", "undefined");
default:
Q_ASSERT(!"unexpected owner trust value");
break;
}
return QString();
}
QString Formatting::validityShort(const Subkey &subkey)
{
if (subkey.isRevoked()) {
return i18n("revoked");
}
if (subkey.isExpired()) {
return i18n("expired");
}
if (subkey.isDisabled()) {
return i18n("disabled");
}
if (subkey.isInvalid()) {
return i18n("invalid");
}
return i18nc("as in good/valid signature", "good");
}
QString Formatting::validityShort(const UserID &uid)
{
if (uid.isRevoked()) {
return i18n("revoked");
}
if (uid.isInvalid()) {
return i18n("invalid");
}
switch (uid.validity()) {
case UserID::Unknown:
return i18nc("unknown trust level", "unknown");
case UserID::Undefined:
return i18nc("undefined trust", "undefined");
case UserID::Never:
return i18n("untrusted");
case UserID::Marginal:
return i18nc("marginal trust", "marginal");
case UserID::Full:
return i18nc("full trust", "full");
case UserID::Ultimate:
return i18nc("ultimate trust", "ultimate");
}
return QString();
}
QString Formatting::validityShort(const UserID::Signature &sig)
{
switch (sig.status()) {
case UserID::Signature::NoError:
if (!sig.isInvalid()) {
/* See RFC 4880 Section 5.2.1 */
switch (sig.certClass()) {
case 0x10: /* Generic */
case 0x11: /* Persona */
case 0x12: /* Casual */
case 0x13: /* Positive */
return i18n("valid");
case 0x30:
return i18n("revoked");
default:
return i18n("class %1", sig.certClass());
}
}
Q_FALLTHROUGH();
// fall through:
case UserID::Signature::GeneralError:
return i18n("invalid");
case UserID::Signature::SigExpired:
return i18n("expired");
case UserID::Signature::KeyExpired:
return i18n("certificate expired");
case UserID::Signature::BadSignature:
return i18nc("fake/invalid signature", "bad");
case UserID::Signature::NoPublicKey: {
/* GnuPG returns the same error for no public key as for expired
* or revoked certificates. */
const auto key = KeyCache::instance()->findByKeyIDOrFingerprint(sig.signerKeyID());
if (key.isNull()) {
return i18n("no public key");
} else if (key.isExpired()) {
return i18n("key expired");
} else if (key.isRevoked()) {
return i18n("key revoked");
} else if (key.isDisabled()) {
return i18n("key disabled");
}
/* can't happen */
return QStringLiteral("unknown");
}
}
return QString();
}
QIcon Formatting::validityIcon(const UserID::Signature &sig)
{
switch (sig.status()) {
case UserID::Signature::NoError:
if (!sig.isInvalid()) {
/* See RFC 4880 Section 5.2.1 */
switch (sig.certClass()) {
case 0x10: /* Generic */
case 0x11: /* Persona */
case 0x12: /* Casual */
case 0x13: /* Positive */
return QIcon::fromTheme(QStringLiteral("emblem-success"));
case 0x30:
return QIcon::fromTheme(QStringLiteral("emblem-error"));
default:
return QIcon();
}
}
Q_FALLTHROUGH();
// fall through:
case UserID::Signature::BadSignature:
case UserID::Signature::GeneralError:
return QIcon::fromTheme(QStringLiteral("emblem-error"));
case UserID::Signature::SigExpired:
case UserID::Signature::KeyExpired:
return QIcon::fromTheme(QStringLiteral("emblem-information"));
case UserID::Signature::NoPublicKey:
return QIcon::fromTheme(QStringLiteral("emblem-question"));
}
return QIcon();
}
QString Formatting::formatKeyLink(const Key &key)
{
if (key.isNull()) {
return QString();
}
return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(QLatin1String(key.primaryFingerprint()), Formatting::prettyName(key));
}
QString Formatting::formatForComboBox(const GpgME::Key &key)
{
const QString name = prettyName(key);
QString mail = prettyEMail(key);
if (!mail.isEmpty()) {
mail = QLatin1Char('<') + mail + QLatin1Char('>');
}
return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1String(key.shortKeyID())).simplified();
}
namespace
{
static QString keyToString(const Key &key)
{
Q_ASSERT(!key.isNull());
const QString email = Formatting::prettyEMail(key);
const QString name = Formatting::prettyName(key);
if (name.isEmpty()) {
return email;
} else if (email.isEmpty()) {
return name;
} else {
return QStringLiteral("%1 <%2>").arg(name, email);
}
}
}
const char *Formatting::summaryToString(const Signature::Summary summary)
{
if (summary & Signature::Red) {
return "RED";
}
if (summary & Signature::Green) {
return "GREEN";
}
return "YELLOW";
}
QString Formatting::signatureToString(const Signature &sig, const Key &key)
{
if (sig.isNull()) {
return QString();
}
const bool red = (sig.summary() & Signature::Red);
const bool valid = (sig.summary() & Signature::Valid);
if (red) {
if (key.isNull()) {
if (const char *fpr = sig.fingerprint()) {
return i18n("Bad signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString()));
} else {
return i18n("Bad signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString()));
}
} else {
return i18n("Bad signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString()));
}
} else if (valid) {
if (key.isNull()) {
if (const char *fpr = sig.fingerprint()) {
return i18n("Good signature by unknown certificate %1.", QString::fromLatin1(fpr));
} else {
return i18n("Good signature by an unknown certificate.");
}
} else {
return i18n("Good signature by %1.", keyToString(key));
}
} else if (key.isNull()) {
if (const char *fpr = sig.fingerprint()) {
return i18n("Invalid signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString()));
} else {
return i18n("Invalid signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString()));
}
} else {
return i18n("Invalid signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString()));
}
}
//
// ImportResult
//
QString Formatting::importMetaData(const Import &import, const QStringList &ids)
{
const QString result = importMetaData(import);
if (result.isEmpty()) {
return QString();
} else {
return result + QLatin1Char('\n') + i18n("This certificate was imported from the following sources:") + QLatin1Char('\n') + ids.join(QLatin1Char('\n'));
}
}
QString Formatting::importMetaData(const Import &import)
{
if (import.isNull()) {
return QString();
}
if (import.error().isCanceled()) {
return i18n("The import of this certificate was canceled.");
}
if (import.error()) {
return i18n("An error occurred importing this certificate: %1", QString::fromLocal8Bit(import.error().asString()));
}
const unsigned int status = import.status();
if (status & Import::NewKey) {
return (status & Import::ContainedSecretKey) ? i18n("This certificate was new to your keystore. The secret key is available.")
: i18n("This certificate is new to your keystore.");
}
QStringList results;
if (status & Import::NewUserIDs) {
results.push_back(i18n("New user-ids were added to this certificate by the import."));
}
if (status & Import::NewSignatures) {
results.push_back(i18n("New signatures were added to this certificate by the import."));
}
if (status & Import::NewSubkeys) {
results.push_back(i18n("New subkeys were added to this certificate by the import."));
}
return results.empty() ? i18n("The import contained no new data for this certificate. It is unchanged.") : results.join(QLatin1Char('\n'));
}
//
// Overview in CertificateDetailsDialog
//
QString Formatting::formatOverview(const Key &key)
{
return toolTip(key, AllOptions);
}
QString Formatting::usageString(const Subkey &sub)
{
QStringList usageStrings;
if (sub.canCertify()) {
usageStrings << i18n("Certify");
}
if (sub.canSign()) {
usageStrings << i18n("Sign");
}
if (sub.canEncrypt()) {
usageStrings << i18n("Encrypt");
}
if (sub.canAuthenticate()) {
usageStrings << i18n("Authenticate");
}
return usageStrings.join(QLatin1String(", "));
}
QString Formatting::summaryLine(const Key &key)
{
return keyToString(key) + QLatin1Char(' ')
+ i18nc("(validity, protocol, creation date)",
"(%1, %2, created: %3)",
Formatting::complianceStringShort(key),
displayName(key.protocol()),
Formatting::creationDateString(key));
}
QString Formatting::summaryLine(const KeyGroup &group)
{
switch (group.source()) {
case KeyGroup::ApplicationConfig:
case KeyGroup::GnuPGConfig:
return i18ncp("name of group of keys (n key(s), validity)",
"%2 (1 key, %3)",
"%2 (%1 keys, %3)",
group.keys().size(),
group.name(),
Formatting::complianceStringShort(group));
case KeyGroup::Tags:
return i18ncp("name of group of keys (n key(s), validity, tag)",
"%2 (1 key, %3, tag)",
"%2 (%1 keys, %3, tag)",
group.keys().size(),
group.name(),
Formatting::complianceStringShort(group));
default:
return i18ncp("name of group of keys (n key(s), validity, group ...)",
"%2 (1 key, %3, unknown origin)",
"%2 (%1 keys, %3, unknown origin)",
group.keys().size(),
group.name(),
Formatting::complianceStringShort(group));
}
}
namespace
{
QIcon iconForValidity(UserID::Validity validity)
{
switch (validity) {
case UserID::Ultimate:
case UserID::Full:
case UserID::Marginal:
return QIcon::fromTheme(QStringLiteral("emblem-success"));
case UserID::Never:
return QIcon::fromTheme(QStringLiteral("emblem-error"));
case UserID::Undefined:
case UserID::Unknown:
default:
return QIcon::fromTheme(QStringLiteral("emblem-information"));
}
}
}
// Icon for certificate selection indication
QIcon Formatting::iconForUid(const UserID &uid)
{
return iconForValidity(uid.validity());
}
QString Formatting::validity(const UserID &uid)
{
switch (uid.validity()) {
case UserID::Ultimate:
return i18n("The certificate is marked as your own.");
case UserID::Full:
return i18n("The certificate belongs to this recipient.");
case UserID::Marginal:
return i18n("The trust model indicates marginally that the certificate belongs to this recipient.");
case UserID::Never:
return i18n("This certificate should not be used.");
case UserID::Undefined:
case UserID::Unknown:
default:
return i18n("There is no indication that this certificate belongs to this recipient.");
}
}
QString Formatting::validity(const KeyGroup &group)
{
if (group.isNull()) {
return QString();
}
const KeyGroup::Keys &keys = group.keys();
if (keys.size() == 0) {
return i18n("This group does not contain any keys.");
}
return getValidityStatement(keys);
}
namespace
{
template<typename Container>
UserID::Validity minimalValidity(const Container &keys)
{
const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [](int validity, const Key &key) {
return std::min<int>(validity, minimalValidityOfNotRevokedUserIDs(key));
});
return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
}
}
QIcon Formatting::validityIcon(const KeyGroup &group)
{
return iconForValidity(minimalValidity(group.keys()));
}
bool Formatting::uidsHaveFullValidity(const Key &key)
{
return allUserIDsHaveFullValidity(key);
}
QString Formatting::complianceMode()
{
const auto complianceValue = getCryptoConfigStringValue("gpg", "compliance");
return complianceValue == QLatin1String("gnupg") ? QString() : complianceValue;
}
bool Formatting::isKeyDeVs(const GpgME::Key &key)
{
- for (const auto &sub : key.subkeys()) {
- if (sub.isExpired() || sub.isRevoked()) {
- // Ignore old subkeys
- continue;
- }
- if (!sub.isDeVs()) {
- return false;
- }
- }
- return true;
+ return DeVSCompliance::allSubkeysAreCompliant(key);
}
QString Formatting::complianceStringForKey(const GpgME::Key &key)
{
// There will likely be more in the future for other institutions
// for now we only have DE-VS
if (DeVSCompliance::isCompliant()) {
return isRemoteKey(key) //
? i18nc("@info the compliance of the key with certain requirements is unknown", "unknown")
- : DeVSCompliance::name(Kleo::allUserIDsHaveFullValidity(key) && isKeyDeVs(key));
+ : DeVSCompliance::name(Kleo::allUserIDsHaveFullValidity(key) && DeVSCompliance::allSubkeysAreCompliant(key));
}
return QString();
}
QString Formatting::complianceStringShort(const GpgME::Key &key)
{
const bool keyValidityChecked = (key.keyListMode() & GpgME::Validate);
if (keyValidityChecked && Kleo::allUserIDsHaveFullValidity(key)) {
- if (DeVSCompliance::isCompliant() && Formatting::isKeyDeVs(key)) {
+ if (DeVSCompliance::isCompliant() && DeVSCompliance::allSubkeysAreCompliant(key)) {
return QStringLiteral("★ ") + DeVSCompliance::name(true);
}
return i18nc("As in all user IDs are valid.", "certified");
}
if (key.isExpired()) {
return i18n("expired");
}
if (key.isRevoked()) {
return i18n("revoked");
}
if (key.isDisabled()) {
return i18n("disabled");
}
if (key.isInvalid()) {
return i18n("invalid");
}
if (keyValidityChecked) {
return i18nc("As in not all user IDs are valid.", "not certified");
}
return i18nc("The validity of the user IDs has not been/could not be checked", "not checked");
}
QString Formatting::complianceStringShort(const KeyGroup &group)
{
const KeyGroup::Keys &keys = group.keys();
const bool allKeysFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity);
if (allKeysFullyValid) {
return i18nc("As in all keys are valid.", "all certified");
}
return i18nc("As in not all keys are valid.", "not all certified");
}
QString Formatting::prettyID(const char *id)
{
if (!id) {
return QString();
}
QString ret = QString::fromLatin1(id).toUpper().replace(QRegularExpression(QStringLiteral("(....)")), QStringLiteral("\\1 ")).trimmed();
// For the standard 10 group fingerprint let us use a double space in the
// middle to increase readability
if (ret.size() == 49) {
ret.insert(24, QLatin1Char(' '));
}
return ret;
}
QString Formatting::accessibleHexID(const char *id)
{
static const QRegularExpression groupOfFourRegExp{QStringLiteral("(?:(.)(.)(.)(.))")};
QString ret;
ret = QString::fromLatin1(id);
if (!ret.isEmpty() && (ret.size() % 4 == 0)) {
ret = ret.replace(groupOfFourRegExp, QStringLiteral("\\1 \\2 \\3 \\4, ")).chopped(2);
}
return ret;
}
QString Formatting::origin(int o)
{
switch (o) {
case Key::OriginKS:
return i18n("Keyserver");
case Key::OriginDane:
return QStringLiteral("DANE");
case Key::OriginWKD:
return QStringLiteral("WKD");
case Key::OriginURL:
return QStringLiteral("URL");
case Key::OriginFile:
return i18n("File import");
case Key::OriginSelf:
return i18n("Generated");
case Key::OriginOther:
case Key::OriginUnknown:
default:
return i18n("Unknown");
}
}
QString Formatting::deVsString(bool compliant)
{
return DeVSCompliance::name(compliant);
}
namespace
{
QString formatTrustScope(const char *trustScope)
{
static const QRegularExpression escapedNonAlphaNum{QStringLiteral(R"(\\([^0-9A-Za-z]))")};
const auto scopeRegExp = QString::fromUtf8(trustScope);
if (scopeRegExp.startsWith(u"<[^>]+[@.]") && scopeRegExp.endsWith(u">$")) {
// looks like a trust scope regular expression created by gpg
auto domain = scopeRegExp.mid(10, scopeRegExp.size() - 10 - 2);
domain.replace(escapedNonAlphaNum, QStringLiteral(R"(\1)"));
return domain;
}
return scopeRegExp;
}
}
QString Formatting::trustSignatureDomain(const GpgME::UserID::Signature &sig)
{
return formatTrustScope(sig.trustScope());
}
QString Formatting::trustSignature(const GpgME::UserID::Signature &sig)
{
switch (sig.trustValue()) {
case TrustSignatureTrust::Partial:
return i18nc("Certifies this key as partially trusted introducer for 'domain name'.",
"Certifies this key as partially trusted introducer for '%1'.",
trustSignatureDomain(sig));
case TrustSignatureTrust::Complete:
return i18nc("Certifies this key as fully trusted introducer for 'domain name'.",
"Certifies this key as fully trusted introducer for '%1'.",
trustSignatureDomain(sig));
default:
return {};
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Jan 3, 11:46 PM (1 d, 7 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
ee/1e/c58b400fdbd2b64d8b3a40c7249e

Event Timeline