Libkleo

keycache.cpp
1/* -*- mode: c++; c-basic-offset:4 -*-
2 models/keycache.cpp
3
4 This file is part of Kleopatra, the KDE keymanager
5 SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB
6 SPDX-FileCopyrightText: 2018 Intevation GmbH
7 SPDX-FileCopyrightText: 2020, 2021 g10 Code GmbH
8 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
9
10 SPDX-License-Identifier: GPL-2.0-or-later
11*/
12
13#include <config-libkleo.h>
14
15#include "keycache.h"
16#include "keycache_p.h"
17
18#include <libkleo/algorithm.h>
19#include <libkleo/compat.h>
20#include <libkleo/debug.h>
21#include <libkleo/dn.h>
22#include <libkleo/enum.h>
23#include <libkleo/filesystemwatcher.h>
24#include <libkleo/keygroup.h>
25#include <libkleo/keygroupconfig.h>
26#include <libkleo/keyhelpers.h>
27#include <libkleo/predicates.h>
28#include <libkleo/qtstlhelpers.h>
29#include <libkleo/stl_util.h>
30
31#include <libkleo_debug.h>
32
33#include <KSharedConfig>
34
35#include <QGpgME/CryptoConfig>
36#include <QGpgME/ListAllKeysJob>
37#include <QGpgME/Protocol>
38
39#include <QEventLoop>
40#include <QPointer>
41#include <QTimer>
42
43#include <gpgme++/context.h>
44#include <gpgme++/decryptionresult.h>
45#include <gpgme++/error.h>
46#include <gpgme++/key.h>
47#include <gpgme++/keylistresult.h>
48#include <gpgme++/verificationresult.h>
49
50#include <gpg-error.h>
51
52#include <algorithm>
53#include <chrono>
54#include <functional>
55#include <iterator>
56#include <utility>
57
58using namespace std::chrono_literals;
59using namespace Kleo;
60using namespace GpgME;
61using namespace KMime::Types;
62
63static const unsigned int hours2ms = 1000 * 60 * 60;
64
65//
66//
67// KeyCache
68//
69//
70
71namespace
72{
73
74make_comparator_str(ByEMail, .first.c_str());
75
76}
77
78class Kleo::KeyCacheAutoRefreshSuspension
79{
80 KeyCacheAutoRefreshSuspension()
81 {
82 qCDebug(LIBKLEO_LOG) << __func__;
83 auto cache = KeyCache::mutableInstance();
84 cache->enableFileSystemWatcher(false);
85 m_refreshInterval = cache->refreshInterval();
86 cache->setRefreshInterval(0);
87 cache->cancelKeyListing();
88 m_cache = cache;
89 }
90
91public:
92 ~KeyCacheAutoRefreshSuspension()
93 {
94 qCDebug(LIBKLEO_LOG) << __func__;
95 if (auto cache = m_cache.lock()) {
96 cache->enableFileSystemWatcher(true);
97 cache->setRefreshInterval(m_refreshInterval);
98 }
99 }
100
101 static std::shared_ptr<KeyCacheAutoRefreshSuspension> instance()
102 {
103 static std::weak_ptr<KeyCacheAutoRefreshSuspension> self;
104 if (auto s = self.lock()) {
105 return s;
106 } else {
107 s = std::shared_ptr<KeyCacheAutoRefreshSuspension>{new KeyCacheAutoRefreshSuspension{}};
108 self = s;
109 return s;
110 }
111 }
112
113private:
114 std::weak_ptr<KeyCache> m_cache;
115 int m_refreshInterval = 0;
116};
117
118class KeyCache::Private
119{
120 friend class ::Kleo::KeyCache;
121 KeyCache *const q;
122
123public:
124 explicit Private(KeyCache *qq)
125 : q(qq)
126 , m_refreshInterval(1)
127 , m_initalized(false)
128 , m_pgpOnly(true)
129 , m_remarks_enabled(false)
130 {
131 connect(&m_autoKeyListingTimer, &QTimer::timeout, q, [this]() {
132 q->startKeyListing();
133 });
134 updateAutoKeyListingTimer();
135 }
136
137 ~Private()
138 {
139 if (m_refreshJob) {
140 m_refreshJob->cancel();
141 }
142 }
143
144 template<template<template<typename U> class Op> class Comp>
145 std::vector<Key>::const_iterator find(const std::vector<Key> &keys, const char *key) const
146 {
147 ensureCachePopulated();
148 const auto it = std::lower_bound(keys.begin(), keys.end(), key, Comp<std::less>());
149 if (it == keys.end() || Comp<std::equal_to>()(*it, key)) {
150 return it;
151 } else {
152 return keys.end();
153 }
154 }
155
156 template<template<template<typename U> class Op> class Comp>
157 std::vector<Subkey>::const_iterator find(const std::vector<Subkey> &keys, const char *key) const
158 {
159 ensureCachePopulated();
160 const auto it = std::lower_bound(keys.begin(), keys.end(), key, Comp<std::less>());
161 if (it == keys.end() || Comp<std::equal_to>()(*it, key)) {
162 return it;
163 } else {
164 return keys.end();
165 }
166 }
167
168 std::vector<Key>::const_iterator find_fpr(const char *fpr) const
169 {
170 return find<_detail::ByFingerprint>(by.fpr, fpr);
171 }
172
173 std::pair<std::vector<std::pair<std::string, Key>>::const_iterator, std::vector<std::pair<std::string, Key>>::const_iterator>
174 find_email(const char *email) const
175 {
176 ensureCachePopulated();
177 return std::equal_range(by.email.begin(), by.email.end(), email, ByEMail<std::less>());
178 }
179
180 std::vector<Key> find_mailbox(const QString &email, bool sign) const;
181
182 std::vector<Subkey>::const_iterator find_keygrip(const char *keygrip) const
183 {
184 return find<_detail::ByKeyGrip>(by.keygrip, keygrip);
185 }
186
187 std::vector<Subkey>::const_iterator find_subkeyid(const char *subkeyid) const
188 {
189 return find<_detail::ByKeyID>(by.subkeyid, subkeyid);
190 }
191
192 std::vector<Key>::const_iterator find_keyid(const char *keyid) const
193 {
194 return find<_detail::ByKeyID>(by.keyid, keyid);
195 }
196
197 std::vector<Key>::const_iterator find_shortkeyid(const char *shortkeyid) const
198 {
199 return find<_detail::ByShortKeyID>(by.shortkeyid, shortkeyid);
200 }
201
202 std::pair<std::vector<Key>::const_iterator, std::vector<Key>::const_iterator> find_subjects(const char *chain_id) const
203 {
204 ensureCachePopulated();
205 return std::equal_range(by.chainid.begin(), by.chainid.end(), chain_id, _detail::ByChainID<std::less>());
206 }
207
208 void refreshJobDone(const KeyListResult &result);
209
210 void setRefreshInterval(int interval)
211 {
212 m_refreshInterval = interval;
213 updateAutoKeyListingTimer();
214 }
215
216 int refreshInterval() const
217 {
218 return m_refreshInterval;
219 }
220
221 void updateAutoKeyListingTimer()
222 {
223 setAutoKeyListingInterval(hours2ms * m_refreshInterval);
224 }
225 void setAutoKeyListingInterval(int ms)
226 {
227 m_autoKeyListingTimer.stop();
228 m_autoKeyListingTimer.setInterval(ms);
229 if (ms != 0) {
230 m_autoKeyListingTimer.start();
231 }
232 }
233
234 void ensureCachePopulated() const;
235
236 void readGroupsFromGpgConf()
237 {
238 // According to Werner Koch groups are more of a hack to solve
239 // a valid usecase (e.g. several keys defined for an internal mailing list)
240 // that won't make it in the proper keylist interface. And using gpgconf
241 // was the suggested way to support groups.
242 auto conf = QGpgME::cryptoConfig();
243 if (!conf) {
244 return;
245 }
246
247 auto entry = getCryptoConfigEntry(conf, "gpg", "group");
248 if (!entry) {
249 return;
250 }
251
252 // collect the key fingerprints for all groups read from the configuration
253 QMap<QString, QStringList> fingerprints;
254 const auto stringValueList = entry->stringValueList();
255 for (const QString &value : stringValueList) {
256 const QStringList split = value.split(QLatin1Char('='));
257 if (split.size() != 2) {
258 qCDebug(LIBKLEO_LOG) << "Ignoring invalid group config:" << value;
259 continue;
260 }
261 const QString groupName = split[0];
262 const QString fingerprint = split[1];
263 fingerprints[groupName].push_back(fingerprint);
264 }
265
266 // add all groups read from the configuration to the list of groups
267 for (auto it = fingerprints.cbegin(); it != fingerprints.cend(); ++it) {
268 const QString groupName = it.key();
269 const std::vector<Key> groupKeys = q->findByFingerprint(toStdStrings(it.value()));
270 KeyGroup g(groupName, groupName, groupKeys, KeyGroup::GnuPGConfig);
271 m_groups.push_back(g);
272 }
273 }
274
275 void readGroupsFromGroupsConfig()
276 {
277 Q_ASSERT(m_groupConfig);
278 if (!m_groupConfig) {
279 qCWarning(LIBKLEO_LOG) << __func__ << "group config not set";
280 return;
281 }
282
283 m_groups = m_groupConfig->readGroups();
284 }
285
286 KeyGroup writeGroupToGroupsConfig(const KeyGroup &group)
287 {
288 Q_ASSERT(m_groupConfig);
289 if (!m_groupConfig) {
290 qCWarning(LIBKLEO_LOG) << __func__ << "group config not set";
291 return {};
292 }
293
294 Q_ASSERT(!group.isNull());
295 Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
296 if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
297 qCDebug(LIBKLEO_LOG) << __func__ << "group cannot be written to application configuration:" << group;
298 return group;
299 }
300
301 return m_groupConfig->writeGroup(group);
302 }
303
304 bool removeGroupFromGroupsConfig(const KeyGroup &group)
305 {
306 Q_ASSERT(m_groupConfig);
307 if (!m_groupConfig) {
308 qCWarning(LIBKLEO_LOG) << __func__ << "group config not set";
309 return false;
310 }
311
312 Q_ASSERT(!group.isNull());
313 Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
314 if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
315 qCDebug(LIBKLEO_LOG) << __func__ << "group cannot be removed from application configuration:" << group;
316 return false;
317 }
318
319 return m_groupConfig->removeGroup(group);
320 }
321
322 void updateGroupCache()
323 {
324 // Update Group Keys
325 // this is a quick thing as it only involves reading the config
326 // so no need for a job.
327
328 m_groups.clear();
329 if (m_groupsEnabled) {
330 readGroupsFromGpgConf();
331 readGroupsFromGroupsConfig();
332 }
333 }
334
335 bool insert(const KeyGroup &group)
336 {
337 Q_ASSERT(!group.isNull());
338 Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
339 if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
340 qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Invalid group:" << group;
341 return false;
342 }
343 const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) {
344 return g.source() == group.source() && g.id() == group.id();
345 });
346 if (it != m_groups.cend()) {
347 qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Group already present in list of groups:" << group;
348 return false;
349 }
350
351 const KeyGroup savedGroup = writeGroupToGroupsConfig(group);
352 if (savedGroup.isNull()) {
353 qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Writing group" << group.id() << "to config file failed";
354 return false;
355 }
356
357 m_groups.push_back(savedGroup);
358
359 Q_EMIT q->groupAdded(savedGroup);
360
361 return true;
362 }
363
364 bool update(const KeyGroup &group)
365 {
366 Q_ASSERT(!group.isNull());
367 Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
368 if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
369 qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Invalid group:" << group;
370 return false;
371 }
372 const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) {
373 return g.source() == group.source() && g.id() == group.id();
374 });
375 if (it == m_groups.cend()) {
376 qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Group not found in list of groups:" << group;
377 return false;
378 }
379 const auto groupIndex = std::distance(m_groups.cbegin(), it);
380
381 const KeyGroup savedGroup = writeGroupToGroupsConfig(group);
382 if (savedGroup.isNull()) {
383 qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Writing group" << group.id() << "to config file failed";
384 return false;
385 }
386
387 m_groups[groupIndex] = savedGroup;
388
389 Q_EMIT q->groupUpdated(savedGroup);
390
391 return true;
392 }
393
394 bool remove(const KeyGroup &group)
395 {
396 Q_ASSERT(!group.isNull());
397 Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
398 if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
399 qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Invalid group:" << group;
400 return false;
401 }
402 const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) {
403 return g.source() == group.source() && g.id() == group.id();
404 });
405 if (it == m_groups.cend()) {
406 qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Group not found in list of groups:" << group;
407 return false;
408 }
409
410 const bool success = removeGroupFromGroupsConfig(group);
411 if (!success) {
412 qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Removing group" << group.id() << "from config file failed";
413 return false;
414 }
415
416 m_groups.erase(it);
417
418 Q_EMIT q->groupRemoved(group);
419
420 return true;
421 }
422
423private:
424 QPointer<RefreshKeysJob> m_refreshJob;
425 std::vector<std::shared_ptr<FileSystemWatcher>> m_fsWatchers;
426 QTimer m_autoKeyListingTimer;
427 int m_refreshInterval;
428
429 struct By {
430 std::vector<Key> fpr, keyid, shortkeyid, chainid;
431 std::vector<std::pair<std::string, Key>> email;
432 std::vector<Subkey> subkeyid, keygrip;
433 } by;
434 bool m_initalized;
435 bool m_pgpOnly;
436 bool m_remarks_enabled;
437 bool m_groupsEnabled = false;
438 std::shared_ptr<KeyGroupConfig> m_groupConfig;
439 std::vector<KeyGroup> m_groups;
440};
441
442std::shared_ptr<const KeyCache> KeyCache::instance()
443{
444 return mutableInstance();
445}
446
447std::shared_ptr<KeyCache> KeyCache::mutableInstance()
448{
449 static std::weak_ptr<KeyCache> self;
450 try {
451 return std::shared_ptr<KeyCache>(self);
452 } catch (const std::bad_weak_ptr &) {
453 const std::shared_ptr<KeyCache> s(new KeyCache);
454 self = s;
455 return s;
456 }
457}
458
459KeyCache::KeyCache()
460 : QObject()
461 , d(new Private(this))
462{
463}
464
465KeyCache::~KeyCache()
466{
467}
468
469void KeyCache::setGroupsEnabled(bool enabled)
470{
471 d->m_groupsEnabled = enabled;
472 if (d->m_initalized) {
473 d->updateGroupCache();
474 }
475}
476
477void KeyCache::setGroupConfig(const std::shared_ptr<KeyGroupConfig> &groupConfig)
478{
479 d->m_groupConfig = groupConfig;
480}
481
482void KeyCache::enableFileSystemWatcher(bool enable)
483{
484 for (const auto &i : std::as_const(d->m_fsWatchers)) {
485 i->setEnabled(enable);
486 }
487}
488
489void KeyCache::setRefreshInterval(int hours)
490{
491 d->setRefreshInterval(hours);
492}
493
494int KeyCache::refreshInterval() const
495{
496 return d->refreshInterval();
497}
498
499std::shared_ptr<KeyCacheAutoRefreshSuspension> KeyCache::suspendAutoRefresh()
500{
501 return KeyCacheAutoRefreshSuspension::instance();
502}
503
504void KeyCache::reload(GpgME::Protocol /*proto*/, ReloadOption option)
505{
506 qCDebug(LIBKLEO_LOG) << this << __func__ << "option:" << option;
507 const bool forceReload = option & ForceReload;
508 if (d->m_refreshJob && !forceReload) {
509 qCDebug(LIBKLEO_LOG) << this << __func__ << "- refresh already running";
510 return;
511 }
512 if (d->m_refreshJob) {
513 disconnect(d->m_refreshJob.data(), nullptr, this, nullptr);
514 d->m_refreshJob->cancel();
515 d->m_refreshJob.clear();
516 }
517
518 d->updateAutoKeyListingTimer();
519
520 enableFileSystemWatcher(false);
521 d->m_refreshJob = new RefreshKeysJob(this);
522 connect(d->m_refreshJob.data(), &RefreshKeysJob::done, this, [this](const GpgME::KeyListResult &r) {
523 qCDebug(LIBKLEO_LOG) << d->m_refreshJob.data() << "RefreshKeysJob::done";
524 d->refreshJobDone(r);
525 });
526 connect(d->m_refreshJob.data(), &RefreshKeysJob::canceled, this, [this]() {
527 qCDebug(LIBKLEO_LOG) << d->m_refreshJob.data() << "RefreshKeysJob::canceled";
528 d->m_refreshJob.clear();
529 });
530 d->m_refreshJob->start();
531}
532
533void KeyCache::cancelKeyListing()
534{
535 if (!d->m_refreshJob) {
536 return;
537 }
538 d->m_refreshJob->cancel();
539}
540
541void KeyCache::addFileSystemWatcher(const std::shared_ptr<FileSystemWatcher> &watcher)
542{
543 if (!watcher) {
544 return;
545 }
546 d->m_fsWatchers.push_back(watcher);
547 connect(watcher.get(), &FileSystemWatcher::directoryChanged, this, [this]() {
548 startKeyListing();
549 });
550 connect(watcher.get(), &FileSystemWatcher::fileChanged, this, [this]() {
551 startKeyListing();
552 });
553
554 watcher->setEnabled(d->m_refreshJob.isNull());
555}
556
557void KeyCache::enableRemarks(bool value)
558{
559 if (!d->m_remarks_enabled && value) {
560 d->m_remarks_enabled = value;
561 if (d->m_initalized && !d->m_refreshJob) {
562 qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled";
563 reload();
564 }
565 } else {
566 d->m_remarks_enabled = value;
567 }
568}
569
570bool KeyCache::remarksEnabled() const
571{
572 return d->m_remarks_enabled;
573}
574
575void KeyCache::Private::refreshJobDone(const KeyListResult &result)
576{
577 m_refreshJob.clear();
578 q->enableFileSystemWatcher(true);
579 if (!m_initalized && q->remarksEnabled()) {
580 // trigger another key listing to read signatures and signature notations
582 q,
583 [this]() {
584 qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled";
585 q->reload();
586 },
588 }
589 m_initalized = true;
590 updateGroupCache();
591 Q_EMIT q->keyListingDone(result);
592}
593
594const Key &KeyCache::findByFingerprint(const char *fpr) const
595{
596 const std::vector<Key>::const_iterator it = d->find_fpr(fpr);
597 if (it == d->by.fpr.end()) {
598 static const Key null;
599 return null;
600 } else {
601 return *it;
602 }
603}
604
605const Key &KeyCache::findByFingerprint(const std::string &fpr) const
606{
607 return findByFingerprint(fpr.c_str());
608}
609
610std::vector<GpgME::Key> KeyCache::findByFingerprint(const std::vector<std::string> &fprs) const
611{
612 std::vector<Key> keys;
613 keys.reserve(fprs.size());
614 for (const auto &fpr : fprs) {
615 const Key key = findByFingerprint(fpr.c_str());
616 if (key.isNull()) {
617 qCDebug(LIBKLEO_LOG) << __func__ << "Ignoring unknown key with fingerprint:" << fpr.c_str();
618 continue;
619 }
620 keys.push_back(key);
621 }
622 return keys;
623}
624
625std::vector<Key> KeyCache::findByEMailAddress(const char *email) const
626{
627 const auto pair = d->find_email(email);
628 std::vector<Key> result;
629 result.reserve(std::distance(pair.first, pair.second));
630 std::transform(pair.first, pair.second, std::back_inserter(result), [](const std::pair<std::string, Key> &pair) {
631 return pair.second;
632 });
633 return result;
634}
635
636std::vector<Key> KeyCache::findByEMailAddress(const std::string &email) const
637{
638 return findByEMailAddress(email.c_str());
639}
640
641const Key &KeyCache::findByShortKeyID(const char *id) const
642{
643 const std::vector<Key>::const_iterator it = d->find_shortkeyid(id);
644 if (it != d->by.shortkeyid.end()) {
645 return *it;
646 }
647 static const Key null;
648 return null;
649}
650
651const Key &KeyCache::findByShortKeyID(const std::string &id) const
652{
653 return findByShortKeyID(id.c_str());
654}
655
656const Key &KeyCache::findByKeyIDOrFingerprint(const char *id) const
657{
658 {
659 // try by.fpr first:
660 const std::vector<Key>::const_iterator it = d->find_fpr(id);
661 if (it != d->by.fpr.end()) {
662 return *it;
663 }
664 }
665 {
666 // try by.keyid next:
667 const std::vector<Key>::const_iterator it = d->find_keyid(id);
668 if (it != d->by.keyid.end()) {
669 return *it;
670 }
671 }
672 static const Key null;
673 return null;
674}
675
676const Key &KeyCache::findByKeyIDOrFingerprint(const std::string &id) const
677{
678 return findByKeyIDOrFingerprint(id.c_str());
679}
680
681std::vector<Key> KeyCache::findByKeyIDOrFingerprint(const std::vector<std::string> &ids) const
682{
683 std::vector<std::string> keyids;
684 std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(keyids), [](const std::string &str) {
685 return !str.c_str() || !*str.c_str();
686 });
687
688 // this is just case-insensitive string search:
689 std::sort(keyids.begin(), keyids.end(), _detail::ByFingerprint<std::less>());
690
691 std::vector<Key> result;
692 result.reserve(keyids.size()); // dups shouldn't happen
693 d->ensureCachePopulated();
694
695 kdtools::set_intersection(d->by.fpr.begin(),
696 d->by.fpr.end(),
697 keyids.begin(),
698 keyids.end(),
699 std::back_inserter(result),
700 _detail::ByFingerprint<std::less>());
701 if (result.size() < keyids.size()) {
702 // note that By{Fingerprint,KeyID,ShortKeyID} define the same
703 // order for _strings_
704 kdtools::set_intersection(d->by.keyid.begin(),
705 d->by.keyid.end(),
706 keyids.begin(),
707 keyids.end(),
708 std::back_inserter(result),
709 _detail::ByKeyID<std::less>());
710 }
711 // duplicates shouldn't happen, but make sure nonetheless:
712 std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
713 result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
714
715 // we skip looking into short key ids here, as it's highly
716 // unlikely they're used for this purpose. We might need to revise
717 // this decision, but only after testing.
718 return result;
719}
720
721const Subkey &KeyCache::findSubkeyByKeyGrip(const char *grip, Protocol protocol) const
722{
723 static const Subkey null;
724 d->ensureCachePopulated();
725 const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), grip, _detail::ByKeyGrip<std::less>());
726 if (range.first == range.second) {
727 return null;
728 } else if (protocol == UnknownProtocol) {
729 return *range.first;
730 } else {
731 for (auto it = range.first; it != range.second; ++it) {
732 if (it->parent().protocol() == protocol) {
733 return *it;
734 }
735 }
736 }
737 return null;
738}
739
740const Subkey &KeyCache::findSubkeyByKeyGrip(const std::string &grip, Protocol protocol) const
741{
742 return findSubkeyByKeyGrip(grip.c_str(), protocol);
743}
744
745std::vector<Subkey> KeyCache::findSubkeysByKeyID(const std::vector<std::string> &ids) const
746{
747 std::vector<std::string> sorted;
748 sorted.reserve(ids.size());
749 std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(sorted), [](const std::string &str) {
750 return !str.c_str() || !*str.c_str();
751 });
752
753 std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
754
755 std::vector<Subkey> result;
756 d->ensureCachePopulated();
757 kdtools::set_intersection(d->by.subkeyid.begin(),
758 d->by.subkeyid.end(),
759 sorted.begin(),
760 sorted.end(),
761 std::back_inserter(result),
762 _detail::ByKeyID<std::less>());
763 return result;
764}
765
766std::vector<Key> KeyCache::findRecipients(const DecryptionResult &res) const
767{
768 std::vector<std::string> keyids;
769 const auto recipients = res.recipients();
770 for (const DecryptionResult::Recipient &r : recipients) {
771 if (const char *kid = r.keyID()) {
772 keyids.push_back(kid);
773 }
774 }
775 const std::vector<Subkey> subkeys = findSubkeysByKeyID(keyids);
776 std::vector<Key> result;
777 result.reserve(subkeys.size());
778 std::transform(subkeys.begin(), subkeys.end(), std::back_inserter(result), std::mem_fn(&Subkey::parent));
779
780 std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
781 result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
782 return result;
783}
784
785std::vector<Key> KeyCache::findSigners(const VerificationResult &res) const
786{
787 std::vector<std::string> fprs;
788 const auto signatures = res.signatures();
789 for (const Signature &s : signatures) {
790 if (const char *fpr = s.fingerprint()) {
791 fprs.push_back(fpr);
792 }
793 }
794 return findByKeyIDOrFingerprint(fprs);
795}
796
797std::vector<Key> KeyCache::findSigningKeysByMailbox(const QString &mb) const
798{
799 return d->find_mailbox(mb, true);
800}
801
802std::vector<Key> KeyCache::findEncryptionKeysByMailbox(const QString &mb) const
803{
804 return d->find_mailbox(mb, false);
805}
806
807namespace
808{
809#define DO(op, meth, meth2) \
810 if (op key.meth()) { \
811 } else { \
812 qDebug("rejecting for signing: %s: %s", #meth2, key.primaryFingerprint()); \
813 return false; \
814 }
815#define ACCEPT(meth) DO(!!, meth, !meth)
816#define REJECT(meth) DO(!, meth, meth)
817struct ready_for_signing {
818 bool operator()(const Key &key) const
819 {
820 ACCEPT(hasSecret);
821#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
822 ACCEPT(hasSign);
823#else
824 ACCEPT(canSign);
825#endif
826 REJECT(isRevoked);
827 REJECT(isExpired);
828 REJECT(isDisabled);
829 REJECT(isInvalid);
830 return true;
831#undef DO
832 }
833};
834
835#define DO(op, meth, meth2) \
836 if (op key.meth()) { \
837 } else { \
838 qDebug("rejecting for encrypting: %s: %s", #meth2, key.primaryFingerprint()); \
839 return false; \
840 }
841struct ready_for_encryption {
842 bool operator()(const Key &key) const
843 {
844#if 1
845#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
846 ACCEPT(hasEncrypt);
847#else
848 ACCEPT(canEncrypt);
849#endif
850 REJECT(isRevoked);
851 REJECT(isExpired);
852 REJECT(isDisabled);
853 REJECT(isInvalid);
854 return true;
855#else
856 return key.hasEncrypt() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid();
857#endif
858 }
859#undef DO
860#undef ACCEPT
861#undef REJECT
862};
863}
864
865std::vector<Key> KeyCache::Private::find_mailbox(const QString &email, bool sign) const
866{
867 if (email.isEmpty()) {
868 return std::vector<Key>();
869 }
870
871 const auto pair = find_email(email.toUtf8().constData());
872 std::vector<Key> result;
873 result.reserve(std::distance(pair.first, pair.second));
874 if (sign) {
875 kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_signing());
876 } else {
877 kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_encryption());
878 }
879
880 return result;
881}
882
883std::vector<Key> KeyCache::findSubjects(const GpgME::Key &key, Options options) const
884{
885 if (key.isNull()) {
886 return {};
887 }
888
889 return findSubjects(std::vector<Key>(1, key), options);
890}
891
892std::vector<Key> KeyCache::findSubjects(const std::vector<Key> &keys, Options options) const
893{
894 std::vector<Key> result;
895
896 if (keys.empty()) {
897 return result;
898 }
899
900 // get the immediate subjects
901 for (const auto &key : keys) {
902 const auto firstAndLastSubject = d->find_subjects(key.primaryFingerprint());
903 result.insert(result.end(), firstAndLastSubject.first, firstAndLastSubject.second);
904 }
905 // remove duplicates
906 _detail::sort_by_fpr(result);
907 _detail::remove_duplicates_by_fpr(result);
908
909 if (options & RecursiveSearch) {
910 for (std::vector<Key> furtherSubjects = findSubjects(result, NoOption); //
911 !furtherSubjects.empty();
912 furtherSubjects = findSubjects(furtherSubjects, NoOption)) {
913 std::vector<Key> combined;
914 combined.reserve(result.size() + furtherSubjects.size());
915 std::merge(result.begin(),
916 result.end(),
917 furtherSubjects.begin(),
918 furtherSubjects.end(),
919 std::back_inserter(combined),
920 _detail::ByFingerprint<std::less>());
921 _detail::remove_duplicates_by_fpr(combined);
922 if (result.size() == combined.size()) {
923 // no new subjects were found; this happens if a chain has a cycle
924 break;
925 }
926 result.swap(combined);
927 }
928 }
929
930 return result;
931}
932
933std::vector<Key> KeyCache::findIssuers(const Key &key, Options options) const
934{
935 std::vector<Key> result;
936
937 if (key.isNull()) {
938 return result;
939 }
940
941 if (options & IncludeSubject) {
942 result.push_back(key);
943 }
944
945 if (key.isRoot()) {
946 return result;
947 }
948
949 Key issuer = findByFingerprint(key.chainID());
950
951 if (issuer.isNull()) {
952 return result;
953 }
954
955 result.push_back(issuer);
956
957 if (!(options & RecursiveSearch)) {
958 return result;
959 }
960
961 while (!issuer.isRoot()) {
962 issuer = findByFingerprint(result.back().chainID());
963 if (issuer.isNull()) {
964 break;
965 }
966 const bool chainAlreadyContainsIssuer = Kleo::contains_if(result, [issuer](const auto &key) {
967 return _detail::ByFingerprint<std::equal_to>()(issuer, key);
968 });
969 // we also add the issuer if the chain already contains it, so that
970 // the user can spot the cycle
971 result.push_back(issuer);
972 if (chainAlreadyContainsIssuer) {
973 // break on cycle in chain
974 break;
975 }
976 }
977
978 return result;
979}
980
981static std::string email(const UserID &uid)
982{
983 // Prefer the gnupg normalized one
984 const std::string addr = uid.addrSpec();
985 if (!addr.empty()) {
986 return addr;
987 }
988 const std::string email = uid.email();
989 if (email.empty()) {
990 return DN(uid.id())[QStringLiteral("EMAIL")].trimmed().toUtf8().constData();
991 }
992 if (email[0] == '<' && email[email.size() - 1] == '>') {
993 return email.substr(1, email.size() - 2);
994 } else {
995 return email;
996 }
997}
998
999static std::vector<std::string> emails(const Key &key)
1000{
1001 std::vector<std::string> emails;
1002 const auto userIDs = key.userIDs();
1003 for (const UserID &uid : userIDs) {
1004 const std::string e = email(uid);
1005 if (!e.empty()) {
1006 emails.push_back(e);
1007 }
1008 }
1009 std::sort(emails.begin(), emails.end(), ByEMail<std::less>());
1010 emails.erase(std::unique(emails.begin(), emails.end(), ByEMail<std::equal_to>()), emails.end());
1011 return emails;
1012}
1013
1014void KeyCache::remove(const Key &key)
1015{
1016 if (key.isNull()) {
1017 return;
1018 }
1019
1020 const char *fpr = key.primaryFingerprint();
1021 if (!fpr) {
1022 return;
1023 }
1024
1025 {
1026 const auto range = std::equal_range(d->by.fpr.begin(), d->by.fpr.end(), fpr, _detail::ByFingerprint<std::less>());
1027 d->by.fpr.erase(range.first, range.second);
1028 }
1029
1030 if (const char *keyid = key.keyID()) {
1031 const auto range = std::equal_range(d->by.keyid.begin(), d->by.keyid.end(), keyid, _detail::ByKeyID<std::less>());
1032 const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) {
1033 return _detail::ByFingerprint<std::equal_to>()(fpr, key);
1034 });
1035 d->by.keyid.erase(it, range.second);
1036 }
1037
1038 if (const char *shortkeyid = key.shortKeyID()) {
1039 const auto range = std::equal_range(d->by.shortkeyid.begin(), d->by.shortkeyid.end(), shortkeyid, _detail::ByShortKeyID<std::less>());
1040 const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) {
1041 return _detail::ByFingerprint<std::equal_to>()(fpr, key);
1042 });
1043 d->by.shortkeyid.erase(it, range.second);
1044 }
1045
1046 if (const char *chainid = key.chainID()) {
1047 const auto range = std::equal_range(d->by.chainid.begin(), d->by.chainid.end(), chainid, _detail::ByChainID<std::less>());
1048 const auto range2 = std::equal_range(range.first, range.second, fpr, _detail::ByFingerprint<std::less>());
1049 d->by.chainid.erase(range2.first, range2.second);
1050 }
1051
1052 const auto emailsKey{emails(key)};
1053 for (const std::string &email : emailsKey) {
1054 const auto range = std::equal_range(d->by.email.begin(), d->by.email.end(), email, ByEMail<std::less>());
1055 const auto it = std::remove_if(range.first, range.second, [fpr](const std::pair<std::string, Key> &pair) {
1056 return qstricmp(fpr, pair.second.primaryFingerprint()) == 0;
1057 });
1058 d->by.email.erase(it, range.second);
1059 }
1060
1061 const auto keySubKeys{key.subkeys()};
1062 for (const Subkey &subkey : keySubKeys) {
1063 if (const char *keyid = subkey.keyID()) {
1064 const auto range = std::equal_range(d->by.subkeyid.begin(), d->by.subkeyid.end(), keyid, _detail::ByKeyID<std::less>());
1065 const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) {
1066 return !qstricmp(fpr, subkey.parent().primaryFingerprint());
1067 });
1068 d->by.subkeyid.erase(it, range.second);
1069 }
1070 if (const char *keygrip = subkey.keyGrip()) {
1071 const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), keygrip, _detail::ByKeyGrip<std::less>());
1072 const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) {
1073 return !qstricmp(fpr, subkey.parent().primaryFingerprint());
1074 });
1075 d->by.keygrip.erase(it, range.second);
1076 }
1077 }
1078}
1079
1080void KeyCache::remove(const std::vector<Key> &keys)
1081{
1082 for (const Key &key : keys) {
1083 remove(key);
1084 }
1085}
1086
1087const std::vector<GpgME::Key> &KeyCache::keys() const
1088{
1089 d->ensureCachePopulated();
1090 return d->by.fpr;
1091}
1092
1093std::vector<Key> KeyCache::secretKeys() const
1094{
1095 std::vector<Key> keys = this->keys();
1096 keys.erase(std::remove_if(keys.begin(),
1097 keys.end(),
1098 [](const Key &key) {
1099 return !key.hasSecret();
1100 }),
1101 keys.end());
1102 return keys;
1103}
1104
1105KeyGroup KeyCache::group(const QString &id) const
1106{
1107 KeyGroup result{};
1108 const auto it = std::find_if(std::cbegin(d->m_groups), std::cend(d->m_groups), [id](const auto &g) {
1109 return g.id() == id;
1110 });
1111 if (it != std::cend(d->m_groups)) {
1112 result = *it;
1113 }
1114 return result;
1115}
1116
1117std::vector<KeyGroup> KeyCache::groups() const
1118{
1119 d->ensureCachePopulated();
1120 return d->m_groups;
1121}
1122
1123std::vector<KeyGroup> KeyCache::configurableGroups() const
1124{
1125 std::vector<KeyGroup> groups;
1126 groups.reserve(d->m_groups.size());
1127 std::copy_if(d->m_groups.cbegin(), d->m_groups.cend(), std::back_inserter(groups), [](const KeyGroup &group) {
1128 return group.source() == KeyGroup::ApplicationConfig;
1129 });
1130 return groups;
1131}
1132
1133namespace
1134{
1135bool compareById(const KeyGroup &lhs, const KeyGroup &rhs)
1136{
1137 return lhs.id() < rhs.id();
1138}
1139
1140std::vector<KeyGroup> sortedById(std::vector<KeyGroup> groups)
1141{
1142 std::sort(groups.begin(), groups.end(), &compareById);
1143 return groups;
1144}
1145}
1146
1147void KeyCache::saveConfigurableGroups(const std::vector<KeyGroup> &groups)
1148{
1149 const std::vector<KeyGroup> oldGroups = sortedById(configurableGroups());
1150 const std::vector<KeyGroup> newGroups = sortedById(groups);
1151
1152 {
1153 std::vector<KeyGroup> removedGroups;
1154 std::set_difference(oldGroups.begin(), oldGroups.end(), newGroups.begin(), newGroups.end(), std::back_inserter(removedGroups), &compareById);
1155 for (const auto &group : std::as_const(removedGroups)) {
1156 qCDebug(LIBKLEO_LOG) << "Removing group" << group;
1157 d->remove(group);
1158 }
1159 }
1160 {
1161 std::vector<KeyGroup> updatedGroups;
1162 std::set_intersection(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(updatedGroups), &compareById);
1163 for (const auto &group : std::as_const(updatedGroups)) {
1164 qCDebug(LIBKLEO_LOG) << "Updating group" << group;
1165 d->update(group);
1166 }
1167 }
1168 {
1169 std::vector<KeyGroup> addedGroups;
1170 std::set_difference(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(addedGroups), &compareById);
1171 for (const auto &group : std::as_const(addedGroups)) {
1172 qCDebug(LIBKLEO_LOG) << "Adding group" << group;
1173 d->insert(group);
1174 }
1175 }
1176
1177 Q_EMIT keysMayHaveChanged();
1178}
1179
1180bool KeyCache::insert(const KeyGroup &group)
1181{
1182 if (!d->insert(group)) {
1183 return false;
1184 }
1185
1186 Q_EMIT keysMayHaveChanged();
1187
1188 return true;
1189}
1190
1191bool KeyCache::update(const KeyGroup &group)
1192{
1193 if (!d->update(group)) {
1194 return false;
1195 }
1196
1197 Q_EMIT keysMayHaveChanged();
1198
1199 return true;
1200}
1201
1202bool KeyCache::remove(const KeyGroup &group)
1203{
1204 if (!d->remove(group)) {
1205 return false;
1206 }
1207
1208 Q_EMIT keysMayHaveChanged();
1209
1210 return true;
1211}
1212
1213void KeyCache::refresh(const std::vector<Key> &keys)
1214{
1215 // make this better...
1216 clear();
1217 insert(keys);
1218}
1219
1220void KeyCache::insert(const Key &key)
1221{
1222 insert(std::vector<Key>(1, key));
1223}
1224
1225namespace
1226{
1227
1228template<template<template<typename T> class Op> class T1, template<template<typename T> class Op> class T2>
1229struct lexicographically {
1230 using result_type = bool;
1231
1232 template<typename U, typename V>
1233 bool operator()(const U &lhs, const V &rhs) const
1234 {
1235 return T1<std::less>()(lhs, rhs) //
1236 || (T1<std::equal_to>()(lhs, rhs) && T2<std::less>()(lhs, rhs));
1237 }
1238};
1239
1240}
1241
1242void KeyCache::insert(const std::vector<Key> &keys)
1243{
1244 // 1. filter out keys with empty fingerprints:
1245 std::vector<Key> sorted;
1246 sorted.reserve(keys.size());
1247 std::copy_if(keys.begin(), keys.end(), std::back_inserter(sorted), [](const Key &key) {
1248 auto fp = key.primaryFingerprint();
1249 return fp && *fp;
1250 });
1251
1252 // this is sub-optimal, but makes implementation from here on much easier
1253 remove(sorted);
1254
1255 // 2. sort by fingerprint:
1256 std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint<std::less>());
1257
1258 // 2a. insert into fpr index:
1259 std::vector<Key> by_fpr;
1260 by_fpr.reserve(sorted.size() + d->by.fpr.size());
1261 std::merge(sorted.begin(), sorted.end(), d->by.fpr.begin(), d->by.fpr.end(), std::back_inserter(by_fpr), _detail::ByFingerprint<std::less>());
1262
1263 // 3. build email index:
1264 std::vector<std::pair<std::string, Key>> pairs;
1265 pairs.reserve(sorted.size());
1266 for (const Key &key : std::as_const(sorted)) {
1267 const std::vector<std::string> emails = ::emails(key);
1268 for (const std::string &e : emails) {
1269 pairs.push_back(std::make_pair(e, key));
1270 }
1271 }
1272 std::sort(pairs.begin(), pairs.end(), ByEMail<std::less>());
1273
1274 // 3a. insert into email index:
1275 std::vector<std::pair<std::string, Key>> by_email;
1276 by_email.reserve(pairs.size() + d->by.email.size());
1277 std::merge(pairs.begin(), pairs.end(), d->by.email.begin(), d->by.email.end(), std::back_inserter(by_email), ByEMail<std::less>());
1278
1279 // 3.5: stable-sort by chain-id (effectively lexicographically<ByChainID,ByFingerprint>)
1280 std::stable_sort(sorted.begin(), sorted.end(), _detail::ByChainID<std::less>());
1281
1282 // 3.5a: insert into chain-id index:
1283 std::vector<Key> nonroot;
1284 nonroot.reserve(sorted.size());
1285 std::vector<Key> by_chainid;
1286 by_chainid.reserve(sorted.size() + d->by.chainid.size());
1287 std::copy_if(sorted.cbegin(), sorted.cend(), std::back_inserter(nonroot), [](const Key &key) {
1288 return !key.isRoot();
1289 });
1290 std::merge(nonroot.cbegin(),
1291 nonroot.cend(),
1292 d->by.chainid.cbegin(),
1293 d->by.chainid.cend(),
1294 std::back_inserter(by_chainid),
1295 lexicographically<_detail::ByChainID, _detail::ByFingerprint>());
1296
1297 // 4. sort by key id:
1298 std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
1299
1300 // 4a. insert into keyid index:
1301 std::vector<Key> by_keyid;
1302 by_keyid.reserve(sorted.size() + d->by.keyid.size());
1303 std::merge(sorted.begin(), sorted.end(), d->by.keyid.begin(), d->by.keyid.end(), std::back_inserter(by_keyid), _detail::ByKeyID<std::less>());
1304
1305 // 5. sort by short key id:
1306 std::sort(sorted.begin(), sorted.end(), _detail::ByShortKeyID<std::less>());
1307
1308 // 5a. insert into short keyid index:
1309 std::vector<Key> by_shortkeyid;
1310 by_shortkeyid.reserve(sorted.size() + d->by.shortkeyid.size());
1311 std::merge(sorted.begin(),
1312 sorted.end(),
1313 d->by.shortkeyid.begin(),
1314 d->by.shortkeyid.end(),
1315 std::back_inserter(by_shortkeyid),
1316 _detail::ByShortKeyID<std::less>());
1317
1318 // 6. build subkey ID index:
1319 std::vector<Subkey> subkeys;
1320 subkeys.reserve(sorted.size());
1321 for (const Key &key : std::as_const(sorted)) {
1322 const auto keySubkeys{key.subkeys()};
1323 for (const Subkey &subkey : keySubkeys) {
1324 subkeys.push_back(subkey);
1325 }
1326 }
1327
1328 // 6a sort by key id:
1329 std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyID<std::less>());
1330
1331 // 6b. insert into subkey ID index:
1332 std::vector<Subkey> by_subkeyid;
1333 by_subkeyid.reserve(subkeys.size() + d->by.subkeyid.size());
1334 std::merge(subkeys.begin(), subkeys.end(), d->by.subkeyid.begin(), d->by.subkeyid.end(), std::back_inserter(by_subkeyid), _detail::ByKeyID<std::less>());
1335
1336 // 6c. sort by key grip
1337 std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyGrip<std::less>());
1338
1339 // 6d. insert into subkey keygrip index:
1340 std::vector<Subkey> by_keygrip;
1341 by_keygrip.reserve(subkeys.size() + d->by.keygrip.size());
1342 std::merge(subkeys.begin(), subkeys.end(), d->by.keygrip.begin(), d->by.keygrip.end(), std::back_inserter(by_keygrip), _detail::ByKeyGrip<std::less>());
1343
1344 // now commit (well, we already removed keys...)
1345 by_fpr.swap(d->by.fpr);
1346 by_keyid.swap(d->by.keyid);
1347 by_shortkeyid.swap(d->by.shortkeyid);
1348 by_email.swap(d->by.email);
1349 by_subkeyid.swap(d->by.subkeyid);
1350 by_keygrip.swap(d->by.keygrip);
1351 by_chainid.swap(d->by.chainid);
1352
1353 for (const Key &key : std::as_const(sorted)) {
1354 d->m_pgpOnly &= key.protocol() == GpgME::OpenPGP;
1355 }
1356
1357 Q_EMIT keysMayHaveChanged();
1358}
1359
1360void KeyCache::clear()
1361{
1362 d->by = Private::By();
1363}
1364
1365//
1366//
1367// RefreshKeysJob
1368//
1369//
1370
1371class KeyCache::RefreshKeysJob::Private
1372{
1373 RefreshKeysJob *const q;
1374
1375public:
1376 Private(KeyCache *cache, RefreshKeysJob *qq);
1377 void doStart();
1378 Error startKeyListing(GpgME::Protocol protocol);
1379 void listAllKeysJobDone(const KeyListResult &res, const std::vector<Key> &nextKeys)
1380 {
1381 std::vector<Key> keys;
1382 keys.reserve(m_keys.size() + nextKeys.size());
1383 if (m_keys.empty()) {
1384 keys = nextKeys;
1385 } else {
1386 std::merge(m_keys.begin(), m_keys.end(), nextKeys.begin(), nextKeys.end(), std::back_inserter(keys), _detail::ByFingerprint<std::less>());
1387 }
1388 m_keys.swap(keys);
1389 jobDone(res);
1390 }
1391 void emitDone(const KeyListResult &result);
1392 void updateKeyCache();
1393
1394 QPointer<KeyCache> m_cache;
1395 QList<QGpgME::ListAllKeysJob *> m_jobsPending;
1396 std::vector<Key> m_keys;
1397 KeyListResult m_mergedResult;
1398 bool m_canceled;
1399
1400private:
1401 void jobDone(const KeyListResult &res);
1402};
1403
1404KeyCache::RefreshKeysJob::Private::Private(KeyCache *cache, RefreshKeysJob *qq)
1405 : q(qq)
1406 , m_cache(cache)
1407 , m_canceled(false)
1408{
1409 Q_ASSERT(m_cache);
1410}
1411
1412void KeyCache::RefreshKeysJob::Private::jobDone(const KeyListResult &result)
1413{
1414 if (m_canceled) {
1415 q->deleteLater();
1416 return;
1417 }
1418
1419 QObject *const sender = q->sender();
1420 if (sender) {
1421 sender->disconnect(q);
1422 }
1423 Q_ASSERT(m_jobsPending.size() > 0);
1424 m_jobsPending.removeOne(qobject_cast<QGpgME::ListAllKeysJob *>(sender));
1425 m_mergedResult.mergeWith(result);
1426 if (m_jobsPending.size() > 0) {
1427 return;
1428 }
1429 updateKeyCache();
1430 emitDone(m_mergedResult);
1431}
1432
1433void KeyCache::RefreshKeysJob::Private::emitDone(const KeyListResult &res)
1434{
1435 q->deleteLater();
1436 Q_EMIT q->done(res);
1437}
1438
1439KeyCache::RefreshKeysJob::RefreshKeysJob(KeyCache *cache, QObject *parent)
1440 : QObject(parent)
1441 , d(new Private(cache, this))
1442{
1443}
1444
1445KeyCache::RefreshKeysJob::~RefreshKeysJob()
1446{
1447 delete d;
1448}
1449
1450void KeyCache::RefreshKeysJob::start()
1451{
1452 qCDebug(LIBKLEO_LOG) << "KeyCache::RefreshKeysJob" << __func__;
1453 QTimer::singleShot(0, this, [this]() {
1454 d->doStart();
1455 });
1456}
1457
1458void KeyCache::RefreshKeysJob::cancel()
1459{
1460 d->m_canceled = true;
1461 std::for_each(d->m_jobsPending.begin(), d->m_jobsPending.end(), std::mem_fn(&QGpgME::ListAllKeysJob::slotCancel));
1462 Q_EMIT canceled();
1463}
1464
1465void KeyCache::RefreshKeysJob::Private::doStart()
1466{
1467 if (m_canceled) {
1468 q->deleteLater();
1469 return;
1470 }
1471
1472 Q_ASSERT(m_jobsPending.size() == 0);
1473 m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::OpenPGP)));
1474 m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::CMS)));
1475
1476 if (m_jobsPending.size() != 0) {
1477 return;
1478 }
1479
1480 const bool hasError = m_mergedResult.error() || m_mergedResult.error().isCanceled();
1481 emitDone(hasError ? m_mergedResult : KeyListResult(Error(GPG_ERR_UNSUPPORTED_OPERATION)));
1482}
1483
1484void KeyCache::RefreshKeysJob::Private::updateKeyCache()
1485{
1486 if (!m_cache || m_canceled) {
1487 q->deleteLater();
1488 return;
1489 }
1490
1491 std::vector<Key> cachedKeys = m_cache->initialized() ? m_cache->keys() : std::vector<Key>();
1492 std::sort(cachedKeys.begin(), cachedKeys.end(), _detail::ByFingerprint<std::less>());
1493 std::vector<Key> keysToRemove;
1494 std::set_difference(cachedKeys.begin(),
1495 cachedKeys.end(),
1496 m_keys.begin(),
1497 m_keys.end(),
1498 std::back_inserter(keysToRemove),
1499 _detail::ByFingerprint<std::less>());
1500 m_cache->remove(keysToRemove);
1501 m_cache->refresh(m_keys);
1502}
1503
1504Error KeyCache::RefreshKeysJob::Private::startKeyListing(GpgME::Protocol proto)
1505{
1506 const auto *const protocol = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
1507 if (!protocol) {
1508 return Error();
1509 }
1510 QGpgME::ListAllKeysJob *const job = protocol->listAllKeysJob(/*includeSigs*/ false, /*validate*/ true);
1511 if (!job) {
1512 return Error();
1513 }
1514 if (!m_cache->initialized()) {
1515 // avoid delays during the initial key listing
1516 job->setOptions(QGpgME::ListAllKeysJob::DisableAutomaticTrustDatabaseCheck);
1517 }
1518
1519#if 0
1520 aheinecke: 2017.01.12:
1521
1522 For unknown reasons the new style connect fails at runtime
1523 over library borders into QGpgME from the GpgME repo
1524 when cross compiled for Windows and default arguments
1525 are used in the Signal.
1526
1527 This was tested with gcc 4.9 (Mingw 3.0.2) and we could not
1528 find an explanation for this. So until this is fixed or we understand
1529 the problem we need to use the old style connect for QGpgME signals.
1530
1531 The new style connect of the canceled signal right below
1532 works fine.
1533
1534 connect(job, &QGpgME::ListAllKeysJob::result,
1535 q, [this](const GpgME::KeyListResult &res, const std::vector<GpgME::Key> &keys) {
1536 listAllKeysJobDone(res, keys);
1537 });
1538#endif
1539 connect(job, SIGNAL(result(GpgME::KeyListResult, std::vector<GpgME::Key>)), q, SLOT(listAllKeysJobDone(GpgME::KeyListResult, std::vector<GpgME::Key>)));
1540
1541 connect(q, &RefreshKeysJob::canceled, job, &QGpgME::Job::slotCancel);
1542
1543 // Only do this for initialized keycaches to avoid huge waits for
1544 // signature notations during initial keylisting.
1545 if (proto == GpgME::OpenPGP && m_cache->remarksEnabled() && m_cache->initialized()) {
1546 auto ctx = QGpgME::Job::context(job);
1547 if (ctx) {
1548 ctx->addKeyListMode(KeyListMode::Signatures | KeyListMode::SignatureNotations);
1549 }
1550 }
1551
1552 const Error error = job->start(true);
1553
1554 if (!error && !error.isCanceled()) {
1555 m_jobsPending.push_back(job);
1556 }
1557 return error;
1558}
1559
1560bool KeyCache::initialized() const
1561{
1562 return d->m_initalized;
1563}
1564
1565void KeyCache::Private::ensureCachePopulated() const
1566{
1567 if (!m_initalized) {
1568 q->startKeyListing();
1569 QEventLoop loop;
1570 loop.connect(q, &KeyCache::keyListingDone, &loop, &QEventLoop::quit);
1571 qCDebug(LIBKLEO_LOG) << "Waiting for keycache.";
1572 loop.exec();
1573 qCDebug(LIBKLEO_LOG) << "Keycache available.";
1574 }
1575}
1576
1577bool KeyCache::pgpOnly() const
1578{
1579 return d->m_pgpOnly;
1580}
1581
1582static bool keyIsOk(const Key &k)
1583{
1584 return !k.isExpired() && !k.isRevoked() && !k.isInvalid() && !k.isDisabled();
1585}
1586
1587static bool uidIsOk(const UserID &uid)
1588{
1589 return keyIsOk(uid.parent()) && !uid.isRevoked() && !uid.isInvalid();
1590}
1591
1592static bool subkeyIsOk(const Subkey &s)
1593{
1594 return !s.isRevoked() && !s.isInvalid() && !s.isDisabled();
1595}
1596
1597namespace
1598{
1599time_t creationTimeOfNewestSuitableSubKey(const Key &key, KeyCache::KeyUsage usage)
1600{
1601 time_t creationTime = 0;
1602 for (const Subkey &s : key.subkeys()) {
1603 if (!subkeyIsOk(s)) {
1604 continue;
1605 }
1606 if (usage == KeyCache::KeyUsage::Sign && !s.canSign()) {
1607 continue;
1608 }
1609 if (usage == KeyCache::KeyUsage::Encrypt && !s.canEncrypt()) {
1610 continue;
1611 }
1612 if (s.creationTime() > creationTime) {
1613 creationTime = s.creationTime();
1614 }
1615 }
1616 return creationTime;
1617}
1618
1619struct BestMatch {
1620 Key key;
1621 UserID uid;
1622 time_t creationTime = 0;
1623};
1624}
1625
1626GpgME::Key KeyCache::findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const
1627{
1628 d->ensureCachePopulated();
1629 if (!addr) {
1630 return {};
1631 }
1632
1633 // support lookup of email addresses enclosed in angle brackets
1634 QByteArray address(addr);
1635 if (address.size() > 1 && address[0] == '<' && address[address.size() - 1] == '>') {
1636 address = address.mid(1, address.size() - 2);
1637 }
1638 address = address.toLower();
1639
1640 BestMatch best;
1641 for (const Key &k : findByEMailAddress(address.constData())) {
1642 if (proto != Protocol::UnknownProtocol && k.protocol() != proto) {
1643 continue;
1644 }
1645 if (usage == KeyUsage::Encrypt && !keyHasEncrypt(k)) {
1646 continue;
1647 }
1648 if (usage == KeyUsage::Sign && (!keyHasSign(k) || !k.hasSecret())) {
1649 continue;
1650 }
1651 const time_t creationTime = creationTimeOfNewestSuitableSubKey(k, usage);
1652 if (creationTime == 0) {
1653 // key does not have a suitable (and usable) subkey
1654 continue;
1655 }
1656 for (const UserID &u : k.userIDs()) {
1657 if (QByteArray::fromStdString(u.addrSpec()).toLower() != address) {
1658 // user ID does not match the given email address
1659 continue;
1660 }
1661 if (best.uid.isNull()) {
1662 // we have found our first candidate
1663 best = {k, u, creationTime};
1664 } else if (!uidIsOk(best.uid) && uidIsOk(u)) {
1665 // validity of the new key is better
1666 best = {k, u, creationTime};
1667 } else if (!k.isExpired() && best.uid.validity() < u.validity()) {
1668 // validity of the new key is better
1669 best = {k, u, creationTime};
1670 } else if (best.key.isExpired() && !k.isExpired()) {
1671 // validity of the new key is better
1672 best = {k, u, creationTime};
1673 } else if (best.uid.validity() == u.validity() && uidIsOk(u) && best.creationTime < creationTime) {
1674 // both keys/user IDs have same validity, but the new key is newer
1675 best = {k, u, creationTime};
1676 }
1677 }
1678 }
1679
1680 return best.key;
1681}
1682
1683namespace
1684{
1685template<typename T>
1686bool allKeysAllowUsage(const T &keys, KeyCache::KeyUsage usage)
1687{
1688 switch (usage) {
1689 case KeyCache::KeyUsage::AnyUsage:
1690 return true;
1691 case KeyCache::KeyUsage::Sign:
1692 return std::all_of(std::begin(keys),
1693 std::end(keys),
1694#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
1695 std::mem_fn(&Key::hasSign)
1696#else
1697 Kleo::keyHasSign
1698#endif
1699 );
1700 case KeyCache::KeyUsage::Encrypt:
1701 return std::all_of(std::begin(keys),
1702 std::end(keys),
1703#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
1704 std::mem_fn(&Key::hasEncrypt)
1705#else
1706 Kleo::keyHasEncrypt
1707#endif
1708 );
1709 case KeyCache::KeyUsage::Certify:
1710 return std::all_of(std::begin(keys),
1711 std::end(keys),
1712#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
1713 std::mem_fn(&Key::hasCertify)
1714#else
1715 Kleo::keyHasCertify
1716#endif
1717 );
1718 case KeyCache::KeyUsage::Authenticate:
1719 return std::all_of(std::begin(keys),
1720 std::end(keys),
1721#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
1722 std::mem_fn(&Key::hasAuthenticate)
1723#else
1724 Kleo::keyHasAuthenticate
1725#endif
1726 );
1727 }
1728 qCDebug(LIBKLEO_LOG) << __func__ << "called with invalid usage" << int(usage);
1729 return false;
1730}
1731}
1732
1733KeyGroup KeyCache::findGroup(const QString &name, Protocol protocol, KeyUsage usage) const
1734{
1735 d->ensureCachePopulated();
1736
1737 Q_ASSERT(usage == KeyUsage::Sign || usage == KeyUsage::Encrypt);
1738 for (const auto &group : std::as_const(d->m_groups)) {
1739 if (group.name() == name) {
1740 const KeyGroup::Keys &keys = group.keys();
1741 if (allKeysAllowUsage(keys, usage) && (protocol == UnknownProtocol || allKeysHaveProtocol(keys, protocol))) {
1742 return group;
1743 }
1744 }
1745 }
1746
1747 return {};
1748}
1749
1750std::vector<Key> KeyCache::getGroupKeys(const QString &groupName) const
1751{
1752 std::vector<Key> result;
1753 for (const KeyGroup &g : std::as_const(d->m_groups)) {
1754 if (g.name() == groupName) {
1755 const KeyGroup::Keys &keys = g.keys();
1756 std::copy(keys.cbegin(), keys.cend(), std::back_inserter(result));
1757 }
1758 }
1759 _detail::sort_by_fpr(result);
1760 _detail::remove_duplicates_by_fpr(result);
1761 return result;
1762}
1763
1764void KeyCache::setKeys(const std::vector<GpgME::Key> &keys)
1765{
1766 // disable regular key listing and cancel running key listing
1767 setRefreshInterval(0);
1768 cancelKeyListing();
1769 clear();
1770 insert(keys);
1771 d->m_initalized = true;
1772 Q_EMIT keyListingDone(KeyListResult());
1773}
1774
1775void KeyCache::setGroups(const std::vector<KeyGroup> &groups)
1776{
1777 Q_ASSERT(d->m_initalized && "Call setKeys() before setting groups");
1778 d->m_groups = groups;
1779 Q_EMIT keysMayHaveChanged();
1780}
1781
1782#include "moc_keycache.cpp"
1783#include "moc_keycache_p.cpp"
DN parser and reorderer.
Definition dn.h:27
PostalAddress address(const QVariant &location)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KGuiItem remove()
KGuiItem insert()
KGuiItem clear()
QByteArray fromStdString(const std::string &str)
QByteArray toLower() const const
int exec(ProcessEventsFlags flags)
void quit()
bool removeOne(const AT &t)
qsizetype size() const const
const_iterator cbegin() const const
const_iterator cend() const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QObject * sender() const const
void clear()
QueuedConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setInterval(int msec)
void start()
void stop()
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:12 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.