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

KDE's Doxygen guidelines are available online.