Page MenuHome GnuPG

No OneTemporary

diff --git a/src/models/keycache.cpp b/src/models/keycache.cpp
index daf3914a..c60e7086 100644
--- a/src/models/keycache.cpp
+++ b/src/models/keycache.cpp
@@ -1,1746 +1,1746 @@
/* -*- mode: c++; c-basic-offset:4 -*-
models/keycache.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2018 Intevation GmbH
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keycache.h"
#include "keycache_p.h"
#include "kleo/keygroup.h"
#include "kleo/predicates.h"
#include "kleo/stl_util.h"
#include "kleo/dn.h"
+
+#include "utils/compat.h"
#include "utils/filesystemwatcher.h"
#include <KConfigGroup>
#include <KSharedConfig>
#include <gpgme++/error.h>
#include <gpgme++/context.h>
#include <gpgme++/key.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/verificationresult.h>
#include <gpgme++/keylistresult.h>
#include <qgpgme/protocol.h>
#include <qgpgme/listallkeysjob.h>
#include <qgpgme/cryptoconfig.h>
#include <gpg-error.h>
//#include <kmime/kmime_header_parsing.h>
#include <QPointer>
#include <QTimer>
#include <QEventLoop>
#include <utility>
#include <algorithm>
#include <functional>
#include <iterator>
#include "kleo/debug.h"
#include "libkleo_debug.h"
using namespace Kleo;
using namespace GpgME;
using namespace KMime::Types;
static const unsigned int hours2ms = 1000 * 60 * 60;
static const QString groupNamePrefix = QStringLiteral("Group-");
//
//
// KeyCache
//
//
namespace
{
make_comparator_str(ByEMail, .first.c_str());
QStringList getFingerprints(const KeyGroup::Keys &keys)
{
QStringList fingerprints;
fingerprints.reserve(keys.size());
std::transform(keys.cbegin(), keys.cend(),
std::back_inserter(fingerprints),
[] (const Key &key) {
return QString::fromLatin1(key.primaryFingerprint());
});
return fingerprints;
}
}
class KeyCache::Private
{
friend class ::Kleo::KeyCache;
KeyCache *const q;
public:
explicit Private(KeyCache *qq) : q(qq), m_refreshInterval(1), m_initalized(false), m_pgpOnly(true), m_remarks_enabled(false)
{
connect(&m_autoKeyListingTimer, &QTimer::timeout, q, [this]() { q->startKeyListing(); });
updateAutoKeyListingTimer();
}
~Private()
{
if (m_refreshJob) {
m_refreshJob->cancel();
}
}
template < template <template <typename U> class Op> class Comp>
std::vector<Key>::const_iterator find(const std::vector<Key> &keys, const char *key) const
{
ensureCachePopulated();
const auto it =
std::lower_bound(keys.begin(), keys.end(), key, Comp<std::less>());
if (it == keys.end() || Comp<std::equal_to>()(*it, key)) {
return it;
} else {
return keys.end();
}
}
template < template <template <typename U> class Op> class Comp>
std::vector<Subkey>::const_iterator find(const std::vector<Subkey> &keys, const char *key) const
{
ensureCachePopulated();
const auto it =
std::lower_bound(keys.begin(), keys.end(), key, Comp<std::less>());
if (it == keys.end() || Comp<std::equal_to>()(*it, key)) {
return it;
} else {
return keys.end();
}
}
std::vector<Key>::const_iterator find_fpr(const char *fpr) const
{
return find<_detail::ByFingerprint>(by.fpr, fpr);
}
std::pair< std::vector< std::pair<std::string, Key> >::const_iterator,
std::vector< std::pair<std::string, Key> >::const_iterator >
find_email(const char *email) const
{
ensureCachePopulated();
return std::equal_range(by.email.begin(), by.email.end(),
email, ByEMail<std::less>());
}
std::vector<Key> find_mailbox(const QString &email, bool sign) const;
std::vector<Subkey>::const_iterator find_keygrip(const char *keygrip) const
{
return find<_detail::ByKeyGrip>(by.keygrip, keygrip);
}
std::vector<Subkey>::const_iterator find_subkeyid(const char *subkeyid) const
{
return find<_detail::ByKeyID>(by.subkeyid, subkeyid);
}
std::vector<Key>::const_iterator find_keyid(const char *keyid) const
{
return find<_detail::ByKeyID>(by.keyid, keyid);
}
std::vector<Key>::const_iterator find_shortkeyid(const char *shortkeyid) const
{
return find<_detail::ByShortKeyID>(by.shortkeyid, shortkeyid);
}
std::pair <
std::vector<Key>::const_iterator,
std::vector<Key>::const_iterator
> find_subjects(const char *chain_id) const
{
ensureCachePopulated();
return std::equal_range(by.chainid.begin(), by.chainid.end(),
chain_id, _detail::ByChainID<std::less>());
}
void refreshJobDone(const KeyListResult &result);
void setRefreshInterval(int interval)
{
m_refreshInterval = interval;
updateAutoKeyListingTimer();
}
int refreshInterval() const
{
return m_refreshInterval;
}
void updateAutoKeyListingTimer()
{
setAutoKeyListingInterval(hours2ms * m_refreshInterval);
}
void setAutoKeyListingInterval(int ms)
{
m_autoKeyListingTimer.stop();
m_autoKeyListingTimer.setInterval(ms);
if (ms != 0) {
m_autoKeyListingTimer.start();
}
}
void ensureCachePopulated() const;
std::vector<Key> getKeysForGroup(const QStringList &fingerprints) const
{
std::vector<Key> groupKeys;
groupKeys.reserve(fingerprints.size());
for (const QString &fpr : fingerprints) {
const Key key = q->findByFingerprint(fpr.toLatin1().constData());
if (key.isNull()) {
qCDebug (LIBKLEO_LOG) << "Ignoring unknown key with fingerprint:" << fpr;
continue;
}
groupKeys.push_back(key);
}
return groupKeys;
}
void readGroupsFromGpgConf()
{
// According to Werner Koch groups are more of a hack to solve
// a valid usecase (e.g. several keys defined for an internal mailing list)
// that won't make it in the proper keylist interface. And using gpgconf
// was the suggested way to support groups.
auto conf = QGpgME::cryptoConfig();
if (!conf) {
return;
}
- auto entry = conf->entry(QStringLiteral("gpg"),
- QStringLiteral("Configuration"),
- QStringLiteral("group"));
+ auto entry = getCryptoConfigEntry(conf, "gpg", "group");
if (!entry) {
return;
}
// collect the key fingerprints for all groups read from the configuration
QMap<QString, QStringList> fingerprints;
for (const QString &value: entry->stringValueList()) {
const QStringList split = value.split(QLatin1Char('='));
if (split.size() != 2) {
qCDebug (LIBKLEO_LOG) << "Ignoring invalid group config:" << value;
continue;
}
const QString groupName = split[0];
const QString fingerprint = split[1];
fingerprints[groupName].push_back(fingerprint);
}
// add all groups read from the configuration to the list of groups
for (auto it = fingerprints.cbegin(); it != fingerprints.cend(); ++it) {
const QString groupName = it.key();
const std::vector<Key> groupKeys = getKeysForGroup(it.value());
KeyGroup g(groupName, groupName, groupKeys, KeyGroup::GnuPGConfig);
m_groups.push_back(g);
}
}
KeyGroup readGroupFromGroupsConfig(const QString &groupId) const
{
Q_ASSERT(!m_groupsConfigName.isEmpty());
KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(m_groupsConfigName);
const KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + groupId);
const QString groupName = configGroup.readEntry("Name", QString());
const QStringList fingerprints = configGroup.readEntry("Keys", QStringList());
const std::vector<Key> groupKeys = getKeysForGroup(fingerprints);
// treat group as immutable if any of its entries is immutable
const QStringList entries = configGroup.keyList();
const bool isImmutable = configGroup.isImmutable() || std::any_of(entries.begin(), entries.end(),
[configGroup] (const QString &entry) {
return configGroup.isEntryImmutable(entry);
});
KeyGroup g(groupId, groupName, groupKeys, KeyGroup::ApplicationConfig);
g.setIsImmutable(isImmutable);
qCDebug(LIBKLEO_LOG) << "Read group" << g;
return g;
}
void readGroupsFromGroupsConfig()
{
if (m_groupsConfigName.isEmpty()) {
qCDebug(LIBKLEO_LOG) << "readGroupsFromGroupsConfig - name of application configuration file is unset";
return;
}
const KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(m_groupsConfigName);
const QStringList configGroups = groupsConfig->groupList();
for (const QString &configGroupName : configGroups) {
qCDebug(LIBKLEO_LOG) << "Reading config group" << configGroupName;
if (configGroupName.startsWith(groupNamePrefix)) {
const QString keyGroupId = configGroupName.mid(groupNamePrefix.size());
if (keyGroupId.isEmpty()) {
qCWarning(LIBKLEO_LOG) << "Config group" << configGroupName << "has empty group id";
continue;
}
KeyGroup group = readGroupFromGroupsConfig(keyGroupId);
m_groups.push_back(group);
}
}
}
KeyGroup writeGroupToGroupsConfig(const KeyGroup &group)
{
if (m_groupsConfigName.isEmpty()) {
qCDebug(LIBKLEO_LOG) << "writeGroupToGroupsConfig - name of application configuration file is unset";
return KeyGroup();
}
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << "writeGroupToGroupsConfig - group cannot be written to application configuration:" << group;
return KeyGroup();
}
KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(m_groupsConfigName);
KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + group.id());
qCDebug(LIBKLEO_LOG) << "Writing config group" << configGroup.name();
configGroup.writeEntry("Name", group.name());
configGroup.writeEntry("Keys", getFingerprints(group.keys()));
// reread group to ensure that it reflects the saved group in case of immutable entries
return readGroupFromGroupsConfig(group.id());
}
bool removeGroupFromGroupsConfig(const KeyGroup &group)
{
if (m_groupsConfigName.isEmpty()) {
qCDebug(LIBKLEO_LOG) << "removeGroupFromGroupsConfig - name of application configuration file is unset";
return false;
}
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << "removeGroupFromGroupsConfig - group cannot be removed from application configuration:" << group;
return false;
}
KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(m_groupsConfigName);
KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + group.id());
qCDebug(LIBKLEO_LOG) << "Removing config group" << configGroup.name();
configGroup.deleteGroup();
return true;
}
void updateGroupCache()
{
// Update Group Keys
// this is a quick thing as it only involves reading the config
// so no need for a job.
m_groups.clear();
if (m_groupsEnabled) {
readGroupsFromGpgConf();
readGroupsFromGroupsConfig();
}
}
bool insert(const KeyGroup &group)
{
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Invalid group:" << group;
return false;
}
const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(),
[group] (const auto &g) {
return g.source() == group.source() && g.id() == group.id();
});
if (it != m_groups.cend()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Group already present in list of groups:" << group;
return false;
}
const KeyGroup savedGroup = writeGroupToGroupsConfig(group);
if (savedGroup.isNull()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Writing group" << group.id() << "to config file failed";
return false;
}
m_groups.push_back(savedGroup);
Q_EMIT q->groupAdded(savedGroup);
return true;
}
bool update(const KeyGroup &group)
{
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Invalid group:" << group;
return false;
}
const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(),
[group] (const auto &g) {
return g.source() == group.source() && g.id() == group.id();
});
if (it == m_groups.cend()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Group not found in list of groups:" << group;
return false;
}
const auto groupIndex = std::distance(m_groups.cbegin(), it);
const KeyGroup savedGroup = writeGroupToGroupsConfig(group);
if (savedGroup.isNull()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Writing group" << group.id() << "to config file failed";
return false;
}
m_groups[groupIndex] = savedGroup;
Q_EMIT q->groupUpdated(savedGroup);
return true;
}
bool remove(const KeyGroup &group)
{
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Invalid group:" << group;
return false;
}
const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(),
[group] (const auto &g) {
return g.source() == group.source() && g.id() == group.id();
});
if (it == m_groups.cend()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Group not found in list of groups:" << group;
return false;
}
const bool success = removeGroupFromGroupsConfig(group);
if (!success) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Removing group" << group.id() << "from config file failed";
return false;
}
m_groups.erase(it);
Q_EMIT q->groupRemoved(group);
return true;
}
private:
QPointer<RefreshKeysJob> m_refreshJob;
std::vector<std::shared_ptr<FileSystemWatcher> > m_fsWatchers;
QTimer m_autoKeyListingTimer;
int m_refreshInterval;
struct By {
std::vector<Key> fpr, keyid, shortkeyid, chainid;
std::vector< std::pair<std::string, Key> > email;
std::vector<Subkey> subkeyid, keygrip;
} by;
bool m_initalized;
bool m_pgpOnly;
bool m_remarks_enabled;
bool m_groupsEnabled = false;
QString m_groupsConfigName;
std::vector<KeyGroup> m_groups;
};
std::shared_ptr<const KeyCache> KeyCache::instance()
{
return mutableInstance();
}
std::shared_ptr<KeyCache> KeyCache::mutableInstance()
{
static std::weak_ptr<KeyCache> self;
try {
return std::shared_ptr<KeyCache>(self);
} catch (const std::bad_weak_ptr &) {
const std::shared_ptr<KeyCache> s(new KeyCache);
self = s;
return s;
}
}
KeyCache::KeyCache()
: QObject(), d(new Private(this))
{
}
KeyCache::~KeyCache() {}
void KeyCache::setGroupsEnabled(bool enabled)
{
d->m_groupsEnabled = enabled;
if (d->m_initalized) {
d->updateGroupCache();
}
}
void KeyCache::setGroupsConfig(const QString &filename)
{
d->m_groupsConfigName = filename;
}
void KeyCache::enableFileSystemWatcher(bool enable)
{
for (const auto &i : qAsConst(d->m_fsWatchers)) {
i->setEnabled(enable);
}
}
void KeyCache::setRefreshInterval(int hours)
{
d->setRefreshInterval(hours);
}
int KeyCache::refreshInterval() const
{
return d->refreshInterval();
}
void KeyCache::reload(GpgME::Protocol /*proto*/)
{
if (d->m_refreshJob) {
return;
}
d->updateAutoKeyListingTimer();
enableFileSystemWatcher(false);
d->m_refreshJob = new RefreshKeysJob(this);
connect(d->m_refreshJob.data(), &RefreshKeysJob::done,
this, [this](const GpgME::KeyListResult &r) {
d->refreshJobDone(r);
});
d->m_refreshJob->start();
}
void KeyCache::cancelKeyListing()
{
if (!d->m_refreshJob) {
return;
}
d->m_refreshJob->cancel();
}
void KeyCache::addFileSystemWatcher(const std::shared_ptr<FileSystemWatcher> &watcher)
{
if (!watcher) {
return;
}
d->m_fsWatchers.push_back(watcher);
connect(watcher.get(), &FileSystemWatcher::directoryChanged,
this, [this]() { startKeyListing(); });
connect(watcher.get(), &FileSystemWatcher::fileChanged,
this, [this]() { startKeyListing(); });
watcher->setEnabled(d->m_refreshJob.isNull());
}
void KeyCache::enableRemarks(bool value)
{
if (!d->m_remarks_enabled && value) {
d->m_remarks_enabled = value;
if (d->m_initalized && !d->m_refreshJob) {
qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled";
reload();
} else {
connect(d->m_refreshJob.data(), &RefreshKeysJob::done,
this, [this](const GpgME::KeyListResult &) {
qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled";
QTimer::singleShot(1000, this, [this](){ reload(); });
});
}
} else {
d->m_remarks_enabled = value;
}
}
bool KeyCache::remarksEnabled() const
{
return d->m_remarks_enabled;
}
void KeyCache::Private::refreshJobDone(const KeyListResult &result)
{
q->enableFileSystemWatcher(true);
m_initalized = true;
updateGroupCache();
Q_EMIT q->keyListingDone(result);
}
const Key &KeyCache::findByFingerprint(const char *fpr) const
{
const std::vector<Key>::const_iterator it = d->find_fpr(fpr);
if (it == d->by.fpr.end()) {
static const Key null;
return null;
} else {
return *it;
}
}
const Key &KeyCache::findByFingerprint(const std::string &fpr) const
{
return findByFingerprint(fpr.c_str());
}
std::vector<Key> KeyCache::findByEMailAddress(const char *email) const
{
const auto pair = d->find_email(email);
std::vector<Key> result;
result.reserve(std::distance(pair.first, pair.second));
std::transform(pair.first, pair.second,
std::back_inserter(result),
[](const std::pair<std::string, Key> &pair) {
return pair.second;
});
return result;
}
std::vector<Key> KeyCache::findByEMailAddress(const std::string &email) const
{
return findByEMailAddress(email.c_str());
}
const Key &KeyCache::findByShortKeyID(const char *id) const
{
const std::vector<Key>::const_iterator it = d->find_shortkeyid(id);
if (it != d->by.shortkeyid.end()) {
return *it;
}
static const Key null;
return null;
}
const Key &KeyCache::findByShortKeyID(const std::string &id) const
{
return findByShortKeyID(id.c_str());
}
const Key &KeyCache::findByKeyIDOrFingerprint(const char *id) const
{
{
// try by.fpr first:
const std::vector<Key>::const_iterator it = d->find_fpr(id);
if (it != d->by.fpr.end()) {
return *it;
}
}{
// try by.keyid next:
const std::vector<Key>::const_iterator it = d->find_keyid(id);
if (it != d->by.keyid.end()) {
return *it;
}
}
static const Key null;
return null;
}
const Key &KeyCache::findByKeyIDOrFingerprint(const std::string &id) const
{
return findByKeyIDOrFingerprint(id.c_str());
}
std::vector<Key> KeyCache::findByKeyIDOrFingerprint(const std::vector<std::string> &ids) const
{
std::vector<std::string> keyids;
std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(keyids),
[](const std::string &str) {
return !str.c_str() || !*str.c_str();
});
// this is just case-insensitive string search:
std::sort(keyids.begin(), keyids.end(), _detail::ByFingerprint<std::less>());
std::vector<Key> result;
result.reserve(keyids.size()); // dups shouldn't happen
d->ensureCachePopulated();
kdtools::set_intersection(d->by.fpr.begin(), d->by.fpr.end(),
keyids.begin(), keyids.end(),
std::back_inserter(result),
_detail::ByFingerprint<std::less>());
if (result.size() < keyids.size()) {
// note that By{Fingerprint,KeyID,ShortKeyID} define the same
// order for _strings_
kdtools::set_intersection(d->by.keyid.begin(), d->by.keyid.end(),
keyids.begin(), keyids.end(),
std::back_inserter(result),
_detail::ByKeyID<std::less>());
}
// duplicates shouldn't happen, but make sure nonetheless:
std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
// we skip looking into short key ids here, as it's highly
// unlikely they're used for this purpose. We might need to revise
// this decision, but only after testing.
return result;
}
const Subkey &KeyCache::findSubkeyByKeyGrip(const char *grip, Protocol protocol) const
{
static const Subkey null;
d->ensureCachePopulated();
const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(),
grip, _detail::ByKeyGrip<std::less>());
if (range.first == d->by.keygrip.end()) {
return null;
} else if (protocol == UnknownProtocol) {
return *range.first;
} else {
for (auto it = range.first; it != range.second; ++it) {
if (it->parent().protocol() == protocol) {
return *it;
}
}
}
return null;
}
const Subkey &KeyCache::findSubkeyByKeyGrip(const std::string &grip, Protocol protocol) const
{
return findSubkeyByKeyGrip(grip.c_str(), protocol);
}
std::vector<Subkey> KeyCache::findSubkeysByKeyID(const std::vector<std::string> &ids) const
{
std::vector<std::string> sorted;
sorted.reserve(ids.size());
std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(sorted),
[](const std::string &str) {
return !str.c_str() || !*str.c_str();
});
std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
std::vector<Subkey> result;
d->ensureCachePopulated();
kdtools::set_intersection(d->by.subkeyid.begin(), d->by.subkeyid.end(),
sorted.begin(), sorted.end(),
std::back_inserter(result),
_detail::ByKeyID<std::less>());
return result;
}
std::vector<Key> KeyCache::findRecipients(const DecryptionResult &res) const
{
std::vector<std::string> keyids;
const auto recipients = res.recipients();
for (const DecryptionResult::Recipient &r : recipients)
if (const char *kid = r.keyID()) {
keyids.push_back(kid);
}
const std::vector<Subkey> subkeys = findSubkeysByKeyID(keyids);
std::vector<Key> result;
result.reserve(subkeys.size());
std::transform(subkeys.begin(), subkeys.end(), std::back_inserter(result),
std::mem_fn(&Subkey::parent));
std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
return result;
}
std::vector<Key> KeyCache::findSigners(const VerificationResult &res) const
{
std::vector<std::string> fprs;
const auto signatures = res.signatures();
for (const Signature &s : signatures)
if (const char *fpr = s.fingerprint()) {
fprs.push_back(fpr);
}
return findByKeyIDOrFingerprint(fprs);
}
std::vector<Key> KeyCache::findSigningKeysByMailbox(const QString &mb) const
{
return d->find_mailbox(mb, true);
}
std::vector<Key> KeyCache::findEncryptionKeysByMailbox(const QString &mb) const
{
return d->find_mailbox(mb, false);
}
namespace
{
#define DO( op, meth, meth2 ) if ( op key.meth() ) {} else { qDebug( "rejecting for signing: %s: %s", #meth2, key.primaryFingerprint() ); return false; }
#define ACCEPT( meth ) DO( !!, meth, !meth )
#define REJECT( meth ) DO( !, meth, meth )
struct ready_for_signing : std::unary_function<Key, bool> {
bool operator()(const Key &key) const
{
#if 1
ACCEPT(hasSecret);
ACCEPT(canReallySign);
REJECT(isRevoked);
REJECT(isExpired);
REJECT(isDisabled);
REJECT(isInvalid);
return true;
#else
return key.hasSecret() &&
key.canReallySign() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid();
#endif
#undef DO
}
};
struct ready_for_encryption : std::unary_function<Key, bool> {
#define DO( op, meth, meth2 ) if ( op key.meth() ) {} else { qDebug( "rejecting for encrypting: %s: %s", #meth2, key.primaryFingerprint() ); return false; }
bool operator()(const Key &key) const
{
#if 1
ACCEPT(canEncrypt);
REJECT(isRevoked);
REJECT(isExpired);
REJECT(isDisabled);
REJECT(isInvalid);
return true;
#else
return
key.canEncrypt() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid();
#endif
}
#undef DO
#undef ACCEPT
#undef REJECT
};
}
std::vector<Key> KeyCache::Private::find_mailbox(const QString &email, bool sign) const
{
if (email.isEmpty()) {
return std::vector<Key>();
}
const auto pair = find_email(email.toUtf8().constData());
std::vector<Key> result;
result.reserve(std::distance(pair.first, pair.second));
if (sign)
kdtools::copy_2nd_if(pair.first, pair.second,
std::back_inserter(result),
ready_for_signing());
else
kdtools::copy_2nd_if(pair.first, pair.second,
std::back_inserter(result),
ready_for_encryption());
return result;
}
std::vector<Key> KeyCache::findSubjects(const GpgME::Key &key, Options options) const
{
return findSubjects(std::vector<Key>(1, key), options);
}
std::vector<Key> KeyCache::findSubjects(const std::vector<Key> &keys, Options options) const
{
return findSubjects(keys.begin(), keys.end(), options);
}
std::vector<Key> KeyCache::findSubjects(std::vector<Key>::const_iterator first, std::vector<Key>::const_iterator last, Options options) const
{
if (first == last) {
return std::vector<Key>();
}
std::vector<Key> result;
while (first != last) {
const auto pair = d->find_subjects(first->primaryFingerprint());
result.insert(result.end(), pair.first, pair.second);
++first;
}
std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
if (options & RecursiveSearch) {
const std::vector<Key> furtherSubjects = findSubjects(result, options);
std::vector<Key> combined;
combined.reserve(result.size() + furtherSubjects.size());
std::merge(result.begin(), result.end(),
furtherSubjects.begin(), furtherSubjects.end(),
std::back_inserter(combined),
_detail::ByFingerprint<std::less>());
combined.erase(std::unique(combined.begin(), combined.end(), _detail::ByFingerprint<std::equal_to>()), combined.end());
result.swap(combined);
}
return result;
}
std::vector<Key> KeyCache::findIssuers(const Key &key, Options options) const
{
if (key.isNull()) {
return std::vector<Key>();
}
std::vector<Key> result;
if (options & IncludeSubject) {
result.push_back(key);
}
if (key.isRoot()) {
return result;
}
const Key &issuer = findByFingerprint(key.chainID());
if (issuer.isNull()) {
return result;
}
result.push_back(issuer);
if (!(options & RecursiveSearch)) {
return result;
}
while (!result.back().isNull() && !result.back().isRoot()) {
result.push_back(findByFingerprint(result.back().chainID()));
}
if (result.back().isNull()) {
result.pop_back();
}
return result;
}
std::vector<Key> KeyCache::findIssuers(const std::vector<Key> &keys, Options options) const
{
return findIssuers(keys.begin(), keys.end(), options);
}
std::vector<Key> KeyCache::findIssuers(std::vector<Key>::const_iterator first, std::vector<Key>::const_iterator last, Options options) const
{
if (first == last) {
return std::vector<Key>();
}
// extract chain-ids, identifying issuers:
std::vector<const char *> chainIDs;
chainIDs.reserve(last - first);
kdtools::transform_if(first, last, std::back_inserter(chainIDs),
std::mem_fn(&Key::chainID),
[](const Key &key) { return !key.isRoot(); });
std::sort(chainIDs.begin(), chainIDs.end(), _detail::ByFingerprint<std::less>());
const auto lastUniqueChainID = std::unique(chainIDs.begin(), chainIDs.end(), _detail::ByFingerprint<std::less>());
std::vector<Key> result;
result.reserve(lastUniqueChainID - chainIDs.begin());
d->ensureCachePopulated();
kdtools::set_intersection(d->by.fpr.begin(), d->by.fpr.end(),
chainIDs.begin(), lastUniqueChainID,
std::back_inserter(result),
_detail::ByFingerprint<std::less>());
if (options & IncludeSubject) {
const unsigned int rs = result.size();
result.insert(result.end(), first, last);
std::inplace_merge(result.begin(), result.begin() + rs, result.end(),
_detail::ByFingerprint<std::less>());
}
if (!(options & RecursiveSearch)) {
return result;
}
const std::vector<Key> l2result = findIssuers(result, options & ~IncludeSubject);
const unsigned long result_size = result.size();
result.insert(result.end(), l2result.begin(), l2result.end());
std::inplace_merge(result.begin(), result.begin() + result_size, result.end(),
_detail::ByFingerprint<std::less>());
return result;
}
static std::string email(const UserID &uid)
{
// Prefer the gnupg normalized one
const std::string addr = uid.addrSpec();
if (!addr.empty()) {
return addr;
}
const std::string email = uid.email();
if (email.empty()) {
return DN(uid.id())[QStringLiteral("EMAIL")].trimmed().toUtf8().constData();
}
if (email[0] == '<' && email[email.size() - 1] == '>') {
return email.substr(1, email.size() - 2);
} else {
return email;
}
}
static std::vector<std::string> emails(const Key &key)
{
std::vector<std::string> emails;
const auto userIDs = key.userIDs();
for (const UserID &uid : userIDs) {
const std::string e = email(uid);
if (!e.empty()) {
emails.push_back(e);
}
}
std::sort(emails.begin(), emails.end(), ByEMail<std::less>());
emails.erase(std::unique(emails.begin(), emails.end(), ByEMail<std::equal_to>()), emails.end());
return emails;
}
void KeyCache::remove(const Key &key)
{
if (key.isNull()) {
return;
}
const char *fpr = key.primaryFingerprint();
if (!fpr) {
return;
}
Q_EMIT aboutToRemove(key);
{
const auto range = std::equal_range(d->by.fpr.begin(), d->by.fpr.end(), fpr,
_detail::ByFingerprint<std::less>());
d->by.fpr.erase(range.first, range.second);
}
if (const char *keyid = key.keyID()) {
const auto range = std::equal_range(d->by.keyid.begin(), d->by.keyid.end(), keyid,
_detail::ByKeyID<std::less>());
const auto it = std::remove_if(range.first, range.second,
[fpr](const GpgME::Key &key) {
return _detail::ByFingerprint<std::equal_to>()(fpr, key);
});
d->by.keyid.erase(it, range.second);
}
if (const char *shortkeyid = key.shortKeyID()) {
const auto range = std::equal_range(d->by.shortkeyid.begin(), d->by.shortkeyid.end(), shortkeyid,
_detail::ByShortKeyID<std::less>());
const auto it = std::remove_if(range.first, range.second,
[fpr](const GpgME::Key &key) {
return _detail::ByFingerprint<std::equal_to>()(fpr, key);
});
d->by.shortkeyid.erase(it, range.second);
}
if (const char *chainid = key.chainID()) {
const auto range = std::equal_range(d->by.chainid.begin(), d->by.chainid.end(), chainid,
_detail::ByChainID<std::less>());
const auto range2 = std::equal_range(range.first, range.second, fpr, _detail::ByFingerprint<std::less>());
d->by.chainid.erase(range2.first, range2.second);
}
Q_FOREACH (const std::string &email, emails(key)) {
const auto range = std::equal_range(d->by.email.begin(), d->by.email.end(), email, ByEMail<std::less>());
const auto it = std::remove_if(range.first, range.second,
[fpr](const std::pair<std::string, Key> &pair) {
return qstricmp(fpr, pair.second.primaryFingerprint()) == 0;
});
d->by.email.erase(it, range.second);
}
Q_FOREACH (const Subkey &subkey, key.subkeys()) {
if (const char *keyid = subkey.keyID()) {
const auto range = std::equal_range(d->by.subkeyid.begin(), d->by.subkeyid.end(), keyid,
_detail::ByKeyID<std::less>());
const auto it = std::remove_if(range.first, range.second,
[fpr] (const Subkey &subkey) {
return !qstricmp(fpr, subkey.parent().primaryFingerprint());
});
d->by.subkeyid.erase(it, range.second);
}
if (const char *keygrip = subkey.keyGrip()) {
const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), keygrip,
_detail::ByKeyGrip<std::less>());
const auto it = std::remove_if(range.first, range.second,
[fpr] (const Subkey &subkey) {
return !qstricmp(fpr, subkey.parent().primaryFingerprint());
});
d->by.keygrip.erase(it, range.second);
}
}
}
void KeyCache::remove(const std::vector<Key> &keys)
{
for (const Key &key : keys) {
remove(key);
}
}
const std::vector<GpgME::Key> &KeyCache::keys() const
{
d->ensureCachePopulated();
return d->by.fpr;
}
std::vector<Key> KeyCache::secretKeys() const
{
std::vector<Key> keys = this->keys();
keys.erase(std::remove_if(keys.begin(), keys.end(),
[](const Key &key) { return !key.hasSecret(); }),
keys.end());
return keys;
}
std::vector<KeyGroup> KeyCache::groups() const
{
d->ensureCachePopulated();
return d->m_groups;
}
std::vector<KeyGroup> KeyCache::configurableGroups() const
{
std::vector<KeyGroup> groups;
groups.reserve(d->m_groups.size());
std::copy_if(d->m_groups.cbegin(), d->m_groups.cend(),
std::back_inserter(groups),
[] (const KeyGroup &group) { return group.source() == KeyGroup::ApplicationConfig; });
return groups;
}
namespace
{
bool compareById(const KeyGroup &lhs, const KeyGroup &rhs)
{
return lhs.id() < rhs.id();
}
std::vector<KeyGroup> sortedById(std::vector<KeyGroup> groups)
{
std::sort(groups.begin(), groups.end(), &compareById);
return groups;
}
}
void KeyCache::saveConfigurableGroups(const std::vector<KeyGroup> &groups)
{
const std::vector<KeyGroup> oldGroups = sortedById(configurableGroups());
const std::vector<KeyGroup> newGroups = sortedById(groups);
{
std::vector<KeyGroup> removedGroups;
std::set_difference(oldGroups.begin(), oldGroups.end(),
newGroups.begin(), newGroups.end(),
std::back_inserter(removedGroups),
&compareById);
for (const auto &group : qAsConst(removedGroups)) {
qCDebug(LIBKLEO_LOG) << "Removing group" << group;
d->remove(group);
}
}
{
std::vector<KeyGroup> updatedGroups;
std::set_intersection(newGroups.begin(), newGroups.end(),
oldGroups.begin(), oldGroups.end(),
std::back_inserter(updatedGroups),
&compareById);
for (const auto &group : qAsConst(updatedGroups)) {
qCDebug(LIBKLEO_LOG) << "Updating group" << group;
d->update(group);
}
}
{
std::vector<KeyGroup> addedGroups;
std::set_difference(newGroups.begin(), newGroups.end(),
oldGroups.begin(), oldGroups.end(),
std::back_inserter(addedGroups),
&compareById);
for (const auto &group : qAsConst(addedGroups)) {
qCDebug(LIBKLEO_LOG) << "Adding group" << group;
d->insert(group);
}
}
Q_EMIT keysMayHaveChanged();
}
bool KeyCache::insert(const KeyGroup &group)
{
if (!d->insert(group)) {
return false;
}
Q_EMIT keysMayHaveChanged();
return true;
}
bool KeyCache::update(const KeyGroup &group)
{
if (!d->update(group)) {
return false;
}
Q_EMIT keysMayHaveChanged();
return true;
}
bool KeyCache::remove(const KeyGroup &group)
{
if (!d->remove(group)) {
return false;
}
Q_EMIT keysMayHaveChanged();
return true;
}
void KeyCache::refresh(const std::vector<Key> &keys)
{
// make this better...
clear();
insert(keys);
}
void KeyCache::insert(const Key &key)
{
insert(std::vector<Key>(1, key));
}
namespace
{
template <
template <template <typename T> class Op> class T1,
template <template <typename T> class Op> class T2
> struct lexicographically
{
using result_type = bool;
template <typename U, typename V>
bool operator()(const U &lhs, const V &rhs) const
{
return
T1<std::less>()(lhs, rhs) ||
(T1<std::equal_to>()(lhs, rhs) &&
T2<std::less>()(lhs, rhs))
;
}
};
}
void KeyCache::insert(const std::vector<Key> &keys)
{
// 1. remove those with empty fingerprints:
std::vector<Key> sorted;
sorted.reserve(keys.size());
std::remove_copy_if(keys.begin(), keys.end(),
std::back_inserter(sorted),
[](const Key &key) {
auto fp = key.primaryFingerprint();
return !fp|| !*fp;
});
Q_FOREACH (const Key &key, sorted) {
remove(key); // this is sub-optimal, but makes implementation from here on much easier
}
// 2. sort by fingerprint:
std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint<std::less>());
// 2a. insert into fpr index:
std::vector<Key> by_fpr;
by_fpr.reserve(sorted.size() + d->by.fpr.size());
std::merge(sorted.begin(), sorted.end(),
d->by.fpr.begin(), d->by.fpr.end(),
std::back_inserter(by_fpr),
_detail::ByFingerprint<std::less>());
// 3. build email index:
std::vector< std::pair<std::string, Key> > pairs;
pairs.reserve(sorted.size());
Q_FOREACH (const Key &key, sorted) {
const std::vector<std::string> emails = ::emails(key);
for (const std::string &e : emails) {
pairs.push_back(std::make_pair(e, key));
}
}
std::sort(pairs.begin(), pairs.end(), ByEMail<std::less>());
// 3a. insert into email index:
std::vector< std::pair<std::string, Key> > by_email;
by_email.reserve(pairs.size() + d->by.email.size());
std::merge(pairs.begin(), pairs.end(),
d->by.email.begin(), d->by.email.end(),
std::back_inserter(by_email),
ByEMail<std::less>());
// 3.5: stable-sort by chain-id (effectively lexicographically<ByChainID,ByFingerprint>)
std::stable_sort(sorted.begin(), sorted.end(), _detail::ByChainID<std::less>());
// 3.5a: insert into chain-id index:
std::vector<Key> nonroot;
nonroot.reserve(sorted.size());
std::vector<Key> by_chainid;
by_chainid.reserve(sorted.size() + d->by.chainid.size());
std::copy_if(sorted.cbegin(), sorted.cend(),
std::back_inserter(nonroot),
[](const Key &key) { return !key.isRoot(); });
std::merge(nonroot.cbegin(), nonroot.cend(),
d->by.chainid.cbegin(), d->by.chainid.cend(),
std::back_inserter(by_chainid),
lexicographically<_detail::ByChainID, _detail::ByFingerprint>());
// 4. sort by key id:
std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
// 4a. insert into keyid index:
std::vector<Key> by_keyid;
by_keyid.reserve(sorted.size() + d->by.keyid.size());
std::merge(sorted.begin(), sorted.end(),
d->by.keyid.begin(), d->by.keyid.end(),
std::back_inserter(by_keyid),
_detail::ByKeyID<std::less>());
// 5. sort by short key id:
std::sort(sorted.begin(), sorted.end(), _detail::ByShortKeyID<std::less>());
// 5a. insert into short keyid index:
std::vector<Key> by_shortkeyid;
by_shortkeyid.reserve(sorted.size() + d->by.shortkeyid.size());
std::merge(sorted.begin(), sorted.end(),
d->by.shortkeyid.begin(), d->by.shortkeyid.end(),
std::back_inserter(by_shortkeyid),
_detail::ByShortKeyID<std::less>());
// 6. build subkey ID index:
std::vector<Subkey> subkeys;
subkeys.reserve(sorted.size());
Q_FOREACH (const Key &key, sorted)
Q_FOREACH (const Subkey &subkey, key.subkeys()) {
subkeys.push_back(subkey);
}
// 6a sort by key id:
std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyID<std::less>());
// 6b. insert into subkey ID index:
std::vector<Subkey> by_subkeyid;
by_subkeyid.reserve(subkeys.size() + d->by.subkeyid.size());
std::merge(subkeys.begin(), subkeys.end(),
d->by.subkeyid.begin(), d->by.subkeyid.end(),
std::back_inserter(by_subkeyid),
_detail::ByKeyID<std::less>());
// 6c. sort by key grip
std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyGrip<std::less>());
// 6d. insert into subkey keygrip index:
std::vector<Subkey> by_keygrip;
by_keygrip.reserve(subkeys.size() + d->by.keygrip.size());
std::merge(subkeys.begin(), subkeys.end(),
d->by.keygrip.begin(), d->by.keygrip.end(),
std::back_inserter(by_keygrip),
_detail::ByKeyGrip<std::less>());
// now commit (well, we already removed keys...)
by_fpr.swap(d->by.fpr);
by_keyid.swap(d->by.keyid);
by_shortkeyid.swap(d->by.shortkeyid);
by_email.swap(d->by.email);
by_subkeyid.swap(d->by.subkeyid);
by_keygrip.swap(d->by.keygrip);
by_chainid.swap(d->by.chainid);
for (const Key &key : qAsConst(sorted)) {
d->m_pgpOnly &= key.protocol() == GpgME::OpenPGP;
Q_EMIT added(key);
}
Q_EMIT keysMayHaveChanged();
}
void KeyCache::clear()
{
d->by = Private::By();
}
//
//
// RefreshKeysJob
//
//
class KeyCache::RefreshKeysJob::Private
{
RefreshKeysJob *const q;
public:
Private(KeyCache *cache, RefreshKeysJob *qq);
void doStart();
Error startKeyListing(GpgME::Protocol protocol);
void listAllKeysJobDone(const KeyListResult &res, const std::vector<Key> &nextKeys)
{
std::vector<Key> keys;
keys.reserve(m_keys.size() + nextKeys.size());
if (m_keys.empty()) {
keys = nextKeys;
} else
std::merge(m_keys.begin(), m_keys.end(),
nextKeys.begin(), nextKeys.end(),
std::back_inserter(keys),
_detail::ByFingerprint<std::less>());
m_keys.swap(keys);
jobDone(res);
}
void emitDone(const KeyListResult &result);
void updateKeyCache();
QPointer<KeyCache> m_cache;
QVector<QGpgME::ListAllKeysJob*> m_jobsPending;
std::vector<Key> m_keys;
KeyListResult m_mergedResult;
bool m_canceled;
private:
void jobDone(const KeyListResult &res);
};
KeyCache::RefreshKeysJob::Private::Private(KeyCache *cache, RefreshKeysJob *qq)
: q(qq)
, m_cache(cache)
, m_canceled(false)
{
Q_ASSERT(m_cache);
}
void KeyCache::RefreshKeysJob::Private::jobDone(const KeyListResult &result)
{
if (m_canceled) {
q->deleteLater();
return;
}
QObject *const sender = q->sender();
if (sender) {
sender->disconnect(q);
}
Q_ASSERT(m_jobsPending.size() > 0);
m_jobsPending.removeOne(qobject_cast<QGpgME::ListAllKeysJob*>(sender));
m_mergedResult.mergeWith(result);
if (m_jobsPending.size() > 0) {
return;
}
updateKeyCache();
emitDone(m_mergedResult);
}
void KeyCache::RefreshKeysJob::Private::emitDone(const KeyListResult &res)
{
q->deleteLater();
Q_EMIT q->done(res);
}
KeyCache::RefreshKeysJob::RefreshKeysJob(KeyCache *cache, QObject *parent) : QObject(parent), d(new Private(cache, this))
{
}
KeyCache::RefreshKeysJob::~RefreshKeysJob()
{
delete d;
}
void KeyCache::RefreshKeysJob::start()
{
QTimer::singleShot(0, this, [this](){ d->doStart(); });
}
void KeyCache::RefreshKeysJob::cancel()
{
d->m_canceled = true;
std::for_each(d->m_jobsPending.begin(), d->m_jobsPending.end(),
std::mem_fn(&QGpgME::ListAllKeysJob::slotCancel));
Q_EMIT canceled();
}
void KeyCache::RefreshKeysJob::Private::doStart()
{
if (m_canceled) {
q->deleteLater();
return;
}
Q_ASSERT(m_jobsPending.size() == 0);
m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::OpenPGP)));
m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::CMS)));
if (m_jobsPending.size() != 0) {
return;
}
const bool hasError = m_mergedResult.error() || m_mergedResult.error().isCanceled();
emitDone(hasError ? m_mergedResult : KeyListResult(Error(GPG_ERR_UNSUPPORTED_OPERATION)));
}
void KeyCache::RefreshKeysJob::Private::updateKeyCache()
{
if (!m_cache || m_canceled) {
q->deleteLater();
return;
}
std::vector<Key> cachedKeys = m_cache->initialized() ? m_cache->keys() : std::vector<Key>();
std::sort(cachedKeys.begin(), cachedKeys.end(), _detail::ByFingerprint<std::less>());
std::vector<Key> keysToRemove;
std::set_difference(cachedKeys.begin(), cachedKeys.end(),
m_keys.begin(), m_keys.end(),
std::back_inserter(keysToRemove),
_detail::ByFingerprint<std::less>());
m_cache->remove(keysToRemove);
m_cache->refresh(m_keys);
}
Error KeyCache::RefreshKeysJob::Private::startKeyListing(GpgME::Protocol proto)
{
const auto * const protocol = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
if (!protocol) {
return Error();
}
QGpgME::ListAllKeysJob *const job = protocol->listAllKeysJob(/*includeSigs*/false, /*validate*/true);
if (!job) {
return Error();
}
#if 0
aheinecke: 2017.01.12:
For unknown reasons the new style connect fails at runtime
over library borders into QGpgME from the GpgME repo
when cross compiled for Windows and default arguments
are used in the Signal.
This was tested with gcc 4.9 (Mingw 3.0.2) and we could not
find an explanation for this. So until this is fixed or we understand
the problem we need to use the old style connect for QGpgME signals.
The new style connect of the canceled signal right below
works fine.
connect(job, &QGpgME::ListAllKeysJob::result,
q, [this](const GpgME::KeyListResult &res, const std::vector<GpgME::Key> &keys) {
listAllKeysJobDone(res, keys);
});
#endif
connect(job, SIGNAL(result(GpgME::KeyListResult,std::vector<GpgME::Key>)),
q, SLOT(listAllKeysJobDone(GpgME::KeyListResult,std::vector<GpgME::Key>)));
connect(q, &RefreshKeysJob::canceled, job, &QGpgME::Job::slotCancel);
// Only do this for initialized keycaches to avoid huge waits for
// signature notations during initial keylisting.
if (proto == GpgME::OpenPGP && m_cache->remarksEnabled() && m_cache->initialized()) {
auto ctx = QGpgME::Job::context(job);
if (ctx) {
ctx->addKeyListMode(KeyListMode::Signatures |
KeyListMode::SignatureNotations);
}
}
const Error error = job->start(true);
if (!error && !error.isCanceled()) {
m_jobsPending.push_back(job);
}
return error;
}
bool KeyCache::initialized() const
{
return d->m_initalized;
}
void KeyCache::Private::ensureCachePopulated() const
{
if (!m_initalized) {
q->startKeyListing();
QEventLoop loop;
loop.connect(q, &KeyCache::keyListingDone,
&loop, &QEventLoop::quit);
qCDebug(LIBKLEO_LOG) << "Waiting for keycache.";
loop.exec();
qCDebug(LIBKLEO_LOG) << "Keycache available.";
}
}
bool KeyCache::pgpOnly() const
{
return d->m_pgpOnly;
}
static bool keyIsOk(const Key &k)
{
return !k.isExpired() && !k.isRevoked() && !k.isInvalid() && !k.isDisabled();
}
static bool uidIsOk(const UserID &uid)
{
return keyIsOk(uid.parent()) && !uid.isRevoked() && !uid.isInvalid();
}
static bool subkeyIsOk(const Subkey &s)
{
return !s.isRevoked() && !s.isInvalid() && !s.isDisabled();
}
namespace
{
time_t creationTimeOfNewestSuitableSubKey(const Key &key, bool needSign, bool needEncrypt)
{
time_t creationTime = 0;
for (const Subkey &s: key.subkeys()) {
if (!subkeyIsOk(s)) {
continue;
}
if (needSign && !s.canSign()) {
continue;
}
if (needEncrypt && !s.canEncrypt()) {
continue;
}
if (s.creationTime() > creationTime) {
creationTime = s.creationTime();
}
}
return creationTime;
}
struct BestMatch
{
Key key;
UserID uid;
time_t creationTime = 0;
};
}
std::vector<GpgME::Key> KeyCache::findBestByMailBox(const char *addr, GpgME::Protocol proto,
bool needSign, bool needEncrypt) const
{
d->ensureCachePopulated();
std::vector<GpgME::Key> ret;
if (!addr) {
return ret;
}
if (proto == Protocol::OpenPGP) {
// Groups take precedence over automatic keys. We return all here
// so if a group key for example is expired we can show that.
ret = getGroupKeys(QString::fromUtf8(addr));
if (!ret.empty()) {
return ret;
}
}
// support lookup of email addresses enclosed in angle brackets
QByteArray address(addr);
if (address[0] == '<' && address[address.size() - 1] == '>') {
address = address.mid(1, address.size() - 2);
}
address = address.toLower();
BestMatch best;
for (const Key &k: findByEMailAddress(address.constData())) {
if (proto != Protocol::UnknownProtocol && k.protocol() != proto) {
continue;
}
if (needEncrypt && !k.canEncrypt()) {
continue;
}
if (needSign && (!k.canSign() || !k.hasSecret())) {
continue;
}
const time_t creationTime = creationTimeOfNewestSuitableSubKey(k, needSign, needEncrypt);
if (creationTime == 0) {
// key does not have a suitable (and usable) subkey
continue;
}
for (const UserID &u: k.userIDs()) {
if (QByteArray::fromStdString(u.addrSpec()).toLower() != address) {
// user ID does not match the given email address
continue;
}
if (best.uid.isNull()) {
// we have found our first candidate
best = {k, u, creationTime};
} else if (!uidIsOk(best.uid) && uidIsOk(u)) {
// validity of the new key is better
best = {k, u, creationTime};
} else if (!k.isExpired() && best.uid.validity() < u.validity()) {
// validity of the new key is better
best = {k, u, creationTime};
} else if (best.key.isExpired() && !k.isExpired()) {
// validity of the new key is better
best = {k, u, creationTime};
} else if (best.uid.validity() == u.validity() && uidIsOk(u) && best.creationTime < creationTime) {
// both keys/user IDs have same validity, but the new key is newer
best = {k, u, creationTime};
}
}
}
ret.push_back(best.key);
return ret;
}
std::vector<Key> KeyCache::getGroupKeys(const QString &groupName) const
{
std::vector<Key> result;
for (const KeyGroup &g : qAsConst(d->m_groups)) {
if (g.name() == groupName) {
const KeyGroup::Keys &keys = g.keys();
std::copy(keys.cbegin(), keys.cend(), std::back_inserter(result));
}
}
_detail::sort_by_fpr(result);
_detail::remove_duplicates_by_fpr(result);
return result;
}
#include "moc_keycache_p.cpp"
#include "moc_keycache.cpp"
diff --git a/src/utils/formatting.cpp b/src/utils/formatting.cpp
index 1ab16f31..2cbee02a 100644
--- a/src/utils/formatting.cpp
+++ b/src/utils/formatting.cpp
@@ -1,1205 +1,1206 @@
/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
utils/formatting.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "formatting.h"
#include "kleo/dn.h"
#include "kleo/keyfiltermanager.h"
#include "kleo/keygroup.h"
+#include "utils/compat.h"
+
#include <gpgme++/key.h>
#include <gpgme++/importresult.h>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include <KLocalizedString>
#include <KEmailAddress>
#include <QString>
#include <QStringList>
#include <QDateTime>
#include <QTextDocument> // for Qt::escape
#include <QLocale>
#include <QIcon>
#include <QRegularExpression>
#include "models/keycache.h"
using namespace GpgME;
using namespace Kleo;
//
// Name
//
QString Formatting::prettyName(int proto, const char *id, const char *name_, const char *comment_)
{
if (proto == OpenPGP) {
const QString name = QString::fromUtf8(name_);
if (name.isEmpty()) {
return QString();
}
const QString comment = QString::fromUtf8(comment_);
if (comment.isEmpty()) {
return name;
}
return QStringLiteral("%1 (%2)").arg(name, comment);
}
if (proto == CMS) {
const DN subject(id);
const QString cn = subject[QStringLiteral("CN")].trimmed();
if (cn.isEmpty()) {
return subject.prettyDN();
}
return cn;
}
return QString();
}
QString Formatting::prettyNameAndEMail(int proto, const char *id, const char *name_, const char *email_, const char *comment_)
{
return prettyNameAndEMail(proto, QString::fromUtf8(id), QString::fromUtf8(name_), prettyEMail(email_, id), QString::fromUtf8(comment_));
}
QString Formatting::prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment)
{
if (proto == OpenPGP) {
if (name.isEmpty()) {
if (email.isEmpty()) {
return QString();
} else if (comment.isEmpty()) {
return QStringLiteral("<%1>").arg(email);
} else {
return QStringLiteral("(%2) <%1>").arg(email, comment);
}
}
if (email.isEmpty()) {
if (comment.isEmpty()) {
return name;
} else {
return QStringLiteral("%1 (%2)").arg(name, comment);
}
}
if (comment.isEmpty()) {
return QStringLiteral("%1 <%2>").arg(name, email);
} else {
return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment);
}
}
if (proto == CMS) {
const DN subject(id);
const QString cn = subject[QStringLiteral("CN")].trimmed();
if (cn.isEmpty()) {
return subject.prettyDN();
}
return cn;
}
return QString();
}
QString Formatting::prettyUserID(const UserID &uid)
{
if (uid.parent().protocol() == OpenPGP) {
return prettyNameAndEMail(uid);
}
const QByteArray id = QByteArray(uid.id()).trimmed();
if (id.startsWith('<')) {
return prettyEMail(uid.email(), uid.id());
}
if (id.startsWith('('))
// ### parse uri/dns:
{
return QString::fromUtf8(uid.id());
} else {
return DN(uid.id()).prettyDN();
}
}
QString Formatting::prettyKeyID(const char *id)
{
if (!id) {
return QString();
}
return QLatin1String("0x") + QString::fromLatin1(id).toUpper();
}
QString Formatting::prettyNameAndEMail(const UserID &uid)
{
return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment());
}
QString Formatting::prettyNameAndEMail(const Key &key)
{
return prettyNameAndEMail(key.userID(0));
}
QString Formatting::prettyName(const Key &key)
{
return prettyName(key.userID(0));
}
QString Formatting::prettyName(const UserID &uid)
{
return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment());
}
QString Formatting::prettyName(const UserID::Signature &sig)
{
return prettyName(OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment());
}
//
// EMail
//
QString Formatting::prettyEMail(const Key &key)
{
for (unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) {
const QString email = prettyEMail(key.userID(i));
if (!email.isEmpty()) {
return email;
}
}
return QString();
}
QString Formatting::prettyEMail(const UserID &uid)
{
return prettyEMail(uid.email(), uid.id());
}
QString Formatting::prettyEMail(const UserID::Signature &sig)
{
return prettyEMail(sig.signerEmail(), sig.signerUserID());
}
QString Formatting::prettyEMail(const char *email_, const char *id)
{
QString email, name, comment;
if (email_ && KEmailAddress::splitAddress(QString::fromUtf8(email_),
name, email, comment) == KEmailAddress::AddressOk) {
return email;
} else {
return DN(id)[QStringLiteral("EMAIL")].trimmed();
}
}
//
// Tooltip
//
namespace
{
static QString protect_whitespace(QString s)
{
static const QLatin1Char SP(' '), NBSP('\xA0');
return s.replace(SP, NBSP);
}
template <typename T_arg>
QString format_row(const QString &field, const T_arg &arg)
{
return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg);
}
QString format_row(const QString &field, const QString &arg)
{
return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg.toHtmlEscaped());
}
QString format_row(const QString &field, const char *arg)
{
return format_row(field, QString::fromUtf8(arg));
}
QString format_keytype(const Key &key)
{
const Subkey subkey = key.subkey(0);
if (key.hasSecret()) {
return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString()));
} else {
return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString()));
}
}
QString format_subkeytype(const Subkey &subkey)
{
const auto algo = subkey.publicKeyAlgorithm();
if (algo == Subkey::AlgoECC ||
algo == Subkey::AlgoECDSA ||
algo == Subkey::AlgoECDH ||
algo == Subkey::AlgoEDDSA) {
return QString::fromStdString(subkey.algoName());
}
return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString()));
}
QString format_keyusage(const Key &key)
{
QStringList capabilities;
if (key.canReallySign()) {
if (key.isQualified()) {
capabilities.push_back(i18n("Signing (Qualified)"));
} else {
capabilities.push_back(i18n("Signing"));
}
}
if (key.canEncrypt()) {
capabilities.push_back(i18n("Encryption"));
}
if (key.canCertify()) {
capabilities.push_back(i18n("Certifying User-IDs"));
}
if (key.canAuthenticate()) {
capabilities.push_back(i18n("SSH Authentication"));
}
return capabilities.join(QLatin1String(", "));
}
QString format_subkeyusage(const Subkey &subkey)
{
QStringList capabilities;
if (subkey.canSign()) {
if (subkey.isQualified()) {
capabilities.push_back(i18n("Signing (Qualified)"));
} else {
capabilities.push_back(i18n("Signing"));
}
}
if (subkey.canEncrypt()) {
capabilities.push_back(i18n("Encryption"));
}
if (subkey.canCertify()) {
capabilities.push_back(i18n("Certifying User-IDs"));
}
if (subkey.canAuthenticate()) {
capabilities.push_back(i18n("SSH Authentication"));
}
return capabilities.join(QLatin1String(", "));
}
static QString time_t2string(time_t t)
{
QDateTime dt;
dt.setTime_t(t);
return QLocale().toString(dt, QLocale::ShortFormat);
}
static QString make_red(const QString &txt)
{
return QLatin1String("<font color=\"red\">") + txt.toHtmlEscaped() + QLatin1String("</font>");
}
}
QString Formatting::toolTip(const Key &key, int flags)
{
if (flags == 0 || (key.protocol() != CMS && key.protocol() != OpenPGP)) {
return QString();
}
const Subkey subkey = key.subkey(0);
QString result;
if (flags & Validity) {
if (key.protocol() == OpenPGP || (key.keyListMode() & Validate))
if (key.isRevoked()) {
result = make_red(i18n("Revoked"));
} else if (key.isExpired()) {
result = make_red(i18n("Expired"));
} else if (key.isDisabled()) {
result = i18n("Disabled");
} else {
unsigned int fullyTrusted = 0;
for (const auto &uid: key.userIDs()) {
if (uid.validity() >= UserID::Validity::Full) {
fullyTrusted++;
}
}
if (fullyTrusted == key.numUserIDs()) {
result = i18n("All User-IDs are certified.");
const auto compliance = complianceStringForKey(key);
if (!compliance.isEmpty()) {
result += QStringLiteral("<br>") + compliance;
}
} else {
result = i18np("One User-ID is not certified.", "%1 User-IDs are not certified.", key.numUserIDs() - fullyTrusted);
}
}
else {
result = i18n("The validity cannot be checked at the moment.");
}
}
if (flags == Validity) {
return result;
}
result += QLatin1String("<table border=\"0\">");
if (key.protocol() == CMS) {
if (flags & SerialNumber) {
result += format_row(i18n("Serial number"), key.issuerSerial());
}
if (flags & Issuer) {
result += format_row(i18n("Issuer"), key.issuerName());
}
}
if (flags & UserIDs) {
const std::vector<UserID> uids = key.userIDs();
if (!uids.empty())
result += format_row(key.protocol() == CMS
? i18n("Subject")
: i18n("User-ID"), prettyUserID(uids.front()));
if (uids.size() > 1)
for (auto it = uids.begin() + 1, end = uids.end(); it != end; ++it)
if (!it->isRevoked() && !it->isInvalid()) {
result += format_row(i18n("a.k.a."), prettyUserID(*it));
}
}
if (flags & ExpiryDates) {
result += format_row(i18n("Created"), time_t2string(subkey.creationTime()));
if (key.isExpired()) {
result += format_row(i18n("Expired"), time_t2string(subkey.expirationTime()));
} else if (!subkey.neverExpires()) {
result += format_row(i18n("Expires"), time_t2string(subkey.expirationTime()));
}
}
if (flags & CertificateType) {
result += format_row(i18n("Type"), format_keytype(key));
}
if (flags & CertificateUsage) {
result += format_row(i18n("Usage"), format_keyusage(key));
}
if (flags & KeyID) {
result += format_row(i18n("Key-ID"), QString::fromLatin1(key.shortKeyID()));
}
if (flags & Fingerprint) {
result += format_row(i18n("Fingerprint"), key.primaryFingerprint());
}
if (flags & OwnerTrust) {
if (key.protocol() == OpenPGP) {
result += format_row(i18n("Certification trust"), ownerTrustShort(key));
} else if (key.isRoot()) {
result += format_row(i18n("Trusted issuer?"),
key.userID(0).validity() == UserID::Ultimate ? i18n("Yes") :
/* else */ i18n("No"));
}
}
if (flags & StorageLocation) {
if (const char *card = subkey.cardSerialNumber()) {
result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
} else {
result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
}
}
if (flags & Subkeys) {
for (const auto &sub: key.subkeys()) {
result += QLatin1String("<hr/>");
result += format_row(i18n("Subkey"), sub.fingerprint());
if (sub.isRevoked()) {
result += format_row(i18n("Status"), i18n("Revoked"));
} else if (sub.isExpired()) {
result += format_row(i18n("Status"), i18n("Expired"));
}
if (flags & ExpiryDates) {
result += format_row(i18n("Created"), time_t2string(sub.creationTime()));
if (key.isExpired()) {
result += format_row(i18n("Expired"), time_t2string(sub.expirationTime()));
} else if (!subkey.neverExpires()) {
result += format_row(i18n("Expires"), time_t2string(sub.expirationTime()));
}
}
if (flags & CertificateType) {
result += format_row(i18n("Type"), format_subkeytype(sub));
}
if (flags & CertificateUsage) {
result += format_row(i18n("Usage"), format_subkeyusage(sub));
}
if (flags & StorageLocation) {
if (const char *card = sub.cardSerialNumber()) {
result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
} else {
result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
}
}
}
}
result += QLatin1String("</table>");
return result;
}
namespace
{
template <typename Container>
QString getValidityStatement(const Container &keys)
{
const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [] (const Key &key) { return key.protocol() == OpenPGP; });
const bool allKeysAreValidated = std::all_of(keys.cbegin(), keys.cend(), [] (const Key &key) { return key.keyListMode() & Validate; });
if (allKeysAreOpenPGP || allKeysAreValidated) {
const bool someKeysAreBad = std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::isBad));
if (someKeysAreBad) {
return i18n("Some keys are revoked, expired, disabled, or invalid.");
} else {
const bool allKeysAreFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Formatting::uidsHaveFullValidity);
if (allKeysAreFullyValid) {
return i18n("All keys are certified.");
} else {
return i18n("Some keys are not certified.");
}
}
}
return i18n("The validity of the keys cannot be checked at the moment.");
}
}
QString Formatting::toolTip(const KeyGroup &group, int flags)
{
static const unsigned int maxNumKeysForTooltip = 20;
if (group.isNull()) {
return QString();
}
const KeyGroup::Keys &keys = group.keys();
if (keys.size() == 0) {
return i18nc("@info:tooltip", "This group does not contain any keys.");
}
const QString validity = (flags & Validity) ? getValidityStatement(keys) : QString();
if (flags == Validity) {
return validity;
}
// list either up to maxNumKeysForTooltip keys or (maxNumKeysForTooltip-1) keys followed by "and n more keys"
const unsigned int numKeysForTooltip = keys.size() > maxNumKeysForTooltip ? maxNumKeysForTooltip - 1 : keys.size();
QStringList result;
result.reserve(3 + 2 + numKeysForTooltip + 2);
if (!validity.isEmpty()) {
result.push_back("<p>");
result.push_back(validity.toHtmlEscaped());
result.push_back("</p>");
}
result.push_back("<p>");
result.push_back(i18n("Keys:"));
{
auto it = keys.cbegin();
for (unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) {
result.push_back(QLatin1String("<br>") + Formatting::summaryLine(*it).toHtmlEscaped());
}
}
if (keys.size() > numKeysForTooltip) {
result.push_back(QLatin1String("<br>") + i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip));
}
result.push_back("</p>");
return result.join(QLatin1Char('\n'));
}
//
// Creation and Expiration
//
namespace
{
static QDate time_t2date(time_t t)
{
if (!t) {
return {};
}
QDateTime dt;
dt.setTime_t(t);
return dt.date();
}
static QString date2string(const QDate &date)
{
return QLocale().toString(date, QLocale::ShortFormat);
}
template <typename T>
QString expiration_date_string(const T &tee)
{
return tee.neverExpires() ? QString() : date2string(time_t2date(tee.expirationTime()));
}
template <typename T>
QDate creation_date(const T &tee)
{
return time_t2date(tee.creationTime());
}
template <typename T>
QDate expiration_date(const T &tee)
{
return time_t2date(tee.expirationTime());
}
}
QString Formatting::dateString(time_t t)
{
return date2string(time_t2date(t));
}
QString Formatting::expirationDateString(const Key &key)
{
return expiration_date_string(key.subkey(0));
}
QString Formatting::expirationDateString(const Subkey &subkey)
{
return expiration_date_string(subkey);
}
QString Formatting::expirationDateString(const UserID::Signature &sig)
{
return expiration_date_string(sig);
}
QDate Formatting::expirationDate(const Key &key)
{
return expiration_date(key.subkey(0));
}
QDate Formatting::expirationDate(const Subkey &subkey)
{
return expiration_date(subkey);
}
QDate Formatting::expirationDate(const UserID::Signature &sig)
{
return expiration_date(sig);
}
QString Formatting::creationDateString(const Key &key)
{
return date2string(creation_date(key.subkey(0)));
}
QString Formatting::creationDateString(const Subkey &subkey)
{
return date2string(creation_date(subkey));
}
QString Formatting::creationDateString(const UserID::Signature &sig)
{
return date2string(creation_date(sig));
}
QDate Formatting::creationDate(const Key &key)
{
return creation_date(key.subkey(0));
}
QDate Formatting::creationDate(const Subkey &subkey)
{
return creation_date(subkey);
}
QDate Formatting::creationDate(const UserID::Signature &sig)
{
return creation_date(sig);
}
//
// Types
//
QString Formatting::displayName(Protocol p)
{
if (p == CMS) {
return i18nc("X.509/CMS encryption standard", "S/MIME");
}
if (p == OpenPGP) {
return i18n("OpenPGP");
}
return i18nc("Unknown encryption protocol", "Unknown");
}
QString Formatting::type(const Key &key)
{
return displayName(key.protocol());
}
QString Formatting::type(const Subkey &subkey)
{
return QString::fromUtf8(subkey.publicKeyAlgorithmAsString());
}
QString Formatting::type(const KeyGroup &group)
{
Q_UNUSED(group)
return i18nc("a group of keys/certificates", "Group");
}
//
// Status / Validity
//
QString Formatting::ownerTrustShort(const Key &key)
{
return ownerTrustShort(key.ownerTrust());
}
QString Formatting::ownerTrustShort(Key::OwnerTrust trust)
{
switch (trust) {
case Key::Unknown: return i18nc("unknown trust level", "unknown");
case Key::Never: return i18n("untrusted");
case Key::Marginal: return i18nc("marginal trust", "marginal");
case Key::Full: return i18nc("full trust", "full");
case Key::Ultimate: return i18nc("ultimate trust", "ultimate");
case Key::Undefined: return i18nc("undefined trust", "undefined");
default:
Q_ASSERT(!"unexpected owner trust value");
break;
}
return QString();
}
QString Formatting::validityShort(const Subkey &subkey)
{
if (subkey.isRevoked()) {
return i18n("revoked");
}
if (subkey.isExpired()) {
return i18n("expired");
}
if (subkey.isDisabled()) {
return i18n("disabled");
}
if (subkey.isInvalid()) {
return i18n("invalid");
}
return i18nc("as in good/valid signature", "good");
}
QString Formatting::validityShort(const UserID &uid)
{
if (uid.isRevoked()) {
return i18n("revoked");
}
if (uid.isInvalid()) {
return i18n("invalid");
}
switch (uid.validity()) {
case UserID::Unknown: return i18nc("unknown trust level", "unknown");
case UserID::Undefined: return i18nc("undefined trust", "undefined");
case UserID::Never: return i18n("untrusted");
case UserID::Marginal: return i18nc("marginal trust", "marginal");
case UserID::Full: return i18nc("full trust", "full");
case UserID::Ultimate: return i18nc("ultimate trust", "ultimate");
}
return QString();
}
QString Formatting::validityShort(const UserID::Signature &sig)
{
switch (sig.status()) {
case UserID::Signature::NoError:
if (!sig.isInvalid()) {
/* See RFC 4880 Section 5.2.1 */
switch (sig.certClass()) {
case 0x10: /* Generic */
case 0x11: /* Persona */
case 0x12: /* Casual */
case 0x13: /* Positive */
return i18n("valid");
case 0x30:
return i18n("revoked");
default:
return i18n("class %1", sig.certClass());
}
}
Q_FALLTHROUGH();
// fall through:
case UserID::Signature::GeneralError:
return i18n("invalid");
case UserID::Signature::SigExpired: return i18n("expired");
case UserID::Signature::KeyExpired: return i18n("certificate expired");
case UserID::Signature::BadSignature: return i18nc("fake/invalid signature", "bad");
case UserID::Signature::NoPublicKey: {
/* GnuPG returns the same error for no public key as for expired
* or revoked certificates. */
const auto key = KeyCache::instance()->findByKeyIDOrFingerprint (sig.signerKeyID());
if (key.isNull()) {
return i18n("no public key");
} else if (key.isExpired()) {
return i18n("key expired");
} else if (key.isRevoked()) {
return i18n("key revoked");
} else if (key.isDisabled()) {
return i18n("key disabled");
}
/* can't happen */
return QStringLiteral("unknown");
}
}
return QString();
}
QIcon Formatting::validityIcon(const UserID::Signature &sig)
{
switch (sig.status()) {
case UserID::Signature::NoError:
if (!sig.isInvalid()) {
/* See RFC 4880 Section 5.2.1 */
switch (sig.certClass()) {
case 0x10: /* Generic */
case 0x11: /* Persona */
case 0x12: /* Casual */
case 0x13: /* Positive */
return QIcon::fromTheme(QStringLiteral("emblem-success"));
case 0x30:
return QIcon::fromTheme(QStringLiteral("emblem-error"));
default:
return QIcon();
}
}
Q_FALLTHROUGH();
// fall through:
case UserID::Signature::BadSignature:
case UserID::Signature::GeneralError:
return QIcon::fromTheme(QStringLiteral("emblem-error"));
case UserID::Signature::SigExpired:
case UserID::Signature::KeyExpired:
return QIcon::fromTheme(QStringLiteral("emblem-information"));
case UserID::Signature::NoPublicKey:
return QIcon::fromTheme(QStringLiteral("emblem-question"));
}
return QIcon();
}
QString Formatting::formatKeyLink(const Key &key)
{
if (key.isNull()) {
return QString();
}
return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(QLatin1String(key.primaryFingerprint()), Formatting::prettyName(key));
}
QString Formatting::formatForComboBox(const GpgME::Key &key)
{
const QString name = prettyName(key);
QString mail = prettyEMail(key);
if (!mail.isEmpty()) {
mail = QLatin1Char('<') + mail + QLatin1Char('>');
}
return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1String(key.shortKeyID())).simplified();
}
namespace
{
static QString keyToString(const Key &key)
{
Q_ASSERT(!key.isNull());
const QString email = Formatting::prettyEMail(key);
const QString name = Formatting::prettyName(key);
if (name.isEmpty()) {
return email;
} else if (email.isEmpty()) {
return name;
} else {
return QStringLiteral("%1 <%2>").arg(name, email);
}
}
}
const char *Formatting::summaryToString(const Signature::Summary summary)
{
if (summary & Signature::Red) {
return "RED";
}
if (summary & Signature::Green) {
return "GREEN";
}
return "YELLOW";
}
QString Formatting::signatureToString(const Signature &sig, const Key &key)
{
if (sig.isNull()) {
return QString();
}
const bool red = (sig.summary() & Signature::Red);
const bool valid = (sig.summary() & Signature::Valid);
if (red)
if (key.isNull())
if (const char *fpr = sig.fingerprint()) {
return i18n("Bad signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString()));
} else {
return i18n("Bad signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString()));
}
else {
return i18n("Bad signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString()));
}
else if (valid)
if (key.isNull())
if (const char *fpr = sig.fingerprint()) {
return i18n("Good signature by unknown certificate %1.", QString::fromLatin1(fpr));
} else {
return i18n("Good signature by an unknown certificate.");
}
else {
return i18n("Good signature by %1.", keyToString(key));
}
else if (key.isNull())
if (const char *fpr = sig.fingerprint()) {
return i18n("Invalid signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString()));
} else {
return i18n("Invalid signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString()));
}
else {
return i18n("Invalid signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString()));
}
}
//
// ImportResult
//
QString Formatting::importMetaData(const Import &import, const QStringList &ids)
{
const QString result = importMetaData(import);
if (result.isEmpty()) {
return QString();
} else
return result + QLatin1Char('\n') +
i18n("This certificate was imported from the following sources:") + QLatin1Char('\n') +
ids.join(QLatin1Char('\n'));
}
QString Formatting::importMetaData(const Import &import)
{
if (import.isNull()) {
return QString();
}
if (import.error().isCanceled()) {
return i18n("The import of this certificate was canceled.");
}
if (import.error())
return i18n("An error occurred importing this certificate: %1",
QString::fromLocal8Bit(import.error().asString()));
const unsigned int status = import.status();
if (status & Import::NewKey)
return (status & Import::ContainedSecretKey)
? i18n("This certificate was new to your keystore. The secret key is available.")
: i18n("This certificate is new to your keystore.");
QStringList results;
if (status & Import::NewUserIDs) {
results.push_back(i18n("New user-ids were added to this certificate by the import."));
}
if (status & Import::NewSignatures) {
results.push_back(i18n("New signatures were added to this certificate by the import."));
}
if (status & Import::NewSubkeys) {
results.push_back(i18n("New subkeys were added to this certificate by the import."));
}
return results.empty()
? i18n("The import contained no new data for this certificate. It is unchanged.")
: results.join(QLatin1Char('\n'));
}
//
// Overview in CertificateDetailsDialog
//
QString Formatting::formatOverview(const Key &key)
{
return toolTip(key, AllOptions);
}
QString Formatting::usageString(const Subkey &sub)
{
QStringList usageStrings;
if (sub.canCertify()) {
usageStrings << i18n("Certify");
}
if (sub.canSign()) {
usageStrings << i18n("Sign");
}
if (sub.canEncrypt()) {
usageStrings << i18n("Encrypt");
}
if (sub.canAuthenticate()) {
usageStrings << i18n("Authenticate");
}
return usageStrings.join(QLatin1String(", "));
}
QString Formatting::summaryLine(const Key &key)
{
return keyToString(key) + QLatin1Char(' ') +
i18nc("(validity, protocol, creation date)",
"(%1, %2, created: %3)",
Formatting::complianceStringShort(key),
displayName(key.protocol()),
Formatting::creationDateString(key));
}
QString Formatting::summaryLine(const KeyGroup &group)
{
switch (group.source()) {
case KeyGroup::ApplicationConfig:
case KeyGroup::GnuPGConfig:
return i18ncp("name of group of keys (n key(s), validity)",
"%2 (1 key, %3)", "%2 (%1 keys, %3)",
group.keys().size(), group.name(), Formatting::complianceStringShort(group));
case KeyGroup::Tags:
return i18ncp("name of group of keys (n key(s), validity, tag)",
"%2 (1 key, %3, tag)", "%2 (%1 keys, %3, tag)",
group.keys().size(), group.name(), Formatting::complianceStringShort(group));
default:
return i18ncp("name of group of keys (n key(s), validity, group ...)",
"%2 (1 key, %3, unknown origin)", "%2 (%1 keys, %3, unknown origin)",
group.keys().size(), group.name(), Formatting::complianceStringShort(group));
}
}
namespace
{
QIcon iconForValidity(UserID::Validity validity)
{
switch (validity) {
case UserID::Ultimate:
case UserID::Full:
case UserID::Marginal:
return QIcon::fromTheme(QStringLiteral("emblem-success"));
case UserID::Never:
return QIcon::fromTheme(QStringLiteral("emblem-error"));
case UserID::Undefined:
case UserID::Unknown:
default:
return QIcon::fromTheme(QStringLiteral("emblem-information"));
}
}
}
// Icon for certificate selection indication
QIcon Formatting::iconForUid(const UserID &uid)
{
return iconForValidity(uid.validity());
}
QString Formatting::validity(const UserID &uid)
{
switch (uid.validity()) {
case UserID::Ultimate:
return i18n("The certificate is marked as your own.");
case UserID::Full:
return i18n("The certificate belongs to this recipient.");
case UserID::Marginal:
return i18n("The trust model indicates marginally that the certificate belongs to this recipient.");
case UserID::Never:
return i18n("This certificate should not be used.");
case UserID::Undefined:
case UserID::Unknown:
default:
return i18n("There is no indication that this certificate belongs to this recipient.");
}
}
QString Formatting::validity(const KeyGroup &group)
{
if (group.isNull()) {
return QString();
}
const KeyGroup::Keys &keys = group.keys();
if (keys.size() == 0) {
return i18n("This group does not contain any keys.");
}
return getValidityStatement(keys);
}
namespace
{
UserID::Validity minimalValidityOfNotRevokedUserIDs(const Key &key)
{
std::vector<UserID> userIDs = key.userIDs();
const auto endOfNotRevokedUserIDs = std::remove_if(userIDs.begin(), userIDs.end(), std::mem_fn(&UserID::isRevoked));
const int minValidity = std::accumulate(userIDs.begin(), endOfNotRevokedUserIDs, UserID::Ultimate + 1,
[] (int validity, const UserID &userID) {
return std::min(validity, static_cast<int>(userID.validity()));
});
return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
}
template <typename Container>
UserID::Validity minimalValidity(const Container& keys)
{
const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1,
[] (int validity, const Key &key) {
return std::min<int>(validity, minimalValidityOfNotRevokedUserIDs(key));
});
return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
}
}
QIcon Formatting::validityIcon(const KeyGroup &group)
{
return iconForValidity(minimalValidity(group.keys()));
}
bool Formatting::uidsHaveFullValidity(const Key &key)
{
return minimalValidityOfNotRevokedUserIDs(key) >= UserID::Full;
}
QString Formatting::complianceMode()
{
const QGpgME::CryptoConfig *const config = QGpgME::cryptoConfig();
if (!config) {
return QString();
}
- const QGpgME::CryptoConfigEntry *const entry = config->entry(QStringLiteral("gpg"), QStringLiteral("Configuration"), QStringLiteral("compliance"));
-
+ const QGpgME::CryptoConfigEntry *const entry = getCryptoConfigEntry(config, "gpg", "compliance");
if (!entry || entry->stringValue() == QLatin1String("gnupg")) {
return QString();
}
return entry->stringValue();
}
bool Formatting::isKeyDeVs(const GpgME::Key &key)
{
for (const auto &sub: key.subkeys()) {
if (sub.isExpired() || sub.isRevoked()) {
// Ignore old subkeys
continue;
}
if (!sub.isDeVs()) {
return false;
}
}
return true;
}
QString Formatting::complianceStringForKey(const GpgME::Key &key)
{
// There will likely be more in the future for other institutions
// for now we only have DE-VS
if (complianceMode() == QLatin1String("de-vs")) {
if (uidsHaveFullValidity(key) && isKeyDeVs(key)) {
return i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"May be used for %1 communication.", deVsString());
} else {
return i18nc("VS-NfD-conforming is a German standard for restricted documents. For which special restrictions about algorithms apply. The string describes if a key is compliant to that..",
"May <b>not</b> be used for %1 communication.", deVsString());
}
}
return QString();
}
QString Formatting::complianceStringShort(const GpgME::Key &key)
{
if (Formatting::uidsHaveFullValidity(key)) {
if (complianceMode() == QLatin1String("de-vs")
&& Formatting::isKeyDeVs(key)) {
return QStringLiteral("★ ") + deVsString(true);
}
return i18nc("As in all user IDs are valid.", "certified");
}
if (key.isExpired()) {
return i18n("expired");
}
if (key.isRevoked()) {
return i18n("revoked");
}
if (key.isDisabled()) {
return i18n("disabled");
}
if (key.isInvalid()) {
return i18n("invalid");
}
return i18nc("As in not all user IDs are valid.", "not certified");
}
QString Formatting::complianceStringShort(const KeyGroup &group)
{
const KeyGroup::Keys &keys = group.keys();
const bool allKeysFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Formatting::uidsHaveFullValidity);
if (allKeysFullyValid) {
return i18nc("As in all keys are valid.", "all certified");
}
return i18nc("As in not all keys are valid.", "not all certified");
}
QString Formatting::prettyID(const char *id)
{
if (!id) {
return QString();
}
QString ret = QString::fromLatin1(id).toUpper().replace(QRegularExpression(QStringLiteral("(....)")),
QStringLiteral("\\1 ")).trimmed();
// For the standard 10 group fingerprint let us use a double space in the
// middle to increase readability
if (ret.size() == 49) {
ret.insert(24, QLatin1Char(' '));
}
return ret;
}
QString Formatting::origin(int o)
{
switch (o) {
case Key::OriginKS:
return i18n("Keyserver");
case Key::OriginDane:
return QStringLiteral("DANE");
case Key::OriginWKD:
return QStringLiteral("WKD");
case Key::OriginURL:
return QStringLiteral("URL");
case Key::OriginFile:
return i18n("File import");
case Key::OriginSelf:
return i18n("Generated");
case Key::OriginOther:
case Key::OriginUnknown:
default:
return i18n("Unknown");
}
}
QString Formatting::deVsString(bool compliant)
{
const auto filter = KeyFilterManager::instance()->keyFilterByID(compliant ?
QStringLiteral("de-vs-filter") :
QStringLiteral("not-de-vs-filter"));
if (!filter) {
return compliant ? i18n("VS-NfD compliant") : i18n("Not VS-NfD compliant");
}
return filter->name();
}
diff --git a/src/utils/gnupg.cpp b/src/utils/gnupg.cpp
index d37f7552..d7afcba2 100644
--- a/src/utils/gnupg.cpp
+++ b/src/utils/gnupg.cpp
@@ -1,501 +1,499 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/gnupg.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "gnupg.h"
+#include "utils/compat.h"
#include "utils/hex.h"
#include <gpgme++/engineinfo.h>
#include <gpgme++/error.h>
#include <gpgme++/key.h>
#include <QGpgME/Protocol>
#include <QGpgME/CryptoConfig>
#include "libkleo_debug.h"
#include <QDir>
#include <QFile>
#include <QString>
#include <QProcess>
#include <QByteArray>
#include <QStandardPaths>
#include <QCoreApplication>
#include <gpg-error.h>
#ifdef Q_OS_WIN
#include "gnupg-registry.h"
#endif // Q_OS_WIN
#include <algorithm>
#include <array>
#include <KLocalizedString>
using namespace GpgME;
QString Kleo::gnupgHomeDirectory()
{
static QString homeDir = QString::fromUtf8(GpgME::dirInfo("homedir"));
return homeDir;
}
int Kleo::makeGnuPGError(int code)
{
return gpg_error(static_cast<gpg_err_code_t>(code));
}
static QString findGpgExe(GpgME::Engine engine, const QString &exe)
{
const GpgME::EngineInfo info = GpgME::engineInfo(engine);
return info.fileName() ? QFile::decodeName(info.fileName()) : QStandardPaths::findExecutable(exe);
}
QString Kleo::gpgConfPath()
{
static const auto path = findGpgExe(GpgME::GpgConfEngine, QStringLiteral("gpgconf"));
return path;
}
QString Kleo::gpgSmPath()
{
static const auto path = findGpgExe(GpgME::GpgSMEngine, QStringLiteral("gpgsm"));
return path;
}
QString Kleo::gpgPath()
{
static const auto path = findGpgExe(GpgME::GpgEngine, QStringLiteral("gpg"));
return path;
}
QStringList Kleo::gnupgFileWhitelist()
{
return QStringList()
// The obvious pubring
<< QStringLiteral("pubring.gpg")
// GnuPG 2.1 pubring
<< QStringLiteral("pubring.kbx")
// Trust in X509 Certificates
<< QStringLiteral("trustlist.txt")
// Trustdb controls ownertrust and thus WOT validity
<< QStringLiteral("trustdb.gpg")
// We want to update when smartcard status changes
<< QStringLiteral("reader*.status")
// No longer used in 2.1 but for 2.0 we want this
<< QStringLiteral("secring.gpg")
// Changes to the trustmodel / compliance mode might
// affect validity so we check this, too.
// Globbing for gpg.conf* here will trigger too often
// as gpgconf creates files like gpg.conf.bak or
// gpg.conf.tmp12312.gpgconf that should not trigger
// a change.
<< QStringLiteral("gpg.conf")
<< QStringLiteral("gpg.conf-?")
<< QStringLiteral("gpg.conf-?.?")
;
}
namespace
{
class Gpg4win
{
public:
static const Gpg4win *instance()
{
// We use singleton to do the signature check only once.
static Gpg4win *inst = nullptr;
if (!inst) {
inst = new Gpg4win();
}
return inst;
}
private:
QString mVersion;
QString mDescription;
QString mDescLong;
bool mSignedVersion;
Gpg4win() :
mVersion(QStringLiteral("Unknown Windows Version")),
mDescription(i18n("Certificate Manager and Unified Crypto GUI")),
mDescLong(QStringLiteral("<a href=https://www.gpg4win.org>Visit the Gpg4win homepage</a>")),
mSignedVersion(false)
{
const QString instPath = Kleo::gpg4winInstallPath();
const QString verPath = instPath + QStringLiteral("/../VERSION");
QFile versionFile(verPath);
QString versVersion;
QString versDescription;
QString versDescLong;
// Open the file first to avoid a verify and then read issue where
// "auditors" might say its an issue,...
if (!versionFile.open(QIODevice::ReadOnly)) {
// No need to translate this should only be the case in development
// builds.
return;
} else {
// Expect a three line format of three HTML strings.
versVersion = QString::fromUtf8(versionFile.readLine()).trimmed();
versDescription = QString::fromUtf8(versionFile.readLine()).trimmed();
versDescLong = QString::fromUtf8(versionFile.readLine()).trimmed();
}
const QString sigPath = verPath + QStringLiteral(".sig");
QFileInfo versionSig(instPath + QStringLiteral("/../VERSION.sig"));
QString signedVersion;
if (versionSig.exists()) {
/* We have a signed version so let us check it against the GnuPG
* release keys. */
QProcess gpgv;
gpgv.setProgram(Kleo::gpgPath().replace(QStringLiteral("gpg.exe"), QStringLiteral("gpgv.exe")));
const QString keyringPath(QStringLiteral("%1/../share/gnupg/distsigkey.gpg").arg(Kleo::gnupgInstallPath()));
gpgv.setArguments(QStringList() << QStringLiteral("--keyring")
<< keyringPath
<< QStringLiteral("--")
<< sigPath
<< verPath);
gpgv.start();
gpgv.waitForFinished();
if (gpgv.exitStatus() == QProcess::NormalExit &&
!gpgv.exitCode()) {
qCDebug(LIBKLEO_LOG) << "Valid Version: " << versVersion;
mVersion = versVersion;
mDescription = versDescription;
mDescLong = versDescLong;
mSignedVersion = true;
} else {
qCDebug(LIBKLEO_LOG) << "gpgv failed with stderr: " << gpgv.readAllStandardError();
qCDebug(LIBKLEO_LOG) << "gpgv stdout" << gpgv.readAllStandardOutput();
}
} else {
qCDebug(LIBKLEO_LOG) << "No signed VERSION file found.";
}
// Also take Version information from unsigned Versions.
mVersion = versVersion;
}
public:
const QString &version() const
{
return mVersion;
}
const QString &description() const
{
return mDescription;
}
const QString &longDescription() const
{
return mDescLong;
}
bool isSignedVersion() const
{
return mSignedVersion;
}
};
} // namespace
bool Kleo::gpg4winSignedversion()
{
return Gpg4win::instance()->isSignedVersion();
}
QString Kleo::gpg4winVersion()
{
return Gpg4win::instance()->version();
}
QString Kleo::gpg4winDescription()
{
return Gpg4win::instance()->description();
}
QString Kleo::gpg4winLongDescription()
{
return Gpg4win::instance()->longDescription();
}
QString Kleo::gpg4winInstallPath()
{
#ifdef Q_OS_WIN
// QApplication::applicationDirPath is only used as a fallback
// to support the case where Kleopatra is not installed from
// Gpg4win but Gpg4win is also installed.
char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE",
"Software\\GPG4Win",
"Install Directory");
if (!instDir) {
// Fallback to HKCU
instDir = read_w32_registry_string("HKEY_CURRENT_USER",
"Software\\GPG4Win",
"Install Directory");
}
if (instDir) {
QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin");
free(instDir);
return ret;
}
qCDebug(LIBKLEO_LOG) << "Gpg4win not found. Falling back to Kleopatra instdir.";
#endif
return QCoreApplication::applicationDirPath();
}
QString Kleo::gnupgInstallPath()
{
#ifdef Q_OS_WIN
// QApplication::applicationDirPath is only used as a fallback
// to support the case where Kleopatra is not installed from
// Gpg4win but Gpg4win is also installed.
char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE",
"Software\\GnuPG",
"Install Directory");
if (!instDir) {
// Fallback to HKCU
instDir = read_w32_registry_string("HKEY_CURRENT_USER",
"Software\\GnuPG",
"Install Directory");
}
if (instDir) {
QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin");
free(instDir);
return ret;
}
qCDebug(LIBKLEO_LOG) << "GnuPG not found. Falling back to gpgconf list dir.";
#endif
return gpgConfListDir("bindir");
}
QString Kleo::gpgConfListDir(const char *which)
{
if (!which || !*which) {
return QString();
}
const QString gpgConfPath = Kleo::gpgConfPath();
if (gpgConfPath.isEmpty()) {
return QString();
}
QProcess gpgConf;
qCDebug(LIBKLEO_LOG) << "gpgConfListDir: starting " << qPrintable(gpgConfPath) << " --list-dirs";
gpgConf.start(gpgConfPath, QStringList() << QStringLiteral("--list-dirs"));
if (!gpgConf.waitForFinished()) {
qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): failed to execute gpgconf: " << qPrintable(gpgConf.errorString());
qCDebug(LIBKLEO_LOG) << "output was:\n" << gpgConf.readAllStandardError().constData();
return QString();
}
const QList<QByteArray> lines = gpgConf.readAllStandardOutput().split('\n');
for (const QByteArray &line : lines)
if (line.startsWith(which) && line[qstrlen(which)] == ':') {
const int begin = qstrlen(which) + 1;
int end = line.size();
while (end && (line[end - 1] == '\n' || line[end - 1] == '\r')) {
--end;
}
const QString result = QDir::fromNativeSeparators(QFile::decodeName(hexdecode(line.mid(begin, end - begin))));
qCDebug(LIBKLEO_LOG) << "gpgConfListDir: found " << qPrintable(result)
<< " for '" << which << "'entry";
return result;
}
qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): didn't find '" << which << "'"
<< "entry in output:\n" << gpgConf.readAllStandardError().constData();
return QString();
}
static std::array<int, 3> getVersionFromString(const char *actual, bool &ok)
{
std::array<int, 3> ret;
ok = false;
if (!actual) {
return ret;
}
QString versionString = QString::fromLatin1(actual);
// Try to fix it up
QRegExp rx(QLatin1String(R"((\d+)\.(\d+)\.(\d+)(?:-svn\d+)?.*)"));
for (int i = 0; i < 3; i++) {
if (!rx.exactMatch(versionString)) {
versionString += QStringLiteral(".0");
} else {
ok = true;
break;
}
}
if (!ok) {
qCDebug(LIBKLEO_LOG) << "Can't parse version " << actual;
return ret;
}
for (int i = 0; i < 3; ++i) {
ret[i] = rx.cap(i + 1).toUInt(&ok);
if (!ok) {
return ret;
}
}
ok = true;
return ret;
}
bool Kleo::versionIsAtLeast(const char *minimum, const char *actual)
{
if (!minimum || !actual) {
return false;
}
bool ok;
const auto minimum_version = getVersionFromString(minimum, ok);
if (!ok) {
return false;
}
const auto actual_version = getVersionFromString(actual, ok);
if (!ok) {
return false;
}
return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version),
std::begin(minimum_version), std::end(minimum_version));
}
bool Kleo::engineIsVersion(int major, int minor, int patch, GpgME::Engine engine)
{
static QMap<Engine, std::array<int, 3> > cachedVersions;
const int required_version[] = {major, minor, patch};
// Gpgconf means spawning processes which is expensive on windows.
std::array<int, 3> actual_version;
if (!cachedVersions.contains(engine)) {
const Error err = checkEngine(engine);
if (err.code() == GPG_ERR_INV_ENGINE) {
qCDebug(LIBKLEO_LOG) << "isVersion: invalid engine. '";
return false;
}
const char *actual = GpgME::engineInfo(engine).version();
bool ok;
actual_version = getVersionFromString(actual, ok);
qCDebug(LIBKLEO_LOG) << "Parsed" << actual << "as: "
<< actual_version[0] << '.'
<< actual_version[1] << '.'
<< actual_version[2] << '.';
if (!ok) {
return false;
}
cachedVersions.insert(engine, actual_version);
} else {
actual_version = cachedVersions.value(engine);
}
// return ! ( actual_version < required_version )
return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version),
std::begin(required_version), std::end(required_version));
}
const QString& Kleo::paperKeyInstallPath()
{
static const QString pkPath = QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath()).isEmpty() ?
QStandardPaths::findExecutable(QStringLiteral("paperkey")) :
QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath());
return pkPath;
}
bool Kleo::haveKeyserverConfigured()
{
if (engineIsVersion(2, 1, 19)) {
// since 2.1.19 there is a builtin keyserver
return true;
}
const QGpgME::CryptoConfig *const config = QGpgME::cryptoConfig();
if (!config) {
return false;
}
- const QGpgME::CryptoConfigEntry *const entry = config->entry(QStringLiteral("gpg"), QStringLiteral("Keyserver"), QStringLiteral("keyserver"));
+ const QGpgME::CryptoConfigEntry *const entry = Kleo::getCryptoConfigEntry(config, "gpg", "keyserver");
return entry && !entry->stringValue().isEmpty();
}
bool Kleo::gpgComplianceP(const char *mode)
{
const auto conf = QGpgME::cryptoConfig();
- const auto entry = conf->entry(QStringLiteral("gpg"),
- QStringLiteral("Configuration"),
- QStringLiteral("compliance"));
-
+ const auto entry = getCryptoConfigEntry(conf, "gpg", "compliance");
return entry && entry->stringValue() == QString::fromLatin1(mode);
}
enum GpgME::UserID::Validity Kleo::keyValidity(const GpgME::Key &key)
{
enum UserID::Validity validity = UserID::Validity::Unknown;
for (const auto &uid: key.userIDs()) {
if (validity == UserID::Validity::Unknown
|| validity > uid.validity()) {
validity = uid.validity();
}
}
return validity;
}
#ifdef Q_OS_WIN
static QString fromEncoding (unsigned int src_encoding, const char *data)
{
int n = MultiByteToWideChar(src_encoding, 0, data, -1, NULL, 0);
if (n < 0) {
return QString();
}
wchar_t *result = (wchar_t *) malloc ((n+1) * sizeof *result);
n = MultiByteToWideChar(src_encoding, 0, data, -1, result, n);
if (n < 0) {
free(result);
return QString();
}
const auto ret = QString::fromWCharArray(result, n);
free(result);
return ret;
}
#endif
QString Kleo::stringFromGpgOutput(const QByteArray &ba)
{
#ifdef Q_OS_WIN
/* Qt on Windows uses GetACP while GnuPG prefers
* GetConsoleOutputCP.
*
* As we are not a console application GetConsoleOutputCP
* usually returns 0.
* From experience the closest thing that let's us guess
* what GetConsoleOutputCP returns for a console application
* it appears to be the OEMCP.
*/
unsigned int cpno = GetConsoleOutputCP ();
if (!cpno) {
cpno = GetOEMCP();
}
if (!cpno) {
cpno = GetACP();
}
if (!cpno) {
qCDebug(LIBKLEO_LOG) << "Failed to find native codepage";
return QString();
}
return fromEncoding(cpno, ba.constData());
#else
return QString::fromLocal8Bit(ba);
#endif
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 8, 7:55 AM (12 h, 43 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
2e/a2/98e40e33e34fd02552140182c2c7

Event Timeline