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 
58 using namespace std::chrono_literals;
59 using namespace Kleo;
60 using namespace GpgME;
61 using namespace KMime::Types;
62 
63 static const unsigned int hours2ms = 1000 * 60 * 60;
64 
65 //
66 //
67 // KeyCache
68 //
69 //
70 
71 namespace
72 {
73 
74 make_comparator_str(ByEMail, .first.c_str());
75 
76 }
77 
78 class 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 
91 public:
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 
113 private:
114  std::weak_ptr<KeyCache> m_cache;
115  int m_refreshInterval = 0;
116 };
117 
118 class KeyCache::Private
119 {
120  friend class ::Kleo::KeyCache;
121  KeyCache *const q;
122 
123 public:
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 
423 private:
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 
442 std::shared_ptr<const KeyCache> KeyCache::instance()
443 {
444  return mutableInstance();
445 }
446 
447 std::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 
459 KeyCache::KeyCache()
460  : QObject()
461  , d(new Private(this))
462 {
463 }
464 
465 KeyCache::~KeyCache()
466 {
467 }
468 
469 void KeyCache::setGroupsEnabled(bool enabled)
470 {
471  d->m_groupsEnabled = enabled;
472  if (d->m_initalized) {
473  d->updateGroupCache();
474  }
475 }
476 
477 void KeyCache::setGroupConfig(const std::shared_ptr<KeyGroupConfig> &groupConfig)
478 {
479  d->m_groupConfig = groupConfig;
480 }
481 
482 void KeyCache::enableFileSystemWatcher(bool enable)
483 {
484  for (const auto &i : std::as_const(d->m_fsWatchers)) {
485  i->setEnabled(enable);
486  }
487 }
488 
489 void KeyCache::setRefreshInterval(int hours)
490 {
491  d->setRefreshInterval(hours);
492 }
493 
494 int KeyCache::refreshInterval() const
495 {
496  return d->refreshInterval();
497 }
498 
499 std::shared_ptr<KeyCacheAutoRefreshSuspension> KeyCache::suspendAutoRefresh()
500 {
501  return KeyCacheAutoRefreshSuspension::instance();
502 }
503 
504 void KeyCache::reload(GpgME::Protocol /*proto*/)
505 {
506  if (d->m_refreshJob) {
507  return;
508  }
509 
510  d->updateAutoKeyListingTimer();
511 
512  enableFileSystemWatcher(false);
513  d->m_refreshJob = new RefreshKeysJob(this);
514  connect(d->m_refreshJob.data(), &RefreshKeysJob::done, this, [this](const GpgME::KeyListResult &r) {
515  d->refreshJobDone(r);
516  });
517  connect(d->m_refreshJob.data(), &RefreshKeysJob::canceled, this, [this]() {
518  d->m_refreshJob.clear();
519  });
520  d->m_refreshJob->start();
521 }
522 
523 void KeyCache::cancelKeyListing()
524 {
525  if (!d->m_refreshJob) {
526  return;
527  }
528  d->m_refreshJob->cancel();
529 }
530 
531 void KeyCache::addFileSystemWatcher(const std::shared_ptr<FileSystemWatcher> &watcher)
532 {
533  if (!watcher) {
534  return;
535  }
536  d->m_fsWatchers.push_back(watcher);
537  connect(watcher.get(), &FileSystemWatcher::directoryChanged, this, [this]() {
538  startKeyListing();
539  });
540  connect(watcher.get(), &FileSystemWatcher::fileChanged, this, [this]() {
541  startKeyListing();
542  });
543 
544  watcher->setEnabled(d->m_refreshJob.isNull());
545 }
546 
547 void KeyCache::enableRemarks(bool value)
548 {
549  if (!d->m_remarks_enabled && value) {
550  d->m_remarks_enabled = value;
551  if (d->m_initalized && !d->m_refreshJob) {
552  qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled";
553  reload();
554  }
555  } else {
556  d->m_remarks_enabled = value;
557  }
558 }
559 
560 bool KeyCache::remarksEnabled() const
561 {
562  return d->m_remarks_enabled;
563 }
564 
565 void KeyCache::Private::refreshJobDone(const KeyListResult &result)
566 {
567  m_refreshJob.clear();
568  q->enableFileSystemWatcher(true);
569  if (!m_initalized && q->remarksEnabled()) {
570  // trigger another key listing to read signatures and signature notations
572  q,
573  [this]() {
574  qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled";
575  q->reload();
576  },
578  }
579  m_initalized = true;
580  updateGroupCache();
581  Q_EMIT q->keyListingDone(result);
582 }
583 
584 const Key &KeyCache::findByFingerprint(const char *fpr) const
585 {
586  const std::vector<Key>::const_iterator it = d->find_fpr(fpr);
587  if (it == d->by.fpr.end()) {
588  static const Key null;
589  return null;
590  } else {
591  return *it;
592  }
593 }
594 
595 const Key &KeyCache::findByFingerprint(const std::string &fpr) const
596 {
597  return findByFingerprint(fpr.c_str());
598 }
599 
600 std::vector<GpgME::Key> KeyCache::findByFingerprint(const std::vector<std::string> &fprs) const
601 {
602  std::vector<Key> keys;
603  keys.reserve(fprs.size());
604  for (const auto &fpr : fprs) {
605  const Key key = findByFingerprint(fpr.c_str());
606  if (key.isNull()) {
607  qCDebug(LIBKLEO_LOG) << __func__ << "Ignoring unknown key with fingerprint:" << fpr.c_str();
608  continue;
609  }
610  keys.push_back(key);
611  }
612  return keys;
613 }
614 
615 std::vector<Key> KeyCache::findByEMailAddress(const char *email) const
616 {
617  const auto pair = d->find_email(email);
618  std::vector<Key> result;
619  result.reserve(std::distance(pair.first, pair.second));
620  std::transform(pair.first, pair.second, std::back_inserter(result), [](const std::pair<std::string, Key> &pair) {
621  return pair.second;
622  });
623  return result;
624 }
625 
626 std::vector<Key> KeyCache::findByEMailAddress(const std::string &email) const
627 {
628  return findByEMailAddress(email.c_str());
629 }
630 
631 const Key &KeyCache::findByShortKeyID(const char *id) const
632 {
633  const std::vector<Key>::const_iterator it = d->find_shortkeyid(id);
634  if (it != d->by.shortkeyid.end()) {
635  return *it;
636  }
637  static const Key null;
638  return null;
639 }
640 
641 const Key &KeyCache::findByShortKeyID(const std::string &id) const
642 {
643  return findByShortKeyID(id.c_str());
644 }
645 
646 const Key &KeyCache::findByKeyIDOrFingerprint(const char *id) const
647 {
648  {
649  // try by.fpr first:
650  const std::vector<Key>::const_iterator it = d->find_fpr(id);
651  if (it != d->by.fpr.end()) {
652  return *it;
653  }
654  }
655  {
656  // try by.keyid next:
657  const std::vector<Key>::const_iterator it = d->find_keyid(id);
658  if (it != d->by.keyid.end()) {
659  return *it;
660  }
661  }
662  static const Key null;
663  return null;
664 }
665 
666 const Key &KeyCache::findByKeyIDOrFingerprint(const std::string &id) const
667 {
668  return findByKeyIDOrFingerprint(id.c_str());
669 }
670 
671 std::vector<Key> KeyCache::findByKeyIDOrFingerprint(const std::vector<std::string> &ids) const
672 {
673  std::vector<std::string> keyids;
674  std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(keyids), [](const std::string &str) {
675  return !str.c_str() || !*str.c_str();
676  });
677 
678  // this is just case-insensitive string search:
679  std::sort(keyids.begin(), keyids.end(), _detail::ByFingerprint<std::less>());
680 
681  std::vector<Key> result;
682  result.reserve(keyids.size()); // dups shouldn't happen
683  d->ensureCachePopulated();
684 
685  kdtools::set_intersection(d->by.fpr.begin(),
686  d->by.fpr.end(),
687  keyids.begin(),
688  keyids.end(),
689  std::back_inserter(result),
690  _detail::ByFingerprint<std::less>());
691  if (result.size() < keyids.size()) {
692  // note that By{Fingerprint,KeyID,ShortKeyID} define the same
693  // order for _strings_
694  kdtools::set_intersection(d->by.keyid.begin(),
695  d->by.keyid.end(),
696  keyids.begin(),
697  keyids.end(),
698  std::back_inserter(result),
699  _detail::ByKeyID<std::less>());
700  }
701  // duplicates shouldn't happen, but make sure nonetheless:
702  std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
703  result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
704 
705  // we skip looking into short key ids here, as it's highly
706  // unlikely they're used for this purpose. We might need to revise
707  // this decision, but only after testing.
708  return result;
709 }
710 
711 const Subkey &KeyCache::findSubkeyByKeyGrip(const char *grip, Protocol protocol) const
712 {
713  static const Subkey null;
714  d->ensureCachePopulated();
715  const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), grip, _detail::ByKeyGrip<std::less>());
716  if (range.first == range.second) {
717  return null;
718  } else if (protocol == UnknownProtocol) {
719  return *range.first;
720  } else {
721  for (auto it = range.first; it != range.second; ++it) {
722  if (it->parent().protocol() == protocol) {
723  return *it;
724  }
725  }
726  }
727  return null;
728 }
729 
730 const Subkey &KeyCache::findSubkeyByKeyGrip(const std::string &grip, Protocol protocol) const
731 {
732  return findSubkeyByKeyGrip(grip.c_str(), protocol);
733 }
734 
735 std::vector<Subkey> KeyCache::findSubkeysByKeyID(const std::vector<std::string> &ids) const
736 {
737  std::vector<std::string> sorted;
738  sorted.reserve(ids.size());
739  std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(sorted), [](const std::string &str) {
740  return !str.c_str() || !*str.c_str();
741  });
742 
743  std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
744 
745  std::vector<Subkey> result;
746  d->ensureCachePopulated();
747  kdtools::set_intersection(d->by.subkeyid.begin(),
748  d->by.subkeyid.end(),
749  sorted.begin(),
750  sorted.end(),
751  std::back_inserter(result),
752  _detail::ByKeyID<std::less>());
753  return result;
754 }
755 
756 std::vector<Key> KeyCache::findRecipients(const DecryptionResult &res) const
757 {
758  std::vector<std::string> keyids;
759  const auto recipients = res.recipients();
760  for (const DecryptionResult::Recipient &r : recipients) {
761  if (const char *kid = r.keyID()) {
762  keyids.push_back(kid);
763  }
764  }
765  const std::vector<Subkey> subkeys = findSubkeysByKeyID(keyids);
766  std::vector<Key> result;
767  result.reserve(subkeys.size());
768  std::transform(subkeys.begin(), subkeys.end(), std::back_inserter(result), std::mem_fn(&Subkey::parent));
769 
770  std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
771  result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
772  return result;
773 }
774 
775 std::vector<Key> KeyCache::findSigners(const VerificationResult &res) const
776 {
777  std::vector<std::string> fprs;
778  const auto signatures = res.signatures();
779  for (const Signature &s : signatures) {
780  if (const char *fpr = s.fingerprint()) {
781  fprs.push_back(fpr);
782  }
783  }
784  return findByKeyIDOrFingerprint(fprs);
785 }
786 
787 std::vector<Key> KeyCache::findSigningKeysByMailbox(const QString &mb) const
788 {
789  return d->find_mailbox(mb, true);
790 }
791 
792 std::vector<Key> KeyCache::findEncryptionKeysByMailbox(const QString &mb) const
793 {
794  return d->find_mailbox(mb, false);
795 }
796 
797 namespace
798 {
799 #define DO(op, meth, meth2) \
800  if (op key.meth()) { \
801  } else { \
802  qDebug("rejecting for signing: %s: %s", #meth2, key.primaryFingerprint()); \
803  return false; \
804  }
805 #define ACCEPT(meth) DO(!!, meth, !meth)
806 #define REJECT(meth) DO(!, meth, meth)
807 struct ready_for_signing {
808  bool operator()(const Key &key) const
809  {
810  ACCEPT(hasSecret);
811 #if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
812  ACCEPT(hasSign);
813 #else
814  ACCEPT(canSign);
815 #endif
816  REJECT(isRevoked);
817  REJECT(isExpired);
818  REJECT(isDisabled);
819  REJECT(isInvalid);
820  return true;
821 #undef DO
822  }
823 };
824 
825 #define DO(op, meth, meth2) \
826  if (op key.meth()) { \
827  } else { \
828  qDebug("rejecting for encrypting: %s: %s", #meth2, key.primaryFingerprint()); \
829  return false; \
830  }
831 struct ready_for_encryption {
832  bool operator()(const Key &key) const
833  {
834 #if 1
835 #if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
836  ACCEPT(hasEncrypt);
837 #else
838  ACCEPT(canEncrypt);
839 #endif
840  REJECT(isRevoked);
841  REJECT(isExpired);
842  REJECT(isDisabled);
843  REJECT(isInvalid);
844  return true;
845 #else
846  return key.hasEncrypt() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid();
847 #endif
848  }
849 #undef DO
850 #undef ACCEPT
851 #undef REJECT
852 };
853 }
854 
855 std::vector<Key> KeyCache::Private::find_mailbox(const QString &email, bool sign) const
856 {
857  if (email.isEmpty()) {
858  return std::vector<Key>();
859  }
860 
861  const auto pair = find_email(email.toUtf8().constData());
862  std::vector<Key> result;
863  result.reserve(std::distance(pair.first, pair.second));
864  if (sign) {
865  kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_signing());
866  } else {
867  kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_encryption());
868  }
869 
870  return result;
871 }
872 
873 std::vector<Key> KeyCache::findSubjects(const GpgME::Key &key, Options options) const
874 {
875  if (key.isNull()) {
876  return {};
877  }
878 
879  return findSubjects(std::vector<Key>(1, key), options);
880 }
881 
882 std::vector<Key> KeyCache::findSubjects(const std::vector<Key> &keys, Options options) const
883 {
884  std::vector<Key> result;
885 
886  if (keys.empty()) {
887  return result;
888  }
889 
890  // get the immediate subjects
891  for (const auto &key : keys) {
892  const auto firstAndLastSubject = d->find_subjects(key.primaryFingerprint());
893  result.insert(result.end(), firstAndLastSubject.first, firstAndLastSubject.second);
894  }
895  // remove duplicates
896  _detail::sort_by_fpr(result);
897  _detail::remove_duplicates_by_fpr(result);
898 
899  if (options & RecursiveSearch) {
900  for (std::vector<Key> furtherSubjects = findSubjects(result, NoOption); //
901  !furtherSubjects.empty();
902  furtherSubjects = findSubjects(furtherSubjects, NoOption)) {
903  std::vector<Key> combined;
904  combined.reserve(result.size() + furtherSubjects.size());
905  std::merge(result.begin(),
906  result.end(),
907  furtherSubjects.begin(),
908  furtherSubjects.end(),
909  std::back_inserter(combined),
910  _detail::ByFingerprint<std::less>());
911  _detail::remove_duplicates_by_fpr(combined);
912  if (result.size() == combined.size()) {
913  // no new subjects were found; this happens if a chain has a cycle
914  break;
915  }
916  result.swap(combined);
917  }
918  }
919 
920  return result;
921 }
922 
923 std::vector<Key> KeyCache::findIssuers(const Key &key, Options options) const
924 {
925  std::vector<Key> result;
926 
927  if (key.isNull()) {
928  return result;
929  }
930 
931  if (options & IncludeSubject) {
932  result.push_back(key);
933  }
934 
935  if (key.isRoot()) {
936  return result;
937  }
938 
939  Key issuer = findByFingerprint(key.chainID());
940 
941  if (issuer.isNull()) {
942  return result;
943  }
944 
945  result.push_back(issuer);
946 
947  if (!(options & RecursiveSearch)) {
948  return result;
949  }
950 
951  while (!issuer.isRoot()) {
952  issuer = findByFingerprint(result.back().chainID());
953  if (issuer.isNull()) {
954  break;
955  }
956  const bool chainAlreadyContainsIssuer = Kleo::contains_if(result, [issuer](const auto &key) {
957  return _detail::ByFingerprint<std::equal_to>()(issuer, key);
958  });
959  // we also add the issuer if the chain already contains it, so that
960  // the user can spot the cycle
961  result.push_back(issuer);
962  if (chainAlreadyContainsIssuer) {
963  // break on cycle in chain
964  break;
965  }
966  }
967 
968  return result;
969 }
970 
971 static std::string email(const UserID &uid)
972 {
973  // Prefer the gnupg normalized one
974  const std::string addr = uid.addrSpec();
975  if (!addr.empty()) {
976  return addr;
977  }
978  const std::string email = uid.email();
979  if (email.empty()) {
980  return DN(uid.id())[QStringLiteral("EMAIL")].trimmed().toUtf8().constData();
981  }
982  if (email[0] == '<' && email[email.size() - 1] == '>') {
983  return email.substr(1, email.size() - 2);
984  } else {
985  return email;
986  }
987 }
988 
989 static std::vector<std::string> emails(const Key &key)
990 {
991  std::vector<std::string> emails;
992  const auto userIDs = key.userIDs();
993  for (const UserID &uid : userIDs) {
994  const std::string e = email(uid);
995  if (!e.empty()) {
996  emails.push_back(e);
997  }
998  }
999  std::sort(emails.begin(), emails.end(), ByEMail<std::less>());
1000  emails.erase(std::unique(emails.begin(), emails.end(), ByEMail<std::equal_to>()), emails.end());
1001  return emails;
1002 }
1003 
1004 void KeyCache::remove(const Key &key)
1005 {
1006  if (key.isNull()) {
1007  return;
1008  }
1009 
1010  const char *fpr = key.primaryFingerprint();
1011  if (!fpr) {
1012  return;
1013  }
1014 
1015  {
1016  const auto range = std::equal_range(d->by.fpr.begin(), d->by.fpr.end(), fpr, _detail::ByFingerprint<std::less>());
1017  d->by.fpr.erase(range.first, range.second);
1018  }
1019 
1020  if (const char *keyid = key.keyID()) {
1021  const auto range = std::equal_range(d->by.keyid.begin(), d->by.keyid.end(), keyid, _detail::ByKeyID<std::less>());
1022  const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) {
1023  return _detail::ByFingerprint<std::equal_to>()(fpr, key);
1024  });
1025  d->by.keyid.erase(it, range.second);
1026  }
1027 
1028  if (const char *shortkeyid = key.shortKeyID()) {
1029  const auto range = std::equal_range(d->by.shortkeyid.begin(), d->by.shortkeyid.end(), shortkeyid, _detail::ByShortKeyID<std::less>());
1030  const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) {
1031  return _detail::ByFingerprint<std::equal_to>()(fpr, key);
1032  });
1033  d->by.shortkeyid.erase(it, range.second);
1034  }
1035 
1036  if (const char *chainid = key.chainID()) {
1037  const auto range = std::equal_range(d->by.chainid.begin(), d->by.chainid.end(), chainid, _detail::ByChainID<std::less>());
1038  const auto range2 = std::equal_range(range.first, range.second, fpr, _detail::ByFingerprint<std::less>());
1039  d->by.chainid.erase(range2.first, range2.second);
1040  }
1041 
1042  const auto emailsKey{emails(key)};
1043  for (const std::string &email : emailsKey) {
1044  const auto range = std::equal_range(d->by.email.begin(), d->by.email.end(), email, ByEMail<std::less>());
1045  const auto it = std::remove_if(range.first, range.second, [fpr](const std::pair<std::string, Key> &pair) {
1046  return qstricmp(fpr, pair.second.primaryFingerprint()) == 0;
1047  });
1048  d->by.email.erase(it, range.second);
1049  }
1050 
1051  const auto keySubKeys{key.subkeys()};
1052  for (const Subkey &subkey : keySubKeys) {
1053  if (const char *keyid = subkey.keyID()) {
1054  const auto range = std::equal_range(d->by.subkeyid.begin(), d->by.subkeyid.end(), keyid, _detail::ByKeyID<std::less>());
1055  const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) {
1056  return !qstricmp(fpr, subkey.parent().primaryFingerprint());
1057  });
1058  d->by.subkeyid.erase(it, range.second);
1059  }
1060  if (const char *keygrip = subkey.keyGrip()) {
1061  const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), keygrip, _detail::ByKeyGrip<std::less>());
1062  const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) {
1063  return !qstricmp(fpr, subkey.parent().primaryFingerprint());
1064  });
1065  d->by.keygrip.erase(it, range.second);
1066  }
1067  }
1068 }
1069 
1070 void KeyCache::remove(const std::vector<Key> &keys)
1071 {
1072  for (const Key &key : keys) {
1073  remove(key);
1074  }
1075 }
1076 
1077 const std::vector<GpgME::Key> &KeyCache::keys() const
1078 {
1079  d->ensureCachePopulated();
1080  return d->by.fpr;
1081 }
1082 
1083 std::vector<Key> KeyCache::secretKeys() const
1084 {
1085  std::vector<Key> keys = this->keys();
1086  keys.erase(std::remove_if(keys.begin(),
1087  keys.end(),
1088  [](const Key &key) {
1089  return !key.hasSecret();
1090  }),
1091  keys.end());
1092  return keys;
1093 }
1094 
1095 KeyGroup KeyCache::group(const QString &id) const
1096 {
1097  KeyGroup result{};
1098  const auto it = std::find_if(std::cbegin(d->m_groups), std::cend(d->m_groups), [id](const auto &g) {
1099  return g.id() == id;
1100  });
1101  if (it != std::cend(d->m_groups)) {
1102  result = *it;
1103  }
1104  return result;
1105 }
1106 
1107 std::vector<KeyGroup> KeyCache::groups() const
1108 {
1109  d->ensureCachePopulated();
1110  return d->m_groups;
1111 }
1112 
1113 std::vector<KeyGroup> KeyCache::configurableGroups() const
1114 {
1115  std::vector<KeyGroup> groups;
1116  groups.reserve(d->m_groups.size());
1117  std::copy_if(d->m_groups.cbegin(), d->m_groups.cend(), std::back_inserter(groups), [](const KeyGroup &group) {
1118  return group.source() == KeyGroup::ApplicationConfig;
1119  });
1120  return groups;
1121 }
1122 
1123 namespace
1124 {
1125 bool compareById(const KeyGroup &lhs, const KeyGroup &rhs)
1126 {
1127  return lhs.id() < rhs.id();
1128 }
1129 
1130 std::vector<KeyGroup> sortedById(std::vector<KeyGroup> groups)
1131 {
1132  std::sort(groups.begin(), groups.end(), &compareById);
1133  return groups;
1134 }
1135 }
1136 
1137 void KeyCache::saveConfigurableGroups(const std::vector<KeyGroup> &groups)
1138 {
1139  const std::vector<KeyGroup> oldGroups = sortedById(configurableGroups());
1140  const std::vector<KeyGroup> newGroups = sortedById(groups);
1141 
1142  {
1143  std::vector<KeyGroup> removedGroups;
1144  std::set_difference(oldGroups.begin(), oldGroups.end(), newGroups.begin(), newGroups.end(), std::back_inserter(removedGroups), &compareById);
1145  for (const auto &group : std::as_const(removedGroups)) {
1146  qCDebug(LIBKLEO_LOG) << "Removing group" << group;
1147  d->remove(group);
1148  }
1149  }
1150  {
1151  std::vector<KeyGroup> updatedGroups;
1152  std::set_intersection(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(updatedGroups), &compareById);
1153  for (const auto &group : std::as_const(updatedGroups)) {
1154  qCDebug(LIBKLEO_LOG) << "Updating group" << group;
1155  d->update(group);
1156  }
1157  }
1158  {
1159  std::vector<KeyGroup> addedGroups;
1160  std::set_difference(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(addedGroups), &compareById);
1161  for (const auto &group : std::as_const(addedGroups)) {
1162  qCDebug(LIBKLEO_LOG) << "Adding group" << group;
1163  d->insert(group);
1164  }
1165  }
1166 
1167  Q_EMIT keysMayHaveChanged();
1168 }
1169 
1170 bool KeyCache::insert(const KeyGroup &group)
1171 {
1172  if (!d->insert(group)) {
1173  return false;
1174  }
1175 
1176  Q_EMIT keysMayHaveChanged();
1177 
1178  return true;
1179 }
1180 
1181 bool KeyCache::update(const KeyGroup &group)
1182 {
1183  if (!d->update(group)) {
1184  return false;
1185  }
1186 
1187  Q_EMIT keysMayHaveChanged();
1188 
1189  return true;
1190 }
1191 
1192 bool KeyCache::remove(const KeyGroup &group)
1193 {
1194  if (!d->remove(group)) {
1195  return false;
1196  }
1197 
1198  Q_EMIT keysMayHaveChanged();
1199 
1200  return true;
1201 }
1202 
1203 void KeyCache::refresh(const std::vector<Key> &keys)
1204 {
1205  // make this better...
1206  clear();
1207  insert(keys);
1208 }
1209 
1210 void KeyCache::insert(const Key &key)
1211 {
1212  insert(std::vector<Key>(1, key));
1213 }
1214 
1215 namespace
1216 {
1217 
1218 template<template<template<typename T> class Op> class T1, template<template<typename T> class Op> class T2>
1219 struct lexicographically {
1220  using result_type = bool;
1221 
1222  template<typename U, typename V>
1223  bool operator()(const U &lhs, const V &rhs) const
1224  {
1225  return T1<std::less>()(lhs, rhs) //
1226  || (T1<std::equal_to>()(lhs, rhs) && T2<std::less>()(lhs, rhs));
1227  }
1228 };
1229 
1230 }
1231 
1232 void KeyCache::insert(const std::vector<Key> &keys)
1233 {
1234  // 1. remove those with empty fingerprints:
1235  std::vector<Key> sorted;
1236  sorted.reserve(keys.size());
1237  std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(sorted), [](const Key &key) {
1238  auto fp = key.primaryFingerprint();
1239  return !fp || !*fp;
1240  });
1241 
1242  Q_FOREACH (const Key &key, sorted) {
1243  remove(key); // this is sub-optimal, but makes implementation from here on much easier
1244  }
1245 
1246  // 2. sort by fingerprint:
1247  std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint<std::less>());
1248 
1249  // 2a. insert into fpr index:
1250  std::vector<Key> by_fpr;
1251  by_fpr.reserve(sorted.size() + d->by.fpr.size());
1252  std::merge(sorted.begin(), sorted.end(), d->by.fpr.begin(), d->by.fpr.end(), std::back_inserter(by_fpr), _detail::ByFingerprint<std::less>());
1253 
1254  // 3. build email index:
1255  std::vector<std::pair<std::string, Key>> pairs;
1256  pairs.reserve(sorted.size());
1257  for (const Key &key : std::as_const(sorted)) {
1258  const std::vector<std::string> emails = ::emails(key);
1259  for (const std::string &e : emails) {
1260  pairs.push_back(std::make_pair(e, key));
1261  }
1262  }
1263  std::sort(pairs.begin(), pairs.end(), ByEMail<std::less>());
1264 
1265  // 3a. insert into email index:
1266  std::vector<std::pair<std::string, Key>> by_email;
1267  by_email.reserve(pairs.size() + d->by.email.size());
1268  std::merge(pairs.begin(), pairs.end(), d->by.email.begin(), d->by.email.end(), std::back_inserter(by_email), ByEMail<std::less>());
1269 
1270  // 3.5: stable-sort by chain-id (effectively lexicographically<ByChainID,ByFingerprint>)
1271  std::stable_sort(sorted.begin(), sorted.end(), _detail::ByChainID<std::less>());
1272 
1273  // 3.5a: insert into chain-id index:
1274  std::vector<Key> nonroot;
1275  nonroot.reserve(sorted.size());
1276  std::vector<Key> by_chainid;
1277  by_chainid.reserve(sorted.size() + d->by.chainid.size());
1278  std::copy_if(sorted.cbegin(), sorted.cend(), std::back_inserter(nonroot), [](const Key &key) {
1279  return !key.isRoot();
1280  });
1281  std::merge(nonroot.cbegin(),
1282  nonroot.cend(),
1283  d->by.chainid.cbegin(),
1284  d->by.chainid.cend(),
1285  std::back_inserter(by_chainid),
1286  lexicographically<_detail::ByChainID, _detail::ByFingerprint>());
1287 
1288  // 4. sort by key id:
1289  std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
1290 
1291  // 4a. insert into keyid index:
1292  std::vector<Key> by_keyid;
1293  by_keyid.reserve(sorted.size() + d->by.keyid.size());
1294  std::merge(sorted.begin(), sorted.end(), d->by.keyid.begin(), d->by.keyid.end(), std::back_inserter(by_keyid), _detail::ByKeyID<std::less>());
1295 
1296  // 5. sort by short key id:
1297  std::sort(sorted.begin(), sorted.end(), _detail::ByShortKeyID<std::less>());
1298 
1299  // 5a. insert into short keyid index:
1300  std::vector<Key> by_shortkeyid;
1301  by_shortkeyid.reserve(sorted.size() + d->by.shortkeyid.size());
1302  std::merge(sorted.begin(),
1303  sorted.end(),
1304  d->by.shortkeyid.begin(),
1305  d->by.shortkeyid.end(),
1306  std::back_inserter(by_shortkeyid),
1307  _detail::ByShortKeyID<std::less>());
1308 
1309  // 6. build subkey ID index:
1310  std::vector<Subkey> subkeys;
1311  subkeys.reserve(sorted.size());
1312  for (const Key &key : std::as_const(sorted)) {
1313  const auto keySubkeys{key.subkeys()};
1314  for (const Subkey &subkey : keySubkeys) {
1315  subkeys.push_back(subkey);
1316  }
1317  }
1318 
1319  // 6a sort by key id:
1320  std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyID<std::less>());
1321 
1322  // 6b. insert into subkey ID index:
1323  std::vector<Subkey> by_subkeyid;
1324  by_subkeyid.reserve(subkeys.size() + d->by.subkeyid.size());
1325  std::merge(subkeys.begin(), subkeys.end(), d->by.subkeyid.begin(), d->by.subkeyid.end(), std::back_inserter(by_subkeyid), _detail::ByKeyID<std::less>());
1326 
1327  // 6c. sort by key grip
1328  std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyGrip<std::less>());
1329 
1330  // 6d. insert into subkey keygrip index:
1331  std::vector<Subkey> by_keygrip;
1332  by_keygrip.reserve(subkeys.size() + d->by.keygrip.size());
1333  std::merge(subkeys.begin(), subkeys.end(), d->by.keygrip.begin(), d->by.keygrip.end(), std::back_inserter(by_keygrip), _detail::ByKeyGrip<std::less>());
1334 
1335  // now commit (well, we already removed keys...)
1336  by_fpr.swap(d->by.fpr);
1337  by_keyid.swap(d->by.keyid);
1338  by_shortkeyid.swap(d->by.shortkeyid);
1339  by_email.swap(d->by.email);
1340  by_subkeyid.swap(d->by.subkeyid);
1341  by_keygrip.swap(d->by.keygrip);
1342  by_chainid.swap(d->by.chainid);
1343 
1344  for (const Key &key : std::as_const(sorted)) {
1345  d->m_pgpOnly &= key.protocol() == GpgME::OpenPGP;
1346  }
1347 
1348  Q_EMIT keysMayHaveChanged();
1349 }
1350 
1351 void KeyCache::clear()
1352 {
1353  d->by = Private::By();
1354 }
1355 
1356 //
1357 //
1358 // RefreshKeysJob
1359 //
1360 //
1361 
1362 class KeyCache::RefreshKeysJob::Private
1363 {
1364  RefreshKeysJob *const q;
1365 
1366 public:
1367  Private(KeyCache *cache, RefreshKeysJob *qq);
1368  void doStart();
1369  Error startKeyListing(GpgME::Protocol protocol);
1370  void listAllKeysJobDone(const KeyListResult &res, const std::vector<Key> &nextKeys)
1371  {
1372  std::vector<Key> keys;
1373  keys.reserve(m_keys.size() + nextKeys.size());
1374  if (m_keys.empty()) {
1375  keys = nextKeys;
1376  } else {
1377  std::merge(m_keys.begin(), m_keys.end(), nextKeys.begin(), nextKeys.end(), std::back_inserter(keys), _detail::ByFingerprint<std::less>());
1378  }
1379  m_keys.swap(keys);
1380  jobDone(res);
1381  }
1382  void emitDone(const KeyListResult &result);
1383  void updateKeyCache();
1384 
1385  QPointer<KeyCache> m_cache;
1386  QList<QGpgME::ListAllKeysJob *> m_jobsPending;
1387  std::vector<Key> m_keys;
1388  KeyListResult m_mergedResult;
1389  bool m_canceled;
1390 
1391 private:
1392  void jobDone(const KeyListResult &res);
1393 };
1394 
1395 KeyCache::RefreshKeysJob::Private::Private(KeyCache *cache, RefreshKeysJob *qq)
1396  : q(qq)
1397  , m_cache(cache)
1398  , m_canceled(false)
1399 {
1400  Q_ASSERT(m_cache);
1401 }
1402 
1403 void KeyCache::RefreshKeysJob::Private::jobDone(const KeyListResult &result)
1404 {
1405  if (m_canceled) {
1406  q->deleteLater();
1407  return;
1408  }
1409 
1410  QObject *const sender = q->sender();
1411  if (sender) {
1412  sender->disconnect(q);
1413  }
1414  Q_ASSERT(m_jobsPending.size() > 0);
1415  m_jobsPending.removeOne(qobject_cast<QGpgME::ListAllKeysJob *>(sender));
1416  m_mergedResult.mergeWith(result);
1417  if (m_jobsPending.size() > 0) {
1418  return;
1419  }
1420  updateKeyCache();
1421  emitDone(m_mergedResult);
1422 }
1423 
1424 void KeyCache::RefreshKeysJob::Private::emitDone(const KeyListResult &res)
1425 {
1426  q->deleteLater();
1427  Q_EMIT q->done(res);
1428 }
1429 
1430 KeyCache::RefreshKeysJob::RefreshKeysJob(KeyCache *cache, QObject *parent)
1431  : QObject(parent)
1432  , d(new Private(cache, this))
1433 {
1434 }
1435 
1436 KeyCache::RefreshKeysJob::~RefreshKeysJob()
1437 {
1438  delete d;
1439 }
1440 
1442 {
1443  QTimer::singleShot(0, this, [this]() {
1444  d->doStart();
1445  });
1446 }
1447 
1448 void KeyCache::RefreshKeysJob::cancel()
1449 {
1450  d->m_canceled = true;
1451  std::for_each(d->m_jobsPending.begin(), d->m_jobsPending.end(), std::mem_fn(&QGpgME::ListAllKeysJob::slotCancel));
1452  Q_EMIT canceled();
1453 }
1454 
1455 void KeyCache::RefreshKeysJob::Private::doStart()
1456 {
1457  if (m_canceled) {
1458  q->deleteLater();
1459  return;
1460  }
1461 
1462  Q_ASSERT(m_jobsPending.size() == 0);
1463  m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::OpenPGP)));
1464  m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::CMS)));
1465 
1466  if (m_jobsPending.size() != 0) {
1467  return;
1468  }
1469 
1470  const bool hasError = m_mergedResult.error() || m_mergedResult.error().isCanceled();
1471  emitDone(hasError ? m_mergedResult : KeyListResult(Error(GPG_ERR_UNSUPPORTED_OPERATION)));
1472 }
1473 
1474 void KeyCache::RefreshKeysJob::Private::updateKeyCache()
1475 {
1476  if (!m_cache || m_canceled) {
1477  q->deleteLater();
1478  return;
1479  }
1480 
1481  std::vector<Key> cachedKeys = m_cache->initialized() ? m_cache->keys() : std::vector<Key>();
1482  std::sort(cachedKeys.begin(), cachedKeys.end(), _detail::ByFingerprint<std::less>());
1483  std::vector<Key> keysToRemove;
1484  std::set_difference(cachedKeys.begin(),
1485  cachedKeys.end(),
1486  m_keys.begin(),
1487  m_keys.end(),
1488  std::back_inserter(keysToRemove),
1489  _detail::ByFingerprint<std::less>());
1490  m_cache->remove(keysToRemove);
1491  m_cache->refresh(m_keys);
1492 }
1493 
1494 Error KeyCache::RefreshKeysJob::Private::startKeyListing(GpgME::Protocol proto)
1495 {
1496  const auto *const protocol = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
1497  if (!protocol) {
1498  return Error();
1499  }
1500  QGpgME::ListAllKeysJob *const job = protocol->listAllKeysJob(/*includeSigs*/ false, /*validate*/ true);
1501  if (!job) {
1502  return Error();
1503  }
1504  if (!m_cache->initialized()) {
1505  // avoid delays during the initial key listing
1506  job->setOptions(QGpgME::ListAllKeysJob::DisableAutomaticTrustDatabaseCheck);
1507  }
1508 
1509 #if 0
1510  aheinecke: 2017.01.12:
1511 
1512  For unknown reasons the new style connect fails at runtime
1513  over library borders into QGpgME from the GpgME repo
1514  when cross compiled for Windows and default arguments
1515  are used in the Signal.
1516 
1517  This was tested with gcc 4.9 (Mingw 3.0.2) and we could not
1518  find an explanation for this. So until this is fixed or we understand
1519  the problem we need to use the old style connect for QGpgME signals.
1520 
1521  The new style connect of the canceled signal right below
1522  works fine.
1523 
1524  connect(job, &QGpgME::ListAllKeysJob::result,
1525  q, [this](const GpgME::KeyListResult &res, const std::vector<GpgME::Key> &keys) {
1526  listAllKeysJobDone(res, keys);
1527  });
1528 #endif
1529  connect(job, SIGNAL(result(GpgME::KeyListResult, std::vector<GpgME::Key>)), q, SLOT(listAllKeysJobDone(GpgME::KeyListResult, std::vector<GpgME::Key>)));
1530 
1531  connect(q, &RefreshKeysJob::canceled, job, &QGpgME::Job::slotCancel);
1532 
1533  // Only do this for initialized keycaches to avoid huge waits for
1534  // signature notations during initial keylisting.
1535  if (proto == GpgME::OpenPGP && m_cache->remarksEnabled() && m_cache->initialized()) {
1536  auto ctx = QGpgME::Job::context(job);
1537  if (ctx) {
1538  ctx->addKeyListMode(KeyListMode::Signatures | KeyListMode::SignatureNotations);
1539  }
1540  }
1541 
1542  const Error error = job->start(true);
1543 
1544  if (!error && !error.isCanceled()) {
1545  m_jobsPending.push_back(job);
1546  }
1547  return error;
1548 }
1549 
1550 bool KeyCache::initialized() const
1551 {
1552  return d->m_initalized;
1553 }
1554 
1555 void KeyCache::Private::ensureCachePopulated() const
1556 {
1557  if (!m_initalized) {
1558  q->startKeyListing();
1559  QEventLoop loop;
1560  loop.connect(q, &KeyCache::keyListingDone, &loop, &QEventLoop::quit);
1561  qCDebug(LIBKLEO_LOG) << "Waiting for keycache.";
1562  loop.exec();
1563  qCDebug(LIBKLEO_LOG) << "Keycache available.";
1564  }
1565 }
1566 
1567 bool KeyCache::pgpOnly() const
1568 {
1569  return d->m_pgpOnly;
1570 }
1571 
1572 static bool keyIsOk(const Key &k)
1573 {
1574  return !k.isExpired() && !k.isRevoked() && !k.isInvalid() && !k.isDisabled();
1575 }
1576 
1577 static bool uidIsOk(const UserID &uid)
1578 {
1579  return keyIsOk(uid.parent()) && !uid.isRevoked() && !uid.isInvalid();
1580 }
1581 
1582 static bool subkeyIsOk(const Subkey &s)
1583 {
1584  return !s.isRevoked() && !s.isInvalid() && !s.isDisabled();
1585 }
1586 
1587 namespace
1588 {
1589 time_t creationTimeOfNewestSuitableSubKey(const Key &key, KeyCache::KeyUsage usage)
1590 {
1591  time_t creationTime = 0;
1592  for (const Subkey &s : key.subkeys()) {
1593  if (!subkeyIsOk(s)) {
1594  continue;
1595  }
1596  if (usage == KeyCache::KeyUsage::Sign && !s.canSign()) {
1597  continue;
1598  }
1599  if (usage == KeyCache::KeyUsage::Encrypt && !s.canEncrypt()) {
1600  continue;
1601  }
1602  if (s.creationTime() > creationTime) {
1603  creationTime = s.creationTime();
1604  }
1605  }
1606  return creationTime;
1607 }
1608 
1609 struct BestMatch {
1610  Key key;
1611  UserID uid;
1612  time_t creationTime = 0;
1613 };
1614 }
1615 
1616 GpgME::Key KeyCache::findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const
1617 {
1618  d->ensureCachePopulated();
1619  if (!addr) {
1620  return {};
1621  }
1622 
1623  // support lookup of email addresses enclosed in angle brackets
1624  QByteArray address(addr);
1625  if (address.size() > 1 && address[0] == '<' && address[address.size() - 1] == '>') {
1626  address = address.mid(1, address.size() - 2);
1627  }
1628  address = address.toLower();
1629 
1630  BestMatch best;
1631  for (const Key &k : findByEMailAddress(address.constData())) {
1632  if (proto != Protocol::UnknownProtocol && k.protocol() != proto) {
1633  continue;
1634  }
1635  if (usage == KeyUsage::Encrypt && !keyHasEncrypt(k)) {
1636  continue;
1637  }
1638  if (usage == KeyUsage::Sign && (!keyHasSign(k) || !k.hasSecret())) {
1639  continue;
1640  }
1641  const time_t creationTime = creationTimeOfNewestSuitableSubKey(k, usage);
1642  if (creationTime == 0) {
1643  // key does not have a suitable (and usable) subkey
1644  continue;
1645  }
1646  for (const UserID &u : k.userIDs()) {
1647  if (QByteArray::fromStdString(u.addrSpec()).toLower() != address) {
1648  // user ID does not match the given email address
1649  continue;
1650  }
1651  if (best.uid.isNull()) {
1652  // we have found our first candidate
1653  best = {k, u, creationTime};
1654  } else if (!uidIsOk(best.uid) && uidIsOk(u)) {
1655  // validity of the new key is better
1656  best = {k, u, creationTime};
1657  } else if (!k.isExpired() && best.uid.validity() < u.validity()) {
1658  // validity of the new key is better
1659  best = {k, u, creationTime};
1660  } else if (best.key.isExpired() && !k.isExpired()) {
1661  // validity of the new key is better
1662  best = {k, u, creationTime};
1663  } else if (best.uid.validity() == u.validity() && uidIsOk(u) && best.creationTime < creationTime) {
1664  // both keys/user IDs have same validity, but the new key is newer
1665  best = {k, u, creationTime};
1666  }
1667  }
1668  }
1669 
1670  return best.key;
1671 }
1672 
1673 namespace
1674 {
1675 template<typename T>
1676 bool allKeysAllowUsage(const T &keys, KeyCache::KeyUsage usage)
1677 {
1678  switch (usage) {
1679  case KeyCache::KeyUsage::AnyUsage:
1680  return true;
1681  case KeyCache::KeyUsage::Sign:
1682  return std::all_of(std::begin(keys),
1683  std::end(keys),
1684 #if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
1685  std::mem_fn(&Key::hasSign)
1686 #else
1687  Kleo::keyHasSign
1688 #endif
1689  );
1690  case KeyCache::KeyUsage::Encrypt:
1691  return std::all_of(std::begin(keys),
1692  std::end(keys),
1693 #if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
1694  std::mem_fn(&Key::hasEncrypt)
1695 #else
1696  Kleo::keyHasEncrypt
1697 #endif
1698  );
1699  case KeyCache::KeyUsage::Certify:
1700  return std::all_of(std::begin(keys),
1701  std::end(keys),
1702 #if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
1703  std::mem_fn(&Key::hasCertify)
1704 #else
1705  Kleo::keyHasCertify
1706 #endif
1707  );
1708  case KeyCache::KeyUsage::Authenticate:
1709  return std::all_of(std::begin(keys),
1710  std::end(keys),
1711 #if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
1712  std::mem_fn(&Key::hasAuthenticate)
1713 #else
1714  Kleo::keyHasAuthenticate
1715 #endif
1716  );
1717  }
1718  qCDebug(LIBKLEO_LOG) << __func__ << "called with invalid usage" << int(usage);
1719  return false;
1720 }
1721 }
1722 
1723 KeyGroup KeyCache::findGroup(const QString &name, Protocol protocol, KeyUsage usage) const
1724 {
1725  d->ensureCachePopulated();
1726 
1727  Q_ASSERT(usage == KeyUsage::Sign || usage == KeyUsage::Encrypt);
1728  for (const auto &group : std::as_const(d->m_groups)) {
1729  if (group.name() == name) {
1730  const KeyGroup::Keys &keys = group.keys();
1731  if (allKeysAllowUsage(keys, usage) && (protocol == UnknownProtocol || allKeysHaveProtocol(keys, protocol))) {
1732  return group;
1733  }
1734  }
1735  }
1736 
1737  return {};
1738 }
1739 
1740 std::vector<Key> KeyCache::getGroupKeys(const QString &groupName) const
1741 {
1742  std::vector<Key> result;
1743  for (const KeyGroup &g : std::as_const(d->m_groups)) {
1744  if (g.name() == groupName) {
1745  const KeyGroup::Keys &keys = g.keys();
1746  std::copy(keys.cbegin(), keys.cend(), std::back_inserter(result));
1747  }
1748  }
1749  _detail::sort_by_fpr(result);
1750  _detail::remove_duplicates_by_fpr(result);
1751  return result;
1752 }
1753 
1754 void KeyCache::setKeys(const std::vector<GpgME::Key> &keys)
1755 {
1756  // disable regular key listing and cancel running key listing
1757  setRefreshInterval(0);
1758  cancelKeyListing();
1759  clear();
1760  insert(keys);
1761  d->m_initalized = true;
1762  Q_EMIT keyListingDone(KeyListResult());
1763 }
1764 
1765 void KeyCache::setGroups(const std::vector<KeyGroup> &groups)
1766 {
1767  Q_ASSERT(d->m_initalized && "Call setKeys() before setting groups");
1768  d->m_groups = groups;
1769  Q_EMIT keysMayHaveChanged();
1770 }
1771 
1772 #include "moc_keycache.cpp"
1773 #include "moc_keycache_p.cpp"
DN parser and reorderer.
Definition: dn.h:26
QByteArray toLower() const const
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
const QList< QKeySequence > & reload()
Q_SCRIPTABLE Q_NOREPLY void start()
QObject * sender() const const
bool insert(Part *part, qint64 *insertId=nullptr)
int exec(QEventLoop::ProcessEventsFlags flags)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QMap::const_iterator cbegin() const const
QByteArray fromStdString(const std::string &str)
void quit()
QMap::const_iterator cend() const const
void timeout()
bool remove(const QString &column, const QVariant &value)
QueuedConnection
PostalAddress address(const QVariant &location)
QAction * find(const QObject *recvr, const char *slot, QObject *parent)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QAction * clear(const QObject *recvr, const char *slot, QObject *parent)
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
void update(Part *part, const QByteArray &data, qint64 dataSize)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 03:56:14 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.