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

KDE's Doxygen guidelines are available online.