Commit 16733a65 authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

In mixed mode create "best of" list of resolved OpenPGP and S/MIME keys

If resolution with mixed protocols is allowed, then create a "best of"
list of the resolved OpenPGP and S/MIME keys, i.e. for a given recipient
take either the OpenPGP keys or the S/MIME keys where the choice if
based first on validity and then on the preferred protocol (with OpenPGP
as last resort).

GnuPG-bug-id: 5283
parent af081b7f
Pipeline #55512 passed with stage
in 11 minutes and 13 seconds
......@@ -132,6 +132,22 @@ private Q_SLOTS:
testKey("sender-mixed@example.net", CMS).primaryFingerprint());
}
void test_in_mixed_mode_smime_key_with_higher_validity_is_preferred_over_openpgp_key()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false);
resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net", "prefer-smime@example.net"});
const bool success = resolver.resolve();
QVERIFY(success);
QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).size(), 3);
QVERIFY(resolver.encryptionKeys().value(UnknownProtocol).contains("sender-openpgp@example.net"));
QVERIFY(resolver.encryptionKeys().value(UnknownProtocol).contains("sender-smime@example.net"));
QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("prefer-smime@example.net").size(), 1);
QCOMPARE(resolver.encryptionKeys().value(UnknownProtocol).value("prefer-smime@example.net")[0].primaryFingerprint(),
testKey("prefer-smime@example.net", CMS).primaryFingerprint());
}
void test_encryption_keys_result_has_no_entry_for_unresolved_recipients()
{
KeyResolverCore resolver(/*encrypt=*/ true, /*sign=*/ false);
......
......@@ -43,6 +43,7 @@ public:
, mDialogWindowFlags(Qt::WindowFlags())
, mPreferredProtocol(UnknownProtocol)
{
mCore.setAllowMixedProtocols(allowMixed);
}
~Private() = default;
......
......@@ -46,6 +46,28 @@ static inline bool ValidSigningKey(const Key &key)
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;
}
} // namespace
class KeyResolverCore::Private
......@@ -75,6 +97,7 @@ public:
void setSigningKeys(const QStringList &fingerprints);
std::vector<Key> resolveRecipient(const QString &address, Protocol protocol);
void resolveEnc(Protocol proto);
void mergeEncryptionKeys();
QStringList unresolvedRecipients(GpgME::Protocol protocol) const;
bool resolve();
......@@ -92,6 +115,7 @@ public:
// The cache is needed as a member variable to avoid rebuilding
// it between calls if we are the only user.
std::shared_ptr<const KeyCache> mCache;
bool mAllowMixed = true;
Protocol mPreferredProtocol;
int mMinimumValidity;
QString mCompliance;
......@@ -334,6 +358,40 @@ void KeyResolverCore::Private::resolveEnc(Protocol proto)
}
}
void KeyResolverCore::Private::mergeEncryptionKeys()
{
for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) {
const QString &address = it.key();
auto &protocolKeysMap = it.value();
if (!protocolKeysMap[UnknownProtocol].empty()) {
// override keys are set for address
continue;
}
const std::vector<Key> &keysOpenPGP = protocolKeysMap.value(OpenPGP);
const std::vector<Key> &keysCMS = protocolKeysMap.value(CMS);
if (keysOpenPGP.empty() && keysCMS.empty()) {
continue;
} else if (!keysOpenPGP.empty() && keysCMS.empty()) {
protocolKeysMap[UnknownProtocol] = keysOpenPGP;
} else if (keysOpenPGP.empty() && !keysCMS.empty()) {
protocolKeysMap[UnknownProtocol] = keysCMS;
} else {
// check whether OpenPGP keys or S/MIME keys have higher validity
const int validityPGP = minimumValidity(keysOpenPGP, address);
const int validityCMS = minimumValidity(keysCMS, address);
if ((validityPGP > validityCMS)
|| (validityPGP == validityCMS && mPreferredProtocol == OpenPGP)) {
protocolKeysMap[UnknownProtocol] = keysOpenPGP;
} else if ((validityCMS > validityPGP)
|| (validityCMS == validityPGP && mPreferredProtocol == CMS)) {
protocolKeysMap[UnknownProtocol] = keysCMS;
} else {
protocolKeysMap[UnknownProtocol] = keysOpenPGP;
}
}
}
}
QStringList KeyResolverCore::Private::unresolvedRecipients(GpgME::Protocol protocol) const
{
QStringList result;
......@@ -373,6 +431,10 @@ bool KeyResolverCore::Private::resolve()
const QStringList unresolvedCMS = unresolvedRecipients(CMS);
bool cmsOnly = unresolvedCMS.empty() && (!mSign || mSigKeys.contains(CMS));
if (mAllowMixed && mFormat == UnknownProtocol) {
mergeEncryptionKeys();
}
// Check if we need the user to select different keys.
bool needsUser = false;
if (!pgpOnly && !cmsOnly) {
......@@ -456,6 +518,11 @@ void KeyResolverCore::setOverrideKeys(const QMap<Protocol, QMap<QString, QString
d->setOverrideKeys(overrides);
}
void KeyResolverCore::setAllowMixedProtocols(bool allowMixed)
{
d->mAllowMixed = allowMixed;
}
void KeyResolverCore::setPreferredProtocol(Protocol proto)
{
d->mPreferredProtocol = proto;
......
......@@ -48,6 +48,8 @@ public:
void setOverrideKeys(const QMap<GpgME::Protocol, QMap<QString, QStringList> > &overrides);
void setAllowMixedProtocols(bool allowMixed);
void setPreferredProtocol(GpgME::Protocol proto);
void setMinimumValidity(int validity);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment