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 ACCEPT(hasSign);
861 REJECT(isRevoked);
862 REJECT(isExpired);
863 REJECT(isDisabled);
864 REJECT(isInvalid);
865 return true;
866#undef DO
867 }
868};
869
870#define DO(op, meth, meth2) \
871 if (op key.meth()) { \
872 } else { \
873 qDebug("rejecting for encrypting: %s: %s", #meth2, key.primaryFingerprint()); \
874 return false; \
875 }
876struct ready_for_encryption {
877 bool operator()(const Key &key) const
878 {
879 ACCEPT(hasEncrypt);
880 REJECT(isRevoked);
881 REJECT(isExpired);
882 REJECT(isDisabled);
883 REJECT(isInvalid);
884 return true;
885 }
886#undef DO
887#undef ACCEPT
888#undef REJECT
889};
890}
891
892std::vector<Key> KeyCache::Private::find_mailbox(const QString &email, bool sign) const
893{
894 if (email.isEmpty()) {
895 return std::vector<Key>();
896 }
897
898 const auto pair = find_email(email.toUtf8().constData());
899 std::vector<Key> result;
900 result.reserve(std::distance(pair.first, pair.second));
901 if (sign) {
902 kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_signing());
903 } else {
904 kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_encryption());
905 }
906
907 return result;
908}
909
910std::vector<Key> KeyCache::findSubjects(const GpgME::Key &key, Options options) const
911{
912 if (key.isNull()) {
913 return {};
914 }
915
916 return findSubjects(std::vector<Key>(1, key), options);
917}
918
919std::vector<Key> KeyCache::findSubjects(const std::vector<Key> &keys, Options options) const
920{
921 std::vector<Key> result;
922
923 if (keys.empty()) {
924 return result;
925 }
926
927 // get the immediate subjects
928 for (const auto &key : keys) {
929 const auto firstAndLastSubject = d->find_subjects(key.primaryFingerprint());
930 result.insert(result.end(), firstAndLastSubject.first, firstAndLastSubject.second);
931 }
932 // remove duplicates
933 _detail::sort_by_fpr(result);
934 _detail::remove_duplicates_by_fpr(result);
935
936 if (options & RecursiveSearch) {
937 for (std::vector<Key> furtherSubjects = findSubjects(result, NoOption); //
938 !furtherSubjects.empty();
939 furtherSubjects = findSubjects(furtherSubjects, NoOption)) {
940 std::vector<Key> combined;
941 combined.reserve(result.size() + furtherSubjects.size());
942 std::merge(result.begin(),
943 result.end(),
944 furtherSubjects.begin(),
945 furtherSubjects.end(),
946 std::back_inserter(combined),
947 _detail::ByFingerprint<std::less>());
948 _detail::remove_duplicates_by_fpr(combined);
949 if (result.size() == combined.size()) {
950 // no new subjects were found; this happens if a chain has a cycle
951 break;
952 }
953 result.swap(combined);
954 }
955 }
956
957 return result;
958}
959
960std::vector<Key> KeyCache::findIssuers(const Key &key, Options options) const
961{
962 std::vector<Key> result;
963
964 if (key.isNull()) {
965 return result;
966 }
967
968 if (options & IncludeSubject) {
969 result.push_back(key);
970 }
971
972 if (key.isRoot()) {
973 return result;
974 }
975
976 Key issuer = findByFingerprint(key.chainID());
977
978 if (issuer.isNull()) {
979 return result;
980 }
981
982 result.push_back(issuer);
983
984 if (!(options & RecursiveSearch)) {
985 return result;
986 }
987
988 while (!issuer.isRoot()) {
989 issuer = findByFingerprint(result.back().chainID());
990 if (issuer.isNull()) {
991 break;
992 }
993 const bool chainAlreadyContainsIssuer = Kleo::contains_if(result, [issuer](const auto &key) {
994 return _detail::ByFingerprint<std::equal_to>()(issuer, key);
995 });
996 // we also add the issuer if the chain already contains it, so that
997 // the user can spot the cycle
998 result.push_back(issuer);
999 if (chainAlreadyContainsIssuer) {
1000 // break on cycle in chain
1001 break;
1002 }
1003 }
1004
1005 return result;
1006}
1007
1008static std::string email(const UserID &uid)
1009{
1010 // Prefer the gnupg normalized one
1011 const std::string addr = uid.addrSpec();
1012 if (!addr.empty()) {
1013 return addr;
1014 }
1015 const std::string email = uid.email();
1016 if (email.empty()) {
1017 return DN(uid.id())[QStringLiteral("EMAIL")].trimmed().toUtf8().constData();
1018 }
1019 if (email[0] == '<' && email[email.size() - 1] == '>') {
1020 return email.substr(1, email.size() - 2);
1021 } else {
1022 return email;
1023 }
1024}
1025
1026static std::vector<std::string> emails(const Key &key)
1027{
1028 std::vector<std::string> emails;
1029 const auto userIDs = key.userIDs();
1030 for (const UserID &uid : userIDs) {
1031 const std::string e = email(uid);
1032 if (!e.empty()) {
1033 emails.push_back(e);
1034 }
1035 }
1036 std::sort(emails.begin(), emails.end(), ByEMail<std::less>());
1037 emails.erase(std::unique(emails.begin(), emails.end(), ByEMail<std::equal_to>()), emails.end());
1038 return emails;
1039}
1040
1041void KeyCache::remove(const Key &key)
1042{
1043 if (key.isNull()) {
1044 return;
1045 }
1046
1047 const char *fpr = key.primaryFingerprint();
1048 if (!fpr) {
1049 return;
1050 }
1051
1052 {
1053 const auto range = std::equal_range(d->by.fpr.begin(), d->by.fpr.end(), fpr, _detail::ByFingerprint<std::less>());
1054 d->by.fpr.erase(range.first, range.second);
1055 }
1056
1057 if (const char *keyid = key.keyID()) {
1058 const auto range = std::equal_range(d->by.keyid.begin(), d->by.keyid.end(), keyid, _detail::ByKeyID<std::less>());
1059 const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) {
1060 return _detail::ByFingerprint<std::equal_to>()(fpr, key);
1061 });
1062 d->by.keyid.erase(it, range.second);
1063 }
1064
1065 if (const char *chainid = key.chainID()) {
1066 const auto range = std::equal_range(d->by.chainid.begin(), d->by.chainid.end(), chainid, _detail::ByChainID<std::less>());
1067 const auto range2 = std::equal_range(range.first, range.second, fpr, _detail::ByFingerprint<std::less>());
1068 d->by.chainid.erase(range2.first, range2.second);
1069 }
1070
1071 const auto emailsKey{emails(key)};
1072 for (const std::string &email : emailsKey) {
1073 const auto range = std::equal_range(d->by.email.begin(), d->by.email.end(), email, ByEMail<std::less>());
1074 const auto it = std::remove_if(range.first, range.second, [fpr](const std::pair<std::string, Key> &pair) {
1075 return qstricmp(fpr, pair.second.primaryFingerprint()) == 0;
1076 });
1077 d->by.email.erase(it, range.second);
1078 }
1079
1080 const auto keySubKeys{key.subkeys()};
1081 for (const Subkey &subkey : keySubKeys) {
1082 if (const char *subkeyfpr = subkey.fingerprint()) {
1083 const auto range = std::equal_range(d->by.subkeyfpr.begin(), d->by.subkeyfpr.end(), subkeyfpr, _detail::BySubkeyFingerprint<std::less>());
1084 const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) {
1085 return !qstricmp(fpr, subkey.parent().primaryFingerprint());
1086 });
1087 d->by.subkeyfpr.erase(it, range.second);
1088 }
1089 if (const char *keyid = subkey.keyID()) {
1090 const auto range = std::equal_range(d->by.subkeyid.begin(), d->by.subkeyid.end(), keyid, _detail::ByKeyID<std::less>());
1091 const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) {
1092 return !qstricmp(fpr, subkey.parent().primaryFingerprint());
1093 });
1094 d->by.subkeyid.erase(it, range.second);
1095 }
1096 if (const char *keygrip = subkey.keyGrip()) {
1097 const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), keygrip, _detail::ByKeyGrip<std::less>());
1098 const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) {
1099 return !qstricmp(fpr, subkey.parent().primaryFingerprint());
1100 });
1101 d->by.keygrip.erase(it, range.second);
1102 }
1103 }
1104}
1105
1106void KeyCache::remove(const std::vector<Key> &keys)
1107{
1108 for (const Key &key : keys) {
1109 remove(key);
1110 }
1111}
1112
1113const std::vector<GpgME::Key> &KeyCache::keys() const
1114{
1115 d->ensureCachePopulated();
1116 return d->by.fpr;
1117}
1118
1119std::vector<Key> KeyCache::secretKeys() const
1120{
1121 std::vector<Key> keys = this->keys();
1122 keys.erase(std::remove_if(keys.begin(),
1123 keys.end(),
1124 [](const Key &key) {
1125 return !key.hasSecret();
1126 }),
1127 keys.end());
1128 return keys;
1129}
1130
1131KeyGroup KeyCache::group(const QString &id) const
1132{
1133 KeyGroup result{};
1134 const auto it = std::find_if(std::cbegin(d->m_groups), std::cend(d->m_groups), [id](const auto &g) {
1135 return g.id() == id;
1136 });
1137 if (it != std::cend(d->m_groups)) {
1138 result = *it;
1139 }
1140 return result;
1141}
1142
1143std::vector<KeyGroup> KeyCache::groups() const
1144{
1145 d->ensureCachePopulated();
1146 return d->m_groups;
1147}
1148
1149std::vector<KeyGroup> KeyCache::configurableGroups() const
1150{
1151 std::vector<KeyGroup> groups;
1152 groups.reserve(d->m_groups.size());
1153 std::copy_if(d->m_groups.cbegin(), d->m_groups.cend(), std::back_inserter(groups), [](const KeyGroup &group) {
1154 return group.source() == KeyGroup::ApplicationConfig;
1155 });
1156 return groups;
1157}
1158
1159namespace
1160{
1161bool compareById(const KeyGroup &lhs, const KeyGroup &rhs)
1162{
1163 return lhs.id() < rhs.id();
1164}
1165
1166std::vector<KeyGroup> sortedById(std::vector<KeyGroup> groups)
1167{
1168 std::sort(groups.begin(), groups.end(), &compareById);
1169 return groups;
1170}
1171}
1172
1173void KeyCache::saveConfigurableGroups(const std::vector<KeyGroup> &groups)
1174{
1175 const std::vector<KeyGroup> oldGroups = sortedById(configurableGroups());
1176 const std::vector<KeyGroup> newGroups = sortedById(groups);
1177
1178 {
1179 std::vector<KeyGroup> removedGroups;
1180 std::set_difference(oldGroups.begin(), oldGroups.end(), newGroups.begin(), newGroups.end(), std::back_inserter(removedGroups), &compareById);
1181 for (const auto &group : std::as_const(removedGroups)) {
1182 qCDebug(LIBKLEO_LOG) << "Removing group" << group;
1183 d->remove(group);
1184 }
1185 }
1186 {
1187 std::vector<KeyGroup> updatedGroups;
1188 std::set_intersection(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(updatedGroups), &compareById);
1189 for (const auto &group : std::as_const(updatedGroups)) {
1190 qCDebug(LIBKLEO_LOG) << "Updating group" << group;
1191 d->update(group);
1192 }
1193 }
1194 {
1195 std::vector<KeyGroup> addedGroups;
1196 std::set_difference(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(addedGroups), &compareById);
1197 for (const auto &group : std::as_const(addedGroups)) {
1198 qCDebug(LIBKLEO_LOG) << "Adding group" << group;
1199 d->insert(group);
1200 }
1201 }
1202
1203 Q_EMIT keysMayHaveChanged();
1204}
1205
1206bool KeyCache::insert(const KeyGroup &group)
1207{
1208 if (!d->insert(group)) {
1209 return false;
1210 }
1211
1212 Q_EMIT keysMayHaveChanged();
1213
1214 return true;
1215}
1216
1217bool KeyCache::update(const KeyGroup &group)
1218{
1219 if (!d->update(group)) {
1220 return false;
1221 }
1222
1223 Q_EMIT keysMayHaveChanged();
1224
1225 return true;
1226}
1227
1228bool KeyCache::remove(const KeyGroup &group)
1229{
1230 if (!d->remove(group)) {
1231 return false;
1232 }
1233
1234 Q_EMIT keysMayHaveChanged();
1235
1236 return true;
1237}
1238
1239void KeyCache::refresh(const std::vector<Key> &keys)
1240{
1241 // make this better...
1242 clear();
1243 insert(keys);
1244}
1245
1246void KeyCache::insert(const Key &key)
1247{
1248 insert(std::vector<Key>(1, key));
1249}
1250
1251namespace
1252{
1253
1254template<template<template<typename T> class Op> class T1, template<template<typename T> class Op> class T2>
1255struct lexicographically {
1256 using result_type = bool;
1257
1258 template<typename U, typename V>
1259 bool operator()(const U &lhs, const V &rhs) const
1260 {
1261 return T1<std::less>()(lhs, rhs) //
1262 || (T1<std::equal_to>()(lhs, rhs) && T2<std::less>()(lhs, rhs));
1263 }
1264};
1265
1266}
1267
1268void KeyCache::insert(const std::vector<Key> &keys)
1269{
1270 // 1. filter out keys with empty fingerprints:
1271 std::vector<Key> sorted;
1272 sorted.reserve(keys.size());
1273 std::copy_if(keys.begin(), keys.end(), std::back_inserter(sorted), [](const Key &key) {
1274 auto fp = key.primaryFingerprint();
1275 return fp && *fp;
1276 });
1277
1278 // this is sub-optimal, but makes implementation from here on much easier
1279 remove(sorted);
1280
1281 // 2. sort by fingerprint:
1282 std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint<std::less>());
1283
1284 // 2a. insert into fpr index:
1285 std::vector<Key> by_fpr;
1286 by_fpr.reserve(sorted.size() + d->by.fpr.size());
1287 std::merge(sorted.begin(), sorted.end(), d->by.fpr.begin(), d->by.fpr.end(), std::back_inserter(by_fpr), _detail::ByFingerprint<std::less>());
1288
1289 // 3. build email index:
1290 std::vector<std::pair<std::string, Key>> pairs;
1291 pairs.reserve(sorted.size());
1292 for (const Key &key : std::as_const(sorted)) {
1293 const std::vector<std::string> emails = ::emails(key);
1294 for (const std::string &e : emails) {
1295 pairs.push_back(std::make_pair(e, key));
1296 }
1297 }
1298 std::sort(pairs.begin(), pairs.end(), ByEMail<std::less>());
1299
1300 // 3a. insert into email index:
1301 std::vector<std::pair<std::string, Key>> by_email;
1302 by_email.reserve(pairs.size() + d->by.email.size());
1303 std::merge(pairs.begin(), pairs.end(), d->by.email.begin(), d->by.email.end(), std::back_inserter(by_email), ByEMail<std::less>());
1304
1305 // 3.5: stable-sort by chain-id (effectively lexicographically<ByChainID,ByFingerprint>)
1306 std::stable_sort(sorted.begin(), sorted.end(), _detail::ByChainID<std::less>());
1307
1308 // 3.5a: insert into chain-id index:
1309 std::vector<Key> nonroot;
1310 nonroot.reserve(sorted.size());
1311 std::vector<Key> by_chainid;
1312 by_chainid.reserve(sorted.size() + d->by.chainid.size());
1313 std::copy_if(sorted.cbegin(), sorted.cend(), std::back_inserter(nonroot), [](const Key &key) {
1314 return !key.isRoot();
1315 });
1316 std::merge(nonroot.cbegin(),
1317 nonroot.cend(),
1318 d->by.chainid.cbegin(),
1319 d->by.chainid.cend(),
1320 std::back_inserter(by_chainid),
1321 lexicographically<_detail::ByChainID, _detail::ByFingerprint>());
1322
1323 // 4. sort by key id:
1324 std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
1325
1326 // 4a. insert into keyid index:
1327 std::vector<Key> by_keyid;
1328 by_keyid.reserve(sorted.size() + d->by.keyid.size());
1329 std::merge(sorted.begin(), sorted.end(), d->by.keyid.begin(), d->by.keyid.end(), std::back_inserter(by_keyid), _detail::ByKeyID<std::less>());
1330
1331 // 5. has been removed
1332
1333 // 6. build subkey ID index:
1334 std::vector<Subkey> subkeys;
1335 subkeys.reserve(sorted.size());
1336 for (const Key &key : std::as_const(sorted)) {
1337 const auto keySubkeys{key.subkeys()};
1338 for (const Subkey &subkey : keySubkeys) {
1339 if (subkey.canRenc()) {
1340 continue;
1341 }
1342 subkeys.push_back(subkey);
1343 }
1344 }
1345
1346 // 6a sort by key id:
1347 std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyID<std::less>());
1348
1349 // 6b. insert into subkey ID index:
1350 std::vector<Subkey> by_subkeyid;
1351 by_subkeyid.reserve(subkeys.size() + d->by.subkeyid.size());
1352 std::merge(subkeys.begin(), subkeys.end(), d->by.subkeyid.begin(), d->by.subkeyid.end(), std::back_inserter(by_subkeyid), _detail::ByKeyID<std::less>());
1353
1354 // 6c. sort by key grip
1355 std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyGrip<std::less>());
1356
1357 // 6d. insert into subkey keygrip index:
1358 std::vector<Subkey> by_keygrip;
1359 by_keygrip.reserve(subkeys.size() + d->by.keygrip.size());
1360 std::merge(subkeys.begin(), subkeys.end(), d->by.keygrip.begin(), d->by.keygrip.end(), std::back_inserter(by_keygrip), _detail::ByKeyGrip<std::less>());
1361
1362 // 6e sort by fingerprint:
1363 std::sort(subkeys.begin(), subkeys.end(), _detail::BySubkeyFingerprint<std::less>());
1364
1365 // 6f. insert into subkey fingerprint index:
1366 std::vector<Subkey> by_subkeyfpr;
1367 by_subkeyfpr.reserve(subkeys.size() + d->by.subkeyfpr.size());
1368 std::merge(subkeys.begin(),
1369 subkeys.end(),
1370 d->by.subkeyfpr.begin(),
1371 d->by.subkeyfpr.end(),
1372 std::back_inserter(by_subkeyfpr),
1373 _detail::BySubkeyFingerprint<std::less>());
1374
1375 // now commit (well, we already removed keys...)
1376 by_fpr.swap(d->by.fpr);
1377 by_keyid.swap(d->by.keyid);
1378 by_email.swap(d->by.email);
1379 by_subkeyfpr.swap(d->by.subkeyfpr);
1380 by_subkeyid.swap(d->by.subkeyid);
1381 by_keygrip.swap(d->by.keygrip);
1382 by_chainid.swap(d->by.chainid);
1383
1384 for (const Key &key : std::as_const(sorted)) {
1385 d->m_pgpOnly &= key.protocol() == GpgME::OpenPGP;
1386 }
1387
1388 d->m_cards.clear();
1389 for (const auto &key : keys) {
1390 for (const auto &subkey : key.subkeys()) {
1391 if (!subkey.isSecret() || !d->m_cards[QByteArray(subkey.keyGrip())].empty()) {
1392 continue;
1393 }
1394 const auto data = readSecretKeyFile(QString::fromLatin1(subkey.keyGrip()));
1395 for (const auto &line : data) {
1396 if (line.startsWith(QByteArrayLiteral("Token"))) {
1397 const auto split = line.split(' ');
1398 if (split.size() > 2) {
1399 const auto keyRef = QString::fromUtf8(split[2]).trimmed();
1400 d->m_cards[QByteArray(subkey.keyGrip())].push_back(CardKeyStorageInfo{
1401 QString::fromUtf8(split[1]),
1402 split.size() > 4 ? QString::fromLatin1(
1403 QString::fromUtf8(split[4]).trimmed().replace(QLatin1Char('+'), QLatin1Char(' ')).toUtf8().percentDecoded())
1404 : QString(),
1405 keyRef,
1406 });
1407 }
1408 }
1409 }
1410 }
1411 }
1412
1413 Q_EMIT keysMayHaveChanged();
1414}
1415
1416void KeyCache::clear()
1417{
1418 d->by = Private::By();
1419}
1420
1421//
1422//
1423// RefreshKeysJob
1424//
1425//
1426
1427class KeyCache::RefreshKeysJob::Private
1428{
1429 RefreshKeysJob *const q;
1430
1431public:
1432 Private(KeyCache *cache, RefreshKeysJob *qq);
1433 void doStart();
1434 Error startKeyListing(GpgME::Protocol protocol);
1435 void listAllKeysJobDone(const KeyListResult &res, const std::vector<Key> &nextKeys)
1436 {
1437 if (!nextKeys.empty()) {
1438 std::vector<Key> keys;
1439 keys.reserve(m_keys.size() + nextKeys.size());
1440 if (m_keys.empty()) {
1441 keys = nextKeys;
1442 } else {
1443 std::merge(m_keys.begin(), m_keys.end(), nextKeys.begin(), nextKeys.end(), std::back_inserter(keys), _detail::ByFingerprint<std::less>());
1444 }
1445 m_keys.swap(keys);
1446 }
1447 jobDone(res);
1448 }
1449 void emitDone(const KeyListResult &result);
1450 void updateKeyCache();
1451
1452 QPointer<KeyCache> m_cache;
1453 QList<QGpgME::ListAllKeysJob *> m_jobsPending;
1454 std::vector<Key> m_keys;
1455 KeyListResult m_mergedResult;
1456 bool m_canceled;
1457
1458private:
1459 void jobDone(const KeyListResult &res);
1460};
1461
1462KeyCache::RefreshKeysJob::Private::Private(KeyCache *cache, RefreshKeysJob *qq)
1463 : q(qq)
1464 , m_cache(cache)
1465 , m_canceled(false)
1466{
1467 Q_ASSERT(m_cache);
1468}
1469
1470void KeyCache::RefreshKeysJob::Private::jobDone(const KeyListResult &result)
1471{
1472 if (m_canceled) {
1473 q->deleteLater();
1474 return;
1475 }
1476
1477 QObject *const sender = q->sender();
1478 if (sender) {
1479 sender->disconnect(q);
1480 }
1481 Q_ASSERT(!m_jobsPending.empty());
1482 m_jobsPending.removeOne(qobject_cast<QGpgME::ListAllKeysJob *>(sender));
1483 m_mergedResult.mergeWith(result);
1484 if (!m_jobsPending.empty()) {
1485 return;
1486 }
1487 updateKeyCache();
1488 emitDone(m_mergedResult);
1489}
1490
1491void KeyCache::RefreshKeysJob::Private::emitDone(const KeyListResult &res)
1492{
1493 q->deleteLater();
1494 Q_EMIT q->done(res);
1495}
1496
1497KeyCache::RefreshKeysJob::RefreshKeysJob(KeyCache *cache, QObject *parent)
1498 : QObject(parent)
1499 , d(new Private(cache, this))
1500{
1501}
1502
1503KeyCache::RefreshKeysJob::~RefreshKeysJob()
1504{
1505 delete d;
1506}
1507
1508void KeyCache::RefreshKeysJob::start()
1509{
1510 qCDebug(LIBKLEO_LOG) << "KeyCache::RefreshKeysJob" << __func__;
1511 QTimer::singleShot(0, this, [this]() {
1512 d->doStart();
1513 });
1514}
1515
1516void KeyCache::RefreshKeysJob::cancel()
1517{
1518 d->m_canceled = true;
1519 std::for_each(d->m_jobsPending.begin(), d->m_jobsPending.end(), std::mem_fn(&QGpgME::ListAllKeysJob::slotCancel));
1520 Q_EMIT canceled();
1521}
1522
1523void KeyCache::RefreshKeysJob::Private::doStart()
1524{
1525 if (m_canceled) {
1526 q->deleteLater();
1527 return;
1528 }
1529
1530 Q_ASSERT(m_jobsPending.empty());
1531 m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::OpenPGP)));
1532 m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::CMS)));
1533
1534 if (!m_jobsPending.empty()) {
1535 return;
1536 }
1537
1538 const bool hasError = m_mergedResult.error() || m_mergedResult.error().isCanceled();
1539 emitDone(hasError ? m_mergedResult : KeyListResult(Error(GPG_ERR_UNSUPPORTED_OPERATION)));
1540}
1541
1542void KeyCache::RefreshKeysJob::Private::updateKeyCache()
1543{
1544 if (!m_cache || m_canceled) {
1545 q->deleteLater();
1546 return;
1547 }
1548
1549 std::vector<Key> cachedKeys = m_cache->initialized() ? m_cache->keys() : std::vector<Key>();
1550 std::sort(cachedKeys.begin(), cachedKeys.end(), _detail::ByFingerprint<std::less>());
1551 std::vector<Key> keysToRemove;
1552 std::set_difference(cachedKeys.begin(),
1553 cachedKeys.end(),
1554 m_keys.begin(),
1555 m_keys.end(),
1556 std::back_inserter(keysToRemove),
1557 _detail::ByFingerprint<std::less>());
1558 m_cache->remove(keysToRemove);
1559 m_cache->refresh(m_keys);
1560}
1561
1562Error KeyCache::RefreshKeysJob::Private::startKeyListing(GpgME::Protocol proto)
1563{
1564 const auto *const protocol = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
1565 if (!protocol) {
1566 return Error();
1567 }
1568 QGpgME::ListAllKeysJob *const job = protocol->listAllKeysJob(/*includeSigs*/ false, /*validate*/ true);
1569 if (!job) {
1570 return Error();
1571 }
1572 if (!m_cache->initialized()) {
1573 // avoid delays during the initial key listing
1574 job->setOptions(QGpgME::ListAllKeysJob::DisableAutomaticTrustDatabaseCheck);
1575 }
1576
1577#if 0
1578 aheinecke: 2017.01.12:
1579
1580 For unknown reasons the new style connect fails at runtime
1581 over library borders into QGpgME from the GpgME repo
1582 when cross compiled for Windows and default arguments
1583 are used in the Signal.
1584
1585 This was tested with gcc 4.9 (Mingw 3.0.2) and we could not
1586 find an explanation for this. So until this is fixed or we understand
1587 the problem we need to use the old style connect for QGpgME signals.
1588
1589 The new style connect of the canceled signal right below
1590 works fine.
1591
1592 connect(job, &QGpgME::ListAllKeysJob::result,
1593 q, [this](const GpgME::KeyListResult &res, const std::vector<GpgME::Key> &keys) {
1594 listAllKeysJobDone(res, keys);
1595 });
1596#endif
1597 connect(job, SIGNAL(result(GpgME::KeyListResult, std::vector<GpgME::Key>)), q, SLOT(listAllKeysJobDone(GpgME::KeyListResult, std::vector<GpgME::Key>)));
1598
1599 connect(q, &RefreshKeysJob::canceled, job, &QGpgME::Job::slotCancel);
1600
1601 // Only do this for initialized keycaches to avoid huge waits for
1602 // signature notations during initial keylisting.
1603 if (proto == GpgME::OpenPGP && m_cache->remarksEnabled() && m_cache->initialized()) {
1604 auto ctx = QGpgME::Job::context(job);
1605 if (ctx) {
1606 ctx->addKeyListMode(KeyListMode::Signatures | KeyListMode::SignatureNotations);
1607 }
1608 }
1609
1610 const Error error = job->start(true);
1611
1612 if (!error && !error.isCanceled()) {
1613 m_jobsPending.push_back(job);
1614 }
1615 return error;
1616}
1617
1618bool KeyCache::initialized() const
1619{
1620 return d->m_initalized;
1621}
1622
1623void KeyCache::Private::ensureCachePopulated() const
1624{
1625 if (!m_initalized) {
1626 q->startKeyListing();
1627 QEventLoop loop;
1628 loop.connect(q, &KeyCache::keyListingDone, &loop, &QEventLoop::quit);
1629 qCDebug(LIBKLEO_LOG) << "Waiting for keycache.";
1630 loop.exec();
1631 qCDebug(LIBKLEO_LOG) << "Keycache available.";
1632 }
1633}
1634
1635bool KeyCache::pgpOnly() const
1636{
1637 return d->m_pgpOnly;
1638}
1639
1640static bool keyIsOk(const Key &k)
1641{
1642 return !k.isExpired() && !k.isRevoked() && !k.isInvalid() && !k.isDisabled();
1643}
1644
1645static bool uidIsOk(const UserID &uid)
1646{
1647 return keyIsOk(uid.parent()) && !uid.isRevoked() && !uid.isInvalid();
1648}
1649
1650static bool subkeyIsOk(const Subkey &s)
1651{
1652 return !s.isRevoked() && !s.isInvalid() && !s.isDisabled();
1653}
1654
1655namespace
1656{
1657time_t creationTimeOfNewestSuitableSubKey(const Key &key, KeyCache::KeyUsage usage)
1658{
1659 time_t creationTime = 0;
1660 for (const Subkey &s : key.subkeys()) {
1661 if (!subkeyIsOk(s)) {
1662 continue;
1663 }
1664 if (usage == KeyCache::KeyUsage::Sign && !s.canSign()) {
1665 continue;
1666 }
1667 if (usage == KeyCache::KeyUsage::Encrypt && !s.canEncrypt()) {
1668 continue;
1669 }
1670 if (s.creationTime() > creationTime) {
1671 creationTime = s.creationTime();
1672 }
1673 }
1674 return creationTime;
1675}
1676
1677struct BestMatch {
1678 Key key;
1679 UserID uid;
1680 time_t creationTime = 0;
1681};
1682}
1683
1684GpgME::Key KeyCache::findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const
1685{
1686 d->ensureCachePopulated();
1687 if (!addr) {
1688 return {};
1689 }
1690
1691 // support lookup of email addresses enclosed in angle brackets
1692 QByteArray address(addr);
1693 if (address.size() > 1 && address[0] == '<' && address[address.size() - 1] == '>') {
1694 address = address.mid(1, address.size() - 2);
1695 }
1696 address = address.toLower();
1697
1698 BestMatch best;
1699 for (const Key &k : findByEMailAddress(address.constData())) {
1700 if (proto != Protocol::UnknownProtocol && k.protocol() != proto) {
1701 continue;
1702 }
1703 if (usage == KeyUsage::Encrypt && !keyHasEncrypt(k)) {
1704 continue;
1705 }
1706 if (usage == KeyUsage::Sign && (!keyHasSign(k) || !k.hasSecret())) {
1707 continue;
1708 }
1709 const time_t creationTime = creationTimeOfNewestSuitableSubKey(k, usage);
1710 if (creationTime == 0) {
1711 // key does not have a suitable (and usable) subkey
1712 continue;
1713 }
1714 for (const UserID &u : k.userIDs()) {
1715 if (QByteArray::fromStdString(u.addrSpec()).toLower() != address) {
1716 // user ID does not match the given email address
1717 continue;
1718 }
1719 if (best.uid.isNull()) {
1720 // we have found our first candidate
1721 best = {k, u, creationTime};
1722 } else if (!uidIsOk(best.uid) && uidIsOk(u)) {
1723 // validity of the new key is better
1724 best = {k, u, creationTime};
1725 } else if (!k.isExpired() && best.uid.validity() < u.validity()) {
1726 // validity of the new key is better
1727 best = {k, u, creationTime};
1728 } else if (best.key.isExpired() && !k.isExpired()) {
1729 // validity of the new key is better
1730 best = {k, u, creationTime};
1731 } else if (best.uid.validity() == u.validity() && uidIsOk(u) && best.creationTime < creationTime) {
1732 // both keys/user IDs have same validity, but the new key is newer
1733 best = {k, u, creationTime};
1734 }
1735 }
1736 }
1737
1738 return best.key;
1739}
1740
1741namespace
1742{
1743template<typename T>
1744bool allKeysAllowUsage(const T &keys, KeyCache::KeyUsage usage)
1745{
1746 switch (usage) {
1747 case KeyCache::KeyUsage::AnyUsage:
1748 return true;
1749 case KeyCache::KeyUsage::Sign:
1750 return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::hasSign));
1751 case KeyCache::KeyUsage::Encrypt:
1752 return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::hasEncrypt));
1753 case KeyCache::KeyUsage::Certify:
1754 return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::hasCertify));
1755 case KeyCache::KeyUsage::Authenticate:
1756 return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::hasAuthenticate));
1757 }
1758 qCDebug(LIBKLEO_LOG) << __func__ << "called with invalid usage" << int(usage);
1759 return false;
1760}
1761}
1762
1763KeyGroup KeyCache::findGroup(const QString &name, Protocol protocol, KeyUsage usage) const
1764{
1765 d->ensureCachePopulated();
1766
1767 Q_ASSERT(usage == KeyUsage::Sign || usage == KeyUsage::Encrypt);
1768 for (const auto &group : std::as_const(d->m_groups)) {
1769 if (group.name() == name) {
1770 const KeyGroup::Keys &keys = group.keys();
1771 if (allKeysAllowUsage(keys, usage) && (protocol == UnknownProtocol || allKeysHaveProtocol(keys, protocol))) {
1772 return group;
1773 }
1774 }
1775 }
1776
1777 return {};
1778}
1779
1780std::vector<Key> KeyCache::getGroupKeys(const QString &groupName) const
1781{
1782 std::vector<Key> result;
1783 for (const KeyGroup &g : std::as_const(d->m_groups)) {
1784 if (g.name() == groupName) {
1785 const KeyGroup::Keys &keys = g.keys();
1786 std::copy(keys.cbegin(), keys.cend(), std::back_inserter(result));
1787 }
1788 }
1789 _detail::sort_by_fpr(result);
1790 _detail::remove_duplicates_by_fpr(result);
1791 return result;
1792}
1793
1794void KeyCache::setKeys(const std::vector<GpgME::Key> &keys)
1795{
1796 // disable regular key listing and cancel running key listing
1797 setRefreshInterval(0);
1798 cancelKeyListing();
1799 clear();
1800 insert(keys);
1801 d->m_initalized = true;
1802 Q_EMIT keyListingDone(KeyListResult());
1803}
1804
1805void KeyCache::setGroups(const std::vector<KeyGroup> &groups)
1806{
1807 Q_ASSERT(d->m_initalized && "Call setKeys() before setting groups");
1808 d->m_groups = groups;
1809 Q_EMIT keysMayHaveChanged();
1810}
1811
1812std::vector<CardKeyStorageInfo> KeyCache::cardsForSubkey(const GpgME::Subkey &subkey) const
1813{
1814 return d->m_cards[QByteArray(subkey.keyGrip())];
1815}
1816
1817#include "moc_keycache.cpp"
1818#include "moc_keycache_p.cpp"
DN parser and reorderer.
Definition dn.h:27
bool insert(Part *part, qint64 *insertId=nullptr)
bool remove(const QString &column, const QVariant &value)
PostalAddress address(const QVariant &location)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
const QList< QKeySequence > & replace()
QByteArray fromStdString(const std::string &str)
QByteArray toLower() const const
int exec(ProcessEventsFlags flags)
void quit()
bool empty() const const
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 Sat Dec 21 2024 16:56:14 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.