Page MenuHome GnuPG

No OneTemporary

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

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

Event Timeline