Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34634004
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
77 KB
Subscribers
None
View Options
diff --git a/src/kleo/keyresolver.h b/src/kleo/keyresolver.h
index bf7a95e5..fced9cc2 100644
--- a/src/kleo/keyresolver.h
+++ b/src/kleo/keyresolver.h
@@ -1,188 +1,187 @@
/* -*- 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 <Libkleo/Enum>
-
#include <QMap>
#include <QObject>
#include <QString>
-#include <QStringList>
#include <gpgme++/global.h>
#include <memory>
#include <vector>
+#include "kleo_export.h"
+
+class QStringList;
+
namespace GpgME
{
class Key;
}
namespace Kleo
{
/**
* 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.
* @a protocol hints at the protocol of the signing and encryption keys,
* i.e. if @a protocol is either @c GpgME::OpenPGP or @c GpgME::CMS, then
* all keys have the corresponding protocol. Otherwise, the keys have
* mixed protocols.
* @a signingKeys contains the signing keys to use. It contains
* zero or one OpenPGP key and zero or one S/MIME key.
* @a encryptionKeys contains the encryption keys to use for the
* different recipients. The keys of the map represent the normalized
* email addresses of the recipients.
*/
struct Solution
{
GpgME::Protocol protocol = GpgME::UnknownProtocol;
std::vector<GpgME::Key> signingKeys;
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);
~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);
/**
* 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 precendent 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> d;
};
} // namespace Kleo
diff --git a/src/kleo/keyresolvercore.cpp b/src/kleo/keyresolvercore.cpp
index 0f39cdfb..f291017c 100644
--- a/src/kleo/keyresolvercore.cpp
+++ b/src/kleo/keyresolvercore.cpp
@@ -1,785 +1,786 @@
/* -*- 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 "keyresolvercore.h"
+#include "kleo/enum.h"
#include "kleo/keygroup.h"
#include "models/keycache.h"
#include "utils/formatting.h"
#include <gpgme++/key.h>
#include "libkleo_debug.h"
using namespace Kleo;
using namespace GpgME;
namespace {
QDebug operator<<(QDebug debug, const GpgME::Key &key)
{
if (key.isNull()) {
debug << "Null";
} else {
debug << Formatting::summaryLine(key);
}
return debug.maybeSpace();
}
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)
, mCompliance(Formatting::complianceMode())
{
}
~Private() = default;
bool isAcceptableSigningKey(const Key &key);
bool isAcceptableEncryptionKey(const Key &key, const QString &address = QString());
void setSender(const QString &address);
void addRecipients(const QStringList &addresses);
void setOverrideKeys(const QMap<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();
QStringList unresolvedRecipients(GpgME::Protocol protocol) const;
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;
QString mCompliance;
};
bool KeyResolverCore::Private::isAcceptableSigningKey(const Key &key)
{
if (!ValidSigningKey(key)) {
return false;
}
if (mCompliance == QLatin1String("de-vs")) {
if (!Formatting::isKeyDeVs(key)) {
qCDebug(LIBKLEO_LOG) << "Rejected sig key" << key.primaryFingerprint()
<< "because it is not de-vs compliant.";
return false;
}
}
return true;
}
bool KeyResolverCore::Private::isAcceptableEncryptionKey(const Key &key, const QString &address)
{
if (!ValidEncryptionKey(key)) {
return false;
}
if (mCompliance == QLatin1String("de-vs")) {
if (!Formatting::isKeyDeVs(key)) {
qCDebug(LIBKLEO_LOG) << "Rejected enc key" << key.primaryFingerprint()
<< "because it is not de-vs compliant.";
return false;
}
}
if (address.isEmpty()) {
return true;
}
for (const auto &uid: key.userIDs()) {
if (uid.addrSpec() == address.toStdString()) {
if (uid.validity() >= mMinimumValidity) {
return true;
}
}
}
return false;
}
void KeyResolverCore::Private::setSender(const QString &address)
{
const auto normalized = UserID::addrSpecFromString (address.toUtf8().constData());
if (normalized.empty()) {
// should not happen bug in the caller, non localized
// error for bug reporting.
mFatalErrors << QStringLiteral("The sender address '%1' could not be extracted").arg(address);
return;
}
const auto normStr = QString::fromUtf8(normalized.c_str());
if (mSign) {
mSender = normStr;
}
addRecipients({address});
}
void KeyResolverCore::Private::addRecipients(const QStringList &addresses)
{
if (!mEncrypt) {
return;
}
// Internally we work with normalized addresses. Normalization
// matches the gnupg one.
for (const auto &addr: addresses) {
// PGP Uids are defined to be UTF-8 (RFC 4880 §5.11)
const auto normalized = UserID::addrSpecFromString (addr.toUtf8().constData());
if (normalized.empty()) {
// should not happen bug in the caller, non localized
// error for bug reporting.
mFatalErrors << QStringLiteral("The mail address for '%1' could not be extracted").arg(addr);
continue;
}
const QString normStr = QString::fromUtf8(normalized.c_str());
mRecipients << normStr;
// Initially add empty lists of keys for both protocols
mEncKeys[normStr] = {{CMS, {}}, {OpenPGP, {}}};
}
}
void KeyResolverCore::Private::setOverrideKeys(const QMap<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, KeyUsage::Sign);
if (group.isNull()) {
group = mCache->findGroup(address, UnknownProtocol, 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, 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, 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, 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;
}
QStringList KeyResolverCore::Private::unresolvedRecipients(GpgME::Protocol protocol) const
{
QStringList result;
result.reserve(mEncKeys.size());
for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) {
const auto &protocolKeysMap = it.value();
if (protocolKeysMap.value(protocol).empty()) {
result.push_back(it.key());
}
}
return result;
}
namespace
{
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 || mSigKeys.contains(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 || mSigKeys.contains(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);
const bool allAddressesAreResolved = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys),
[] (const auto &keys) { return !keys.empty(); });
if (allAddressesAreResolved) {
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/kleo/keyresolvercore.h b/src/kleo/keyresolvercore.h
index 15d7643d..496e7f50 100644
--- a/src/kleo/keyresolvercore.h
+++ b/src/kleo/keyresolvercore.h
@@ -1,87 +1,87 @@
/* -*- 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 <Libkleo/KeyResolver>
-#include "kleo_export.h"
-
#include <QMap>
#include <gpgme++/global.h>
#include <memory>
#include <vector>
+#include "kleo_export.h"
+
class QString;
class QStringList;
namespace GpgME
{
class Key;
}
namespace Kleo
{
class KLEO_EXPORT KeyResolverCore
{
public:
enum SolutionFlags
{
SomeUnresolved = 0,
AllResolved = 1,
OpenPGPOnly = 2,
CMSOnly = 4,
MixedProtocols = OpenPGPOnly | CMSOnly,
Error = 0x1000,
ResolvedMask = AllResolved | Error,
ProtocolsMask = OpenPGPOnly | CMSOnly | Error,
};
struct Result
{
SolutionFlags flags;
KeyResolver::Solution solution;
KeyResolver::Solution alternative;
};
explicit KeyResolverCore(bool encrypt, bool sign,
GpgME::Protocol format = GpgME::UnknownProtocol);
~KeyResolverCore();
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> d;
};
} // namespace Kleo
diff --git a/src/ui/newkeyapprovaldialog.cpp b/src/ui/newkeyapprovaldialog.cpp
index d53418db..93f1c002 100644
--- a/src/ui/newkeyapprovaldialog.cpp
+++ b/src/ui/newkeyapprovaldialog.cpp
@@ -1,901 +1,903 @@
/* -*- 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 "newkeyapprovaldialog.h"
-#include "kleo/defaultkeyfilter.h"
+
#include "keyselectioncombo.h"
#include "progressdialog.h"
+#include "kleo/defaultkeyfilter.h"
#include "utils/formatting.h"
-#include "libkleo_debug.h"
+#include <KLocalizedString>
+#include <KMessageBox>
#include <QApplication>
#include <QButtonGroup>
#include <QCheckBox>
#include <QDesktopWidget>
#include <QDialogButtonBox>
#include <QGroupBox>
+#include <QHBoxLayout>
#include <QLabel>
#include <QMap>
#include <QPushButton>
#include <QRadioButton>
#include <QScrollArea>
#include <QToolTip>
#include <QVBoxLayout>
#include <QGpgME/DefaultKeyGenerationJob>
-#include <QGpgME/CryptoConfig>
-#include <QGpgME/Protocol>
+#include <QGpgME/Job>
+
#include <gpgme++/keygenerationresult.h>
#include <gpgme++/key.h>
-#include <KLocalizedString>
-#include <KMessageBox>
+#include "libkleo_debug.h"
using namespace Kleo;
using namespace GpgME;
QDebug operator<<(QDebug debug, const GpgME::Key &key)
{
if (key.isNull()) {
debug << "Null";
} else {
debug << Formatting::summaryLine(key);
}
return debug.maybeSpace();
}
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);
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->setToolTip(i18n("Show keys matching the email address"));
} else {
mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters")));
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 enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key)
{
enum GpgME::UserID::Validity validity = GpgME::UserID::Validity::Unknown;
for (const auto &uid: key.userIDs()) {
if (validity == GpgME::UserID::Validity::Unknown
|| validity > uid.validity()) {
validity = uid.validity();
}
}
return validity;
}
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, 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, 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;
Protocol currentProtocol()
{
const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked();
const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked();
if (mAllowMixed) {
if (openPGPButtonChecked && !smimeButtonChecked) {
return OpenPGP;
}
if (!openPGPButtonChecked && smimeButtonChecked) {
return CMS;
}
} else if (openPGPButtonChecked) {
return OpenPGP;
} else if (smimeButtonChecked) {
return CMS;
}
return 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: qAsConst(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: qAsConst(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(Protocol protocol)
{
switch (protocol) {
case OpenPGP:
return s_pgpEncryptFilter;
case CMS:
return s_smimeEncryptFilter;
default:
return s_encryptFilter;
}
}
void updateWidgets()
{
const Protocol protocol = currentProtocol();
const auto encryptionFilter = encryptionKeyFilter(protocol);
for (auto combo: qAsConst(mSigningCombos)) {
auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
if (!widget) {
qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget";
continue;
}
widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == UnknownProtocol || widget->fixedProtocol() == protocol);
}
for (auto combo: qAsConst(mEncCombos)) {
auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
if (!widget) {
qCDebug(LIBKLEO_LOG) << "Failed to find combo widget";
continue;
}
widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == 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 == UnknownProtocol);
}
}
auto createProtocolLabel(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, Protocol protocol = 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>::of(&QComboBox::currentIndexChanged), q, [this] () {
updateOkButton();
});
return comboWidget;
}
void setSigningKeys(std::vector<GpgME::Key> preferredKeys, 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, 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>::of(&QComboBox::currentIndexChanged), q, [this] () {
updateOkButton();
});
mEncCombos << combo;
mAllCombos << combo;
combo->setProperty("address", addr);
return comboWidget;
}
void addEncryptionAddr(const QString &addr,
Protocol preferredKeysProtocol, const std::vector<GpgME::Key> &preferredKeys,
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(Protocol preferredKeysProtocol, const QMap<QString, std::vector<GpgME::Key>> &preferredKeys,
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));
group->setAlignment(Qt::AlignLeft);
auto encGrid = new QGridLayout(group);
addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid);
mScrollLayout->addWidget(group);
}
auto group = new QGroupBox(i18n("Encrypt to others:"));
group->setAlignment(Qt::AlignLeft);
auto encGrid = new QGridLayout;
group->setLayout(encGrid);
mScrollLayout->addWidget(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->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 (Formatting::complianceMode() != QLatin1String("de-vs")) {
return;
}
// Handle compliance
bool de_vs = true;
const Protocol protocol = currentProtocol();
for (const auto combo: qAsConst(mAllCombos)) {
if (!combo->isVisible()) {
continue;
}
const auto key = combo->currentKey();
if (key.isNull()) {
continue;
}
if (protocol != UnknownProtocol && key.protocol() != protocol) {
continue;
}
if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) {
de_vs = false;
break;
}
}
mOkButton->setIcon(QIcon::fromTheme(de_vs
? QStringLiteral("security-high")
: QStringLiteral("security-medium")));
mOkButton->setStyleSheet(QStringLiteral("background-color: ") + (de_vs
? QStringLiteral("#D5FAE2") // KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name()
: QStringLiteral("#FAE9EB"))); //KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name()));
mComplianceLbl->setText(de_vs
? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"%1 communication possible.", Formatting::deVsString())
: i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"%1 communication not possible.", Formatting::deVsString()));
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 = QApplication::desktop()->screenGeometry(this);
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/ui/newkeyapprovaldialog.h b/src/ui/newkeyapprovaldialog.h
index 4f779375..991dc169 100644
--- a/src/ui/newkeyapprovaldialog.h
+++ b/src/ui/newkeyapprovaldialog.h
@@ -1,82 +1,80 @@
/* -*- c++ -*-
newkeyapprovaldialog.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 <Libkleo/KeyResolver>
-#include "kleo_export.h"
-
#include <QDialog>
-#include <gpgme++/key.h>
+#include <memory>
-#include <vector>
+#include "kleo_export.h"
namespace Kleo
{
/** @brief A dialog to show for encryption / signing key approval or selection.
*
* This class is intended to replace the old KeyApprovalDialog with a new
* and simpler interface.
*
* Resolved recipients in this API means a recipient could be resolved
* to a single useful key. An unresolved recipient is a recipient for
* whom no key could be found. Import / Search will be offered for such
* a recipient. Multiple keys for signing / recipient can come e.g. from
* group configuration or Addressbook / Identity configuration.
*
* The Dialog uses the Level System for validity display and shows an
* overall outgoing level.
*
*/
class KLEO_EXPORT NewKeyApprovalDialog : public QDialog
{
Q_OBJECT
public:
/** @brief Create a new Key Approval Dialog.
*
* @param sender: The address of the sender, this may be used if signing is not
* specified to identify a recipient for which "Generate Key" should
* be offered.
* @param preferredSolution: The preferred signing and/or encryption keys for the sender
* and the recipients.
* @param alternativeSolution: An alternative set of signing and/or encryption keys for the sender
* and the recipients. Typically, S/MIME-only, if preferred solution is OpenPGP-only,
* and vice versa. Ignored, if mixed protocol selection is allowed.
* @param allowMixed: Whether or not the dialog should allow mixed S/MIME / OpenPGP key selection.
* @param forcedProtocol: A specific forced protocol.
* @param parent: The parent widget.
* @param f: The Qt window flags.
*/
explicit NewKeyApprovalDialog(bool encrypt,
bool sign,
const QString &sender,
KeyResolver::Solution preferredSolution,
KeyResolver::Solution alternativeSolution,
bool allowMixed,
GpgME::Protocol forcedProtocol,
QWidget *parent = nullptr,
Qt::WindowFlags f = Qt::WindowFlags());
~NewKeyApprovalDialog() override;
/** @brief The selected signing and/or encryption keys. Only valid after the dialog was accepted. */
KeyResolver::Solution result();
private:
class Private;
std::unique_ptr<Private> d;
};
} // namespace kleo
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Jan 20, 11:36 PM (1 d, 20 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
1d/fd/5d96ebb379bcc242d4bffec5c3ee
Attached To
rLIBKLEO Libkleo
Event Timeline
Log In to Comment