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

KDE's Doxygen guidelines are available online.