Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34768375
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
46 KB
Subscribers
None
View Options
diff --git a/src/kleo/keyresolver.cpp b/src/kleo/keyresolver.cpp
index 4ef8af94..357a305a 100644
--- a/src/kleo/keyresolver.cpp
+++ b/src/kleo/keyresolver.cpp
@@ -1,219 +1,245 @@
/* -*- c++ -*-
keyresolver.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 "keyresolver.h"
#include "keyresolvercore.h"
#include <libkleo/formatting.h>
#include <libkleo/keycache.h>
#include <libkleo/keygroup.h>
#include <libkleo/newkeyapprovaldialog.h>
#include <libkleo_debug.h>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace GpgME;
class KeyResolver::Private
{
public:
Private(KeyResolver *qq, bool enc, bool sig, Protocol fmt, bool allowMixed)
: q(qq)
- , mCore(enc, sig, fmt)
+ , mCore(std::make_unique<KeyResolverCore>(enc, sig, fmt))
, mFormat(fmt)
, mEncrypt(enc)
, mSign(sig)
, mAllowMixed(allowMixed)
, mCache(KeyCache::instance())
, mDialogWindowFlags(Qt::WindowFlags())
, mPreferredProtocol(UnknownProtocol)
{
- mCore.setAllowMixedProtocols(allowMixed);
+ Q_ASSERT(mCore);
+ mCore->setAllowMixedProtocols(allowMixed);
+ }
+
+ Private(KeyResolver *qq, std::unique_ptr<KeyResolverCore> core, bool allowMixed)
+ : q(qq)
+ , mCore(std::move(core))
+ , mFormat(mCore->format())
+ , mEncrypt(mCore->encrypt())
+ , mSign(mCore->sign())
+ , mAllowMixed(allowMixed)
+ , mCache(KeyCache::instance())
+ , mDialogWindowFlags(Qt::WindowFlags())
+ , mPreferredProtocol(UnknownProtocol)
+ {
+ Q_ASSERT(mCore);
+ mCore->setAllowMixedProtocols(allowMixed);
}
~Private() = default;
KeyResolver::Solution expandUnresolvedGroups(KeyResolver::Solution solution);
void showApprovalDialog(KeyResolverCore::Result result, QWidget *parent);
void dialogAccepted();
KeyResolver *const q;
- KeyResolverCore mCore;
+ std::unique_ptr<KeyResolverCore> const mCore;
Solution mResult;
Protocol mFormat;
bool mEncrypt;
bool mSign;
bool mAllowMixed;
// 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;
std::unique_ptr<NewKeyApprovalDialog> mDialog;
Qt::WindowFlags mDialogWindowFlags;
Protocol mPreferredProtocol;
};
static bool lessThan(const Key &leftKey, const Key &rightKey)
{
// shouldn't happen, but still put null keys at the end
if (leftKey.isNull()) {
return false;
}
if (rightKey.isNull()) {
return true;
}
// first sort by the displayed name and/or email address
const auto leftNameAndOrEmail = Formatting::nameAndEmailForSummaryLine(leftKey);
const auto rightNameAndOrEmail = Formatting::nameAndEmailForSummaryLine(rightKey);
const int cmp = QString::localeAwareCompare(leftNameAndOrEmail, rightNameAndOrEmail);
if (cmp) {
return cmp < 0;
}
// sort certificates with identical name/email address by their fingerprints
return strcmp(leftKey.primaryFingerprint(), rightKey.primaryFingerprint()) < 0;
}
KeyResolver::Solution KeyResolver::Private::expandUnresolvedGroups(KeyResolver::Solution solution)
{
for (auto it = solution.encryptionKeys.begin(); it != solution.encryptionKeys.end(); ++it) {
const auto &address = it.key();
if (!it.value().empty()) {
continue;
}
const auto keyMatchingAddress = mCache->findBestByMailBox(address.toUtf8().constData(), solution.protocol, KeyCache::KeyUsage::Encrypt);
if (!keyMatchingAddress.isNull()) {
continue;
}
const auto groupMatchingAddress = mCache->findGroup(address, solution.protocol, KeyCache::KeyUsage::Encrypt);
if (!groupMatchingAddress.isNull()) {
qCDebug(LIBKLEO_LOG) << __func__ << "Expanding unresolved" << address << "with matching group";
const auto &groupKeys = groupMatchingAddress.keys();
std::vector<Key> keys;
keys.reserve(groupKeys.size());
std::copy(groupKeys.begin(), groupKeys.end(), std::back_inserter(keys));
std::sort(keys.begin(), keys.end(), lessThan);
it.value() = keys;
}
}
return solution;
}
void KeyResolver::Private::showApprovalDialog(KeyResolverCore::Result result, QWidget *parent)
{
const auto preferredSolution = expandUnresolvedGroups(std::move(result.solution));
const auto alternativeSolution = expandUnresolvedGroups(std::move(result.alternative));
- const QString sender = mCore.normalizedSender();
+ const QString sender = mCore->normalizedSender();
mDialog = std::make_unique<NewKeyApprovalDialog>(mEncrypt,
mSign,
sender,
std::move(preferredSolution),
std::move(alternativeSolution),
mAllowMixed,
mFormat,
parent,
mDialogWindowFlags);
connect(mDialog.get(), &QDialog::accepted, q, [this]() {
dialogAccepted();
});
connect(mDialog.get(), &QDialog::rejected, q, [this]() {
Q_EMIT q->keysResolved(false, false);
});
mDialog->open();
}
void KeyResolver::Private::dialogAccepted()
{
mResult = mDialog->result();
Q_EMIT q->keysResolved(true, false);
}
void KeyResolver::start(bool showApproval, QWidget *parentWidget)
{
qCDebug(LIBKLEO_LOG) << "Starting ";
if (!d->mSign && !d->mEncrypt) {
// nothing to do
return Q_EMIT keysResolved(true, true);
}
- const auto result = d->mCore.resolve();
+ const auto result = d->mCore->resolve();
const bool success = (result.flags & KeyResolverCore::AllResolved);
if (success && !showApproval) {
d->mResult = std::move(result.solution);
Q_EMIT keysResolved(true, false);
return;
} else if (success) {
qCDebug(LIBKLEO_LOG) << "No need for the user showing approval anyway.";
}
d->showApprovalDialog(std::move(result), parentWidget);
}
KeyResolver::KeyResolver(bool encrypt, bool sign, Protocol fmt, bool allowMixed)
: d(new Private(this, encrypt, sign, fmt, allowMixed))
{
}
+KeyResolver::KeyResolver(std::unique_ptr<KeyResolverCore> keyResolverCore, bool allowMixed)
+ : d(new Private(this, std::move(keyResolverCore), allowMixed))
+{
+}
+
Kleo::KeyResolver::~KeyResolver() = default;
void KeyResolver::setRecipients(const QStringList &addresses)
{
- d->mCore.setRecipients(addresses);
+ d->mCore->setRecipients(addresses);
}
void KeyResolver::setSender(const QString &address)
{
- d->mCore.setSender(address);
+ d->mCore->setSender(address);
+}
+
+QString KeyResolver::normalizedSender() const
+{
+ return d->mCore->normalizedSender();
}
void KeyResolver::setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList>> &overrides)
{
- d->mCore.setOverrideKeys(overrides);
+ d->mCore->setOverrideKeys(overrides);
}
void KeyResolver::setSigningKeys(const QStringList &fingerprints)
{
- d->mCore.setSigningKeys(fingerprints);
+ d->mCore->setSigningKeys(fingerprints);
}
KeyResolver::Solution KeyResolver::result() const
{
return d->mResult;
}
void KeyResolver::setDialogWindowFlags(Qt::WindowFlags flags)
{
d->mDialogWindowFlags = flags;
}
void KeyResolver::setPreferredProtocol(Protocol proto)
{
- d->mCore.setPreferredProtocol(proto);
+ d->mCore->setPreferredProtocol(proto);
}
void KeyResolver::setMinimumValidity(int validity)
{
- d->mCore.setMinimumValidity(validity);
+ d->mCore->setMinimumValidity(validity);
}
#include "moc_keyresolver.cpp"
diff --git a/src/kleo/keyresolver.h b/src/kleo/keyresolver.h
index ec39aff1..d2602e5b 100644
--- a/src/kleo/keyresolver.h
+++ b/src/kleo/keyresolver.h
@@ -1,197 +1,211 @@
/* -*- c++ -*-
keyresolver.h
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
*/
#pragma once
#include "kleo_export.h"
#include <QMap>
#include <QObject>
#include <QString>
#include <QStringList>
#include <gpgme++/global.h>
#include <memory>
#include <vector>
namespace GpgME
{
class Key;
}
namespace Kleo
{
+class KeyResolverCore;
/**
* Class to find Keys for E-Mail signing and encryption.
*
* The KeyResolver uses the Keycache to find keys for signing
* or encryption.
*
* Overrides can be provided for address book integration.
*
* If no override key(s) are provided for an address and no
* KeyGroup for this address is found, then the key
* with a uid that matches the address and has the highest
* validity is used. If both keys have the same validity,
* then the key with the newest subkey is used.
*
* The KeyResolver also supports groups so the number of
* encryption keys does not necessarily
* need to match the amount of sender addresses. For this reason
* maps are used to map addresses to lists of keys.
*
* The keys can be OpenPGP keys and S/MIME (CMS) keys.
* As a caller you need to partition the keys by their protocol and
* send one message for each protocol for the recipients and signed
* by the signing keys.
*/
class KLEO_EXPORT KeyResolver : public QObject
{
Q_OBJECT
public:
/**
* Solution represents the solution found by the KeyResolver.
*/
struct Solution {
/**
* This property holds a hint at the protocol of the signing and encryption
* keys, i.e. if @p protocol is either @c GpgME::OpenPGP or @c GpgME::CMS,
* then all keys have the corresponding protocol. Otherwise, the keys have
* mixed protocols.
*/
GpgME::Protocol protocol = GpgME::UnknownProtocol;
/**
* This property contains the signing keys to use. It contains zero or one
* OpenPGP key and zero or one S/MIME key.
*/
std::vector<GpgME::Key> signingKeys;
/**
* This property contains the encryption keys to use for the different recipients.
*
* The list of keys will contain for regular users either one S/MIME key
* or one OpenPGP key. For a group address, the list of keys will instead contain
* the keys required to encrypt for every member of the group.
*
* The keys of the map represent the normalized email addresses of the recipients.
*
* @see Kleo::KeyGroup
*/
QMap<QString, std::vector<GpgME::Key>> encryptionKeys;
};
/** Creates a new key resolver object.
*
* @param encrypt: Should encryption keys be selected.
* @param sign: Should signing keys be selected.
* @param protocol: A specific key protocol (OpenPGP, S/MIME) for selection. Default: Both protocols.
* @param allowMixed: Specify if multiple message formats may be resolved.
**/
explicit KeyResolver(bool encrypt, bool sign, GpgME::Protocol protocol = GpgME::UnknownProtocol, bool allowMixed = true);
+ /**
+ * Creates a new key resolver object using a custom key resolver implementation.
+ *
+ * @param keyResolverCore: A custom key resolver implementation
+ * @param allowMixed: Specify if multiple message formats may be resolved.
+ **/
+ explicit KeyResolver(std::unique_ptr<KeyResolverCore> keyResolverCore, bool allowMixed = true);
+
~KeyResolver() override;
/**
* Set the list of recipient addresses.
*
* @param addresses: A list of (not necessarily normalized) email addresses
*/
void setRecipients(const QStringList &addresses);
/**
* Set the sender's address.
*
* This address is added to the list of recipients (for encryption to self)
* and it is used for signing key resolution, if the signing keys are not
* explicitly set through setSigningKeys.
*
* @param sender: The sender of this message.
*/
void setSender(const QString &sender);
+ /**
+ * Get the normalized sender email address
+ */
+ QString normalizedSender() const;
+
/**
* Set up possible override keys for recipients addresses.
* The keys for the fingerprints are looked
* up and used when found.
*
* Overrides for @c GpgME::UnknownProtocol are used regardless of the
* protocol. Overrides for a specific protocol are only used for this
* protocol. Overrides for @c GpgME::UnknownProtocol takes precedence over
* overrides for a specific protocol.
*
* @param overrides: A map of \<protocol\> -> (\<address\> \<fingerprints\>)
*/
void setOverrideKeys(const QMap<GpgME::Protocol, QMap<QString, QStringList>> &overrides);
/**
* Set explicit signing keys to use.
*/
void setSigningKeys(const QStringList &fingerprints);
/**
* Set the minimum user id validity for autoresolution.
*
* The default value is marginal
*
* @param validity int representation of a GpgME::UserID::Validity.
*/
void setMinimumValidity(int validity);
/**
* Get the result of the resolution.
*
* @return the resolved keys for signing and encryption.
*/
Solution result() const;
/**
* Starts the key resolving procedure. Emits keysResolved on success or
* error.
*
* @param showApproval: If set to true a dialog listing the keys
* will always be shown.
* @param parentWidget: Optional, a Widget to use as parent for dialogs.
*/
void start(bool showApproval, QWidget *parentWidget = nullptr);
/**
* Set window flags for a possible dialog.
*/
void setDialogWindowFlags(Qt::WindowFlags flags);
/**
* Set the protocol that is preferred to be displayed first when
* it is not clear from the keys. E.g. if both OpenPGP and S/MIME
* can be resolved.
*/
void setPreferredProtocol(GpgME::Protocol proto);
Q_SIGNALS:
/**
* Emitted when key resolution finished.
*
* @param success: The general result. If true continue sending,
* if false abort.
* @param sendUnencrypted: If there could be no key found for one of
* the recipients the user was queried if the
* mail should be sent out unencrypted.
* sendUnencrypted is true if the user agreed
* to this.*/
void keysResolved(bool success, bool sendUnencrypted);
private:
class Private;
std::unique_ptr<Private> const d;
};
} // namespace Kleo
diff --git a/src/kleo/keyresolvercore.cpp b/src/kleo/keyresolvercore.cpp
index e9f59007..556e42c0 100644
--- a/src/kleo/keyresolvercore.cpp
+++ b/src/kleo/keyresolvercore.cpp
@@ -1,769 +1,784 @@
/* -*- 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/compat.h>
#include <libkleo/compliance.h>
#include <libkleo/formatting.h>
#include <libkleo/gnupg.h>
#include <libkleo/keycache.h>
#include <libkleo/keyhelpers.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() || !Kleo::keyHasEncrypt(key)) {
return false;
}
return true;
}
static inline bool ValidSigningKey(const Key &key)
{
if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !Kleo::keyHasSign(key) || !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;
}
} // 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() && !DeVSCompliance::keyIsCompliant(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() && !DeVSCompliance::keyIsCompliant(key)) {
qCDebug(LIBKLEO_LOG) << "Rejected enc key" << key.primaryFingerprint() << "because it is not de-vs compliant.";
return false;
}
if (address.isEmpty()) {
// group key must satisfy minimum validity for all user IDs
return Kleo::minimalValidityOfNotRevokedUserIDs(key) >= mMinimumValidity;
}
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 {
if (protocolKeysMap[OpenPGP].empty()) {
protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP);
}
if (protocolKeysMap[CMS].empty()) {
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();
const 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;
+bool KeyResolverCore::encrypt() const
+{
+ return d->mEncrypt;
+}
+
+bool KeyResolverCore::sign() const
+{
+ return d->mSign;
+}
+
+GpgME::Protocol KeyResolverCore::format() const
+{
+ return d->mFormat;
+}
+
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/kleo/keyresolvercore.h b/src/kleo/keyresolvercore.h
index 185c0a23..2e4d50d5 100644
--- a/src/kleo/keyresolvercore.h
+++ b/src/kleo/keyresolvercore.h
@@ -1,85 +1,94 @@
/* -*- c++ -*-
kleo/keyresolvercore.h
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
*/
#pragma once
#include "kleo_export.h"
#include <Libkleo/KeyResolver>
#include <QMap>
#include <QStringList>
#include <gpgme++/global.h>
#include <memory>
#include <vector>
class QString;
namespace GpgME
{
class Key;
}
namespace Kleo
{
+/**
+ * Key resolver
+ */
class KLEO_EXPORT KeyResolverCore
{
public:
enum SolutionFlags {
// clang-format off
SomeUnresolved = 0,
AllResolved = 1,
OpenPGPOnly = 2,
CMSOnly = 4,
MixedProtocols = OpenPGPOnly | CMSOnly,
Error = 0x1000,
ResolvedMask = AllResolved | Error,
ProtocolsMask = OpenPGPOnly | CMSOnly | Error,
// clang-format on
};
struct Result {
SolutionFlags flags;
KeyResolver::Solution solution;
KeyResolver::Solution alternative;
};
explicit KeyResolverCore(bool encrypt, bool sign, GpgME::Protocol format = GpgME::UnknownProtocol);
~KeyResolverCore();
+ bool encrypt() const;
+
+ bool sign() const;
+
+ GpgME::Protocol format() const;
+
void setSender(const QString &sender);
QString normalizedSender() const;
void setRecipients(const QStringList &addresses);
void setSigningKeys(const QStringList &fingerprints);
void setOverrideKeys(const QMap<GpgME::Protocol, QMap<QString, QStringList>> &overrides);
void setAllowMixedProtocols(bool allowMixed);
void setPreferredProtocol(GpgME::Protocol proto);
void setMinimumValidity(int validity);
Result resolve();
private:
class Private;
std::unique_ptr<Private> const d;
};
} // namespace Kleo
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 25, 7:07 AM (18 h, 1 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
a6/68/4ce4efdcd465a2232a86422b333b
Attached To
rLIBKLEO Libkleo
Event Timeline
Log In to Comment