Messagelib

keyresolver.cpp
1/* -*- c++ -*-
2 keyresolver.cpp
3
4 This file is part of libkleopatra, the KDE keymanagement library
5 SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
6
7 Based on kpgp.cpp
8 Copyright (C) 2001,2002 the KPGP authors
9 See file libkdenetwork/AUTHORS.kpgp for details
10
11 SPDX-License-Identifier: GPL-2.0-or-later
12*/
13
14#include "composer/keyresolver.h"
15
16#include "contactpreference/savecontactpreferencejob.h"
17
18#include "utils/kleo_util.h"
19#include <KCursorSaver>
20
21#include <KEmailAddress>
22
23#include <Libkleo/Algorithm>
24#include <Libkleo/Compliance>
25#include <Libkleo/ExpiryChecker>
26#include <Libkleo/KeySelectionDialog>
27
28#include <QGpgME/KeyListJob>
29#include <QGpgME/Protocol>
30
31#include <gpgme++/key.h>
32#include <gpgme++/keylistresult.h>
33
34#include "messagecomposer_debug.h"
35#include <Akonadi/ContactSearchJob>
36#include <KLocalizedString>
37#include <KMessageBox>
38
39#include <MessageCore/AutocryptRecipient>
40#include <MessageCore/AutocryptStorage>
41
42#include <QPointer>
43
44#include <algorithm>
45#include <cassert>
46#include <ctime>
47#include <functional>
48#include <iostream>
49#include <iterator>
50#include <map>
51#include <memory>
52#include <set>
53
54using namespace MessageComposer;
55using namespace Kleo;
56
57//
58// some predicates to be used in STL algorithms:
59//
60
61static inline bool EmptyKeyList(const KeyApprovalDialog::Item &item)
62{
63 return item.keys.empty();
64}
65
66static inline QString ItemDotAddress(const KeyResolver::Item &item)
67{
68 return item.address;
69}
70
71static inline bool ApprovalNeeded(const KeyResolver::Item &item)
72{
73 bool approvalNeeded = item.pref == Kleo::NeverEncrypt || item.keys.empty();
74 if (!approvalNeeded && Kleo::DeVSCompliance::isCompliant()) {
75 approvalNeeded = !Kleo::all_of(item.keys, &Kleo::DeVSCompliance::keyIsCompliant);
76 }
77 return approvalNeeded;
78}
79
80static inline KeyResolver::Item CopyKeysAndEncryptionPreferences(const KeyResolver::Item &oldItem, const KeyApprovalDialog::Item &newItem)
81{
82 return KeyResolver::Item(oldItem.address, newItem.keys, newItem.pref, oldItem.signPref, oldItem.format);
83}
84
85static bool ValidOpenPGPEncryptionKey(const GpgME::Key &key)
86{
87 if (key.protocol() != GpgME::OpenPGP) {
88 return false;
89 }
90 if (key.isRevoked()) {
91 qCWarning(MESSAGECOMPOSER_LOG) << "is revoked";
92 }
93 if (key.isExpired()) {
94 qCWarning(MESSAGECOMPOSER_LOG) << "is expired";
95 }
96 if (key.isDisabled()) {
97 qCWarning(MESSAGECOMPOSER_LOG) << "is disabled";
98 }
99 if (!key.canEncrypt()) {
100 qCWarning(MESSAGECOMPOSER_LOG) << "can't encrypt";
101 }
102 if (key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt()) {
103 return false;
104 }
105 return true;
106}
107
108static bool ValidTrustedOpenPGPEncryptionKey(const GpgME::Key &key)
109{
110 if (!ValidOpenPGPEncryptionKey(key)) {
111 return false;
112 }
113 const std::vector<GpgME::UserID> uids = key.userIDs();
114 auto end(uids.end());
115 for (auto it = uids.begin(); it != end; ++it) {
116 if (!it->isRevoked() && it->validity() >= GpgME::UserID::Marginal) {
117 return true;
118 } else if (it->isRevoked()) {
119 qCWarning(MESSAGECOMPOSER_LOG) << "a userid is revoked";
120 } else {
121 qCWarning(MESSAGECOMPOSER_LOG) << "bad validity" << int(it->validity());
122 }
123 }
124 return false;
125}
126
127static bool ValidSMIMEEncryptionKey(const GpgME::Key &key)
128{
129 if (key.protocol() != GpgME::CMS) {
130 return false;
131 }
132 if (key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt()) {
133 return false;
134 }
135 return true;
136}
137
138static bool ValidTrustedSMIMEEncryptionKey(const GpgME::Key &key)
139{
140 if (!ValidSMIMEEncryptionKey(key)) {
141 return false;
142 }
143 return true;
144}
145
146static inline bool ValidTrustedEncryptionKey(const GpgME::Key &key)
147{
148 switch (key.protocol()) {
149 case GpgME::OpenPGP:
150 return ValidTrustedOpenPGPEncryptionKey(key);
151 case GpgME::CMS:
152 return ValidTrustedSMIMEEncryptionKey(key);
153 default:
154 return false;
155 }
156}
157
158static inline bool ValidEncryptionKey(const GpgME::Key &key)
159{
160 switch (key.protocol()) {
161 case GpgME::OpenPGP:
162 return ValidOpenPGPEncryptionKey(key);
163 case GpgME::CMS:
164 return ValidSMIMEEncryptionKey(key);
165 default:
166 return false;
167 }
168}
169
170static inline bool ValidSigningKey(const GpgME::Key &key)
171{
172 if (key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign()) {
173 return false;
174 }
175 return key.hasSecret();
176}
177
178static inline bool ValidOpenPGPSigningKey(const GpgME::Key &key)
179{
180 return key.protocol() == GpgME::OpenPGP && ValidSigningKey(key);
181}
182
183static inline bool ValidSMIMESigningKey(const GpgME::Key &key)
184{
185 return key.protocol() == GpgME::CMS && ValidSigningKey(key);
186}
187
188static inline bool NotValidTrustedOpenPGPEncryptionKey(const GpgME::Key &key)
189{
190 return !ValidTrustedOpenPGPEncryptionKey(key);
191}
192
193static inline bool NotValidTrustedSMIMEEncryptionKey(const GpgME::Key &key)
194{
195 return !ValidTrustedSMIMEEncryptionKey(key);
196}
197
198static inline bool NotValidTrustedEncryptionKey(const GpgME::Key &key)
199{
200 return !ValidTrustedEncryptionKey(key);
201}
202
203static inline bool NotValidEncryptionKey(const GpgME::Key &key)
204{
205 return !ValidEncryptionKey(key);
206}
207
208static inline bool NotValidOpenPGPSigningKey(const GpgME::Key &key)
209{
210 return !ValidOpenPGPSigningKey(key);
211}
212
213static inline bool NotValidSMIMESigningKey(const GpgME::Key &key)
214{
215 return !ValidSMIMESigningKey(key);
216}
217
218namespace
219{
220struct ByTrustScore {
221 static int score(const GpgME::UserID &uid)
222 {
223 return uid.isRevoked() || uid.isInvalid() ? -1 : uid.validity();
224 }
225
226 bool operator()(const GpgME::UserID &lhs, const GpgME::UserID &rhs) const
227 {
228 return score(lhs) < score(rhs);
229 }
230};
231}
232
233static std::vector<GpgME::UserID> matchingUIDs(const std::vector<GpgME::UserID> &uids, const QString &address)
234{
235 if (address.isEmpty()) {
236 return {};
237 }
238
239 std::vector<GpgME::UserID> result;
240 result.reserve(uids.size());
241
242 for (auto it = uids.begin(), end = uids.end(); it != end; ++it) {
243 // PENDING(marc) check DN for an EMAIL, too, in case of X.509 certs... :/
244 if (const char *email = it->email()) {
245 if (*email && QString::fromUtf8(email).simplified().toLower() == address) {
246 result.push_back(*it);
247 }
248 }
249 }
250 return result;
251}
252
253static GpgME::UserID findBestMatchUID(const GpgME::Key &key, const QString &address)
254{
255 const std::vector<GpgME::UserID> all = key.userIDs();
256 if (all.empty()) {
257 return {};
258 }
259 const std::vector<GpgME::UserID> matching = matchingUIDs(all, address.toLower());
260 const std::vector<GpgME::UserID> &v = matching.empty() ? all : matching;
261 return *std::max_element(v.begin(), v.end(), ByTrustScore());
262}
263
264static QStringList keysAsStrings(const std::vector<GpgME::Key> &keys)
265{
266 QStringList strings;
267 strings.reserve(keys.size());
268 for (auto it = keys.begin(); it != keys.end(); ++it) {
269 assert(!(*it).userID(0).isNull());
270 const auto userID = (*it).userID(0);
271 QString keyLabel = QString::fromUtf8(userID.email());
272 if (keyLabel.isEmpty()) {
273 keyLabel = QString::fromUtf8(userID.name());
274 }
275 if (keyLabel.isEmpty()) {
276 keyLabel = QString::fromUtf8(userID.id());
277 }
278 strings.append(keyLabel);
279 }
280 return strings;
281}
282
283static std::vector<GpgME::Key> trustedOrConfirmed(const std::vector<GpgME::Key> &keys, const QString &address, bool &canceled)
284{
285 // PENDING(marc) work on UserIDs here?
286 std::vector<GpgME::Key> fishies;
287 std::vector<GpgME::Key> ickies;
288 std::vector<GpgME::Key> rewookies;
289 auto it = keys.begin();
290 const auto end = keys.end();
291 for (; it != end; ++it) {
292 const GpgME::Key &key = *it;
293 assert(ValidEncryptionKey(key));
294 const GpgME::UserID uid = findBestMatchUID(key, address);
295 if (uid.isRevoked()) {
296 rewookies.push_back(key);
297 }
298 if (!uid.isRevoked()) {
299 if (uid.validity() == GpgME::UserID::Marginal) {
300 fishies.push_back(key);
301 }
302 if (uid.validity() < GpgME::UserID::Never) {
303 ickies.push_back(key);
304 }
305 }
306 }
307
308 if (fishies.empty() && ickies.empty() && rewookies.empty()) {
309 return keys;
310 }
311
312 // if some keys are not fully trusted, let the user confirm their use
313 QString msg = address.isEmpty() ? i18n(
314 "One or more of your configured OpenPGP encryption "
315 "keys or S/MIME certificates is not fully trusted "
316 "for encryption.")
317 : i18n(
318 "One or more of the OpenPGP encryption keys or S/MIME "
319 "certificates for recipient \"%1\" is not fully trusted "
320 "for encryption.",
321 address);
322
323 if (!fishies.empty()) {
324 // certificates can't have marginal trust
325 msg += i18n("\nThe following keys are only marginally trusted: \n");
326 msg += keysAsStrings(fishies).join(QLatin1Char(','));
327 }
328 if (!ickies.empty()) {
329 msg += i18n("\nThe following keys or certificates have unknown trust level: \n");
330 msg += keysAsStrings(ickies).join(QLatin1Char(','));
331 }
332 if (!rewookies.empty()) {
333 msg += i18n("\nThe following keys or certificates are <b>revoked</b>: \n");
334 msg += keysAsStrings(rewookies).join(QLatin1Char(','));
335 }
336
338 msg,
339 i18nc("@title:window", "Not Fully Trusted Encryption Keys"),
342 QStringLiteral("not fully trusted encryption key warning"))
344 return keys;
345 } else {
346 canceled = true;
347 }
348 return {};
349}
350
351namespace
352{
353struct IsNotForFormat : public std::function<bool(GpgME::Key)> {
354 IsNotForFormat(Kleo::CryptoMessageFormat f)
355 : format(f)
356 {
357 }
358
359 bool operator()(const GpgME::Key &key) const
360 {
361 return (isOpenPGP(format) && key.protocol() != GpgME::OpenPGP) || (isSMIME(format) && key.protocol() != GpgME::CMS);
362 }
363
364 const Kleo::CryptoMessageFormat format;
365};
366
367struct IsForFormat : std::function<bool(GpgME::Key)> {
368 explicit IsForFormat(Kleo::CryptoMessageFormat f)
369 : protocol(isOpenPGP(f) ? GpgME::OpenPGP
370 : isSMIME(f) ? GpgME::CMS
371 : GpgME::UnknownProtocol)
372 {
373 }
374
375 bool operator()(const GpgME::Key &key) const
376 {
377 return key.protocol() == protocol;
378 }
379
380 const GpgME::Protocol protocol;
381};
382}
383
384class KeyResolver::SigningPreferenceCounter : public std::function<void(KeyResolver::Item)>
385{
386public:
387 SigningPreferenceCounter() = default;
388
389 void operator()(const KeyResolver::Item &item);
390#define make_int_accessor(x) \
391 unsigned int num##x() const \
392 { \
393 return m##x; \
394 }
395 make_int_accessor(UnknownSigningPreference) make_int_accessor(NeverSign) make_int_accessor(AlwaysSign) make_int_accessor(AlwaysSignIfPossible)
396 make_int_accessor(AlwaysAskForSigning) make_int_accessor(AskSigningWheneverPossible) make_int_accessor(Total)
397#undef make_int_accessor
398 private : unsigned int mTotal = 0;
399 unsigned int mUnknownSigningPreference = 0;
400 unsigned int mNeverSign = 0;
401 unsigned int mAlwaysSign = 0;
402 unsigned int mAlwaysSignIfPossible = 0;
403 unsigned int mAlwaysAskForSigning = 0;
404 unsigned int mAskSigningWheneverPossible = 0;
405};
406
407void KeyResolver::SigningPreferenceCounter::operator()(const KeyResolver::Item &item)
408{
409 switch (item.signPref) {
410#define CASE(x) \
411 case x: \
412 ++m##x; \
413 break
414 CASE(UnknownSigningPreference);
415 CASE(NeverSign);
416 CASE(AlwaysSign);
417 CASE(AlwaysSignIfPossible);
418 CASE(AlwaysAskForSigning);
419 CASE(AskSigningWheneverPossible);
420#undef CASE
421 }
422 ++mTotal;
423}
424
425class KeyResolver::EncryptionPreferenceCounter : public std::function<void(Item)>
426{
427 const KeyResolver *_this;
428
429public:
430 EncryptionPreferenceCounter(const KeyResolver *kr, EncryptionPreference defaultPreference)
431 : _this(kr)
432 , mDefaultPreference(defaultPreference)
433 {
434 }
435
436 void operator()(Item &item);
437
438 template<typename Container>
439 void process(Container &c)
440 {
441 *this = std::for_each(c.begin(), c.end(), *this);
442 }
443
444#define make_int_accessor(x) \
445 unsigned int num##x() const \
446 { \
447 return m##x; \
448 }
449 make_int_accessor(NoKey) make_int_accessor(NeverEncrypt) make_int_accessor(UnknownPreference) make_int_accessor(AlwaysEncrypt)
450 make_int_accessor(AlwaysEncryptIfPossible) make_int_accessor(AlwaysAskForEncryption) make_int_accessor(AskWheneverPossible) make_int_accessor(Total)
451#undef make_int_accessor
452 private : EncryptionPreference mDefaultPreference;
453 unsigned int mTotal = 0;
454 unsigned int mNoKey = 0;
455 unsigned int mNeverEncrypt = 0;
456 unsigned int mUnknownPreference = 0;
457 unsigned int mAlwaysEncrypt = 0;
458 unsigned int mAlwaysEncryptIfPossible = 0;
459 unsigned int mAlwaysAskForEncryption = 0;
460 unsigned int mAskWheneverPossible = 0;
461};
462
463void KeyResolver::EncryptionPreferenceCounter::operator()(Item &item)
464{
465 if (_this) {
466 if (item.needKeys) {
467 item.keys = _this->getEncryptionKeys(item.address, true);
468 }
469 if (item.keys.empty()) {
470 ++mNoKey;
471 return;
472 }
473 }
474 switch (!item.pref ? mDefaultPreference : item.pref) {
475#define CASE(x) \
476 case Kleo::x: \
477 ++m##x; \
478 break
479 CASE(NeverEncrypt);
480 CASE(UnknownPreference);
481 CASE(AlwaysEncrypt);
482 CASE(AlwaysEncryptIfPossible);
483 CASE(AlwaysAskForEncryption);
484 CASE(AskWheneverPossible);
485#undef CASE
486 }
487 ++mTotal;
488}
489
490namespace
491{
492class FormatPreferenceCounterBase : public std::function<void(KeyResolver::Item)>
493{
494public:
495 FormatPreferenceCounterBase() = default;
496
497#define make_int_accessor(x) \
498 unsigned int num##x() const \
499 { \
500 return m##x; \
501 }
502 make_int_accessor(Total) make_int_accessor(InlineOpenPGP) make_int_accessor(OpenPGPMIME) make_int_accessor(SMIME) make_int_accessor(SMIMEOpaque)
503#undef make_int_accessor
504
505 [[nodiscard]] unsigned int numOf(Kleo::CryptoMessageFormat f) const
506 {
507 switch (f) {
508#define CASE(x) \
509 case Kleo::x##Format: \
510 return m##x
511 CASE(InlineOpenPGP);
512 CASE(OpenPGPMIME);
513 CASE(SMIME);
514 CASE(SMIMEOpaque);
515#undef CASE
516 default:
517 return 0;
518 }
519 }
520
521protected:
522 unsigned int mTotal = 0;
523 unsigned int mInlineOpenPGP = 0;
524 unsigned int mOpenPGPMIME = 0;
525 unsigned int mSMIME = 0;
526 unsigned int mSMIMEOpaque = 0;
527};
528
529class EncryptionFormatPreferenceCounter : public FormatPreferenceCounterBase
530{
531public:
532 EncryptionFormatPreferenceCounter() = default;
533
534 void operator()(const KeyResolver::Item &item);
535};
536
537class SigningFormatPreferenceCounter : public FormatPreferenceCounterBase
538{
539public:
540 SigningFormatPreferenceCounter() = default;
541
542 void operator()(const KeyResolver::Item &item);
543};
544
545#define CASE(x) \
546 if (item.format & Kleo::x##Format) { \
547 ++m##x; \
548 }
549void EncryptionFormatPreferenceCounter::operator()(const KeyResolver::Item &item)
550{
551 if (item.format & (Kleo::InlineOpenPGPFormat | Kleo::OpenPGPMIMEFormat)
552 && std::any_of(item.keys.begin(),
553 item.keys.end(),
554 ValidTrustedOpenPGPEncryptionKey)) { // -= trusted?
555 CASE(OpenPGPMIME);
556 CASE(InlineOpenPGP);
557 }
558 if (item.format & (Kleo::SMIMEFormat | Kleo::SMIMEOpaqueFormat)
559 && std::any_of(item.keys.begin(),
560 item.keys.end(),
561 ValidTrustedSMIMEEncryptionKey)) { // -= trusted?
562 CASE(SMIME);
563 CASE(SMIMEOpaque);
564 }
565 ++mTotal;
566}
567
568void SigningFormatPreferenceCounter::operator()(const KeyResolver::Item &item)
569{
570 CASE(InlineOpenPGP);
571 CASE(OpenPGPMIME);
572 CASE(SMIME);
573 CASE(SMIMEOpaque);
574 ++mTotal;
575}
576
577#undef CASE
578} // anon namespace
579
580static QString canonicalAddress(const QString &_address)
581{
583 if (!address.contains(QLatin1Char('@'))) {
584 // local address
585 // return address + '@' + KNetwork::KResolver::localHostName();
586 return address + QLatin1StringView("@localdomain");
587 } else {
588 return address;
589 }
590}
591
592struct FormatInfo {
593 std::vector<KeyResolver::SplitInfo> splitInfos;
594 std::vector<GpgME::Key> signKeys;
595};
596
597struct Q_DECL_HIDDEN MessageComposer::KeyResolver::KeyResolverPrivate {
598 bool mAkonadiLookupEnabled = true;
599 bool mAutocryptEnabled = false;
600 std::set<QByteArray> alreadyWarnedFingerprints;
601
602 std::vector<GpgME::Key> mOpenPGPSigningKeys; // signing
603 std::vector<GpgME::Key> mSMIMESigningKeys; // signing
604
605 std::vector<GpgME::Key> mOpenPGPEncryptToSelfKeys; // encryption to self
606 std::vector<GpgME::Key> mSMIMEEncryptToSelfKeys; // encryption to self
607
608 std::vector<Item> mPrimaryEncryptionKeys; // encryption to To/CC
609 std::vector<Item> mSecondaryEncryptionKeys; // encryption to BCC
610
611 std::map<CryptoMessageFormat, FormatInfo> mFormatInfoMap;
612
613 // key=email address, value=crypto preferences for this contact (from kabc)
614 using ContactPreferencesMap = std::map<QString, MessageComposer::ContactPreference>;
615 ContactPreferencesMap mContactPreferencesMap;
616 std::map<QByteArray, QString> mAutocryptMap;
617 std::shared_ptr<Kleo::ExpiryChecker> expiryChecker;
618};
619
620KeyResolver::KeyResolver(bool encToSelf, bool showApproval, bool oppEncryption, unsigned int f, const std::shared_ptr<Kleo::ExpiryChecker> &expiryChecker)
621 : d(new KeyResolverPrivate)
622 , mEncryptToSelf(encToSelf)
623 , mShowApprovalDialog(showApproval)
624 , mOpportunisticEncyption(oppEncryption)
625 , mCryptoMessageFormats(f)
626{
627 d->expiryChecker = expiryChecker;
628}
629
630KeyResolver::~KeyResolver() = default;
631
632ResolverResult KeyResolver::setEncryptToSelfKeys(const QStringList &fingerprints)
633{
634 if (!encryptToSelf()) {
635 return Ok;
636 }
637
638 std::vector<GpgME::Key> keys = lookup(fingerprints);
639 std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(d->mOpenPGPEncryptToSelfKeys),
640 NotValidTrustedOpenPGPEncryptionKey); // -= trusted?
641 std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(d->mSMIMEEncryptToSelfKeys),
642 NotValidTrustedSMIMEEncryptionKey); // -= trusted?
643
644 if (d->mOpenPGPEncryptToSelfKeys.size() + d->mSMIMEEncryptToSelfKeys.size() < keys.size()) {
645 // too few keys remain...
646 const QString msg = i18n(
647 "One or more of your configured OpenPGP encryption "
648 "keys or S/MIME certificates is not usable for "
649 "encryption. Please reconfigure your encryption keys "
650 "and certificates for this identity in the identity "
651 "configuration dialog.\n"
652 "If you choose to continue, and the keys are needed "
653 "later on, you will be prompted to specify the keys "
654 "to use.");
656 msg,
657 i18nc("@title:window", "Unusable Encryption Keys"),
660 QStringLiteral("unusable own encryption key warning"))
662 ? Ok
663 : Canceled;
664 }
665
666 // check for near-expiry:
667 std::vector<GpgME::Key>::const_iterator end(d->mOpenPGPEncryptToSelfKeys.end());
668
669 for (auto it = d->mOpenPGPEncryptToSelfKeys.begin(); it != end; ++it) {
670 d->expiryChecker->checkKey(*it, Kleo::ExpiryChecker::OwnEncryptionKey);
671 }
672 std::vector<GpgME::Key>::const_iterator end2(d->mSMIMEEncryptToSelfKeys.end());
673 for (auto it = d->mSMIMEEncryptToSelfKeys.begin(); it != end2; ++it) {
674 d->expiryChecker->checkKey(*it, Kleo::ExpiryChecker::OwnEncryptionKey);
675 }
676
677 return Ok;
678}
679
680ResolverResult KeyResolver::setSigningKeys(const QStringList &fingerprints)
681{
682 std::vector<GpgME::Key> keys = lookup(fingerprints, true); // secret keys
683 std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(d->mOpenPGPSigningKeys), NotValidOpenPGPSigningKey);
684 std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(d->mSMIMESigningKeys), NotValidSMIMESigningKey);
685
686 if (d->mOpenPGPSigningKeys.size() + d->mSMIMESigningKeys.size() < keys.size()) {
687 // too few keys remain...
688 const QString msg = i18n(
689 "One or more of your configured OpenPGP signing keys "
690 "or S/MIME signing certificates is not usable for "
691 "signing. Please reconfigure your signing keys "
692 "and certificates for this identity in the identity "
693 "configuration dialog.\n"
694 "If you choose to continue, and the keys are needed "
695 "later on, you will be prompted to specify the keys "
696 "to use.");
698 msg,
699 i18nc("@title:window", "Unusable Signing Keys"),
702 QStringLiteral("unusable signing key warning"))
704 ? Ok
705 : Canceled;
706 }
707
708 // check for near expiry:
709
710 for (auto it = d->mOpenPGPSigningKeys.begin(), total = d->mOpenPGPSigningKeys.end(); it != total; ++it) {
711 d->expiryChecker->checkKey(*it, Kleo::ExpiryChecker::OwnSigningKey);
712 }
713
714 for (auto it = d->mSMIMESigningKeys.begin(), total = d->mSMIMESigningKeys.end(); it != total; ++it) {
715 d->expiryChecker->checkKey(*it, Kleo::ExpiryChecker::OwnSigningKey);
716 }
717
718 return Ok;
719}
720
722{
723 d->mPrimaryEncryptionKeys = getEncryptionItems(addresses);
724}
725
727{
728 d->mSecondaryEncryptionKeys = getEncryptionItems(addresses);
729}
730
731std::vector<KeyResolver::Item> KeyResolver::getEncryptionItems(const QStringList &addresses)
732{
733 std::vector<Item> items;
734 items.reserve(addresses.size());
735 QStringList::const_iterator end(addresses.constEnd());
736 for (QStringList::const_iterator it = addresses.constBegin(); it != end; ++it) {
737 QString addr = canonicalAddress(*it).toLower();
738 const auto pref = lookupContactPreferences(addr);
739
740 items.emplace_back(*it, /*getEncryptionKeys( *it, true ),*/
741 pref.encryptionPreference,
742 pref.signingPreference,
743 pref.cryptoMessageFormat);
744 }
745 return items;
746}
747
748static Kleo::Action action(bool doit, bool ask, bool donot, bool requested)
749{
750 if (requested && !donot) {
751 return Kleo::DoIt;
752 }
753 if (doit && !ask && !donot) {
754 return Kleo::DoIt;
755 }
756 if (!doit && ask && !donot) {
757 return Kleo::Ask;
758 }
759 if (!doit && !ask && donot) {
760 return requested ? Kleo::Conflict : Kleo::DontDoIt;
761 }
762 if (!doit && !ask && !donot) {
763 return Kleo::DontDoIt;
764 }
765 return Kleo::Conflict;
766}
767
768Kleo::Action KeyResolver::checkSigningPreferences(bool signingRequested) const
769{
770 if (signingRequested && d->mOpenPGPSigningKeys.empty() && d->mSMIMESigningKeys.empty()) {
771 return Impossible;
772 }
773
774 SigningPreferenceCounter count;
775 count = std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), count);
776 count = std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), count);
777
778 unsigned int sign = count.numAlwaysSign();
779 unsigned int ask = count.numAlwaysAskForSigning();
780 const unsigned int dontSign = count.numNeverSign();
781 if (signingPossible()) {
782 sign += count.numAlwaysSignIfPossible();
783 ask += count.numAskSigningWheneverPossible();
784 }
785
786 return action(sign, ask, dontSign, signingRequested);
787}
788
789bool KeyResolver::signingPossible() const
790{
791 return !d->mOpenPGPSigningKeys.empty() || !d->mSMIMESigningKeys.empty();
792}
793
794Kleo::Action KeyResolver::checkEncryptionPreferences(bool encryptionRequested) const
795{
796 if (d->mPrimaryEncryptionKeys.empty() && d->mSecondaryEncryptionKeys.empty()) {
797 return DontDoIt;
798 }
799
800 if (encryptionRequested && encryptToSelf() && d->mOpenPGPEncryptToSelfKeys.empty() && d->mSMIMEEncryptToSelfKeys.empty()) {
801 return Impossible;
802 }
803
804 if (!encryptionRequested && !mOpportunisticEncyption) {
805 // try to minimize crypto ops (including key lookups) by only
806 // looking up keys when at least one of the encryption
807 // preferences needs it:
808 EncryptionPreferenceCounter count(nullptr, UnknownPreference);
809 count.process(d->mPrimaryEncryptionKeys);
810 count.process(d->mSecondaryEncryptionKeys);
811 if (!count.numAlwaysEncrypt()
812 && !count.numAlwaysAskForEncryption() // this guy might not need a lookup, when declined, but it's too complex to implement that here
813 && !count.numAlwaysEncryptIfPossible() && !count.numAskWheneverPossible()) {
814 return DontDoIt;
815 }
816 }
817
818 EncryptionPreferenceCounter count(this, mOpportunisticEncyption ? AskWheneverPossible : UnknownPreference);
819 count = std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), count);
820 count = std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), count);
821
822 unsigned int encrypt = count.numAlwaysEncrypt();
823 unsigned int ask = count.numAlwaysAskForEncryption();
824 const unsigned int dontEncrypt = count.numNeverEncrypt() + count.numNoKey();
825 if (encryptionPossible()) {
826 encrypt += count.numAlwaysEncryptIfPossible();
827 ask += count.numAskWheneverPossible();
828 }
829
830 const Action act = action(encrypt, ask, dontEncrypt, encryptionRequested);
831 if (act != Ask
832 || std::for_each(
833 d->mPrimaryEncryptionKeys.begin(),
834 d->mPrimaryEncryptionKeys.end(),
835 std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), EncryptionPreferenceCounter(this, UnknownPreference)))
836 .numAlwaysAskForEncryption()) {
837 return act;
838 } else {
839 return AskOpportunistic;
840 }
841}
842
843bool KeyResolver::encryptionPossible() const
844{
845 return std::none_of(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), EmptyKeyList)
846 && std::none_of(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), EmptyKeyList);
847}
848
849ResolverResult KeyResolver::resolveAllKeys(bool &signingRequested, bool &encryptionRequested)
850{
851 if (!encryptionRequested && !signingRequested) {
852 // make a dummy entry with all recipients, but no signing or
853 // encryption keys to avoid special-casing on the caller side:
854 dump();
855 d->mFormatInfoMap[OpenPGPMIMEFormat].splitInfos.emplace_back(allRecipients());
856 dump();
857 return Ok;
858 }
859 ResolverResult result = Ok;
860 if (encryptionRequested) {
861 bool finalySendUnencrypted = false;
862 result = resolveEncryptionKeys(signingRequested, finalySendUnencrypted);
863 if (finalySendUnencrypted) {
864 encryptionRequested = false;
865 }
866 }
867 if (result != Ok) {
868 return result;
869 }
870 if (encryptionRequested) {
871 result = resolveSigningKeysForEncryption();
872 } else {
873 result = resolveSigningKeysForSigningOnly();
874 if (result == Failure) {
875 signingRequested = false;
876 return Ok;
877 }
878 }
879 return result;
880}
881
882ResolverResult KeyResolver::resolveEncryptionKeys(bool signingRequested, bool &finalySendUnencrypted)
883{
884 //
885 // 1. Get keys for all recipients:
886 //
887 qCDebug(MESSAGECOMPOSER_LOG) << "resolving enc keys" << d->mPrimaryEncryptionKeys.size();
888 for (auto it = d->mPrimaryEncryptionKeys.begin(); it != d->mPrimaryEncryptionKeys.end(); ++it) {
889 qCDebug(MESSAGECOMPOSER_LOG) << "checking primary:" << it->address;
890 if (!it->needKeys) {
891 continue;
892 }
893 it->keys = getEncryptionKeys(it->address, false);
894 qCDebug(MESSAGECOMPOSER_LOG) << "got # keys:" << it->keys.size();
895 if (it->keys.empty()) {
896 return Canceled;
897 }
898 QString addr = canonicalAddress(it->address).toLower();
899 const auto pref = lookupContactPreferences(addr);
900 it->pref = pref.encryptionPreference;
901 it->signPref = pref.signingPreference;
902 it->format = pref.cryptoMessageFormat;
903 qCDebug(MESSAGECOMPOSER_LOG) << "set key data:" << int(it->pref) << int(it->signPref) << int(it->format);
904 }
905
906 for (auto it = d->mSecondaryEncryptionKeys.begin(), total = d->mSecondaryEncryptionKeys.end(); it != total; ++it) {
907 if (!it->needKeys) {
908 continue;
909 }
910 it->keys = getEncryptionKeys(it->address, false);
911 if (it->keys.empty()) {
912 return Canceled;
913 }
914 QString addr = canonicalAddress(it->address).toLower();
915 const auto pref = lookupContactPreferences(addr);
916 it->pref = pref.encryptionPreference;
917 it->signPref = pref.signingPreference;
918 it->format = pref.cryptoMessageFormat;
919 }
920
921 // 1a: Present them to the user
922
923 const ResolverResult res = showKeyApprovalDialog(finalySendUnencrypted);
924 if (res != Ok) {
925 return res;
926 }
927
928 //
929 // 2. Check what the primary recipients need
930 //
931
932 // 2a. Try to find a common format for all primary recipients,
933 // else use as many formats as needed
934
935 const EncryptionFormatPreferenceCounter primaryCount =
936 std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), EncryptionFormatPreferenceCounter());
937
938 CryptoMessageFormat commonFormat = AutoFormat;
939 for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
940 if (!(concreteCryptoMessageFormats[i] & mCryptoMessageFormats)) {
941 continue;
942 }
943 if (signingRequested && signingKeysFor(concreteCryptoMessageFormats[i]).empty()) {
944 continue;
945 }
946 if (encryptToSelf() && encryptToSelfKeysFor(concreteCryptoMessageFormats[i]).empty()) {
947 continue;
948 }
949 if (primaryCount.numOf(concreteCryptoMessageFormats[i]) == primaryCount.numTotal()) {
950 commonFormat = concreteCryptoMessageFormats[i];
951 break;
952 }
953 }
954 qCDebug(MESSAGECOMPOSER_LOG) << "got commonFormat for primary recipients:" << int(commonFormat);
955 if (commonFormat != AutoFormat) {
956 addKeys(d->mPrimaryEncryptionKeys, commonFormat);
957 } else {
958 addKeys(d->mPrimaryEncryptionKeys);
959 }
960
961 collapseAllSplitInfos(); // these can be encrypted together
962
963 // 2b. Just try to find _something_ for each secondary recipient,
964 // with a preference to a common format (if that exists)
965
966 const EncryptionFormatPreferenceCounter secondaryCount =
967 std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), EncryptionFormatPreferenceCounter());
968
969 if (commonFormat != AutoFormat && secondaryCount.numOf(commonFormat) == secondaryCount.numTotal()) {
970 addKeys(d->mSecondaryEncryptionKeys, commonFormat);
971 } else {
972 addKeys(d->mSecondaryEncryptionKeys);
973 }
974
975 // 3. Check for expiry:
976
977 for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
978 const std::vector<SplitInfo> si_list = encryptionItems(concreteCryptoMessageFormats[i]);
979 for (auto sit = si_list.begin(), total = si_list.end(); sit != total; ++sit) {
980 for (auto kit = sit->keys.begin(); kit != sit->keys.end(); ++kit) {
981 d->expiryChecker->checkKey(*kit, Kleo::ExpiryChecker::EncryptionKey);
982 }
983 }
984 }
985
986 // 4. Check that we have the right keys for encryptToSelf()
987
988 if (!encryptToSelf()) {
989 return Ok;
990 }
991
992 // 4a. Check for OpenPGP keys
993
994 qCDebug(MESSAGECOMPOSER_LOG) << "sizes of encryption items:" << encryptionItems(InlineOpenPGPFormat).size() << encryptionItems(OpenPGPMIMEFormat).size()
995 << encryptionItems(SMIMEFormat).size() << encryptionItems(SMIMEOpaqueFormat).size();
996 if (!encryptionItems(InlineOpenPGPFormat).empty() || !encryptionItems(OpenPGPMIMEFormat).empty()) {
997 // need them
998 if (d->mOpenPGPEncryptToSelfKeys.empty()) {
999 const QString msg = i18n(
1000 "Examination of recipient's encryption preferences "
1001 "yielded that the message should be encrypted using "
1002 "OpenPGP, at least for some recipients;\n"
1003 "however, you have not configured valid trusted "
1004 "OpenPGP encryption keys for this identity.\n"
1005 "You may continue without encrypting to yourself, "
1006 "but be aware that you will not be able to read your "
1007 "own messages if you do so.");
1009 msg,
1010 i18nc("@title:window", "Unusable Encryption Keys"),
1013 QStringLiteral("encrypt-to-self will fail warning"))
1015 return Canceled;
1016 }
1017 // FIXME: Allow selection
1018 }
1019 addToAllSplitInfos(d->mOpenPGPEncryptToSelfKeys, InlineOpenPGPFormat | OpenPGPMIMEFormat);
1020 }
1021
1022 // 4b. Check for S/MIME certs:
1023
1024 if (!encryptionItems(SMIMEFormat).empty() || !encryptionItems(SMIMEOpaqueFormat).empty()) {
1025 // need them
1026 if (d->mSMIMEEncryptToSelfKeys.empty()) {
1027 // don't have one
1028 const QString msg = i18n(
1029 "Examination of recipient's encryption preferences "
1030 "yielded that the message should be encrypted using "
1031 "S/MIME, at least for some recipients;\n"
1032 "however, you have not configured valid "
1033 "S/MIME encryption certificates for this identity.\n"
1034 "You may continue without encrypting to yourself, "
1035 "but be aware that you will not be able to read your "
1036 "own messages if you do so.");
1038 msg,
1039 i18nc("@title:window", "Unusable Encryption Keys"),
1042 QStringLiteral("encrypt-to-self will fail warning"))
1044 return Canceled;
1045 }
1046 // FIXME: Allow selection
1047 }
1048 addToAllSplitInfos(d->mSMIMEEncryptToSelfKeys, SMIMEFormat | SMIMEOpaqueFormat);
1049 }
1050
1051 // FIXME: Present another message if _both_ OpenPGP and S/MIME keys
1052 // are missing.
1053
1054 return Ok;
1055}
1056
1057ResolverResult KeyResolver::resolveSigningKeysForEncryption()
1058{
1059 if ((!encryptionItems(InlineOpenPGPFormat).empty() || !encryptionItems(OpenPGPMIMEFormat).empty()) && d->mOpenPGPSigningKeys.empty()) {
1060 const QString msg = i18n(
1061 "Examination of recipient's signing preferences "
1062 "yielded that the message should be signed using "
1063 "OpenPGP, at least for some recipients;\n"
1064 "however, you have not configured valid "
1065 "OpenPGP signing certificates for this identity.");
1067 msg,
1068 i18nc("@title:window", "Unusable Signing Keys"),
1069 KGuiItem(i18nc("@action:button", "Do Not OpenPGP-Sign")),
1071 QStringLiteral("signing will fail warning"))
1073 return Canceled;
1074 }
1075 // FIXME: Allow selection
1076 }
1077 if ((!encryptionItems(SMIMEFormat).empty() || !encryptionItems(SMIMEOpaqueFormat).empty()) && d->mSMIMESigningKeys.empty()) {
1078 const QString msg = i18n(
1079 "Examination of recipient's signing preferences "
1080 "yielded that the message should be signed using "
1081 "S/MIME, at least for some recipients;\n"
1082 "however, you have not configured valid "
1083 "S/MIME signing certificates for this identity.");
1085 msg,
1086 i18nc("@title:window", "Unusable Signing Keys"),
1087 KGuiItem(i18nc("@action:button", "Do Not S/MIME-Sign")),
1089 QStringLiteral("signing will fail warning"))
1091 return Canceled;
1092 }
1093 // FIXME: Allow selection
1094 }
1095
1096 // FIXME: Present another message if _both_ OpenPGP and S/MIME keys
1097 // are missing.
1098
1099 for (auto it = d->mFormatInfoMap.begin(); it != d->mFormatInfoMap.end(); ++it) {
1100 if (!it->second.splitInfos.empty()) {
1101 dump();
1102 it->second.signKeys = signingKeysFor(it->first);
1103 dump();
1104 }
1105 }
1106
1107 return Ok;
1108}
1109
1110ResolverResult KeyResolver::resolveSigningKeysForSigningOnly()
1111{
1112 //
1113 // we don't need to distinguish between primary and secondary
1114 // recipients here:
1115 //
1116 SigningFormatPreferenceCounter count;
1117 count = std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), count);
1118 count = std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), count);
1119
1120 // try to find a common format that works for all (and that we have signing keys for):
1121
1122 CryptoMessageFormat commonFormat = AutoFormat;
1123
1124 for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
1125 const auto res = concreteCryptoMessageFormats[i];
1126 if (!(mCryptoMessageFormats & res)) {
1127 continue; // skip
1128 }
1129 if (signingKeysFor(res).empty()) {
1130 continue; // skip
1131 }
1132 if (count.numOf(res) == count.numTotal()) {
1133 commonFormat = res;
1134 break;
1135 }
1136 }
1137
1138 if (commonFormat != AutoFormat) { // found
1139 dump();
1140 FormatInfo &fi = d->mFormatInfoMap[commonFormat];
1141 fi.signKeys = signingKeysFor(commonFormat);
1142 fi.splitInfos.resize(1);
1143 fi.splitInfos.front() = SplitInfo(allRecipients());
1144 dump();
1145 return Ok;
1146 }
1147
1148 const QString msg = i18n(
1149 "Examination of recipient's signing preferences "
1150 "showed no common type of signature matching your "
1151 "available signing keys.\n"
1152 "Send message without signing?");
1153 if (KMessageBox::warningContinueCancel(nullptr, msg, i18nc("@title:window", "No signing possible"), KStandardGuiItem::cont()) == KMessageBox::Continue) {
1154 d->mFormatInfoMap[OpenPGPMIMEFormat].splitInfos.emplace_back(allRecipients());
1155 return Failure; // means "Ok, but without signing"
1156 }
1157 return Canceled;
1158}
1159
1160std::vector<GpgME::Key> KeyResolver::signingKeysFor(CryptoMessageFormat f) const
1161{
1162 if (isOpenPGP(f)) {
1163 return d->mOpenPGPSigningKeys;
1164 }
1165 if (isSMIME(f)) {
1166 return d->mSMIMESigningKeys;
1167 }
1168 return {};
1169}
1170
1171std::vector<GpgME::Key> KeyResolver::encryptToSelfKeysFor(CryptoMessageFormat f) const
1172{
1173 if (isOpenPGP(f)) {
1174 return d->mOpenPGPEncryptToSelfKeys;
1175 }
1176 if (isSMIME(f)) {
1177 return d->mSMIMEEncryptToSelfKeys;
1178 }
1179 return {};
1180}
1181
1182QStringList KeyResolver::allRecipients() const
1183{
1185 std::transform(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), std::back_inserter(result), ItemDotAddress);
1186 std::transform(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), std::back_inserter(result), ItemDotAddress);
1187 return result;
1188}
1189
1190void KeyResolver::collapseAllSplitInfos()
1191{
1192 dump();
1193 for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
1194 auto pos = d->mFormatInfoMap.find(concreteCryptoMessageFormats[i]);
1195 if (pos == d->mFormatInfoMap.end()) {
1196 continue;
1197 }
1198 std::vector<SplitInfo> &v = pos->second.splitInfos;
1199 if (v.size() < 2) {
1200 continue;
1201 }
1202 SplitInfo &si = v.front();
1203 for (auto it = v.begin() + 1; it != v.end(); ++it) {
1204 si.keys.insert(si.keys.end(), it->keys.begin(), it->keys.end());
1205 std::copy(it->recipients.begin(), it->recipients.end(), std::back_inserter(si.recipients));
1206 }
1207 v.resize(1);
1208 }
1209 dump();
1210}
1211
1212void KeyResolver::addToAllSplitInfos(const std::vector<GpgME::Key> &keys, unsigned int f)
1213{
1214 dump();
1215 if (!f || keys.empty()) {
1216 return;
1217 }
1218 for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
1219 if (!(f & concreteCryptoMessageFormats[i])) {
1220 continue;
1221 }
1222 auto pos = d->mFormatInfoMap.find(concreteCryptoMessageFormats[i]);
1223 if (pos == d->mFormatInfoMap.end()) {
1224 continue;
1225 }
1226 std::vector<SplitInfo> &v = pos->second.splitInfos;
1227 for (auto it = v.begin(); it != v.end(); ++it) {
1228 it->keys.insert(it->keys.end(), keys.begin(), keys.end());
1229 }
1230 }
1231 dump();
1232}
1233
1234void KeyResolver::dump() const
1235{
1236#ifndef NDEBUG
1237 if (d->mFormatInfoMap.empty()) {
1238 qCDebug(MESSAGECOMPOSER_LOG) << "Keyresolver: Format info empty";
1239 }
1240 for (auto it = d->mFormatInfoMap.begin(); it != d->mFormatInfoMap.end(); ++it) {
1241 qCDebug(MESSAGECOMPOSER_LOG) << "Format info for " << Kleo::cryptoMessageFormatToString(it->first) << ": Signing keys: ";
1242 for (auto sit = it->second.signKeys.begin(); sit != it->second.signKeys.end(); ++sit) {
1243 qCDebug(MESSAGECOMPOSER_LOG) << " " << sit->shortKeyID() << " ";
1244 }
1245 unsigned int i = 0;
1246 for (auto sit = it->second.splitInfos.begin(), sitEnd = it->second.splitInfos.end(); sit != sitEnd; ++sit, ++i) {
1247 qCDebug(MESSAGECOMPOSER_LOG) << " SplitInfo #" << i << " encryption keys: ";
1248 for (auto kit = sit->keys.begin(), sitEnd = sit->keys.end(); kit != sitEnd; ++kit) {
1249 qCDebug(MESSAGECOMPOSER_LOG) << " " << kit->shortKeyID();
1250 }
1251 qCDebug(MESSAGECOMPOSER_LOG) << " SplitInfo #" << i << " recipients: " << qPrintable(sit->recipients.join(QLatin1StringView(", ")));
1252 }
1253 }
1254#endif
1255}
1256
1257ResolverResult KeyResolver::showKeyApprovalDialog(bool &finalySendUnencrypted)
1258{
1259 const bool showKeysForApproval = showApprovalDialog() || std::any_of(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), ApprovalNeeded)
1260 || std::any_of(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), ApprovalNeeded);
1261
1262 if (!showKeysForApproval) {
1263 return Ok;
1264 }
1265
1266 std::vector<Kleo::KeyApprovalDialog::Item> items;
1267 items.reserve(d->mPrimaryEncryptionKeys.size() + d->mSecondaryEncryptionKeys.size());
1268 std::copy(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), std::back_inserter(items));
1269 std::copy(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), std::back_inserter(items));
1270
1271 std::vector<GpgME::Key> senderKeys;
1272 senderKeys.reserve(d->mOpenPGPEncryptToSelfKeys.size() + d->mSMIMEEncryptToSelfKeys.size());
1273 std::copy(d->mOpenPGPEncryptToSelfKeys.begin(), d->mOpenPGPEncryptToSelfKeys.end(), std::back_inserter(senderKeys));
1274 std::copy(d->mSMIMEEncryptToSelfKeys.begin(), d->mSMIMEEncryptToSelfKeys.end(), std::back_inserter(senderKeys));
1275
1277
1278 QPointer<Kleo::KeyApprovalDialog> dlg = new Kleo::KeyApprovalDialog(items, senderKeys);
1279
1280 if (dlg->exec() == QDialog::Rejected) {
1281 delete dlg;
1282 return Canceled;
1283 }
1284
1285 items = dlg->items();
1286 senderKeys = dlg->senderKeys();
1287 const bool prefsChanged = dlg->preferencesChanged();
1288 delete dlg;
1289
1290 if (prefsChanged) {
1291 for (uint i = 0, total = items.size(); i < total; ++i) {
1292 auto pref = lookupContactPreferences(items[i].address);
1293 pref.encryptionPreference = items[i].pref;
1294 pref.pgpKeyFingerprints.clear();
1295 pref.smimeCertFingerprints.clear();
1296 const std::vector<GpgME::Key> &keys = items[i].keys;
1297 for (auto it = keys.begin(), end = keys.end(); it != end; ++it) {
1298 if (it->protocol() == GpgME::OpenPGP) {
1299 if (const char *fpr = it->primaryFingerprint()) {
1300 pref.pgpKeyFingerprints.push_back(QLatin1StringView(fpr));
1301 }
1302 } else if (it->protocol() == GpgME::CMS) {
1303 if (const char *fpr = it->primaryFingerprint()) {
1304 pref.smimeCertFingerprints.push_back(QLatin1StringView(fpr));
1305 }
1306 }
1307 }
1308 saveContactPreference(items[i].address, pref);
1309 }
1310 }
1311
1312 // show a warning if the user didn't select an encryption key for
1313 // herself:
1314 if (encryptToSelf() && senderKeys.empty()) {
1315 const QString msg = i18n(
1316 "You did not select an encryption key for yourself "
1317 "(encrypt to self). You will not be able to decrypt "
1318 "your own message if you encrypt it.");
1319 if (KMessageBox::warningContinueCancel(nullptr, msg, i18nc("@title:window", "Missing Key Warning"), KGuiItem(i18nc("@action:button", "&Encrypt")))
1321 return Canceled;
1322 } else {
1323 mEncryptToSelf = false;
1324 }
1325 }
1326
1327 // count empty key ID lists
1328 const unsigned int emptyListCount = std::count_if(items.begin(), items.end(), EmptyKeyList);
1329
1330 // show a warning if the user didn't select an encryption key for
1331 // some of the recipients
1332 if (items.size() == emptyListCount) {
1333 const QString msg = (d->mPrimaryEncryptionKeys.size() + d->mSecondaryEncryptionKeys.size() == 1)
1334 ? i18n(
1335 "You did not select an encryption key for the "
1336 "recipient of this message; therefore, the message "
1337 "will not be encrypted.")
1338 : i18n(
1339 "You did not select an encryption key for any of the "
1340 "recipients of this message; therefore, the message "
1341 "will not be encrypted.");
1343 msg,
1344 i18nc("@title:window", "Missing Key Warning"),
1345 KGuiItem(i18nc("@action:button", "Send &Unencrypted")))
1347 return Canceled;
1348 }
1349 finalySendUnencrypted = true;
1350 } else if (emptyListCount > 0) {
1351 const QString msg = (emptyListCount == 1) ? i18n(
1352 "You did not select an encryption key for one of "
1353 "the recipients: this person will not be able to "
1354 "decrypt the message if you encrypt it.")
1355 : i18n(
1356 "You did not select encryption keys for some of "
1357 "the recipients: these persons will not be able to "
1358 "decrypt the message if you encrypt it.");
1360 if (KMessageBox::warningContinueCancel(nullptr, msg, i18nc("@title:window", "Missing Key Warning"), KGuiItem(i18nc("@action:button", "&Encrypt")))
1362 return Canceled;
1363 }
1364 }
1365
1366 std::transform(d->mPrimaryEncryptionKeys.begin(),
1367 d->mPrimaryEncryptionKeys.end(),
1368 items.begin(),
1369 d->mPrimaryEncryptionKeys.begin(),
1370 CopyKeysAndEncryptionPreferences);
1371 std::transform(d->mSecondaryEncryptionKeys.begin(),
1372 d->mSecondaryEncryptionKeys.end(),
1373 items.begin() + d->mPrimaryEncryptionKeys.size(),
1374 d->mSecondaryEncryptionKeys.begin(),
1375 CopyKeysAndEncryptionPreferences);
1376
1377 d->mOpenPGPEncryptToSelfKeys.clear();
1378 d->mSMIMEEncryptToSelfKeys.clear();
1379
1380 std::remove_copy_if(senderKeys.begin(),
1381 senderKeys.end(),
1382 std::back_inserter(d->mOpenPGPEncryptToSelfKeys),
1383 NotValidTrustedOpenPGPEncryptionKey); // -= trusted (see above, too)?
1384 std::remove_copy_if(senderKeys.begin(),
1385 senderKeys.end(),
1386 std::back_inserter(d->mSMIMEEncryptToSelfKeys),
1387 NotValidTrustedSMIMEEncryptionKey); // -= trusted (see above, too)?
1388
1389 return Ok;
1390}
1391
1392std::vector<KeyResolver::SplitInfo> KeyResolver::encryptionItems(Kleo::CryptoMessageFormat f) const
1393{
1394 dump();
1395 auto it = d->mFormatInfoMap.find(f);
1396 return it != d->mFormatInfoMap.end() ? it->second.splitInfos : std::vector<SplitInfo>();
1397}
1398
1399void KeyResolver::setAutocryptEnabled(bool autocryptEnabled)
1400{
1401 d->mAutocryptEnabled = autocryptEnabled;
1402}
1403
1404std::map<QByteArray, QString> KeyResolver::useAutocrypt() const
1405{
1406 return d->mAutocryptMap;
1407}
1408
1409void KeyResolver::setAkonadiLookupEnabled(bool akonadiLoopkupEnabled)
1410{
1411 d->mAkonadiLookupEnabled = akonadiLoopkupEnabled;
1412}
1413
1414std::vector<GpgME::Key> KeyResolver::signingKeys(CryptoMessageFormat f) const
1415{
1416 dump();
1417 auto it = d->mFormatInfoMap.find(f);
1418 return it != d->mFormatInfoMap.end() ? it->second.signKeys : std::vector<GpgME::Key>();
1419}
1420
1421//
1422//
1423// KeyResolverPrivate helper methods below:
1424//
1425//
1426
1427std::vector<GpgME::Key> KeyResolver::selectKeys(const QString &person, const QString &msg, const std::vector<GpgME::Key> &selectedKeys) const
1428{
1429 const bool opgp = containsOpenPGP(mCryptoMessageFormats);
1430 const bool x509 = containsSMIME(mCryptoMessageFormats);
1431
1432 QPointer<Kleo::KeySelectionDialog> dlg = new Kleo::KeySelectionDialog(
1433 i18n("Encryption Key Selection"),
1434 msg,
1436 selectedKeys,
1437 Kleo::KeySelectionDialog::ValidEncryptionKeys & ~(opgp ? 0 : Kleo::KeySelectionDialog::OpenPGPKeys) & ~(x509 ? 0 : Kleo::KeySelectionDialog::SMIMEKeys),
1438 true,
1439 true); // multi-selection and "remember choice" box
1440
1441 if (dlg->exec() != QDialog::Accepted) {
1442 delete dlg;
1443 return {};
1444 }
1445
1446 std::vector<GpgME::Key> keys = dlg->selectedKeys();
1447 keys.erase(std::remove_if(keys.begin(), keys.end(), NotValidEncryptionKey), keys.end());
1448 if (!keys.empty() && dlg->rememberSelection()) {
1449 setKeysForAddress(person, dlg->pgpKeyFingerprints(), dlg->smimeFingerprints());
1450 }
1451
1452 delete dlg;
1453 return keys;
1454}
1455
1456std::vector<GpgME::Key> KeyResolver::getEncryptionKeys(const QString &person, bool quiet) const
1457{
1458 const QString address = canonicalAddress(person).toLower();
1459
1460 // First look for this person's address in the address->key dictionary
1461 const QStringList fingerprints = keysForAddress(address);
1462
1463 if (!fingerprints.empty()) {
1464 qCDebug(MESSAGECOMPOSER_LOG) << "Using encryption keys 0x" << fingerprints.join(QLatin1StringView(", 0x")) << "for" << person;
1465 std::vector<GpgME::Key> keys = lookup(fingerprints);
1466 if (!keys.empty()) {
1467 // Check if all of the keys are trusted and valid encryption keys
1468 if (std::any_of(keys.begin(), keys.end(),
1469 NotValidTrustedEncryptionKey)) { // -= trusted?
1470 // not ok, let the user select: this is not conditional on !quiet,
1471 // since it's a bug in the configuration and the user should be
1472 // notified about it as early as possible:
1473 keys = selectKeys(person,
1474 i18nc("if in your language something like "
1475 "'certificate(s)' is not possible please "
1476 "use the plural in the translation",
1477 "There is a problem with the "
1478 "encryption certificate(s) for \"%1\".\n\n"
1479 "Please re-select the certificate(s) which should "
1480 "be used for this recipient.",
1481 person),
1482 keys);
1483 }
1484 bool canceled = false;
1485 keys = trustedOrConfirmed(keys, address, canceled);
1486 if (canceled) {
1487 return {};
1488 }
1489
1490 if (!keys.empty()) {
1491 return keys;
1492 }
1493 // keys.empty() is considered cancel by callers, so go on
1494 }
1495 }
1496
1497 // Now search all public keys for matching keys
1498 std::vector<GpgME::Key> matchingKeys = lookup(QStringList(address));
1499 matchingKeys.erase(std::remove_if(matchingKeys.begin(), matchingKeys.end(), NotValidEncryptionKey), matchingKeys.end());
1500
1501 if (matchingKeys.empty() && d->mAutocryptEnabled) {
1502 qCDebug(MESSAGECOMPOSER_LOG) << "Search in Autocrypt storage a key for " << address;
1503 const auto storage = MessageCore::AutocryptStorage::self();
1504 const auto recipient = storage->getRecipient(address.toUtf8());
1505 if (recipient) {
1506 const auto key = recipient->gpgKey();
1507 if (!key.isNull() && ValidEncryptionKey(key)) {
1508 qCDebug(MESSAGECOMPOSER_LOG) << "Found an valid autocrypt key.";
1509 matchingKeys.push_back(key);
1510 } else {
1511 const auto gossipKey = recipient->gossipKey();
1512 if (!gossipKey.isNull() && ValidEncryptionKey(gossipKey)) {
1513 qCDebug(MESSAGECOMPOSER_LOG) << "Found an valid autocrypt gossip key.";
1514 matchingKeys.push_back(gossipKey);
1515 }
1516 }
1517 }
1518 // Accept any autocrypt key, as the validility is not used in Autocrypt.
1519 if (matchingKeys.size() == 1) {
1520 if (recipient->prefer_encrypt()) {
1521 d->mContactPreferencesMap[address].encryptionPreference = AlwaysEncryptIfPossible;
1522 }
1523 d->mAutocryptMap[matchingKeys[0].primaryFingerprint()] = address;
1524 return matchingKeys;
1525 }
1526 }
1527
1528 // if called with quite == true (from EncryptionPreferenceCounter), we only want to
1529 // check if there are keys for this recipients, not (yet) their validity, so
1530 // don't show the untrusted encryption key warning in that case
1531 bool canceled = false;
1532 if (!quiet) {
1533 matchingKeys = trustedOrConfirmed(matchingKeys, address, canceled);
1534 }
1535 if (canceled) {
1536 return {};
1537 }
1538 if (quiet || matchingKeys.size() == 1) {
1539 return matchingKeys;
1540 }
1541
1542 // no match until now, or more than one key matches; let the user
1543 // choose the key(s)
1544 // FIXME: let user get the key from keyserver
1545 return trustedOrConfirmed(selectKeys(person,
1546 matchingKeys.empty() ? i18nc("if in your language something like "
1547 "'certificate(s)' is not possible please "
1548 "use the plural in the translation",
1549 "<qt>No valid and trusted encryption certificate was "
1550 "found for \"%1\".<br/><br/>"
1551 "Select the certificate(s) which should "
1552 "be used for this recipient. If there is no suitable certificate in the list "
1553 "you can also search for external certificates by clicking the button: "
1554 "search for external certificates.</qt>",
1555 person.toHtmlEscaped())
1556 : i18nc("if in your language something like "
1557 "'certificate(s)' is not possible please "
1558 "use the plural in the translation",
1559 "More than one certificate matches \"%1\".\n\n"
1560 "Select the certificate(s) which should "
1561 "be used for this recipient.",
1562 person.toHtmlEscaped()),
1563 matchingKeys),
1564 address,
1565 canceled);
1566 // we can ignore 'canceled' here, since trustedOrConfirmed() returns
1567 // an empty vector when canceled == true, and we'd just do the same
1568}
1569
1570std::vector<GpgME::Key> KeyResolver::lookup(const QStringList &patterns, bool secret) const
1571{
1572 if (patterns.empty()) {
1573 return {};
1574 }
1575 qCDebug(MESSAGECOMPOSER_LOG) << "( \"" << patterns.join(QLatin1StringView("\", \"")) << "\"," << secret << ")";
1576 std::vector<GpgME::Key> result;
1577 if (mCryptoMessageFormats & (InlineOpenPGPFormat | OpenPGPMIMEFormat)) {
1578 if (const QGpgME::Protocol *p = QGpgME::openpgp()) {
1579 std::unique_ptr<QGpgME::KeyListJob> job(p->keyListJob(false, false, true)); // use validating keylisting
1580 if (job.get()) {
1581 std::vector<GpgME::Key> keys;
1582 job->exec(patterns, secret, keys);
1583 result.insert(result.end(), keys.begin(), keys.end());
1584 }
1585 }
1586 }
1587 if (mCryptoMessageFormats & (SMIMEFormat | SMIMEOpaqueFormat)) {
1588 if (const QGpgME::Protocol *p = QGpgME::smime()) {
1589 std::unique_ptr<QGpgME::KeyListJob> job(p->keyListJob(false, false, true)); // use validating keylisting
1590 if (job.get()) {
1591 std::vector<GpgME::Key> keys;
1592 job->exec(patterns, secret, keys);
1593 result.insert(result.end(), keys.begin(), keys.end());
1594 }
1595 }
1596 }
1597 qCDebug(MESSAGECOMPOSER_LOG) << " returned" << result.size() << "keys";
1598 return result;
1599}
1600
1601void KeyResolver::addKeys(const std::vector<Item> &items, CryptoMessageFormat f)
1602{
1603 dump();
1604 for (auto it = items.begin(); it != items.end(); ++it) {
1605 SplitInfo si(QStringList(it->address));
1606 std::remove_copy_if(it->keys.begin(), it->keys.end(), std::back_inserter(si.keys), IsNotForFormat(f));
1607 dump();
1608 if (si.keys.empty()) {
1609 qCWarning(MESSAGECOMPOSER_LOG) << "Kleo::KeyResolver::addKeys(): Fix EncryptionFormatPreferenceCounter."
1610 << "It detected a common format, but the list of such keys for recipient \"" << it->address << "\" is empty!";
1611 }
1612 d->mFormatInfoMap[f].splitInfos.push_back(si);
1613 }
1614 dump();
1615}
1616
1617void KeyResolver::addKeys(const std::vector<Item> &items)
1618{
1619 dump();
1620 for (auto it = items.begin(); it != items.end(); ++it) {
1621 SplitInfo si(QStringList(it->address));
1622 CryptoMessageFormat f = AutoFormat;
1623 for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
1624 const CryptoMessageFormat fmt = concreteCryptoMessageFormats[i];
1625 if ((fmt & it->format) && std::any_of(it->keys.begin(), it->keys.end(), IsForFormat(fmt))) {
1626 f = fmt;
1627 break;
1628 }
1629 }
1630 if (f == AutoFormat) {
1631 qCWarning(MESSAGECOMPOSER_LOG) << "Something went wrong. Didn't find a format for \"" << it->address << "\"";
1632 } else {
1633 std::remove_copy_if(it->keys.begin(), it->keys.end(), std::back_inserter(si.keys), IsNotForFormat(f));
1634 }
1635 d->mFormatInfoMap[f].splitInfos.push_back(si);
1636 }
1637 dump();
1638}
1639
1640MessageComposer::ContactPreference KeyResolver::lookupContactPreferences(const QString &address) const
1641{
1642 const auto it = d->mContactPreferencesMap.find(address);
1643 if (it != d->mContactPreferencesMap.end()) {
1644 return it->second;
1645 }
1646
1647 MessageComposer::ContactPreference pref;
1648
1649 if (!d->mAkonadiLookupEnabled) {
1650 return pref;
1651 }
1652
1653 auto job = new Akonadi::ContactSearchJob();
1654 job->setLimit(1);
1655 job->setQuery(Akonadi::ContactSearchJob::Email, address);
1656 job->exec();
1657
1658 const KContacts::Addressee::List res = job->contacts();
1659 if (!res.isEmpty()) {
1660 KContacts::Addressee addr = res.at(0);
1661 pref.fillFromAddressee(addr);
1662 }
1663
1664 const_cast<KeyResolver *>(this)->setContactPreferences(address, pref);
1665
1666 return pref;
1667}
1668
1669void KeyResolver::setContactPreferences(const QString &address, const MessageComposer::ContactPreference &pref)
1670{
1671 d->mContactPreferencesMap.insert(std::make_pair(address, pref));
1672}
1673
1674void KeyResolver::saveContactPreference(const QString &email, const MessageComposer::ContactPreference &pref) const
1675{
1676 d->mContactPreferencesMap.insert(std::make_pair(email, pref));
1677 auto saveContactPreferencesJob = new MessageComposer::SaveContactPreferenceJob(email, pref);
1678 saveContactPreferencesJob->start();
1679}
1680
1681QStringList KeyResolver::keysForAddress(const QString &address) const
1682{
1683 if (address.isEmpty()) {
1684 return {};
1685 }
1686 const QString addr = canonicalAddress(address).toLower();
1687 const auto pref = lookupContactPreferences(addr);
1688 return pref.pgpKeyFingerprints + pref.smimeCertFingerprints;
1689}
1690
1691void KeyResolver::setKeysForAddress(const QString &address, const QStringList &pgpKeyFingerprints, const QStringList &smimeCertFingerprints) const
1692{
1693 if (address.isEmpty()) {
1694 return;
1695 }
1696 const QString addr = canonicalAddress(address).toLower();
1697 auto pref = lookupContactPreferences(addr);
1698 pref.pgpKeyFingerprints = pgpKeyFingerprints;
1699 pref.smimeCertFingerprints = smimeCertFingerprints;
1700 saveContactPreference(addr, pref);
1701}
1702
1703bool KeyResolver::encryptToSelf() const
1704{
1705 return mEncryptToSelf;
1706}
1707
1708bool KeyResolver::showApprovalDialog() const
1709{
1710 return mShowApprovalDialog;
1711}
AddresseeList List
KeyResolver(bool encrypt, bool sign, GpgME::Protocol protocol=GpgME::UnknownProtocol, bool allowMixed=true)
void setSigningKeys(const QStringList &fingerprints)
Solution result() const
void setContactPreferences(const QString &address, const ContactPreference &preference)
Sets crypto preferences for given email address.
void setPrimaryRecipients(const QStringList &addresses)
Set the list of primary (To/CC) recipient addresses.
std::vector< GpgME::Key > signingKeys(Kleo::CryptoMessageFormat f) const
void setAkonadiLookupEnabled(bool akonadiLookupEnabled)
Disable ContactSearchJob in KeyResolver.
ResolverResult setEncryptToSelfKeys(const QStringList &fingerprints)
Set the fingerprints of keys to be used for encrypting to self.
ResolverResult resolveAllKeys(bool &signingRequested, bool &encryptionRequested)
Queries the user for missing keys and displays a key approval dialog if needed.
void setAutocryptEnabled(bool autocryptEnabled)
If Autocrypt keys are used to find valid PGP Keys.
void setSecondaryRecipients(const QStringList &addresses)
Set the list of secondary (BCC) recipient addresses.
Kleo::Action checkEncryptionPreferences(bool encryptionRequested) const
Determine whether to encrypt or not, depending on the per-recipient encryption preferences,...
std::vector< SplitInfo > encryptionItems(Kleo::CryptoMessageFormat f) const
Kleo::Action checkSigningPreferences(bool signingRequested) const
Determine whether to sign or not, depending on the per-recipient signing preferences,...
KCODECS_EXPORT QByteArray extractEmailAddress(const QByteArray &address)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
PostalAddress address(const QVariant &location)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
KGuiItem cont()
KGuiItem cancel()
const QList< QKeySequence > & end()
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
void append(QList< T > &&value)
const_iterator constBegin() const const
const_iterator constEnd() const const
bool empty() const const
void reserve(qsizetype size)
qsizetype size() const const
UnknownProtocol
const QChar at(qsizetype position) const const
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString toHtmlEscaped() const const
QString toLower() const const
QString join(QChar separator) const const
WaitCursor
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:59:12 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.